This repository contains a demo project created as part of my DevOps studies in the TechWorld with Nana β DevOps Bootcamp.
Demo Project: Ansible Integration in Jenkins
Technologies used: Ansible, Jenkins, DigitalOcean, AWS, Boto3, Docker, Java, Maven, Linux, Git
Project Description:
- Create and configure a dedicated server for Jenkins
- Create and configure a dedicated server for Ansible Control Node
- Write Ansible Playbook, which configures 2 EC2 Instances
- Add ssh key file credentials in Jenkins for Ansible Control Node server and Ansible Managed Node servers
- Configure Jenkins to execute the Ansible Playbook on remote Ansible Control Node server as part of the CI/CD pipeline
- So the Jenkinsfile configuration will do the following:
- a.Connect to the remote Ansible Control Node server
- b.Copy Ansible playbook and configuration files to the remote Ansible Control Node server
- c.Copy the ssh keys for the Ansible Managed Node servers to the Ansible Control Node server
- d.Install Ansible, Python3 and Boto3 on the Ansible Control Node server
- e.With everything installed and copied to the remote Ansible Control Node server, execute the playbook remotely on that Control Node that will configure the 2 EC2 Managed Nodes
The pipeline runs on Jenkins, copies the Ansible files to a remote Ansible Control Node, and from there runs a playbook that configures 2 EC2 Managed Nodes. Ansible is agentless, so only the control node needs Ansible installed β the managed nodes are reached over plain SSH.
Install Jenkins on DigitalOcean following the earlier modules:
- https://github.com/explicit-logic/jenkins-module-8.1
- https://github.com/explicit-logic/jenkins-module-8.2
- Create a droplet on DigitalOcean
| Setting | Value |
|---|---|
| CPU Option | Regular |
| vCPU | 2 |
| RAM | 2 GB |
| Disk | 60 GB |
Name it ansible-server.
Connect to the droplet and install Ansible. ansible-core ships the ansible-playbook binary used later in the pipeline:
ssh root@<PUBLIC-IP>
apt update
apt install ansible-coreInstall the AWS SDK packages boto3 / botocore. Ansible's aws_ec2 dynamic inventory plugin uses them to query AWS for the managed nodes at runtime, so you never hardcode the EC2 IP addresses:
apt install python3-boto3Configure AWS credentials so the plugin can authenticate against your account:
mkdir .aws
vim .aws/credentialsCopy in your local default credentials (view them with cat ~/.aws/credentials):
[default]
aws_access_key_id = <YOUR_ACCESS_KEY_ID>
aws_secret_access_key = <YOUR_SECRET_ACCESS_KEY>Exit the server.
Launch 2 Amazon Linux EC2 instances. Create a new key pair named ansible-jenkins and download the .pem file β Ansible will use it to SSH into the managed nodes as user ec2-user.
Everything the control node needs lives in the ansible/ directory.
ansible.cfg β points Ansible at the dynamic inventory and sets the SSH user and key used to reach the managed nodes:
[defaults]
host_key_checking = False
inventory = inventory_aws_ec2.yaml
enable_plugins = aws_ec2
remote_user = ec2-user
private_key_file = ~/ssh-key.peminventory_aws_ec2.yaml β the dynamic inventory. Set regions to the region your instances run in:
---
plugin: aws_ec2
regions:
- eu-central-1
keyed_groups:
- key: tags
prefix: tag
- key: instance_type
prefix: instance_typemy-playbook.yaml β installs Docker and the Docker Compose plugin on every managed node:
---
- name: Install Docker
hosts: all
become: yes
tasks:
- name: Install Docker
yum:
name: docker
update_cache: yes
state: present
- name: Start docker daemon
systemd:
name: docker
state: started
- name: Install Docker-compose
hosts: all
tasks:
- name: Create docker-compose directory
file:
path: ~/.docker/cli-plugins
state: directory
- name: Get architecture of remote machine
shell: uname -m
register: remote_arch
- name: Install docker-compose
get_url:
url: "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-{{ remote_arch.stdout }}"
dest: ~/.docker/cli-plugins/docker-compose
mode: +xπ Modules used:
ansible.builtin.yumΒ·ansible.builtin.systemd_serviceΒ·ansible.builtin.fileΒ·ansible.builtin.get_url
The pipeline needs two SSH keys: one to reach the control node (DigitalOcean root key) and one to reach the managed nodes (the EC2 key pair).
a. Control Node key β add a global credential in Jenkins
- Kind:
SSH Username with private key - ID:
ansible-server-key - Username:
root
Jenkins requires the classic PEM (RSA) key format. Convert the key you used to create ansible-server in place:
ssh-keygen -p -f ~/.ssh/id_rsa -m pem -P "" -N ""Verify the first line now reads -----BEGIN RSA PRIVATE KEY-----:
cat ~/.ssh/id_rsaCopy the whole value into Jenkins.
π Jenkins β using SSH credentials Β· SSH Credentials plugin
b. Managed Node key β add a second global credential
- Kind:
SSH Username with private key - ID:
ec2-server-key - Username:
ec2-user
Copy in the downloaded EC2 key:
cat ~/Downloads/ansible-jenkins.pemThis stage uses an ANSIBLE_SERVER build parameter (the public IP of the control node) and the two credentials above. It scps the contents of ansible/ to the control node and drops the EC2 key there as ssh-key.pem (the path ansible.cfg expects).
stage("copy files to ansible server") {
steps {
script {
echo "copying all neccessary files to ansible control node"
withCredentials([sshUserPrivateKey(credentialsId: 'ansible-server-key', keyFileVariable: 'PK')]) {
sh 'scp -o StrictHostKeyChecking=no -i $PK ansible/* root@$ANSIBLE_SERVER:/root'
withCredentials([sshUserPrivateKey(credentialsId: 'ec2-server-key', keyFileVariable: 'keyFile')]) {
sh 'scp -o StrictHostKeyChecking=no -i $PK $keyFile root@$ANSIBLE_SERVER:/root/ssh-key.pem'
}
}
}
}
}
β οΈ Note the single quotes around theshsteps. They let the shell β not Groovy β expand$PK/$keyFile, which keeps the secret out of the interpolated Groovy string (Jenkins warns about this otherwise).π
withCredentials/sshUserPrivateKeyΒ· Handling credentials in a Jenkinsfile
- Create a new item in Jenkins
- Name:
ansible-pipeline - Type:
Pipeline
Configure Pipeline script from SCM:
- Repository URL: https://github.com/explicit-logic/ansible-module-15.7
- Credentials:
github(username and password) - Branch Specifier:
main
Run it with Build with Parameters, setting ANSIBLE_SERVER to the control node's public IP.
Connect to the Ansible server and confirm the files transferred:
ssh root@<ansible-server-ip>
lsInstall the Jenkins plugin SSH Pipeline Steps β it provides the sshCommand and sshScript steps.
Add the stage below. Start with ls -l to confirm the SSH connection to the control node works:
stage("execute ansible playbook") {
steps {
script {
echo "calling ansible playbook to configure ec2 instances"
def remote = [:]
remote.name = "ansible-server"
remote.host = "$ANSIBLE_SERVER"
remote.allowAnyHosts = true
withCredentials([sshUserPrivateKey(credentialsId: 'ansible-server-key', keyFileVariable: 'keyFile', usernameVariable: 'user')]) {
remote.user = user
remote.identityFile = keyFile
sshCommand remote: remote, command: "ls -l"
}
}
}
}Once the connection works, replace ls -l with the playbook run:
sshCommand remote: remote, command: "ansible-playbook my-playbook.yaml"Instead of installing Ansible/Boto3 on the control node by hand (step 2), let the pipeline do it. prepare-ansible-server.sh installs the prerequisites:
#!/usr/bin/env bash
apt update
apt install ansible -y
apt install python3-boto3Run it from the pipeline with sshScript, just before the playbook command:
sshScript remote: remote, script: "prepare-ansible-server.sh"
sshCommand remote: remote, command: "ansible-playbook my-playbook.yaml"Execute the pipeline.
Verify Docker landed on an EC2 managed node:
mv ~/Downloads/ansible-jenkins.pem ~/.ssh
chmod 400 ~/.ssh/ansible-jenkins.pem
ssh -i ~/.ssh/ansible-jenkins.pem ec2-user@<ec2-ip-address>
docker version
docker compose versionTo avoid ongoing charges once the demo is done:
- Terminate the 2 EC2 instances
- Delete the
ansible-jenkinsEC2 key pair - Destroy the
ansible-serverDigitalOcean droplet (and the Jenkins droplet if no longer needed) - Remove the
ansible-server-keyandec2-server-keycredentials from Jenkins











