Merge branch 'stable-3.1'

* stable-3.1:
  Remove unused roles
  Move all the installs in one place
  Create stack to run load tests
  Better resource assignment for dual master
  Propagate master 1 logs to AWS CloudWatch
  Use nested CF template for Task Execution Role
  Bump up Gerrit version to 3.1.6
  Allow slave to run on multiple EC2 instances
  Retry more aggressively to mount EFS
  Don't fail if direcotory exists when starting up cluster

Change-Id: Ibd3e10f4e2c703e9cea8ff5cddf8c5c26c0d728e
diff --git a/Makefile.common b/Makefile.common
index 37ae390..e5bf82a 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -4,4 +4,8 @@
 
 cluster-keys:
 	aws ec2 describe-key-pairs --region $(AWS_REGION) --key-names $(CLUSTER_KEYS) > /dev/null 2>&1 || \
-		aws ec2 create-key-pair --region $(AWS_REGION) --key-name $(CLUSTER_KEYS) --query 'KeyMaterial' --output text > $(CLUSTER_KEYS).pem
\ No newline at end of file
+		aws ec2 create-key-pair --region $(AWS_REGION) --key-name $(CLUSTER_KEYS) --query 'KeyMaterial' --output text > $(CLUSTER_KEYS).pem
+
+upload-common-templates:
+	export AWS_PAGER=; aws s3api head-bucket --bucket $(TEMPLATE_BUCKET_NAME) 2>/dev/null || aws s3api create-bucket --bucket $(TEMPLATE_BUCKET_NAME)
+	aws s3 cp ../common-templates/cf-gerrit-task-execution-role.yml s3://$(TEMPLATE_BUCKET_NAME)/
diff --git a/common-templates/cf-gerrit-task-execution-role.yml b/common-templates/cf-gerrit-task-execution-role.yml
new file mode 100644
index 0000000..889111e
--- /dev/null
+++ b/common-templates/cf-gerrit-task-execution-role.yml
@@ -0,0 +1,45 @@
+
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+
+Resources:
+    # This is a role which is used by the ECS tasks themselves.
+    ECSTaskExecutionRole:
+      Type: AWS::IAM::Role
+      Properties:
+        AssumeRolePolicyDocument:
+          Statement:
+          - Effect: Allow
+            Principal:
+              Service: [ecs-tasks.amazonaws.com]
+            Action:
+              - sts:AssumeRole
+        Path: /
+        Policies:
+          - PolicyName: AmazonECSTaskExecutionRolePolicy
+            PolicyDocument:
+              Statement:
+              - Effect: Allow
+                Action:
+                  # Allow the ECS Tasks to download images from ECR
+                  - 'ecr:GetAuthorizationToken'
+                  - 'ecr:BatchCheckLayerAvailability'
+                  - 'ecr:GetDownloadUrlForLayer'
+                  - 'ecr:BatchGetImage'
+                  # Allow the ECS tasks to upload logs to CloudWatch
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: '*'
+          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
+            PolicyDocument:
+              Statement:
+              - Effect: Allow
+                Action:
+                  # Allow the ECS Tasks to get SSH Keys
+                  - 'secretsmanager:GetSecretValue'
+                  - 'kms:Decrypt'
+                Resource: '*'
+
+Outputs:
+  TaskExecutionRoleRef:
+    Value: !Ref ECSTaskExecutionRole
diff --git a/common.env b/common.env
index bc62bcd..0de0fd5 100644
--- a/common.env
+++ b/common.env
@@ -22,3 +22,6 @@
 SYSLOG_HEAD_SHA1=$(shell find $(SYSLOG_IMAGE_DIR) -type f -exec cat {} \; | shasum | cut -c 1-20)
 
 IMAGE_TAG=$(GERRIT_VERSION).$(GERRIT_PATCH)-$(HEAD_SHA1)
+
+# Nested templates bucket
+TEMPLATE_BUCKET_NAME=aws-gerrit-cf-templates
diff --git a/dual-master/Makefile b/dual-master/Makefile
index 6c0280a..e0032f0 100644
--- a/dual-master/Makefile
+++ b/dual-master/Makefile
@@ -15,7 +15,8 @@
 				service-lb wait-for-service-lb-deletion wait-for-service-lb-creation \
 				gerrit-build gerrit-publish haproxy-publish syslog-sidecar-publish
 
-create-all: gerrit-publish haproxy-publish syslog-sidecar-publish \
+create-all: upload-common-templates \
+						gerrit-publish haproxy-publish syslog-sidecar-publish \
 						cluster wait-for-cluster-creation \
 						service-slave service-master-1 \
 						wait-for-service-master-1-creation wait-for-service-slave-creation \
@@ -54,7 +55,7 @@
 		ParameterKey=GerritIndexVolume,ParameterValue=gerrit-index-master-1 \
 		ParameterKey=GerritCacheVolume,ParameterValue=gerrit-cache-master-1 \
 		ParameterKey=GerritDbVolume,ParameterValue=gerrit-db-master-1 \
-		ParameterKey=GerritLogsVolume,ParameterValue=gerrit-logs-master1 \
+		ParameterKey=GerritLogsVolume,ParameterValue=gerrit-logs-master-1 \
 		ParameterKey=PeerSubdomain,ParameterValue=$(MASTER2_SUBDOMAIN) \
 		ParameterKey=LBSubdomain,ParameterValue=$(LB_SUBDOMAIN) \
 		ParameterKey=SlaveServiceStackName,ParameterValue=$(SERVICE_SLAVE_STACK_NAME)
