Terraform guide 3 - CI/CD environment

Terraform: CI/CD environment

Building a custom Jenkins image

  1. Setup environment

    1
    2
    mkdir -p jenkins
    nano vi Dockerfile
  2. Edit dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    FROM jenkins/jenkins:lts
    USER root
    RUN apt-get update -y && apt-get -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
    RUN curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey
    RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable"
    RUN apt-get update -y
    RUN apt-get install -y docker-ce docker-ce-cli containerd.io
    RUN curl -O https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_linux_amd64.zip && unzip terraform_0.11.13_linux_amd64.zip -d /usr/local/bin/
    USER ${user}
  3. Build the image

    1
    2
    3
    docker build -t jenkins:terraform .
    # check images
    docker image ls

Setting up Jenkins

  1. Edit main.tf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # Jenkins Volume
    resource "docker_volume" "jenkins_volume" {
    name = "jenkins_data"
    }

    # Start the Jenkins Container
    resource "docker_container" "jenkins_container" {
    name = "jenkins"
    image = "jenkins:terraform"
    ports {
    internal = "8080"
    external = "8080"
    }

    volumes {
    volume_name = "${docker_volume.jenkins_volume.name}"
    container_path = "/var/jenkins_home"
    }

    volumes {
    host_path = "/var/run/docker.sock"
    container_path = "/var/run/docker.sock"
    }
    }
  2. Deploy via Terraform

    1
    2
    3
    4
    5
    6
    terraform init
    terraform plan -out=tfplan
    # deploy Jenkins
    terraform apply tfplan
    # get the admin password
    docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Creating a Jenkins simple job

  • Job will deploy a Docker container using Terraform, list the container, and then destroy it
  • example
    1. In the Jenkins dashboard: ‘New Item’
    2. Select ‘Freestyle Project’, item name = “DeployGhost”
    3. Source Code Management -> Git. Repository URL = https://github.com/linuxacademy/content-terraform-docker.git
    4. Build section -> Add build step -> Execute shell
      1
      2
      3
      4
      5
      terraform init
      terraform plan -out=tfplan
      terraform apply tfplan
      docker container ls
      terraform destroy -auto-approve
    5. ‘Build Now’ (left-hand menu) -> arrow next to #1 -> Console Output, wait for result

Building a Jenkins pipeline

Deploy out a Ghost blog

  1. Jenkins dashboard -> ‘New Item’ -> item name = “PipelinePart1”, Pipeline, Ok
  2. Check the box for ‘This project is parameterized’
    • ‘Add Parameter’ -> Choice Parameter
      • Name = “action”
      • Choices = “Deploy”, “Destroy” (make sure they are on separate lines)
      • Description “The action that will be executed”
    • ‘Add Parameter’ -> Choice Parameter
      • Name = “image_name”
      • Choices = “ghost:latest”, “ghost:alpine” (mak3 sure they are on separate lines)
      • Description = “Enter The image Ghost Blog will deploy”
    • ‘Add Parameter’ -> String Parameter
      • Name = “ext_port”
      • Default Value = “80”
      • DEscription = “The Public Port”
  3. In the Pipeline section, ‘Definition of Pipeline script’, and add
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    node {
    git 'https://github.com/linuxacademy/content-terraform-docker.git'
    if(action == 'Deploy') {
    stage('init') {
    sh """
    terraform init
    """
    }
    stage('plan') {
    sh label: 'terraform plan', script: "terraform plan -out=tfplan -input=false -var image_name=${image_name} -var ext_port=${ext_port}"
    script {
    timeout(time: 10, unit: 'MINUTES') {
    input(id: "Deploy Gate", message: "Deploy environment?", ok: 'Deploy')
    }
    }
    }
    stage('apply') {
    sh label: 'terraform apply', script: "terraform apply -lock=false -input=false tfplan"
    }
    }

    if(action == 'Destroy') {
    stage('plan_destroy') {
    sh label: 'terraform plan destroy', script: "terraform plan -destroy -out=tfdestroyplan -input=false -var image_name=${image_name} -var ext_port=${ext_port}"
    }
    stage('destroy') {
    script {
    timeout(time: 10, unit: 'MINUTES') {
    input(id: "Destroy Gate", message: "Destroy environment?", ok: 'Destroy')
    }
    }
    sh label: 'Destroy environment', script: "terraform apply -lock=false -input=false tfdestroyplan"
    }
    }
    }

