Récap complet de l'infra CI sonic/openus : Gitea, Woodpecker server/agent, Dependency-Track, tips Woodpecker next, checklist nouvelle app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
249 lines
7.2 KiB
Markdown
249 lines
7.2 KiB
Markdown
# Woodpecker next — Bugs connus et règles de la stack
|
|
|
|
Tous les problèmes rencontrés en production et leurs fixes.
|
|
Source : `docs-sonic/retrospective-problemes-2026-03-17.md`
|
|
|
|
---
|
|
|
|
## Règles à appliquer systématiquement
|
|
|
|
| Règle | Raison |
|
|
|---|---|
|
|
| **Jamais `${VAR}` dans `commands:`** — toujours `$VAR` | Woodpecker next substitue `${VAR}` au parse YAML, avant exécution. Vaut pour les secrets, vars CI, et vars shell locales. |
|
|
| **`from_secret` et `volumes:` dans des steps séparés** | Bug Woodpecker next : les secrets sont vides si le step a aussi des `volumes:`. |
|
|
| **Chemins absolus après un `cd`** | Un `cd` dans `commands:` persiste pour toutes les lignes suivantes du même step. |
|
|
| **`ACME_EXIT=0; cmd \|\| ACME_EXIT=$?`** pour capturer les codes non-zéro | `set -e` est actif dans les blocs `- \|`. `cmd; VAR=$?` tue le script si `cmd` retourne != 0. |
|
|
|
|
---
|
|
|
|
## 1. `${VAR}` substitué au parse YAML
|
|
|
|
**Symptôme** : valeurs vides dans les commandes, ou `docker exec "-db"` (nom coupé).
|
|
|
|
```bash
|
|
# ❌
|
|
echo "${PS_DOMAIN}"
|
|
docker exec "${PROJECT}-db"
|
|
|
|
# ✅
|
|
env | grep '^PS_DOMAIN=' | cut -d= -f2
|
|
docker exec "$PROJECT-db"
|
|
```
|
|
|
|
---
|
|
|
|
## 2. `from_secret` + `volumes:` incompatibles dans le même step
|
|
|
|
**Symptôme** : secrets vides (`DTRACK_TOKEN` vide, etc.) quand le step a aussi un `volumes:`.
|
|
|
|
**Fix** : séparer en deux steps :
|
|
- step avec `volumes:` : lit depuis des fichiers copiés dans le workspace ou dans un chemin monté
|
|
- step avec `from_secret` : pas de `volumes:`
|
|
|
|
Exemple de découpage :
|
|
```yaml
|
|
# ✅ Step secrets uniquement (pas de volumes)
|
|
- name: write-env
|
|
environment:
|
|
MON_SECRET:
|
|
from_secret: mon_secret
|
|
commands:
|
|
- echo "MON_SECRET=$MON_SECRET" > .env.deploy
|
|
|
|
# ✅ Step deploy avec volumes (lit depuis .env.deploy)
|
|
- name: deploy
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- /opt/monapp:/opt/monapp
|
|
commands:
|
|
- cp .env.deploy /opt/monapp/.env
|
|
```
|
|
|
|
---
|
|
|
|
## 3. `set -e` et capture de codes de retour non-zéro
|
|
|
|
**Symptôme** : pipeline échoue sur une commande qui retourne un code non-zéro attendu
|
|
(ex : acme.sh exit 2 = cert déjà valide).
|
|
|
|
```bash
|
|
# ❌ fragile : set -e tue le script si exit != 0
|
|
cmd ; EXIT=$?
|
|
|
|
# ✅ correct
|
|
EXIT=0
|
|
cmd || EXIT=$?
|
|
if [ "$EXIT" -ne 0 ] && [ "$EXIT" -ne 2 ]; then # 2 = cas acceptable
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
---
|
|
|
|
## 4. YAML cassé par variables shell multi-lignes
|
|
|
|
**Symptôme** : `yaml: line XX: could not find expected ':'`.
|
|
|
|
**Cause** : une variable assignée sur plusieurs lignes dans un `- |` est interprétée comme YAML.
|
|
|
|
```bash
|
|
# ❌ casse le YAML
|
|
ROUTES="route add ... \
|
|
route add ..."
|
|
|
|
# ✅
|
|
ROUTES=$(printf 'route add %s %s/* http://%s:80/\nroute add %s %s:443/* http://%s:80/' \
|
|
"$SERVICE" "$DOMAIN" "$IP" "$SERVICE" "$DOMAIN" "$IP")
|
|
```
|
|
|
|
---
|
|
|
|
## 5. CWD modifié par `cd` dans les commandes précédentes
|
|
|
|
**Symptôme** : `cat: can't open '.env.deploy': No such file or directory` alors que le fichier existe.
|
|
|
|
**Cause** : un `cd /opt/monapp` dans une ligne précédente change le CWD pour tout le step.
|
|
|
|
```yaml
|
|
# ❌
|
|
commands:
|
|
- cd /opt/monapp && docker compose up -d
|
|
- PASS=$(grep '^DB_PASSWORD=' .env.deploy | cut -d= -f2) # cherche dans /opt/monapp/
|
|
|
|
# ✅ copier avant le cd, puis chemin absolu
|
|
commands:
|
|
- cp .env.deploy /opt/monapp/.env
|
|
- cd /opt/monapp && docker compose up -d
|
|
- PASS=$(grep '^DB_PASSWORD=' /opt/monapp/.env | cut -d= -f2)
|
|
```
|
|
|
|
---
|
|
|
|
## 6. `SERVICE_80_CHECK_TCP` : valeur `"true"` obligatoire
|
|
|
|
Registrator ignore les valeurs vides. Avec `PS_SSL_ENABLED_EVERYWHERE=1`,
|
|
le service redirige tout en 302 → un check HTTP voit "failing". Utiliser TCP.
|
|
|
|
```yaml
|
|
# ❌ ignoré par Registrator
|
|
- SERVICE_80_CHECK_TCP=
|
|
|
|
# ✅
|
|
- SERVICE_80_CHECK_TCP=true
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Routes Fabio : toujours `/*`
|
|
|
|
Fabio utilise le glob matcher. `/` matche uniquement le chemin exact.
|
|
Sans `/*`, tous les sous-chemins tombent sur le catch-all nginx → 404.
|
|
|
|
```
|
|
# ❌ ne route que /
|
|
route add monservice mondomaine.fr/ http://172.22.0.x:80/
|
|
|
|
# ✅ route tout
|
|
route add monservice mondomaine.fr/* http://172.22.0.x:80/
|
|
route add monservice mondomaine.fr:443/* http://172.22.0.x:80/
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Consul catalog seul insuffisant pour Fabio
|
|
|
|
**Testé en production** : Fabio ne détecte pas les services via le catalog Consul même
|
|
quand ils sont healthy avec les bons tags `urlprefix-*`.
|
|
|
|
**Le KV `fabio/config/<projet>` est obligatoire.** Mettre à jour l'IP à chaque déploiement :
|
|
|
|
```bash
|
|
# Dans le step deploy, après docker compose up :
|
|
IP=$(docker inspect "$PROJECT-app" \
|
|
--format '{{(index .NetworkSettings.Networks "sonic").IPAddress}}')
|
|
ROUTES=$(printf 'route add %s %s/* http://%s:80/\nroute add %s %s:443/* http://%s:80/' \
|
|
"$PROJECT" "$DOMAIN" "$IP" "$PROJECT" "$DOMAIN" "$IP")
|
|
docker exec sonic-consul env CONSUL_HTTP_TOKEN="$CTOK" consul kv put "fabio/config/$PROJECT" "$ROUTES"
|
|
```
|
|
|
|
Utiliser une **sous-clé** `fabio/config/$PROJECT` (pas `fabio/config`) pour ne pas écraser
|
|
les routes des autres projets.
|
|
|
|
---
|
|
|
|
## 9. acme.sh : toujours `--home /etc/acme.sh`
|
|
|
|
Sans ce flag, acme.sh écrit dans `/root/.acme.sh` dans le container → non persistant.
|
|
|
|
```bash
|
|
# ✅ pattern complet (idempotent)
|
|
ACME_EXIT=0
|
|
docker exec sonic-acme-1 /app/acme.sh \
|
|
--home /etc/acme.sh \
|
|
--issue -d "$DOMAIN" \
|
|
--webroot /usr/share/nginx/html \
|
|
--server letsencrypt \
|
|
--accountemail support+acme@asycn.io || ACME_EXIT=$?
|
|
[ "$ACME_EXIT" -ne 0 ] && [ "$ACME_EXIT" -ne 2 ] && exit 1
|
|
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/fullchain.cer /host/certs/$DOMAIN-cert.pem
|
|
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/$DOMAIN.key /host/certs/$DOMAIN-key.pem
|
|
```
|
|
|
|
acme-companion (`LETSENCRYPT_HOST`) ne couvre que les containers sur le réseau `nginx-proxy`.
|
|
Pour les services Fabio, acme.sh via `docker exec` est la seule méthode qui fonctionne.
|
|
|
|
---
|
|
|
|
## 10. Instances Consul stale après renommage de container
|
|
|
|
Changer `COMPOSE_PROJECT_NAME` → nouveaux containers, nouvelle IP, mais les anciennes
|
|
registrations Consul restent actives → Fabio peut router vers des IPs mortes.
|
|
|
|
**Fix** :
|
|
1. `docker stop <ancien-container> && docker rm <ancien-container>`
|
|
2. Registrator désenregistre automatiquement à l'arrêt
|
|
3. Supprimer les entrées manuelles orphelines si besoin :
|
|
```bash
|
|
curl -X PUT -H "X-Consul-Token: $CTOK" \
|
|
http://localhost:8500/v1/agent/service/deregister/<service-id>
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Syft : image distroless incompatible avec Woodpecker
|
|
|
|
`anchore/syft:latest` est distroless → `exec: "/bin/sh": no such file or directory`.
|
|
|
|
```yaml
|
|
# ❌
|
|
image: anchore/syft:latest
|
|
|
|
# ✅
|
|
image: alpine:3.20
|
|
commands:
|
|
- apk add --no-cache curl
|
|
- curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin latest
|
|
- syft mon-image:tag -o cyclonedx-json --file .reports/sbom.cyclonedx.json
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Dependency-Track : token sans permissions → HTTP 403
|
|
|
|
Le token de l'équipe Automation doit avoir les permissions :
|
|
- `BOM_UPLOAD`
|
|
- `PROJECT_CREATION`
|
|
|
|
Administration > Access Management > Teams > Automation > **Permissions**.
|
|
|
|
Pour debugger une réponse HTTP :
|
|
```bash
|
|
# ❌ -f masque le body de l'erreur
|
|
curl -sf -X POST ...
|
|
|
|
# ✅ capture HTTP code + body
|
|
HTTP=$(curl -s -o /tmp/resp.txt -w "%{http_code}" -X POST ...)
|
|
echo "HTTP $HTTP : $(cat /tmp/resp.txt)"
|
|
[ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1
|
|
```
|