Exercises and projects
Objectives
- Consolidate your Ansible knowledge
- Put all the concepts into practice
- Develop operational skills
Exercise 1: Installation and first steps
Objective
Install Ansible and validate the configuration.
Tasks
- Install Ansible on your machine
- Create the project structure
- Configure the inventory with localhost
- Run a ping test
Structure to create
ansible-labs/
├── ansible.cfg
├── inventory/
│ └── hosts.ini
└── playbooks/
Files
# inventory/hosts.ini
[local]
localhost ansible_connection=local
# ansible.cfg
[defaults]
inventory = ./inventory/hosts.ini
host_key_checking = False
Validation
ansible all -m ping
# Attendu: localhost | SUCCESS
Exercise 2: Ad-hoc commands
Objective
Master the basic operations with ad-hoc commands.
Tasks
- Display the system information
- Create a /tmp/ansible-test directory
- Create a file with content
- Check the disk space
Commands
# 1. Infos système
ansible localhost -m setup -a "filter=ansible_distribution*"
# 2. Créer un répertoire
ansible localhost -m file -a "path=/tmp/ansible-test state=directory"
# 3. Créer un fichier
ansible localhost -m copy -a "content='Hello Ansible' dest=/tmp/ansible-test/hello.txt"
# 4. Espace disque
ansible localhost -a "df -h"
Exercise 3: First playbook
Objective
Write a simple playbook.
Tasks
Create a playbook that:
- Displays a welcome message
- Creates a "testuser" user
- Creates a directory for this user
Solution
# playbooks/first-playbook.yml
---
- name: Mon premier playbook
hosts: localhost
become: yes
vars:
username: testuser
user_home: /home/testuser
tasks:
- name: Afficher un message
debug:
msg: "Bienvenue dans Ansible !"
- name: Créer l'utilisateur
user:
name: "{{ username }}"
state: present
shell: /bin/bash
create_home: yes
- name: Créer le répertoire projets
file:
path: "{{ user_home }}/projets"
state: directory
owner: "{{ username }}"
mode: '0755'
Exercise 4: Advanced inventory
Objective
Create a multi-environment inventory.
Structure
inventory/
├── production/
│ ├── hosts.ini
│ └── group_vars/
│ ├── all.yml
│ └── webservers.yml
├── staging/
│ ├── hosts.ini
│ └── group_vars/
│ └── all.yml
└── development/
└── hosts.ini
Files to create
# inventory/production/hosts.ini
[webservers]
web-01 ansible_host=192.168.1.10
web-02 ansible_host=192.168.1.11
[databases]
db-01 ansible_host=192.168.1.20
[production:children]
webservers
databases
# inventory/production/group_vars/all.yml
---
environment: production
debug_enabled: false
log_level: warn
# inventory/production/group_vars/webservers.yml
---
http_port: 80
nginx_worker_processes: 4
Exercise 5: Playbook with conditions and loops
Objective
Use control structures.
Playbook
# playbooks/packages.yml
---
- name: Gestion des packages
hosts: localhost
become: yes
vars:
common_packages:
- vim
- htop
- curl
- git
optional_packages:
- { name: docker.io, install: true }
- { name: nginx, install: false }
tasks:
- name: Mettre à jour le cache apt
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Installer les packages communs
apt:
name: "{{ common_packages }}"
state: present
- name: Installer les packages optionnels
apt:
name: "{{ item.name }}"
state: present
loop: "{{ optional_packages }}"
when: item.install | bool
- name: Afficher les packages installés
debug:
msg: "Package {{ item.name }} - Installé: {{ item.install }}"
loop: "{{ optional_packages }}"
Exercise 6: Create a role
Objective
Create a role to install and configure nginx.
Structure
ansible-galaxy init --init-path roles/ nginx
Role files
# roles/nginx/defaults/main.yml
---
nginx_http_port: 80
nginx_server_name: localhost
nginx_document_root: /var/www/html
# roles/nginx/tasks/main.yml
---
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Create document root
file:
path: "{{ nginx_document_root }}"
state: directory
mode: '0755'
- name: Configure nginx
template:
src: default.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload nginx
- name: Deploy index page
template:
src: index.html.j2
dest: "{{ nginx_document_root }}/index.html"
- name: Start and enable nginx
service:
name: nginx
state: started
enabled: yes
# roles/nginx/handlers/main.yml
---
- name: Reload nginx
service:
name: nginx
state: reloaded
{# roles/nginx/templates/default.conf.j2 #}
server {
listen {{ nginx_http_port }};
server_name {{ nginx_server_name }};
root {{ nginx_document_root }};
location / {
try_files $uri $uri/ =404;
}
}
{# roles/nginx/templates/index.html.j2 #}
<!DOCTYPE html>
<html>
<head>
<title>{{ nginx_server_name }}</title>
</head>
<body>
<h1>Bienvenue sur {{ nginx_server_name }}</h1>
<p>Déployé avec Ansible sur {{ ansible_hostname }}</p>
<p>Date: {{ ansible_date_time.iso8601 }}</p>
</body>
</html>
Usage
# playbooks/webservers.yml
---
- name: Configure web servers
hosts: webservers
become: yes
roles:
- role: nginx
nginx_server_name: example.com
nginx_http_port: 8080
Project 1: LAMP stack
Objective
Deploy a complete LAMP stack with Ansible.
Architecture
Project structure
lamp-project/
├── ansible.cfg
├── inventory/
│ └── hosts.ini
├── group_vars/
│ └── all.yml
├── roles/
│ ├── common/
│ ├── apache/
│ ├── php/
│ └── mysql/
└── playbooks/
└── site.yml
Main playbook
# playbooks/site.yml
---
- name: Deploy LAMP Stack
hosts: all
become: yes
roles:
- common
- mysql
- php
- apache
Variables
# group_vars/all.yml
---
# MySQL
mysql_root_password: "{{ vault_mysql_root_password }}"
mysql_databases:
- name: myapp
encoding: utf8mb4
mysql_users:
- name: appuser
password: "{{ vault_mysql_app_password }}"
priv: "myapp.*:ALL"
# PHP
php_version: "8.1"
php_extensions:
- php-mysql
- php-curl
- php-gd
- php-mbstring
# Apache
apache_vhosts:
- servername: myapp.local
documentroot: /var/www/myapp
Project 2: Application deployment
Objective
Automate the deployment of a web application.
Workflow
Deployment playbook
# playbooks/deploy.yml
---
- name: Deploy Application
hosts: webservers
become: yes
vars:
app_name: myapp
app_path: /var/www/{{ app_name }}
app_repo: https://github.com/myorg/myapp.git
app_branch: main
tasks:
- name: Pre-deployment tasks
block:
- name: Enable maintenance mode
copy:
content: "Maintenance in progress..."
dest: "{{ app_path }}/public/maintenance.html"
- name: Backup current version
archive:
path: "{{ app_path }}"
dest: "/backups/{{ app_name }}-{{ ansible_date_time.iso8601_basic_short }}.tar.gz"
ignore_errors: yes
- name: Deployment
block:
- name: Pull latest code
git:
repo: "{{ app_repo }}"
dest: "{{ app_path }}"
version: "{{ app_branch }}"
force: yes
register: git_result
- name: Install dependencies
command: composer install --no-dev --optimize-autoloader
args:
chdir: "{{ app_path }}"
when: git_result.changed
- name: Run database migrations
command: php artisan migrate --force
args:
chdir: "{{ app_path }}"
- name: Clear cache
command: php artisan cache:clear
args:
chdir: "{{ app_path }}"
- name: Set permissions
file:
path: "{{ app_path }}/storage"
owner: www-data
group: www-data
mode: '0775'
recurse: yes
- name: Post-deployment
block:
- name: Disable maintenance mode
file:
path: "{{ app_path }}/public/maintenance.html"
state: absent
- name: Reload PHP-FPM
service:
name: php8.1-fpm
state: reloaded
- name: Health check
uri:
url: "http://localhost/health"
status_code: 200
retries: 5
delay: 2
handlers:
- name: Restart apache
service:
name: apache2
state: restarted
Project 3: Docker infrastructure
Objective
Manage a Docker infrastructure with Ansible.
Playbook
# playbooks/docker-infra.yml
---
- name: Docker Infrastructure
hosts: docker_hosts
become: yes
collections:
- community.docker
vars:
docker_networks:
- name: frontend
driver: bridge
- name: backend
driver: bridge
internal: yes
docker_volumes:
- postgres_data
- redis_data
docker_containers:
- name: postgres
image: postgres:15
networks:
- backend
volumes:
- postgres_data:/var/lib/postgresql/data
env:
POSTGRES_PASSWORD: "{{ vault_postgres_password }}"
- name: redis
image: redis:7-alpine
networks:
- backend
volumes:
- redis_data:/data
- name: nginx
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend
- backend
tasks:
- name: Create Docker networks
community.docker.docker_network:
name: "{{ item.name }}"
driver: "{{ item.driver | default('bridge') }}"
internal: "{{ item.internal | default(false) }}"
loop: "{{ docker_networks }}"
- name: Create Docker volumes
community.docker.docker_volume:
name: "{{ item }}"
loop: "{{ docker_volumes }}"
- name: Start containers
community.docker.docker_container:
name: "{{ item.name }}"
image: "{{ item.image }}"
networks: "{{ item.networks | default(omit) }}"
volumes: "{{ item.volumes | default(omit) }}"
ports: "{{ item.ports | default(omit) }}"
env: "{{ item.env | default(omit) }}"
state: started
restart_policy: unless-stopped
loop: "{{ docker_containers }}"
Validation quiz
Questions
-
Which command checks Ansible connectivity?
- a)
ansible --ping - b)
ansible all -m ping - c)
ansible-ping - d)
ping ansible
- a)
-
Where do you define the highest-precedence variables?
- a) defaults/main.yml
- b) group_vars/all.yml
- c) extra-vars (-e)
- d) vars/main.yml
-
Which module do you use to generate a configuration file?
- a) copy
- b) file
- c) template
- d) lineinfile
-
What does "idempotent" mean?
- a) Fast to run
- b) Same result on every run
- c) Multi-OS compatible
- d) Parallel execution
-
How do you organize reusable code?
- a) Nested playbooks
- b) Roles
- c) Shell scripts
- d) Variables
Answers
- b)
ansible all -m ping - c) extra-vars (-e) - Highest precedence
- c) template - For dynamic Jinja2 files
- b) Same result on every run
- b) Roles - Organized and reusable structure
Additional resources
Official documentation
Community
Recommended roles
geerlingguy.*- Professional-quality rolesdebops.*- Debian/Ubuntu infrastructurerobertdebock.*- Multi-platform roles
Conclusion
Congratulations! You have completed the Ansible course.
You now master:
- Installation and configuration
- Inventory and ad-hoc commands
- Playbooks and variables
- Roles and templates
- Ansible Galaxy and collections
Keep practicing and explore real-world projects!