Sunday, 24 October 2021

Deploy Nginx with Terraform and Ansible

 The goal is to implement Devops best practices to use Terraform for provisioning and Ansible to configure your server in Jenkins Pipelines. We will go over the main concepts that need to be considered and a Jenkinsfile that runs Terraform and Ansible. The Jenkinsfile will consists of parameters that allows us to pass data as variables in our pipeline job. 

  • Install Terraform on Jenkins Server
  • Install Python on Jenkins Server
  • Install Ansible on Jenkins Server
  • Install Terraform & Credentials Plugin on Jenkins
  • Configure Terraform
  • Store and Encrypt Credentials in Jenkins
  • Setting up CD Pipeline with Terraform + Ansible to Deploy Nginx
  • Playbook to Deploy Nginx
  • Run Pipeline Job


Install Terraform on Jenkins Server


Use the following commands to install Terraform on Jenkins server and move the binaries to the correct path as shown below.


  • sudo apt install unzip
  • wget https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip
  • unzip terraform_0.12.24_linux_amd64.zip
  • sudo mv terraform /usr/bin/

Install Python on Jenkins Server


Use the following commands to install Python on Jenkins server 

  • sudo apt-get update
    sudo apt-get install python-minimal -y
    python --version


Install Ansible on Jenkins Server


Use the following commands to install Ansible on Jenkins server 

  • sudo apt-get update 
  • sudo apt-get install software-properties-common

  • sudo apt-add-repository ppa:ansible/ansible
  • sudo apt-get update
  • sudo apt install ansible
  • ansible --version
  • (Note: Ansible version should show 2.9.27 or higher)

Install Terraform & Credentials plugin on Jenkins

Go to Manage Jenkins > Manage Plugins >Available > search Terraform as shown below:



Ensure to install Credentials and Credentials Binding as this is required for securing your credentials.

As you can see, Terraform Plugin is already installed on my Jenkins hence why it's displayed in the Installed section.

Store and Encrypt Credentials in Jenkins (Access and Secret Key) 

