"Ansible Automation for CI/CD: Seamless SonarQube Integration and Deployment Solutions"

Shazia MasseyShazia Massey
6 min read

The Ansible project workflow involves setting up three EC2 instances of type t2.medium, establishing SSH connectivity with keys, and installing Ansible on the master VM. An Ansible playbook is then created to automate the installation of JDK 17, Java, Maven, Docker, SonarQube, and Trivy. The CI/CD pipeline includes cloning the repository, pulling the latest updates, and performing code compilation, testing, and SonarQube analysis. Environment variables are securely managed using Ansible Vault. The application is built into a JAR file, and Docker management involves building and tagging the Docker image, scanning it with Trivy, and pushing it to Docker Hub.

Deployment is managed through Docker containers. Updates are controlled by the docker-container-condition.sh script, which ensures that the deployment always uses the latest Docker image named secret-santa. When changes occur in the source code, Ansible triggers the script to check for updates. The script then stops the existing Docker container, creates a new image reflecting the latest changes, and redeploys the updated container. In real-time projects, Docker volumes are commonly used to persist and safeguard data. However, for this demo project, Docker volumes are not utilized.

SSH Key Configuration (Master):

# sudo apt update
# ssh-keygen
# cd /home/ubuntu/.ssh
# cat id_rsa.pub (paste the public key to target servers)


# ssh-keyscan -H <target-1 IP>   >> ~/.ssh/known_hosts
# ssh-keyscan -H <target-2 iP>   >> ~/.ssh/known_hosts

SSH Connection Setup for Target Servers:

# cd /home/ubuntu/.ssh
# vi authorized_keys
# chmod 700 ~/.ssh
# chmod 600 ~/.ssh/authorized_keys

After setting up SSH keys for target servers, the next step is to configure Ansible on the master node. Here’s a detailed guide for configuring Ansible on the master node:

Configuring Ansible on the Master:

# mkdir shazia
# cd shazia
# ansible
# sudo apt install ansible
# ansible --version

Here’s a sample Ansible playbook YAML file designed to install JDK 17, Java, Maven, Docker, SonarQube, and Trivy on a Debian-based system (such as Ubuntu). The playbook starts by updating the package index to ensure the latest package information is available. It then uses apt to install JDK, Maven, and Docker, adds Docker’s official GPG key and repository to get the latest Docker version, and installs Docker Compose by downloading and installing it. Next, it downloads, unzips, and sets up SonarQube in the /opt directory and installs Trivy using a .deb package. Finally, the playbook performs cleanup by removing temporary files created during the installation. Adjust paths and versions as needed for your specific environment and requirements.

Installing Tools Using YML:

# vi install-tools.yml


---
- name: Install Java, Docker, SonarQube, and Trivy
  hosts: all
  become: yes
  tasks:
    - name: update repo
      command: sudo apt update

    - name: Install OpenJDK 17
      apt:
        name: openjdk-17-jre-headless
        state: present

    - name: Install Maven
      apt:
        name: maven
        state: present

    - name: Install Docker
      apt:
        name: docker.io
        state: present

    - name: Set permissions for Docker socket
      command: chmod 666 /var/run/docker.sock
      become: true

    - name: Run SonarQube container
      command: docker run -d -p 9000:9000 sonarqube:lts-community
      become: true

    - name: Install dependencies for Trivy
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - wget
        - apt-transport-https
        - gnupg
        - lsb-release

    - name: Add GPG key for Trivy
      shell: wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
      become: true

    - name: Add Trivy repository
      shell: echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
      become: true

    - name: Update apt cache
      apt:
        update_cache: yes

    - name: Install Trivy
      apt:
        name: trivy
        state: present
    - name: Download SonarScanner
      get_url:
        url: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.8.0.2856-linux.zip
        dest: /tmp/sonar-scanner-cli-4.8.0.2856-linux.zip

    - name: Install unzip
      apt:
        name: unzip
        state: present

    - name: Unzip SonarScanner
      unarchive:
        src: /tmp/sonar-scanner-cli-4.8.0.2856-linux.zip
        dest: /home/ubuntu/
        remote_src: yes
        creates: /home/ubuntu/sonar-scanner-4.8.0.2856-linux

Configuring the Ansible Inventory:

By default, Ansible uses /etc/ansible/hosts for the inventory file. We can use this file or create a custom inventory file. To create a custom inventory file, you can place it anywhere on the master node, for example, /home/ubuntu/inventory


# vi inventory


