Author SHA1 Message Date
arcodangeandClaude Opus 5 f53c3adb7d fix(redis) — il mourait toutes les 90 s, et c'est le .fr qui tombait
Helm Charts / Detect changed charts (pull_request) Successful in 1m14s
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Symptôme observé : `kadans.arcodange.fr` et `gitea.arcodange.fr` en 403, corps
vide, depuis n'importe quel client extérieur — pendant que `arcodange.fr` et
`www` répondaient 200. Ça ressemble à un bannissement d'IP. Ça n'en était pas un.

La chaîne, remontée de bout en bout :

1. Le sous-chart fige ses sondes à `timeoutSeconds: 1` sur `redis-cli ping` et
   n'expose aucune valeur pour les surcharger.
2. Sans réservation de ressources, le conteneur est en QoS `BestEffort` : sur un
   Raspberry Pi chargé, lancer `redis-cli` dépasse la seconde.
3. « Liveness probe failed: command timed out after 1s » 836 fois en 23 jours,
   135 redémarrages, SIGTERM après ~90 s de vie à chaque tour.
4. Le plugin crowdsec de Traefik utilise ce Redis comme cache de décisions.
   Cache injoignable ⇒ `isCrowdsecStreamHealthy:false` ⇒ refus par défaut de tout
   client absent de `clientTrustedIPs` ⇒ le 403.

D'où le motif trompeur : seuls les hôtes portant le middleware crowdsec
tombaient, et depuis le LAN (IP de confiance) tout paraissait sain — l'origine
répondait 401, son défi d'authentification normal. Trois fausses pistes en sont
sorties : Cloudflare, un bannissement crowdsec, une règle de pare-feu. Aucune
n'était la bonne, et `cscli decisions list` disait `isBanned:false` du début à la
fin.

La réservation fait passer le conteneur en `Burstable` et lui garantit sa part.
Les limites restent larges : Redis n'est pas ce qui sature ce nœud.