In this step, we will be storing and encrypting the access and secret key in Jenkins to maximize security and minimize the chances of exposing our credentials.

    • Go to Manage Jenkins > Manage Credentials > Click on Jenkins the highlighted link as shown below


    • Select Add Credentials
    • Choose Secret text in the Kind field
    •  Enter the following below:
    Note: Modify the yellow highlighted text with the right value.
    • Secret = EnterYourSecretKeyHere
    • ID = AWS_SECRET_ACCESS_KEY
    • Description = AWS_SECRET_ACCESS_KEY
    Click OK

    Add another credential and enter the following:

    • Secret = EnterYourAccessIDHere
    • ID = AWS_ACCESS_KEY_ID
    • Description = AWS_ACCESS_KEY_ID

    Click OK







    • Choose SSH Username with private key in the Kind field
    • ID = ENTER-KEY-NAME
    • DESCRIPTION = ENTER-KEY-NAME
    • DESCRIPTION = ENTER-KEY-NAME
    • PRIVATE KEY = ENTER-PRIVATE-KEY

    • Click OK

    Configure Terraform

    Go to Manage Jenkins > Global Tool Configuration > It will display Terraform on the list.

    • Enter terraform in the Name field
    • Provide the path /usr/bin/ as shown below














    Setting up CD Pipeline with Terraform + Ansible to Deploy Nginx


    • Go to Jenkins > New Items. Enter nginx-pipeline in name field > Choose Pipeline > Click OK


    • Select Configure after creation.
    • Go to Build Triggers and enable Trigger builds remotely.
    • Enter tf_token as Authentication Token

     

    Bitbucket Changes
      • Create a new Bitbucket Repo and call it nginx-pipeline
      • Go to Repository Settings after creation and select Webhooks
      • Click Add Webhooks
      • Enter tf_token as the Title
      • Copy and paste the url as shown below
                  http://JENKINS_URL:8080/job/nginx-pipeline/buildWithParameters?token=tf_token

    • Status should be active
    • Click on skip certificate verification
    • triggers --> repository push
    • Go back to Jenkins and select Configure
    • Scroll down to Pipeline and click on the drop down to select Pipeline Script From SCM
    • Enter credentials for Bitbucket, Leave the Branch master as the default, Make sure script path is Jenkinsfile
    • Right click on Pipeline Syntax and open in a new tab. 
    • Choose Checkout from Version Control in the Sample Step field
    • Enter Bitbucket Repository URL and Credentials, leave the branches blank

    • Click GENERATE PIPELINE SCRIPT, copy credentialsId and url (This is required for Jenkinsfile script)



    Create Workspace for Terraform and Ansible Pipeline

    • Open File Explorer, navigate to Desktop and create a folder

      terraform_ansible

    • Once folder has been created, open Visual Code Studio and add folder to workspace







    • Open a New Terminal
    • Run the command before cloning repo: git init
    • Navigate to nginx-pipeline repo in Bitbucket
    • Clone the repo with SSH or HTTPS
    • Create S3 bucket in AWS to configure the backend and store terraform state files in storage. (Name the S3 Bucket whatever you prefer)

    • Create a new file main.tf and copy the below code in yellow color



















    provider "aws" {

        region = var.region

        version = "~> 2.0"

    }


    terraform {

    backend "s3" {

          bucket = "S3-BUCKET-NAME"

          key    = "nginx/terraform.tfstste"

          region = "us-east-2"

       }

    }


    locals {

      ssh_user         = "ubuntu"

      key_name         = "name-of-key"

      private_key_path = "files/name-of-key.pem"

    }

    resource "aws_instance" "nginx" {

      ami                         = data.aws_ami.ubuntu.id

      subnet_id                   = "subnet-7ce9c814"

      instance_type               = "t2.micro"

      associate_public_ip_address = true

      security_groups             = [aws_security_group.nginx.id]

      key_name                    = local.key_name


      provisioner "remote-exec" {

        inline = [

            "echo 'Wait until SSH is ready'",      

            ]


    ## PASS PRIVATE KEY AS VARIABLE


        connection {

          type        = "ssh"

          user        = local.ssh_user

          private_key = file(local.private_key_path) 

          host        = aws_instance.nginx.public_ip

        }

      }

      provisioner "local-exec" {

        command = "ansible-playbook  -i ${aws_instance.nginx.public_ip}, --private-key ${local.private_key_path} nginx.yml"

      }

      tags = {

        Name = "u2-${var.environment}-${var.application}"

        CreatedBy = var.launched_by

        Application = var.application

        OS = var.os

        Environment = var.environment

      }

    }


    output "nginx_ip" {

      value = aws_instance.nginx.public_ip

    }


    data "aws_ami" "ubuntu" {

        most_recent = true


        filter {

            name   = "name"

            values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"]

        }


        filter {

            name   = "virtualization-type"

            values = ["hvm"]

        }


        owners = ["099720109477"] # Canonical

    }

    • Create a new file nginx.yml and copy the below code in yellow color

    ---

    - name: Install Nginx

      hosts: all

      remote_user: ubuntu

      become: yes


      roles:

      - nginx

    • Create a new file security.tf and copy the below code in yellow color

    resource "aws_security_group" "nginx" {
    name = "u2-${var.environment}-sg-${var.application}"
    description = "EC2 SG"
    ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    }
    ingress {
         from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
       }
       ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    }
    #Allow all outbound
    egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    }
    }

    • Create a new file variable.tf and copy the below code in yellow color. 

    variable region {
      type        = string
      default = "us-east-2"
    }
    variable "instance_type" {}
    variable "application" {}
    variable "environment" {}
    ############## tags
    variable os {
      type        = string
      default = "Ubuntu"
    }
    variable launched_by {
      type        = string
      default = "USER"
    }
    ############## end tags


    Ansible Playbook to Deploy Nginx

    • Create this folders roles/nginx/tasks in the same directory then create a file called main.yml and copy the below code in yellow color. 

    ---


    - name: Install Python Dependencies

      raw: "{{ item }}"

      loop:

        - sudo apt-get update

        - sudo apt-get -y install python

      become: true

      ignore_errors: true


    - name: Ensure Nginx is at the latest version

      apt:

        name: nginx

        state: latest

        update_cache: yes


    - name: Make sure Nginx is running

      systemd:

        state: started

        name: nginx



    • Create this folders roles/nginx/handlers in the same directory then create a file called main.yml and copy the below code in yellow color. 

    - name: restart nginx

      service:

        name: nginx

        state: restarted


    • Create a new file Jenkinsfile and copy the below code in yellow color. 


      pipeline {

          agent {

            node {

              label "master"

            } 

          }


          parameters {

              string(name: 'AppName', defaultValue: 'Enter App Name', description: 'Name of application', )

              choice(choices: ['master', 'dev', 'qa', 'prod'], description: 'Select lifecycle to Deploy', name: 'Branch')

              choice(choices: ['t2.micro', 't2.small', 't2.medium'], description: 'Select Instance Size', name: 'InstanceSize')

              booleanParam(name: 'autoApprove', defaultValue: false, description: 'Automatically run apply after generating plan?')

          }



           environment {

              AWS_ACCESS_KEY_ID     = credentials('AWS_ACCESS_KEY_ID')

              AWS_SECRET_ACCESS_KEY = credentials('AWS_SECRET_ACCESS_KEY')

              TF_VAR_instance_type = "${params.InstanceSize}"

              TF_VAR_environment = "${params.Branch}"

              TF_VAR_application = "${params.AppName}"

          }

      // 


          stages {

            stage('checkout') {

              steps {

                  echo "Pulling changes from the branch ${params.Branch}"

                  git credentialsId: 'bitbucket', url: 'paste-url-here' , branch: "${params.Branch}"

              }

            }


              stage('terraform plan') {

                  steps {

                      withCredentials([sshUserPrivateKey(credentialsId: 'name-of-key', keyFileVariable: 'SSH_KEY')]) 

                      {

                      sh 'mkdir -p files'

                      sh 'if [ -f "files/name-of-key.pem" ] ; then rm -rf files/name-of-key.pem; else echo "No entry found"; fi'

                      sh 'cp "$SSH_KEY" files/name-of-key.pem'

                      sh "pwd ; terraform init -input=true"

                      sh "terraform plan -input=true -out tfplan"

                      sh 'terraform show -no-color tfplan > tfplan.txt'

      }

                  }

              }

              

              stage('terraform apply approval') {

                 when {

                     not {

                         equals expected: true, actual: params.autoApprove

                     }

                 }


                 steps {

                     script {

                          def plan = readFile 'tfplan.txt'

                          input message: "Do you want to apply the plan?",

                          parameters: [text(name: 'Plan', description: 'Please review the plan', defaultValue: plan)]

                     }

                 }

             }


              stage('terraform apply') {

                  steps {

                      sh "terraform apply -input=true tfplan"

                  }

              }

              

              stage('terraform destroy approval') {

                  steps {

                      input 'Run terraform destroy?'

                  }

              }

              stage('terraform destroy') {

                  steps {

                      sh 'terraform destroy -force'

                  }

              }

          }


        }


    • Commit and push code changes to Repo with the following:
      • In Vscode, navigate to Source Code Icon on the right tabs on the side
      • Enter commit message
      • Click the + icon to stage changes 

      • Push changes by clicking on the ðŸ”„0 ⬇️ 1 ⬆️ as shown below

    Run Pipeline Job

    • Go to nginx-pipeline on Jenkins and run build 
    Note: The pipeline job will fail the first time to capture the parameters in Jenkinsfile

    • The next time you run a build you should see as shown below





    • Enter Nginx in the AppName field
    • Select a Branch/Lifecycle to deploy server
    • Choose t2.small or t2.medium for Nginx server.
    • Go to Console Output to track progress
    Note: You can abort the destroy step and rerun the step by installing Blue Ocean Plugin on Jenkins to delete the resources created.


    How to upgrade Maven

      java.lang.IllegalStateException I had installed maven in my ubuntu using command  apt install maven This installed maven in path /usr/shar...