Merge branch 'stable-3.1'

* stable-3.1:
  Use load-balancer service ports from HAProxy
  Fix dual-master recipe: add  missing template parameters
  Fix dual-master recipe adding missing parameter HTTPGerritMaster2Port
  Base all image paths on the project ROOT_DIR
  Fix SHA1 computation for haproxy / syslog-sidecar
  Add missing LB_SUBDOMAIN for dual-master config
  Add healthcheck plugin
  Fix Gerrit root directory
  Remove .empty file
  Add HAProxy in front of the dual masters
  Fix Gerrit sha1 calculation
  Remove auth section duplicate

Change-Id: I31953d155de52acbafc3badfe1da599d18debeb6
diff --git a/Makefile.common b/Makefile.common
index 4f31260..37ae390 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1,4 +1,5 @@
-include $(dir $(realpath $(lastword $(MAKEFILE_LIST))))common.env
+ROOT_DIR=$(dir $(realpath $(filter %Makefile.common,$(MAKEFILE_LIST))))
+include $(ROOT_DIR)common.env
 
 
 cluster-keys:
diff --git a/common.env b/common.env
index 8f5a906..be6480b 100644
--- a/common.env
+++ b/common.env
@@ -12,7 +12,14 @@
 GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$(GERRIT_BRANCH)/job
 LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
 
-# Image version
-IMAGE_DIR=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))gerrit
-HEAD_SHA1=$(shell git $(IMAGE_DIR) -type f -exec cat {} \; | shasum | cut -c 1-20)
+# Image directories
+IMAGE_DIR:=$(ROOT_DIR)/gerrit
+HAPROXY_IMAGE_DIR=$(ROOT_DIR)/dual-master/haproxy
+SYSLOG_IMAGE_DIR=$(ROOT_DIR)/dual-master/syslog-sidecar
+
+# Image SHA1 versions
+HEAD_SHA1=$(shell find $(IMAGE_DIR) -type f -exec cat {} \; | shasum | cut -c 1-20)
+HAPROXY_HEAD_SHA1=$(shell find $(HAPROXY_IMAGE_DIR) -type f -exec cat {} \; | shasum | cut -c 1-20)
+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)
diff --git a/dual-master/Makefile b/dual-master/Makefile
index d91e7c6..038eb70 100644
--- a/dual-master/Makefile
+++ b/dual-master/Makefile
@@ -4,17 +4,20 @@
 CLUSTER_TEMPLATE:=cf-cluster.yml
 SERVICE_MASTER_TEMPLATE:=cf-service-master.yml
 DNS_ROUTING_TEMPLATE:=cf-dns-route.yml
+LOAD_BALANCER_TEMPLATE:=cf-service-lb.yml
 AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
 
 .PHONY: create-all delete-all \
 				cluster cluster-keys service-master-1 dns-routing \
 				wait-for-cluster-creation wait-for-service-master-1-creation wait-for-service-master-2-creation wait-for-dns-routing-creation \
 				wait-for-cluster-deletion wait-for-service-master-1-deletion wait-for-service-master-2-deletion wait-for-dns-routing-deletion \
-				gerrit-build gerrit-publish
+				service-lb wait-for-service-lb-deletion wait-for-service-lb-creation \
+				gerrit-build gerrit-publish haproxy-publish syslog-sidecar-publish
 
 create-all: cluster wait-for-cluster-creation \
 						service-master-1 wait-for-service-master-1-creation \
 						service-master-2 wait-for-service-master-2-creation \
+						service-lb wait-for-service-lb-creation \
 						dns-routing wait-for-dns-routing-creation
 
 cluster: cluster-keys
@@ -39,9 +42,17 @@
 		ParameterKey=Subdomain,ParameterValue=$(MASTER1_SUBDOMAIN) \
 		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
 		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
+		ParameterKey=HTTPHostPort,ParameterValue=$(HTTP_HOST_PORT_MASTER1) \
+		ParameterKey=SSHHostPort,ParameterValue=$(SSH_HOST_PORT_MASTER1) \
 		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
+		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG) \
+		ParameterKey=GerritDataVolume,ParameterValue=gerrit-data-master-1 \
+		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=PeerSubdomain,ParameterValue=$(MASTER2_SUBDOMAIN) \
-		ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG)
+		ParameterKey=LBSubdomain,ParameterValue=$(LB_SUBDOMAIN)
 
 service-master-2:
 	$(AWS_FC_COMMAND) create-stack \
@@ -64,7 +75,26 @@
 		ParameterKey=GerritCacheVolume,ParameterValue=gerrit-cache-master-2 \
 		ParameterKey=GerritDbVolume,ParameterValue=gerrit-db-master-2 \
 		ParameterKey=GerritLogsVolume,ParameterValue=gerrit-logs-master-2 \
