Deploy across multiple AZs

Increase availability of all recipes by deploying across two AZs.

Bug: Issue 14898
Change-Id: I4d3eda0b8090aa421397a1b9b7de50c4b6ec8b1f
diff --git a/Configuration.md b/Configuration.md
index a32ec80..84eabc4 100644
--- a/Configuration.md
+++ b/Configuration.md
@@ -61,18 +61,44 @@
 `1024` by default.
 * `GERRIT_CONTAINER_FDS_HARD_LIMIT`: The hard limit for file descriptors allowed in the Gerrit container
 `1024` by default.
+
+* `LOAD_BALANCER_SCHEME`: Optional. The Load Balancer scheme type. `internet-facing` by default.
+  Allowed values: internal, internet-facing
+
+#### NETWORKING
+
+All recipes are deployed in a single VPC, on public subnets that span across two
+AZs.  They can be deployed either in pre-existing VPC where multiple subnets have
+already been created or in a new VPC.
+
+to deploy AWS gerrit in an existing VPC, *ALL* following parameters need to be set.
+
 * `INTERNET_GATEWAY_ID`: Optional. Id of the existing Internet Gateway.
   If not set, create a new Internet Gateway
 * `VPC_ID`: Optional. Id of the existing VPC.
   If not set, create a new VPC.
 * `VPC_CIDR`: Optional. CIDR mask for the VPC.
   `10.0.0.0/16` by default.
-* `SUBNET_ID`: Optional. Id of the existing Subnet.
+* `SUBNET1_ID`: Optional. Id of the existing Subnet1.
   If not set, create a new Network Stack.
-* `SUBNET_CIDR`: Optional. CIDR mask of the Subnet.
+* `SUBNET2_ID`: Optional. Id of the existing Subnet2.
+  If not set, create a new Network Stack.
+* `SUBNET1_CIDR`: Optional. CIDR mask of the Subnet1.
   `10.0.0.0/24` by default.
-* LOAD_BALANCER_SCHEME: Optional. The Load Balancer scheme type. `internet-facing` by default.
-  Allowed values: internal, internet-facing
+  Note that this is ignored when`SUBNET1_ID` is provided
+* `SUBNET2_CIDR`: Optional. CIDR mask of the Subnet2.
+  `10.0.32.0/24` by default.
+  Note that this is ignored when`SUBNET2_ID` is provided
+* `SUBNET1_AZ`: Conditional. The Availability Zone of subnet1
+    the first AZ in the `region` by default.
+    Note that this is mandatory when `SUBNET1_ID` is provided, and it is expected
+    to be AZ in which that subnet belongs.
+* `SUBNET2_AZ`: Conditional. The Availability Zone of subnet2
+  the second AZ in the `region` by default.
+  Note that this is mandatory when `SUBNET2_ID` is provided, and it is expected
+  to be AZ in which that subnet belongs.
+
+When not specified, a new VPC with two subnets in two regions will be created.
 
 #### CloudWatch Monitoring
 
diff --git a/Makefile.common b/Makefile.common
index 21a411a..05f6679 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -146,6 +146,36 @@
 		$(eval GERRIT_OPTIONAL_PARAMS_REPLICA_CAPACITY_PROVIDER := $(GERRIT_OPTIONAL_PARAMS_REPLICA_CAPACITY_PROVIDER) ParameterKey=ReplicaCapacityProviderMaxStepSize,ParameterValue=$(REPLICA_CAPACITY_PROVIDER_MAX_STEP_SIZE))
 endif
 
+set-optional-network-params:
+	$(eval GERRIT_OPTIONAL_PARAMS_NETWORK=)
+ifdef INTERNET_GATEWAY_ID
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=InternetGatewayIdProp,ParameterValue=$(INTERNET_GATEWAY_ID))
+endif
+ifdef VPC_ID
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=VPCIdProp,ParameterValue=$(VPC_ID))
+endif
+ifdef VPC_CIDR
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=VPCCIDR,ParameterValue=$(VPC_CIDR))
+endif
+ifdef SUBNET1_CIDR
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=Subnet1CIDR,ParameterValue=$(SUBNET1_CIDR))
+endif
+ifdef SUBNET1_ID
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=Subnet1IdProp,ParameterValue=$(SUBNET1_ID))
+endif
+ifdef SUBNET1_AZ
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=Subnet1AZProp,ParameterValue=$(SUBNET1_AZ))
+endif
+ifdef SUBNET2_CIDR
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=Subnet2CIDR,ParameterValue=$(SUBNET2_CIDR))
+endif
+ifdef SUBNET2_ID
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=Subnet2IdProp,ParameterValue=$(SUBNET2_ID))
+endif
+ifdef SUBNET2_AZ
+		$(eval GERRIT_OPTIONAL_PARAMS_NETWORK := $(GERRIT_OPTIONAL_PARAMS_NETWORK) ParameterKey=Subnet2AZProp,ParameterValue=$(SUBNET2_AZ))
+endif
+
 confirm-persistent-stack-deletion:
 	@echo ""
 	@echo "* * * * WARNING * * * * this is going to completely destroy the stack, including git data."
diff --git a/common-templates/cf-efs-stack.yml b/common-templates/cf-efs-stack.yml
index 643f987..e7e3004 100644
--- a/common-templates/cf-efs-stack.yml
+++ b/common-templates/cf-efs-stack.yml
@@ -7,14 +7,20 @@
   ProvisionedThroughputInMibps:
     Description:  The fs throughput, measured in MiB/s. Valid values are 1-1024.
     Type: Number
-  PublicSubnet:
-    Description: The public subnet into whith allowing this EFS to be mounted on
+  PublicSubnet1:
+    Description: The mount target of this EFS for subnet1
+    Type: String
+  PublicSubnet2:
+    Description: The mount target of this EFS for subnet2
     Type: String
   SecurityGroupVPCID:
     Description: The ID of the VPC for the security group
     Type: String
