├── README.md ├── ha_deployment └── ha_deployment.template.yaml ├── ha_stateful ├── ha_stateful.zookeeper.template.yaml └── ha_stateful_stack.sh ├── ha_willreplace ├── ha_willreplace.template.yaml ├── ha_willreplace_migrations.template.yaml ├── ha_willreplace_migrations_stack_update.sh └── ha_willreplace_stack_update.sh ├── self_terminating_instance └── self_terminating_instance.template.json └── vpn_bastion ├── vpn_bastion.yaml ├── vpn_s3_bucket.yaml └── vpn_stack.sh /README.md: -------------------------------------------------------------------------------- 1 | # Cloudformation-examples 2 | 3 | Some useful CF templates to go along witb tutorial articles: 4 | 5 | * [How to create a self terminating instance](https://techpunch.co.uk/development/how-to-use-cloudformation-in-aws-to-create-a-self-terminating-instance) 6 | * [High availability deployments using Ansible and Packer](https://techpunch.co.uk/development/high-availability-image-deployments-in-aws-using-ansible-and-packer) 7 | * [How to create an OpenVPN Bastion machine in AWS](https://techpunch.co.uk/development/how-to-create-an-openvpn-bastion-machine-in-aws) 8 | * [How to use the will replace feature of AWS Auto Scaling Groups](https://techpunch.co.uk/development/how-to-use-the-will-replace-feature-of-aws-auto-scaling-groups) 9 | * [How to perform high availability deployments of stateful applications in AWS - Zookeeper edition](https://techpunch.co.uk/development/how-to-perform-high-availability-deployments-of-stateful-applications-in-aws-zookeeper-edition) 10 | -------------------------------------------------------------------------------- /ha_deployment/ha_deployment.template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | AWSTemplateFormatVersion: '2010-09-09' 4 | Description: HA Example 5 | 6 | Parameters: 7 | InstanceType: 8 | Description: WebServer EC2 instance type 9 | Type: String 10 | Default: t2.nano 11 | AllowedValues: 12 | - t2.nano 13 | - t2.small 14 | ConstraintDescription: must be a valid EC2 instance type. 15 | KeyName: 16 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 17 | Type: AWS::EC2::KeyPair::KeyName 18 | ConstraintDescription: must be the name of an existing EC2 KeyPair. 19 | InstanceCount: 20 | Description: Number of EC2 instances to launch 21 | Type: Number 22 | Default: '1' 23 | 24 | Mappings: 25 | AWSInstanceType2Arch: 26 | t2.nano: 27 | Arch: HVM64 28 | t2.micro: 29 | Arch: HVM64 30 | AWSRegionArch2AMI: 31 | us-east-1: 32 | PV64: ami-1ccae774 33 | HVM64: ami-1ecae776 34 | HVMG2: ami-8c6b40e4 35 | us-west-2: 36 | PV64: ami-ff527ecf 37 | HVM64: ami-e7527ed7 38 | HVMG2: ami-abbe919b 39 | us-west-1: 40 | PV64: ami-d514f291 41 | HVM64: ami-d114f295 42 | HVMG2: ami-f31ffeb7 43 | eu-west-1: 44 | PV64: ami-bf0897c8 45 | HVM64: ami-a10897d6 46 | HVMG2: ami-d5bc24a2 47 | eu-central-1: 48 | PV64: ami-ac221fb1 49 | HVM64: ami-a8221fb5 50 | HVMG2: ami-7cd2ef61 51 | ap-northeast-1: 52 | PV64: ami-27f90e27 53 | HVM64: ami-cbf90ecb 54 | HVMG2: ami-6318e863 55 | ap-southeast-1: 56 | PV64: ami-acd9e8fe 57 | HVM64: ami-68d8e93a 58 | HVMG2: ami-3807376a 59 | ap-southeast-2: 60 | PV64: ami-ff9cecc5 61 | HVM64: ami-fd9cecc7 62 | HVMG2: ami-89790ab3 63 | sa-east-1: 64 | PV64: ami-bb2890a6 65 | HVM64: ami-b52890a8 66 | HVMG2: NOT_SUPPORTED 67 | cn-north-1: 68 | PV64: ami-fa39abc3 69 | HVM64: ami-f239abcb 70 | HVMG2: NOT_SUPPORTED 71 | 72 | Resources: 73 | InternetGateway: 74 | Type: AWS::EC2::InternetGateway 75 | Properties: 76 | Tags: 77 | - Key: Application 78 | Value: !Ref AWS::StackId 79 | 80 | VPC: 81 | Type: AWS::EC2::VPC 82 | Properties: 83 | CidrBlock: 10.0.0.0/16 84 | Tags: 85 | - Key: Application 86 | Value: !Ref AWS::StackId 87 | 88 | AttachGateway: 89 | Type: AWS::EC2::VPCGatewayAttachment 90 | Properties: 91 | VpcId: !Ref VPC 92 | InternetGatewayId: !Ref InternetGateway 93 | 94 | PublicSubnet: 95 | Type: AWS::EC2::Subnet 96 | Properties: 97 | VpcId: !Ref VPC 98 | CidrBlock: 10.0.1.0/24 99 | Tags: 100 | - Key: Application 101 | Value: !Ref AWS::StackId 102 | 103 | RouteTable: 104 | Type: AWS::EC2::RouteTable 105 | Properties: 106 | VpcId: !Ref VPC 107 | Tags: 108 | - Key: Application 109 | Value: !Ref AWS::StackId 110 | 111 | Route: 112 | Type: AWS::EC2::Route 113 | DependsOn: AttachGateway 114 | Properties: 115 | RouteTableId: !Ref RouteTable 116 | DestinationCidrBlock: 0.0.0.0/0 117 | GatewayId: !Ref InternetGateway 118 | 119 | PublicSubnetRouteTableAssociation: 120 | Type: AWS::EC2::SubnetRouteTableAssociation 121 | Properties: 122 | SubnetId: !Ref PublicSubnet 123 | RouteTableId: !Ref RouteTable 124 | 125 | PublicSshSecurityGroup: 126 | Type: AWS::EC2::SecurityGroup 127 | Properties: 128 | GroupDescription: Enable external SSH access 129 | VpcId: !Ref VPC 130 | SecurityGroupIngress: 131 | - IpProtocol: tcp 132 | FromPort: '22' 133 | ToPort: '22' 134 | CidrIp: 0.0.0.0/0 135 | 136 | PublicWebSecurityGroup: 137 | Type: AWS::EC2::SecurityGroup 138 | Properties: 139 | GroupDescription: Enable external web access 140 | VpcId: !Ref VPC 141 | SecurityGroupIngress: 142 | - IpProtocol: tcp 143 | FromPort: '80' 144 | ToPort: '80' 145 | CidrIp: 0.0.0.0/0 146 | 147 | WebServerGroup: 148 | Type: AWS::AutoScaling::AutoScalingGroup 149 | Properties: 150 | VPCZoneIdentifier: 151 | - !Ref PublicSubnet 152 | LaunchConfigurationName: !Ref WebLaunchConfig 153 | MinSize: '1' 154 | MaxSize: '3' 155 | DesiredCapacity: !Ref InstanceCount 156 | LoadBalancerNames: 157 | - !Ref WebElasticLoadBalancer 158 | HealthCheckGracePeriod: '300' 159 | HealthCheckType: ELB 160 | CreationPolicy: 161 | ResourceSignal: 162 | Count: '1' 163 | Timeout: PT5M 164 | UpdatePolicy: 165 | AutoScalingRollingUpdate: 166 | MinInstancesInService: '1' 167 | MaxBatchSize: '1' 168 | PauseTime: PT5M 169 | WaitOnResourceSignals: 'true' 170 | 171 | WebLaunchConfig: 172 | Type: AWS::AutoScaling::LaunchConfiguration 173 | Properties: 174 | AssociatePublicIpAddress: 'true' 175 | ImageId: 176 | !FindInMap 177 | - AWSRegionArch2AMI 178 | - !Ref AWS::Region 179 | - !FindInMap 180 | - AWSInstanceType2Arch 181 | - !Ref InstanceType 182 | - Arch 183 | InstanceType: 184 | !Ref InstanceType 185 | SecurityGroups: 186 | - !Ref PublicSshSecurityGroup 187 | - !Ref PublicWebSecurityGroup 188 | KeyName: !Ref KeyName 189 | UserData: 190 | Fn::Base64: !Sub | 191 | #!/bin/bash 192 | yum update 193 | yum install -y aws-cfn-bootstrap 194 | /opt/aws/bin/cfn-init \ 195 | --resource WebLaunchConfig \ 196 | --stack ${AWS::StackName} \ 197 | --region ${AWS::Region} 198 | yum install -y nginx 199 | service nginx start 200 | sleep 30s 201 | ps auxw | grep -P '\b'nginx'(?!-)\b' 202 | /opt/aws/bin/cfn-signal -e $? \ 203 | --stack ${AWS::StackName} \ 204 | --resource WebServerGroup \ 205 | --region ${AWS::Region} 206 | 207 | WebElasticLoadBalancer: 208 | Type: AWS::ElasticLoadBalancing::LoadBalancer 209 | Properties: 210 | CrossZone: 'true' 211 | Scheme: internet-facing 212 | SecurityGroups: 213 | - !Ref PublicWebSecurityGroup 214 | Subnets: 215 | - !Ref PublicSubnet 216 | Listeners: 217 | - LoadBalancerPort: '80' 218 | InstancePort: '80' 219 | Protocol: HTTP 220 | 221 | -------------------------------------------------------------------------------- /ha_stateful/ha_stateful.zookeeper.template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # ha_stateful.zookeeper.template.yaml 3 | 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Description: HA Stateful Zookeeper Example 6 | 7 | Parameters: 8 | InstanceType: 9 | Description: Stateful Zookeeper EC2 instance type 10 | Type: String 11 | Default: t2.nano 12 | AllowedValues: 13 | - t2.nano 14 | - t2.micro 15 | ConstraintDescription: must be a valid EC2 instance type. 16 | KeyName: 17 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 18 | Type: AWS::EC2::KeyPair::KeyName 19 | ConstraintDescription: must be the name of an existing EC2 KeyPair. 20 | InstanceImageId: 21 | Description: Image ID for EC2 instances 22 | Type: String 23 | 24 | Resources: 25 | InternetGateway: 26 | Type: AWS::EC2::InternetGateway 27 | Properties: 28 | Tags: 29 | - Key: Stack 30 | Value: !Ref AWS::StackId 31 | 32 | VPC: 33 | Type: AWS::EC2::VPC 34 | Properties: 35 | CidrBlock: 10.0.0.0/22 36 | EnableDnsSupport: 'true' 37 | EnableDnsHostnames: 'true' 38 | Tags: 39 | - Key: Stack 40 | Value: !Ref AWS::StackId 41 | 42 | AttachGateway: 43 | Type: AWS::EC2::VPCGatewayAttachment 44 | Properties: 45 | VpcId: !Ref VPC 46 | InternetGatewayId: !Ref InternetGateway 47 | 48 | PublicSubnetA: 49 | Type: AWS::EC2::Subnet 50 | Properties: 51 | AvailabilityZone: !Sub ${AWS::Region}a 52 | CidrBlock: 10.0.0.0/24 53 | MapPublicIpOnLaunch: 'false' 54 | VpcId: !Ref VPC 55 | Tags: 56 | - Key: Stack 57 | Value: !Ref AWS::StackId 58 | 59 | PublicSubnetB: 60 | Type: AWS::EC2::Subnet 61 | Properties: 62 | AvailabilityZone: !Sub ${AWS::Region}b 63 | CidrBlock: 10.0.1.0/24 64 | MapPublicIpOnLaunch: 'false' 65 | VpcId: !Ref VPC 66 | Tags: 67 | - Key: Stack 68 | Value: !Ref AWS::StackId 69 | 70 | PublicSubnetC: 71 | Type: AWS::EC2::Subnet 72 | Properties: 73 | AvailabilityZone: !Sub ${AWS::Region}c 74 | CidrBlock: 10.0.2.0/24 75 | MapPublicIpOnLaunch: 'false' 76 | VpcId: !Ref VPC 77 | Tags: 78 | - Key: Stack 79 | Value: !Ref AWS::StackId 80 | 81 | RouteTable: 82 | Type: AWS::EC2::RouteTable 83 | Properties: 84 | VpcId: !Ref VPC 85 | Tags: 86 | - Key: Stack 87 | Value: !Ref AWS::StackId 88 | 89 | Route: 90 | Type: AWS::EC2::Route 91 | DependsOn: AttachGateway 92 | Properties: 93 | RouteTableId: !Ref RouteTable 94 | DestinationCidrBlock: 0.0.0.0/0 95 | GatewayId: !Ref InternetGateway 96 | 97 | PublicSubnetARouteTableAssociation: 98 | Type: AWS::EC2::SubnetRouteTableAssociation 99 | Properties: 100 | SubnetId: !Ref PublicSubnetA 101 | RouteTableId: !Ref RouteTable 102 | 103 | PublicSubnetBRouteTableAssociation: 104 | Type: AWS::EC2::SubnetRouteTableAssociation 105 | Properties: 106 | SubnetId: !Ref PublicSubnetB 107 | RouteTableId: !Ref RouteTable 108 | 109 | PublicSubnetCRouteTableAssociation: 110 | Type: AWS::EC2::SubnetRouteTableAssociation 111 | Properties: 112 | SubnetId: !Ref PublicSubnetC 113 | RouteTableId: !Ref RouteTable 114 | 115 | HostedZonePrivate: 116 | Type: AWS::Route53::HostedZone 117 | Properties: 118 | Name: internal 119 | VPCs: 120 | - VPCId: !Ref VPC 121 | VPCRegion: !Sub ${AWS::Region} 122 | 123 | PublicSshSecurityGroup: 124 | Type: AWS::EC2::SecurityGroup 125 | Properties: 126 | GroupDescription: Enable external SSH access 127 | VpcId: !Ref VPC 128 | SecurityGroupIngress: 129 | - IpProtocol: tcp 130 | FromPort: '22' 131 | ToPort: '22' 132 | CidrIp: 0.0.0.0/0 133 | 134 | StatefulZookeeperSecurityGroup: 135 | Type: AWS::EC2::SecurityGroup 136 | Properties: 137 | GroupDescription: Enable external access 138 | VpcId: !Ref VPC 139 | SecurityGroupIngress: 140 | - IpProtocol: tcp 141 | FromPort: '2181' 142 | ToPort: '2181' 143 | CidrIp: 10.0.0.0/22 144 | - IpProtocol: tcp 145 | FromPort: '2888' 146 | ToPort: '2888' 147 | CidrIp: 10.0.0.0/22 148 | - IpProtocol: tcp 149 | FromPort: '3888' 150 | ToPort: '3888' 151 | CidrIp: 10.0.0.0/22 152 | 153 | StatefulZookeeperElbSecurityGroup: 154 | Type: AWS::EC2::SecurityGroup 155 | Properties: 156 | GroupDescription: Enable external ELB access 157 | VpcId: !Ref VPC 158 | SecurityGroupIngress: 159 | - IpProtocol: tcp 160 | FromPort: '2181' 161 | ToPort: '2181' 162 | CidrIp: 10.0.0.0/22 163 | 164 | StatefulZookeeperRole: 165 | Type: AWS::IAM::Role 166 | Properties: 167 | AssumeRolePolicyDocument: 168 | Version: '2012-10-17' 169 | Statement: 170 | - Effect: Allow 171 | Principal: 172 | Service: 173 | - ec2.amazonaws.com 174 | Action: 175 | - sts:AssumeRole 176 | Path: "/" 177 | Policies: 178 | - PolicyName: StatefulZookeeperPolicy 179 | PolicyDocument: 180 | Version: '2012-10-17' 181 | Statement: 182 | - Action: 183 | - ec2:AttachNetworkInterface 184 | - ec2:AttachVolume 185 | - ec2:DescribeInstances 186 | - ec2:DescribeNetworkInterfaces 187 | - ec2:DescribeTags 188 | - ec2:DescribeVolumes 189 | Resource: "*" 190 | Effect: Allow 191 | 192 | StatefulZookeeperInstanceProfile: 193 | Type: AWS::IAM::InstanceProfile 194 | Properties: 195 | Path: "/" 196 | Roles: 197 | - Ref: StatefulZookeeperRole 198 | 199 | StatefulZookeeperLoadBalancer: 200 | Type: AWS::ElasticLoadBalancing::LoadBalancer 201 | Properties: 202 | CrossZone: 'true' 203 | Scheme: internal 204 | SecurityGroups: 205 | - !Ref StatefulZookeeperElbSecurityGroup 206 | Subnets: 207 | - !Ref PublicSubnetA 208 | - !Ref PublicSubnetB 209 | - !Ref PublicSubnetC 210 | Listeners: 211 | - LoadBalancerPort: '2181' 212 | InstancePort: '2181' 213 | Protocol: TCP 214 | 215 | StatefulZookeeperLoadBalancerDns: 216 | Type: AWS::Route53::RecordSet 217 | Properties: 218 | AliasTarget: 219 | DNSName: !GetAtt StatefulZookeeperLoadBalancer.DNSName 220 | HostedZoneId: !GetAtt StatefulZookeeperLoadBalancer.CanonicalHostedZoneNameID 221 | HostedZoneId: !Ref HostedZonePrivate 222 | Name: zookeeper.internal 223 | Type: A 224 | 225 | StatefulZookeeperLaunchConfig: 226 | Type: AWS::AutoScaling::LaunchConfiguration 227 | DependsOn: 228 | - StatefulZookeeperEniOne 229 | - StatefulZookeeperEniTwo 230 | - StatefulZookeeperEniThree 231 | Metadata: 232 | AWS::CloudFormation::Init: 233 | config: 234 | files: 235 | /etc/zookeeper/conf/environment: 236 | content: | 237 | NAME=zookeeper 238 | ZOOCFGDIR=/etc/$NAME/conf 239 | CLASSPATH="$ZOOCFGDIR:/usr/share/java/jline.jar:/usr/share/java/log4j-1.2.jar:/usr/share/java/xercesImpl.jar:/usr/share/java/xmlParserAPIs.jar:/usr/share/java/netty.jar:/usr/share/java/slf4j-api.jar:/usr/share/java/slf4j-log4j12.jar:/usr/share/java/zookeeper.jar" 240 | ZOOCFG="$ZOOCFGDIR/zoo.cfg" 241 | ZOO_LOG_DIR=/var/log/$NAME 242 | USER=$NAME 243 | GROUP=$NAME 244 | PIDDIR=/var/run/$NAME 245 | PIDFILE=$PIDDIR/$NAME.pid 246 | SCRIPTNAME=/etc/init.d/$NAME 247 | JAVA=/usr/bin/java 248 | ZOOMAIN="org.apache.zookeeper.server.quorum.QuorumPeerMain" 249 | ZOO_LOG4J_PROP="INFO,ROLLINGFILE" 250 | JMXLOCALONLY=false 251 | JAVA_OPTS="-Xmx128m -Xms128m" 252 | mode: "000644" 253 | owner: "root" 254 | group: "root" 255 | /etc/zookeeper/conf/zoo.cfg: 256 | content: !Sub | 257 | tickTime=1000 258 | initLimit=1800 259 | syncLimit=300 260 | dataDir=/var/lib/zookeeper 261 | clientPort=2181 262 | server.1=${StatefulZookeeperEniOne.PrimaryPrivateIpAddress}:2888:3888 263 | server.2=${StatefulZookeeperEniTwo.PrimaryPrivateIpAddress}:2888:3888 264 | server.3=${StatefulZookeeperEniThree.PrimaryPrivateIpAddress}:2888:3888 265 | leaderServes=yes 266 | mode: "000644" 267 | owner: "root" 268 | group: "root" 269 | Properties: 270 | AssociatePublicIpAddress: 'true' 271 | IamInstanceProfile: !Ref StatefulZookeeperInstanceProfile 272 | ImageId: !Ref InstanceImageId 273 | InstanceType: !Ref InstanceType 274 | SecurityGroups: 275 | - !Ref PublicSshSecurityGroup 276 | - !Ref StatefulZookeeperSecurityGroup 277 | KeyName: !Ref KeyName 278 | UserData: 279 | Fn::Base64: !Sub | 280 | #!/bin/bash 281 | set -euo pipefail 282 | addgroup \ 283 | -gid 5000 \ 284 | zookeeper 285 | adduser \ 286 | --gid 5000 \ 287 | --uid 5000 \ 288 | --no-create-home \ 289 | --disabled-password \ 290 | --disabled-login \ 291 | --system \ 292 | zookeeper 293 | apt-get update 294 | apt-get install -y zookeeper zookeeperd python2.7 python-pip curl jq ntp 295 | service zookeeper stop 296 | pip install awscli https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz 297 | cfn-init \ 298 | --resource StatefulZookeeperLaunchConfig \ 299 | --stack ${AWS::StackName} \ 300 | --region ${AWS::Region} 301 | INSTANCE_ID=$( curl -s http://169.254.169.254/latest/meta-data/instance-id ) 302 | INSTANCE_TAGS=$( aws ec2 describe-tags \ 303 | --filters "Name=resource-id,Values=$INSTANCE_ID" \ 304 | --region ${AWS::Region} \ 305 | --query "{ instanceeniid:Tags[?Key=='instanceeniid'].Value, instanceindex:Tags[?Key=='instanceindex'].Value, instancevolume:Tags[?Key=='instancevolume'].Value }" ) 306 | INSTANCE_ENI_ID=$( echo "$INSTANCE_TAGS" | jq -r '.instanceeniid | .[0]' ) 307 | aws ec2 attach-network-interface \ 308 | --network-interface-id $INSTANCE_ENI_ID \ 309 | --instance-id $INSTANCE_ID \ 310 | --region ${AWS::Region} \ 311 | --device-index 1 312 | INSTANCE_ENI_IP=$( aws ec2 describe-network-interfaces \ 313 | --network-interface-ids $INSTANCE_ENI_ID \ 314 | --region ${AWS::Region} \ 315 | --query "NetworkInterfaces[0].PrivateIpAddress" \ 316 | --output text ) 317 | GATEWAY_IP=$( /sbin/ip route | awk '/default/ { print $3 }' ) 318 | echo -e "auto eth1\niface eth1 inet dhcp\n post-up ip route add default via $GATEWAY_IP dev eth1 tab 2\n post-up ip rule add from $INSTANCE_ENI_IP/32 tab 2 priority 600" \ 319 | > /etc/network/interfaces.d/eth1.cfg 320 | sleep 15s 321 | service networking restart 322 | INSTANCE_VOLUME_ID=$( echo "$INSTANCE_TAGS" | jq -r '.instancevolume | .[0]' ) 323 | aws ec2 attach-volume \ 324 | --volume-id $INSTANCE_VOLUME_ID \ 325 | --instance-id $INSTANCE_ID \ 326 | --region ${AWS::Region} \ 327 | --device /dev/xvdb 328 | sleep 15s 329 | set +e 330 | blkid -L zookeeperdata 331 | FILESYSTEM_EXISTS=$? 332 | set -e 333 | if [[ $FILESYSTEM_EXISTS -ne 0 ]]; then 334 | mkfs -t ext4 -L zookeeperdata /dev/xvdb 335 | fi 336 | mkdir -p /var/lib/zookeeper 337 | mount /dev/xvdb /var/lib/zookeeper 338 | echo "LABEL=zookeeperdata /var/lib/zookeeper ext4 nofail 0 0" >> /etc/fstab 339 | chown -R zookeeper:zookeeper /var/lib/zookeeper 340 | INSTANCE_ASG=$( aws ec2 describe-instances \ 341 | --instance-id $INSTANCE_ID \ 342 | --region ${AWS::Region} \ 343 | --query "Reservations[0].Instances[0].Tags[?Key=='aws:cloudformation:logical-id'].Value" \ 344 | --output text ) 345 | echo "$INSTANCE_TAGS" \ 346 | | jq -r '.instanceindex | .[0]' \ 347 | > /var/lib/zookeeper/myid 348 | service zookeeper start 349 | set +e 350 | ps auxw | grep -P '\b'zookeeper'(?!-)\b' 351 | ZOOKEEPER_RUNNING=$? 352 | set -e 353 | cfn-signal \ 354 | -e $ZOOKEEPER_RUNNING \ 355 | --stack ${AWS::StackName} \ 356 | --resource $INSTANCE_ASG \ 357 | --region ${AWS::Region} 358 | 359 | StatefulZookeeperEniOne: 360 | Type: "AWS::EC2::NetworkInterface" 361 | Properties: 362 | Description: StatefulZookeeperGroupOne ENI 363 | GroupSet: 364 | - !Ref PublicSshSecurityGroup 365 | - !Ref StatefulZookeeperSecurityGroup 366 | SubnetId: !Ref PublicSubnetA 367 | 368 | StatefulZookeeperVolumeOne: 369 | Type: "AWS::EC2::Volume" 370 | Properties: 371 | AvailabilityZone: !Sub ${AWS::Region}a 372 | Size: 8 373 | VolumeType: gp2 374 | 375 | StatefulZookeeperGroupOne: 376 | Type: AWS::AutoScaling::AutoScalingGroup 377 | DependsOn: 378 | - StatefulZookeeperEniOne 379 | - StatefulZookeeperVolumeOne 380 | Properties: 381 | VPCZoneIdentifier: 382 | - !Ref PublicSubnetA 383 | LaunchConfigurationName: !Ref StatefulZookeeperLaunchConfig 384 | LoadBalancerNames: 385 | - !Ref StatefulZookeeperLoadBalancer 386 | DesiredCapacity: 1 387 | MinSize: 0 388 | MaxSize: 1 389 | HealthCheckGracePeriod: '300' 390 | HealthCheckType: EC2 391 | Tags: 392 | - Key: instanceeniid 393 | Value: !Ref StatefulZookeeperEniOne 394 | PropagateAtLaunch: 'true' 395 | - Key: instanceindex 396 | Value: '1' 397 | PropagateAtLaunch: 'true' 398 | - Key: instancevolume 399 | Value: !Ref StatefulZookeeperVolumeOne 400 | PropagateAtLaunch: 'true' 401 | - Key: Name 402 | Value: 'zookeeper-one' 403 | PropagateAtLaunch: 'true' 404 | CreationPolicy: 405 | ResourceSignal: 406 | Count: 1 407 | Timeout: PT10M 408 | UpdatePolicy: 409 | AutoScalingRollingUpdate: 410 | MaxBatchSize: 1 411 | MinInstancesInService: 0 412 | PauseTime: PT10M 413 | WaitOnResourceSignals: 'true' 414 | 415 | StatefulZookeeperEniTwo: 416 | Type: "AWS::EC2::NetworkInterface" 417 | Properties: 418 | Description: StatefulZookeeperGroupTwo ENI 419 | GroupSet: 420 | - !Ref PublicSshSecurityGroup 421 | - !Ref StatefulZookeeperSecurityGroup 422 | SubnetId: !Ref PublicSubnetB 423 | 424 | StatefulZookeeperVolumeTwo: 425 | Type: "AWS::EC2::Volume" 426 | Properties: 427 | AvailabilityZone: !Sub ${AWS::Region}b 428 | Size: 8 429 | VolumeType: gp2 430 | 431 | StatefulZookeeperGroupTwo: 432 | Type: AWS::AutoScaling::AutoScalingGroup 433 | DependsOn: 434 | - StatefulZookeeperEniTwo 435 | - StatefulZookeeperVolumeTwo 436 | - StatefulZookeeperGroupOne 437 | Properties: 438 | VPCZoneIdentifier: 439 | - !Ref PublicSubnetB 440 | LaunchConfigurationName: !Ref StatefulZookeeperLaunchConfig 441 | LoadBalancerNames: 442 | - !Ref StatefulZookeeperLoadBalancer 443 | DesiredCapacity: 1 444 | MinSize: 0 445 | MaxSize: 1 446 | HealthCheckGracePeriod: '300' 447 | HealthCheckType: EC2 448 | Tags: 449 | - Key: instanceeniid 450 | Value: !Ref StatefulZookeeperEniTwo 451 | PropagateAtLaunch: 'true' 452 | - Key: instanceindex 453 | Value: '2' 454 | PropagateAtLaunch: 'true' 455 | - Key: instancevolume 456 | Value: !Ref StatefulZookeeperVolumeTwo 457 | PropagateAtLaunch: 'true' 458 | - Key: Name 459 | Value: 'zookeeper-two' 460 | PropagateAtLaunch: 'true' 461 | CreationPolicy: 462 | ResourceSignal: 463 | Count: 1 464 | Timeout: PT10M 465 | UpdatePolicy: 466 | AutoScalingRollingUpdate: 467 | MaxBatchSize: 1 468 | MinInstancesInService: 0 469 | PauseTime: PT10M 470 | WaitOnResourceSignals: 'true' 471 | 472 | StatefulZookeeperEniThree: 473 | Type: "AWS::EC2::NetworkInterface" 474 | Properties: 475 | Description: StatefulZookeeperGroupThree ENI 476 | GroupSet: 477 | - !Ref PublicSshSecurityGroup 478 | - !Ref StatefulZookeeperSecurityGroup 479 | SubnetId: !Ref PublicSubnetC 480 | 481 | StatefulZookeeperVolumeThree: 482 | Type: "AWS::EC2::Volume" 483 | Properties: 484 | AvailabilityZone: !Sub ${AWS::Region}c 485 | Size: 8 486 | VolumeType: gp2 487 | 488 | StatefulZookeeperGroupThree: 489 | Type: AWS::AutoScaling::AutoScalingGroup 490 | DependsOn: 491 | - StatefulZookeeperEniThree 492 | - StatefulZookeeperVolumeThree 493 | - StatefulZookeeperGroupTwo 494 | Properties: 495 | VPCZoneIdentifier: 496 | - !Ref PublicSubnetC 497 | LaunchConfigurationName: !Ref StatefulZookeeperLaunchConfig 498 | LoadBalancerNames: 499 | - !Ref StatefulZookeeperLoadBalancer 500 | DesiredCapacity: 1 501 | MinSize: 0 502 | MaxSize: 1 503 | HealthCheckGracePeriod: '300' 504 | HealthCheckType: EC2 505 | Tags: 506 | - Key: instanceeniid 507 | Value: !Ref StatefulZookeeperEniThree 508 | PropagateAtLaunch: 'true' 509 | - Key: instanceindex 510 | Value: '3' 511 | PropagateAtLaunch: 'true' 512 | - Key: instancevolume 513 | Value: !Ref StatefulZookeeperVolumeThree 514 | PropagateAtLaunch: 'true' 515 | - Key: Name 516 | Value: 'zookeeper-three' 517 | PropagateAtLaunch: 'true' 518 | CreationPolicy: 519 | ResourceSignal: 520 | Count: 1 521 | Timeout: PT10M 522 | UpdatePolicy: 523 | AutoScalingRollingUpdate: 524 | MaxBatchSize: 1 525 | MinInstancesInService: 0 526 | PauseTime: PT10M 527 | WaitOnResourceSignals: 'true' 528 | -------------------------------------------------------------------------------- /ha_stateful/ha_stateful_stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # ha_stateful_stack.sh 6 | # Usage: ha_stateful_stack.sh [AMI_ID] [SERVICE_TYPE] [SSH_KEY_PAIR_NAME] 7 | 8 | function main { 9 | local AMI_ID 10 | local SERVICE_TYPE 11 | local SSH_KEY_NAME 12 | local STACK_EXISTS 13 | 14 | AMI_ID=${1-} 15 | SERVICE_TYPE=${2-} 16 | SSH_KEY_NAME=${3-} 17 | 18 | if [[ -z ${AMI_ID} ]] || [[ -z ${SERVICE_TYPE} ]] || [[ -z ${SSH_KEY_NAME} ]]; then 19 | echo "Missing required arguments" >&2 20 | exit 1 21 | fi 22 | 23 | set +e 24 | aws cloudformation describe-stacks \ 25 | --stack-name ha-stateful-"${SERVICE_TYPE}" 26 | STACK_EXISTS=$? 27 | set -e 28 | 29 | if [[ $STACK_EXISTS -ne 0 ]]; then 30 | echo "Creating stack" 31 | 32 | aws cloudformation create-stack \ 33 | --stack-name ha-stateful-"${SERVICE_TYPE}" \ 34 | --template-body file://ha_stateful."${SERVICE_TYPE}".template.yaml \ 35 | --capabilities CAPABILITY_IAM \ 36 | --parameters \ 37 | ParameterKey=KeyName,ParameterValue="${SSH_KEY_NAME}" \ 38 | ParameterKey=InstanceImageId,ParameterValue="${AMI_ID}" 39 | 40 | aws cloudformation wait stack-create-complete \ 41 | --stack-name ha-stateful-"${SERVICE_TYPE}" 42 | 43 | echo "Stack created" 44 | else 45 | echo "Updating stack" 46 | 47 | aws cloudformation update-stack \ 48 | --stack-name ha-stateful-"${SERVICE_TYPE}" \ 49 | --template-body file://ha_stateful."${SERVICE_TYPE}".template.yaml \ 50 | --capabilities CAPABILITY_IAM \ 51 | --parameters \ 52 | ParameterKey=KeyName,ParameterValue="${SSH_KEY_NAME}" \ 53 | ParameterKey=InstanceImageId,ParameterValue="${AMI_ID}" 54 | 55 | aws cloudformation wait stack-update-complete \ 56 | --stack-name ha-stateful-"${SERVICE_TYPE}" 57 | 58 | echo "Stack updated" 59 | fi 60 | } 61 | 62 | main "$@" 63 | -------------------------------------------------------------------------------- /ha_willreplace/ha_willreplace.template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # ha_deployment_willreplace.template.yaml 3 | 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Description: HA Will Replace Example 6 | 7 | Parameters: 8 | InstanceType: 9 | Description: WebServer EC2 instance type 10 | Type: String 11 | Default: t2.nano 12 | AllowedValues: 13 | - t2.nano 14 | - t2.micro 15 | ConstraintDescription: must be a valid EC2 instance type. 16 | KeyName: 17 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 18 | Type: AWS::EC2::KeyPair::KeyName 19 | ConstraintDescription: must be the name of an existing EC2 KeyPair. 20 | InstanceCount: 21 | Description: Number of EC2 instances to launch 22 | Type: Number 23 | Default: '1' 24 | InstanceCountMax: 25 | Description: Maximum number of EC2 instances to launch 26 | Type: Number 27 | Default: '6' 28 | InstanceImageId: 29 | Description: Image ID for EC2 instances 30 | Type: String 31 | 32 | Resources: 33 | InternetGateway: 34 | Type: AWS::EC2::InternetGateway 35 | Properties: 36 | Tags: 37 | - Key: Application 38 | Value: !Ref AWS::StackId 39 | 40 | VPC: 41 | Type: AWS::EC2::VPC 42 | Properties: 43 | CidrBlock: 10.0.0.0/16 44 | Tags: 45 | - Key: Application 46 | Value: !Ref AWS::StackId 47 | 48 | AttachGateway: 49 | Type: AWS::EC2::VPCGatewayAttachment 50 | Properties: 51 | VpcId: !Ref VPC 52 | InternetGatewayId: !Ref InternetGateway 53 | 54 | PublicSubnet: 55 | Type: AWS::EC2::Subnet 56 | Properties: 57 | VpcId: !Ref VPC 58 | CidrBlock: 10.0.1.0/24 59 | Tags: 60 | - Key: Application 61 | Value: !Ref AWS::StackId 62 | 63 | RouteTable: 64 | Type: AWS::EC2::RouteTable 65 | Properties: 66 | VpcId: !Ref VPC 67 | Tags: 68 | - Key: Application 69 | Value: !Ref AWS::StackId 70 | 71 | Route: 72 | Type: AWS::EC2::Route 73 | DependsOn: AttachGateway 74 | Properties: 75 | RouteTableId: !Ref RouteTable 76 | DestinationCidrBlock: 0.0.0.0/0 77 | GatewayId: !Ref InternetGateway 78 | 79 | PublicSubnetRouteTableAssociation: 80 | Type: AWS::EC2::SubnetRouteTableAssociation 81 | Properties: 82 | SubnetId: !Ref PublicSubnet 83 | RouteTableId: !Ref RouteTable 84 | 85 | PublicSshSecurityGroup: 86 | Type: AWS::EC2::SecurityGroup 87 | Properties: 88 | GroupDescription: Enable external SSH access 89 | VpcId: !Ref VPC 90 | SecurityGroupIngress: 91 | - IpProtocol: tcp 92 | FromPort: '22' 93 | ToPort: '22' 94 | CidrIp: 0.0.0.0/0 95 | 96 | PublicWebSecurityGroup: 97 | Type: AWS::EC2::SecurityGroup 98 | Properties: 99 | GroupDescription: Enable external web access 100 | VpcId: !Ref VPC 101 | SecurityGroupIngress: 102 | - IpProtocol: tcp 103 | FromPort: '80' 104 | ToPort: '80' 105 | CidrIp: 0.0.0.0/0 106 | 107 | WebServerGroup: 108 | Type: AWS::AutoScaling::AutoScalingGroup 109 | Properties: 110 | VPCZoneIdentifier: 111 | - !Ref PublicSubnet 112 | LaunchConfigurationName: !Ref WebLaunchConfig 113 | DesiredCapacity: !Ref InstanceCount 114 | MinSize: 1 115 | MaxSize: !Ref InstanceCountMax 116 | LoadBalancerNames: 117 | - !Ref WebElasticLoadBalancer 118 | HealthCheckGracePeriod: '300' 119 | HealthCheckType: ELB 120 | CreationPolicy: 121 | ResourceSignal: 122 | Count: !Ref InstanceCount 123 | Timeout: PT5M 124 | UpdatePolicy: 125 | AutoScalingReplacingUpdate: 126 | WillReplace: 'true' 127 | 128 | WebLaunchConfig: 129 | Type: AWS::AutoScaling::LaunchConfiguration 130 | Properties: 131 | AssociatePublicIpAddress: 'true' 132 | ImageId: !Ref InstanceImageId 133 | InstanceType: !Ref InstanceType 134 | SecurityGroups: 135 | - !Ref PublicSshSecurityGroup 136 | - !Ref PublicWebSecurityGroup 137 | KeyName: !Ref KeyName 138 | UserData: 139 | Fn::Base64: !Sub | 140 | #!/bin/bash 141 | yum update 142 | yum install -y aws-cfn-bootstrap 143 | /opt/aws/bin/cfn-init --resource WebLaunchConfig --stack ${AWS::StackName} --region ${AWS::Region} 144 | yum install -y nginx 145 | service nginx start 146 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region} 147 | 148 | WebElasticLoadBalancer: 149 | Type: AWS::ElasticLoadBalancing::LoadBalancer 150 | Properties: 151 | CrossZone: 'false' 152 | Scheme: internet-facing 153 | SecurityGroups: 154 | - !Ref PublicWebSecurityGroup 155 | Subnets: 156 | - !Ref PublicSubnet 157 | Listeners: 158 | - LoadBalancerPort: '80' 159 | InstancePort: '80' 160 | Protocol: HTTP 161 | 162 | Outputs: 163 | AutoScalingGroup: 164 | Description: AutoScalingGroup ID for stack 165 | Value: !Ref WebServerGroup 166 | -------------------------------------------------------------------------------- /ha_willreplace/ha_willreplace_migrations.template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # ha_willreplace_migrations.template.yaml 3 | 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Description: HA Will Replace Example 6 | 7 | Parameters: 8 | InstanceType: 9 | Description: WebServer EC2 instance type 10 | Type: String 11 | Default: t2.nano 12 | AllowedValues: 13 | - t2.nano 14 | - t2.micro 15 | ConstraintDescription: must be a valid EC2 instance type. 16 | KeyName: 17 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 18 | Type: AWS::EC2::KeyPair::KeyName 19 | ConstraintDescription: must be the name of an existing EC2 KeyPair. 20 | InstanceCount: 21 | Description: Number of EC2 instances to launch 22 | Type: Number 23 | Default: '1' 24 | InstanceCountMax: 25 | Description: Maximum number of EC2 instances to launch 26 | Type: Number 27 | Default: '6' 28 | InstanceImageId: 29 | Description: Image ID for EC2 instances 30 | Type: String 31 | 32 | Resources: 33 | InternetGateway: 34 | Type: AWS::EC2::InternetGateway 35 | Properties: 36 | Tags: 37 | - Key: Application 38 | Value: !Ref AWS::StackId 39 | 40 | VPC: 41 | Type: AWS::EC2::VPC 42 | Properties: 43 | CidrBlock: 10.0.0.0/16 44 | Tags: 45 | - Key: Application 46 | Value: !Ref AWS::StackId 47 | 48 | AttachGateway: 49 | Type: AWS::EC2::VPCGatewayAttachment 50 | Properties: 51 | VpcId: !Ref VPC 52 | InternetGatewayId: !Ref InternetGateway 53 | 54 | PublicSubnet: 55 | Type: AWS::EC2::Subnet 56 | Properties: 57 | VpcId: !Ref VPC 58 | CidrBlock: 10.0.1.0/24 59 | Tags: 60 | - Key: Application 61 | Value: !Ref AWS::StackId 62 | 63 | RouteTable: 64 | Type: AWS::EC2::RouteTable 65 | Properties: 66 | VpcId: !Ref VPC 67 | Tags: 68 | - Key: Application 69 | Value: !Ref AWS::StackId 70 | 71 | Route: 72 | Type: AWS::EC2::Route 73 | DependsOn: AttachGateway 74 | Properties: 75 | RouteTableId: !Ref RouteTable 76 | DestinationCidrBlock: 0.0.0.0/0 77 | GatewayId: !Ref InternetGateway 78 | 79 | PublicSubnetRouteTableAssociation: 80 | Type: AWS::EC2::SubnetRouteTableAssociation 81 | Properties: 82 | SubnetId: !Ref PublicSubnet 83 | RouteTableId: !Ref RouteTable 84 | 85 | PublicSshSecurityGroup: 86 | Type: AWS::EC2::SecurityGroup 87 | Properties: 88 | GroupDescription: Enable external SSH access 89 | VpcId: !Ref VPC 90 | SecurityGroupIngress: 91 | - IpProtocol: tcp 92 | FromPort: '22' 93 | ToPort: '22' 94 | CidrIp: 0.0.0.0/0 95 | 96 | PublicWebSecurityGroup: 97 | Type: AWS::EC2::SecurityGroup 98 | Properties: 99 | GroupDescription: Enable external web access 100 | VpcId: !Ref VPC 101 | SecurityGroupIngress: 102 | - IpProtocol: tcp 103 | FromPort: '80' 104 | ToPort: '80' 105 | CidrIp: 0.0.0.0/0 106 | 107 | MigrationGroup: 108 | Type: AWS::AutoScaling::AutoScalingGroup 109 | Properties: 110 | VPCZoneIdentifier: 111 | - !Ref PublicSubnet 112 | LaunchConfigurationName: !Ref MigrationLaunchConfig 113 | DesiredCapacity: 1 114 | MinSize: 0 115 | MaxSize: 1 116 | CreationPolicy: 117 | ResourceSignal: 118 | Count: 1 119 | Timeout: PT5M 120 | UpdatePolicy: 121 | AutoScalingReplacingUpdate: 122 | WillReplace: 'true' 123 | 124 | MigrationLaunchConfig: 125 | Type: AWS::AutoScaling::LaunchConfiguration 126 | Properties: 127 | AssociatePublicIpAddress: 'true' 128 | ImageId: !Ref InstanceImageId 129 | InstanceType: !Ref InstanceType 130 | SecurityGroups: 131 | - !Ref PublicSshSecurityGroup 132 | KeyName: !Ref KeyName 133 | UserData: 134 | Fn::Base64: !Sub | 135 | #!/bin/bash 136 | yum update 137 | yum install -y aws-cfn-bootstrap 138 | /opt/aws/bin/cfn-init --resource MigrationLaunchConfig --stack ${AWS::StackName} --region ${AWS::Region} 139 | yum install -y nginx 140 | service nginx start 141 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MigrationGroup --region ${AWS::Region} 142 | 143 | WebServerGroup: 144 | Type: AWS::AutoScaling::AutoScalingGroup 145 | DependsOn: MigrationGroup 146 | Properties: 147 | VPCZoneIdentifier: 148 | - !Ref PublicSubnet 149 | LaunchConfigurationName: !Ref WebLaunchConfig 150 | DesiredCapacity: !Ref InstanceCount 151 | MinSize: 1 152 | MaxSize: !Ref InstanceCountMax 153 | LoadBalancerNames: 154 | - !Ref WebElasticLoadBalancer 155 | HealthCheckGracePeriod: '300' 156 | HealthCheckType: ELB 157 | CreationPolicy: 158 | ResourceSignal: 159 | Count: !Ref InstanceCount 160 | Timeout: PT5M 161 | UpdatePolicy: 162 | AutoScalingReplacingUpdate: 163 | WillReplace: 'true' 164 | 165 | WebLaunchConfig: 166 | Type: AWS::AutoScaling::LaunchConfiguration 167 | Properties: 168 | AssociatePublicIpAddress: 'true' 169 | ImageId: !Ref InstanceImageId 170 | InstanceType: !Ref InstanceType 171 | SecurityGroups: 172 | - !Ref PublicSshSecurityGroup 173 | - !Ref PublicWebSecurityGroup 174 | KeyName: !Ref KeyName 175 | UserData: 176 | Fn::Base64: !Sub | 177 | #!/bin/bash 178 | yum update 179 | yum install -y aws-cfn-bootstrap 180 | /opt/aws/bin/cfn-init --resource WebLaunchConfig --stack ${AWS::StackName} --region ${AWS::Region} 181 | yum install -y nginx 182 | service nginx start 183 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region} 184 | 185 | WebElasticLoadBalancer: 186 | Type: AWS::ElasticLoadBalancing::LoadBalancer 187 | Properties: 188 | CrossZone: 'false' 189 | Scheme: internet-facing 190 | SecurityGroups: 191 | - !Ref PublicWebSecurityGroup 192 | Subnets: 193 | - !Ref PublicSubnet 194 | Listeners: 195 | - LoadBalancerPort: '80' 196 | InstancePort: '80' 197 | Protocol: HTTP 198 | 199 | Outputs: 200 | AutoScalingGroup: 201 | Description: AutoScalingGroup ID for stack 202 | Value: !Ref WebServerGroup 203 | AutoScalingGroupMigration: 204 | Description: Migration AutoScalingGroup ID for stack 205 | Value: !Ref MigrationGroup 206 | -------------------------------------------------------------------------------- /ha_willreplace/ha_willreplace_migrations_stack_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # ha_willreplace_migrations_stack_update.sh 6 | # Usage: ha_willreplace_migrations_stack_update.sh [AMI_ID] [SSH_KEY_PAIR_NAME] 7 | 8 | function main { 9 | local AMI_ID 10 | local SSH_KEY_NAME 11 | local ASG_ID 12 | local ASG_CURRENT_INSTANCE_COUNT 13 | 14 | AMI_ID=${1-} 15 | SSH_KEY_NAME=${2-} 16 | 17 | if [[ -z ${AMI_ID} ]] || [[ -z ${SSH_KEY_NAME} ]]; then 18 | echo "Missing required arguments" >&2 19 | exit 1 20 | fi 21 | 22 | # Grab ASG ID 23 | ASG_ID=$( aws cloudformation describe-stacks \ 24 | --stack-name ha-willreplace-migrations \ 25 | --query "Stacks[0].Outputs[?OutputKey=='AutoScalingGroup'].OutputValue" \ 26 | --output text ) 27 | 28 | # Grab current instance count from ASG 29 | ASG_CURRENT_INSTANCE_COUNT=$( aws autoscaling describe-auto-scaling-groups \ 30 | --auto-scaling-group-names "${ASG_ID}" \ 31 | --query "AutoScalingGroups[0].DesiredCapacity" \ 32 | --output text ) 33 | 34 | echo "Current instance count: ${ASG_CURRENT_INSTANCE_COUNT}" 35 | 36 | # Update stack 37 | aws cloudformation update-stack \ 38 | --stack-name ha-willreplace-migrations \ 39 | --template-body file://ha_willreplace_migrations.template.yaml \ 40 | --parameters \ 41 | ParameterKey=KeyName,ParameterValue="${SSH_KEY_NAME}" \ 42 | ParameterKey=InstanceCount,ParameterValue="${ASG_CURRENT_INSTANCE_COUNT}" \ 43 | ParameterKey=InstanceImageId,ParameterValue="${AMI_ID}" 44 | 45 | aws cloudformation wait stack-update-complete \ 46 | --stack-name ha-willreplace-migrations 47 | 48 | echo "Stack updated" 49 | 50 | # Minimise the migration ASG 51 | MIGRATION_ASG_ID=$( aws cloudformation describe-stacks \ 52 | --stack-name ha-willreplace-migrations \ 53 | --query "Stacks[0].Outputs[?OutputKey=='AutoScalingGroupMigration'].OutputValue" \ 54 | --output text ) 55 | 56 | aws autoscaling update-auto-scaling-group \ 57 | --auto-scaling-group-name "${MIGRATION_ASG_ID}" \ 58 | --desired-capacity 0 59 | 60 | echo "Migration group minimised" 61 | 62 | } 63 | 64 | main "$@" 65 | -------------------------------------------------------------------------------- /ha_willreplace/ha_willreplace_stack_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # ha_willreplace_stack_update.sh 6 | # Usage: ha_willreplace_stack_update.sh [AMI_ID] [SSH_KEY_PAIR_NAME] 7 | 8 | function main { 9 | local AMI_ID 10 | local SSH_KEY_NAME 11 | local ASG_ID 12 | local ASG_CURRENT_INSTANCE_COUNT 13 | 14 | AMI_ID=${1-} 15 | SSH_KEY_NAME=${2-} 16 | 17 | if [[ -z ${AMI_ID} ]] || [[ -z ${SSH_KEY_NAME} ]]; then 18 | echo "Missing required arguments" >&2 19 | exit 1 20 | fi 21 | 22 | # Grab ASG ID 23 | ASG_ID=$( aws cloudformation describe-stacks \ 24 | --stack-name ha-willreplace \ 25 | --query "Stacks[0].Outputs[?OutputKey=='AutoScalingGroup'].OutputValue" \ 26 | --output text ) 27 | 28 | # Grab current instance count from ASG 29 | ASG_CURRENT_INSTANCE_COUNT=$( aws autoscaling describe-auto-scaling-groups \ 30 | --auto-scaling-group-names "${ASG_ID}" \ 31 | --query "AutoScalingGroups[0].DesiredCapacity" \ 32 | --output text ) 33 | 34 | echo "Current instance count: ${ASG_CURRENT_INSTANCE_COUNT}" 35 | 36 | # Update stack 37 | aws cloudformation update-stack \ 38 | --stack-name ha-willreplace \ 39 | --template-body file://ha_willreplace.template.yaml \ 40 | --parameters \ 41 | ParameterKey=KeyName,ParameterValue="${SSH_KEY_NAME}" \ 42 | ParameterKey=InstanceCount,ParameterValue="${ASG_CURRENT_INSTANCE_COUNT}" \ 43 | ParameterKey=InstanceImageId,ParameterValue="${AMI_ID}" 44 | 45 | aws cloudformation wait stack-update-complete \ 46 | --stack-name ha-willreplace 47 | 48 | echo "Stack updated" 49 | } 50 | 51 | main "$@" 52 | -------------------------------------------------------------------------------- /self_terminating_instance/self_terminating_instance.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"AWS Self Terminating example", 4 | "Parameters":{ 5 | "KeyName":{ 6 | "Description":"Name of an existing EC2 KeyPair to enable SSH access to the instances", 7 | "Type":"String", 8 | "MinLength":"1", 9 | "MaxLength":"255", 10 | "AllowedPattern":"[\\x20-\\x7E]*", 11 | "ConstraintDescription":"can contain only ASCII characters." 12 | }, 13 | "InstanceType":{ 14 | "Description":"WebServer EC2 instance type", 15 | "Type":"String", 16 | "Default":"t1.micro", 17 | "AllowedValues":[ 18 | "t1.micro", 19 | "m1.small", 20 | "m1.medium", 21 | "m1.large", 22 | "m1.xlarge", 23 | "m2.xlarge", 24 | "m2.2xlarge", 25 | "m2.4xlarge", 26 | "m3.xlarge", 27 | "m3.2xlarge", 28 | "c1.medium", 29 | "c1.xlarge", 30 | "cc1.4xlarge", 31 | "cc2.8xlarge", 32 | "cg1.4xlarge" 33 | ], 34 | "ConstraintDescription":"must be a valid EC2 instance type." 35 | }, 36 | "SSHLocation":{ 37 | "Description":" The IP address range that can be used to SSH to the EC2 instances", 38 | "Type":"String", 39 | "MinLength":"9", 40 | "MaxLength":"18", 41 | "Default":"0.0.0.0/0", 42 | "AllowedPattern":"(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 43 | "ConstraintDescription":"must be a valid IP CIDR range of the form x.x.x.x/x." 44 | }, 45 | "ScaleUpTme":{ 46 | "Description":"Recurring cron style scale up time", 47 | "Type":"String", 48 | "Default":"0 0 * * *" 49 | }, 50 | "ScaleDownTme":{ 51 | "Description":"Recurring cron style scale down time (dead switch in case cron overruns)", 52 | "Type":"String", 53 | "Default":"0 12 * * *" 54 | } 55 | }, 56 | "Mappings":{ 57 | "AWSInstanceType2Arch":{ 58 | "t1.micro":{ 59 | "Arch":"32" 60 | }, 61 | "m1.small":{ 62 | "Arch":"32" 63 | }, 64 | "m1.large":{ 65 | "Arch":"64" 66 | }, 67 | "m1.xlarge":{ 68 | "Arch":"64" 69 | }, 70 | "m2.xlarge":{ 71 | "Arch":"64" 72 | }, 73 | "m2.2xlarge":{ 74 | "Arch":"64" 75 | }, 76 | "m2.4xlarge":{ 77 | "Arch":"64" 78 | }, 79 | "m3.xlarge":{ 80 | "Arch":"64" 81 | }, 82 | "m3.2xlarge":{ 83 | "Arch":"64" 84 | }, 85 | "c1.medium":{ 86 | "Arch":"32" 87 | }, 88 | "c1.xlarge":{ 89 | "Arch":"64" 90 | }, 91 | "cc1.4xlarge":{ 92 | "Arch":"64" 93 | } 94 | }, 95 | "AWSRegionArch2AMI":{ 96 | "us-east-1":{ 97 | "32":"ami-7f418316", 98 | "64":"ami-7341831a" 99 | }, 100 | "us-west-1":{ 101 | "32":"ami-951945d0", 102 | "64":"ami-971945d2" 103 | }, 104 | "us-west-2":{ 105 | "32":"ami-16fd7026", 106 | "64":"ami-10fd7020" 107 | }, 108 | "eu-west-1":{ 109 | "32":"ami-24506250", 110 | "64":"ami-20506254" 111 | }, 112 | "sa-east-1":{ 113 | "32":"ami-3e3be423", 114 | "64":"ami-3c3be421" 115 | }, 116 | "ap-southeast-1":{ 117 | "32":"ami-74dda626", 118 | "64":"ami-7edda62c" 119 | }, 120 | "ap-southeast-2":{ 121 | "32":"ami-b3990e89", 122 | "64":"ami-bd990e87" 123 | }, 124 | "ap-northeast-1":{ 125 | "32":"ami-dcfa4edd", 126 | "64":"ami-e8fa4ee9" 127 | } 128 | } 129 | }, 130 | "Resources":{ 131 | "SelfTerminatingLaunchConfiguration":{ 132 | "Type":"AWS::AutoScaling::LaunchConfiguration", 133 | "Metadata":{ 134 | "AWS::CloudFormation::Init":{ 135 | "config":{ 136 | "packages":{ 137 | "yum":{ 138 | "aws-cli":[ 139 | 140 | ] 141 | } 142 | } 143 | } 144 | } 145 | }, 146 | "Properties":{ 147 | "ImageId":{ 148 | "Fn::FindInMap":[ 149 | "AWSRegionArch2AMI", 150 | { 151 | "Ref":"AWS::Region" 152 | }, 153 | { 154 | "Fn::FindInMap":[ 155 | "AWSInstanceType2Arch", 156 | { 157 | "Ref":"InstanceType" 158 | }, 159 | "Arch" 160 | ] 161 | } 162 | ] 163 | }, 164 | "InstanceType":{ 165 | "Ref":"InstanceType" 166 | }, 167 | "SecurityGroups":[ 168 | { 169 | "Ref":"SelfTerminatingSecurityGroup" 170 | } 171 | ], 172 | "KeyName":{ 173 | "Ref":"KeyName" 174 | }, 175 | "IamInstanceProfile":{ 176 | "Ref":"SelfTerminatingInstanceProfile" 177 | }, 178 | "UserData":{ 179 | "Fn::Base64":{ 180 | "Fn::Join":[ 181 | "", 182 | [ 183 | "#!/bin/bash\n", 184 | "yum update -y aws-cfn-bootstrap\n", 185 | "/opt/aws/bin/cfn-init -s ", 186 | { 187 | "Ref":"AWS::StackId" 188 | }, 189 | " -r SelfTerminatingLaunchConfiguration", 190 | " --region ", 191 | { 192 | "Ref":"AWS::Region" 193 | }, 194 | "\n", 195 | "echo 'Do something useful'\n", 196 | "instance_id=`curl http://169.254.169.254/latest/meta-data/instance-id`\n", 197 | "autoscale_group=`aws ec2 describe-tags --filters \"Name=resource-id,Values=$instance_id\"", 198 | " --region ", 199 | { 200 | "Ref":"AWS::Region" 201 | }, 202 | " \"Name=key,Values=aws:autoscaling:groupName\"", 203 | " | sed -ne 's\/[ ]*\"Value\":\\s\"\\(.*\\)\",\/\\1\/p'`\n", 204 | "aws autoscaling update-auto-scaling-group --auto-scaling-group-name $autoscale_group", 205 | " --region ", 206 | { 207 | "Ref":"AWS::Region" 208 | }, 209 | " --min-size 0 --max-size 0 --desired-capacity 0\n" 210 | ] 211 | ] 212 | } 213 | } 214 | } 215 | }, 216 | "SelfTerminatingAutoScalingGroup":{ 217 | "Type":"AWS::AutoScaling::AutoScalingGroup", 218 | "Properties":{ 219 | "AvailabilityZones":{ 220 | "Fn::GetAZs":{ 221 | "Ref":"AWS::Region" 222 | } 223 | }, 224 | "LaunchConfigurationName":{ 225 | "Ref":"SelfTerminatingLaunchConfiguration" 226 | }, 227 | "Tags":[ 228 | { 229 | "Key":"Name", 230 | "Value":"SelfTerminating", 231 | "PropagateAtLaunch":"true" 232 | } 233 | ], 234 | "MinSize":"0", 235 | "MaxSize":"0", 236 | "DesiredCapacity":"0" 237 | } 238 | }, 239 | "SelfTerminatingScaleUpScheduledAction":{ 240 | "Type":"AWS::AutoScaling::ScheduledAction", 241 | "Properties":{ 242 | "AutoScalingGroupName":{ 243 | "Ref":"SelfTerminatingAutoScalingGroup" 244 | }, 245 | "MaxSize":"1", 246 | "MinSize":"1", 247 | "DesiredCapacity":"1", 248 | "Recurrence":{ 249 | "Ref":"ScaleUpTme" 250 | } 251 | } 252 | }, 253 | "SelfTerminatingScaleDownScheduledAction":{ 254 | "Type":"AWS::AutoScaling::ScheduledAction", 255 | "Properties":{ 256 | "AutoScalingGroupName":{ 257 | "Ref":"SelfTerminatingAutoScalingGroup" 258 | }, 259 | "MaxSize":"0", 260 | "MinSize":"0", 261 | "DesiredCapacity":"0", 262 | "Recurrence":{ 263 | "Ref":"ScaleDownTme" 264 | } 265 | } 266 | }, 267 | "SelfTerminatingRole":{ 268 | "Type":"AWS::IAM::Role", 269 | "Properties":{ 270 | "Path":"/", 271 | "Policies":[ 272 | { 273 | "PolicyName":"SelfTerminatingRole", 274 | "PolicyDocument":{ 275 | "Version":"2012-10-17", 276 | "Statement":[ 277 | { 278 | "Action":[ 279 | "ec2:Describe*", 280 | "cloudformation:DescribeStackResource" 281 | ], 282 | "Resource":[ 283 | "*" 284 | ], 285 | "Effect":"Allow" 286 | }, 287 | { 288 | "Effect":"Allow", 289 | "Action":"autoscaling:UpdateAutoScalingGroup", 290 | "Resource":[ 291 | "*" 292 | ] 293 | } 294 | ] 295 | } 296 | } 297 | ], 298 | "AssumeRolePolicyDocument":{ 299 | "Statement":[ 300 | { 301 | "Action":[ 302 | "sts:AssumeRole" 303 | ], 304 | "Effect":"Allow", 305 | "Principal":{ 306 | "Service":[ 307 | "ec2.amazonaws.com" 308 | ] 309 | } 310 | } 311 | ] 312 | } 313 | } 314 | }, 315 | "SelfTerminatingInstanceProfile":{ 316 | "Type":"AWS::IAM::InstanceProfile", 317 | "Properties":{ 318 | "Path":"/", 319 | "Roles":[ 320 | { 321 | "Ref":"SelfTerminatingRole" 322 | } 323 | ] 324 | } 325 | }, 326 | "SelfTerminatingSecurityGroup":{ 327 | "Type":"AWS::EC2::SecurityGroup", 328 | "Properties":{ 329 | "GroupDescription":"Enable SSH access from specific IPs only", 330 | "SecurityGroupIngress":[ 331 | { 332 | "IpProtocol":"tcp", 333 | "FromPort":"22", 334 | "ToPort":"22", 335 | "CidrIp":{ 336 | "Ref":"SSHLocation" 337 | } 338 | } 339 | ] 340 | } 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /vpn_bastion/vpn_bastion.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | Description: Bastion stack 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | 6 | Parameters: 7 | ImageId: 8 | Description: AMI ID of the API Lists 9 | Type: AWS::EC2::Image::Id 10 | InstanceType: 11 | Description: EC2 instance type 12 | Type: String 13 | Default: t2.nano 14 | AllowedValues: 15 | - t2.nano 16 | - t2.micro 17 | ConstraintDescription: must be a valid EC2 instance type. 18 | KeyName: 19 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 20 | Type: AWS::EC2::KeyPair::KeyName 21 | ConstraintDescription: must be the name of an existing EC2 KeyPair. 22 | S3VpnKeysBucketName: 23 | Description: Name of S3 Secrets Bucket 24 | Type: String 25 | 26 | Resources: 27 | InternetGateway: 28 | Type: AWS::EC2::InternetGateway 29 | Properties: 30 | Tags: 31 | - Key: Stack 32 | Value: !Ref AWS::StackId 33 | 34 | VPC: 35 | Type: AWS::EC2::VPC 36 | Properties: 37 | CidrBlock: 10.0.0.0/22 38 | EnableDnsSupport: 'true' 39 | EnableDnsHostnames: 'true' 40 | Tags: 41 | - Key: Stack 42 | Value: !Ref AWS::StackId 43 | 44 | AttachGateway: 45 | Type: AWS::EC2::VPCGatewayAttachment 46 | Properties: 47 | VpcId: !Ref VPC 48 | InternetGatewayId: !Ref InternetGateway 49 | 50 | PublicSubnetA: 51 | Type: AWS::EC2::Subnet 52 | Properties: 53 | AvailabilityZone: !Sub ${AWS::Region}a 54 | CidrBlock: 10.0.0.0/24 55 | MapPublicIpOnLaunch: 'false' 56 | VpcId: !Ref VPC 57 | Tags: 58 | - Key: Stack 59 | Value: !Ref AWS::StackId 60 | 61 | PublicSubnetB: 62 | Type: AWS::EC2::Subnet 63 | Properties: 64 | AvailabilityZone: !Sub ${AWS::Region}b 65 | CidrBlock: 10.0.1.0/24 66 | MapPublicIpOnLaunch: 'false' 67 | VpcId: !Ref VPC 68 | Tags: 69 | - Key: Stack 70 | Value: !Ref AWS::StackId 71 | 72 | PublicSubnetC: 73 | Type: AWS::EC2::Subnet 74 | Properties: 75 | AvailabilityZone: !Sub ${AWS::Region}c 76 | CidrBlock: 10.0.2.0/24 77 | MapPublicIpOnLaunch: 'false' 78 | VpcId: !Ref VPC 79 | Tags: 80 | - Key: Stack 81 | Value: !Ref AWS::StackId 82 | 83 | RouteTable: 84 | Type: AWS::EC2::RouteTable 85 | Properties: 86 | VpcId: !Ref VPC 87 | Tags: 88 | - Key: Stack 89 | Value: !Ref AWS::StackId 90 | 91 | Route: 92 | Type: AWS::EC2::Route 93 | DependsOn: AttachGateway 94 | Properties: 95 | RouteTableId: !Ref RouteTable 96 | DestinationCidrBlock: 0.0.0.0/0 97 | GatewayId: !Ref InternetGateway 98 | 99 | PublicSubnetARouteTableAssociation: 100 | Type: AWS::EC2::SubnetRouteTableAssociation 101 | Properties: 102 | SubnetId: !Ref PublicSubnetA 103 | RouteTableId: !Ref RouteTable 104 | 105 | PublicSubnetBRouteTableAssociation: 106 | Type: AWS::EC2::SubnetRouteTableAssociation 107 | Properties: 108 | SubnetId: !Ref PublicSubnetB 109 | RouteTableId: !Ref RouteTable 110 | 111 | PublicSubnetCRouteTableAssociation: 112 | Type: AWS::EC2::SubnetRouteTableAssociation 113 | Properties: 114 | SubnetId: !Ref PublicSubnetC 115 | RouteTableId: !Ref RouteTable 116 | 117 | BastionSG: 118 | Type: AWS::EC2::SecurityGroup 119 | Properties: 120 | GroupDescription: Enable access to Bastion 121 | VpcId: !Ref VPC 122 | SecurityGroupIngress: 123 | - IpProtocol: tcp 124 | FromPort: '22' 125 | ToPort: '22' 126 | CidrIp: 10.0.0.0/22 127 | - IpProtocol: udp 128 | FromPort: '1194' 129 | ToPort: '1194' 130 | CidrIp: 0.0.0.0/0 131 | 132 | BastionRole: 133 | Type: AWS::IAM::Role 134 | Properties: 135 | AssumeRolePolicyDocument: 136 | Version: '2012-10-17' 137 | Statement: 138 | - Effect: Allow 139 | Principal: 140 | Service: 141 | - ec2.amazonaws.com 142 | Action: 143 | - sts:AssumeRole 144 | Path: "/" 145 | 146 | BastionAccessPolicy: 147 | Type: AWS::IAM::Policy 148 | Properties: 149 | PolicyName: BastionAccessPolicy 150 | PolicyDocument: 151 | Version: '2012-10-17' 152 | Statement: 153 | - Action: 154 | - ec2:AssociateAddress 155 | - ec2:DescribeInstances 156 | Effect: Allow 157 | Resource: "*" 158 | - Action: 159 | - s3:List* 160 | - s3:Get* 161 | Effect: Allow 162 | Resource: 163 | - !Sub arn:aws:s3:::${S3VpnKeysBucketName}/* 164 | - !Sub arn:aws:s3:::${S3VpnKeysBucketName} 165 | Roles: 166 | - !Ref BastionRole 167 | 168 | BastionInstanceProfile: 169 | Type: AWS::IAM::InstanceProfile 170 | Properties: 171 | Path: "/" 172 | Roles: 173 | - !Ref BastionRole 174 | 175 | BastionEIP: 176 | Type: "AWS::EC2::EIP" 177 | 178 | BastionLaunchConfig: 179 | Type: AWS::AutoScaling::LaunchConfiguration 180 | Metadata: 181 | AWS::CloudFormation::Init: 182 | config: 183 | files: 184 | /etc/openvpn/server.conf: 185 | content: !Sub | 186 | port 1194 187 | proto udp 188 | dev tun 189 | server 172.16.0.0 255.255.252.0 190 | push "route 10.0.0.0 255.255.252.0" 191 | ca /etc/openvpn/keys/ca.crt 192 | cert /etc/openvpn/keys/server.crt 193 | key /etc/openvpn/keys/server.key 194 | dh /etc/openvpn/keys/dh2048.pem 195 | tls-server 196 | tls-auth /etc/openvpn/keys/static.key 0 197 | tls-version-min 1.2 198 | tls-cipher TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 199 | cipher AES-256-CBC 200 | auth SHA512 201 | ifconfig-pool-persist ipp.txt 202 | keepalive 10 120 203 | ping-timer-rem 204 | comp-lzo 205 | persist-key 206 | persist-tun 207 | status openvpn-status.log 208 | log-append /var/log/openvpn.log 209 | verb 3 210 | max-clients 100 211 | user nobody 212 | group nogroup 213 | mode: "000644" 214 | owner: "root" 215 | group: "root" 216 | Properties: 217 | AssociatePublicIpAddress: 'true' 218 | IamInstanceProfile: !Ref BastionInstanceProfile 219 | ImageId: !Ref ImageId 220 | InstanceMonitoring: false 221 | InstanceType: !Ref InstanceType 222 | KeyName: !Ref KeyName 223 | SecurityGroups: 224 | - !Ref BastionSG 225 | UserData: 226 | Fn::Base64: !Sub | 227 | #!/bin/bash 228 | set -euo pipefail 229 | apt-get update && apt-get install -y curl python-pip ntp wget 230 | wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg|apt-key add - 231 | echo "deb http://build.openvpn.net/debian/openvpn/stable xenial main" \ 232 | > /etc/apt/sources.list.d/openvpn-aptrepo.list 233 | apt-get update && apt-get install -y openvpn 234 | pip install awscli https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz 235 | cfn-init \ 236 | --resource BastionLaunchConfig \ 237 | --stack ${AWS::StackName} \ 238 | --region ${AWS::Region} 239 | mkdir -p /etc/openvpn/keys 240 | aws s3 cp s3://${S3VpnKeysBucketName} \ 241 | /etc/openvpn/keys \ 242 | --recursive \ 243 | --include "ca.crt" \ 244 | --include "server.crt" \ 245 | --include "server.key" \ 246 | --include "static.key" \ 247 | --include "dh2048.pem" 248 | chmod -R 0600 /etc/openvpn/keys 249 | echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf 250 | sysctl -p 251 | iptables -t nat -A POSTROUTING -s 172.16.0.0/22 -o eth0 -j MASQUERADE 252 | INSTANCE_ID=$( curl -s http://169.254.169.254/latest/meta-data/instance-id ) 253 | aws ec2 associate-address \ 254 | --region ${AWS::Region} \ 255 | --instance-id $INSTANCE_ID \ 256 | --public-ip ${BastionEIP} 257 | systemctl daemon-reload 258 | service openvpn start 259 | INSTANCE_ASG=$( aws ec2 describe-instances \ 260 | --instance-id $INSTANCE_ID \ 261 | --region ${AWS::Region} \ 262 | --query "Reservations[0].Instances[0].Tags[?Key=='aws:cloudformation:logical-id'].Value" \ 263 | --output text ) 264 | set +e 265 | ps auxw | grep -P '\b'openvpn'(?!-)\b' 266 | OPENVPN_RUNNING=$? 267 | set -e 268 | cfn-signal \ 269 | -e $OPENVPN_RUNNING \ 270 | --stack ${AWS::StackName} \ 271 | --resource $INSTANCE_ASG \ 272 | --region ${AWS::Region} 273 | 274 | BastionGroup: 275 | Type: AWS::AutoScaling::AutoScalingGroup 276 | Properties: 277 | DesiredCapacity: 1 278 | LaunchConfigurationName: !Ref BastionLaunchConfig 279 | MaxSize: 2 280 | MinSize: 1 281 | VPCZoneIdentifier: 282 | - !Ref PublicSubnetA 283 | - !Ref PublicSubnetB 284 | - !Ref PublicSubnetC 285 | CreationPolicy: 286 | ResourceSignal: 287 | Count: 1 288 | Timeout: PT10M 289 | UpdatePolicy: 290 | AutoScalingRollingUpdate: 291 | MaxBatchSize: 1 292 | MinInstancesInService: 1 293 | PauseTime: PT10M 294 | WaitOnResourceSignals: 'true' 295 | -------------------------------------------------------------------------------- /vpn_bastion/vpn_s3_bucket.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | Description: S3 bucket 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | 6 | Parameters: 7 | BucketPrefix: 8 | Description: Prefix for S3 bucket 9 | Type: String 10 | 11 | Resources: 12 | S3Bucket: 13 | Type: AWS::S3::Bucket 14 | Properties: 15 | AccessControl: Private 16 | BucketName: !Sub ${BucketPrefix}-vpn-tutorial-keys-bucket 17 | VersioningConfiguration: 18 | Status: "Enabled" 19 | -------------------------------------------------------------------------------- /vpn_bastion/vpn_stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | function main { 6 | local AMI_ID 7 | local BUCKET_PREFIX 8 | local SSH_KEY_NAME 9 | local S3_STACK_EXISTS 10 | local VPN_STACK_EXISTS 11 | local USAGE 12 | local DIR 13 | 14 | USAGE="$(basename "$0") [BUCKET_PREFIX] [SSH_KEY_PAIR_NAME] [AMI_ID](optional)" 15 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 16 | BUCKET_PREFIX=${1-} 17 | SSH_KEY_NAME=${2-} 18 | AMI_ID=${3-} 19 | 20 | if [[ -z ${BUCKET_PREFIX} ]] || [[ -z ${SSH_KEY_NAME} ]]; then 21 | echo "Missing required arguments" >&2 22 | echo "$USAGE" >&2 23 | exit 1 24 | fi 25 | 26 | set +e 27 | aws cloudformation describe-stacks \ 28 | --stack-name vpn-stack-s3 \ 29 | &> /dev/null 30 | S3_STACK_EXISTS=$? 31 | aws cloudformation describe-stacks \ 32 | --stack-name vpn-stack \ 33 | &> /dev/null 34 | VPN_STACK_EXISTS=$? 35 | set -e 36 | 37 | if [[ -z "${AMI_ID}" ]]; then 38 | AMI_ID=$( aws ec2 describe-images \ 39 | --owners 099720109477 \ 40 | --filters "Name=name,Values=*ubuntu-xenial-16.04-amd64*" \ 41 | "Name=virtualization-type,Values=hvm" \ 42 | "Name=root-device-type,Values=ebs" \ 43 | "Name=hypervisor,Values=xen" \ 44 | --output text \ 45 | --query "reverse(sort_by(Images, &CreationDate))|[].ImageId | [0]" ) 46 | fi 47 | 48 | echo "Using AMI ID: ${AMI_ID}" 49 | 50 | if [[ $S3_STACK_EXISTS -ne 0 ]]; then 51 | echo "Creating vpn s3 stack" 52 | 53 | aws cloudformation create-stack \ 54 | --stack-name vpn-stack-s3 \ 55 | --template-body file://"${DIR}"/vpn_s3_bucket.yaml \ 56 | --parameters \ 57 | ParameterKey=BucketPrefix,ParameterValue="${BUCKET_PREFIX}" \ 58 | &> /dev/null 59 | 60 | aws cloudformation wait stack-create-complete \ 61 | --stack-name vpn-stack-s3 62 | 63 | echo "Stack created" 64 | fi 65 | 66 | if [[ $VPN_STACK_EXISTS -ne 0 ]]; then 67 | echo "Creating vpn stack" 68 | 69 | aws cloudformation create-stack \ 70 | --stack-name vpn-stack \ 71 | --template-body file://"${DIR}"/vpn_bastion.yaml \ 72 | --capabilities CAPABILITY_IAM \ 73 | --parameters \ 74 | ParameterKey=ImageId,ParameterValue="${AMI_ID}" \ 75 | ParameterKey=KeyName,ParameterValue="${SSH_KEY_NAME}" \ 76 | ParameterKey=S3VpnKeysBucketName,ParameterValue="${BUCKET_PREFIX}"-vpn-tutorial-keys-bucket \ 77 | &> /dev/null 78 | 79 | aws cloudformation wait stack-create-complete \ 80 | --stack-name vpn-stack 81 | 82 | echo "Stack created" 83 | else 84 | echo "Updating vpn stack" 85 | 86 | aws cloudformation update-stack \ 87 | --stack-name vpn-stack \ 88 | --template-body file://"${DIR}"/vpn_bastion.yaml \ 89 | --capabilities CAPABILITY_IAM \ 90 | --parameters \ 91 | ParameterKey=ImageId,ParameterValue="${AMI_ID}" \ 92 | ParameterKey=KeyName,ParameterValue="${SSH_KEY_NAME}" \ 93 | ParameterKey=S3VpnKeysBucketName,ParameterValue="${BUCKET_PREFIX}"-vpn-tutorial-keys-bucket \ 94 | &> /dev/null 95 | 96 | aws cloudformation wait stack-update-complete \ 97 | --stack-name vpn-stack 98 | 99 | echo "Stack updated" 100 | fi 101 | } 102 | 103 | main "$@" 104 | --------------------------------------------------------------------------------