Ansible
Ansible prépare les deux machines à partir de zéro : paquets, Docker, pare-feu, Traefik, application, Jenkins. Tout est dans timemanager-devops/ansible/.
La configuration : ansible.cfg
[defaults]
inventory = inventory.ini
host_key_checking = True
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
[ssh_connection]
pipelining = True| Option | Effet |
|---|---|
inventory = inventory.ini | Pas besoin de passer -i à chaque commande |
host_key_checking = True | Refuse une machine dont l'empreinte SSH a changé (protection contre l'usurpation) |
become = True | Les tâches s'exécutent avec sudo |
become_ask_pass = False | sudo sans mot de passe (configuré sur les VM) |
pipelining = True | Moins de connexions SSH, donc plus rapide |
Le playbook : playbook.yml
- name: Setup Time Manager VMs
hosts: all
roles:
- { role: base, tags: base }
- { role: docker, tags: docker }
- { role: ufw, tags: ufw }
- name: Setup prod VM
hosts: prod
roles:
- { role: traefik, tags: traefik }
- { role: app, tags: app }
- name: Setup tooling VM
hosts: tooling
roles:
- { role: jenkins, tags: jenkins }| Rôle | Cible | Responsabilité |
|---|---|---|
base | les deux | les paquets de base |
docker | les deux | Docker Engine, Compose, groupe docker |
ufw | les deux | le pare-feu de la machine, ports par machine |
traefik | tm-prod | le conteneur Traefik, sa configuration statique et dynamique |
app | tm-prod | l'utilisateur deploy, le compose de l'application, le .env, la connexion à GHCR, le démarrage |
jenkins | tm-tooling | l'image Jenkins, le .env généré, la clé de déploiement, le démarrage |
Chaque rôle a un tag du même nom : on peut n'appliquer qu'un rôle, par exemple ansible-playbook playbook.yml --tags app.
L'ordre compte : Docker doit être installé avant de lancer Traefik, et Traefik doit avoir créé le réseau timemanager avant que l'application s'y branche.
Les variables
| Fichier | Contenu | Chiffré |
|---|---|---|
group_vars/all.yml | les valeurs partagées et non secrètes : domaines, chemins, propriétaire GHCR, clé publique de Jenkins | non |
group_vars/prod.yml | les secrets de tm-prod : jeton de lecture GHCR, mot de passe de la base, secret Phoenix, clé de l'API d'emails | oui (Ansible Vault) |
group_vars/tooling.yml | les secrets de tm-tooling : mot de passe admin de Jenkins, jeton GitHub, clé privée de déploiement | oui (Ansible Vault) |
Les secrets d'une machine ne sont chargés que pour elle : tm-prod ne reçoit jamais les secrets de Jenkins, et tm-tooling ne reçoit jamais ceux de la base.
group_vars/all.yml : pour toutes les machines
ansible_user: eliashounnou
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
app_root: /opt/timemanager
docker_network: timemanager
ghcr_owner: saliehsab
domain_api: api.time.jostud.com
domain_ci: ci.time.jostud.com
vite_api_url: "https://{{ domain_api }}/api"
domain_front: time.jostud.com
prod_private_ip: "{{ hostvars['tm-prod'].private_ip }}"
tooling_private_ip: "{{ hostvars['tm-tooling'].private_ip }}"
jenkins_deploy_pubkey: "ssh-ed25519 AAAA... jenkins_timemanager"
postgres_user: timemanager
postgres_db: timemanager| Variable | Sens |
|---|---|
ansible_user, ansible_ssh_private_key_file | Avec qui et quelle clé Ansible se connecte |
app_root | Le dossier de travail sur les serveurs |
docker_network | Le nom du réseau partagé entre Traefik et l'application |
ghcr_owner | Le compte GitHub qui possède les images Docker |
domain_* | Les noms de domaine |
vite_api_url | L'adresse de l'API inscrite dans le site web au moment du build |
prod_private_ip, tooling_private_ip | Lues dans l'inventaire avec hostvars : on ne les écrit qu'une fois |
jenkins_deploy_pubkey | La clé publique de Jenkins, autorisée à se connecter en deploy sur tm-prod |
postgres_user, postgres_db | Le nom d'utilisateur et de base PostgreSQL |
Les {{ ... }} sont des références à d'autres variables (syntaxe Jinja2).
group_vars/prod.yml et group_vars/tooling.yml : les secrets
Ces deux fichiers sont chiffrés avec Ansible Vault. Ils commencent par $ANSIBLE_VAULT;1.1;AES256 et sont illisibles sans le mot de passe du vault. Ils contiennent :
| Fichier | Variable | Utilisée par |
|---|---|---|
prod.yml | postgres_password | rôle app (.env) |
secret_key_base | rôle app (.env) | |
ghcr_read_token | rôle app (docker login) | |
brevo_api_key, mail_from | rôle app (.env), pour l'envoi des emails | |
tooling.yml | jenkins_admin_password | rôle jenkins (.env) |
github_token | rôle jenkins (.env) | |
jenkins_deploy_key | rôle jenkins (clé SSH privée) |
Voir Secrets.
Les rôles, un par un
base
Met à jour la liste des paquets (au plus une fois par heure) et installe les outils de base : curl, wget, git, unzip, ca-certificates, gnupg, lsb-release.
docker
- Crée
/etc/apt/keyrings. - Télécharge la clé GPG officielle de Docker.
- Ajoute le dépôt officiel Docker pour la version d'Ubuntu de la machine (
ansible_distribution_release). - Installe
docker-ce. - Télécharge le binaire
docker-composedans/usr/local/bin. - Démarre Docker et l'active au démarrage de la machine.
- Ajoute
ansible_userau groupedocker(pour lancerdockersanssudo).
ufw
- Installe UFW.
- Autorise le port 22 (SSH) partout.
- Autorise 80 et 443 uniquement sur les machines du groupe
prod(when: inventory_hostname in groups['prod']). - Active UFW avec une politique « tout refuser par défaut ».
Autoriser SSH avant d'activer
L'ordre des tâches est volontaire. Activer le pare-feu avant d'autoriser le port 22 couperait la connexion d'Ansible et rendrait la machine injoignable.
traefik (tm-prod)
- Crée
/opt/timemanager/traefik. - Crée le réseau Docker
timemanager(ignore_errors: yes: s'il existe déjà, ce n'est pas une erreur). - Sur ton ordinateur (
delegate_to: localhost), emballe le dossiertraefik/dans une archive. - Copie l'archive sur le serveur et la décompresse dans
/opt/timemanager/. - Crée
acme.jsonavec les droits0600(Traefik refuse de démarrer si ce fichier est lisible par d'autres). - Lance
docker compose up -d. - Supprime l'archive locale.
run_once: true sur les tâches locales évite de créer l'archive plusieurs fois si plusieurs machines sont visées.
app (tm-prod)
- Crée l'utilisateur
deploy, membre du groupedocker. - Autorise la clé publique de Jenkins à se connecter en
deploy. - Crée
/opt/timemanager/app, propriété dedeploy. - Copie
app/docker-compose.yml. - Écrit
.envavec les secrets, en0600:
GHCR_OWNER=...
POSTGRES_USER=...
POSTGRES_PASSWORD=...
POSTGRES_DB=...
SECRET_KEY_BASE=...
PHX_HOST=api.time.jostud.com
CORS_ORIGINS=https://time.jostud.com
BREVO_API_KEY=...
MAIL_FROM=...- Connecte
deployau registre GHCR avec un jeton en lecture seule (pour télécharger les images privées). - Lance
docker compose up -den tant quedeploy.
no_log: true sur les tâches qui manipulent des secrets : ils n'apparaissent jamais dans la sortie d'Ansible.
jenkins (tm-tooling)
- Crée
/opt/timemanager. - Emballe le dossier
jenkins/en local, le copie et le décompresse sur le serveur. - Génère
/opt/timemanager/jenkins/.envà partir du modèletemplates/jenkins.env.j2:
JENKINS_ADMIN_PASSWORD={{ jenkins_admin_password }}
JENKINS_PUBLIC_URL=https://{{ domain_ci }}
JENKINS_BIND_IP={{ tooling_private_ip }}
GITHUB_USERNAME={{ ghcr_owner }}
GITHUB_TOKEN={{ github_token }}
GHCR_USERNAME={{ ghcr_owner }}
GHCR_TOKEN={{ github_token }}
VITE_API_URL={{ vite_api_url }}
PROD_HOST={{ prod_private_ip }}- Crée
secrets/en0700et y écrit la clé SSH privée de déploiement en0600. - Construit l'image et lance Jenkins :
docker compose up -d --build. - Attend que Jenkins réponde (jusqu'à 15 essais espacés de 20 s, soit 5 minutes). Les codes 200, 403 et 503 comptent comme « démarré ».
- Supprime l'archive locale.
- Installe une tâche cron à 4 h du matin qui supprime les images et le cache de build de plus de 72 h (les builds remplissent vite le disque).
Lancer Ansible
Depuis timemanager-devops/ansible/ :
# tout, sur les deux machines
ansible-playbook playbook.yml --ask-vault-pass
# seulement Traefik
ansible-playbook playbook.yml --tags traefik --ask-vault-pass
# seulement la machine de prod
ansible-playbook playbook.yml --limit prod --ask-vault-pass
# voir ce qui changerait, sans rien faire
ansible-playbook playbook.yml --check --diff --ask-vault-passD'autres exemples dans Opérations courantes.