-  SecurityGroupCidrIp:
-    Description: The IPv4 address range for the security group, in CIDR format
+  SecurityGroupCidrIp1:
+    Description: The in CIDR range of subnet1 allowed mounting EFS
+    Type: String
+  SecurityGroupCidrIp2:
+    Description: The in CIDR range of subnet2 allowed mounting EFS
     Type: String
   TagValue:
     Description: A tag value for this EFS resource
@@ -33,24 +39,43 @@
         - Key: Name
           Value: !Ref TagValue
 
-  GitMountTarget:
+  GitMountTargetSubnet1:
     Type: AWS::EFS::MountTarget
     Properties:
       FileSystemId: !Ref FileSystem
-      SubnetId: !Ref PublicSubnet
+      SubnetId: !Ref PublicSubnet1
       SecurityGroups:
-        - !Ref MountTargetSecurityGroup
+        - !Ref MountTargetSecurityGroup1
 
-  MountTargetSecurityGroup:
+  GitMountTargetSubnet2:
+    Type: AWS::EFS::MountTarget
+    Properties:
+      FileSystemId: !Ref FileSystem
+      SubnetId: !Ref PublicSubnet2
+      SecurityGroups:
+        - !Ref MountTargetSecurityGroup2
+
+  MountTargetSecurityGroup1:
     Type: AWS::EC2::SecurityGroup
     Properties:
       VpcId: !Ref SecurityGroupVPCID
-      GroupDescription: "Security group for mount target"
+      GroupDescription: "Security group for mount target in subnet 1"
       SecurityGroupIngress:
         - IpProtocol: TCP
           FromPort: 2049
           ToPort: 2049
-          CidrIp: !Ref SecurityGroupCidrIp
+          CidrIp: !Ref SecurityGroupCidrIp1
+
+  MountTargetSecurityGroup2:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      VpcId: !Ref SecurityGroupVPCID
+      GroupDescription: "Security group for mount target in subnet 2"
+      SecurityGroupIngress:
+        - IpProtocol: TCP
+          FromPort: 2049
+          ToPort: 2049
+          CidrIp: !Ref SecurityGroupCidrIp2
 
 Outputs:
   FileSystemID:
diff --git a/common-templates/cf-gerrit-network-stack.yml b/common-templates/cf-gerrit-network-stack.yml
index 8a779be..16e66bf 100644
--- a/common-templates/cf-gerrit-network-stack.yml
+++ b/common-templates/cf-gerrit-network-stack.yml
@@ -10,22 +10,42 @@
     Type: String
     Default: ""
     Description: VPC id. If empty VPC will be created
-  SubnetIdProp:
+  Subnet1IdProp:
     Type: String
     Default: ""
-    Description: Subnet id. If empty Network Stack will be created
-  SubnetCIDR:
+    Description: Subnet1 id. If empty Network Stack will be created
+  Subnet2IdProp:
+    Type: String
+    Default: ""
+    Description: Subnet2 id. If empty Network Stack will be created
+  Subnet1CIDR:
     Type: String
     Default: 10.0.0.0/24
-    Description: Subnet CIDR.
+    Description: Subnet 1 CIDR.
+  Subnet2CIDR:
+    Type: String
+    Default: 10.0.32.0/24
+    Description: Subnet 2 CIDR.
   VPCCIDR:
     Type: String
     Default: 10.0.0.0/16
     Description: VPC CIDR.
+  Subnet1AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet1
+  Subnet2AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet2
 
 Conditions:
   CreateVPC: !Equals [!Ref VPCIdProp, ""]
-  CreateNetworkStack: !Equals [!Ref SubnetIdProp, ""]
+  CreateNetworkStack: !Equals [!Ref Subnet1IdProp, ""]
+  HasSubnet1AZ: !Not
+    - !Equals [!Ref Subnet1AZProp, ""]
+  HasSubnet2AZ: !Not
+    - !Equals [!Ref Subnet2AZProp, ""]
   CreateInternetGateway: !And
      - !Equals [!Ref InternetGatewayIdProp, ""]
      - !Condition CreateNetworkStack
@@ -44,11 +64,29 @@
     Type: AWS::EC2::Subnet
     Properties:
       AvailabilityZone:
-         Fn::Select:
-         - 0
-         - Fn::GetAZs: {Ref: 'AWS::Region'}
+        Fn::If:
+          - HasSubnet1AZ
+          - !Ref 'Subnet1AZProp'
+          - Fn::Select:
+              - 0
+              - Fn::GetAZs: ""
       VpcId: !If [CreateVPC, !Ref 'VPC', !Ref 'VPCIdProp' ]
-      CidrBlock: !Ref 'SubnetCIDR'
+      CidrBlock: !Ref 'Subnet1CIDR'
+      MapPublicIpOnLaunch: true
+
+  PublicSubnetTwo:
+    Condition: CreateNetworkStack
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone:
+        Fn::If:
+          - HasSubnet2AZ
+          - !Ref 'Subnet2AZProp'
+          - Fn::Select:
+              - 1
+              - Fn::GetAZs: ""
+      VpcId: !If [CreateVPC, !Ref 'VPC', !Ref 'VPCIdProp' ]
+      CidrBlock: !Ref 'Subnet2CIDR'
       MapPublicIpOnLaunch: true
 
   # Setup networking resources for the public subnets. Containers
@@ -82,10 +120,24 @@
     Properties:
       SubnetId: !Ref PublicSubnetOne
       RouteTableId: !Ref PublicRouteTable
+  PublicSubnetTwoRouteTableAssociation:
+    Condition: CreateNetworkStack
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      SubnetId: !Ref PublicSubnetTwo
+      RouteTableId: !Ref PublicRouteTable
 Outputs:
   VPCRef:
     Value: !If [CreateVPC, !Ref 'VPC', !Ref 'VPCIdProp' ]
   PublicSubnetOneRef:
