Add active-active network load balancer
Register both gerrit-1 and gerrit-2 behind the same network load
balancer to allow serving traffic in an active-active architecture.
To allow so, both gerrit-1 and gerrit-2 needs to expose the same host
ports in order to allow traffic forwarding from the same SSH and HTTP
listeners.
For this reason the HTTP_HOST_PORT_PRIMARY and SSH_HOST_PORT_PRIMARY
variables have been removed, as effectively redundant.
A new PRIMARIES_GERRIT_SUBDOMAIN variable has been introduced so that
the active-active subdomain can be configured.
Note that whilst the haproxy is still installed and available, the
ultimate goal is to remove it and to allow _only_ active/active traffic
to go through the newly introduced load balancer.
Before doing that however a global-refdb needs to be introduced and
target groups need to be configured to maintain sticky sessions.
Bug: Issue 15128
Change-Id: I0ba035d4570ca77b1193c46bb612b2c6ea28d0fb
diff --git a/dual-primary/Makefile b/dual-primary/Makefile
index 6a2e56c..9007fe5 100644
--- a/dual-primary/Makefile
+++ b/dual-primary/Makefile
@@ -76,6 +76,9 @@
ifdef PRIMARY_MAX_COUNT
$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=PrimaryMaxCount,ParameterValue=$(PRIMARY_MAX_COUNT))
endif
+ifdef LOAD_BALANCER_SCHEME
+ $(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=PrimariesGerritLoadBalancerScheme,ParameterValue=$(LOAD_BALANCER_SCHEME))
+endif
$(AWS_FC_COMMAND) create-stack \
--stack-name $(CLUSTER_STACK_NAME) \
@@ -85,6 +88,7 @@
--parameters \
ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS) \
ParameterKey=TemplateBucketName,ParameterValue=$(TEMPLATE_BUCKET_NAME) \
+ ParameterKey=PrimariesGerritCertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
$(GERRIT_OPTIONAL_PARAMS_NETWORK) \
$(CLUSTER_OPTIONAL_PARAMS) \
$(GERRIT_OPTIONAL_PRIMARY_VOLUME) \
@@ -130,8 +134,6 @@
ParameterKey=ReplicaSubdomain,ParameterValue=$(REPLICA_SUBDOMAIN) \
ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
- ParameterKey=HTTPHostPort,ParameterValue=$(HTTP_HOST_PORT_PRIMARY1) \
- ParameterKey=SSHHostPort,ParameterValue=$(SSH_HOST_PORT_PRIMARY1) \
ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG) \
ParameterKey=PeerSubdomain,ParameterValue=$(PRIMARY2_SUBDOMAIN) \
@@ -188,8 +190,6 @@
ParameterKey=ReplicaSubdomain,ParameterValue=$(REPLICA_SUBDOMAIN) \
ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
- ParameterKey=HTTPHostPort,ParameterValue=$(HTTP_HOST_PORT_PRIMARY2) \
- ParameterKey=SSHHostPort,ParameterValue=$(SSH_HOST_PORT_PRIMARY2) \
ParameterKey=GerritKeyPrefix,ParameterValue=$(GERRIT_KEY_PREFIX)\
ParameterKey=DockerImage,ParameterValue=aws-gerrit/gerrit:$(IMAGE_TAG) \
ParameterKey=PeerSubdomain,ParameterValue=$(PRIMARY1_SUBDOMAIN) \
@@ -305,7 +305,10 @@
--parameters \
ParameterKey=Primary1ServiceStackName,ParameterValue=$(SERVICE_PRIMARY1_STACK_NAME) \
ParameterKey=Primary2ServiceStackName,ParameterValue=$(SERVICE_PRIMARY2_STACK_NAME) \
- ParameterKey=LBServiceStackName,ParameterValue=$(LOAD_BALANCER_STACK_NAME)
+ ParameterKey=LBServiceStackName,ParameterValue=$(LOAD_BALANCER_STACK_NAME) \
+ ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+ ParameterKey=PrimariesGerritHostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+ ParameterKey=PrimariesGerritSubdomain,ParameterValue=$(PRIMARIES_GERRIT_SUBDOMAIN)
dashboard:
ifeq ($(METRICS_CLOUDWATCH_ENABLED),true)
diff --git a/dual-primary/README.md b/dual-primary/README.md
index 9ce2085..f08f391 100644
--- a/dual-primary/README.md
+++ b/dual-primary/README.md
@@ -160,12 +160,10 @@
* `DASHBOARD_STACK_NAME` : Optional. Name of the dashboard stack. `gerrit-dashboard` by default.
* `PRIMARY1_SUBDOMAIN`: Optional. Name of the primary 1 sub domain. `gerrit-primary-1-demo` by default.
* `PRIMARY2_SUBDOMAIN`: Optional. Name of the primary 2 sub domain. `gerrit-primary-2-demo` by default.
-* `HTTP_HOST_PORT_PRIMARY1`: Optional. Gerrit Host HTTP port for primary1 (must be different from primary2). `9080` by default.
-* `SSH_HOST_PORT_PRIMARY1:`: Optional. Gerrit Host SSH port for primary1 (must be different from primary2). `29418` by default.
-* `HTTP_HOST_PORT_PRIMARY2`: Optional. Gerrit Host HTTP port for primary2 (must be different from primary1). `9080` by default.
-* `SSH_HOST_PORT_PRIMARY2:`: Optional. Gerrit Host SSH port for primary2 (must be different from primary1). `29418` by default.
* `REPLICA_SUBDOMAIN`: Mandatory. The subdomain of the Gerrit replica. For example: `<AWS_PREFIX>-replica`
* `LB_SUBDOMAIN`: Mandatory. The subdomain of the Gerrit load balancer. For example: `<AWS_PREFIX>-dual-primary`
+* `PRIMARIES_GERRIT_SUBDOMAIN`: Mandatory. The subdomain of the lb serving traffic to both primary gerrit instances.
+ For example: `<AWS_PREFIX>-primaries`
* `PRIMARY_FILESYSTEM_THROUGHPUT_MODE`: Optional. The throughput mode for the primary file system to be created.
default: `bursting`. More info [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html)
* `PRIMARY_FILESYSTEM_PROVISIONED_THROUGHPUT_IN_MIBPS`: Optional. Only used when `PRIMARY_FILESYSTEM_THROUGHPUT_MODE` is set to `provisioned`.
diff --git a/dual-primary/cf-cluster.yml b/dual-primary/cf-cluster.yml
index cf729be..fd456ac 100644
--- a/dual-primary/cf-cluster.yml
+++ b/dual-primary/cf-cluster.yml
@@ -155,6 +155,14 @@
Default: 1
MinValue: 1
MaxValue: 10
+ PrimariesGerritLoadBalancerScheme:
+ Description: The schema of the load balancer serving requests to primary gerrit instances
+ Type: String
+ Default: internet-facing
+ AllowedValues: [internal, internet-facing]
+ PrimariesGerritCertificateArn:
+ Description: SSL Certificates ARN for the load balancer serving requests to the primary gerrit instances
+ Type: String
Conditions:
isProvisionedThroughput: !Equals [!Ref PrimaryFileSystemThroughputMode, "provisioned"]
@@ -167,6 +175,13 @@
- !Equals [!Ref Subnet1IdProp, ""]
- !Equals [!Ref Subnet2IdProp, ""]
+Mappings:
+ Gerrit:
+ Port:
+ HTTPS: 443
+ HTTP: 8080
+ SSH: 29418
+
Resources:
# ECS Resources
ECSCluster:
@@ -289,6 +304,65 @@
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource HAProxyECSAutoScalingGroup --region ${AWS::Region}
+ PrimariesGerritLoadBalancer:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Type: network
+ Scheme: !Ref PrimariesGerritLoadBalancerScheme
+ Name: 'primary-gerrit-instances'
+ LoadBalancerAttributes:
+ - Key: 'load_balancing.cross_zone.enabled'
+ Value: true
+ Subnets:
+ - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+ - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
+
+ PrimariesGerritHTTPTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: PrimariesGerritLoadBalancer
+ Properties:
+ VpcId: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.VPCRef, !Ref VPCIdProp]
+ Port: !FindInMap ['Gerrit', 'Port', 'HTTP']
+ Protocol: TCP
+ HealthCheckPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
+ HealthCheckProtocol: HTTP
+ HealthCheckPath: '/config/server/healthcheck~status'
+ Name: 'primaries-gerrit-http'
+
+ PrimariesGerritHTTPSListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ Properties:
+ Certificates:
+ - CertificateArn: !Ref PrimariesGerritCertificateArn
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref PrimariesGerritHTTPTargetGroup
+ LoadBalancerArn: !Ref PrimariesGerritLoadBalancer
+ Port: !FindInMap ['Gerrit', 'Port', 'HTTPS']
+ Protocol: TLS
+
+ PrimariesGerritSSHTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: PrimariesGerritLoadBalancer
+ Properties:
+ VpcId: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.VPCRef, !Ref VPCIdProp]
+ Port: !FindInMap ['Gerrit', 'Port', 'SSH']
+ Protocol: TCP
+ HealthCheckPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
+ HealthCheckProtocol: HTTP
+ HealthCheckPath: '/config/server/healthcheck~status'
+ Name: 'primaries-gerrit-ssh'
+
+ PrimariesGerritSSHListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref PrimariesGerritSSHTargetGroup
+ LoadBalancerArn: !Ref PrimariesGerritLoadBalancer
+ Port: !FindInMap ['Gerrit', 'Port', 'SSH']
+ Protocol: TCP
+
Primary1ASG:
Type: AWS::CloudFormation::Stack
Properties:
@@ -473,3 +547,23 @@
Value: !If [CreateReplicaEFS, !GetAtt ReplicaGitFileSystemPermanentStack.Outputs.FileSystemID, !Ref ReplicaFileSystemID ]
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ReplicaFileSystemID' ] ]
+ PrimariesGerritHTTPTargetGroup:
+ Description: The target group registering both gerrit1 and gerrit2 serving HTTP traffic
+ Value: !Ref 'PrimariesGerritHTTPTargetGroup'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrimariesGerritHTTPTargetGroup' ] ]
+ PrimariesGerritSSHTargetGroup:
+ Description: The target group registering both gerrit1 and gerrit2 serving SSH traffic
+ Value: !Ref 'PrimariesGerritSSHTargetGroup'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrimariesGerritSSHTargetGroup' ] ]
+ PrimariesGerritLoadBalancerDNSName:
+ Description: The DNS name of the load balancer serving requests to both gerrit1 and gerrit2
+ Value: !GetAtt 'PrimariesGerritLoadBalancer.DNSName'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrimariesGerritLoadBalancerDNSName' ] ]
+ PrimariesGerritCanonicalHostedZoneID:
+ Description: Canonical Hosted Zone ID of the load balancer serving requests to both gerrit1 and gerrit2
+ Value: !GetAtt 'PrimariesGerritLoadBalancer.CanonicalHostedZoneID'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrimariesGerritCanonicalHostedZoneID' ] ]
\ No newline at end of file
diff --git a/dual-primary/cf-dns-route.yml b/dual-primary/cf-dns-route.yml
index 5048f75..1755fbd 100644
--- a/dual-primary/cf-dns-route.yml
+++ b/dual-primary/cf-dns-route.yml
@@ -1,10 +1,14 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: A stack for the Gerrit service Route53 routing.
Parameters:
+ ClusterStackName:
+ Description: Stack name of the Cluster
+ Type: String
+ Default: gerrit-cluster-stack
Primary1ServiceStackName:
- Description: Stack name of the ECS Primary Gerrit service
- Type: String
- Default: gerrit-service-primary-1
+ Description: Stack name of the ECS Primary Gerrit service
+ Type: String
+ Default: gerrit-service-primary-1
Primary2ServiceStackName:
Description: Stack name of the ECS Primary Gerrit service
Type: String
@@ -13,6 +17,12 @@
Description: Stack name of the ECS LB service
Type: String
Default: gerrit-service
+ PrimariesGerritSubdomain:
+ Description: The subdomain of the load balancer serving requests to primary gerrit instances
+ Type: String
+ PrimariesGerritHostedZoneName:
+ Description: The zone name of the load balancer serving requests to primary gerrit instances
+ Type: String
Resources:
Primary1DnsRecord:
@@ -84,3 +94,27 @@
Fn::ImportValue:
!Join [':', [!Ref 'LBServiceStackName', 'CanonicalHostedZoneID']]
EvaluateTargetHealth: False
+
+ PrimariesGerritDnsRecord:
+ Type: AWS::Route53::RecordSet
+ Properties:
+ Name:
+ !Join
+ - '.'
+ - - !Ref PrimariesGerritSubdomain
+ - !Ref PrimariesGerritHostedZoneName
+ HostedZoneName:
+ !Join
+ - ''
+ - - !Ref PrimariesGerritHostedZoneName
+ - '.'
+ Comment: DNS name for the load balancer serving requests to primary gerrit instances
+ Type: A
+ AliasTarget:
+ DNSName:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'PrimariesGerritLoadBalancerDNSName']]
+ HostedZoneId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'PrimariesGerritCanonicalHostedZoneID']]
+ EvaluateTargetHealth: False
\ No newline at end of file
diff --git a/dual-primary/cf-service-primary.yml b/dual-primary/cf-service-primary.yml
index 7aa35c6..d08c07a 100644
--- a/dual-primary/cf-service-primary.yml
+++ b/dual-primary/cf-service-primary.yml
@@ -23,14 +23,6 @@
DockerRegistryUrl:
Description: Docker registry URL
Type: String
- HTTPHostPort:
- Description: Gerrit HTTP port
- Type: Number
- Default: 8080
- SSHHostPort:
- Description: Gerrit SSH port
- Type: Number
- Default: 29418
CertificateArn:
Description: SSL Certificates ARN
Type: String
@@ -207,12 +199,9 @@
Logs: gerrit-logs
Service:
Name: gerrit-primary
- LoadBalancer:
- HTTPPort: 8080
- SSHPort: 29418
- Container:
- HTTPPort: 8080
- SSHPort: 29418
+ Port:
+ HTTP: 8080
+ SSH: 29418
Git:
Daemon:
Port: 9418
@@ -233,12 +222,21 @@
TaskDefinition: !Ref TaskDefinition
LoadBalancers:
- ContainerName: !FindInMap ['Gerrit', 'Service', 'Name']
- ContainerPort: !FindInMap ['Gerrit', 'Container', 'HTTPPort']
+ ContainerPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
TargetGroupArn: !Ref HTTPTargetGroup
- ContainerName: !FindInMap ['Gerrit', 'Service', 'Name']
- ContainerPort: !FindInMap ['Gerrit', 'Container', 'SSHPort']
+ ContainerPort: !FindInMap ['Gerrit', 'Port', 'SSH']
TargetGroupArn: !Ref SSHTargetGroup
-
+ - ContainerName: !FindInMap ['Gerrit', 'Service', 'Name']
+ ContainerPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
+ TargetGroupArn:
+ Fn::ImportValue:
+ !Join [ ':', [ !Ref 'ClusterStackName', 'PrimariesGerritHTTPTargetGroup' ] ]
+ - ContainerName: !FindInMap ['Gerrit', 'Service', 'Name']
+ ContainerPort: !FindInMap ['Gerrit', 'Port', 'SSH']
+ TargetGroupArn:
+ Fn::ImportValue:
+ !Join [ ':', [ !Ref 'ClusterStackName', 'PrimariesGerritSSHTargetGroup' ] ]
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
@@ -259,7 +257,7 @@
- Name: HTTPD_LISTEN_URL
Value: !Sub
- 'proxy-https://*:${HTTPContainerPort}/'
- - { HTTPContainerPort: !FindInMap ['Gerrit', 'Container', 'HTTPPort'] }
+ - { HTTPContainerPort: !FindInMap ['Gerrit', 'Port', 'HTTP'] }
- Name: AWS_REGION
Value: !Ref AWS::Region
- Name: SETUP_REPLICATION
@@ -273,7 +271,7 @@
- Name: HA_PEER_URL
Value: !Sub
- 'http://${PeerSubdomain}.${HostedZoneName}:${HTTPGerritLBPort}'
- - { HTTPGerritLBPort: !FindInMap ['Gerrit', 'LoadBalancer', 'HTTPPort'] }
+ - { HTTPGerritLBPort: !FindInMap ['Gerrit', 'Port', 'HTTP'] }
- Name: HOSTED_ZONE_NAME
Value: !Ref HostedZoneName
- Name: REINDEX_AT_STARTUP
@@ -362,11 +360,11 @@
Cpu: !Ref GerritCPU
Memory: !Ref GerritRAM
PortMappings:
- - ContainerPort: !FindInMap ['Gerrit', 'Container', 'HTTPPort']
- HostPort: !Ref HTTPHostPort
+ - ContainerPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
+ HostPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
Protocol: tcp
- - ContainerPort: !FindInMap ['Gerrit', 'Container', 'SSHPort']
- HostPort: !Ref SSHHostPort
+ - ContainerPort: !FindInMap ['Gerrit', 'Port', 'SSH']
+ HostPort: !FindInMap ['Gerrit', 'Port', 'SSH']
Protocol: tcp
LogConfiguration:
LogDriver: awslogs
@@ -421,9 +419,9 @@
VpcId:
Fn::ImportValue:
!Join [':', [!Ref 'ClusterStackName', 'VPCId']]
- Port: !Ref HTTPHostPort
+ Port: !FindInMap ['Gerrit', 'Port', 'HTTP']
Protocol: TCP
- HealthCheckPort: !Ref HTTPHostPort
+ HealthCheckPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
HealthCheckProtocol: HTTP
HealthCheckPath: '/config/server/healthcheck~status'
Name: !Sub 'gerrit-${GerritInstanceNumber}-primary-http'
@@ -436,7 +434,7 @@
- Type: forward
TargetGroupArn: !Ref HTTPTargetGroup
LoadBalancerArn: !Ref LoadBalancer
- Port: !FindInMap ['Gerrit', 'LoadBalancer', 'HTTPPort']
+ Port: !FindInMap ['Gerrit', 'Port', 'HTTP']
Protocol: TCP
SSHTargetGroup:
@@ -446,9 +444,9 @@
VpcId:
Fn::ImportValue:
!Join [':', [!Ref 'ClusterStackName', 'VPCId']]
- Port: !Ref SSHHostPort
+ Port: !FindInMap ['Gerrit', 'Port', 'SSH']
Protocol: TCP
- HealthCheckPort: !Ref HTTPHostPort
+ HealthCheckPort: !FindInMap ['Gerrit', 'Port', 'HTTP']
HealthCheckProtocol: HTTP
HealthCheckPath: '/config/server/healthcheck~status'
Name: !Sub 'gerrit-${GerritInstanceNumber}-primary-ssh'
@@ -461,7 +459,7 @@
- Type: forward
TargetGroupArn: !Ref SSHTargetGroup
LoadBalancerArn: !Ref LoadBalancer
- Port: !FindInMap ['Gerrit', 'LoadBalancer', 'SSHPort']
+ Port: !FindInMap ['Gerrit', 'Port', 'SSH']
Protocol: TCP
ECSTaskExecutionRoleStack:
diff --git a/dual-primary/setup.env.template b/dual-primary/setup.env.template
index 78a215d..c282ff6 100644
--- a/dual-primary/setup.env.template
+++ b/dual-primary/setup.env.template
@@ -14,10 +14,6 @@
# REMOTE_REPLICATION_TARGET_HOST:=other-site-replication.yourcompany.com
SERVICE_REPLICA_STACK_NAME:=$(AWS_PREFIX)-service-replica
-HTTP_HOST_PORT_PRIMARY2:=8081
-SSH_HOST_PORT_PRIMARY2:=29419
-HTTP_HOST_PORT_PRIMARY1:=8080
-SSH_HOST_PORT_PRIMARY1:=29418
DNS_ROUTING_STACK_NAME:=$(AWS_PREFIX)-dns-routing
DASHBOARD_STACK_NAME:=$(AWS_PREFIX)-dashboard
LOAD_BALANCER_STACK_NAME:=$(AWS_PREFIX)-load-balancer
@@ -26,6 +22,7 @@
PRIMARY2_SUBDOMAIN:=$(AWS_PREFIX)-primary-2.gerrit-demo
REPLICA_SUBDOMAIN:=$(AWS_PREFIX)-replica.gerrit-demo
LB_SUBDOMAIN=$(AWS_PREFIX)-lb.gerrit-demo
+PRIMARIES_GERRIT_SUBDOMAIN=$(AWS_PREFIX)-primaries.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
GERRIT_RAM=6000