-		ParameterKey=PeerSubdomain,ParameterValue=$(MASTER1_SUBDOMAIN)
+		ParameterKey=PeerSubdomain,ParameterValue=$(MASTER1_SUBDOMAIN) \
+		ParameterKey=LBSubdomain,ParameterValue=$(LB_SUBDOMAIN)
+
+service-lb:
+	$(AWS_FC_COMMAND) create-stack \
+		--stack-name $(LOAD_BALANCER_STACK_NAME) \
+		--capabilities CAPABILITY_IAM  \
+		--template-body file://`pwd`/$(LOAD_BALANCER_TEMPLATE) \
+		--region $(AWS_REGION) \
+		--parameters \
+		ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+		ParameterKey=Master1ServiceStackName,ParameterValue=$(SERVICE_MASTER1_STACK_NAME) \
+		ParameterKey=Master2ServiceStackName,ParameterValue=$(SERVICE_MASTER2_STACK_NAME) \
+		ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
+		ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
+		ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
+		ParameterKey=Subdomain,ParameterValue=$(LB_SUBDOMAIN) \
+		ParameterKey=HAProxyDockerImage,ParameterValue=aws-gerrit/haproxy:$(HAPROXY_HEAD_SHA1) \
+		ParameterKey=SidecarDockerImage,ParameterValue=aws-gerrit/syslog-sidecar:$(SYSLOG_HEAD_SHA1)
 
 dns-routing:
 	$(AWS_FC_COMMAND) create-stack \
@@ -74,7 +104,8 @@
 		--region $(AWS_REGION) \
 		--parameters \
 		ParameterKey=Master1ServiceStackName,ParameterValue=$(SERVICE_MASTER1_STACK_NAME) \
-		ParameterKey=Master2ServiceStackName,ParameterValue=$(SERVICE_MASTER2_STACK_NAME)
+		ParameterKey=Master2ServiceStackName,ParameterValue=$(SERVICE_MASTER2_STACK_NAME) \
+		ParameterKey=LBServiceStackName,ParameterValue=$(LOAD_BALANCER_STACK_NAME)
 
 wait-for-cluster-creation:
 	@echo "*** Wait for cluster stack '$(CLUSTER_STACK_NAME)' creation"
@@ -97,6 +128,13 @@
 	--region $(AWS_REGION)
 	@echo "*** Service stack '$(SERVICE_MASTER2_STACK_NAME)' created"
 