-    Value: !If [CreateNetworkStack, !Ref 'PublicSubnetOne', !Ref 'SubnetIdProp' ]
+    Value: !If [CreateNetworkStack, !Ref 'PublicSubnetOne', !Ref 'Subnet1IdProp' ]
+  PublicSubnetOneAZ:
+    Value: !If [CreateNetworkStack, !GetAtt PublicSubnetOne.AvailabilityZone, !Ref 'Subnet1AZProp' ]
   PublicOneCIDR:
-    Value: !Ref 'SubnetCIDR'
+    Value: !Ref 'Subnet1CIDR'
+  PublicSubnetTwoRef:
+    Value: !If [CreateNetworkStack, !Ref 'PublicSubnetTwo', !Ref 'Subnet2IdProp' ]
+  PublicSubnetTwoAZ:
+    Value: !If [CreateNetworkStack, !GetAtt PublicSubnetTwo.AvailabilityZone, !Ref 'Subnet2AZProp' ]
+  PublicTwoCIDR:
+    Value: !Ref 'Subnet2CIDR'
diff --git a/common-templates/cf-gerrit-volume.yml b/common-templates/cf-gerrit-volume.yml
index 05c9b43..d03d038 100644
--- a/common-templates/cf-gerrit-volume.yml
+++ b/common-templates/cf-gerrit-volume.yml
@@ -13,6 +13,9 @@
       Description: Gerrit volume size in GiB
       Type: Number
       Default: 10
+  AvailabilityZone:
+    Description: The Availability Zone in which to create the volume
+    Type: String
 
 Conditions:
   CreateEBSVolume: !Equals [!Ref GerritVolumeId, ""]
@@ -24,10 +27,7 @@
     Condition: CreateEBSVolume
     Type: AWS::EC2::Volume
     Properties:
-      AvailabilityZone: !Select
-        - 0
-        - !GetAZs
-           Ref: 'AWS::Region'
+      AvailabilityZone: !Ref AvailabilityZone
       SnapshotId: !If [CreateEBSVolumeBasedOnSnapshot, !Ref GerritVolumeSnapshotId, !Ref "AWS::NoValue"]
       Size: !If [CreateEBSVolumeBasedOnSnapshot,  !Ref "AWS::NoValue", !Ref GerritVolumeSizeInGiB]
 
diff --git a/common-templates/cf-primary-asg.yml b/common-templates/cf-primary-asg.yml
index 0b09086..86d265c 100644
--- a/common-templates/cf-primary-asg.yml
+++ b/common-templates/cf-primary-asg.yml
@@ -41,6 +41,9 @@
   SubnetId:
     Description: The subnet ID where gerrit primary in the Auto Scaling group can be created
     Type: String
+  AvailabilityZone:
+    Description: The Availability Zone in which to create the volume from a snapshot
+    Type: String
   LogGroupName:
     Description: The log group name
     Type: String
@@ -235,6 +238,7 @@
         GerritVolumeId: ""
         GerritVolumeSnapshotId: !Ref GerritVolumeSnapshotId
         GerritVolumeSizeInGiB: !Ref GerritVolumeSizeInGiB
+        AvailabilityZone: !Ref AvailabilityZone
 
 Outputs:
   PrimaryLaunchConfiguration:
diff --git a/dual-primary/Makefile b/dual-primary/Makefile
index 8b44868..6a2e56c 100644
--- a/dual-primary/Makefile
+++ b/dual-primary/Makefile
@@ -53,7 +53,8 @@
 cluster: cluster-keys set-optional-gerrit-primary-volume \
 			set-optional-params-for-replica-filesystem \
 			set-optional-params-for-replica-auto-scaling-capacity \
-			set-optional-params-for-replica-capacity-provider
+			set-optional-params-for-replica-capacity-provider \
+			set-optional-network-params
 ifdef CLUSTER_INSTANCE_TYPE
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=InstanceType,ParameterValue=$(CLUSTER_INSTANCE_TYPE))
 endif
@@ -66,12 +67,6 @@
 ifdef PRIMARY_FILESYSTEM_ID
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=PrimaryFileSystemID,ParameterValue=$(PRIMARY_FILESYSTEM_ID))
 endif
-ifdef SUBNET_CIDR
-		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=SubnetCIDR,ParameterValue=$(SUBNET_CIDR))
-endif
-ifdef VPC_CIDR
-		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=VPCCIDR,ParameterValue=$(VPC_CIDR))
-endif
 ifdef HA_PROXY_MAX_COUNT
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=HAProxyMaxCount,ParameterValue=$(HA_PROXY_MAX_COUNT))
 endif
@@ -90,9 +85,7 @@
 		--parameters \
 		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS) \
 		ParameterKey=TemplateBucketName,ParameterValue=$(TEMPLATE_BUCKET_NAME) \
-		ParameterKey=InternetGatewayIdProp,ParameterValue=$(INTERNET_GATEWAY_ID) \
-		ParameterKey=VPCIdProp,ParameterValue=$(VPC_ID) \
-		ParameterKey=SubnetIdProp,ParameterValue=$(SUBNET_ID) \
+		$(GERRIT_OPTIONAL_PARAMS_NETWORK) \
 		$(CLUSTER_OPTIONAL_PARAMS) \
 		$(GERRIT_OPTIONAL_PRIMARY_VOLUME) \
 		$(GERRIT_OPTIONAL_PARAMS_REPLICA_FILESYSTEM) \
diff --git a/dual-primary/README.md b/dual-primary/README.md
index 137a3a4..9ce2085 100644
--- a/dual-primary/README.md
+++ b/dual-primary/README.md
@@ -19,23 +19,6 @@
 * `cf-service-replication`: Define a replication stack that will allow git replication
 over the EFS volume, which is mounted by the primary instances.
 
