Automated Jenkins Server & Toolchain Provisioning
Structure 8 modular Ansible roles under roles/.
- Time
- 31 min
- Level
- Intermediate
- Objectives
- 4 objectives
- Cost
- Low cost
Where this fits in the platform
Already built
This lab adds
- A working Jenkins host, built with no manual step
Before you start
Cost — Low cost
— one `t3.micro` for Jenkins. Note that Jenkins wants more memory than a micro provides for real builds; a `t3.small` is ~$15/month.
Nothing to pay in the browser. Open the terminal runs this against a simulated cloud — the same API calls and the same commands, with no account and no bill. The figure above applies only if you build it in your own.
The scenario#
Setting up the build server is a wiki page with nineteen steps. It was last accurate in March. The person who wrote it has left, and the server is one disk failure from being unbuildable.
Hands-on environment
Run this lab in a real terminal, free and in your browser. The environment is temporary and yours alone — break it as much as you like.
Open the terminalOpens in Killercoda, in a new tab — keep this page open for the steps.
Run it on your own machine
Run this lab on your own machine. One command starts the environment, with everything the lab needs already installed:
The lab text targets an EC2 instance. Locally the same playbook targets the node1 container instead — the inventory changes, the roles and handlers do not.
You will need:
- ansible
git clone https://github.com/EgyKode/EgyKode-lab.git
cd EgyKode-lab
./egykode start
./egykode shell
ssh node1You need Docker and Git installed. Everything else runs inside the environment. The first start downloads it and takes a few minutes; later starts are seconds.
Not sure what you already have? Run: npm run doctor — it checks and changes nothing.
Run it on AWS
This lab builds real cloud infrastructure, so it needs your own AWS account. Follow the cost and cleanup notes above — the resources are yours, and so is the bill.
Anything you tick here is your own record. EgyKode cannot see inside that terminal, so the success criteria stay self-assessed even when the environment checks your work for you.
The composition
Step 1 of 4
What you are building#
A role per concern, composed by one playbook:
playbooks/site.yml
|
+-- common packages, timezone, users, hardening
+-- java OpenJDK 21 (Jenkins refuses to start on 17)
+-- docker engine, the jenkins user in the docker group
+-- jenkins repository, package, plugins, service
+-- aws_cli v2 from the official installer
+-- kubectl pinned to the cluster's minor version
+-- helm via the official script
+-- trivy scanner used by the pipeline
+-- sonarqube quality gate, in a containerA role is a directory layout Ansible understands. Putting a file in
roles/java/tasks/main.yml is the wiring — there is no registration step.
The alternative is one 1,000-line playbook where nothing can be reused and any
change risks everything.
Build it#
What you are proving: You can compose roles into one playbook that takes a bare instance to a working build host
Marking this settles success criterion 1.
# playbooks/site.yml
- name: Provision the build host
hosts: tag_Role_jenkins
become: true
pre_tasks:
- name: Refresh the package cache
ansible.builtin.package:
update_cache: true
changed_when: false # a cache refresh is not a change
roles:
- common
- java
- docker
- { role: jenkins, tags: ["jenkins"] }
- aws_cli
- kubectl
- helm
- trivy
- { role: sonarqube, tags: ["quality"] }changed_when: false on the cache refresh matters more than it looks. A task
that reports changed on every run makes changed=0 unreachable, and once the
number is never zero nobody looks at it again.
What you are proving: You can write role tasks that declare state, so a second run reports no change
Marking this settles success criterion 2.
# roles/jenkins/tasks/main.yml
- name: Add the Jenkins repository key
ansible.builtin.get_url:
url: https://pkg.jenkins.io/redhat-stable/jenkins.io-2026.key
dest: /etc/pki/rpm-gpg/RPM-GPG-KEY-jenkins
mode: "0644"
- name: Add the Jenkins repository
ansible.builtin.yum_repository:
name: jenkins
description: Jenkins stable
baseurl: https://pkg.jenkins.io/redhat-stable
gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-jenkins
gpgcheck: true
- name: Install Jenkins
ansible.builtin.package:
name: "jenkins-{{ jenkins_version }}" # pinned, not "latest"
state: present
notify: Restart jenkins
- name: Ensure the config directory exists
ansible.builtin.file:
path: /var/lib/jenkins/init.groovy.d
state: directory
owner: jenkins
mode: "0755"
- name: Wait for Jenkins to answer
ansible.builtin.uri:
url: "http://127.0.0.1:8080/login"
status_code: [200, 403]
timeout: 5
register: jenkins_up
until: jenkins_up.status in [200, 403]
retries: 30
delay: 5# roles/jenkins/defaults/main.yml
jenkins_version: "2.462.3"
jenkins_port: 8080# roles/jenkins/handlers/main.yml
- name: Restart jenkins
ansible.builtin.service:
name: jenkins
state: restarted
enabled: truedefaults/, not vars/. Both define variables; defaults sits low in
precedence so inventory and --extra-vars can override it, vars sits high
and effectively cannot be overridden. Anything a caller might reasonably change
belongs in defaults, or you have written a role only you can use.
Pin the version. state: latest makes the same playbook produce a
different server next week, which is the opposite of what configuration
management is for.
What you are proving: You can keep secrets in Vault and reference them indirectly, so no password sits in the repository
Marking this settles success criterion 4.
ansible-vault create group_vars/all/vault.ymlvault_sonarqube_admin_password: "..."
vault_jenkins_admin_password: "..."# group_vars/all/main.yml — the indirection is deliberate
sonarqube_admin_password: "{{ vault_sonarqube_admin_password }}"Referencing vault_* variables through plain names keeps every playbook
readable — you can see which values are secret from the mapping file without
decrypting anything.
ansible-playbook playbooks/site.yml --vault-password-file .vault_passWhat you are proving: You can assert each service and binary in a verify playbook that fails loudly when one is missing
Marking this settles success criterion 3.
# playbooks/verify.yml
- hosts: tag_Role_jenkins
become: true
tasks:
- name: Services are running
ansible.builtin.service_facts:
- name: Assert each service is active
ansible.builtin.assert:
that: ansible_facts.services[item ~ '.service'].state == 'running'
fail_msg: "{{ item }} is not running"
loop: [jenkins, docker]
- name: Binaries answer
ansible.builtin.command: "{{ item }} --version"
changed_when: false
loop:
- /usr/local/bin/aws
- /usr/local/bin/kubectl
- /usr/local/bin/helm
- /usr/local/bin/trivyassert is what makes this a test rather than a report. A command that
prints a version and is never checked passes whatever it prints.
Verify it worked#
ansible-playbook playbooks/site.yml
# PLAY RECAP: changed=23
ansible-playbook playbooks/site.yml
# PLAY RECAP: changed=0 <- the point of the whole lab
ansible-playbook playbooks/verify.yml
ansible-playbook playbooks/site.yml --check --diff # what would change
ansible-playbook playbooks/site.yml --tags jenkins # one role onlychanged=0 on the second run is the definition of idempotent, and it is what
lets this playbook run on a schedule to correct drift rather than being a
one-shot installer.
The second run still reports changes
--check --diff names the task. Almost always a command/shell with no
creates: guard, or a template rendering a timestamp.
Jenkins installs but does not start
Java is missing or the wrong major version. journalctl -u jenkins -n 50 says
so directly.
Interactive vault password prompt in CI
Pass --vault-password-file, and keep that file out of the repository.
A handler never fires
Handlers run at the end of a play and are skipped entirely if the notifying
task did not change. --force-handlers while debugging.
The docker group does not take effect
Group membership applies at next login. reset_connection in the play, or
become for the tasks that need it.
Clean up#
# Nothing to destroy here — the instance belongs to the Terraform lab.
ansible -m command -a "systemctl status jenkins --no-pager" tag_Role_jenkinsCost of this lab: Low. You pay for the instance being configured, not for Ansible.
Success criteria
0 of 4
The concept behind it
Phase complete · 05 Configuration management
You can now: A new server is provisioned and configured with no manual steps, and a second run changes nothing.
Next phase
Lab 31 of 59 on the project path