Site icon UnixArena

Jenkins Integration – Terraform IaC Pipeline Setup

How to integrate Terraform IaC (Infrastructure as Code) into Jenkins? Cloud infrastructure offers great agility and flexibility but you need the right toolsets to manage the power of public cloud platforms. To operate in the public cloud efficiently, You need to treat infrastructure as code (IaC). IaC eliminates the manual interaction required to configure Cloud and On-PREM resources. It gives you access to automated tools that you can manage the same way you manage the configuration files. IaC code can be developed, test, deploy, and manage those configurations using source control.

Terraform is one of the most popular IaC tools in the market. In most organizations, Jenkins is primarily used as a CI/CD tool for the software development lifecycle to build and test your products continuously.

To bring governance on terraform CLI, you need tools like Jenkins. Jenkins and Terraform integrations bring a lot more value than paid tools or cloud native toolsets. This integration can be easily scaled for an enterprise as well.

Jenkins – Terraform – Branching strategy

Pre-requisite:

  1. EC2 instance with Jenkins installed and configured
  2. Sample terraform code on git repository

Configure Terraform Plugin on Jenkins:

1. Login to the Jenkins console as an admin user and navigate to the “Manage Jenkins” section.

Manage Jenkins

2. Click on “Manage Plugins”.

Manage Plugins

3. Search for the “terraform” plugin. Select the plugin and click on “Install without restart”

Search for terraform plugin

4. Install the “terraform” plugin.

Terraform plugin installed

5. Click on “Global Tool Configuration” to configure terraform.

Global configuration

6. Enter the terraform binary path. If you don’t have terraform CLI on the Jenkins node, download it and place it under “/usr/local/bin”


Terraform plugin configuration

Jenkins Plugins:

Ensure that you have the following plugins installed on Jenkins.

GitHub Repository

Here is the Github repository link to fork. I have created two branches with a few changes for safer terraform deployment.

https://github.com/UnixArena/jenkins-terraform

GitHub – branching strategy

Creating First Jenkins Pipeline Job for Terraform

1. Let’s assume that you would like to provision an AWS EC2 instance using terraform. Create a new directory on the Jenkins called “Terraform_Demo_IaC_AWS_EC2“. Under this directory, Create a first pipeline job called “dev”.

pipeline job – Terraform

2. Specify the git repository link where the pipeline script will be placed. If it’s a public repository, no need to pass the credentials.

Pipeline script from SCM

3. Specify the branch name as “dev“. We will come back to the GitHub repository branch strategy later.

Specify dev branch – terraform pipeline

4. Update the actual pipeline file name with the path and save the job.

Script Path – Jenkinsfile

5. Let’s clone this job to the master the job. Click on the new Item and enter the job name as “master“.

Copy dev pipeline to master

6. Update the branch details for the master job.

Change the branch from dev to master

7. Our Jenkins jobs are ready.

Jenkins – Terraform pipeline jobs ready

GitHub repo branches – > Jenkins job

The repository branches have been mapped to the Jenkins job.

GitHub – Terraform – Jenkins

AWS Authentication:

You need to configure the IAM role for a service with administrative privileges. Attach the role to the Jenkins instance if the Jenkins instance running on AWS. Otherwise, you need to store the AWS access & secret key in Jenkins credentials and export it as an environment variable.

Assign IAM role with admin privileges

Test our work

The pipeline job has been coded to deploy a new EC2 instance on the AWS us-east2 region. This pipeline job calls the repository “https://github.com/UnixArena/terraform-test” for the deployment.

1. Login to the Jenkins console and navigate to the jobs. Click “dev” and click on “Build Now”.

Build Now – Dev Job

We can see that the “dev” job has been successfully completed. This job is just a dry run for the implementation.

dev job – Successfully completed

Here is the terraform plan output.

+ cd terraform-test/
+ terraform plan -out=tfplan.out
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.main will be created
  + resource "aws_instance" "main" {
      + ami                          = "ami-001089eb624938d9f"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + secondary_private_ips        = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Env"  = "dev"
          + "Name" = "IaC-Test"
        }
      + tenancy                      = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

This plan was saved to: tfplan.out
dev – pipeline job – Terraform stage view

2. Review the plan twice before kicking the master job for the actual implementation. In my case, let me trigger the “master” job for the actual implementation.

Trigger the master job

3. Here is the successful pipeline job result.

[Pipeline] stage
[Pipeline] { (Terraform apply)
[Pipeline] tool
[Pipeline] envVarsForTool
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ cd terraform-test/
+ terraform apply --auto-approve
aws_instance.main: Creating...
aws_instance.main: Still creating... [10s elapsed]
aws_instance.main: Still creating... [20s elapsed]
aws_instance.main: Still creating... [30s elapsed]
aws_instance.main: Creation complete after 31s [id=i-0d5d5e6953eab6884]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Post stage
[Pipeline] cleanWs
[WS-CLEANUP] Deleting project workspace...
[WS-CLEANUP] Deferred wipeout is used...
[WS-CLEANUP] done
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }

[Pipeline] // ansiColor
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Let’s see the stage view

master – pipeline job – Terraform stage view

Is it possible to manage the Terraform state?

Yes. You can include the terraform backend code to preserve the state file. In my example, I have the following code to store the state file safely.

On provider.tf

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

terraform {
  backend "s3" {
  bucket = "ua-terraform-state"
  key = "dev/ec2-app2/terraform.tfstate"
  region = "us-east-2"
  }
}

Test the terraform state file use case:

Let’s assume that you would like to provision one more instance for the same application, how can we do that using this automation pipeline? You just need to update the terraform resource code to increase the instance count from 1 to 2. No modification is required on the pipeline jobs. Even an accidental re-run will not deploy a new instance since you have the state file.

resource "aws_instance" "main" {
  ami           = "ami-001089eb624938d9f"
  instance_type = "t2.micro"
  count         = 2
  
  tags = {
    Name = "IaC-Test"
    Env = "dev"
  }
}
     Plan: 1 to add, 0 to change, 0 to destroy.
IaC- AWS EC2 instance – Deployment

Conclusion:

We have successfully integrated Terraform and Jenkins using pipeline jobs. IaC (Infrastructure as Code) has been demonstrated using this platform on the AWS cloud. A similar pipeline can be leveraged for other clouds and ON-PREM (as long as terraform supports the environment). Jenkins GUI and RBAC mechanism help to secure the terraform pipeline jobs and provide access to the people who actually need it. In the end, we have seen how to manage the existing infrastructure using code and make changes safely by using terraform state management. Hope this article is informative to you.

Exit mobile version