-### Networking
-
-* Single VPC:
- * CIDR: 10.0.0.0/16
-* Single Availability Zone
-* 1 public Subnets:
- * CIDR: 10.0.0.0/24
-* 1 public NLB exposing:
- * Gerrit primary 1 HTTP on port 8080
- * Gerrit primary 1 SSH on port 29418
-* 1 public NLB exposing:
- * Gerrit primary 2 HTTP on port 8081
- * Gerrit primary 2 SSH on port 39418
-* 1 Internet Gateway
-* 2 type A alias DNS entry, for Gerrit primary 1 and 2
-* A wildcard SSL certificate available in [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/)
-
 ### Data persistency
 
 * EBS volumes for:
@@ -93,8 +76,9 @@
 2. Update the `setup.env` to point to existing volumes, for example:
 
 ```bash
-FILESYSTEM_ID=fs-c621b733
-GERRIT_VOLUME_SNAPSHOT_ID=snap-0afa165bdf4881915
+PRIMARY_FILESYSTEM_ID=fs-93514727
+REPLICA_FILESYSTEM_ID=fs-9c514728
+GERRIT_VOLUME_SNAPSHOT_ID=snap-048a5c2dfc14a81eb
 ```
 
 If the network stack was created as part of this deployment (i.e. a new VPC was
@@ -102,9 +86,16 @@
 that the green stack can be deployed in the same VPC, for example:
 
 ```bash
-VPC_ID=vpc-08d2159c53f7a1ff5
-INTERNET_GATEWAY_ID=igw-0c0577829910ce7f3
-SUBNET_ID=subnet-05efd67802b1cbd5b
+VPC_ID=	vpc-03292278512e783c7
+INTERNET_GATEWAY_ID=igw-0cb5b144c294f9411
+
+SUBNET1_ID=subnet-066065ea55fda52cf
+SUBNET1_AZ=us-east-1a
+SUBNET1_CIDR=10.0.0.0/24
+
+SUBNET2_ID=subnet-0fefe45d89ce02b31
+SUBNET2_AZ=us-east-1b
+SUBNET2_CIDR=10.0.32.0/24
 ```
 
 3. Deploy the *green* stack:
diff --git a/dual-primary/cf-cluster.yml b/dual-primary/cf-cluster.yml
index 2797932..cf729be 100644
--- a/dual-primary/cf-cluster.yml
+++ b/dual-primary/cf-cluster.yml
@@ -28,14 +28,30 @@
     Type: String
     Default: ""
     Description: VPC id. If empty VPC will be created
-  SubnetIdProp:
+  Subnet1IdProp:
     Type: String
     Default: ""
-    Description: Subnet id. If empty Network Stack will be created
-  SubnetCIDR:
+    Description: Subnet 1 id. If empty Network Stack will be created
+  Subnet1AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet1
+  Subnet1CIDR:
     Type: String
     Default: 10.0.0.0/24
-    Description: Subnet CIDR.
+    Description: Subnet 1 CIDR.
+  Subnet2IdProp:
+    Type: String
+    Default: ""
+    Description: Subnet id 2. If empty Network Stack will be created
+  Subnet2CIDR:
+    Type: String
+    Default: 10.0.32.0/24
+    Description: Subnet 2 CIDR.
+  Subnet2AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet2
   VPCCIDR:
     Type: String
     Default: 10.0.0.0/16
@@ -148,7 +164,8 @@
     - !Equals [!Ref VPCIdProp, ""]
     - !And
       - !Equals [!Ref InternetGatewayIdProp, ""]
-      - !Equals [!Ref SubnetIdProp, ""]
+      - !Equals [!Ref Subnet1IdProp, ""]
+      - !Equals [!Ref Subnet2IdProp, ""]
 
 Resources:
   # ECS Resources
@@ -191,7 +208,8 @@
     Type: AWS::AutoScaling::AutoScalingGroup
     Properties:
       VPCZoneIdentifier:
-        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
       LaunchConfigurationName: !Ref 'ReplicaLaunchConfiguration'
       MinSize: !Ref ReplicaAutoScalingMinCapacity
       MaxSize: !Ref ReplicaAutoScalingMaxCapacity
@@ -233,7 +251,8 @@
     Type: AWS::AutoScaling::AutoScalingGroup
     Properties:
       VPCZoneIdentifier:
-        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
       LaunchConfigurationName: !Ref 'HAProxyLaunchConfiguration'
       MinSize: '2'
       MaxSize: !Ref HAProxyMaxCount
@@ -285,7 +304,8 @@
         EC2SecurityGroup: !Ref EcsHostSecurityGroup
         EC2InstanceProfile: !Ref EC2InstanceProfile
         FileSystem: !If [CreatePrimaryEFS, !GetAtt PrimaryGitFileSystemPermanentStack.Outputs.FileSystemID, !Ref PrimaryFileSystemID ]
-        SubnetId: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        SubnetId: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        AvailabilityZone: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ, !Ref Subnet1AZProp]
         LogGroupName: !Ref AWS::StackName
         PrimaryMaxCount: !Ref PrimaryMaxCount
         GerritVolumeAttachMaxRetries: !Ref GerritVolumeAttachMaxRetries
@@ -309,7 +329,8 @@
         EC2SecurityGroup: !Ref EcsHostSecurityGroup
         EC2InstanceProfile: !Ref EC2InstanceProfile
         FileSystem: !If [CreatePrimaryEFS, !GetAtt PrimaryGitFileSystemPermanentStack.Outputs.FileSystemID, !Ref PrimaryFileSystemID ]