diff --git a/dual-master/cf-cluster.yml b/dual-master/cf-cluster.yml
index 771dd6a..dc8f02e 100644
--- a/dual-master/cf-cluster.yml
+++ b/dual-master/cf-cluster.yml
@@ -143,7 +143,7 @@
           echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
           # Make sure latest version of the helper scripts are installed as per recommendation:
           # https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-helper-scripts-reference.md#using-the-latest-version
-          yum install -y aws-cfn-bootstrap nfs-utils
+          yum install -y aws-cfn-bootstrap nfs-utils wget
           # EFS setting
           DIR_TGT=/mnt/efs/gerrit-shared
           mkdir -p $DIR_TGT
@@ -159,7 +159,7 @@
           echo $EC2_REGION >> /home/ec2-user/echo.res
           echo $DIR_SRC >> /home/ec2-user/echo.res
           echo $DIR_TGT >> /home/ec2-user/echo.res
-          MAX_RETRIES=5
+          MAX_RETRIES=20
           for i in $(seq 1 $MAX_RETRIES); do
             echo "Mounting EFS volume ($i/$MAX_RETRIES)..."
             `mount -t nfs4 -o nfsvers=4.1,hard,timeo=600,retrans=2 $DIR_SRC:/ $DIR_TGT >> /home/ec2-user/echo.res` \
@@ -203,29 +203,29 @@
                       \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-2/audit_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/replication_log\",
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-1/_data/replication_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
-                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/replication_log\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-1/replication_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/httpd_log\",
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-1/_data/httpd_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
-                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/httpd_log\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-1/httpd_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/sshd_log\",
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-1/_data/sshd_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
-                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/sshd_log\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-1/sshd_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/gc_log\",
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-1/_data/gc_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
-                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/gc_log\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-1/gc_log\",
                       \"timezone\": \"UTC\"
                       },
-                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/audit_log\",
+                      {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master-1/_data/audit_log\",
                       \"log_group_name\": \"${AWS::StackName}\",
-                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/audit_log\",
+                      \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master-1/audit_log\",
                       \"timezone\": \"UTC\"
                       }
                     ]
@@ -234,34 +234,12 @@
               }
             }" >> /home/ec2-user/gerritlogsaccess.json
           # Install the CloudWatch Logs agent
-          yum install -y wget
           wget https://s3.amazonaws.com/amazoncloudwatch-agent/centos/amd64/latest/amazon-cloudwatch-agent.rpm
           rpm -U ./amazon-cloudwatch-agent.rpm
           /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/home/ec2-user/gerritlogsaccess.json -s
           # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
           /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
-  AutoscalingRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Statement:
-        - Effect: Allow
-          Principal:
-            Service: [application-autoscaling.amazonaws.com]
-          Action: ['sts:AssumeRole']
-      Path: /
-      Policies:
-      - PolicyName: service-autoscaling
-        PolicyDocument:
-          Statement:
-          - Effect: Allow
-            Action:
-              - 'application-autoscaling:*'
-              - 'cloudwatch:DescribeAlarms'
-              - 'cloudwatch:PutMetricAlarm'
-              - 'ecs:DescribeServices'
-              - 'ecs:UpdateService'
-            Resource: '*'
+
   EC2InstanceProfile:
     Type: AWS::IAM::InstanceProfile
     Properties:
diff --git a/dual-master/cf-service-lb.yml b/dual-master/cf-service-lb.yml
index 16c0b81..c4d8acc 100644
--- a/dual-master/cf-service-lb.yml
+++ b/dual-master/cf-service-lb.yml
@@ -93,8 +93,8 @@
         Type: AWS::ECS::TaskDefinition
         Properties:
             Family: !Sub '${LBServiceName}TaskDefinition'
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            TaskRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
+            ExecutionRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
             NetworkMode: bridge
             ContainerDefinitions:
                 - Name: !Ref LBServiceName
@@ -135,41 +135,11 @@
                         awslogs-region: !Ref AWS::Region
                         awslogs-stream-prefix: !Ref EnvironmentName
 
-    # This is a role which is used by the ECS tasks themselves.
-    ECSTaskExecutionRole:
-      Type: AWS::IAM::Role
+    ECSTaskExecutionRoleStack:
+      Type: AWS::CloudFormation::Stack
       Properties:
-        AssumeRolePolicyDocument:
-          Statement:
-          - Effect: Allow
-            Principal:
-              Service: [ecs-tasks.amazonaws.com]
-            Action: ['sts:AssumeRole']
-        Path: /
-        Policies:
-          - PolicyName: AmazonECSTaskExecutionRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to download images from ECR
-                  - 'ecr:GetAuthorizationToken'
-                  - 'ecr:BatchCheckLayerAvailability'
-                  - 'ecr:GetDownloadUrlForLayer'
-                  - 'ecr:BatchGetImage'
-                  # Allow the ECS tasks to upload logs to CloudWatch
-                  - 'logs:CreateLogStream'
-                  - 'logs:PutLogEvents'
-                Resource: '*'
-          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to get SSH Keys
-                  - 'secretsmanager:GetSecretValue'
-                  - 'kms:Decrypt'
-                Resource: '*'
+        TemplateURL: https://aws-gerrit-cf-templates.s3.amazonaws.com/cf-gerrit-task-execution-role.yml
+        TimeoutInMinutes: '5'
 
     LoadBalancer:
         Type: AWS::ElasticLoadBalancingV2::LoadBalancer