+wait-for-service-lb-creation:
+	@echo "*** Wait for service lb stack '$(LOAD_BALANCER_STACK_NAME)' creation"
+	$(AWS_FC_COMMAND) wait stack-create-complete \
+	--stack-name $(LOAD_BALANCER_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Service stack '$(LOAD_BALANCER_STACK_NAME)' created"
+
 wait-for-dns-routing-creation:
 	@echo "*** Wait for DNS routing stack '$(DNS_ROUTING_STACK_NAME)' creation"
 	$(AWS_FC_COMMAND) wait stack-create-complete \
@@ -125,6 +163,13 @@
 	--region $(AWS_REGION)
 	@echo "*** Service stack master '$(SERVICE_MASTER2_STACK_NAME)' deleted"
 
+wait-for-service-lb-deletion:
+	@echo "*** Wait for service lb stack '$(LOAD_BALANCER_STACK_NAME)' deletion"
+	$(AWS_FC_COMMAND) wait stack-delete-complete \
+	--stack-name $(LOAD_BALANCER_STACK_NAME) \
+	--region $(AWS_REGION)
+	@echo "*** Service stack '$(LOAD_BALANCER_STACK_NAME)' deleted"
+
 wait-for-dns-routing-deletion:
 	@echo "*** Wait for DNS routing stack '$(DNS_ROUTING_STACK_NAME)' deletion"
 	$(AWS_FC_COMMAND) wait stack-delete-complete \
@@ -147,15 +192,27 @@
 	--stack-name $(SERVICE_MASTER2_STACK_NAME) \
 	--region $(AWS_REGION)
 
+delete-service-lb:
+	$(AWS_FC_COMMAND) delete-stack \
+	--stack-name $(LOAD_BALANCER_STACK_NAME) \
+	--region $(AWS_REGION)
+
 delete-dns-routing:
 	$(AWS_FC_COMMAND) delete-stack \
 	--stack-name $(DNS_ROUTING_STACK_NAME) \
 	--region $(AWS_REGION)
 
 delete-all: delete-dns-routing wait-for-dns-routing-deletion \
+						delete-service-lb wait-for-service-lb-deletion \
 						delete-service-master-1 wait-for-service-master-1-deletion \
 						delete-service-master-2 wait-for-service-master-2-deletion \
 						delete-cluster wait-for-cluster-deletion
 
 gerrit-publish:
 	$(MAKE) -C ../gerrit gerrit-publish RECIPE=dual-master
+
+haproxy-publish:
+	$(MAKE) -C haproxy haproxy-publish
+
+syslog-sidecar-publish:
+	$(MAKE) -C syslog-sidecar syslog-sidecar-publish
diff --git a/dual-master/README.md b/dual-master/README.md
index ff91fd1..7c5cfe4 100644
--- a/dual-master/README.md
+++ b/dual-master/README.md
@@ -146,6 +146,15 @@
 * Add the plugins you want to install in `./gerrit/plugins`
 * Publish the image: `make gerrit-publish`
 
+### Publish custom HAProxy Docker image
+
+* Create the HAProxy and the logging sidecar repository in the Docker registry:
+  `aws ecr create-repository --repository-name aws-gerrit/haproxy`
+  `aws ecr create-repository --repository-name aws-gerrit/syslog-sidecar`
+* Publish the images:
+  `make haproxy-publish`
+  `make syslog-sidecar-publish`
+
 ### Getting Started
 
 * Create the cluster, services and DNS routing stacks:
diff --git a/dual-master/cf-dns-route.yml b/dual-master/cf-dns-route.yml
index c704f1f..d123d7f 100644
--- a/dual-master/cf-dns-route.yml
+++ b/dual-master/cf-dns-route.yml
@@ -9,6 +9,10 @@
       Description: Stack name of the ECS Master Gerrit service
       Type: String
       Default: gerrit-service-master-2
+  LBServiceStackName:
+      Description: Stack name of the ECS LB service
+      Type: String
+      Default: gerrit-service
 
 Resources:
   Master1DnsRecord:
@@ -57,3 +61,26 @@
             Fn::ImportValue:
               !Join [':', [!Ref 'Master2ServiceStackName', 'CanonicalHostedZoneID']]
           EvaluateTargetHealth: False
+  LBDnsRecord:
+      Type: AWS::Route53::RecordSet
+      Properties:
+        Name:
+          !Join
+            - '.'
+            - - Fn::ImportValue: !Join [':', [!Ref 'LBServiceStackName', 'Subdomain']]
+              - Fn::ImportValue: !Join [':', [!Ref 'LBServiceStackName', 'HostedZoneName']]
+        HostedZoneName:
+          !Join
+            - ''
+            - - Fn::ImportValue: !Join [':', [!Ref 'LBServiceStackName', 'HostedZoneName']]
+              - '.'
+        Comment: DNS name for Gerrit LB.
+        Type: A
+        AliasTarget:
+          DNSName:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'LBServiceStackName', 'PublicLoadBalancerDNSName']]
+          HostedZoneId:
+            Fn::ImportValue:
+              !Join [':', [!Ref 'LBServiceStackName', 'CanonicalHostedZoneID']]
+          EvaluateTargetHealth: False
diff --git a/dual-master/cf-service-lb.yml b/dual-master/cf-service-lb.yml
new file mode 100644
index 0000000..16c0b81
--- /dev/null
+++ b/dual-master/cf-service-lb.yml
@@ -0,0 +1,239 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+Parameters:
+  LBServiceName:
+    Type: String
+    Default: gerrit-load-balancer
+  ClusterStackName:
+      Description: Stack name of the ECS cluster to deploy the services
+      Type: String
+      Default: gerrit-cluster
+  EnvironmentName:
+      Description: An environment name used to build the log stream names
+      Type: String
+      Default: test
+  HAProxyDockerImage:
+        Description: HAProxy Docker image
+        Type: String
+        Default: aws-gerrit/haproxy:latest
+  SidecarDockerImage:
+        Description: Syslog sidecar Docker image
+        Type: String
+        Default: aws-gerrit/syslog-sidecar:latest
+  DockerRegistryUrl:
+        Description: Docker registry URL
+        Type: String
+  DesiredCount:
+        Description: How many instances of this task should we run across our cluster?
+        Type: Number
+        Default: 1
+  HTTPGerritPort:
+        Description: Gerrit HTTP port
+        Type: Number
+        Default: 8080
+  SSHGerritPort:
+        Description: Gerrit SSH port
+        Type: Number
+        Default: 29418
+  HTTPContainerPort:
+        Description: Gerrit HTTP port
+        Type: Number
+        Default: 80
+  HTTPSHostPort:
+        Description: Gerrit HTTPS port
+        Type: Number
+        Default: 443
+  HTTPHostPort:
+        Description: HAProxy HTTP port
+        Type: Number
+        Default: 80
+  CertificateArn:
+        Description: SSL Certificates ARN
+        Type: String
+  HostedZoneName:
+        Description: The route53 HostedZoneName.
+        Type: String
+  Subdomain:
+        Description: The subdomain of the Gerrit cluster
+        Type: String
+        Default: gerrit-dual-master
+  GerritKeyPrefix:
+        Description: Gerrit credentials keys prefix
+        Type: String
+  Master1ServiceStackName:
+      Description: Stack name of the ECS Master Gerrit service
+      Type: String
+      Default: gerrit-service-master-1
+  Master2ServiceStackName:
+      Description: Stack name of the ECS Master Gerrit service
+      Type: String
+      Default: gerrit-service-master-2
+  SyslogSidecarServiceName:
+      Description: HAPRoxy Syslog Sidecar service name
+      Type: String
+      Default: gerrit-haproxy-sidecar
+
+Resources:
+    LBService:
+        Type: AWS::ECS::Service
+        DependsOn:
+          - HTTPListener
+        Properties:
+            Cluster:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+            DesiredCount: !Ref DesiredCount
+            TaskDefinition: !Ref TaskDefinition
+            LoadBalancers:
+                - ContainerName: !Ref LBServiceName
+                  ContainerPort: !Ref HTTPContainerPort
+                  TargetGroupArn: !Ref HTTPTargetGroup
+
+    TaskDefinition:
+        Type: AWS::ECS::TaskDefinition
+        Properties:
+            Family: !Sub '${LBServiceName}TaskDefinition'
+            TaskRoleArn: !Ref ECSTaskExecutionRole
+            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            NetworkMode: bridge
+            ContainerDefinitions:
+                - Name: !Ref LBServiceName
+                  Essential: true
+                  Image: !Sub '${DockerRegistryUrl}/${HAProxyDockerImage}'
+                  Environment:
+                    - Name: GERRIT_MASTER_1_URL
+                      Value:
+                        Fn::ImportValue: !Join [':', [!Ref 'Master1ServiceStackName', 'PublicLoadBalancerDNSName']]
+                    - Name: GERRIT_MASTER_2_URL
+                      Value:
+                        Fn::ImportValue: !Join [':', [!Ref 'Master2ServiceStackName', 'PublicLoadBalancerDNSName']]
+                    - Name: SYSLOG_SIDECAR
+                      Value: !Ref SyslogSidecarServiceName
+                  Cpu: 1024
+                  Memory: 2048
+                  PortMappings:
+                    - ContainerPort: !Ref HTTPContainerPort
+                      HostPort: !Ref HTTPHostPort
+                      Protocol: tcp
+                  Links:
+                    - !Ref SyslogSidecarServiceName
+                  LogConfiguration:
+                    LogDriver: awslogs
+                    Options:
+                        awslogs-group: !Ref ClusterStackName
+                        awslogs-region: !Ref AWS::Region
+                        awslogs-stream-prefix: !Ref EnvironmentName
+                - Name: !Ref SyslogSidecarServiceName
+                  Essential: true
+                  Image: !Sub '${DockerRegistryUrl}/${SidecarDockerImage}'
+                  Cpu: 256
+                  Memory: 512
+                  LogConfiguration:
+                    LogDriver: awslogs
+                    Options:
+                        awslogs-group: !Ref ClusterStackName
+                        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
+      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: '*'
+
+    LoadBalancer:
+        Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+        Properties:
+            Type: network
+            Scheme: internet-facing
+            Subnets:
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+            Tags:
+                - Key: Name
+                  Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'LBServiceName', 'alb']]
+
+    HTTPTargetGroup:
+        Type: AWS::ElasticLoadBalancingV2::TargetGroup
+        DependsOn: LoadBalancer
+        Properties:
+            VpcId:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+            Port: !Ref HTTPHostPort
+            Protocol: TCP
+
+    HTTPListener:
+        Type: AWS::ElasticLoadBalancingV2::Listener
+        DependsOn: LoadBalancer
+        Properties:
+            Certificates:
+              - CertificateArn: !Ref CertificateArn
+            DefaultActions:
+            - Type: forward
+              TargetGroupArn: !Ref HTTPTargetGroup
+            LoadBalancerArn: !Ref LoadBalancer
+            Port: !Ref HTTPSHostPort
+            Protocol: TLS
+
+Outputs:
+  PublicLoadBalancerDNSName:
+    Description: The DNS name of the external load balancer
+    Value: !GetAtt 'LoadBalancer.DNSName'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerDNSName' ] ]
+  CanonicalHostedZoneID:
+    Description: Canonical Hosted Zone ID
+    Value: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalHostedZoneID' ] ]
+  PublicLoadBalancerUrl:
+    Description: The url of the external load balancer
+    Value: !Join ['', ['https://', !GetAtt 'LoadBalancer.DNSName']]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]
+  HostedZoneName:
+    Description: Route53 Hosted Zone name
+    Value: !Ref HostedZoneName
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'HostedZoneName' ] ]
+  Subdomain:
+    Description: Service DNS subdomain
+    Value: !Ref Subdomain
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Subdomain' ] ]
+  CanonicalWebUrl:
+    Description: Canonical Web URL
+    Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalWebUrl' ] ]
diff --git a/dual-master/cf-service-master.yml b/dual-master/cf-service-master.yml
index b97fa2c..42ea576 100644
--- a/dual-master/cf-service-master.yml
+++ b/dual-master/cf-service-master.yml
@@ -30,6 +30,10 @@
         Description: How many instances of this task should we run across our cluster?
         Type: Number
         Default: 1