-        SubnetId: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        SubnetId: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
+        AvailabilityZone: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoAZ, !Ref Subnet2AZProp]
         LogGroupName: !Ref AWS::StackName
         PrimaryMaxCount: !Ref PrimaryMaxCount
         GerritVolumeAttachMaxRetries: !Ref GerritVolumeAttachMaxRetries
@@ -369,9 +390,11 @@
       Parameters:
         FileSystemThroughputMode: !Ref PrimaryFileSystemThroughputMode
         ProvisionedThroughputInMibps: !Ref PrimaryProvisionedThroughputInMibps
-        PublicSubnet: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        PublicSubnet1: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        PublicSubnet2: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
         SecurityGroupVPCID: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.VPCRef, !Ref VPCIdProp]
-        SecurityGroupCidrIp: !Ref SubnetCIDR
+        SecurityGroupCidrIp1: !Ref Subnet1CIDR
+        SecurityGroupCidrIp2: !Ref Subnet2CIDR
         TagValue: "efs-for-gerrit-primaries"
 
   ReplicaGitFileSystemPermanentStack:
@@ -384,9 +407,11 @@
       Parameters:
         FileSystemThroughputMode: !Ref ReplicaFileSystemThroughputMode
         ProvisionedThroughputInMibps: !Ref ReplicaProvisionedThroughputInMibps
-        PublicSubnet: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        PublicSubnet1: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        PublicSubnet2: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
         SecurityGroupVPCID: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.VPCRef, !Ref VPCIdProp]
-        SecurityGroupCidrIp: !Ref SubnetCIDR
+        SecurityGroupCidrIp1: !Ref Subnet1CIDR
+        SecurityGroupCidrIp2: !Ref Subnet2CIDR
         TagValue: "efs-for-gerrit-replicas"
 
   ECSTaskNetworkStack:
@@ -400,8 +425,12 @@
         InternetGatewayIdProp: !Ref 'InternetGatewayIdProp'
         VPCIdProp: !Ref 'VPCIdProp'
         VPCCIDR: !Ref 'VPCCIDR'
-        SubnetIdProp: !Ref 'SubnetIdProp'
-        SubnetCIDR: !Ref 'SubnetCIDR'
+        Subnet1IdProp: !Ref 'Subnet1IdProp'
+        Subnet1CIDR: !Ref 'Subnet1CIDR'
+        Subnet1AZProp: !Ref 'Subnet1AZProp'
+        Subnet2IdProp: !Ref 'Subnet2IdProp'
+        Subnet2CIDR: !Ref 'Subnet2CIDR'
+        Subnet2AZProp: !Ref 'Subnet2AZProp'
 
 Outputs:
   ClusterName:
@@ -416,9 +445,24 @@
       Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
   PublicSubnetOne:
     Description: Public subnet one
-    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
     Export:
       Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
+  PublicSubnetOneAZ:
+    Description: Public subnet one AZ
+    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ, !Ref Subnet1AZProp]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOneAZ' ] ]
+  PublicSubnetTwo:
+    Description: Public subnet two
+    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
+  PublicSubnetTwoAZ:
+    Description: Public subnet two AZ
+    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoAZ, !Ref Subnet2AZProp]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwoAZ' ] ]
   ClusterArn:
     Description: The ARN of the ECS cluster
     Value: !GetAtt ECSCluster.Arn
diff --git a/dual-primary/cf-service-lb.yml b/dual-primary/cf-service-lb.yml
index 1d49ffe..a00b66f 100644
--- a/dual-primary/cf-service-lb.yml
+++ b/dual-primary/cf-service-lb.yml
@@ -171,9 +171,14 @@
         Properties:
             Type: network
             Scheme: !Ref 'LoadBalancerScheme'
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'LBServiceName', 'alb']]
diff --git a/dual-primary/cf-service-primary.yml b/dual-primary/cf-service-primary.yml
index d2f1edc..8d51992 100644
--- a/dual-primary/cf-service-primary.yml
+++ b/dual-primary/cf-service-primary.yml
@@ -402,9 +402,14 @@
         Properties:
             Type: network
             Scheme: !Ref 'LoadBalancerScheme'
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !FindInMap ['Gerrit', 'Service', 'Name'], 'nlb']]
diff --git a/dual-primary/cf-service-replica.yml b/dual-primary/cf-service-replica.yml
index 85e9604..aad3eee 100644
--- a/dual-primary/cf-service-replica.yml
+++ b/dual-primary/cf-service-replica.yml
@@ -433,9 +433,14 @@
         Properties:
             Type: network
             Scheme: !Ref 'LoadBalancerScheme'
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
diff --git a/dual-primary/cf-service-replication.yml b/dual-primary/cf-service-replication.yml
index 9930d3f..f8f0594 100644
--- a/dual-primary/cf-service-replication.yml
+++ b/dual-primary/cf-service-replication.yml
@@ -155,9 +155,14 @@
         Properties:
             Type: network
             Scheme: internal
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !Ref GitReplicationServiceName, 'nlb']]
diff --git a/dual-primary/setup.env.template b/dual-primary/setup.env.template
index 51b234f..78a215d 100644
--- a/dual-primary/setup.env.template
+++ b/dual-primary/setup.env.template
@@ -85,4 +85,19 @@
 
 REPLICA_CAPACITY_PROVIDER_TARGET=50
 REPLICA_CAPACITY_PROVIDER_MIN_STEP_SIZE=1
-REPLICA_CAPACITY_PROVIDER_MAX_STEP_SIZE=2
\ No newline at end of file
+REPLICA_CAPACITY_PROVIDER_MAX_STEP_SIZE=2
+
+# Existing VPC settings
+VPC_ID=
+VPC_CIDR=
+INTERNET_GATEWAY_ID=
+
+# Existing SUBNET1 settings
+SUBNET1_CIDR=
+SUBNET1_ID=
+SUBNET1_AZ=
+
+# Existing SUBNET2 settings
+SUBNET2_CIDR=
+SUBNET2_ID=
+SUBNET2_AZ=
\ No newline at end of file
diff --git a/primary-replica/Makefile b/primary-replica/Makefile
index d7f7313..0afea75 100644
--- a/primary-replica/Makefile
+++ b/primary-replica/Makefile
@@ -34,16 +34,11 @@
 cluster: cluster-keys set-optional-gerrit-primary-volume \
 			set-optional-params-for-replica-filesystem \
 			set-optional-params-for-replica-auto-scaling-capacity \
