Files
factory/ansible/arcodange/factory/playbooks/03_cicd.yml
T
arcodangeandClaude Opus 5 93e7abfc57 fix(ci,crowdsec): écarter le runner de pi1, figer la résolution de la forge, couper le cache Redis du bouncer
Trois correctifs issus d'une même soirée de panne.

1. Runner hors du control-plane (03_cicd.yml)

`capacity: 1` ne borne que CHAQUE runner, pas le cluster : avec deux runners,
deux builds lourds tournent en parallèle. Le 2026-08-26 ils ont saturé pi1
(charge 7,38 sur 15 min, 5,7 Go/8), le démon Docker a cessé de répondre et les
DEUX jobs sont morts à la même seconde — 19:49:27, runs #152 et #154,
`context deadline exceeded` sur docker.sock. pi1 porte l'apiserver et
l'ingress. Même famille que 2026-07-23 et 2026-08-15.

Le parallélisme tombe à 1 (pi3 seul), assumé : le runner du Mac (label
`laptop`) absorbe les jobs lourds et va ~10× plus vite — mesuré sur kadans,
184 s contre 1 065 s sur pi3 et 1 915 s sur pi1.

2. Résolution de la forge figée (03_cicd.yml, group_vars/all/gitea.yml)

Les hôtes à runner résolvent `.lab` via un Pi-hole, puis retombent sur les
nameservers IPv6 du routeur, qui répondent NXDOMAIN — réponse valide, donc
retenue. D'où des pulls qui échouent par intermittence pendant que
`getent hosts` réussit. `extra_hosts` + `--add-host` court-circuitent le DNS.

L'IP est dérivée de l'inventaire (premier hôte du groupe `gitea`) plutôt
qu'écrite en dur. Vérifié que c'est équivalent : le registre répond 401 sur
/v2/ via .202 comme via .201, le svclb Traefik écoutant sur 443 de chaque nœud.

⚠ Ça ne couvre PAS le namespace réseau de buildkit, qui n'hérite d'aucun des
deux — corrigé séparément côté cms (pull côté démon, build sans `--pull`).

3. Cache Redis du bouncer coupé (roles/crowdsec)

`redisCacheUnreachableBlock: false` n'est pas honoré par le plugin : redis-0
étant 0/1, TOUTES les requêtes externes prenaient un 403 alors que
`cscli decisions list` était vide. Logs Traefik : `isBanned:false
redis:unreachable` → 403. Le cache mémoire suffit — en mode `stream` les
décisions viennent de la LAPI de toute façon.