+  HTTPGerritLBPort:
+        Description: Gerrit Load balancer HTTP port
+        Type: Number
+        Default: 8080
   HTTPHostPort:
         Description: Gerrit HTTP port
         Type: Number
@@ -60,6 +64,9 @@
         Description: The subdomain of the Gerrit cluster
         Type: String
         Default: gerrit-master-demo
+  LBSubdomain:
+        Description: The subdomain of the Gerrit load balancer
+        Type: String
   GerritKeyPrefix:
         Description: Gerrit credentials keys prefix
         Type: String
@@ -128,7 +135,7 @@
                   Image: !Sub '${DockerRegistryUrl}/${DockerImage}'
                   Environment:
                     - Name: CANONICAL_WEB_URL
-                      Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+                      Value: !Sub 'https://${LBSubdomain}.${HostedZoneName}'
                     - Name: HTTPD_LISTEN_URL
                       Value: !Sub 'proxy-https://*:${HTTPContainerPort}/'
                     - Name: AWS_REGION
@@ -140,7 +147,7 @@
                     - Name: SETUP_HA
                       Value: true
                     - Name: HA_PEER_URL
-                      Value: !Sub 'https://${PeerSubdomain}.${HostedZoneName}'
+                      Value: !Sub 'http://${PeerSubdomain}.${HostedZoneName}:${HTTPGerritLBPort}'
                     - Name: HOSTED_ZONE_NAME
                       Value: !Ref HostedZoneName
                   MountPoints:
