Project Jenkins-Ansible

Deploy Nginx containers on Multiple Nodes using Ansible with Jenkins pipeline

Project Jenkins-Ansible

Hey Learners! Welcome back. In the Day59 challenge, we deployed a static webpage on Nginx on managed nodes using Ansible. In this project, we'll deploy containers on which nginx is running with a static webpage using the Ansible playbook. To automate all these tasks like code, build, deploy we can use Jenkins pipeline and more. Let's start...

Overview

For this project, we store our code on GitHub, from this code we build Docker Image on the Jenkins Mater node itself and push the created images on DockerHub. If any changes are made in the code then we'll build our new Docker image with the new changes and push the same on DockerHub manually. This will be automated with Jenkins(CI) and then deploy the containers on multiple nodes managed by Ansible (CD).

Steps:-

  1. Create Jenkinsfile

  2. Create an Ansible playbook for creating a Docker Container with the latest image

  3. Add Ansible as an Agent node on Jenkins

  4. Deploy containers by executing the Ansible playbook using Jenkins

Prerequisite:-

  • Jenkins on both Machine

  • Ansible Master

  • Docker On both machines

  • Ansible Nodes(As required) with Docker installed

For this project, I have Two virtual machines created on My system for Ansible and Jenkins where Jenkins and Ansible are installed on respective machines.

1- Create Jenkinsfile

Log in to Jenkins using Web UI

Click New item, Enter the name for the project select Pipeline as a project style and click OK.

We can directly write pipeline

To create pipeline syntax click Pipeline Syntax and a new tab will open. Search for Git paste the GitHub repository URL and click Generate Pipeline Script.

Copy the script and paste the same in the steps of the SCM stage.

Create a new repository on DockerHub to store images created.

Add build stage for building Docker image. Use pipeline syntax to search for shell script and use the docker build -t <image-name: Tag> command. Generate pipeline script and paste in the defined stage.

We will push the created image to DockerHub in the next stage. To push images to DockerHub we need DockerHub Credentials.

Go to Pipeline Syntax search for With credentials click on Add and select Secret text.

Select Secret text and provide the Password for DockerHub in Secret. Enter the name for ID. Click Add and click Generate Pipeline Script. Copy and Paste into the login stage.

Now it's time to push our built image on DockerHub. As we already tagged the image with our DockerHub account name we need to execute the docker push <ImageCreated:Tag> command.

The CI part is done.

If we run or build this job for a second time it will not update the image on DockerHub whether you make changes in code as with the latest tag already image is there on a specified repository.

To overcome this we can tag our image with the latest CommitHash as follows.

Note if you mention the command docker build -t <image-name: latest> directly it will replace the local image with the latest tag and not push the latest image as it is already present. To avoid this use the below steps

  • Create one function in Jenkinsfile as shown. To Create a script search for Shell Script and enter the shell command as git rev-parse --short HEAD and Select the Advanced option to click Return Standard Output.

    Note:- corrected command as git rev-parse --short HEAD from docker rev-parse --short HEAD below screenshot shows older command.

  • Generate script and paste in function in Jenkinsfile. Add the Environment block in Jenkinsfile and use the environmental variable specified in the Image tag section. Refer to the below screenshot. In the build stage make sure that you specify the tag for the image as the Environmental variable specified. Also, make changes in the push stage accordingly.

    After a successful build check the Image tag on DockerHub and compare it with the Latest commitHash.

Errors faced:-

  • Jenkins doesn't have permission to Docker. Use sudo usermod -aG docker jenkins command and restart Docker or the system.

  • Till have a Docker permission Issue use sudo chmod 666 /var/run/docker.sock command.

  • Having an error with git rev-parse --short HEAD command

    Make sure first to create only the first stage('SCM') and Build the project to avoid the above error or add a function in the pipeline after building the whole pipeline first.

2- Create an Ansible playbook for creating a Docker Container with the latest image

Now we can create a Playbook for deploying Docker containers on managed node(s).

Create dockerCont_create.yml as follows

---
- name: play to Deploy Docker container
  hosts: localhost #YOU CAN ADD NODES AS PER YOUR REQUIREMENTS
  remote_user: jenkins
  gather_facts: false
  become: true
  tasks:
    - name: deploy docker contaier
      docker_container:
        name: Ansible_Container
        image: "amitvpawar/containeransible:{{Docker_TAG}}"
        state: started
        published_ports:
          - "8787:80"