-			set-optional-params-for-replica-capacity-provider
+			set-optional-params-for-replica-capacity-provider \
+			set-optional-network-params
 ifdef CLUSTER_INSTANCE_TYPE
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=InstanceType,ParameterValue=$(CLUSTER_INSTANCE_TYPE))
 endif
-ifdef SUBNET_CIDR
-		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=SubnetCIDR,ParameterValue=$(SUBNET_CIDR))
-endif
-ifdef VPC_CIDR
-		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=VPCCIDR,ParameterValue=$(VPC_CIDR))
-endif
 ifdef PRIMARY_MAX_COUNT
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=PrimaryMaxCount,ParameterValue=$(PRIMARY_MAX_COUNT))
 endif
@@ -56,9 +51,7 @@
 		--parameters \
 		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS) \
 		ParameterKey=TemplateBucketName,ParameterValue=$(TEMPLATE_BUCKET_NAME) \
-		ParameterKey=InternetGatewayIdProp,ParameterValue=$(INTERNET_GATEWAY_ID) \
-		ParameterKey=VPCIdProp,ParameterValue=$(VPC_ID) \
-		ParameterKey=SubnetIdProp,ParameterValue=$(SUBNET_ID) \
+		$(GERRIT_OPTIONAL_PARAMS_NETWORK) \
 		$(CLUSTER_OPTIONAL_PARAMS) \
 		$(GERRIT_OPTIONAL_PRIMARY_VOLUME) \
 		$(GERRIT_OPTIONAL_PARAMS_REPLICA_FILESYSTEM) \
diff --git a/primary-replica/README.md b/primary-replica/README.md
index ed0fd7f..d710050 100644
--- a/primary-replica/README.md
+++ b/primary-replica/README.md
@@ -12,25 +12,6 @@
 * `cf-dns-route`: define the DNS routing for the service
 * `cf-dashboard`: define the CloudWatch dashboard for the services
 
-### Networking
-
-* Single VPC:
- * CIDR: 10.0.0.0/16
-* Single Availability Zone
-* 1 public Subnets:
- * CIDR: 10.0.0.0/24
-* 1 public NLB exposing:
- * Gerrit primary HTTP on port 8080
- * Gerrit primary SSH on port 29418
-* 1 public NLB exposing:
- * Gerrit replica HTTP on port 8081
- * Gerrit replica SSH on port 39418
- * SSH agent on port 1022
- * Git daemon on port 9418
-* 1 Internet Gateway
-* 2 type A alias DNS entry, for Gerrit primary and replica
-* A SSL certificate available in [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/)
-
 ### Data persistency
 
 * EBS volumes for:
diff --git a/primary-replica/cf-cluster.yml b/primary-replica/cf-cluster.yml
index acf14e3..f80166e 100644
--- a/primary-replica/cf-cluster.yml
+++ b/primary-replica/cf-cluster.yml
@@ -31,14 +31,30 @@
     Type: String
     Default: ""
     Description: VPC id. If empty VPC will be created
-  SubnetIdProp:
+  Subnet1IdProp:
     Type: String
     Default: ""
-    Description: Subnet id. If empty Network Stack will be created
-  SubnetCIDR:
+    Description: Subnet id 1. If empty Network Stack will be created
+  Subnet1CIDR:
     Type: String
     Default: 10.0.0.0/24
-    Description: Subnet CIDR.
+    Description: Subnet 1 CIDR.
+  Subnet1AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet1
+  Subnet2IdProp:
+    Type: String
+    Default: ""
+    Description: Subnet id 2. If empty Network Stack will be created
+  Subnet2CIDR:
+    Type: String
+    Default: 10.0.32.0/24
+    Description: Subnet 2 CIDR.
+  Subnet2AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet2
   VPCCIDR:
     Type: String
     Default: 10.0.0.0/16
@@ -124,7 +140,8 @@
     - !Equals [!Ref VPCIdProp, ""]
     - !And
       - !Equals [!Ref InternetGatewayIdProp, ""]
-      - !Equals [!Ref SubnetIdProp, ""]
+      - !Equals [!Ref Subnet1IdProp, ""]
+      - !Equals [!Ref Subnet2IdProp, ""]
 
 Resources:
   # ECS Resources
@@ -167,7 +184,7 @@
     Type: AWS::AutoScaling::AutoScalingGroup
     Properties:
       VPCZoneIdentifier:
-        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
       LaunchConfigurationName: !Ref 'PrimaryLaunchConfiguration'
       MinSize: '1'
       MaxSize: !Ref PrimaryMaxCount
@@ -290,7 +307,8 @@
     Type: AWS::AutoScaling::AutoScalingGroup
     Properties:
       VPCZoneIdentifier:
-        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        - !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
       LaunchConfigurationName: !Ref 'ReplicaLaunchConfiguration'
       MinSize: !Ref ReplicaAutoScalingMinCapacity
       MaxSize: !Ref ReplicaAutoScalingMaxCapacity
@@ -415,8 +433,12 @@
         InternetGatewayIdProp: !Ref 'InternetGatewayIdProp'
         VPCIdProp: !Ref 'VPCIdProp'
         VPCCIDR: !Ref 'VPCCIDR'