@@ -236,21 +243,19 @@
             VpcId:
               Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
-            Port: !Ref HTTPHostPort
+            Port: !Ref HTTPGerritLBPort
             Protocol: TCP
 
     HTTPListener:
         Type: AWS::ElasticLoadBalancingV2::Listener
         DependsOn: LoadBalancer
         Properties:
-            Certificates:
-              - CertificateArn: !Ref CertificateArn
             DefaultActions:
             - Type: forward
               TargetGroupArn: !Ref HTTPTargetGroup
             LoadBalancerArn: !Ref LoadBalancer
-            Port: !Ref HTTPSHostPort
-            Protocol: TLS
+            Port: !Ref HTTPGerritLBPort
+            Protocol: TCP
 
     SSHTargetGroup:
         Type: AWS::ElasticLoadBalancingV2::TargetGroup
diff --git a/dual-master/haproxy/Dockerfile b/dual-master/haproxy/Dockerfile
new file mode 100644
index 0000000..f55de52
--- /dev/null
+++ b/dual-master/haproxy/Dockerfile
@@ -0,0 +1,12 @@
+FROM haproxy:1.7
+
+RUN apt-get update && \
+    apt-get -y install gettext-base netcat && \
+    mkdir /var/lib/haproxy && \
+    mkdir /var/run/haproxy && \
+    useradd haproxy && \
+    chown haproxy: /var/lib/haproxy /var/run/haproxy
+
+COPY haproxy.cfg.template /usr/local/etc/haproxy/
+
+COPY docker-entrypoint.sh /
diff --git a/dual-master/haproxy/Makefile b/dual-master/haproxy/Makefile
new file mode 100644
index 0000000..98d7370
--- /dev/null
+++ b/dual-master/haproxy/Makefile
@@ -0,0 +1,15 @@
+include ../../Makefile.common
+include ../setup.env
+
+IMAGE_NAME:=haproxy
+
+docker-registry-login:
+	aws ecr get-login-password --region $(AWS_REGION) \
+		| docker login --username AWS --password-stdin $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME)
+
+haproxy-build:
+	docker build -t aws-gerrit/$(IMAGE_NAME):$(HAPROXY_HEAD_SHA1) .
+	docker tag aws-gerrit/$(IMAGE_NAME):$(HAPROXY_HEAD_SHA1) $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):$(HAPROXY_HEAD_SHA1)
+
+haproxy-publish: docker-registry-login haproxy-build
+	docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):$(HAPROXY_HEAD_SHA1)
diff --git a/dual-master/haproxy/docker-entrypoint.sh b/dual-master/haproxy/docker-entrypoint.sh
new file mode 100755
index 0000000..b379564
--- /dev/null
+++ b/dual-master/haproxy/docker-entrypoint.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -e
+
+# first arg is `-f` or `--some-option`
+if [ "${1#-}" != "$1" ]; then
+	set -- haproxy "$@"
+fi
+
+if [ "$1" = 'haproxy' ]; then
+	# if the user wants "haproxy", let's use "haproxy-systemd-wrapper" instead so we can have proper reloadability implemented by upstream
+	shift # "haproxy"
+	set -- "$(which haproxy-systemd-wrapper)" -p /run/haproxy.pid "$@"
+fi
+
+envsubst < /usr/local/etc/haproxy/haproxy.cfg.template > /usr/local/etc/haproxy/haproxy.cfg
+
+exec "$@"
diff --git a/dual-master/haproxy/haproxy.cfg.template b/dual-master/haproxy/haproxy.cfg.template
new file mode 100644
index 0000000..08536ac
--- /dev/null
+++ b/dual-master/haproxy/haproxy.cfg.template
@@ -0,0 +1,76 @@
+global
+    log gerrit-haproxy-sidecar local0 debug
+
+    chroot /var/lib/haproxy
+    stats socket /var/run/haproxy/admin.sock mode 660 level admin
+    stats timeout 30s
+    user haproxy
+    group haproxy
+    daemon
+
+defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    timeout connect 5000
+    timeout client  900000
+    timeout server  900000
+    timeout check 30000
+
+frontend localnodes
+    bind *:80
+    mode http
+    option httplog
+    capture request header X-Forwarded-For len 15
+    log-format %[capture.req.hdr(0)]:%cp\ [%t]\ %f\ %b/%s\ %Tq/%Tw/%Tc/%Tr/%Tt\ %ST\ %B\ %CC\ %CS\ %tsc\ %ac/%fc/%bc/%sc/%rc\ %sq/%bq\ {%hrl}\ {%hsl}\ %{+Q}r
+
+    default_backend master
+
+frontend gitssh
+    bind *:29418
+    mode tcp
+    timeout client  5m
+
+    default_backend ssh
+
+backend master
+    mode http
+    balance roundrobin
+    option forwardfor
+    default-server inter 10s fall 3 rise 2
+    option httpchk GET /config/server/healthcheck~status HTTP/1.0
+    http-check expect status 200
+    server gerrit-1 $GERRIT_MASTER_1_URL:8080 check inter 10s
+    server gerrit-2 $GERRIT_MASTER_2_URL:8080 check inter 10s backup
+
+backend gerrit-1
+    mode http
+    option forwardfor
+    server gerrit-1 $GERRIT_MASTER_1_URL:8080
+
+backend gerrit-2
+    mode http
+    option forwardfor
+    server gerrit-2 $GERRIT_MASTER_2_URL:8080
+
+backend ssh
+    mode tcp
+    option redispatch
+    option httpchk GET /config/server/healthcheck~status HTTP/1.0
+    http-check expect status 200
+    balance source
+    timeout connect 10s
+    timeout server 5m
+    server gerrit-ssh-1 $GERRIT_MASTER_1_URL:29418 check port 8080 inter 10s
+    server gerrit-ssh-2 $GERRIT_MASTER_2_URL:29418 check port 8080 inter 10s backup
+
+backend gerrit-ssh-1
+    mode http
+    option forwardfor
+    server gerrit-ssh-1 $GERRIT_MASTER_1_URL:29418
+
+backend gerrit-ssh-2
+    mode http
+    option forwardfor
+    server gerrit-ssh-2 $GERRIT_MASTER_2_URL:29418
diff --git a/dual-master/setup.env.template b/dual-master/setup.env.template
index d6cbec8..6858912 100644
--- a/dual-master/setup.env.template
+++ b/dual-master/setup.env.template
@@ -4,9 +4,12 @@
 SERVICE_MASTER2_STACK_NAME:=$(AWS_PREFIX)-service-master-2
 HTTP_HOST_PORT_MASTER2:=8081
 SSH_HOST_PORT_MASTER2:=29419