⚠ Ce commit ne corrige PAS la sonde elle-même — elle reste à 1 s, et elle reste
inatteignable depuis les valeurs. Si le clignotement revient malgré la
réservation, il faudra un patch Kustomize au niveau de l'Application ArgoCD, ou
abandonner ce sous-chart. Une vérification en production a été appliquée à chaud
(`timeoutSeconds: 5`, `failureThreshold: 6`) pour rétablir l'accès immédiatement,
mais `selfHeal: true` la ré-écrasera : elle n'est pas la correction, ce commit
l'est.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GdUCA5Uz8QyMwa2P4Pg2hK
2026-07-29 01:56:58 +02:00
arcodange 9de9a9663b Merge pull request 'ci — arrêter les runs qui ne peuvent pas aboutir, et le doublon push+PR' (#23) from arcodange/moins-de-runs-inutiles into main
Helm Charts / Detect changed charts (push) Successful in 18s
Helm Charts / Library charts tool (push) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
2026-07-26 12:39:13 +02:00
arcodangeandClaude Opus 5 2bdc486ae6 ci — arrêter les runs qui ne peuvent pas aboutir, et le doublon push+PR
Helm Charts / Detect changed charts (pull_request) Successful in 17s
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Quatre runs d'une branche déjà mergée ont bloqué, ce matin, l'apply qu'on
attendait. Deux causes, indépendantes :

1. Les workflows tofu (minio, vault, crowdsec, plausible) s'authentifient à
   Vault par un flux OIDC dont un HUMAIN doit ouvrir le lien. Déclenchés tout
   seuls, ils ne peuvent qu'occuper un runner jusqu'au timeout. Ils font en
   plus `apply` en `auto_approve` CONTRE LA PROD : partir sur le push d'une
   branche, c'est appliquer du code que personne n'a relu. → `workflow_dispatch`
   seul, ce qui écrit enfin ce qu'ils faisaient déjà.

   (crowdsec et plausible passaient de toute façon par une ancre YAML, donc
   leurs triggers étaient INERTES — issues 113 → 117 de kadans.)

2. `push` sur toutes les branches + `pull_request` = DEUX runs par commit dès
   qu'une branche a une PR. Vérifié : runs 258/259 et 260/261 portent le même
   SHA. helmcharts, qui travaille seul et mérite de rester automatique, prend
   la forme éprouvée de la CI de kadans : push sur `main`, PR pour la branche.

Chaque clé de trigger porte un corps explicite : un `pull_request:` nu n'est
pas une forme éprouvée ici, et son mode d'échec est le silencieux.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 12:37:46 +02:00
arcodange 901aa9a9dc Merge pull request 'fix(minio) — le provisionneur ne pouvait pas créer de bucket : s3:ListBucket manquait' (#22) from arcodange/provisioner-listbucket into main
Helm Charts / Detect changed charts (push) Successful in 1m0s
Helm Charts / Library charts tool (push) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
MinIO / Auth with gitea for vault (push) Successful in 23s
MinIO / Tofu - minio IAC (push) Successful in 49s
2026-07-26 11:44:18 +02:00
arcodangeandClaude Opus 5 e8ab19962b fix(minio) — le provisionneur ne pouvait pas créer de bucket : s3:ListBucket manquait
Helm Charts / Detect changed charts (push) Successful in 15s
Helm Charts / Detect changed charts (pull_request) Successful in 16s
Helm Charts / Library charts tool (push) Has been skipped
MinIO / Auth with gitea for vault (push) Failing after 8m31s
MinIO / Tofu - minio IAC (push) Has been skipped
Helm Charts / Library charts tool (pull_request) Has been skipped
Helm Charts / Application charts pgcat (push) Has been skipped
MinIO / Auth with gitea for vault (pull_request) Failing after 8m32s
MinIO / Tofu - minio IAC (pull_request) Has been skipped
Helm Charts / Application charts pgcat (pull_request) Has been skipped
Le premier apply réel (kadans) a créé la politique, le compte de service,
l'attachement et le secret Vault — puis a échoué sur le bucket lui-même :

    Error: [FATAL] unable to check bucket (kadans-videos): Access Denied.

Le provider teste l'existence du bucket AVANT de le créer (HeadBucket), et
MinIO exige `s3:ListBucket` pour ça. C'est le seul point que l'ADR annonçait
comme non éprouvé ; il l'est maintenant.

`s3:ListBucket` donne la vue des CLÉS d'un bucket, pas leur contenu :
GetObject / PutObject restent absents, donc le provisionneur — partagé entre
les rôles CI — ne peut toujours pas lire les vidéos d'une autre application.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
2026-07-26 11:43:04 +02:00
arcodange 5c7dce96e1 Merge pull request 'feat(minio) — un compte de service par app consommatrice, borné à son bucket' (#21) from arcodange/minio-comptes-de-service into main
Helm Charts / Detect changed charts (push) Successful in 19s
Hashicorp Vault / Auth with gitea for vault (push) Successful in 2m58s
MinIO / Auth with gitea for vault (push) Successful in 5m52s
Helm Charts / Library charts tool (push) Has been skipped
Hashicorp Vault / Tofu - Vault IAC (push) Successful in 53s
MinIO / Tofu - minio IAC (push) Successful in 33s
Helm Charts / Application charts pgcat (push) Has been skipped
2026-07-26 10:46:39 +02:00
7 changed files with 103 additions and 56 deletions
+8 -5
View File
@@ -2,12 +2,15 @@
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
name: Crowdsec
on: #[push,pull_request]
# À LA DEMANDE, et seulement à la demande — comme minio.yaml : auth Vault par
# flux OIDC (un humain doit ouvrir un lien) et apply `auto_approve` contre la prod.
#
# Note : les triggers `push`/`pull_request` retirés ici étaient de toute façon
# INERTES — ils passaient par une ancre YAML (`&`/`*`), que le parseur
# d'événements de Gitea ne résout pas (vécu sur arcodange/kadans, issues 113
# → 117). Ce workflow ne partait déjà qu'à la main ; c'est maintenant écrit.
on:
workflow_dispatch: {}
push: &crowdsecPaths
paths:
- 'crowdsec/**/*.tf'
pull_request: *crowdsecPaths
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
+26 -8
View File
@@ -2,14 +2,32 @@
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
name: Helm Charts
on: [push,pull_request,workflow_dispatch]
# push: &helmPaths # turns out gitea don't handle well the paths filter
# paths:
# - '*/\.yaml'
# - '*/\.tpl'
# - '*/NOTES.txt'
# - '*/\.helmignore'
# pull_request: *helmPaths
# Celui-ci travaille SEUL (pas d'auth Vault, pas d'apply) : on le garde
# automatique. Mais `push` sur TOUTES les branches + `pull_request` faisait
# partir DEUX runs pour le même commit dès qu'une branche avait une PR.
#
# Même forme que la CI de kadans : la branche est couverte par `pull_request`,
# `main` par le `push` d'après-merge. Un run par événement, aucun angle mort.
#
# (Le filtre de chemins d'origine, resté en commentaire des années sous un
# « gitea don't handle well the paths filter », n'était probablement pas en
# cause : il passait par une ancre YAML, et le parseur d'événements de Gitea ne
# les résout pas — issues 113 → 117 de kadans. Le job `filter-chart` fait déjà
# ce tri au niveau job, donc on n'y retouche pas.)
#
# ⚠ Chaque clé porte un CORPS explicite : un `pull_request:` nu (valeur nulle)
# n'est pas une forme éprouvée sur ce Gitea, et son mode d'échec est le
# silencieux — aucun run, aucune erreur. On copie la forme qui tourne (kadans
# ci.yml), listes dupliquées à la main, sans ancre.
on:
workflow_dispatch: {}
push:
branches: [main]
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
+11 -12
View File
@@ -1,20 +1,19 @@
---
name: MinIO
# ⚠ Triggers écrits EN TOUTES LETTRES, sans ancre YAML (`&`/`*`).
# Une ancre dans un trigger Gitea Actions fait taire push ET pull_request —
# en silence, aucun run, aucune erreur (vécu sur arcodange/kadans, issues 113
# → 117). Les autres workflows de ce repo utilisent encore des ancres : à
# vérifier séparément, c'est probablement pour ça qu'ils ne partent qu'à la
# main (workflow_dispatch).
# À LA DEMANDE, et seulement à la demande. Deux raisons, chacune suffisante :
#
# 1. Ce workflow ne PEUT PAS aboutir sans un humain : l'auth Vault passe par
# un flux OIDC dont le lien doit être ouvert dans un navigateur connecté.
# Déclenché tout seul, il occupe un runner jusqu'à son timeout — et retarde
# les runs que quelqu'un attend vraiment.
# 2. Il fait `terraform apply` en `auto_approve` CONTRE LA PROD. Se déclencher
# sur le push d'une branche, c'est appliquer du code que personne n'a relu.
#
# Au passage : `push` (toutes branches) + `pull_request` faisait partir DEUX runs
# par commit d'une branche en PR — le même SHA, deux fois.
on:
workflow_dispatch: {}
push:
paths:
- 'minio/**/*.tf'
pull_request:
paths:
- 'minio/**/*.tf'
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
+7 -5
View File
@@ -2,12 +2,14 @@
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
name: Plausible
on: #[push,pull_request]
# À LA DEMANDE, et seulement à la demande — comme minio.yaml : auth Vault par
# flux OIDC (un humain doit ouvrir un lien) et apply `auto_approve` contre la prod.
#
# Note : les triggers `push`/`pull_request` retirés ici étaient de toute façon
# INERTES (ancre YAML non résolue par Gitea, issues 113 → 117 de kadans). Ce
# workflow ne partait déjà qu'à la main ; c'est maintenant écrit.
on:
workflow_dispatch: {}
push: &plausiblePaths
paths:
- 'plausible/**/*.tf'
pull_request: *plausiblePaths
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
+10 -15
View File
@@ -2,23 +2,18 @@
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
name: Hashicorp Vault
# ⚠ Triggers écrits EN TOUTES LETTRES, sans ancre YAML (`&`/`*`) : une ancre
# dans un trigger Gitea Actions fait taire push ET pull_request, en silence
# (vécu sur arcodange/kadans, issues 113 → 117) — c'est probablement pourquoi
# ce workflow ne partait qu'à la main.
# Et `*.tfvars` compte AUTANT que `*.tf` : la liste des applications (donc les
# rôles gitea_cicd_<app>) vit dans terraform.tfvars — l'oublier, c'est ajouter
# une app sans jamais créer son rôle.
# À LA DEMANDE, et seulement à la demande — comme minio.yaml, et pour les mêmes
# deux raisons : l'auth Vault exige qu'un humain ouvre un lien OIDC (sans lui, le
# run squatte un runner jusqu'au timeout), et l'apply se fait en `auto_approve`
# contre la prod.
#
# ⚠ Ce qui change AUSSI de nature : `hashicorp-vault/**/*.tfvars` compte autant
# que `*.tf` — la liste des applications (donc les rôles gitea_cicd_<app>) vit
# dans terraform.tfvars. Ce n'est plus un filtre de chemins mais ça reste vrai
# du POURQUOI on relance : ajouter une app au tfvars sans relancer ce workflow,
# c'est une app sans rôle CI.
on:
workflow_dispatch: {}
push:
paths:
- 'hashicorp-vault/**/*.tf'
- 'hashicorp-vault/**/*.tfvars'
pull_request:
paths:
- 'hashicorp-vault/**/*.tf'
- 'hashicorp-vault/**/*.tfvars'
# cancel any previously-started, yet still active runs of this workflow on the same branch
concurrency:
+11 -4
View File
@@ -3,8 +3,9 @@
# qu'elles déclarent leurs buckets sans qu'on leur confie le root.
# Décisions : factory/doc/adr/20260726-stockage-objet-minio.md
#
# Noms d'actions issus de la documentation MinIO, NON éprouvés contre le
# serveur : le premier apply les confirmera ou les corrigera.
# Noms d'actions confirmés par le premier apply réel (kadans, 2026-07-26) : le
# bloc admin passe tel quel ; côté s3 il manquait `s3:ListBucket`, que le
# provider appelle AVANT de créer un bucket pour savoir s'il existe déjà.
resource "minio_iam_policy" "provisioner" {
name = "provisioner"
policy = jsonencode({
@@ -26,9 +27,15 @@ resource "minio_iam_policy" "provisioner" {
Resource = ["arn:aws:s3:::*"]
},
{
# s3:GetObject / s3:PutObject volontairement ABSENTS.
# s3:GetObject / s3:PutObject volontairement ABSENTS : le provisionneur
# ne LIT ni n'ÉCRIT aucun objet, c'est ce qui rend son partage entre
# rôles CI acceptable.
#
# `s3:ListBucket` est la seule concession : MinIO le demande pour un
# HeadBucket, et le provider teste l'existence du bucket avant de le
# créer. Il donne la vue des CLÉS d'un bucket, jamais leur contenu.
Effect = "Allow"
Action = ["s3:CreateBucket", "s3:DeleteBucket", "s3:ListAllMyBuckets", "s3:GetBucketLocation", "s3:GetBucketPolicy", "s3:PutBucketPolicy"]
Action = ["s3:CreateBucket", "s3:DeleteBucket", "s3:ListBucket", "s3:ListAllMyBuckets", "s3:GetBucketLocation", "s3:GetBucketPolicy", "s3:PutBucketPolicy"]
Resource = ["arn:aws:s3:::*"]
},
]
+30 -7
View File
@@ -140,13 +140,36 @@ redis: &redis_config
runAsUser: 999
# -- Compute resources used by the container. More info [here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
#
# ⚠ CE N'EST PAS DU CONFORT — c'est ce qui empêche Redis de mourir en boucle.
#
# Le sous-chart FIGE ses sondes à `timeoutSeconds: 1` sur `redis-cli ping`, et
# n'expose aucune valeur pour les surcharger. Sur ce matériel (Raspberry Pi),
# un conteneur SANS réservation tombe dans la classe QoS `BestEffort` : il est
# le premier affamé quand le nœud est chargé, et le simple lancement de
# `redis-cli` y dépasse la seconde.
#
# Mesuré le 2026-07-29 sur `redis-0` : « Liveness probe failed: command timed
# out: "redis-cli ping" timed out after 1s » **836 fois en 23 jours**, 135
# redémarrages, le conteneur tué par SIGTERM après ~90 s de vie à chaque tour.
#
# ⚠ CE QUE ÇA CASSAIT, ET QUI N'AVAIT RIEN À VOIR AVEC REDIS EN APPARENCE : le
# plugin crowdsec de Traefik utilise ce Redis comme cache de décisions. Cache
# injoignable ⇒ `isCrowdsecStreamHealthy:false` ⇒ le plugin REFUSE PAR DÉFAUT
# tout client non listé dans `clientTrustedIPs` ⇒ **403, corps vide, sur
# `kadans.arcodange.fr` et `gitea.arcodange.fr`**, pendant que les hôtes qui ne
# portent pas ce middleware répondaient normalement. Le symptôme ne nomme ni
# Redis, ni crowdsec, ni la sonde — il ressemble à un bannissement d'IP, et
# c'est par là qu'on cherche d'abord.
#
# Une réservation fait passer le conteneur en `Burstable` et lui garantit sa
# part. Les limites restent larges : Redis n'est pas ce qui sature ce nœud.
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256Mi
# -- Pod-level affinity. More info [here](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling).
affinity: {}