[toc] ### Terraform HCL #### Write a tf file to create an EC2 instance in us-east-1 region ````yaml= ## Create an EC2 instance in us-east-1 provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } resource "aws_instance" "sl-tf-demo-web" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" } ```` #### EC2 instance with tags ````yaml= ## Create an EC2 instance in us-east-1 provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } resource "aws_instance" "sl-tf-demo-web" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" tags = { Name = "demo-tf-sl" } } ```` #### EC2 instance with user-data [inline] ````yaml= ## Create an EC2 instance in us-east-1 provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } resource "aws_instance" "sl-tf-with-sg" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" vpc_security_group_ids = ["sg-0435201630ec513c3"] tags = { Name = "Feb24-sl-tf-demo" } user_data = <<EOF #!/bin/bash yum update -y yum install -y httpd.x86_64 systemctl start httpd.service systemctl enable httpd.service echo "Welcome to Simplilearn CMAT from $(hostname -f). This web server was created using Terraform" > /var/www/html/index.html EOF } ```` :arrow_right: Validation: - Check if the correct SG is attached to the instance. - Verify that the user data is visible in Instance properties (Actions --> instance Settings --> Edit User Data) :mag: ***Notes:*** - *Please create a new Security Group and open incoming ports 22, 80 and 443. Go to security Groups settings and copy the SG id and replace the value in above tf file.* - *Please create a new directory and treat this as a new terraform infra project* #### EC2 instance with user-data [via separate script] ````yaml= ## Create an EC2 instance in us-east-1 provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } resource "aws_instance" "sl-tf-with-sg" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" vpc_security_group_ids = ["sg-0435201630ec513c3"] tags = { Name = "Script-tf-demo" } user_data = "${file("myscript.sh")}" EOF } ```` :mag: ***Notes:*** - *Make sure you have the myscript.sh file present in your terraform project directory* **Sample Directory structure** ```` labsuser@ip-172-31-18-209:~/terraform-demos/script-user-data$ tree . ├── myscript.sh ├── script-user-data.tf ```` ##### Sample user data script ```sh= ## vi mycript.sh #!/bin/bash sudo su - yum update -y yum install -y httpd.x86_64 systemctl start httpd.service systemctl enable httpd.service echo “Welcome to Simplilearn CMAT from $(hostname -f)” > /var/www/html/index.html ``` ##### Validate user data :::success **You can check the user data of the instance as follows:** Select the instance --> Click on **Actions** --> go to **instance settings** --> Click on **Edit user data** ![](https://hackmd.io/_uploads/r1j7jB4o2.png) You should now be able to see the user data of the instance: ![](https://hackmd.io/_uploads/ByENiSVj2.png) :arrow_right: *If you want to edit the user data then you have to stop the instance first* ::: #### EC2 instance with Security Group ````yaml= ## Create an EC2 instance in us-east-1 with a Security Group provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } ## Resource definition resource "aws_instance" "user-data-script" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" vpc_security_group_ids = [aws_security_group.web-sg.id] # resourcetype.resourcename.resourceproperty tags = { Name = "ec2-with-sg-tf-demo" env = "dev" } user_data = "${file("myscript.sh")}" } resource "aws_security_group" "web-sg" { name = "terraform-web-sg" description = "Allow HTTP, HTTPS and SSH traffic via Terraform" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } ```` ##### myscript.sh ````sh= #!/bin/bash sudo yum update -y sudo yum install -y httpd.x86_64 sudo systemctl start httpd.service sudo systemctl enable httpd.service sudo su - echo "Welcome to Simplilearn CMAT from $(hostname -f). This web server was created using Terraform" > /var/www/html/index.html ```` **Understanding CIDR Notation** :::info ```` 172.31.0.0/16 --> 172.31.{0-255}.{0-255} --> 256x256 --> 65,536 10.0.0.0/8 --> 10.{0-255}.{0-255}.{0-255} --> 256x256x256 --> 16,777,216 192.168.0.0/24 --> 192.168.0.{0-255} --> 256 0.0.0.0/0 --> Internet / All IPs in the world 1.2.3.4/32 --> 1.2.3.4 in CIDR format ```` ::: #### Multiple EC2 instances using count ````yaml= ## Create an EC2 instance in us-east-1 with a Security Group provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } ## Resource definition resource "aws_instance" "user-data-script" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" vpc_security_group_ids = [aws_security_group.web-sg.id] # resourcetype.resourcename.resourceproperty count = 3 tags = { Name = "terraform-web-${count.index}" env = "dev" } } resource "aws_security_group" "web-sg" { name = "terraform-web-sg" description = "Allow HTTP, HTTPS and SSH traffic via Terraform" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } ```` #### Outputs in Terraform **provider.tf** ````yaml= ## Create an EC2 instance with outputs provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } ```` **resources.tf** ````yaml= ## Resource definition resource "aws_instance" "user-data-script" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" vpc_security_group_ids = [aws_security_group.web-sg.id] # resourcetype.resourcename.resourceproperty count = 3 tags = { Name = "terraform-web-${count.index}" env = "dev" } } resource "aws_security_group" "web-sg" { name = "terraform-web-sg" description = "Allow HTTP, HTTPS and SSH traffic via Terraform" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } ```` **outputs.tf** ````yaml= output "security-group-id" { value = aws_security_group.web-sg.id } output "EC2-instance-ip-1" { value = aws_instance.user-data-script[0].public_ip } output "EC2-instance-ip-2" { value = aws_instance.user-data-script[1].public_ip } output "EC2-instance-ip-3" { value = aws_instance.user-data-script[2].public_ip } ```` #### Variables in Terraform **provider.tf** ````yaml= ## Create an EC2 instance with outputs provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } ```` **vars.tf** ````yaml= variable "instance-type" { description = "EC2 Instance type to be used" type = string default = "t2.micro" } variable "ami" { description = "EC2 AMI to be used" type = string default = "ami-07d9b9ddc6cd8dd30" } variable "env" { description = "Specify the Environment" type = string } ```` **resources.tf** ````yaml= ## Resource definition resource "aws_instance" "user-data-script" { ami = var.ami instance_type = var.instance-type vpc_security_group_ids = [aws_security_group.web-sg.id] # resourcetype.resourcename.resourceproperty tags = { Name = "tf-var-webserver" env = var.env } } resource "aws_security_group" "web-sg" { name = "tf-var-web-sg" description = "Allow HTTP, HTTPS and SSH traffic via Terraform" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } ```` **outputs.tf** ````yaml= output "security-group-id" { value = aws_security_group.web-sg.id } output "EC2-instance-ip" { value = aws_instance.user-data-script.public_ip } ```` :arrow_right: Your terraform directory should look like the following: ````yaml= labsuser@ip-172-31-18-209:~/terraform-demos/vars$ tree . ├── main.tf ├── providers.tf ├── outputs.tf └── vars.tf ```` :arrow_right: Apply the changes and verify in AWS Console ##### Override the default variables on the command line: ```yaml= Syntax: terraform apply -var "variable_name=variable=value" Example: terraform apply -var "instance-type=t3.micro" ```` :arrow_right: **Classroom activity:** :::info - Edit above exercise to add one more variable for AMI and run the terraform with AMI id - **ami-07d9b9ddc6cd8dd30** ::: ##### Working with tfvars files ````yaml= ## prod.tfvars instance-type = "t3.micro" ami = "ami-07d9b9ddc6cd8dd30" env = "prod" ```` ````yaml= ## dev.tfvars instance-type = "t3.micro" ami = "ami-07d9b9ddc6cd8dd30" env = "dev" ```` :arrow_right: Your terraform directory should look like the following: ````yaml= labsuser@ip-172-31-18-209:~/terraform-demos/vars$ tree . ├── main.tf ├── providers.tf ├── outputs.tf ├── myvars.tfvars └── vars.tf ```` **Run the following command to initialize the variables from tfvars file** ````yaml= terraform apply -var-file="myvars.tfvars" ```` #### auto.tfvars files :::info If we use extension ".auto.tfvars" for the variables files, then they would be loaded automatically and -var-file flag on the command line would not be needed. ::: :::warning Rename one of the "tfvars" files created above to "auto.tfvars". For example: ```hcl= mv dev.tfvars dev.auto.tfvars ``` Once done, run terraform apply again. You should see devvars variables getting initialized automatically without the need for -var-file flag with terraform CLI. ::: ### Terraform States #### Local states ````yaml= labsuser@controller:~/terra-docs/ec2-with-sg$ terraform state Subcommands: list List resources in the state mv Move an item in the state pull Pull current state and output to stdout push Update remote state from a local state file replace-provider Replace provider in the state rm Remove instances from the state show Show a resource in the state ```` #### Understanding terraform State files (Local) ##### before terraform init ```` labsuser@controller:~/terra-docs/terra-state$ ls -al total 28 drwxrwxr-x 3 labsuser labsuser 4096 Mar 26 14:17 . drwxrwxr-x 6 labsuser labsuser 4096 Mar 26 14:02 .. -rw-rw-r-- 1 labsuser labsuser 708 Mar 26 14:03 main.tf ```` ##### terraform init/plan ```` labsuser@controller:~/terra-docs/terra-state$ ls -al total 28 drwxrwxr-x 3 labsuser labsuser 4096 Mar 26 14:17 . drwxrwxr-x 6 labsuser labsuser 4096 Mar 26 14:02 .. drwxr-xr-x 3 labsuser labsuser 4096 Mar 26 14:04 .terraform -rw-r--r-- 1 labsuser labsuser 1377 Mar 26 14:04 .terraform.lock.hcl -rw-rw-r-- 1 labsuser labsuser 708 Mar 26 14:03 main.tf ```` ##### terraform apply (only once) ```` labsuser@controller:~/terra-docs/terra-state$ ls -al total 28 drwxrwxr-x 3 labsuser labsuser 4096 Mar 26 14:17 . drwxrwxr-x 6 labsuser labsuser 4096 Mar 26 14:02 .. drwxr-xr-x 3 labsuser labsuser 4096 Mar 26 14:04 .terraform -rw-r--r-- 1 labsuser labsuser 1377 Mar 26 14:04 .terraform.lock.hcl -rw-rw-r-- 1 labsuser labsuser 708 Mar 26 14:03 main.tf -rw-rw-r-- 1 labsuser labsuser 4437 Mar 26 14:17 terraform.tfstate ```` ##### terraform apply (twice) ```` labsuser@controller:~/terra-docs/terra-state$ ls -al total 36 drwxrwxr-x 3 labsuser labsuser 4096 Mar 26 14:22 . drwxrwxr-x 6 labsuser labsuser 4096 Mar 26 14:02 .. drwxr-xr-x 3 labsuser labsuser 4096 Mar 26 14:04 .terraform -rw-r--r-- 1 labsuser labsuser 1377 Mar 26 14:04 .terraform.lock.hcl -rw-rw-r-- 1 labsuser labsuser 708 Mar 26 14:20 main.tf -rw-rw-r-- 1 labsuser labsuser 4439 Mar 26 14:22 terraform.tfstate -rw-rw-r-- 1 labsuser labsuser 4437 Mar 26 14:22 terraform.tfstate.backup ```` ##### Activity - Remove a resource from Terraform state ````yaml=! Create an instance and security group using previously used tf files. check terraform state: terraform state list Remove Security from terraform state: terraform state rm <sg-id> verify terraform state again: terraform state list Run terraform destroy: terraform destroy -auto-approve Validate: Check on AWS console. EC2 instance should be terminated already however Security group should still be present and not destroyed as it was no longer managed as part of "terraform state". ```` #### Using Amazon S3 as backend for terraform "remote state" ##### Create a backend definition (backend.tf) ```hcl= terraform { backend "s3" { bucket = "tfstate-sk" key = "dev/tfstate" region = "us-east-1" profile = "sladmin" } } ``` :arrow_right: *Profile name should match with what you have configured when configuring AWSCLI. For example if you have not mentioned any profile name then it should be "default". You can check your profile name in "~/.aws/config".* ##### Sample backend file (for simplilearn labs) ````yaml= terraform { backend "s3" { bucket = "<s3-bucket-name>" key = "<bucket-prefix>" region = "us-east-1" access_key = "<Your-access-key>" secret_key = "<Your-secret-key>" token = "<Your-temp-token>" } } ```` ##### provider.tf ```hcl= ## Create an EC2 instance with outputs provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } ``` :arrow_right: *Profile name should match with what you have configured when configuring AWSCLI. For example if you have not mentioned any profile name then it should be "default". You can check your profile name in "~/.aws/config".* ##### resource.tf ```hcl= resource "aws_instance" "sl-tf-web" { ami = "ami-0f34c5ae932e6f0e4" instance_type = "t2.micro" tags = { Name = "webnode-v1" env = "dev" } } ``` :arrow_right: ***Please remember:*** - *Apply usual terraform workflow (init/plan/apply) and validate the changes. You Should be able to see a new file with your terraform state in the S3 bucket/prefix you mentioned in the backend definition* - *Enable Bucket versioning for the S3 bucket so that the state files do not get overwritten everytime you do "terraform apply"* #### Terraform remote state on Terraform Cloud ##### Complete the following activity from Hashicorp official tutorials :::warning https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-remote ::: ### Working with Terraform Provisioners ##### Steps to be performed :::info - Create the EC2 instance - Make sure the Security group has incoming ports (80/443/22) open - make sure the public key is copied on the node (asymmetric keypair) - copy a script file from controller to the node (nginx.sh) - SSH into the instance - run the nginx.sh script - validate - SSH into the instance and check nginx service - check if nginx is accessiebl via the public-IP in a brower #### Files needed - provider.tf - resources.tf - nginx.sh - asymmetric keypair - outputs (optional) ::: :::warning 1. Create provider.tf file and add your access-key and secret-access-key for authentication to AWS account 2. Create create-ec2.tf file which will contain the resource definition 3. Create nginx.sh script file which will be copied to newly created ec2 instance and run to install and configure nginx on the newly created EC2 node. 4. Create outputs.tf to get public ip and public DNS of the ec2 instance to access nginx server. 5. Create sg.tf to create a security group which allows ports 22,80 and 443 incoming. 6. Create asymmetric key pair (locally) using ssh-keygen which will be used by Terraform to ssh into the instance. 7. Run terraform apply and Validate the changes ::: ##### Create a project folder and cd into it ````yaml= mkdir provisioners-tf cd provisioners-tf ```` ##### providers.tf ````yaml= ## vi providers.tf provider "aws" { access_key = "<Your_Access_Key>" secret_key = "<Your_Secret_Key>" token = "<Your_Security_Token>" region = "us-east-1" } ```` ##### create-ec2.tf ````yaml= ## vi create-ec2.tf ## Working with "remote-exec" and "file" provisioners resource "aws_key_pair" "sk-key" { key_name = "sk-key" public_key = file("sk-key.pub") } resource "aws_instance" "mynginx" { ami = "ami-0440d3b780d96b29d" instance_type = "t2.micro" vpc_security_group_ids = [aws_security_group.web-sg.id] key_name = aws_key_pair.sk-key.key_name tags = { Name = "tf-nginx" } provisioner "file" { source = "nginx.sh" destination = "/tmp/nginx.sh" } provisioner "remote-exec" { inline = [ "chmod +x /tmp/nginx.sh", "sudo /tmp/nginx.sh", ] } connection { host = self.public_ip type = "ssh" user = "ec2-user" private_key = file("sk-key") } } ```` :mag: *Make sure to update the correct ***ami-id*** as per the AWS region and your account* ##### sg.tf ```hcl= resource "aws_security_group" "web-sg" { name = "web-sg-tf" description = "Allow HTTP, HTTPS and SSH traffic via Terraform" ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } ``` ##### nginx.sh ````yaml= ## vi nginx.sh #!/bin/bash # install nginx sudo yum update sudo yum install -y nginx # make sure nginx is started systemctl start nginx ```` ##### outputs.tf ````yaml= ## vi outputs.tf ## Outputs to be displayed after "terraform run" output "public_ip" { value = aws_instance.mynginx.public_ip } output "public_dns" { value = aws_instance.mynginx.public_dns } ```` ##### Create "remote-exec" keypair ````yaml= root@terraform:~/provisioners-tf# ssh-keygen -t rsa -C a@b Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): sk-key Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in sk-key Your public key has been saved in sk-key.pub The key fingerprint is: SHA256:2EXO2eDb8cWxpzZyCcCHrWW2xn3wS56GKx5xjlh5hxI a@b The key's randomart image is: +---[RSA 3072]----+ | .+o . | | =o=* ..o| | *E+o ++| | o ..o*+o=+| | . S .*o+@oo| | o B=.* | | . o .o | | ... | | ... | +----[SHA256]-----+ root@terraform:~/provisioners-tf# ```` ##### Final Directory / Module structure Once all the steps are complete, your directory should look like the following: ````yaml= root@terraform:~/provisioners-tf# tree . ├── create-ec2.tf ├── nginx.sh ├── outputs.tf ├── provider.tf ├── sg.tf ├── sk-key └── sk-key.pub 0 directories, 7 files root@terraform:~/provisioners-tf# ```` ##### Run terraform apply and validate the changes ````yaml=! Apply Terraform: terraform apply -auto-approve Validate: Grab the Public IP (or Public DNS) of the instance from Terraform outputs and access it in a web browser such as chrome or firefox. You should be able to see the default nginx page. ```` :mag: If nginx is not accessible on the web browser, make sure you are accessing it on http (80) and not on https (443) ### Terraform Modules :::info - https://developer.hashicorp.com/terraform/tutorials/modules - ::: ### References and Links :::success - https://www.harness.io/blog/blue-green-canary-deployment-strategies - https://docs.aws.amazon.com/pdfs/whitepapers/latest/introduction-devops-aws/introduction-devops-aws.pdf - https://developer.hashicorp.com/terraform/language/settings/backends/configuration - https://www.redhat.com/en/topics/security/what-is-cve - https://developer.hashicorp.com/terraform/tutorials - https://developer.hashicorp.com/terraform/tutorials/configuration-language - https://developer.hashicorp.com/terraform/tutorials/certification-associate-tutorials-003 - https://catalog.us-east-1.prod.workshops.aws/workshops/41c5a1b6-bd3e-41f4-bd46-85ab7dc6dad4/en-US - https://aws-quickstart.github.io/workshop-terraform-modules/ :::