Automate EC2 Replacement at Scale with AWS CLI

Consider a scenario: You have hundreds of EC2 instances which need to be upgraded. Although routine patches & updates can be automated via Systems Manager, major upgrades such as OS updates need a more involved approach. Maybe you’re looking for much more than an upgrade, like re-platforming your application to another operating system. That requires changing the AMI of your instances. The catch is that you don’t want your instances’ external identifiers to change, like their public & private IPs, DNS addresses, etc. This article describes how you can use AWS CLI to automate all this!

In this article, we will build a Bash script that uses AWS CLI to replace hundreds of instances in a go. The approach is simple:

  • First, identify the instances you need to replace, either by tagging them or using a common attribute like AMI ID.
  • Next, fetch these instances & loop through them. For each instance:
    • Extract the key attributes you need, like the instance ID, type, IP, etc.
    • Terminate the instance & wait for it to be terminated.
    • Launch a new instance with the new AMI but the same attributes like IP addresses.

So let’s dive in. First to fetch the instances that need replacement. In this case, we’re looking for instances tagged Upgrade = Pending:

aws ec2 describe-instances \
    --filters "Name=tag:Upgrade,Values=Pending" \
    --output json \
    --query "Reservations[*].Instances[*].
        {Name:Tags[?Key=='Name'].Value[]|[0],
        ID:InstanceId,Type:InstanceType,
        Subnet:SubnetId,IP:PrivateIpAddress}"

Note how the --query param above fetches only the bits we need, nothing more!

Next, we run the output of this command through jq to clean it up. We then loop over the instances & for each instance, we first terminate it after capturing all its metadata we’ll need for the replacement instance:

aws ec2 terminate-instances --instance-ids "$ID"
aws ec2 wait instance-terminated --instance-ids "$ID"

Once that instance has terminated, we can launch the replacement. In this case, we want the replacement to have the same name, type & private IP as the old instance:

aws ec2 run-instances \
        --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$NAME}]" \
        --image-id "$AMI" \
        --instance-type "$TYPE" \
        --subnet-id "$SUBNET" \
        --private-ip-address "$IP"

And just like that, you have changed the AMI of your instance without changing its IP. The AMI used above could have your application pre-baked into it if required.

The entire script looks like this:

Here is what a sample run of this script looks like:

$ ./replace-instances.sh 
Fetching instances tagged with Upgrade = Pending...

Fetched the following instances:
{"Name":"Instance 1","ID":"i-0a7fe41ba694dd618","Type":"t2.micro","Subnet":"subnet-9e7c52e4","IP":"172.31.23.85"}
{"Name":"Instance 2","ID":"i-07449f0e14bb9d52b","Type":"t2.micro","Subnet":"subnet-9e7c52e4","IP":"172.31.17.212"}
{"Name":"Instance 3","ID":"i-0bf6a653aeac83d7f","Type":"t2.micro","Subnet":"subnet-9e7c52e4","IP":"172.31.21.57"}

Upgrading instance:
{"Name":"Instance 1","ID":"i-0a7fe41ba694dd618","Type":"t2.micro","Subnet":"subnet-9e7c52e4","IP":"172.31.23.85"}
This instance will be TERMINATED & replaced with a new instance!
Press enter/return to begin...

Terminating this instance...
Terminated!
Launching NEW instance with the same name, type & IP...
DONE!

Upgrading instance:
{"Name":"Instance 2","ID":"i-07449f0e14bb9d52b","Type":"t2.micro","Subnet":"subnet-9e7c52e4","IP":"172.31.17.212"}
This instance will be TERMINATED & replaced with a new instance!
Press enter/return to begin...

Terminating this instance...
Terminated!
Launching NEW instance with the same name, type & IP...
DONE!

Upgrading instance:
{"Name":"Instance 3","ID":"i-0bf6a653aeac83d7f","Type":"t2.micro","Subnet":"subnet-9e7c52e4","IP":"172.31.21.57"}
This instance will be TERMINATED & replaced with a new instance!
Press enter/return to begin...

Terminating this instance...
Terminated!
Launching NEW instance with the same name, type & IP...
DONE!

In this particular example, there were 3 instances running Ubuntu 18 before running the script:

They were all replaced with Ubuntu 20 instances with the same names, types & private IPs:

About the Author ✍🏻

Harish KM is a Principal DevOps Engineer at QloudX & a top-ranked APN Ambassador. 👨🏻‍💻

With over a decade of industry experience as everything from a full-stack engineer to a cloud architect, Harish has built many world-class solutions for clients around the world! 👷🏻‍♂️

With over 20 certifications in cloud (AWS, Azure, GCP), containers (Kubernetes, Docker) & DevOps (Terraform, Ansible, Jenkins), Harish is an expert in a multitude of technologies. 📚

These days, his focus is on the fascinating world of DevOps & how it can transform the way we do things! 🚀

Leave a Reply

Your email address will not be published.