Terraform Provisioner (Part-11)

Posted by

Terraform Provisioners are used to execute scripts or commands on the local machine or on the created resources after they have been created.

Types of Terraform Provisioners

  1. file
  2. local-exec
  3. remote-exec

1. file Provisioner

The file provisioner is used to copy files or directories from the local machine to a remote resource, such as a virtual machine (VM), after it has been created.

Example:

resource "aws_instance" "example" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  provisioner "file" {
    source      = "local_script.sh"
    destination = "/tmp/remote_script.sh"
  }

  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
}

2. local-exec Provisioner

The local-exec provisioner runs commands or scripts on the local machine where Terraform is being run, after the resource has been created.

Example:

resource "aws_instance" "example" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  provisioner "local-exec" {
    command = "echo ${aws_instance.example.public_ip} > ip_address.txt"
  }
}

3. remote-exec Provisioner

The remote-exec provisioner runs commands or scripts on the remote resource (e.g., a VM) after it has been created. This requires SSH or WinRM connection information.

Example:

resource "aws_instance" "example" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }

  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
}

Explanation

  1. file:
    • Purpose: Copy files or directories from the local machine to the remote resource.
    • When to Use: When you need to transfer configuration files, scripts, or other assets to a VM after it’s been created.
  2. local-exec:
    • Purpose: Run commands or scripts on the local machine after the resource has been created.
    • When to Use: When you need to perform actions on the local machine, such as logging information, sending notifications, or triggering other local processes.
  3. remote-exec:
    • Purpose: Run commands or scripts on the remote resource (e.g., VM) after it has been created.
    • When to Use: When you need to configure or set up software on the VM, such as installing packages, configuring services, or running custom scripts.

Complete Example Using All Three Provisioners

main.tf:

provider "aws" {
  region = "us-west-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  # Copy a file from local machine to VM
  provisioner "file" {
    source      = "local_script.sh"
    destination = "/tmp/remote_script.sh"
  }

  # Run a local command after the instance is created
  provisioner "local-exec" {
    command = "echo ${aws_instance.example.public_ip} > ip_address.txt"
  }

  # Run remote commands on the VM
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }

  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
}

Summary

  • file Provisioner: Copies files from the local machine to the VM.
  • local-exec Provisioner: Runs commands or scripts on the local machine after the resource creation.
  • remote-exec Provisioner: Runs commands or scripts on the remote VM after it has been created.

What is Connection Block in Terraform?

The connection block specifies the connection details that Terraform uses to communicate with the remote resource. It includes information such as the connection type (SSH or WinRM), user credentials, and host details.

Example of a Connection Block

Here’s a breakdown of a typical connection block for an SSH connection:

Example of a Connection Block

Here’s a breakdown of a typical connection block for an SSH connection:

resource "aws_instance" "example" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  # Provisioners would go here...

  connection {
    type        = "ssh"                   # The connection type (SSH in this case)
    user        = "ec2-user"              # The username to use for the connection
    private_key = file("~/.ssh/id_rsa")   # The path to the private key for authentication
    host        = self.public_ip          # The public IP address of the instance
  }
}

Explanation of Connection Block Fields

  • type: Specifies the type of connection. Common values are ssh for Secure Shell connections and winrm for Windows Remote Management connections.
  • user: The username to use for the connection. For AWS EC2 instances, this is often ec2-user or ubuntu, depending on the AMI.
  • private_key: The path to the private key file used for SSH authentication. This is usually specified using the file() function to read the private key from a file on the local machine.
  • host: The hostname or IP address of the remote resource. Typically, this is set to the public IP address of the instance (self.public_ip).

Example Using All Three Provisioners with Connection Block

Let’s revisit the complete example using all three provisioners with a connection block:

main.tf:

provider "aws" {
  region = "us-west-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"

  # Copy a file from local machine to VM
  provisioner "file" {
    source      = "local_script.sh"
    destination = "/tmp/remote_script.sh"
  }

  # Run a local command after the instance is created
  provisioner "local-exec" {
    command = "echo ${aws_instance.example.public_ip} > ip_address.txt"
  }

  # Run remote commands on the VM
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }

  connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("~/.ssh/id_rsa")
    host        = self.public_ip
  }
}

Summary

  • Connection Block: Specifies how Terraform connects to the remote resource.
    • type: The connection type (e.g., ssh or winrm).
    • user: The username to use for the connection.
    • private_key: The private key for SSH authentication.
    • host: The hostname or IP address of the remote resource.

List of connection types in Terraform remote-exec provisioner?


SSH (Secure Shell):

  • Type"ssh"
  • Description: Used for connecting to Unix-based operating systems, including Linux and macOS.
  • Commonly Used With: Linux and Unix-based cloud instances.
  • Example:

connection {
  type        = "ssh"
  user        = "your_username"
  private_key = file("~/.ssh/your_private_key.pem")
  host        = aws_instance.example.public_ip
}

WinRM (Windows Remote Management):

  • Type"winrm"
  • Description: Used for connecting to Windows-based operating systems.
  • Commonly Used With: Windows cloud instances.
  • Example:

