The Case for Immutability — Importing existing INFRA into a Terraform state (Part 1)
What’s the hype about Immutability?
The case for immutability has rocked the boat for sysadmins since the «DevOps skill set» was coined sometime 2013. But «immutability» had not been a buzzword before the notion of «Infrastructure as Code». The codification / templating of configuration and infra components into a seamless definition has promised quick recovery in case of mistaken deployments, much like how the Avenger infinity stones can bring back the good state with just one finger snap.
But before you achieve immutability, a DevOps engineer needs to define an airtight «state», and creating those states should consume 90% of daily DevOps tasks. Please do not confuse «state» with the method of «templating». Logically the state is a by-product of the template and you need templating (infra-coding skills) to reach that airtight state. So ideally speaking, as long as infra is «templated», gone are the days of firefighting an infra incident where the command line is the standard weapon and engineers have to think on their feet, churn on the spot command after command to resolve a system-wide downtime. The answer to making life easier and error-free is Infra Immutability and this blog series about Immutability will discuss that (in a Terraform-based method).
Importation — from manual to immutable
Today, we discuss state importation, which is the entry point of sysadmins who transition from the traditional thinking to the DevOps mindset. For large organizations who are just getting into the DevOps culture and starting to automate things, templating is seen as an immediate solution to automate their manually created «components».
Suppose somebody in your organization created an EC2 instance manually, and here you come (the new DevOps engineer) tasked to do a template out of it:
On the most basic terms of templating a manually created cloud instance is you need to define first the Terraform resource in your script:
resource "aws_instance" "playtime" {
}
The act of defining the resource is your singular attempt to represent your running cloud instance as a template object. Your objective is to represent this object in an immutable state by running a Terraform import command in the following format:
The act of defining the resource is your singular attempt to represent your running cloud instance as a template object. Your objective is to represent this object in an immutable state by running a Terraform import command in the following format:
The act of defining the resource is your singular attempt to represent your running cloud instance as a template object. Your objective is to represent this object in an immutable state by running a Terraform import command in the following format:
AWS_PROFILE={AwsProfileName} terraform import {resource_type}.{resource_name} {instance_id}
So in our case, it will be executed as:
AWS_PROFILE=<AwsProfileName> terraform import aws_instance.playtime instance_id
As you can see the structured import command was derived from logic stating that
«existing instance ID must be represented in a Terraform instance state called «playtime» defined in my Terraform script, authorized by my AWS profile «AwsProfileName» __ .
Once executed, you will be rewarded with a fulfilling message that you have entered the immutable realm:
$ AWS_PROFILE=AwsProfileName terraform import aws_instance.playtime instance_id
aws_instance.playtime: Importing from ID "instance_id"...
aws_instance.playtime: Import complete!
Imported aws_instance (ID: instance_id)
aws_instance.playtime: Refreshing state... (ID: instance_id)
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
$
Now that it is immutable, what makes it a cut above traditional infra?
So the evidence of your success in entering the immutable realm is the statefile, which is a JSON-typed flat text indicating your instance parameters. You gain the following advantages:
- You can manage the parameters and make it fully deployable and destroyable at whim. A state represents your infra components as a code «object» which you just define to deploy or destroy.
- In turn, you lessen the act of manual clicks just to manage the attributes of your cloud instance, which significantly lessens the probability of human error.
- The most important advantage is you however, is that you save time and headache during an emergency. Immutable cloud instances are different from stateless, which means they are pre-configured and locked as a state for deployment. Your infra exists as a templated state locked in time. No more post-deploy configuration, so your golden-arrow solution to infra incidents is a quick redeployment based on the immutable state, which in turn gives you the next advantage…
- The most visible metric you have as a DevOps engineer is that you can achieve a near-zero time for Infra recovery.
Your clients are mad because it took you 3 hours to recover from a failed deployment? Why not try templating the infra and just re-deploy in case of failure?
Reference
Just in case you want to see the statefile which will help you build the full blown Terraform script:
$ cat terraform.tfstate
{
"version": 3,
"terraform_version": "0.11.13",
"serial": 1,
"lineage": "xxxx",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"aws_instance.playtime": {
"type": "aws_instance",
"depends_on": [],
"primary": {
"id": "instance_id",
"attributes": {
"ami": "ami-xxx",
"arn": "arn:aws:ec2:us-east-2:xxxxx:instance/instance_id",
"associate_public_ip_address": "true",
"availability_zone": "us-east-2c",
"cpu_core_count": "1",
"cpu_threads_per_core": "1",
"credit_specification.#": "1",
"credit_specification.0.cpu_credits": "standard",
"disable_api_termination": "false",
"ebs_block_device.#": "0",
"ebs_optimized": "false",
"ephemeral_block_device.#": "0",
"get_password_data": "false",
"iam_instance_profile": "",
"id": "instance_id",
"instance_state": "running",
"instance_type": "t2.micro",
"ipv6_address_count": "0",
"ipv6_addresses.#": "0",
"key_name": "xxx",
"monitoring": "false",
"network_interface.#": "0",
"password_data": "",
"placement_group": "",
"primary_network_interface_id": "xxx",
"private_dns": "xxxx",
"private_ip": "xxx",
"public_dns": "xxxx",
"public_ip": "xxxx",
"root_block_device.#": "1",
"root_block_device.0.delete_on_termination": "true",
"root_block_device.0.iops": "100",
"root_block_device.0.volume_id": "xxx",
"root_block_device.0.volume_size": "30",
"root_block_device.0.volume_type": "gp2",
"security_groups.#": "1",
"security_groups.2525401260": "xxx",
"source_dest_check": "true",
"subnet_id": "xxxx",
"tags.%": "0",
"tenancy": "default",
"volume_tags.%": "0",
"vpc_security_group_ids.#": "1",
"vpc_security_group_ids.2569658128": "xxxx"
},
"meta": {
"xxxx": {
"create": 600000000000,
"delete": 1200000000000,
"update": 600000000000
},
"schema_version": "1"
},
"tainted": false
},
"deposed": [],
"provider": "provider.aws"
}
},
"depends_on": []
}
]
}