Deploying to an Auto Scaling Group and Deployment Config

K
KaiΒ·Β·5 min read

A single instance is a fatal weak point: if it dies, the app goes down. Production runs many instances behind an Auto Scaling Group (ASG) to tolerate failure and scale with load. Deploying to such a group raises a new question: update them all at once (fast but disruptive) or one machine at a time (slow but always leaving a machine serving)? This article deploys to an ASG and uses a deployment config to control exactly that.

πŸ’° Cost

This article runs an ASG with 2 t3.micro instances. Remember to terminate (delete the ASG) when you finish Part IV. Article 11 reuses this ASG, so keep it until the end of Article 11.

Goal

Deploy to an ASG instead of a single instance, understand the deployment configs and their tradeoffs, and grasp the mechanism by which CodeDeploy auto-deploys to instances the ASG launches later.

Launch template and ASG

An ASG needs a launch template describing how to create each instance β€” the same AMI, instance type, instance profile, security group and user-data (install the agent + httpd) as Article 8, but packaged into a mold the ASG can clone:

$ aws ec2 create-launch-template --launch-template-name awscicd-lt \
    --launch-template-data file://lt-data.json

Then the ASG uses that template, spreading 2 instances across 2 AZs:

$ aws autoscaling create-auto-scaling-group --auto-scaling-group-name awscicd-asg \
    --launch-template "LaunchTemplateName=awscicd-lt,Version=1" \
    --min-size 2 --max-size 2 --desired-capacity 2 \
    --vpc-zone-identifier "subnet-aaa,subnet-bbb"

The ASG launches 2 instances on its own; if one dies, it replaces it immediately to keep desired-capacity. Wait for both to be InService before deploying.

Deployment group attached to the ASG

Unlike Article 8 (targeting machines by tag), now the deployment group targets the Auto Scaling group directly:

$ aws deploy create-deployment-group --application-name awscicd-demo \
    --deployment-group-name awscicd-demo-asg-dg \
    --service-role-arn arn:aws:iam::111122223333:role/awscicd-codedeploy-role \
    --auto-scaling-groups awscicd-asg \
    --deployment-config-name CodeDeployDefault.OneAtATime

The --auto-scaling-groups parameter is the difference: CodeDeploy now ties its lifecycle to the ASG, not just to a static set of machines.

Deployment config: fast or safe

A deployment config dictates how many instances get updated at once β€” the tradeoff between speed and the number of machines always ready to serve. CodeDeploy has three built-in configs:

Config Updates Tradeoff
CodeDeployDefault.OneAtATime one machine at a time Safest β€” always leaves (Nβˆ’1) machines serving; slowest
CodeDeployDefault.HalfAtATime half the machines per batch Balanced β€” leaves 50% serving
CodeDeployDefault.AllAtOnce all at once Fastest β€” but can disrupt everything

Each config essentially declares a minimum healthy hosts (number/percentage of machines that must always be healthy). OneAtATime means "minimum Nβˆ’1 healthy machines", so CodeDeploy only takes one machine down at a time. If a machine's deploy fails, CodeDeploy stops and doesn't take down the remaining ones β€” the number of broken machines is capped at the minimum. You can also create a custom config with your own threshold.

Running the deploy

$ DID=$(aws deploy create-deployment --application-name awscicd-demo \
    --deployment-group-name awscicd-demo-asg-dg \
    --s3-location bucket=awscicd-artifacts-...,key=revisions/awscicd-demo.zip,bundleType=zip \
    --query 'deploymentId' --output text)

$ aws deploy get-deployment --deployment-id $DID \
    --query 'deploymentInfo.[deploymentConfigName,status,deploymentOverview]'
[
    "CodeDeployDefault.OneAtATime",
    "Succeeded",
    { "Pending": 0, "InProgress": 0, "Succeeded": 2, "Failed": 0, "Skipped": 0 }
]

OneAtATime finishes the first machine before moving to the second; the summary shows 2 machines Succeeded. Verify both serve the app:

$ # for each instance in the ASG:
i-0491f5e9b43644647 -> awscicd demo app β€” v2
i-086fd09a5971b2da8 -> awscicd demo app β€” v2

A key mechanism: deploying to instances launched later

This is what makes ASG integration entirely different from tag-based deploys. An ASG can launch a new instance at any time β€” when scaling out due to rising load, or when replacing a dead machine. A freshly born instance is empty, with no app yet. CodeDeploy solves this by hooking into the ASG lifecycle: whenever the ASG launches a new instance into the group, CodeDeploy automatically deploys the most recent successful revision to that very instance, before it's put into service.

   ASG (desired=2) ──┬── instance A ─┐
                     └── instance B ──  CodeDeploy deploys the latest revision
                                     β”‚
   load rises β†’ ASG launches inst. Cβ”˜  ← CodeDeploy AUTO-deploys to C as well
                                          (C enters service with the app already present)

The practical consequence: you never have an "empty" instance receiving traffic. This is why ASG-based deploys are the standard for elastic production β€” updates and scale-outs both keep every machine on the right version. (For this mechanism to work, the instance profile must be able to read the revision from S3 β€” exactly the permissions we granted in Article 8.)

🧹 Cleanup

Article 11 (blue/green) reuses this ASG, so keep it until the end of Article 11. When cleaning up:

$ aws autoscaling delete-auto-scaling-group --auto-scaling-group-name awscicd-asg --force-delete
$ aws ec2 delete-launch-template --launch-template-name awscicd-lt
$ aws deploy delete-deployment-group --application-name awscicd-demo --deployment-group-name awscicd-demo-asg-dg

--force-delete terminates the instances in the ASG as well. This is a costly resource (2 EC2s), don't forget it.

Wrap-up

Deploying to an ASG starts with a launch template (the mold for creating instances) and an ASG (keeps enough machines, auto-replaces dead ones). The deployment group attaches to the ASG via --auto-scaling-groups. The deployment config controls the speed-safety tradeoff: OneAtATime (safest, one machine at a time), HalfAtATime, AllAtOnce (fastest, can disrupt) β€” essentially a minimum healthy hosts threshold, and the deploy stops when the broken threshold is exceeded. The core mechanism: CodeDeploy auto-deploys the latest revision to every instance the ASG launches later, so there's no empty machine receiving traffic.

Every deploy so far has been in-place β€” updating directly on the running machine, with a window where that machine is offline. The next article covers a safer deploy style: blue/green β€” stand up a new set of machines in parallel, validate them, then shift traffic over with a load balancer, and auto-rollback per CloudWatch alarm if something goes wrong.