[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**

You should now be able to see the user data of the instance:

: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/
:::