⚠ NE PAS « réparer » Redis en `--save ""` : il sert aussi kadans-jobs (DB 1,
file de jobs) et telegram-gateway (DB 0, sessions d'auth 24 h).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-26 22:55:38 +02:00

211 lines
12 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
- name: Deploy Gitea Action
# `!gitea` : ne pas déployer sur la machine qui héberge Gitea (pi2).
# `!pi1` : NI sur le control-plane k3s. Ajouté le 2026-08-26 après que deux
# builds lourds simultanés — un par runner, `capacity: 1` ne borne
# que CHAQUE runner, pas le cluster — aient saturé pi1 : charge 7,38
# sur 15 min, 5,7 Go/8 utilisés, puis le démon Docker cesse de
# répondre et les DEUX jobs meurent à la même seconde (19:49:27,
# runs #152 et #154, `context deadline exceeded` sur docker.sock).
# pi1 porte l'apiserver et l'ingress : un `nuxt generate` n'a rien à
# y faire. Même famille d'incident que 2026-07-23 et 2026-08-15.
#
# Le parallélisme tombe donc à 1 (pi3 seul). C'est assumé : le runner du Mac
# (label `laptop`) absorbe les jobs lourds, et il est ~10× plus rapide —
# mesuré sur kadans : 184 s sur le Mac contre 1 065 s sur pi3 et 1 915 s sur pi1.
hosts: raspberries:&local:!gitea:!pi1
roles:
- arcodange.factory.gitea_token # generate gitea_api_token used to replace generated token with set name if required
# Image de base des jobs CI lourds (Node + Bun + Chromium), construite ICI,
# sur chaque machine à runner, puis épinglée contre le ramasse-miettes Docker.
# Le même groupe d'hôtes que le runner, et ce n'est pas un détail : avec
# `capacity: 1` (ci-dessous), le parallélisme vient de PLUSIEURS machines, et
# `container:` est résolu par le runner — un job qui atterrit là où l'image
# manque échoue AVANT sa première étape.
- arcodange.factory.ci_base_image
tasks:
- name: Fetch Gitea Token for Action Runner registration
delegate_to: "{{ groups.gitea[0] }}"
delegate_facts: false
ansible.builtin.command:
docker exec gitea su git -c "gitea actions generate-runner-token"
register: gitea_runner_token_cmd
- name: Deploy Gitea Action Docker Compose configuration
include_role:
name: arcodange.factory.deploy_docker_compose
vars:
dockercompose_content:
name: arcodange_factory_gitea_action
services:
gitea_action:
# ⚠ VERSION ÉPINGLÉE (inventory/group_vars/all/gitea.yml), PAS `latest`.
# `latest` + `pull: missing` = tag flottant JAMAIS rafraîchi : chaque
# hôte gardait ce que « latest » voulait dire à son premier pull, d'où
# pi1 en v0.3.1 et pi3 en v0.2.13 sur des machines censées être
# équivalentes (114 s d'écart mesurés sur le même job).
# Avec un tag épinglé, `pull: missing` redevient CORRECT : changer la
# version change le tag, donc l'image est absente, donc elle est tirée.
image: "{{ gitea_runner_image }}:{{ gitea_runner_version }}"
container_name: gitea_action
restart: always
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: >-
http://{{ hostvars[groups.gitea[0]].ansible_host }}:3000
GITEA_RUNNER_REGISTRATION_TOKEN: "{{ gitea_runner_token_cmd.stdout }}"
GITEA_RUNNER_NAME: arcodange_global_runner_{{ inventory_hostname }}
GITEA_RUNNER_LABELS: ubuntu-latest:docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca,ubuntu-latest-ca:docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca,ci-node-playwright:docker://ci-node-playwright:latest
ports:
- "43707:43707"
# Résolution figée de la forge : le runner tire lui-même des
# images depuis gitea.arcodange.lab. Voir le commentaire de
# gitea_lab_ingress_ip (group_vars/all/gitea.yml) — le DNS du
# homelab répond NXDOMAIN par intermittence selon le nameserver
# du chaînage qui répond en premier.
extra_hosts:
- "{{ gitea_lab_fqdn }}:{{ gitea_lab_ingress_ip }}"
networks:
- gitea_action_network
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /etc/ssl/certs:/etc/ssl/certs:ro
- /usr/local/share/ca-certificates/:/usr/local/share/ca-certificates/:ro
- /mnt/arcodange/gitea-runner-cache:/home/git/.cache/actcache
- /mnt/arcodange/gitea-runner-act:/root/.cache/act
configs:
- config.yaml
networks:
gitea_action_network:
name: gitea_action_network
configs:
config.yaml:
content: |
# You don't have to copy this file to your instance,
# just run `./act_runner generate-config > config.yaml` to generate a config file.
#log:
# # The level of logging, can be trace, debug, info, warn, error, fatal
# level: info
runner:
# Where to store the registration result.
file: .runner
# Execute how many tasks concurrently at the same time.
# 1 seul job à la fois : les hôtes (8 Go, control-plane k3s sur pi1) ne survivent pas à 2 builds lourds simultanés.
capacity: 1
# Extra environment variables to run jobs.
envs:
A_TEST_ENV_NAME_1: a_test_env_value_1
A_TEST_ENV_NAME_2: a_test_env_value_2
# Extra environment variables to run jobs from a file.
# It will be ignored if it's empty or the file doesn't exist.
env_file: .env
# The timeout for a job to be finished.
# Please note that the Gitea instance also has a timeout (3h by default) for the job.
# So the job could be stopped by the Gitea instance if it's timeout is shorter than this.
timeout: 3h
# Whether skip verifying the TLS certificate of the Gitea instance.
insecure: true
# The timeout for fetching the job from the Gitea instance.
fetch_timeout: 5s
# The interval for fetching the job from the Gitea instance.
fetch_interval: 2s
# The labels of a runner are used to determine which jobs the runner can run, and how to run them.
# Like: "macos-arm64:host" or "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
# Find more images provided by Gitea at https://gitea.com/gitea/runner-images .
# If it's empty when registering, it will ask for inputting labels.
# If it's empty when execute `daemon`, will use labels in `.runner` file.
labels:
- "ubuntu-latest:docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca"
- "ubuntu-latest-ca:docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca"
# Jobs CI lourds (Node + Bun + Chromium préinstallés) —
# image construite LOCALEMENT par le rôle ci_base_image, sur
# cette machine. Elle n'est volontairement PAS dans le
# registre : 3,81 Go dont une couche de 1,36 Go, dont le
# push casse en « connection reset by peer » (mesuré
# 2026-07-29, kadans#225). `force_pull: false` ci-dessous
# est donc REQUIS pour ce label — sans lui, act_runner
# tenterait un pull et échouerait.
- "ci-node-playwright:docker://ci-node-playwright:latest"
cache:
# Enable cache server to use actions/cache.
enabled: true
# The directory to store the cache data.
# If it's empty, the cache data will be stored in $HOME/.cache/actcache.
dir: "/home/git/.cache/actcache"
# The host of the cache server.
# It's not for the address to listen, but the address to connect from job containers.
# So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
host: "{{ ansible_default_ipv4.address }}"
# The port of the cache server.
# 0 means to use a random available port.
port: 43707
# The external cache server URL. Valid only when enable is true.
# If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself.
# The URL should generally end with "/".
external_server: ""
container:
# Specifies the network to which the container will connect.
# Could be host, bridge or the name of a custom network.
# If it's empty, act_runner will create a network automatically.
network: ""
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
privileged: false
# And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway).
# Plafonds durs : un build ne doit jamais pouvoir affamer l'hôte (incident 2026-07-23 :
# nuxt generate à 3,5 Go RSS sur pi1 → load 150, ingress+API k3s morts → gitea.arcodange.lab injoignable).
# `--add-host` : même raison que l'`extra_hosts` du service
# ci-dessus, mais pour les conteneurs de JOB, qui sont ceux
# qui lancent `docker build --pull` contre la forge.
options: >-
--memory=3g --memory-swap=3g --cpus=2 --pids-limit=512
--add-host={{ gitea_lab_fqdn }}:{{ gitea_lab_ingress_ip }}
# The parent directory of a job's working directory.
# NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically.
# If the path starts with '/', the '/' will be trimmed.
# For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir
# If it's empty, /workspace will be used.
workdir_parent:
# Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
# You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
# For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
# valid_volumes:
# - data
# - /src/*.json
# If you want to allow any volume, please use the following configuration:
# valid_volumes:
# - '**'
valid_volumes: []
# overrides the docker client host with the specified one.
# If it's empty, act_runner will find an available docker host automatically.
# If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers.
# If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work.
docker_host: ""
# Pull docker image(s) even if already present
force_pull: false
# Rebuild docker image(s) even if already present
force_rebuild: false
host:
# The parent directory of a job's working directory.
# If it's empty, $HOME/.cache/act/ will be used.
workdir_parent:
- name: Deploy Gitea Action with Docker Compose
community.docker.docker_compose_v2:
project_src: "/home/pi/arcodange/docker_composes/arcodange_factory_gitea_action"
pull: missing
state: "{{ docker_compose_down_then_up }}"
register: deploy_result
loop: ["absent", "present"]
loop_control:
loop_var: docker_compose_down_then_up