-        SubnetIdProp: !Ref 'SubnetIdProp'
-        SubnetCIDR: !Ref 'SubnetCIDR'
+        Subnet1IdProp: !Ref 'Subnet1IdProp'
+        Subnet1CIDR: !Ref 'Subnet1CIDR'
+        Subnet1AZProp: !Ref 'Subnet1AZProp'
+        Subnet2IdProp: !Ref 'Subnet2IdProp'
+        Subnet2CIDR: !Ref 'Subnet2CIDR'
+        Subnet2AZProp: !Ref 'Subnet2AZProp'
 
   GerritVolumeStack:
     Type: AWS::CloudFormation::Stack
@@ -427,6 +449,7 @@
         GerritVolumeId: !Ref 'GerritVolumeId'
         GerritVolumeSnapshotId: !Ref 'GerritVolumeSnapshotId'
         GerritVolumeSizeInGiB: !Ref 'GerritVolumeSizeInGiB'
+        AvailabilityZone: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ
 
   ReplicaGitFileSystemPermanentStack:
     Type: AWS::CloudFormation::Stack
@@ -438,9 +461,11 @@
       Parameters:
         FileSystemThroughputMode: !Ref ReplicaFileSystemThroughputMode
         ProvisionedThroughputInMibps: !Ref ReplicaProvisionedThroughputInMibps
-        PublicSubnet: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+        PublicSubnet1: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
+        PublicSubnet2: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
         SecurityGroupVPCID: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.VPCRef, !Ref VPCIdProp]
-        SecurityGroupCidrIp: !Ref SubnetCIDR
+        SecurityGroupCidrIp1: !Ref Subnet1CIDR
+        SecurityGroupCidrIp2: !Ref Subnet2CIDR
         TagValue: "efs-for-gerrit-replicas"
 
 Outputs:
@@ -456,9 +481,24 @@
       Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
   PublicSubnetOne:
     Description: Public subnet one
-    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref SubnetIdProp]
+    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef, !Ref Subnet1IdProp]
     Export:
       Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
+  PublicSubnetOneAZ:
+    Description: Public subnet one AZ
+    Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOneAZ' ] ]
+  PublicSubnetTwo:
+    Description: Public subnet two
+    Value: !If [NetworkStackNeeded, !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef, !Ref Subnet2IdProp]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
+  PublicSubnetTwoAZ:
+    Description: Public subnet two AZ
+    Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoAZ
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwoAZ' ] ]
   ClusterArn:
     Description: The ARN of the ECS cluster
     Value: !GetAtt ECSCluster.Arn
diff --git a/primary-replica/cf-service-primary.yml b/primary-replica/cf-service-primary.yml
index f41b430..981d85a 100644
--- a/primary-replica/cf-service-primary.yml
+++ b/primary-replica/cf-service-primary.yml
@@ -343,9 +343,14 @@
         Properties:
             Type: network
             Scheme: !Ref 'LoadBalancerScheme'
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
diff --git a/primary-replica/cf-service-replica.yml b/primary-replica/cf-service-replica.yml
index 2e3d1c6..11f5351 100644
--- a/primary-replica/cf-service-replica.yml
+++ b/primary-replica/cf-service-replica.yml
@@ -433,9 +433,14 @@
         Properties:
             Type: network
             Scheme: !Ref 'LoadBalancerScheme'
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
diff --git a/primary-replica/setup.env.template b/primary-replica/setup.env.template
index 7a3e5f7..8186090 100644
--- a/primary-replica/setup.env.template
+++ b/primary-replica/setup.env.template
@@ -63,4 +63,19 @@
 
 REPLICA_CAPACITY_PROVIDER_TARGET=50
 REPLICA_CAPACITY_PROVIDER_MIN_STEP_SIZE=1
-REPLICA_CAPACITY_PROVIDER_MAX_STEP_SIZE=2
\ No newline at end of file
+REPLICA_CAPACITY_PROVIDER_MAX_STEP_SIZE=2
+
+# Existing VPC settings
+VPC_ID=
+VPC_CIDR=
+INTERNET_GATEWAY_ID=
+
+# Existing SUBNET1 settings
+SUBNET1_CIDR=
+SUBNET1_ID=
+SUBNET1_AZ=
+
+# Existing SUBNET2 settings
+SUBNET2_CIDR=
+SUBNET2_ID=
+SUBNET2_AZ=
\ No newline at end of file
diff --git a/single-primary/Makefile b/single-primary/Makefile
index f878ed7..067c9e8 100644
--- a/single-primary/Makefile
+++ b/single-primary/Makefile
@@ -22,16 +22,10 @@
 						$(optional_git_gc_targets_creation) \
 						dns-routing wait-for-dns-routing-creation
 
-cluster: cluster-keys set-optional-gerrit-primary-volume
+cluster: cluster-keys set-optional-gerrit-primary-volume set-optional-network-params
 ifdef CLUSTER_INSTANCE_TYPE
 		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=InstanceType,ParameterValue=$(CLUSTER_INSTANCE_TYPE))
 endif
-ifdef SUBNET_CIDR
-		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=SubnetCIDR,ParameterValue=$(SUBNET_CIDR))
-endif
-ifdef VPC_CIDR
-		$(eval CLUSTER_OPTIONAL_PARAMS := $(CLUSTER_OPTIONAL_PARAMS) ParameterKey=VPCCIDR,ParameterValue=$(VPC_CIDR))
-endif
 
 	$(AWS_FC_COMMAND) create-stack \
 		--stack-name $(CLUSTER_STACK_NAME) \
@@ -41,9 +35,7 @@
 		--parameters \
 		ParameterKey=ECSKeyName,ParameterValue=$(CLUSTER_KEYS) \
 		ParameterKey=TemplateBucketName,ParameterValue=$(TEMPLATE_BUCKET_NAME) \
