Skip to content

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 ​

ini
[defaults]
inventory = inventory.ini
host_key_checking = True

[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False

[ssh_connection]
pipelining = True
OptionEffet
inventory = inventory.iniPas besoin de passer -i à chaque commande
host_key_checking = TrueRefuse une machine dont l'empreinte SSH a changé (protection contre l'usurpation)
become = TrueLes tâches s'exécutent avec sudo
become_ask_pass = Falsesudo sans mot de passe (configuré sur les VM)
pipelining = TrueMoins de connexions SSH, donc plus rapide

Le playbook : playbook.yml ​

yaml
- 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ôleCibleResponsabilité
baseles deuxles paquets de base
dockerles deuxDocker Engine, Compose, groupe docker
ufwles deuxle pare-feu de la machine, ports par machine
traefiktm-prodle conteneur Traefik, sa configuration statique et dynamique
apptm-prodl'utilisateur deploy, le compose de l'application, le .env, la connexion à GHCR, le démarrage
jenkinstm-toolingl'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 ​

FichierContenuChiffré
group_vars/all.ymlles valeurs partagées et non secrètes : domaines, chemins, propriétaire GHCR, clé publique de Jenkinsnon
group_vars/prod.ymlles secrets de tm-prod : jeton de lecture GHCR, mot de passe de la base, secret Phoenix, clé de l'API d'emailsoui (Ansible Vault)
group_vars/tooling.ymlles secrets de tm-tooling : mot de passe admin de Jenkins, jeton GitHub, clé privée de déploiementoui (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 ​

yaml
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
VariableSens
ansible_user, ansible_ssh_private_key_fileAvec qui et quelle clé Ansible se connecte
app_rootLe dossier de travail sur les serveurs
docker_networkLe nom du réseau partagé entre Traefik et l'application
ghcr_ownerLe compte GitHub qui possède les images Docker
domain_*Les noms de domaine
vite_api_urlL'adresse de l'API inscrite dans le site web au moment du build
prod_private_ip, tooling_private_ipLues dans l'inventaire avec hostvars : on ne les écrit qu'une fois
jenkins_deploy_pubkeyLa clé publique de Jenkins, autorisée à se connecter en deploy sur tm-prod
postgres_user, postgres_dbLe 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 :

FichierVariableUtilisée par
prod.ymlpostgres_passwordrôle app (.env)
secret_key_baserôle app (.env)
ghcr_read_tokenrôle app (docker login)
brevo_api_key, mail_fromrôle app (.env), pour l'envoi des emails
tooling.ymljenkins_admin_passwordrôle jenkins (.env)
github_tokenrôle jenkins (.env)
jenkins_deploy_keyrô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 ​

  1. Crée /etc/apt/keyrings.
  2. Télécharge la clé GPG officielle de Docker.
  3. Ajoute le dépôt officiel Docker pour la version d'Ubuntu de la machine (ansible_distribution_release).
  4. Installe docker-ce.
  5. Télécharge le binaire docker-compose dans /usr/local/bin.
  6. Démarre Docker et l'active au démarrage de la machine.
  7. Ajoute ansible_user au groupe docker (pour lancer docker sans sudo).

ufw ​

  1. Installe UFW.
  2. Autorise le port 22 (SSH) partout.
  3. Autorise 80 et 443 uniquement sur les machines du groupe prod (when: inventory_hostname in groups['prod']).
  4. 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) ​

  1. Crée /opt/timemanager/traefik.
  2. Crée le réseau Docker timemanager (ignore_errors: yes : s'il existe déjà, ce n'est pas une erreur).
  3. Sur ton ordinateur (delegate_to: localhost), emballe le dossier traefik/ dans une archive.
  4. Copie l'archive sur le serveur et la décompresse dans /opt/timemanager/.
  5. Crée acme.json avec les droits 0600 (Traefik refuse de démarrer si ce fichier est lisible par d'autres).
  6. Lance docker compose up -d.
  7. 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) ​

  1. Crée l'utilisateur deploy, membre du groupe docker.
  2. Autorise la clé publique de Jenkins à se connecter en deploy.
  3. Crée /opt/timemanager/app, propriété de deploy.
  4. Copie app/docker-compose.yml.
  5. Écrit .env avec les secrets, en 0600 :
ini
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=...
  1. Connecte deploy au registre GHCR avec un jeton en lecture seule (pour télécharger les images privées).
  2. Lance docker compose up -d en tant que deploy.

no_log: true sur les tâches qui manipulent des secrets : ils n'apparaissent jamais dans la sortie d'Ansible.

jenkins (tm-tooling) ​

  1. Crée /opt/timemanager.
  2. Emballe le dossier jenkins/ en local, le copie et le décompresse sur le serveur.
  3. Génère /opt/timemanager/jenkins/.env à partir du modèle templates/jenkins.env.j2 :
ini
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 }}
  1. Crée secrets/ en 0700 et y écrit la clé SSH privée de déploiement en 0600.
  2. Construit l'image et lance Jenkins : docker compose up -d --build.
  3. 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é ».
  4. Supprime l'archive locale.
  5. 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/ :

bash
# 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-pass

D'autres exemples dans Opérations courantes.