When it comes to using Auto Scaling for your application, a common question is “How can we configure an instance with our application and its dependencies, and make it ready to serve traffic?”
To answer this question, we can turn to lifecycle hooks.
Lifecycle hooks are powerful, because they let us pause the creation (or termination) of an instance so that we can sneak in and perform custom actions. We can, for example:
- Download required files
- Configure the instance
- Perform any other step required to make the instance ready
We can also perform an action before the instance terminates to download critical data or log files.
Once those actions are complete, we resume the process of launching (or terminating) the instance, and we make it available for our load balancer to send it traffic.
While we could use this with a manual approach, we can (and most likely want to) make this an automated process. But how does that work? Let’s take a look.
For this article, let’s focus our attention on using lifecycle hooks on instance launch instead of on instance termination.
How do Lifecycle Hooks Work?
Before we answer the question fully, let’s take a look at how lifecycle hooks work.
To create a lifecycle hook, we call the CLI command:
aws autoscaling put-lifecycle-hook --lifecycle-hook-name hook-name --auto-scaling-group-name my-group-name --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING
--lifecycle-transition parameter, which can be changed to EC2_INSTANCE_TERMINATING. This parameter tells Auto Scaling that any time it launches an instance, it needs to put the instance in a wait state before putting it in an inService state.
Here’s what it looks like:
Auto Scaling receives a scale out event telling it to add an instance or more. The instance is in a pending state, and because we have added a lifecycle hook, the instance goes into a pending:wait state. This is when we can run our custom actions. Once the custom actions are complete and we make a call to complete the lifecycle action, the instance changes to a pending:proceed state before finally going into the inService state.
What Custom Actions Can We Run?
Depending on our needs, we have a few different options for actions that we can run.
- Using CloudWatch events to invoke a Lambda function
- Using a notification target like Amazon SNS or SQS to send notifications
- Running a script on the instance
With option number one, we can call a Lambda function, give it some information, and get a result back. This is possible because Auto Scaling submits an event for a lifecycle action (like instance launching or terminating) to CloudWatch events. That event can, in turn, invoke the Lambda function and pass in information about the instance that is launching or terminating, as well as a special token that we can use to control the lifecycle action.
With option number two, we can send messages with information about the instance and again have a token to control the lifecycle action.
The third option is also very powerful because it allows us to run commands on the instance itself with user data. This user data can download updates, application files and configuration files, run commands on the instance to get it ready to serve traffic, and more.
So now we know what lifecycle hooks are and what options we have when performing custom actions, but how do we complete the action and resume the instance launch? Because at the end of the day, that instance has a role to do and we need it to be ready as soon as possible.
I already mentioned making a call to resume the instance launch process. That call, from the CLI, looks like this:
aws autoscaling complete-lifecycle-action --lifecycle-hook-name my-lifecycle-hook --auto-scaling-group-name my-auto-scaling-group --lifecycle-action-result CONTINUE --lifecycle-action-token bcd2f1b8-9a78-44d3-8a7a-4dd07d7cf635
As mentioned a few paragraphs above, whatever option we choose for our custom actions, we get a special token that is a universally unique identifier (UUID). We can use it here to complete the action.
When completing the action, we don’t necessarily need to go forward. In the command example, I use the
--lifecycle-action-result option to specify CONTINUE, but I could also specify ABANDON if something went wrong and we don’t want the instance to proceed. Using ABANDON tells Auto Scaling that it can terminate the instance, and it stops all actions including any other remaining lifecycle hooks.
But this is not the only way to complete actions. We also have a timeout. By default, this timeout is set to 60 minutes. If our actions run, and if we don’t complete the action before the timeout expires, then the timeout will automatically conclude actions. If you need more time to complete an action, but the timeout is going to expire, you can send a heartbeat to renew the timeout.
We’ve now learned about lifecycle hooks and what we can do with them, as well as what options we have when using them. But before I leave you to use these hooks, do let me warn you about one more potential snag.
When an instance is getting ready to serve traffic, Auto Scaling uses a cooldown period to make sure that it doesn’t trigger another scale out event while another instance is already being provisioned (or being taken down). Because otherwise we would end up with too many or too few instances.
When we implement lifecycle hooks, depending on how much work we perform, we could be adding a non-trivial amount of time to how long it takes for an instance to be ready to serve traffic. If that’s the case, we may need to increase the cool down period or else Auto Scaling thinks it needs to launch more instances. Do keep this in mind.
I hope you’ve enjoyed this write up, and I hope you’ve learned something useful from reading it. If you’re interested in learning more, check out the AWS DevOps Engineer Professional level course where we cover this and more advanced deployment topics.