-		ParameterKey=InternetGatewayIdProp,ParameterValue=$(INTERNET_GATEWAY_ID) \
-		ParameterKey=VPCIdProp,ParameterValue=$(VPC_ID) \
-		ParameterKey=SubnetIdProp,ParameterValue=$(SUBNET_ID) \
+		$(GERRIT_OPTIONAL_PARAMS_NETWORK) \
 		$(CLUSTER_OPTIONAL_PARAMS) \
 		$(GERRIT_OPTIONAL_PRIMARY_VOLUME)
 
diff --git a/single-primary/README.md b/single-primary/README.md
index 66dd6ef..05bf3db 100644
--- a/single-primary/README.md
+++ b/single-primary/README.md
@@ -10,20 +10,6 @@
 * `cf-service`: defined the service stack running Gerrit
 * `cf-dns-route`: defined the DNS routing for the service
 
-### Networking
-
-* Single VPC:
- * CIDR: 10.0.0.0/16
-* Single Availability Zone
-* 1 public Subnets:
- * CIDR: 10.0.0.0/24
-* 1 public NLB exposing:
- * HTTP on port 8080
- * SSH on port 29418
-* 1 Internet Gateway
-* 1 type A alias DNS entry
-* A SSL certificate available in [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/)
-
 ### Data persistency
 
 * EBS volumes for:
diff --git a/single-primary/cf-cluster.yml b/single-primary/cf-cluster.yml
index 6c6f9b4..a9d667d 100644
--- a/single-primary/cf-cluster.yml
+++ b/single-primary/cf-cluster.yml
@@ -36,14 +36,30 @@
     Type: String
     Default: ""
     Description: VPC id. If empty VPC will be created
-  SubnetIdProp:
+  Subnet1IdProp:
     Type: String
     Default: ""
-    Description: Subnet id. If empty Network Stack will be created
-  SubnetCIDR:
+    Description: ID of Subnet 1. If empty Network Stack will be created
+  Subnet1CIDR:
     Type: String
     Default: 10.0.0.0/24
-    Description: Subnet CIDR.
+    Description: Subnet 1 CIDR.
+  Subnet1AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet1
+  Subnet2IdProp:
+    Type: String
+    Default: ""
+    Description: ID of Subnet 2. If empty Network Stack will be created
+  Subnet2CIDR:
+    Type: String
+    Default: 10.0.32.0/24
+    Description: Subnet 2 CIDR.
+  Subnet2AZProp:
+    Type: String
+    Default: ""
+    Description: The Availability Zone of subnet2
   VPCCIDR:
     Type: String
     Default: 10.0.0.0/16
@@ -265,8 +281,12 @@
         InternetGatewayIdProp: !Ref 'InternetGatewayIdProp'
         VPCIdProp: !Ref 'VPCIdProp'
         VPCCIDR: !Ref 'VPCCIDR'
-        SubnetIdProp: !Ref 'SubnetIdProp'
-        SubnetCIDR: !Ref 'SubnetCIDR'
+        Subnet1IdProp: !Ref 'Subnet1IdProp'
+        Subnet1CIDR: !Ref 'Subnet1CIDR'
+        Subnet1AZProp: !Ref 'Subnet1AZProp'
+        Subnet2IdProp: !Ref 'Subnet2IdProp'
+        Subnet2CIDR: !Ref 'Subnet2CIDR'
+        Subnet2AZProp: !Ref 'Subnet2AZProp'
 
   GerritVolumeStack:
     Type: AWS::CloudFormation::Stack
@@ -277,6 +297,7 @@
         GerritVolumeId: !Ref 'GerritVolumeId'
         GerritVolumeSnapshotId: !Ref 'GerritVolumeSnapshotId'
         GerritVolumeSizeInGiB: !Ref 'GerritVolumeSizeInGiB'
+        AvailabilityZone: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ
 
 Outputs:
   ClusterName:
@@ -294,6 +315,21 @@
     Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef
     Export:
       Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
+  PublicSubnetOneAZ:
+    Description: Public subnet one AZ
+    Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOneAZ' ] ]
+  PublicSubnetTwo:
+    Description: Public subnet two
+    Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
+  PublicSubnetTwoAZ:
+    Description: Public subnet two AZ
+    Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoAZ
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwoAZ' ] ]
   ClusterArn:
     Description: The ARN of the ECS cluster
     Value: !GetAtt ECSCluster.Arn
diff --git a/single-primary/cf-service.yml b/single-primary/cf-service.yml
index 548a1af..eb12568 100644
--- a/single-primary/cf-service.yml
+++ b/single-primary/cf-service.yml
@@ -317,9 +317,14 @@
         Properties:
             Type: network
             Scheme: !Ref 'LoadBalancerScheme'
+            LoadBalancerAttributes:
+              - Key: 'load_balancing.cross_zone.enabled'
+                Value: true
             Subnets:
               - Fn::ImportValue:
                   !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetTwo']]
             Tags:
                 - Key: Name
                   Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
diff --git a/single-primary/setup.env.template b/single-primary/setup.env.template
index c4b1c10..b3689d9 100644
--- a/single-primary/setup.env.template
+++ b/single-primary/setup.env.template
@@ -36,4 +36,19 @@
 GIT_GC_ENABLED=false
 SERVICE_GIT_GC_STACK_NAME=$(AWS_PREFIX)-scheduled-gc
 GIT_GC_CRON_EXPRESSION="0 2 ? * SAT *"
-GIT_GC_PROJECT_LIST="All-Users"
\ No newline at end of file
+GIT_GC_PROJECT_LIST="All-Users"
+
+# Existing VPC settings
+VPC_ID=
+VPC_CIDR=
+INTERNET_GATEWAY_ID=
+
+# Existing SUBNET1 settings
+SUBNET1_CIDR=
+SUBNET1_ID=
+SUBNET1_AZ=
+
+# Existing SUBNET2 settings
+SUBNET2_CIDR=
+SUBNET2_ID=
+SUBNET2_AZ=