+HTTP_HOST_PORT_MASTER1:=8080
+SSH_HOST_PORT_MASTER1:=29418
 DNS_ROUTING_STACK_NAME:=$(AWS_PREFIX)-dns-routing
 HOSTED_ZONE_NAME:=yourcompany.com
 MASTER1_SUBDOMAIN:=$(AWS_PREFIX)-master-1.gerrit-demo
 MASTER2_SUBDOMAIN:=$(AWS_PREFIX)-master-2.gerrit-demo
+LB_SUBDOMAIN=$(AWS_PREFIX)-lb.gerrit-demo
 DOCKER_REGISTRY_URI:=<yourAccountId>.dkr.ecr.us-east-1.amazonaws.com
 SSL_CERTIFICATE_ARN=arn:aws:acm:us-east-1:<yourAccountId>:certificate/33e2c235-a4d1-42b7-b866-18d8d744975c
diff --git a/dual-master/syslog-sidecar/Dockerfile b/dual-master/syslog-sidecar/Dockerfile
new file mode 100644
index 0000000..11f3059
--- /dev/null
+++ b/dual-master/syslog-sidecar/Dockerfile
@@ -0,0 +1,11 @@
+FROM alpine:3.4
+MAINTAINER Ryan Schlesinger <ryan@outstand.com>
+
+RUN apk add --no-cache bash syslog-ng
+
+RUN mkdir /sidecar
+COPY config/* /etc/syslog-ng/
+COPY docker-entrypoint.sh /docker-entrypoint.sh
+VOLUME ["/sidecar"]
+CMD ["syslog-ng", "-F"]
+ENTRYPOINT ["/docker-entrypoint.sh"]
diff --git a/dual-master/syslog-sidecar/Makefile b/dual-master/syslog-sidecar/Makefile
new file mode 100644
index 0000000..b2e5e97
--- /dev/null
+++ b/dual-master/syslog-sidecar/Makefile
@@ -0,0 +1,15 @@
+include ../../Makefile.common
+include ../setup.env
+
+IMAGE_NAME:=syslog-sidecar
+
+docker-registry-login:
+	aws ecr get-login-password --region $(AWS_REGION) \
+		| docker login --username AWS --password-stdin $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME)
+
+syslog-sidecar-build:
+	docker build -t aws-gerrit/$(IMAGE_NAME):$(SYSLOG_HEAD_SHA1) .
+	docker tag aws-gerrit/$(IMAGE_NAME):$(SYSLOG_HEAD_SHA1) $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):$(SYSLOG_HEAD_SHA1)
+
+syslog-sidecar-publish: docker-registry-login syslog-sidecar-build
+	docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):$(SYSLOG_HEAD_SHA1)
diff --git a/dual-master/syslog-sidecar/config/syslog-ng-destination.out b/dual-master/syslog-sidecar/config/syslog-ng-destination.out
new file mode 100644
index 0000000..170f7e4
--- /dev/null
+++ b/dual-master/syslog-sidecar/config/syslog-ng-destination.out
@@ -0,0 +1 @@
+  destination d_stdout { pipe("/dev/stdout"); };
diff --git a/dual-master/syslog-sidecar/config/syslog-ng-log.out b/dual-master/syslog-sidecar/config/syslog-ng-log.out
new file mode 100644
index 0000000..6901c41
--- /dev/null
+++ b/dual-master/syslog-sidecar/config/syslog-ng-log.out
@@ -0,0 +1 @@
+log { source(s_all); destination(d_stdout); };
diff --git a/dual-master/syslog-sidecar/config/syslog-ng-plugins.std b/dual-master/syslog-sidecar/config/syslog-ng-plugins.std
new file mode 100644
index 0000000..ef0ce3e
--- /dev/null
+++ b/dual-master/syslog-sidecar/config/syslog-ng-plugins.std
@@ -0,0 +1 @@
+@version: 3.7
diff --git a/dual-master/syslog-sidecar/config/syslog-ng-source.sidecar b/dual-master/syslog-sidecar/config/syslog-ng-source.sidecar
new file mode 100644
index 0000000..1181f78
--- /dev/null
+++ b/dual-master/syslog-sidecar/config/syslog-ng-source.sidecar
@@ -0,0 +1,5 @@
+# sidecar log source for mounting between docker containers
+  network(
+    transport("udp")
+    port("514")
+  );
diff --git a/dual-master/syslog-sidecar/config/syslog-ng-source.std b/dual-master/syslog-sidecar/config/syslog-ng-source.std
new file mode 100644
index 0000000..5a50916
--- /dev/null
+++ b/dual-master/syslog-sidecar/config/syslog-ng-source.std
@@ -0,0 +1,8 @@
+# ---------------------------------------------------------------------------------
+# Default syslog-ng sources; Do not edit this file!
+# append source with line on a file: syslog-ng-source.<package>
+# ---------------------------------------------------------------------------------
+# message generated by Syslog-NG
+  internal();
+# standard Linux log source (this is the default place for the syslog() function to send logs to)
+  unix-dgram("/dev/log");
diff --git a/dual-master/syslog-sidecar/docker-entrypoint.sh b/dual-master/syslog-sidecar/docker-entrypoint.sh
new file mode 100755
index 0000000..be51a09
--- /dev/null
+++ b/dual-master/syslog-sidecar/docker-entrypoint.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# The following two methods are ripped from alpine's syslog-ng package.
+# This allows us (and a user) to customize the config with snippets.
+grep_syslog_conf_entries() {
+  local section="$1" FN filelist
+  grep -v '^#' /etc/syslog-ng/syslog-ng-${section}.std
+  filelist=$(find /etc/syslog-ng/ -maxdepth 1 -type f -name "syslog-ng-${section}.*" | grep -Ev ".backup|.std|~")
+  if [ $? -eq 0 ]
+  then
+    for FN in ${filelist}
+    do
+      grep -v '^#' $FN
+    done
+  fi
+}
+
+update() {
+  local fname='/etc/syslog-ng/syslog-ng.conf'
+  local f_tmp="/etc/syslog-ng/syslog-ng.conf.$$"
+  for ng_std in options source destination filter log
+  do
+    [ -f /etc/syslog-ng/syslog-ng-${ng_std}.std ] || exit 1
+  done
+  {
+    # create options entries
+    grep_syslog_conf_entries plugins
+    echo "options {"
+    grep_syslog_conf_entries options
+    echo "};"
+    # create source entries
+    echo "source s_all {"
+    grep_syslog_conf_entries source
+    echo "};"
+    # create destination entries
+    grep_syslog_conf_entries destination
+    # create filter entries
+    grep_syslog_conf_entries filter
+    # create log entries
+    grep_syslog_conf_entries log
+  } > $f_tmp
+  cp -p $f_tmp $fname
+  rm -f $f_tmp
+}
+
+update
+
+echo Starting "$@"
+exec "$@"
diff --git a/gerrit/Makefile b/gerrit/Makefile
index 2bf2055..a6d6719 100644
--- a/gerrit/Makefile
+++ b/gerrit/Makefile
@@ -7,6 +7,7 @@
 
 gerrit-get-plugins:
 	# Make sure Prometheus Metrics exporter plugin is installed
+	mkdir -p $(@D)/plugins
 
 	@echo "Downloading metrics-reporter-prometheus plugin $(GERRIT_BRANCH)"
 	wget $(GERRIT_CI)/plugin-metrics-reporter-prometheus-bazel-master-$(GERRIT_BRANCH)/$(LAST_BUILD)/metrics-reporter-prometheus/metrics-reporter-prometheus.jar \
@@ -23,6 +24,11 @@
 	-O ./plugins/high-availability.jar \
 	|| { echo >&2 "Cannot download high-availability plugin: Check internet connection. Aborting"; exit 1; }
 
+	@echo "Downloading Health Check plugin $(GERRIT_BRANCH)"
+	wget $(GERRIT_CI)/plugin-healthcheck-bazel-$(GERRIT_BRANCH)/$(LAST_BUILD)/healthcheck/healthcheck.jar \
+	-O ./plugins/healthcheck.jar \
+	|| { echo >&2 "Cannot download healthcheck plugin: Check internet connection. Aborting"; exit 1; }
+
 gerrit-build:
 	cat Dockerfile | \
 		GERRIT_VERSION=$(GERRIT_VERSION) GERRIT_PATCH=$(GERRIT_PATCH) envsubst | \
diff --git a/gerrit/etc/gerrit.config.template b/gerrit/etc/gerrit.config.template
index 5cdbd19..dc11036 100644
--- a/gerrit/etc/gerrit.config.template
+++ b/gerrit/etc/gerrit.config.template
@@ -10,6 +10,7 @@
 [auth]
   type = ldap
   gitBasicAuth = true
+  cookiedomain = .{{ COOKIE_DOMAIN }}
 [ldap]
   server = {{ LDAP_SERVER }}
   username = {{ LDAP_USERNAME }}
@@ -59,6 +60,3 @@
     excludeMetrics = caches/.*
 
 {% endif %}
-
-[auth]
-  cookiedomain = .{{ COOKIE_DOMAIN }}
diff --git a/gerrit/etc/healthcheck.config b/gerrit/etc/healthcheck.config
new file mode 100644
index 0000000..ddea5f1
--- /dev/null
+++ b/gerrit/etc/healthcheck.config
@@ -0,0 +1,9 @@
+[healthcheck]
+  timeout = 10s
+
+[healthcheck "querychanges"]
+  query = status:open OR status:merged OR status:abandoned
+  limit = 0
+
+[healthcheck "auth"]
+  enabled =  false
diff --git a/gerrit/plugins/.empty b/gerrit/plugins/.empty
deleted file mode 100644
index e69de29..0000000
--- a/gerrit/plugins/.empty
+++ /dev/null