diff --git a/dual-master/cf-service-master.yml b/dual-master/cf-service-master.yml
index 4b07bc1..1a4adaa 100644
--- a/dual-master/cf-service-master.yml
+++ b/dual-master/cf-service-master.yml
@@ -134,8 +134,8 @@
         Type: AWS::ECS::TaskDefinition
         Properties:
             Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            TaskRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
+            ExecutionRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
             NetworkMode: bridge
             ContainerDefinitions:
                 - Name: !Ref GerritServiceName
@@ -177,8 +177,8 @@
                       ContainerPath: /var/gerrit/logs
                     - SourceVolume: !Ref GerritEventsVolume
                       ContainerPath: /var/gerrit/events
-                  Cpu: 1024
-                  Memory: 2048
+                  Cpu: 3072
+                  Memory: 9216
                   PortMappings:
                     - ContainerPort: !Ref HTTPContainerPort
                       HostPort: !Ref HTTPHostPort
@@ -293,41 +293,11 @@
             Port: !Ref SSHHostPort
             Protocol: TCP
 
-    # This is a role which is used by the ECS tasks themselves.
-    ECSTaskExecutionRole:
-      Type: AWS::IAM::Role
+    ECSTaskExecutionRoleStack:
+      Type: AWS::CloudFormation::Stack
       Properties:
-        AssumeRolePolicyDocument:
-          Statement:
-          - Effect: Allow
-            Principal:
-              Service: [ecs-tasks.amazonaws.com]
-            Action: ['sts:AssumeRole']
-        Path: /
-        Policies:
-          - PolicyName: AmazonECSTaskExecutionRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to download images from ECR
-                  - 'ecr:GetAuthorizationToken'
-                  - 'ecr:BatchCheckLayerAvailability'
-                  - 'ecr:GetDownloadUrlForLayer'
-                  - 'ecr:BatchGetImage'
-                  # Allow the ECS tasks to upload logs to CloudWatch
-                  - 'logs:CreateLogStream'
-                  - 'logs:PutLogEvents'
-                Resource: '*'
-          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to get SSH Keys
-                  - 'secretsmanager:GetSecretValue'
-                  - 'kms:Decrypt'
-                Resource: '*'
+        TemplateURL: https://aws-gerrit-cf-templates.s3.amazonaws.com/cf-gerrit-task-execution-role.yml
+        TimeoutInMinutes: '5'
 
 Outputs:
   PublicLoadBalancerDNSName:
diff --git a/dual-master/cf-service-slave.yml b/dual-master/cf-service-slave.yml
index 7415836..f2cfe03 100644
--- a/dual-master/cf-service-slave.yml
+++ b/dual-master/cf-service-slave.yml
@@ -109,6 +109,8 @@
         DependsOn:
           - HTTPListener
           - SSHListener
+          - GitListener
+          - GitSSHListener
         Properties:
             Cluster:
               Fn::ImportValue:
@@ -122,19 +124,6 @@
                 - ContainerName: !Ref GerritServiceName
                   ContainerPort: !Ref SSHContainerPort
                   TargetGroupArn: !Ref SSHTargetGroup
-
-    GerritReplicationService:
-        Type: AWS::ECS::Service
-        DependsOn:
-          - GitListener
-          - GitSSHListener
-        Properties:
-            Cluster:
-              Fn::ImportValue:
-                  !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
-            DesiredCount: !Ref DesiredCount
-            TaskDefinition: !Ref GerritReplicationTaskDefinition
-            LoadBalancers:
                 - ContainerName: !Ref GitDaemonServiceName
                   ContainerPort: !Ref GitPort
                   TargetGroupArn: !Ref GitTargetGroup
@@ -146,8 +135,8 @@
         Type: AWS::ECS::TaskDefinition
         Properties:
             Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            TaskRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
+            ExecutionRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
             NetworkMode: bridge
             ContainerDefinitions:
                 - Name: !Ref GerritServiceName
@@ -175,8 +164,8 @@
                       ContainerPath: /var/gerrit/db
                     - SourceVolume: !Ref GerritLogsVolume
                       ContainerPath: /var/gerrit/logs
-                  Cpu: 1024
-                  Memory: 2048
+                  Cpu: 3072
+                  Memory: 9216
                   PortMappings:
                     - ContainerPort: !Ref HTTPContainePort
                       HostPort: !Ref HTTPHostPort
@@ -190,51 +179,6 @@
                         awslogs-group: !Ref ClusterStackName
                         awslogs-region: !Ref AWS::Region
                         awslogs-stream-prefix: !Ref EnvironmentName
-            Volumes:
-              - Name: !Ref 'GerritDbVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
-              - Name: !Ref 'GerritGitVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
-              - Name: !Ref 'GerritDataVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
-              - Name: !Ref 'GerritCacheVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
-              - Name: !Ref 'GerritLogsVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
-
-    GerritReplicationTaskDefinition:
-        Type: AWS::ECS::TaskDefinition
-        Properties:
-            Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
-            NetworkMode: bridge
-            ContainerDefinitions:
                 - Name: !Ref GitDaemonServiceName
                   Essential: true
                   Image: !Sub '${DockerRegistryUrl}/${GitDaemonDockerImage}'
