Ansible : Maîtriser l'Automatisation Infrastructure as Code
Ansible révolutionne la gestion d'infrastructure en permettant l'automatisation sans agent. Ce guide vous accompagnera de l'installation aux techniques avancées pour une utilisation en production.
Fondamentaux et Architecture
Installation et Configuration
#!/bin/bash
# ansible-setup.sh
# Installation sur Ubuntu/Debian
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible
# Installation via pip (recommandé pour la dernière version)
pip3 install ansible ansible-core
# Vérification de l'installation
ansible --version
ansible-config dump --only-changed
# Configuration SSH pour Ansible
ssh-keygen -t rsa -b 4096 -C "ansible@$(hostname)"
# Distribution des clés SSH
for host in server1 server2 server3; do
ssh-copy-id -i ~/.ssh/id_rsa.pub user@$host
done
Structure de Projet Ansible
# Structure recommandée pour un projet Ansible
mkdir -p ansible-infrastructure/{
inventories/{production,staging,development},
group_vars,
host_vars,
roles,
playbooks,
files,
templates,
vault,
collections,
plugins/{modules,filters,lookup}
}
# Fichier de configuration ansible.cfg
cat > ansible-infrastructure/ansible.cfg << 'EOF'
[defaults]
inventory = inventories/production/hosts.yml
remote_user = ansible
private_key_file = ~/.ssh/ansible_rsa
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
bin_ansible_callbacks = True
callback_whitelist = timer, profile_tasks, profile_roles
# Performance
forks = 20
poll_interval = 2
timeout = 30
gather_timeout = 30
# Logging
log_path = /var/log/ansible.log
[inventory]
enable_plugins = host_list, script, auto, yaml, ini, toml
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
EOF
Inventaires et Variables
Inventaire Dynamique
# inventories/production/hosts.yml
all:
children:
webservers:
hosts:
web01:
ansible_host: 10.0.1.10
ansible_user: ubuntu
server_role: frontend
web02:
ansible_host: 10.0.1.11
ansible_user: ubuntu
server_role: frontend
vars:
http_port: 80
https_port: 443
databases:
hosts:
db01:
ansible_host: 10.0.2.10
ansible_user: ubuntu
server_role: primary
db02:
ansible_host: 10.0.2.11
ansible_user: ubuntu
server_role: replica
vars:
mysql_port: 3306
loadbalancers:
hosts:
lb01:
ansible_host: 10.0.0.10
ansible_user: ubuntu
vip: 10.0.0.100
monitoring:
hosts:
monitor01:
ansible_host: 10.0.3.10
ansible_user: ubuntu
vars:
environment: production
datacenter: us-east-1
backup_enabled: true
Script d'Inventaire Dynamique
#!/usr/bin/env python3
# dynamic_inventory.py
import json
import boto3
import argparse
from collections import defaultdict
class EC2Inventory:
def __init__(self):
self.inventory = defaultdict(dict)
self.read_cli_args()
if self.args.list:
self.inventory = self.get_inventory()
elif self.args.host:
self.inventory = self.get_host_info(self.args.host)
print(json.dumps(self.inventory, indent=2))
def read_cli_args(self):
parser = argparse.ArgumentParser()
parser.add_argument('--list', action='store_true')
parser.add_argument('--host', action='store')
self.args = parser.parse_args()
def get_inventory(self):
"""Récupère l'inventaire depuis AWS EC2"""
ec2 = boto3.client('ec2')
inventory = {
'_meta': {
'hostvars': {}
}
}
# Récupérer toutes les instances
response = ec2.describe_instances(
Filters=[
{'Name': 'instance-state-name', 'Values': ['running']}
]
)
for reservation in response['Reservations']:
for instance in reservation['Instances']:
# Extraire les informations de l'instance
instance_id = instance['InstanceId']
private_ip = instance.get('PrivateIpAddress', '')
public_ip = instance.get('PublicIpAddress', '')
# Récupérer les tags
tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
name = tags.get('Name', instance_id)
environment = tags.get('Environment', 'unknown')
role = tags.get('Role', 'unknown')
# Ajouter aux groupes
if environment not in inventory:
inventory[environment] = {'hosts': []}
inventory[environment]['hosts'].append(name)
if role not in inventory:
inventory[role] = {'hosts': []}
inventory[role]['hosts'].append(name)
# Variables d'hôte
inventory['_meta']['hostvars'][name] = {
'ansible_host': public_ip or private_ip,
'ansible_user': 'ubuntu',
'instance_id': instance_id,
'instance_type': instance['InstanceType'],
'private_ip': private_ip,
'public_ip': public_ip,
'environment': environment,
'role': role,
'tags': tags
}
return inventory
def get_host_info(self, hostname):
"""Récupère les informations d'un hôte spécifique"""
inventory = self.get_inventory()
return inventory['_meta']['hostvars'].get(hostname, {})
if __name__ == '__main__':
EC2Inventory()
Gestion des Variables
# group_vars/all.yml
---
# Variables globales
timezone: "Europe/Paris"
ntp_servers:
- "0.pool.ntp.org"
- "1.pool.ntp.org"
# Utilisateurs système
system_users:
- name: deploy
groups: ["sudo", "docker"]
shell: /bin/bash
ssh_keys:
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQ..."
# Packages de base
base_packages:
- curl
- wget
- git
- htop
- vim
- unzip
# Configuration de sécurité
security:
ssh_port: 22
fail2ban_enabled: true
ufw_enabled: true
automatic_updates: true
# group_vars/webservers.yml
---
nginx_version: "1.20"
php_version: "8.1"
ssl_enabled: true
ssl_cert_path: "/etc/ssl/certs"
web_applications:
- name: "myapp"
domain: "example.com"
document_root: "/var/www/myapp"
php_enabled: true
# group_vars/databases.yml
---
mysql_version: "8.0"
mysql_root_password: "{{ vault_mysql_root_password }}"
mysql_databases:
- name: "myapp_prod"
encoding: "utf8mb4"
collation: "utf8mb4_unicode_ci"
mysql_users:
- name: "myapp_user"
password: "{{ vault_mysql_user_password }}"
priv: "myapp_prod.*:ALL"
host: "10.0.1.%"
Playbooks et Tâches
Playbook Principal
# playbooks/site.yml
---
- name: Configure all servers
hosts: all
become: yes
gather_facts: yes
pre_tasks:
- name: Update package cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Ensure system is up to date
apt:
upgrade: safe
when: ansible_os_family == "Debian"
roles:
- common
- security
- monitoring
- name: Configure web servers
hosts: webservers
become: yes
roles:
- nginx
- php
- ssl-certificates
- application-deployment
post_tasks:
- name: Verify web service is running
uri:
url: "http://{{ ansible_default_ipv4.address }}"
method: GET
status_code: 200
delegate_to: localhost
- name: Configure database servers
hosts: databases
become: yes
roles:
- mysql
- database-backup
post_tasks:
- name: Verify MySQL is running
service:
name: mysql
state: started
enabled: yes
- name: Configure load balancers
hosts: loadbalancers
become: yes
roles:
- haproxy
- keepalived
Playbook de Déploiement
# playbooks/deploy.yml
---
- name: Deploy application
hosts: webservers
become: yes
serial: "50%" # Déploiement par batch
vars:
app_name: "myapp"
app_version: "{{ version | default('latest') }}"
app_path: "/var/www/{{ app_name }}"
backup_path: "/var/backups/{{ app_name }}"
pre_tasks:
- name: Create backup directory
file:
path: "{{ backup_path }}"
state: directory
mode: '0755'
- name: Backup current application
archive:
path: "{{ app_path }}"
dest: "{{ backup_path }}/{{ app_name }}-{{ ansible_date_time.epoch }}.tar.gz"
when: app_path is directory
tasks:
- name: Stop application services
service:
name: "{{ item }}"
state: stopped
loop:
- nginx
- php8.1-fpm
- name: Download application archive
get_url:
url: "https://releases.example.com/{{ app_name }}/{{ app_version }}.tar.gz"
dest: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
mode: '0644'
- name: Extract application
unarchive:
src: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
dest: "{{ app_path }}"
remote_src: yes
owner: www-data
group: www-data
mode: '0755'
- name: Install dependencies
composer:
command: install
working_dir: "{{ app_path }}"
no_dev: yes
optimize_autoloader: yes
become_user: www-data
- name: Run database migrations
command: php artisan migrate --force
args:
chdir: "{{ app_path }}"
become_user: www-data
run_once: true
delegate_to: "{{ groups['webservers'][0] }}"
- name: Clear application cache
command: "{{ item }}"
args:
chdir: "{{ app_path }}"
become_user: www-data
loop:
- php artisan config:cache
- php artisan route:cache
- php artisan view:cache
- name: Start application services
service:
name: "{{ item }}"
state: started
enabled: yes
loop:
- php8.1-fpm
- nginx
post_tasks:
- name: Wait for application to be ready
uri:
url: "http://{{ ansible_default_ipv4.address }}/health"
method: GET
status_code: 200
retries: 5
delay: 10
- name: Clean up old backups
find:
paths: "{{ backup_path }}"
age: "7d"
patterns: "*.tar.gz"
register: old_backups
- name: Remove old backups
file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_backups.files }}"
Rôles Ansible
Structure d'un Rôle
# Créer la structure d'un rôle
ansible-galaxy init roles/nginx
# Structure générée
roles/nginx/
├── defaults/main.yml # Variables par défaut
├── files/ # Fichiers statiques
├── handlers/main.yml # Handlers (actions déclenchées)
├── meta/main.yml # Métadonnées du rôle
├── tasks/main.yml # Tâches principales
├── templates/ # Templates Jinja2
├── tests/ # Tests du rôle
└── vars/main.yml # Variables du rôle
Rôle Nginx Complet
# roles/nginx/defaults/main.yml
---
nginx_user: www-data
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 64m
nginx_remove_default_vhost: true
nginx_vhosts: []
nginx_upstreams: []
nginx_extra_conf_options: ""
# SSL Configuration
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
nginx_ssl_prefer_server_ciphers: "off"
# roles/nginx/tasks/main.yml
---
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
notify: restart nginx
- name: Create nginx directories
file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: '0755'
loop:
- /etc/nginx/sites-available
- /etc/nginx/sites-enabled
- /etc/nginx/conf.d
- /var/log/nginx
- name: Generate nginx main configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
notify: restart nginx
- name: Remove default nginx vhost
file:
path: "{{ item }}"
state: absent
loop:
- /etc/nginx/sites-enabled/default
- /etc/nginx/sites-available/default
when: nginx_remove_default_vhost
notify: restart nginx
- name: Generate nginx vhost configurations
template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ item.server_name }}"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_vhosts }}"
notify: restart nginx
- name: Enable nginx vhosts
file:
src: "/etc/nginx/sites-available/{{ item.server_name }}"
dest: "/etc/nginx/sites-enabled/{{ item.server_name }}"
state: link
loop: "{{ nginx_vhosts }}"
notify: restart nginx
- name: Generate nginx upstream configurations
template:
src: upstream.conf.j2
dest: "/etc/nginx/conf.d/{{ item.name }}-upstream.conf"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_upstreams }}"
notify: restart nginx
- name: Ensure nginx is started and enabled
service:
name: nginx
state: started
enabled: yes
- name: Validate nginx configuration
command: nginx -t
changed_when: false
Templates Nginx
{# roles/nginx/templates/nginx.conf.j2 #}
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections }};
use epoll;
multi_accept on;
}
http {
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout {{ nginx_keepalive_timeout }};
types_hash_max_size 2048;
client_max_body_size {{ nginx_client_max_body_size }};
include /etc/nginx/mime.types;
default_type application/octet-stream;
# SSL Settings
ssl_protocols {{ nginx_ssl_protocols }};
ssl_ciphers {{ nginx_ssl_ciphers }};
ssl_prefer_server_ciphers {{ nginx_ssl_prefer_server_ciphers }};
# Logging Settings
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
# Gzip Settings
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
{{ nginx_extra_conf_options }}
# Virtual Host Configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
{# roles/nginx/templates/vhost.conf.j2 #}
{% if item.upstream is defined %}
upstream {{ item.upstream.name }} {
{% for server in item.upstream.servers %}
server {{ server }};
{% endfor %}
}
{% endif %}
server {
listen {{ item.listen | default('80') }};
{% if item.ssl is defined and item.ssl.enabled %}
listen {{ item.ssl.listen | default('443') }} ssl http2;
{% endif %}
server_name {{ item.server_name }};
{% if item.ssl is defined and item.ssl.enabled %}
ssl_certificate {{ item.ssl.certificate }};
ssl_certificate_key {{ item.ssl.certificate_key }};
{% endif %}
root {{ item.root | default('/var/www/html') }};
index {{ item.index | default('index.html index.htm index.php') }};
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
{% if item.locations is defined %}
{% for location in item.locations %}
location {{ location.path }} {
{% if location.proxy_pass is defined %}
proxy_pass {{ location.proxy_pass }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
{% endif %}
{% if location.try_files is defined %}
try_files {{ location.try_files }};
{% endif %}
{% if location.fastcgi_pass is defined %}
fastcgi_pass {{ location.fastcgi_pass }};
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
{% endif %}
{{ location.extra_config | default('') }}
}
{% endfor %}
{% endif %}
# Default location
location / {
try_files $uri $uri/ =404;
}
# PHP handling
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Static files caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
Handlers
# roles/nginx/handlers/main.yml
---
- name: restart nginx
service:
name: nginx
state: restarted
listen: restart nginx
- name: reload nginx
service:
name: nginx
state: reloaded
listen: reload nginx
- name: validate nginx config
command: nginx -t
listen: validate nginx config
Techniques Avancées
Ansible Vault
# Créer un fichier vault
ansible-vault create vault/secrets.yml
# Éditer un fichier vault
ansible-vault edit vault/secrets.yml
# Chiffrer un fichier existant
ansible-vault encrypt group_vars/production/vault.yml
# Déchiffrer un fichier
ansible-vault decrypt group_vars/production/vault.yml
# Utiliser un fichier de mot de passe
echo "my_vault_password" > .vault_pass
chmod 600 .vault_pass
# Configuration dans ansible.cfg
vault_password_file = .vault_pass
# vault/secrets.yml (chiffré)
---
vault_mysql_root_password: "super_secret_password"
vault_mysql_user_password: "another_secret_password"
vault_api_keys:
stripe: "sk_live_..."
sendgrid: "SG...."
vault_ssl_private_keys:
example_com: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
-----END PRIVATE KEY-----
Modules Personnalisés
#!/usr/bin/python
# library/custom_service_check.py
from ansible.module_utils.basic import AnsibleModule
import requests
import time
def check_service_health(url, timeout=30, retries=3):
"""Vérifie la santé d'un service web"""
for attempt in range(retries):
try:
response = requests.get(url, timeout=timeout)
if response.status_code == 200:
return True, f"Service is healthy (HTTP {response.status_code})"
except requests.RequestException as e:
if attempt == retries - 1:
return False, f"Service check failed: {str(e)}"
time.sleep(5)
return False, "Service check failed after all retries"
def main():
module = AnsibleModule(
argument_spec=dict(
url=dict(type='str', required=True),
timeout=dict(type='int', default=30),
retries=dict(type='int', default=3),
expected_status=dict(type='int', default=200)
),
supports_check_mode=True
)
url = module.params['url']
timeout = module.params['timeout']
retries = module.params['retries']
if module.check_mode:
module.exit_json(changed=False, msg="Check mode - would check service health")
success, message = check_service_health(url, timeout, retries)
if success:
module.exit_json(changed=False, msg=message, status="healthy")
else:
module.fail_json(msg=message, status="unhealthy")
if __name__ == '__main__':
main()
Filtres Personnalisés
# filter_plugins/custom_filters.py
def generate_password(length=12, include_symbols=True):
"""Génère un mot de passe aléatoire"""
import random
import string
chars = string.ascii_letters + string.digits
if include_symbols:
chars += "!@#$%^&*"
return ''.join(random.choice(chars) for _ in range(length))
def format_bytes(bytes_value):
"""Formate les bytes en unités lisibles"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if bytes_value < 1024.0:
return f"{bytes_value:.1f} {unit}"
bytes_value /= 1024.0
return f"{bytes_value:.1f} PB"
class FilterModule(object):
def filters(self):
return {
'generate_password': generate_password,
'format_bytes': format_bytes
}
Stratégies de Déploiement
# playbooks/rolling-deployment.yml
---
- name: Rolling deployment with health checks
hosts: webservers
become: yes
serial: 1 # Un serveur à la fois
max_fail_percentage: 0 # Arrêter si un serveur échoue
pre_tasks:
- name: Remove server from load balancer
uri:
url: "http://{{ load_balancer_ip }}/api/servers/{{ inventory_hostname }}/disable"
method: POST
headers:
Authorization: "Bearer {{ lb_api_token }}"
delegate_to: localhost
- name: Wait for connections to drain
wait_for:
timeout: 30
tasks:
- name: Deploy application
include_role:
name: application-deployment
post_tasks:
- name: Health check
uri:
url: "http://{{ ansible_default_ipv4.address }}/health"
method: GET
status_code: 200
retries: 5
delay: 10
- name: Add server back to load balancer
uri:
url: "http://{{ load_balancer_ip }}/api/servers/{{ inventory_hostname }}/enable"
method: POST
headers:
Authorization: "Bearer {{ lb_api_token }}"
delegate_to: localhost
# playbooks/blue-green-deployment.yml
---
- name: Blue-Green deployment
hosts: webservers
become: yes
vars:
current_color: "{{ 'blue' if deployment_slot == 'green' else 'green' }}"
target_color: "{{ deployment_slot }}"
tasks:
- name: Deploy to {{ target_color }} environment
include_role:
name: application-deployment
vars:
app_path: "/var/www/{{ app_name }}-{{ target_color }}"
- name: Health check {{ target_color }} environment
uri:
url: "http://{{ ansible_default_ipv4.address }}:{{ target_color == 'blue' and '8080' or '8081' }}/health"
method: GET
status_code: 200
retries: 10
delay: 5
- name: Switch load balancer to {{ target_color }}
template:
src: nginx-upstream.conf.j2
dest: /etc/nginx/conf.d/app-upstream.conf
vars:
active_color: "{{ target_color }}"
notify: reload nginx
run_once: true
delegate_to: "{{ groups['loadbalancers'][0] }}"
Monitoring et Debugging
Callbacks Personnalisés
# callback_plugins/custom_logger.py
from ansible.plugins.callback import CallbackBase
import json
import time
from datetime import datetime
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'custom_logger'
def __init__(self):
super(CallbackModule, self).__init__()
self.start_time = time.time()
self.results = []
def v2_playbook_on_start(self, playbook):
self.playbook_name = playbook._file_name
print(f"Starting playbook: {self.playbook_name}")
def v2_runner_on_ok(self, result):
self.results.append({
'host': result._host.get_name(),
'task': result._task.get_name(),
'status': 'ok',
'changed': result._result.get('changed', False),
'timestamp': datetime.now().isoformat()
})
def v2_runner_on_failed(self, result, ignore_errors=False):
self.results.append({
'host': result._host.get_name(),
'task': result._task.get_name(),
'status': 'failed',
'error': result._result.get('msg', 'Unknown error'),
'timestamp': datetime.now().isoformat()
})
def v2_playbook_on_stats(self, stats):
duration = time.time() - self.start_time
summary = {
'playbook': self.playbook_name,
'duration': f"{duration:.2f}s",
'summary': {
'ok': sum(1 for r in self.results if r['status'] == 'ok'),
'failed': sum(1 for r in self.results if r['status'] == 'failed'),
'changed': sum(1 for r in self.results if r.get('changed', False))
},
'results': self.results
}
# Sauvegarder dans un fichier JSON
with open(f'/var/log/ansible-{int(time.time())}.json', 'w') as f:
json.dump(summary, f, indent=2)
print(f"Playbook completed in {duration:.2f}s")
Tests et Validation
# playbooks/test-infrastructure.yml
---
- name: Test infrastructure
hosts: all
gather_facts: yes
tasks:
- name: Check system uptime
command: uptime
register: uptime_result
changed_when: false
- name: Verify disk space
assert:
that:
- ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1000000000
fail_msg: "Root filesystem has less than 1GB available"
success_msg: "Root filesystem has sufficient space"
- name: Check memory usage
assert:
that:
- (ansible_memfree_mb / ansible_memtotal_mb * 100) > 10
fail_msg: "Less than 10% memory available"
success_msg: "Memory usage is acceptable"
- name: Verify services are running
service_facts:
- name: Check critical services
assert:
that:
- ansible_facts.services[item + '.service'].state == 'running'
fail_msg: "Service {{ item }} is not running"
loop:
- nginx
- mysql
- ssh
when: inventory_hostname in groups[item + '_servers'] | default([])
# molecule/default/molecule.yml (pour les tests de rôles)
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: ubuntu:20.04
pre_build_image: true
provisioner:
name: ansible
inventory:
host_vars:
instance:
ansible_python_interpreter: /usr/bin/python3
verifier:
name: ansible
Optimisation et Bonnes Pratiques
Performance et Parallélisation
# ansible.cfg optimisé pour la performance
[defaults]
forks = 50
poll_interval = 1
timeout = 10
gather_timeout = 10
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 86400
gathering = smart
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=300s
pipelining = True
control_path_dir = /tmp/.ansible-cp
Idempotence et Tests
# Exemple de tâche idempotente
- name: Ensure application configuration
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
owner: myapp
group: myapp
mode: '0644'
backup: yes
notify: restart myapp
register: config_result
- name: Validate configuration syntax
command: myapp --check-config /etc/myapp/app.conf
changed_when: false
when: config_result is changed
- name: Test configuration before restart
command: myapp --test-config /etc/myapp/app.conf
changed_when: false
failed_when: false
register: config_test
when: config_result is changed
- name: Fail if configuration is invalid
fail:
msg: "Configuration validation failed"
when:
- config_result is changed
- config_test.rc != 0
Conclusion
Ansible transforme la gestion d'infrastructure en permettant :
Automatisation Complète
- Déploiements reproductibles
- Configuration cohérente
- Orchestration complexe
Simplicité d'Usage
- Syntaxe YAML intuitive
- Architecture sans agent
- Courbe d'apprentissage douce
Flexibilité
- Modules extensibles
- Intégrations multiples
- Stratégies de déploiement variées
Fiabilité
- Idempotence garantie
- Tests intégrés
- Rollback automatique
Les techniques présentées dans cet article vous permettront de maîtriser Ansible pour automatiser efficacement vos infrastructures. L'investissement dans l'apprentissage d'Ansible se traduit rapidement par des gains de productivité et de fiabilité significatifs.
Pour un accompagnement dans la mise en place de vos automatisations Ansible, contactez-moi pour une consultation personnalisée.