Add Ansible hosts as per your requirements.

3- Add Ansible as an Agent node on Jenkins

As we have to deploy Docker containers using Jenkins we have to add an Ansible node as a Jenkins Agent node. The Ansible node machine should be up and running.

See my blog on how to add Jenkins Agent node- https://avp23.hashnode.dev/day-28-90daysofdevops

I have already added the Ansible node as the Jenkins Agent node.

4- Deploy containers by executing the Ansible playbook using Jenkins

Now we have to deploy Docker containers with Jenkins.

Create one new deploy stage in the existing Jenkinsfile and run steps on the Ansible agent.

Note- we add the image-tag variable in the YAML file as Docker_TAG and while executing ansible-playbook we provide an extra environmental variable as Docker_TAG with the value getting from the Environmental variable specified in Jenkinsfile as TAG which is equal to the latest commit hash with -e option.

Therefore Now Docker_TAG is assigned with value as CommitHash.

ansible-playbook -e Docker_TAG=${TAG} <playbook.yml> defines image with latest commitHash.

Make sure the Ansible file is present on both Jenkins and Ansible nodes. Passwordless authentication should be done on the Jenkins to Ansible server.

We can put this file in the GitHub repository so we will have this file on the Jenkins node. Add one copy stage for coping YAML file in the Ansible node at the directory where you mention the path while adding the Ansible node as the Agent node for Jenkins.

First, create a pipeline script using the scp command. scp <file/to/copy> <Server's-username/serverIP:/path-specified/project-name/file-name>

The above error gives a path where to copy the YAML file i.e. /home/ansible/jenkins/workspace/ContainersAnsible2/

Note if don't want to enter Server IP as 192.168.XXX.XXX then makes host entry into the /etc/hosts.

Before building a job with these changes make sure the Jenkins user's (Jenkins Master) Key verification is done successfully. Use scp command by switching to jenkins user and by typing yes to add the key permanently or use the ssh-copy-id command to add the key.

To overcome the above error use sudo usermod -aG docker Jenkins and sudo chmod 666 /var/run/docker.sock on Ansible Master node.

If you have the same error use the ansible_become_password variable and provide the root password as the value by adding with credentials secret text.

Now add the ansible-playbook command with the ansible_become_password variable.

ansible-playbook -e "ansible_become_password=$variableforsecrettext" <play-name.yml>

Build the project and access your application deployed.

Note:- I am running this Playbook directly on the Ansible node as don't have any resources to create more nodes on my machine. But you can try adding multiple nodes with a prerequisite.

The playbook will execute on the managed hosts as well.

Finally, after so many failures we accomplished our project.

Try making any changes in code and build this job manually and confirm the changes reflected on managed nodes(Ansible Node in this case) by accessing the nginx webpage.

If you want to manage the newly created nodes you can directly use the below YAML file instead of the YAML file created in Task 2. Three requirements are added as tasks.

---
- name: play to Deploy Docker container
  hosts: localhost #YOU CCAN ADD NODES AS PER YOUR REQUIREMENTS
  remote_user: jenkins
  gather_facts: false
  become: true
  tasks:
    - name: Installing Python pip
      apt:
        name: python-pip
        state: present

    - name: Installimg Docker
      apt:
        name: docker.io
        state: present
        enabled: yes

    - name: Installing Docker-py
      apt:
        name: docker-py
        state: present

    - name: deploy docker contaier
      docker_container:
        name: Ansible_Container
        image: "amitvpawar/containeransible:{{Docker_TAG}}"
        state: started
        published_ports:
          - "8787:80"

Make sure you need to kill and remove the existing container.

docker kill <cont-name> and docker rm <cont-name>

Now build a job. Successfully deployed new commit

Question:- How to make this pipeline fully automated means whenever changes are made in code it should trigger. What major changes do we have to make in the pipeline to run smoothly? Do comment.

Thank you so much for taking the time to read till the end! Hope you found this blog informative.

Feel free to explore more of my content, and don't hesitate to reach out if need any assistance from me or in case of you have any questions.

Find me on:- HashnodeLinkedInGithub

Happy Learning!