@@ -281,6 +225,13 @@
                         awslogs-region: !Ref AWS::Region
                         awslogs-stream-prefix: !Ref EnvironmentName
             Volumes:
+              - Name: !Ref 'GerritDbVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
               - Name: !Ref 'GerritGitVolume'
                 DockerVolumeConfiguration:
                   Scope: shared
@@ -288,6 +239,27 @@
                   Driver: local
                   Labels:
                     gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
+              - Name: !Ref 'GerritDataVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
+              - Name: !Ref 'GerritCacheVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
+              - Name: !Ref 'GerritLogsVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
 
     LoadBalancer:
         Type: AWS::ElasticLoadBalancingV2::LoadBalancer
@@ -310,7 +282,7 @@
                   !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
             Port: !Ref HTTPHostPort
             Protocol: TCP
-            HealthCheckPort: !Ref HTTPContainePort
+            HealthCheckPort: !Ref HTTPHostPort
 
     HTTPListener:
         Type: AWS::ElasticLoadBalancingV2::Listener
@@ -334,7 +306,7 @@
                   !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
             Port: !Ref SSHHostPort
             Protocol: TCP
-            HealthCheckPort: !Ref SSHContainerPort
+            HealthCheckPort: !Ref SSHHostPort
 
     SSHListener:
         Type: AWS::ElasticLoadBalancingV2::Listener
@@ -401,41 +373,12 @@
             HostedZoneId: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
             EvaluateTargetHealth: False
 
-    # This is a role which is used by the ECS tasks themselves.
-    ECSTaskExecutionRole:
-      Type: AWS::IAM::Role
+    ECSTaskExecutionRoleStack:
+      Type: AWS::CloudFormation::Stack
       Properties:
-        AssumeRolePolicyDocument:
-          Statement:
-          - Effect: Allow
-            Principal:
-              Service: [ecs-tasks.amazonaws.com]
-            Action: ['sts:AssumeRole']
-        Path: /
-        Policies:
-          - PolicyName: AmazonECSTaskExecutionRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to download images from ECR
-                  - 'ecr:GetAuthorizationToken'
-                  - 'ecr:BatchCheckLayerAvailability'
-                  - 'ecr:GetDownloadUrlForLayer'
-                  - 'ecr:BatchGetImage'
-                  # Allow the ECS tasks to upload logs to CloudWatch
-                  - 'logs:CreateLogStream'
-                  - 'logs:PutLogEvents'
-                Resource: '*'
-          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to get SSH Keys
-                  - 'secretsmanager:GetSecretValue'
-                  - 'kms:Decrypt'
-                Resource: '*'
+        TemplateURL: https://aws-gerrit-cf-templates.s3.amazonaws.com/cf-gerrit-task-execution-role.yml
+        TimeoutInMinutes: '5'
+
 Outputs:
   PublicLoadBalancerDNSName:
     Description: The DNS name of the external load balancer
diff --git a/gerrit/entrypoint.sh b/gerrit/entrypoint.sh
index 78f0b84..4ceb995 100755
--- a/gerrit/entrypoint.sh
+++ b/gerrit/entrypoint.sh
@@ -7,8 +7,19 @@
 
 if [ $CONTAINER_SLAVE ]; then
   echo "Slave mode..."
+
+  if [ ! -d /var/gerrit/git/All-Projects.git ] ||
+     [ ! -d /var/gerrit/git/All-Users.git ] ||
+     [ `git --git-dir=/var/gerrit/git/All-Projects.git show-ref | wc -l` -eq 0 ] ||
+     [ `git --git-dir=/var/gerrit/git/All-Users.git show-ref | wc -l` -eq 0 ]; then
+     echo "Init phase..."
+     java -jar /var/gerrit/bin/gerrit.war init --no-auto-start --batch --install-all-plugins -d /var/gerrit
+  else
+    echo "Reindexing phase..."
+    java -jar /var/gerrit/bin/gerrit.war reindex --index groups
+  fi
   rm -fr /var/gerrit/plugins/replication.jar
-  java -jar /var/gerrit/bin/gerrit.war reindex --index groups
+
 else
   echo "Master mode (init phase)..."
   java -jar /var/gerrit/bin/gerrit.war init --no-auto-start --batch --install-all-plugins -d /var/gerrit
