A l'issue de ce TD, vous serez capable de :
Vous intégrez l'équipe DevOps d'une plateforme e-commerce en forte croissance. L'infrastructure tourne sur AWS en production, mais l'equipe travaille en local avec Docker pour les environnements de developpement.
Votre mission : automatiser la configuration d'une stack composee de quatre serveurs :
Terraform a déjà provisionné les conteneurs. Vous utilisez Ansible pour configurer.
Un rôle permet d’organiser :
Quel critère doit guider le découpage en rôles : le service (nginx, flask…) ou l'environnement (production, staging…) ? Justifiez.
roles/
nginx/
tasks/main.yml
handlers/main.yml
templates/
Creez la structure suivante :
td-ansible/
docker/
Dockerfile
terraform/
main.tf
ansible/
inventory/
hosts.yml
group_vars/
all.yml
frontend.yml
backend.yml
db.yml
cache.yml
roles/
nginx/
tasks/
main.yml
templates/
nginx.conf.j2
handlers/
main.yml
flask/
tasks/
main.yml
handlers/
main.yml
postgresql/
tasks/
main.yml
handlers/
main.yml
redis/
tasks/
main.yml
handlers/
main.yml
playbook.yml
Construisez l'image manuellement depuis le dossier docker.
Fichier : docker/Dockerfile
FROM ubuntu:22.04
RUN apt-get update -qq && \
apt-get install -y -qq \
openssh-server \
python3 \
python3-pip \
sudo \
curl \
&& mkdir -p /run/sshd \
&& echo 'root:root' | chpasswd \
&& sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
cd docker docker build -t ansible-node:local . cd ..
Créez le fichier suivant, puis appliquez la configuration.
Fichier : terraform/main.tf
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {}
resource "docker_image" "base" {
name = "ansible-node:local"
keep_locally = true
}
resource "docker_network" "stack_network" {
name = "stack_network"
}
resource "docker_container" "nginx" {
name = "nginx"
image = docker_image.base.image_id
networks_advanced {
name = docker_network.stack_network.name
}
ports {
internal = 22
external = 2221
}
ports {
internal = 80
external = 8090
}
}
resource "docker_container" "flask" {
name = "flask"
image = docker_image.base.image_id
networks_advanced {
name = docker_network.stack_network.name
}
ports {
internal = 22
external = 2222
}
}
resource "docker_container" "postgresql" {
name = "postgresql"
image = docker_image.base.image_id
networks_advanced {
name = docker_network.stack_network.name
}
ports {
internal = 22
external = 2223
}
}
resource "docker_container" "redis" {
name = "redis"
image = docker_image.base.image_id
networks_advanced {
name = docker_network.stack_network.name
}
ports {
internal = 22
external = 2224
}
}
cd terraform terraform init terraform apply
Si vous obtenez une erreur indiquant que le réseau ou des conteneurs existent déjà, supprimez-les avant de relancer :
docker rm -f nginx flask postgresql redis docker network rm stack_network terraform apply
Questions :
L'inventaire indique à Ansible quels sont les hôtes à gérer et comment s'y connecter.
Creez le fichier suivant.
Fichier : ansible/inventory/hosts.yml
all:
vars:
ansible_user: root
ansible_password: root
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
children:
frontend:
hosts:
nginx:
ansible_host: 127.0.0.1
ansible_port: 2221
backend:
hosts:
flask:
ansible_host: 127.0.0.1
ansible_port: 2222
db:
hosts:
postgresql:
ansible_host: 127.0.0.1
ansible_port: 2223
cache:
hosts:
redis:
ansible_host: 127.0.0.1
ansible_port: 2224
Testez la connexion aux conteneurs :
Installez au préalable sshpass sur votre machine :
sudo apt-get install -y sshpasspuis :
cd ansible/ ansible all -i inventory/hosts.yml -m ping
Questions :
Les variables permettent de centraliser la configuration et de la rendre reutilisable.
Creez les fichiers suivants.
Fichier : ansible/group_vars/all.yml
app_name: ecommerce app_env: development app_domain: localhost
Fichier : ansible/group_vars/db.yml
db_name: ecommerce_db db_user: app_user db_password: changeme db_port: 5432
Fichier : ansible/group_vars/cache.yml
redis_port: 6379 redis_maxmemory: 256mb
Fichier : ansible/group_vars/backend.yml
flask_port: 5000 flask_debug: true db_host: postgresql cache_host: redis
Fichier : ansible/group_vars/frontend.yml
nginx_port: 80 backend_host: flask backend_port: 5000
On commence par la base de donnees car les autres services en dependent.
Creez le fichier suivant.
Fichier : ansible/roles/postgresql/tasks/main.yml
---
- name: Installation de PostgreSQL
apt:
name:
- postgresql
- postgresql-contrib
- python3-psycopg2
state: present
update_cache: yes
- name: Demarrage de PostgreSQL
service:
name: postgresql
state: started
enabled: yes
- name: Creation de la base de donnees
become: yes
become_user: postgres
postgresql_db:
name: "{{ db_name }}"
state: present
- name: Creation de l'utilisateur applicatif
become: yes
become_user: postgres
postgresql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
state: present
- name: Attribution des privilègees
become: yes
become_user: postgres
postgresql_privs:
database: "{{ db_name }}"
role: "{{ db_user }}"
privs: ALL
type: database
state: present
Creez le fichier suivant.
Fichier : ansible/roles/redis/tasks/main.yml
---
- name: Installation de Redis
apt:
name: redis-server
state: present
update_cache: yes
- name: Configuration de Redis
lineinfile:
path: /etc/redis/redis.conf
regexp: '^maxmemory '
line: "maxmemory {{ redis_maxmemory }}"
notify: restart redis
- name: Demarrage de Redis
service:
name: redis-server
state: started
enabled: yes
Fichier : ansible/roles/redis/handlers/main.yml
---
- name: restart redis
service:
name: redis-server
state: restarted
Questions :
Créez le fichier suivant.
Fichier : ansible/roles/flask/tasks/main.yml
---
- name: Installation des dependances Python
apt:
name:
- python3
- python3-pip
- python3-venv
state: present
update_cache: yes
- name: Creation du repertoire applicatif
file:
path: /opt/flask
state: directory
mode: '0755'
- name: Creation de l'application Flask
copy:
dest: /opt/flask/app.py
content: |
from flask import Flask, jsonify
import os
import redis
import psycopg2
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify(status='ok', env=os.environ.get('APP_ENV', 'unknown'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port={{ flask_port }}, debug={{ flask_debug | lower }})
notify: restart flask
- name: Installation de Flask et des dependances
pip:
name:
- flask
- redis
- psycopg2-binary
executable: pip3
- name: Creation du service systemd Flask
copy:
dest: /etc/systemd/system/flask.service
content: |
[Unit]
Description=Flask Application
After=network.target
[Service]
Environment=APP_ENV={{ app_env }}
Environment=DB_HOST={{ db_host }}
Environment=CACHE_HOST={{ cache_host }}
ExecStart=/usr/bin/python3 /opt/flask/app.py
Restart=always
[Install]
WantedBy=multi-user.target
notify: restart flask
- name: Demarrage de Flask
shell: nohup python3 /opt/flask/app.py > /var/log/flask.log 2>&1 &
args :
chdir: /opt/flask
Fichier : ansible/roles/flask/handlers/main.yml
---
- name: restart flask
shell: pkill -f "python3 /opt/flask/app.py"; nohup python3 /opt/flask/app.py > /var/log/flask.log 2>&1 &
args :
chdir: /opt/flask
Créez les fichiers suivants.
Fichier : ansible/roles/nginx/tasks/main.yml
---
- name: Installation de Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Configuration de Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
mode: '0644'
notify: restart nginx
- name: Demarrage de Nginx
service:
name: nginx
state: started
enabled: yes
Fichier : ansible/roles/nginx/templates/nginx.conf.j2
upstream backend {
server {{ backend_host }}:{{ backend_port }};
}
server {
listen {{ nginx_port }};
server_name {{ app_domain }};
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /health {
proxy_pass http://backend/health;
}
}
Fichier : ansible/roles/nginx/handlers/main.yml
---
- name: restart nginx
service:
name: nginx
state: restarted
Questions :
Creez le fichier suivant.
Fichier : ansible/playbook.yml
---
- name: Configuration de la base de donnees
hosts: db
become: yes
roles:
- postgresql
- name: Configuration du cache
hosts: cache
become: yes
roles:
- redis
- name: Configuration de l'application
hosts: backend
become: yes
roles:
- flask
- name: Configuration du reverse proxy
hosts: frontend
become: yes
roles:
- nginx
Questions :
Executez le playbook :
cd ansible/ ansible-playbook -i inventory/hosts.yml playbook.yml
Verifiez que l'application repond :
curl http://localhost:8090/health
Modifiez le fichier group_vars/frontend.yml et changez backend_host :
backend_host: wronghost
Relancez le playbook, puis testez :
curl http://localhost:8080/health
Questions :
Corrigez la valeur de backend_host et relancez le playbook. Vérifiez que l'application répond correctement.
Vous devez ajouter un nouveau service de monitoring.
Sans aide, créez un rôle Ansible pour installer et configurer Netdata sur le conteneur flask.
Contraintes :
Vérifiez que Netdata est accessible depuis votre navigateur.