connection {
  type     = "winrm"
  host     = aws_instance.example.private_ip
  user     = "Administrator"
  password = "your_password"
}

To establish a WinRM connection over HTTPS with SSL and ignore certificate validation, you can modify the connection block as follows:


connection {
  type        = "winrm"
  host        = aws_instance.example.private_ip
  user        = "Administrator"
  password    = "your_password"
  https       = true  # Enable HTTPS
  insecure    = true  # Ignore SSL certificate validation (for testing purposes, not recommended in production)
  port        = 5986  # Use the default HTTPS port for WinRM
  cacert      = "/path/to/ca.crt"  # Optional path to a CA certificate file (if required)
  cert        = "/path/to/client.crt"  # Optional path to a client certificate file (if required)
  key         = "/path/to/client.key"  # Optional path to the client certificate's private key file (if required)
  timeout     = "5m"  # Set a timeout for the WinRM connection
  max_retries = 3     # Maximum number of connection retries
}

Explanation of the options:

  • https: Set to true to enable HTTPS for the WinRM connection.
  • insecure: Set to true to ignore SSL certificate validation. This option is typically used for testing and debugging but is not recommended for production due to security concerns.
  • port: Use 5986 as the default port for HTTPS WinRM connections.
  • cacert: Optional path to a CA certificate file. Use this if your WinRM server’s certificate is signed by a custom CA.
  • cert: Optional path to a client certificate file. Use this if client certificate authentication is required.
  • key: Optional path to the private key file corresponding to the client certificate.
  • timeout: Set a timeout for the WinRM connection (e.g., "5m" for 5 minutes).
  • max_retries: Maximum number of connection retries in case of failures.

Terrafrom – Example Code for remote-exec, local-exec & file provisioner

resource "aws_instance" "first-ec2" {
  ami           = "ami-03d5c68bab01f3496" # us-west-2
  instance_type = "t2.micro"
  key_name 		= "rajesh-last"
  tags = {
    Name = "RajeshKumar"
  }
  
  connection {
      type     = "ssh"
      user     = "ubuntu"
      private_key = file("rajesh-last.pem")
      #host = aws_instance.web.public_ip
      host = self.public_ip
  }

  provisioner "local-exec" {
    command = "touch devopsschool-local"
  }
  
  provisioner "remote-exec" {
    inline = [
	  "sudo apt-get update",
      "sudo apt-get install apache2 -y",
	  "sudo systemctl start apache2",
    ]
  }
  
  provisioner "file" {
    source      = "terraform.tfstate.backup"
    destination = "/tmp/terraform.tfstate.backup"
  } 
}

Live Output details for above example:

Terraform init

Terraform Plan

Terraform validate

Terraform apply

Terraform apply getting failed due to below error

aws_instance.first-ec2 (remote-exec):   SSH Agent: false
aws_instance.first-ec2 (remote-exec):   Checking Host Key: false
aws_instance.first-ec2 (remote-exec):   Target Platform: unix
aws_instance.first-ec2: Still creating... [5m17s elapsed]
╷
│ Error: remote-exec provisioner error
│
│   with aws_instance.first-ec2,
│   on provisioner.tf line 21, in resource "aws_instance" "first-ec2":
│   21:   provisioner "remote-exec" {
│
│ timeout - last error: dial tcp 54.144.229.232:22: i/o timeout

The error occurring due to inbound and outbound rules

Check Security Group Rules – Verify that the security group rules are correctly applied and allow inbound SSH (port 22) traffic.

  1. Edit Inbound Rules:
    • Click on the Inbound Rules tab.
    • Click Edit inbound rules.
    • Add a rule to allow inbound SSH traffic:
      • Rule #: Choose an appropriate rule number (e.g., 100).
      • Type: SSH.
      • Protocol: TCP (should be automatically selected with SSH type).
      • Port Range: 22.
      • Source: 0.0.0.0/0 (or restrict it to your IP address for security).
      • Allow/Deny: Allow.
    • Click Save changes.
  2. Edit Outbound Rules:
    • Click on the Outbound Rules tab.
    • Click Edit outbound rules.
    • Add a rule to allow outbound SSH traffic:
      • Rule #: Choose an appropriate rule number (e.g., 100).
      • Type: SSH.
      • Protocol: TCP (should be automatically selected with SSH type).
      • Port Range: 22.
      • Destination: 0.0.0.0/0 (or restrict it to your IP address for security).
      • Allow/Deny: Allow.
    • Click Save changes.

Example Screenshots for Reference

Inbound Rules Example

Rule #TypeProtocolPort RangeSourceAllow/Deny
100SSHTCP220.0.0.0/0Allow

Outbound Rules Example

Rule #TypeProtocolPort RangeDestinationAllow/Deny
100SSHTCP220.0.0.0/0Allow

check connection from CMD if able to connect the VM

ssh -i "C:/Users/gufra/Downloads/.ssh/Terraform-Demo.pem" ubuntu@54.175.186.33

Run again Terraform apply

completed successfully

we can see folder created in local and also copy files in remote machine

in Local

In remote:

Also apache installed in remote machine

guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x