Deploy out a Swarm service

  • Setup of a Docker Swarm

    1. On the manager node get the join token
      1
      docker swarm join-token worker
    2. On the worker node run the join command (pasting the join token)
      1
      docker swarm join --token [JOIN_TOKEN] [IP]:2377
  • Get Jenkins Running

  1. Get the Jenkins password
    1
    sudo cat /var/lib/jenkins/secrets/initialAdminPassword
  2. Browse to http://< Swarm_Manager_Public_IP >:8080/ , use the password
  3. Install the suggested plugins
  4. Create a user. Done
  5. If issues with updates or dependencies: Dashboard: ‘Manage Jenkins’, fix things there
  • Set up a pipelin
    1. Jenkins dashboard -> ‘New Item’. Item name =”PipelinePart2”, Pipeline, Ok
    2. Check box for “This project is parameterized”
      • ‘Add Parameter’ -> Choice Parameter
        • Name = “action”
        • Choices = “Deploy” and “Destroy” (make sure they are on separate lines)
        • Description = “The action that will be executed”
      • ‘Add Parameter’ -> Choice Parameter
        • Name = “image_name”
        • Choices = “ghost:latest” and “ghost:alpine” (make sure they are on separate lines)
        • Description = “The image Ghost Blog will deploy”
      • ‘Add Parameter’ -> String Parameter
        • Name = “ghost_ext_port”
        • Default Value = “80”
        • Description = “The Public Port”
    3. In the Pipeline section -> ‘Definition of Pipeline script’, add:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      node {
      git 'https://github.com/linuxacademy/content-terraform-docker-service.git'
      if(action == 'Deploy') {
      stage('init') {
      sh label: 'terraform init', script: "terraform init"
      }
      stage('plan') {
      sh label: 'terraform plan', script: "terraform plan -out=tfplan -input=false -var image_name=${image_name} -var ghost_ext_port=${ghost_ext_port}"
      script {
      timeout(time: 10, unit: 'MINUTES') {
      input(id: "Deploy Gate", message: "Deploy environment?", ok: 'Deploy')
      }
      }
      }
      stage('apply') {
      sh label: 'terraform apply', script: "terraform apply -lock=false -input=false tfplan"
      }
      }

      if(action == 'Destroy') {
      stage('plan_destroy') {
      sh label: 'terraform plan', script: "terraform plan -destroy -out=tfdestroyplan -input=false -var image_name=${image_name} -var ghost_ext_port=${ghost_ext_port}"
      }
      stage('destroy') {
      script {
      timeout(time: 10, unit: 'MINUTES') {
      input(id: "Destroy Gate", message: "Destroy environment?", ok: 'Destroy')
      }
      }
      sh label: 'terraform apply', script: "terraform apply -lock=false -input=false tfdestroyplan"
      }
      stage('cleanup') {
      sh label: 'cleanup', script: "rm -rf terraform.tfstat"
      }
      }
      }

Create a MySQL Swarm service that uses Docker Secrets

  1. Jenkins dashboard -> ‘New Item’. Item name = “PipelinePart3”, Pipeline, Ok
  2. Check the box for ‘This project is parameterized’
    • ‘Add Parameter’ -> Choice Parameter.
      • Name = “action”
      • Choices = “Deploy” and “Destroy”, (make sure they are on separate lines)
      • Description = “The action that will be executed”
    • ‘Add Parameter’ -> String Parameter
      • Name = “mysql_root_password”
      • Default Value = “P4ssW0rd0!”
      • Description = “MySQL root password”
    • ‘Add Parameter’ -> String Parameter
      • Name = “mysql_user_password”
      • Defaut value = “paSsw0rd0!”
      • Description = “MySQL user password”
  3. In the Pipeline section, ‘Definition of Pipeline script’, and add:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    node {
    git 'https://github.com/linuxacademy/content-terraform-docker-secrets.git'
    if(action == 'Deploy') {
    stage('init') {
    sh label: 'terraform init', script: "terraform init"
    }
    stage('plan') {
    def ROOT_PASSWORD = sh (returnStdout: true, script: """echo ${mysql_root_password} | base64""").trim()
    def USER_PASSWORD = sh (returnStdout: true, script: """echo ${mysql_user_password} | base64""").trim()
    sh label: 'terraform plan', script: "terraform plan -out=tfplan -input=false -var mysql_root_password=${ROOT_PASSWORD} -var mysql_db_password=${USER_PASSWORD}"
    script {
    timeout(time: 10, unit: 'MINUTES') {
    input(id: "Deploy Gate", message: "Deploy ${params.project_name}?", ok: 'Deploy')
    }
    }
    }
    stage('apply') {
    sh label: 'terraform apply', script: "terraform apply -lock=false -input=false tfplan"
    }
    }

    if(action == 'Destroy') {
    stage('plan_destroy') {
    def ROOT_PASSWORD = sh (returnStdout: true, script: """echo ${mysql_root_password} | base64""").trim()
    def USER_PASSWORD = sh (returnStdout: true, script: """echo ${mysql_user_password} | base64""").trim()
    sh label: 'terraform plan', script: "terraform plan -destroy -out=tfdestroyplan -input=false -var mysql_root_password=${ROOT_PASSWORD} -var mysql_db_password=${USER_PASSWORD}"
    }
    stage('destroy') {
    script {
    timeout(time: 10, unit: 'MINUTES') {
    input(id: "Destroy Gate", message: "Destroy ${params.project_name}?", ok: 'Destroy')
    }
    }
    sh label: 'terraform apply', script: "terraform apply -lock=false -input=false tfdestroyplan"
    }
    stage('cleanup') {
    sh label: 'cleanup', script: "rm -rf terraform.tfstat"
    }
    }
    }