diff --git a/load-test-fleet/Makefile b/load-test-fleet/Makefile
new file mode 100644
index 0000000..6ebdd6c
--- /dev/null
+++ b/load-test-fleet/Makefile
@@ -0,0 +1,26 @@
+include ../Makefile.common
+
+LOAD_TEST_TEMPLATE:=cf-load-test-workers.yml
+AWS_REGION:=us-east-1
+AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
+LOAD_TEST_STACK_NAME:=gerrit-load-test
+DESIRED_CAPACITY:=3
+ENTRYPOINT:="ls -lrt"
+
+.PHONY: load-test delete-load-test
+
+load-test:
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(LOAD_TEST_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(LOAD_TEST_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=DesiredCapacity,ParameterValue=$(DESIRED_CAPACITY) \
+		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS) \
+		ParameterKey=EntryPoint,ParameterValue="$(ENTRYPOINT)"
+
+delete-load-test:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(LOAD_TEST_STACK_NAME) \
+	--region $(AWS_REGION)
diff --git a/load-test-fleet/README.md b/load-test-fleet/README.md
new file mode 100644
index 0000000..4284059
--- /dev/null
+++ b/load-test-fleet/README.md
@@ -0,0 +1,21 @@
+# Load tests
+
+This is a set of Cloud Formation Templates and scripts to spin up a simple load
+test fleet of EC2 instances.
+
+It can be used to run load tests against stacks created with any recipe.
+
+## How to run it
+
+```
+make load-test ENTRYPOINT="command to run the tests" DESIRED_CAPACITY=5
+```
+
+This will create a CF stack with `DESIRED_CAPACITY` EC2 instances running the
+`ENTRYPOINT` command after startup.
+
+### Cleaning up
+
+```
+make delete-load-tets
+```
diff --git a/load-test-fleet/cf-load-test-workers.yml b/load-test-fleet/cf-load-test-workers.yml
new file mode 100644
index 0000000..c52ef3d
--- /dev/null
+++ b/load-test-fleet/cf-load-test-workers.yml
@@ -0,0 +1,231 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: A stack for deploying instance to run load tests
+Parameters:
+  DesiredCapacity:
+    Type: Number
+    Default: 3
+    Description: Number of EC2 instances to launch in your ECS cluster.
+  MaxSize:
+    Type: Number
+    Default: 10
+    Description: Maximum number of EC2 instances that can be launched in your ECS cluster.
+  ECSAMI:
+    Description: AMI ID
+    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
+    Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id
+  InstanceType:
+    Description: EC2 instance type
+    Type: String
+    Default: m4.xlarge
+    AllowedValues: [t2.micro, t2.small, t2.medium, t2.large, m3.medium, m3.large,
+      m3.xlarge, m3.2xlarge, m4.large, m4.xlarge, m4.2xlarge, m4.4xlarge, m4.10xlarge,
+      c4.large, c4.xlarge, c4.2xlarge, c4.4xlarge, c4.8xlarge, c3.large, c3.xlarge,
+      c3.2xlarge, c3.4xlarge, c3.8xlarge, r3.large, r3.xlarge, r3.2xlarge, r3.4xlarge,
+      r3.8xlarge, i2.xlarge, i2.2xlarge, i2.4xlarge, i2.8xlarge]
+    ConstraintDescription: Please choose a valid instance type.
+  ECSKeyName:
+    Type: String
+    Default: gerrit-cluster-keys
+    Description: EC2 key pair name the cluter's instances
+  EntryPoint:
+    Type: String
+    Default: "docker info"
+    Description: Command to run when startinig the load tests
+Mappings:
+  # Hard values for the subnet masks. These masks define
+  # the range of internal IP addresses that can be assigned.
+  # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255
+  # There is the subnet which cover the ranges:
+  #
+  # 10.0.0.0 - 10.0.0.255
+  SubnetConfig:
+    VPC:
+      CIDR: '10.0.0.0/16'
+    PublicOne:
+      CIDR: '10.0.0.0/24'
+Resources:
+  VPC:
+    Type: AWS::EC2::VPC
+    Properties:
+      EnableDnsSupport: true
+      EnableDnsHostnames: true
+      CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
+
+  PublicSubnetOne:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone:
+         Fn::Select:
+         - 0
+         - Fn::GetAZs: {Ref: 'AWS::Region'}
+      VpcId: !Ref 'VPC'
+      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
+      MapPublicIpOnLaunch: true
+
+  InternetGateway:
+    Type: AWS::EC2::InternetGateway
+  GatewayAttachement:
+    Type: AWS::EC2::VPCGatewayAttachment
+    Properties:
+      VpcId: !Ref 'VPC'
+      InternetGatewayId: !Ref 'InternetGateway'
+  PublicRouteTable:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      VpcId: !Ref 'VPC'
+  PublicRoute:
+    Type: AWS::EC2::Route
+    DependsOn: GatewayAttachement
+    Properties:
+      RouteTableId: !Ref 'PublicRouteTable'
+      DestinationCidrBlock: '0.0.0.0/0'
+      GatewayId: !Ref 'InternetGateway'
+  PublicSubnetOneRouteTableAssociation:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      SubnetId: !Ref PublicSubnetOne
+      RouteTableId: !Ref PublicRouteTable
+
+  # ECS Resources
+  ECSCluster:
+    Type: AWS::ECS::Cluster
+
+  EcsHostSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: Access to the ECS hosts that run containers
+      VpcId: !Ref 'VPC'
+      SecurityGroupIngress:
+          # Allow access to NLB from anywhere on the internet
+          - CidrIp: 0.0.0.0/0
+            IpProtocol: -1
+
+  CloudWatchLogsGroup:
+      Type: AWS::Logs::LogGroup
+      Properties:
+          LogGroupName: !Ref AWS::StackName
+          RetentionInDays: 1
+
+  # Autoscaling group. This launches the actual EC2 instances that will register
+  # themselves as members of the cluster, and run the docker containers.
+  ECSAutoScalingGroup:
+    Type: AWS::AutoScaling::AutoScalingGroup
+    Properties:
+      VPCZoneIdentifier:
+        - !Ref PublicSubnetOne
+      LaunchConfigurationName: !Ref 'ContainerInstances'
+      MinSize: '1'
+      MaxSize: !Ref 'MaxSize'
+      DesiredCapacity: !Ref 'DesiredCapacity'
+    CreationPolicy:
+      ResourceSignal:
+        Timeout: PT15M
+    UpdatePolicy:
+      AutoScalingReplacingUpdate:
+        WillReplace: 'true'
+  ContainerInstances:
+    Type: AWS::AutoScaling::LaunchConfiguration
+    Properties:
+      ImageId: !Ref 'ECSAMI'
+      SecurityGroups: [!Ref 'EcsHostSecurityGroup']
+      InstanceType: !Ref 'InstanceType'
+      IamInstanceProfile: !Ref 'EC2InstanceProfile'
+      KeyName: !Ref ECSKeyName
+      UserData:
+        Fn::Base64: !Sub |
+          #!/bin/bash -xe
+          echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
+          yum install -y aws-cfn-bootstrap
+          # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
+          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
+          ${EntryPoint}
+  AutoscalingRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service: [application-autoscaling.amazonaws.com]
+          Action: ['sts:AssumeRole']
+      Path: /
+      Policies:
+      - PolicyName: service-autoscaling
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 'application-autoscaling:*'
+              - 'cloudwatch:DescribeAlarms'
+              - 'cloudwatch:PutMetricAlarm'
+              - 'ecs:DescribeServices'
+              - 'ecs:UpdateService'
+            Resource: '*'
+  EC2InstanceProfile:
+    Type: AWS::IAM::InstanceProfile
+    Properties:
+      Path: /
+      Roles: [!Ref 'EC2Role']
+
+  # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts
+  # to communciate with the ECS control plane, as well as download the docker
+  # images from ECR to run on your host.
+  EC2Role:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service: [ec2.amazonaws.com]
+          Action: ['sts:AssumeRole']
+      Path: /
+      Policies:
+      - PolicyName: ecs-service
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 'ecs:CreateCluster'
+              - 'ecs:DeregisterContainerInstance'
+              - 'ecs:DiscoverPollEndpoint'
+              - 'ecs:Poll'
+              - 'ecs:RegisterContainerInstance'
+              - 'ecs:StartTelemetrySession'
+              - 'ecs:Submit*'
+              - 'logs:CreateLogStream'
+              - 'logs:PutLogEvents'
+              - 'ecr:GetAuthorizationToken'
+              - 'ecr:BatchGetImage'
+              - 'ecr:GetDownloadUrlForLayer'
+            Resource: '*'
+      - PolicyName: s3-bucket
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 's3:ListBucket'
+            Resource: '*'
+          - Effect: Allow
+            Action:
+              - 's3:PutObject'
+              - 's3:GetObject'
+              - 's3:DeleteObject'
+            Resource: '*'
+
+Outputs:
+  ClusterName:
+    Description: The name of the ECS cluster
+    Value: !Ref 'ECSCluster'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
+  VPCId:
+    Description: The ID of the VPC that this stack is deployed in
+    Value: !Ref 'VPC'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
+  PublicSubnetOne:
+    Description: Public subnet one
+    Value: !Ref 'PublicSubnetOne'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
diff --git a/master-slave/Makefile b/master-slave/Makefile
index 77ee882..b4986ef 100644
--- a/master-slave/Makefile
+++ b/master-slave/Makefile
@@ -14,10 +14,11 @@
 				wait-for-cluster-deletion wait-for-service-master-deletion wait-for-dns-routing-deletion \
 				gerrit-build gerrit-publish
 
-create-all: gerrit-publish git-daemon-publish git-ssh-publish \
+create-all: upload-common-templates \
+						gerrit-publish git-daemon-publish git-ssh-publish \
 						cluster wait-for-cluster-creation \
-						service-slave wait-for-service-slave-creation \
-						service-master wait-for-service-master-creation \
+						service-slave service-master \
+						wait-for-service-master-creation wait-for-service-slave-creation \
 						dns-routing wait-for-dns-routing-creation
 
 cluster: cluster-keys
diff --git a/master-slave/cf-cluster.yml b/master-slave/cf-cluster.yml
index 4f914e5..7330e9d 100644
--- a/master-slave/cf-cluster.yml
+++ b/master-slave/cf-cluster.yml
@@ -208,28 +208,7 @@
           /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/home/ec2-user/gerritlogsaccess.json -s
           # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
           /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
-  AutoscalingRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Statement:
-        - Effect: Allow
-          Principal:
-            Service: [application-autoscaling.amazonaws.com]
-          Action: ['sts:AssumeRole']
-      Path: /
-      Policies:
-      - PolicyName: service-autoscaling
-        PolicyDocument:
-          Statement:
-          - Effect: Allow
-            Action:
-              - 'application-autoscaling:*'
-              - 'cloudwatch:DescribeAlarms'
-              - 'cloudwatch:PutMetricAlarm'
-              - 'ecs:DescribeServices'
-              - 'ecs:UpdateService'
-            Resource: '*'
+
   EC2InstanceProfile:
     Type: AWS::IAM::InstanceProfile
     Properties:
diff --git a/master-slave/cf-service-master.yml b/master-slave/cf-service-master.yml
index 68f1bf8..e2a6135 100644
--- a/master-slave/cf-service-master.yml
+++ b/master-slave/cf-service-master.yml
@@ -104,8 +104,8 @@
         Type: AWS::ECS::TaskDefinition
         Properties:
             Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            TaskRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
+            ExecutionRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
             NetworkMode: bridge
             ContainerDefinitions:
                 - Name: !Ref GerritServiceName
@@ -251,41 +251,12 @@
             Port: !Ref SSHPort
             Protocol: TCP
 
-    # This is a role which is used by the ECS tasks themselves.
-    ECSTaskExecutionRole:
-      Type: AWS::IAM::Role
+    ECSTaskExecutionRoleStack:
+      Type: AWS::CloudFormation::Stack
       Properties:
-        AssumeRolePolicyDocument:
-          Statement:
-          - Effect: Allow
-            Principal:
-              Service: [ecs-tasks.amazonaws.com]
-            Action: ['sts:AssumeRole']
-        Path: /
-        Policies:
-          - PolicyName: AmazonECSTaskExecutionRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to download images from ECR
-                  - 'ecr:GetAuthorizationToken'
-                  - 'ecr:BatchCheckLayerAvailability'
-                  - 'ecr:GetDownloadUrlForLayer'
-                  - 'ecr:BatchGetImage'
-                  # Allow the ECS tasks to upload logs to CloudWatch
-                  - 'logs:CreateLogStream'
-                  - 'logs:PutLogEvents'
-                Resource: '*'
-          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to get SSH Keys
-                  - 'secretsmanager:GetSecretValue'
-                  - 'kms:Decrypt'
-                Resource: '*'
+        TemplateURL: https://aws-gerrit-cf-templates.s3.amazonaws.com/cf-gerrit-task-execution-role.yml
+        TimeoutInMinutes: '5'
+
 Outputs:
   PublicLoadBalancerDNSName:
     Description: The DNS name of the external load balancer
diff --git a/master-slave/cf-service-slave.yml b/master-slave/cf-service-slave.yml
index 7415836..6965e79 100644
--- a/master-slave/cf-service-slave.yml
+++ b/master-slave/cf-service-slave.yml
@@ -109,6 +109,8 @@
         DependsOn:
           - HTTPListener
           - SSHListener
+          - GitListener
+          - GitSSHListener
         Properties:
             Cluster:
               Fn::ImportValue:
@@ -122,19 +124,6 @@
                 - ContainerName: !Ref GerritServiceName
                   ContainerPort: !Ref SSHContainerPort
                   TargetGroupArn: !Ref SSHTargetGroup
-
-    GerritReplicationService:
-        Type: AWS::ECS::Service
-        DependsOn:
-          - GitListener
-          - GitSSHListener
-        Properties:
-            Cluster:
-              Fn::ImportValue:
-                  !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
-            DesiredCount: !Ref DesiredCount
-            TaskDefinition: !Ref GerritReplicationTaskDefinition
-            LoadBalancers:
                 - ContainerName: !Ref GitDaemonServiceName
                   ContainerPort: !Ref GitPort
                   TargetGroupArn: !Ref GitTargetGroup
@@ -146,8 +135,8 @@
         Type: AWS::ECS::TaskDefinition
         Properties:
             Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            TaskRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
+            ExecutionRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
             NetworkMode: bridge
             ContainerDefinitions:
                 - Name: !Ref GerritServiceName
@@ -190,51 +179,6 @@
                         awslogs-group: !Ref ClusterStackName
                         awslogs-region: !Ref AWS::Region
                         awslogs-stream-prefix: !Ref EnvironmentName
-            Volumes:
-              - Name: !Ref 'GerritDbVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
-              - Name: !Ref 'GerritGitVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
-              - Name: !Ref 'GerritDataVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
-              - Name: !Ref 'GerritCacheVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
-              - Name: !Ref 'GerritLogsVolume'
-                DockerVolumeConfiguration:
-                  Scope: shared
-                  Autoprovision: true
-                  Driver: local
-                  Labels:
-                    gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
-
-    GerritReplicationTaskDefinition:
-        Type: AWS::ECS::TaskDefinition
-        Properties:
-            Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
-            NetworkMode: bridge
-            ContainerDefinitions:
                 - Name: !Ref GitDaemonServiceName
                   Essential: true
                   Image: !Sub '${DockerRegistryUrl}/${GitDaemonDockerImage}'
@@ -281,6 +225,13 @@
                         awslogs-region: !Ref AWS::Region
                         awslogs-stream-prefix: !Ref EnvironmentName
             Volumes:
+              - Name: !Ref 'GerritDbVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
               - Name: !Ref 'GerritGitVolume'
                 DockerVolumeConfiguration:
                   Scope: shared
@@ -288,6 +239,27 @@
                   Driver: local
                   Labels:
                     gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
+              - Name: !Ref 'GerritDataVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
+              - Name: !Ref 'GerritCacheVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
+              - Name: !Ref 'GerritLogsVolume'
+                DockerVolumeConfiguration:
+                  Scope: shared
+                  Autoprovision: true
+                  Driver: local
+                  Labels:
+                    gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
 
     LoadBalancer:
         Type: AWS::ElasticLoadBalancingV2::LoadBalancer
@@ -310,7 +282,7 @@
                   !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
             Port: !Ref HTTPHostPort
             Protocol: TCP
-            HealthCheckPort: !Ref HTTPContainePort
+            HealthCheckPort: !Ref HTTPHostPort
 
     HTTPListener:
         Type: AWS::ElasticLoadBalancingV2::Listener
@@ -334,7 +306,7 @@
                   !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
             Port: !Ref SSHHostPort
             Protocol: TCP
-            HealthCheckPort: !Ref SSHContainerPort
+            HealthCheckPort: !Ref SSHHostPort
 
     SSHListener:
         Type: AWS::ElasticLoadBalancingV2::Listener
@@ -401,41 +373,12 @@
             HostedZoneId: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
             EvaluateTargetHealth: False
 
-    # This is a role which is used by the ECS tasks themselves.
-    ECSTaskExecutionRole:
-      Type: AWS::IAM::Role
+    ECSTaskExecutionRoleStack:
+      Type: AWS::CloudFormation::Stack
       Properties:
-        AssumeRolePolicyDocument:
-          Statement:
-          - Effect: Allow
-            Principal:
-              Service: [ecs-tasks.amazonaws.com]
-            Action: ['sts:AssumeRole']
-        Path: /
-        Policies:
-          - PolicyName: AmazonECSTaskExecutionRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to download images from ECR
-                  - 'ecr:GetAuthorizationToken'
-                  - 'ecr:BatchCheckLayerAvailability'
-                  - 'ecr:GetDownloadUrlForLayer'
-                  - 'ecr:BatchGetImage'
-                  # Allow the ECS tasks to upload logs to CloudWatch
-                  - 'logs:CreateLogStream'
-                  - 'logs:PutLogEvents'
-                Resource: '*'
-          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to get SSH Keys
-                  - 'secretsmanager:GetSecretValue'
-                  - 'kms:Decrypt'
-                Resource: '*'
+        TemplateURL: https://aws-gerrit-cf-templates.s3.amazonaws.com/cf-gerrit-task-execution-role.yml
+        TimeoutInMinutes: '5'
+
 Outputs:
   PublicLoadBalancerDNSName:
     Description: The DNS name of the external load balancer
diff --git a/single-master/Makefile b/single-master/Makefile
index b053f53..fca4d15 100644
--- a/single-master/Makefile
+++ b/single-master/Makefile
@@ -12,7 +12,8 @@
 				wait-for-cluster-deletion wait-for-service-deletion wait-for-dns-routing-deletion \
 				gerrit-build gerrit-publish
 
-create-all: gerrit-publish \
+create-all: upload-common-templates \
+						gerrit-publish \
 						cluster wait-for-cluster-creation \
 						service wait-for-service-creation \
 						dns-routing wait-for-dns-routing-creation
diff --git a/single-master/cf-cluster.yml b/single-master/cf-cluster.yml
index 61035ef..85570e2 100644
--- a/single-master/cf-cluster.yml
+++ b/single-master/cf-cluster.yml
@@ -183,28 +183,7 @@
           /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/home/ec2-user/gerritlogsaccess.json -s
           # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
           /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
-  AutoscalingRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Statement:
-        - Effect: Allow
-          Principal:
-            Service: [application-autoscaling.amazonaws.com]
-          Action: ['sts:AssumeRole']
-      Path: /
-      Policies:
-      - PolicyName: service-autoscaling
-        PolicyDocument:
-          Statement:
-          - Effect: Allow
-            Action:
-              - 'application-autoscaling:*'
-              - 'cloudwatch:DescribeAlarms'
-              - 'cloudwatch:PutMetricAlarm'
-              - 'ecs:DescribeServices'
-              - 'ecs:UpdateService'
-            Resource: '*'
+
   EC2InstanceProfile:
     Type: AWS::IAM::InstanceProfile
     Properties:
diff --git a/single-master/cf-service.yml b/single-master/cf-service.yml
index 80a06dc..0f7cd92 100644
--- a/single-master/cf-service.yml
+++ b/single-master/cf-service.yml
@@ -97,8 +97,8 @@
         Type: AWS::ECS::TaskDefinition
         Properties:
             Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
-            TaskRoleArn: !Ref ECSTaskExecutionRole
-            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            TaskRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
+            ExecutionRoleArn: !GetAtt ECSTaskExecutionRoleStack.Outputs.TaskExecutionRoleRef
             NetworkMode: bridge
             ContainerDefinitions:
                 - Name: !Ref GerritServiceName
@@ -241,41 +241,12 @@
             Port: !Ref SSHPort
             Protocol: TCP
 
-    # This is a role which is used by the ECS tasks themselves.
-    ECSTaskExecutionRole:
-      Type: AWS::IAM::Role
+    ECSTaskExecutionRoleStack:
+      Type: AWS::CloudFormation::Stack
       Properties:
-        AssumeRolePolicyDocument:
-          Statement:
-          - Effect: Allow
-            Principal:
-              Service: [ecs-tasks.amazonaws.com]
-            Action: ['sts:AssumeRole']
-        Path: /
-        Policies:
-          - PolicyName: AmazonECSTaskExecutionRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to download images from ECR
-                  - 'ecr:GetAuthorizationToken'
-                  - 'ecr:BatchCheckLayerAvailability'
-                  - 'ecr:GetDownloadUrlForLayer'
-                  - 'ecr:BatchGetImage'
-                  # Allow the ECS tasks to upload logs to CloudWatch
-                  - 'logs:CreateLogStream'
-                  - 'logs:PutLogEvents'
-                Resource: '*'
-          - PolicyName: AmazonECSTaskSecretManagerRolePolicy
-            PolicyDocument:
-              Statement:
-              - Effect: Allow
-                Action:
-                  # Allow the ECS Tasks to get SSH Keys
-                  - 'secretsmanager:GetSecretValue'
-                  - 'kms:Decrypt'
-                Resource: '*'
+        TemplateURL: https://aws-gerrit-cf-templates.s3.amazonaws.com/cf-gerrit-task-execution-role.yml
+        TimeoutInMinutes: '5'
+
 Outputs:
   PublicLoadBalancerDNSName:
     Description: The DNS name of the external load balancer