Skip to main content

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

  1. Install Ansible on your machine
  2. Create the project structure
  3. Configure the inventory with localhost
  4. 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

  1. Display the system information
  2. Create a /tmp/ansible-test directory
  3. Create a file with content
  4. 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:

  1. Displays a welcome message
  2. Creates a "testuser" user
  3. 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

  1. Which command checks Ansible connectivity?

    • a) ansible --ping
    • b) ansible all -m ping
    • c) ansible-ping
    • d) ping ansible
  2. 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
  3. Which module do you use to generate a configuration file?

    • a) copy
    • b) file
    • c) template
    • d) lineinfile
  4. What does "idempotent" mean?

    • a) Fast to run
    • b) Same result on every run
    • c) Multi-OS compatible
    • d) Parallel execution
  5. How do you organize reusable code?

    • a) Nested playbooks
    • b) Roles
    • c) Shell scripts
    • d) Variables

Answers

  1. b) ansible all -m ping
  2. c) extra-vars (-e) - Highest precedence
  3. c) template - For dynamic Jinja2 files
  4. b) Same result on every run
  5. b) Roles - Organized and reusable structure

Additional resources

Official documentation

Community

  • geerlingguy.* - Professional-quality roles
  • debops.* - Debian/Ubuntu infrastructure
  • robertdebock.* - 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!


← Ansible Galaxy | Table of contents