Ansible : Maîtriser l'Automatisation Infrastructure as Code
Guide complet pour automatiser vos infrastructures avec Ansible : playbooks, rôles, inventaires dynamiques, et bonnes pratiques pour la production.
Publié le
16 décembre 2024
Lecture
19 min
Vues
0
Auteur
Florian Courouge
Ansible
Automation
IaC
DevOps
Configuration Management
Orchestration
Table des matières
📋 Vue d'ensemble rapide des sujets traités dans cet article
Cliquez sur les sections ci-dessous pour naviguer rapidement
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
#!/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
# 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.
À propos de l'auteur
Florian Courouge - Expert DevOps et Apache Kafka avec plus de 5 ans d'expérience dans l'architecture de systèmes distribués et l'automatisation d'infrastructures.
Cet article vous a été utile ?
Découvrez mes autres articles techniques ou contactez-moi pour discuter de vos projets DevOps et Kafka.