This Terraform Module creates an Auto Scaling Group (ASG) that can do a zero-downtime rolling deployment. That means
every time you update your app (e.g. publish a new AMI), all you have to do is run terraform apply and the new
version of your app will automatically roll out across your Auto Scaling Group. Note that this module only
creates the ASG and it's up to you to create all the other related resources, such as the launch configuration, ELB,
and security groups.
What's an Auto Scaling Group?
An Auto Scaling Group (ASG) is used to manage a cluster of EC2 Instances. It
can enforce pre-defined rules about how many instances to run in the cluster, scale the number of instances up or
down depending on traffic, and automatically restart instances if they go down.
How does rolling deployment work?
Since Terraform does not have rolling deployment built in (see https://github.com/hashicorp/terraform/issues/1552), we
are faking it using the create_before_destroy lifecycle property. This approach is based on the rolling deploy
strategy used by HashiCorp itself, as described by Paul Hinze
here. As a result, every time you
update your launch configuration (e.g. by specifying a new AMI to deploy), Terraform will:
Create a new ASG with the new launch configuration.
Wait for the new ASG to deploy successfully and for the instances to register with the load balancer (if you
associated an ELB or ALB with this ASG).
Destroy the old ASG.
Since the old ASG is only removed once the new ASG instances are registered with the ELB and serving traffic, there
will be no downtime. Moreover, if anything went wrong while rolling out the new ASG, it will be marked as
tainted (i.e. marked for deletion next time) and the original
ASG will be left unchanged, so again, there is no downtime.
Note that if all we did was use create_before_destroy, on each redeploy, our ASG would reset to its hard-coded
desired_capacity, losing the capacity changes from auto scaling policies. We solve this problem by using an
external data source that runs the Python script
get-desired-capacity.py to fetch the latest value of the
desired_capacity parameter:
If the script finds a value from an already-existing ASG, we use it, to ensure that the changes form auto scaling
events are not lost.
If the script doesn't find an already-existing ASG, that means this is the first deploy, and we fall back to the
hard-coded desired_capacity value.
{"treedata":{"name":"root","toggled":true,"children":[{"name":".circleci","children":[{"name":"config.yml","path":".circleci/config.yml","sha":"b94d0a76cfd53c1c265cecdfb7fc5709783c6cb5"}]},{"name":".gitignore","path":".gitignore","sha":"fd04ff401a18d9c8595968dbbd3a9996d37b6a8b"},{"name":".pre-commit-config.yaml","path":".pre-commit-config.yaml","sha":"addd5d0b1e36748c1c8c751c3fa7755f5dd2522d"},{"name":"CODEOWNERS","path":"CODEOWNERS","sha":"555c0c6e23a7502acbef94fb0b77bfa759ba11e8"},{"name":"CONTRIBUTING.md","path":"CONTRIBUTING.md","sha":"2fb126e11410f30d644f9219847f0a24a52ef4dc"},{"name":"LICENSE.txt","path":"LICENSE.txt","sha":"f4e3d9bd4717a044ed31ad847a300eee74371a78"},{"name":"README.md","path":"README.md","sha":"f8cc0a2b41af68c7781d726a6adb5c68f97ba274"},{"name":"examples","children":[{"name":"asg-rolling-deploy","children":[{"name":"README.md","path":"examples/asg-rolling-deploy/README.md","sha":"f5f2a8e2db00bb7c95975437ac913ef2f9769f7c"},{"name":"with-elb","children":[{"name":"main.tf","path":"examples/asg-rolling-deploy/with-elb/main.tf","sha":"3b6af13eb434d64ebace8b5b95752c6e9d45387d"},{"name":"outputs.tf","path":"examples/asg-rolling-deploy/with-elb/outputs.tf","sha":"330a02ec9378c2c9c4a1423b075384f4ae3ed241"},{"name":"user-data","children":[{"name":"user-data.sh","path":"examples/asg-rolling-deploy/with-elb/user-data/user-data.sh","sha":"7b5fbe6f33805eb5356e9c49db9bd5b141b0816a"}]},{"name":"vars.tf","path":"examples/asg-rolling-deploy/with-elb/vars.tf","sha":"dab89e417460b5fa2676796e945acf27803c6553"}]},{"name":"without-elb","children":[{"name":"main.tf","path":"examples/asg-rolling-deploy/without-elb/main.tf","sha":"04a3e849d0484816b5c7144c0a52276fadf8af6e"},{"name":"outputs.tf","path":"examples/asg-rolling-deploy/without-elb/outputs.tf","sha":"c8db3c807aab3d75888c2dd039b9e81b1312a137"},{"name":"vars.tf","path":"examples/asg-rolling-deploy/without-elb/vars.tf","sha":"1d6d2ee9904841723c7e7c29d9fe5e12e7f6fd6a"}]}]},{"name":"server-group","children":[{"name":"README.md","path":"examples/server-group/README.md","sha":"63c6917a20e10cb2b569e5314bbf3d4ec38ff491"},{"name":"ami","children":[{"name":"server.json","path":"examples/server-group/ami/server.json","sha":"bb08fa43aa2c146a2280af4df25fabc1a39cf65f"}]},{"name":"with-alb","children":[{"name":"main.tf","path":"examples/server-group/with-alb/main.tf","sha":"fac9c2eb58f114d0504d2a95a7eecf5c583314c2"},{"name":"outputs.tf","path":"examples/server-group/with-alb/outputs.tf","sha":"3565caece2f11d61dc0b0a6ed23cc58ce7e6e61c"},{"name":"user-data","children":[{"name":"user-data.sh","path":"examples/server-group/with-alb/user-data/user-data.sh","sha":"064d042a5d0ba6956f4eb8d7ece309b4d6eb4b33"}]},{"name":"vars.tf","path":"examples/server-group/with-alb/vars.tf","sha":"dc708850a0e1169ba4101135f6a707e79bd82850"}]},{"name":"with-elb","children":[{"name":"main.tf","path":"examples/server-group/with-elb/main.tf","sha":"02072d1d82f8f4b87485dab724e562a1513a49f5"},{"name":"outputs.tf","path":"examples/server-group/with-elb/outputs.tf","sha":"fd38915d96770f9588e2fcc79369e64449492286"},{"name":"user-data","children":[{"name":"user-data.sh","path":"examples/server-group/with-elb/user-data/user-data.sh","sha":"f0b175520e85da002b8c26a4a92347fb1eaa1d13"}]},{"name":"vars.tf","path":"examples/server-group/with-elb/vars.tf","sha":"4eb70bfff00b837c8404d044fee311b16e0e0c73"}]},{"name":"without-load-balancer","children":[{"name":"main.tf","path":"examples/server-group/without-load-balancer/main.tf","sha":"36c01e4999e6198d81987ce01383361fcc19d9e8"},{"name":"outputs.tf","path":"examples/server-group/without-load-balancer/outputs.tf","sha":"27911554f10688f23ba9f8e31eadd4409c635f97"},{"name":"user-data","children":[{"name":"user-data.sh","path":"examples/server-group/without-load-balancer/user-data/user-data.sh","sha":"064d042a5d0ba6956f4eb8d7ece309b4d6eb4b33"}]},{"name":"vars.tf","path":"examples/server-group/without-load-balancer/vars.tf","sha":"0724aa38905b313165b621e7866575e0e0abdd4b"}]}]}]},{"name":"modules","children":[{"name":"asg-rolling-deploy","children":[{"name":"README.md","path":"modules/asg-rolling-deploy/README.md","sha":"fbbc6657f0cef2493e9fdb7b42549d69e5ff4080","toggled":true},{"name":"describe-autoscaling-group","children":[{"name":"README.md","path":"modules/asg-rolling-deploy/describe-autoscaling-group/README.md","sha":"062e4ebc0b65610874998a354f441f56114b4e7e"},{"name":"boto3-1.7.10.zip","path":"modules/asg-rolling-deploy/describe-autoscaling-group/boto3-1.7.10.zip","sha":"4b76be11cfa98ddb4314e11a0b28700a11cd2fcc"},{"name":"get-desired-capacity.py","path":"modules/asg-rolling-deploy/describe-autoscaling-group/get-desired-capacity.py","sha":"a8e429f631655ba95eebc11a76fa4100b78eb4a6"}]},{"name":"main.tf","path":"modules/asg-rolling-deploy/main.tf","sha":"5a50b13f6a3eb8f096f4533f0c56fca775dd46de"},{"name":"outputs.tf","path":"modules/asg-rolling-deploy/outputs.tf","sha":"5225c1f98cfc9f91411d91eae7bd692168ea8f4c"},{"name":"vars.tf","path":"modules/asg-rolling-deploy/vars.tf","sha":"5de5dd0407b049772956cdd0c742d63e2eb4bd8d"}],"toggled":true},{"name":"server-group","children":[{"name":"README.md","path":"modules/server-group/README.md","sha":"13d133fb833f3298f4e4755ad9fe0d767a0682ad"},{"name":"main.tf","path":"modules/server-group/main.tf","sha":"73cf9740aee0dd7a3bdd14f834c68e810d006ecd"},{"name":"outputs.tf","path":"modules/server-group/outputs.tf","sha":"42217027f4a9807a5eae6e786b7a1cd0f6976137"},{"name":"rolling-deploy","children":[{"name":"boto3-1.7.10.zip","path":"modules/server-group/rolling-deploy/boto3-1.7.10.zip","sha":"852dcda88e4e760ce8bdb5c56823f08659959a50"},{"name":"helpers.py","path":"modules/server-group/rolling-deploy/helpers.py","sha":"35dc0d9d154895e3ede805fcb72a7fd6ac8c7c1f"},{"name":"rolling_deployment.py","path":"modules/server-group/rolling-deploy/rolling_deployment.py","sha":"e63f3e9a2072daf9d261b739ea9b6eddb2a95f0f"}]},{"name":"vars.tf","path":"modules/server-group/vars.tf","sha":"0e1897c77a9f88117e5c804acfd7cf5e1d52d72c"}]}],"toggled":true},{"name":"terraform-cloud-enterprise-private-module-registry-placeholder.tf","path":"terraform-cloud-enterprise-private-module-registry-placeholder.tf","sha":"ae586c0fe830819580e1009d41a9074f16e65bed"},{"name":"test","children":[{"name":"README.md","path":"test/README.md","sha":"cfa55a38cc6fbd09a311291216eb758159973629"},{"name":"asg_rolling_deploy_test.go","path":"test/asg_rolling_deploy_test.go","sha":"ca7bc5e7ac8b57f3eaee07f4f7417d1ab1e67cd3"},{"name":"go.mod","path":"test/go.mod","sha":"bcd74270514848b3d05920090296c46c3c992d6a"},{"name":"go.sum","path":"test/go.sum","sha":"ffcbd50ec065ec260e1e7c32a867d8acc8b25c58"},{"name":"server_group_test.go","path":"test/server_group_test.go","sha":"7248bb6bbb54a515d8ec9363febf4b4fed90390b"},{"name":"test_helpers.go","path":"test/test_helpers.go","sha":"6541cfcd06db09ede8a03dab111f93baea44e51a"}]}]},"detailsContent":"<h1 class=\"preview__body--title\" id=\"auto-scaling-group-with-rolling-deployment-module\">Auto Scaling Group with Rolling Deployment Module</h1><div class=\"preview__body--border\"></div><p>This Terraform Module creates an Auto Scaling Group (ASG) that can do a zero-downtime rolling deployment. That means\nevery time you update your app (e.g. publish a new AMI), all you have to do is run <code>terraform apply</code> and the new\nversion of your app will automatically roll out across your Auto Scaling Group. Note that this module <em>only</em>\ncreates the ASG and it's up to you to create all the other related resources, such as the launch configuration, ELB,\nand security groups.</p>\n<h2 class=\"preview__body--subtitle\" id=\"whats-an-auto-scaling-group\">What's an Auto Scaling Group?</h2>\n<p>An <a href=\"https://aws.amazon.com/autoscaling/\" class=\"preview__body--description--blue\" target=\"_blank\">Auto Scaling Group</a> (ASG) is used to manage a cluster of EC2 Instances. It\ncan enforce pre-defined rules about how many instances to run in the cluster, scale the number of instances up or\ndown depending on traffic, and automatically restart instances if they go down.</p>\n<h2 class=\"preview__body--subtitle\" id=\"how-does-rolling-deployment-work\">How does rolling deployment work?</h2>\n<p>Since Terraform does not have rolling deployment built in (see https://github.com/hashicorp/terraform/issues/1552), we\nare faking it using the <code>create_before_destroy</code> lifecycle property. This approach is based on the rolling deploy\nstrategy used by HashiCorp itself, <a href=\"https://groups.google.com/forum/#!msg/terraform-tool/7Gdhv1OAc80/iNQ93riiLwAJ\" class=\"preview__body--description--blue\" target=\"_blank\">as described by Paul Hinze\nhere</a>. As a result, every time you\nupdate your launch configuration (e.g. by specifying a new AMI to deploy), Terraform will:</p>\n<ol>\n<li>Create a new ASG with the new launch configuration.</li>\n<li>Wait for the new ASG to deploy successfully and for the instances to register with the load balancer (if you\nassociated an ELB or ALB with this ASG).</li>\n<li>Destroy the old ASG.</li>\n<li>Since the old ASG is only removed once the new ASG instances are registered with the ELB and serving traffic, there\nwill be no downtime. Moreover, if anything went wrong while rolling out the new ASG, it will be marked as\n<a href=\"https://www.terraform.io/docs/commands/taint.html\" class=\"preview__body--description--blue\" target=\"_blank\">tainted</a> (i.e. marked for deletion next time) and the original\nASG will be left unchanged, so again, there is no downtime.</li>\n</ol>\n<p>Note that if all we did was use <code>create_before_destroy</code>, on each redeploy, our ASG would reset to its hard-coded\n<code>desired_capacity</code>, losing the capacity changes from auto scaling policies. We solve this problem by using an\n<a href=\"https://www.terraform.io/docs/providers/external/data_source.html\" class=\"preview__body--description--blue\" target=\"_blank\">external data source</a> that runs the Python script\n<a href=\"/repos/v0.17.1/module-asg/modules/asg-rolling-deploy/describe-autoscaling-group/get-desired-capacity.py\" class=\"preview__body--description--blue\">get-desired-capacity.py</a> to fetch the latest value of the\n<code>desired_capacity</code> parameter:</p>\n<ul>\n<li>\n<p>If the script finds a value from an already-existing ASG, we use it, to ensure that the changes form auto scaling\nevents are not lost.</p>\n</li>\n<li>\n<p>If the script doesn't find an already-existing ASG, that means this is the first deploy, and we fall back to the\nhard-coded <code>desired_capacity</code> value.</p>\n</li>\n</ul>\n<h2 class=\"preview__body--subtitle\" id=\"how-do-you-use-this-module\">How do you use this module?</h2>\n<p>Check out the <a href=\"/repos/v0.17.1/module-asg/examples/asg-rolling-deploy\" class=\"preview__body--description--blue\">asg-rolling-deploy examples</a>.</p>\n","repoName":"module-asg","repoRef":"v0.14.0","serviceDescriptor":{"serviceName":"Auto Scaling Group (stateless)","serviceRepoName":"module-asg","serviceRepoOrg":"gruntwork-io","serviceMainReadmePath":"/modules/asg-rolling-deploy","cloudProviders":["aws"],"description":"Run an Auto Scaling Group for stateless apps. Supports zero-downtime, rolling deployment, auto healing, auto scaling, and IAM Roles.","imageUrl":"auto-scaling2.png","licenseType":"subscriber","technologies":["Terraform","Python","Bash"],"compliance":[],"tags":[""]},"serviceCategoryName":"Server orchestration","fileName":"README.md","filePath":"/modules/asg-rolling-deploy","title":"Repo Browser: Auto Scaling Group (stateless)","description":"Browse the repos in the Gruntwork Infrastructure as Code Library."}