[web]
< target-1 IP >
< target-2 IP >

Now, run the Ansible playbook using the following command:

# ansible-playbook -i inventory install-tools.yml

The docker-container-condition.sh script manages Docker containers based on specific conditions. In this project, it ensures that the deployment uses the latest Docker image by performing the following actions: it stops the existing container, creates a new Docker image if there are updates to the source code, and redeploys the container with the updated image.

# vi docker-container-condition.sh


#!/bin/bash

# Define the name of the Docker container
container_name="secret-santa"

# Check if the Docker container exists
if docker ps -a --format '{{.Names}}' | grep -q "^$container_name$"; then
    # Stop the Docker container
    echo "Stopping Docker container $container_name..."
    docker stop $container_name

    # Remove the Docker container
    echo "Removing Docker container $container_name..."
    docker rm $container_name
else
    echo "Docker container $container_name does not exist."
fi

To access SonarQube, enter the IP address of the target server followed by port 9000 in your browser. After logging in to SonarQube, navigate to Administration, select Security, and then click Generate Tokens to create a new authentication token. This token is essential for integrating SonarQube with Ansible Vault and for configuring credentials.yml.

We utilize Ansible Vault to securely manage environment variables, including Docker credentials (username and password), SonarQube URL, and token. This approach ensures that sensitive information is encrypted and protected, providing a secure method for handling configuration details required for deployment and integration.

# ansible-vault create credentials.yml

We configure the cicd.yaml file to define the Continuous Integration and Continuous Deployment (CI/CD) pipeline. This configuration includes specifying stages for building, testing, code analysis, and deployment. It integrates various tools and processes to automate the workflow, ensuring efficient and reliable software delivery.

# vi cicd.yaml



---
- name: Clone or pull repository, build with Maven, and run SonarQube scanner
  hosts: all
  become: yes
  vars:
    repository_url: https://github.com/shazia-massey/secretsanta-generator.git
  vars_files:
    - credentials.yml
  tasks:
    - name: Clone or pull repository
      ansible.builtin.git:
        repo: "{{ repository_url }}"
        dest: /home/ubuntu/secretsanta-generator
        update: yes
      register: git_result
    - name: Check if repository was cloned or updated
      debug:
        msg: "{{ git_result }}"
    - name: Copy shell script to remote server
      copy:
        src:  docker-container-condition.sh
        dest: /home/ubuntu/secretsanta-generator/docker-container-condition.sh
    - name: Permissions on shell script
      command: chmod +x docker-container-condition.sh
      args:
        chdir: /home/ubuntu/secretsanta-generator   
    - name: Execute shell script
      command: ./docker-container-condition.sh
      args:
        chdir: /home/ubuntu/secretsanta-generator
    - name: Run mvn compile
      command: mvn compile
      args:
        chdir: /home/ubuntu/secretsanta-generator
    - name: Run mvn Test
      command: mvn test
      args:
        chdir: /home/ubuntu/secretsanta-generator
    - name: Execute SonarScanner
      ansible.builtin.command:
        _raw_params: >
          /home/ubuntu/sonar-scanner-4.8.0.2856-linux/bin/sonar-scanner
         -Dsonar.host.url={{ SONARQUBE_URL }}
         -Dsonar.login={{ SONARQUBE_TOKEN }}
         -Dsonar.projectKey=Santa
         -Dsonar.projectName=Santa
         -Dsonar.java.binaries=.
        chdir: /home/ubuntu/secretsanta-generator

    - name: Run mvn build
      command: mvn package
      args:
        chdir: /home/ubuntu/secretsanta-generator
    - name: Build & Tag Docker Image
      command: docker build -t masseys/santa:latest .
      args:
        chdir: /home/ubuntu/secretsanta-generator
    - name: Scan Docker Image
      command: trivy image --format table -o trivy-image-report.html
        masseys/santa:latest
    - name: Log in to Docker registry
      command: docker login -u {{ docker_username }} -p {{ docker_password }}
    - name: Push Docker Image
      command: docker push masseys/santa:latest
    - name: Deploy To Container
      command: docker run -d --name secret-santa -p 8080:8080 masseys/santa:latest
# ansible-playbook -i inventory cicd.yaml --ask-vault-pass

From this project, I acquired expertise in using Ansible for automation, optimizing development workflows, managing Docker containers, and implementing effective credential security practices.

0
Subscribe to my newsletter

Read articles from Shazia Massey directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shazia Massey
Shazia Massey