Aller au contenu

Bonnes pratiques

Optimisation des pipelines

Cache des dependances

Le cache reduit drastiquement le temps de build en evitant de retelecharger les dependances à chaque run.

# Cache Go modules
- name: Cache Go modules
  uses: actions/cache@v4
  with:
    path: ~/go/pkg/mod
    key: go-${{ hashFiles('go.sum') }}
    restore-keys: go-

# Cache Node modules
- name: Cache Node modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('package-lock.json') }}
    restore-keys: npm-
Langage Répertoire a cacher Cle de cache Gain typique
Go ~/go/pkg/mod go-${{ hashFiles('go.sum') }} 60-80%
Node ~/.npm npm-${{ hashFiles('package-lock.json') }} 50-70%
Python ~/.cache/pip pip-${{ hashFiles('requirements.txt') }} 40-60%
Rust ~/.cargo/registry, target/ cargo-${{ hashFiles('Cargo.lock') }} 70-90%
Java ~/.m2/repository maven-${{ hashFiles('pom.xml') }} 50-70%

Stages paralleles

Exécuter les étapes indépendantes en parallele reduit le temps total du pipeline :

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make lint

  test-unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make test-unit

  test-integration:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make test-integration

  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: trivy fs .

  # Ce job attend tous les precedents
  build:
    needs: [lint, test-unit, test-integration, scan]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make build
graph LR
    subgraph Sequential["Sans parallelisme : 15 min"]
        S1[lint] --> S2[test-unit] --> S3[test-integration] --> S4[scan] --> S5[build]
    end
    subgraph Parallel["Avec parallelisme : 7 min"]
        P1[lint] --> P5[build]
        P2[test-unit] --> P5
        P3[test-integration] --> P5
        P4[scan] --> P5
    end

Matrix builds

Tester sur plusieurs versions/OS en une seule définition :

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        go-version: ["1.21", "1.22", "1.23"]
        os: [ubuntu-latest, alpine]
      fail-fast: false    # ne pas arreter les autres si un echoue
    steps:
      - uses: actions/checkout@v4
      - name: Setup Go ${{ matrix.go-version }}
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go-version }}
      - run: go test ./...

Build d'images multi-stages

Reduire la taille de l'image finale en separant le build du runtime :

# Dockerfile multi-stage
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server ./cmd/server

FROM gcr.io/distroless/static:nonroot
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]

Self-hosted runners

Auto-scaling des runners

Pour les charges variables, déployer des runners qui scalent automatiquement :

# Kubernetes : utiliser un Deployment avec HPA
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitea-runner
  namespace: ci-runners
spec:
  replicas: 2        # minimum
  selector:
    matchLabels:
      app: gitea-runner
  template:
    metadata:
      labels:
        app: gitea-runner
    spec:
      serviceAccountName: gitea-runner
      containers:
        - name: runner
          image: gitea/act_runner:latest
          args: ["daemon", "--config", "/config/config.yaml"]
          resources:
            requests:
              cpu: "1"
              memory: 2Gi
            limits:
              cpu: "2"
              memory: 4Gi
          volumeMounts:
            - name: config
              mountPath: /config
            - name: data
              mountPath: /data
      volumes:
        - name: config
          configMap:
            name: runner-config
        - name: data
          emptyDir: {}
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: gitea-runner-hpa
  namespace: ci-runners
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gitea-runner
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Nettoyage des runners

Les runners accumulent des images, des caches et des artefacts. Nettoyer régulièrement :

# CronJob de nettoyage
#!/bin/bash
# /home/actrunner/cleanup.sh

# Supprimer les images non utilisees depuis plus de 24h
podman image prune -a --filter "until=24h" -f

# Supprimer les conteneurs arretes
podman container prune -f

# Supprimer les volumes orphelins
podman volume prune -f

# Verifier l'espace disque
USAGE=$(df -h /home/actrunner | tail -1 | awk '{print $5}' | tr -d '%')
if [ "${USAGE}" -gt 80 ]; then
  echo "WARNING: disk usage at ${USAGE}%"
  # Alerte via webhook
fi
# Cron : executer chaque nuit
echo "0 3 * * * actrunner /home/actrunner/cleanup.sh" | sudo tee /etc/cron.d/runner-cleanup

Resource limits par label

Différents labels pour différents profils de build :

Label CPU RAM Cas d'usage
small 1 2 Go Lint, scan, scripts
ubuntu-latest 2 4 Go Builds standards
large 4 8 Go Builds Java, monorepos
gpu 2+GPU 16 Go ML inference, builds CUDA

GitOps avance

Structure du dépôt de manifestes

Organisation recommandee pour un dépôt fleet-infra géré par Flux :

fleet-infra/
├── clusters/
│   ├── production/
│   │   ├── flux-system/            ← manifestes Flux (bootstrap)
│   │   ├── infrastructure.yaml     ← Kustomization pour l'infra
│   │   ├── apps.yaml               ← Kustomization pour les apps
│   │   └── tenants/                ← Kustomizations par equipe
│   │       ├── team-backend.yaml
│   │       └── team-frontend.yaml
│   └── staging/
│       ├── flux-system/
│       ├── infrastructure.yaml
│       └── apps.yaml
├── infrastructure/
│   ├── controllers/                ← Ingress, cert-manager, ESO...
│   └── configs/                    ← ClusterIssuer, StorageClass...
└── apps/
    ├── base/
    │   ├── myapp/
    │   │   ├── deployment.yaml
    │   │   ├── service.yaml
    │   │   └── kustomization.yaml
    │   └── api/
    └── overlays/
        ├── staging/
        │   └── kustomization.yaml
        └── production/
            └── kustomization.yaml

Kustomization en cascade (infrastructure → apps)

# clusters/production/infrastructure.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: infrastructure-controllers
  namespace: flux-system
spec:
  interval: 1h
  retryInterval: 1m
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./infrastructure/controllers
---
# clusters/production/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 5m
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./apps/overlays/production
  # Les apps attendent que l'infra soit prete
  dependsOn:
    - name: infrastructure-controllers

Promotion entre environnements

Le workflow de promotion suit un chemin predictible :

graph LR
    develop --> staging["staging<br/>(auto-sync, interval 5m)"]
    staging --> production["production<br/>(auto-sync, dependsOn staging)"]
# Workflow de promotion
- name: Promote to production
  if: github.ref == 'refs/heads/main'
  run: |
    TAG="${GITHUB_SHA::8}"

    # Copier le tag de staging vers production
    cd apps/overlays/production
    kustomize edit set image \
      harbor.example.com/org/myapp:${TAG}

    git commit -am "promote: myapp ${TAG} to production"
    git push

Contrôle du déploiement en production

Pour les environnements critiques, utiliser dependsOn pour forcer le staging avant la production. La promotion peut aussi passer par une PR sur le dépôt de manifestes pour ajouter une review humaine.

Drift detection et correction

Flux détecte automatiquement les modifications manuelles sur le cluster quand le feature gate DetectDrift est activé :

# Verifier le drift via les evenements
flux get kustomizations

# Voir les evenements de drift
kubectl -n flux-system get events \
  --field-selector reason=DriftDetected

# Forcer la reconciliation
flux reconcile kustomization myapp-production --with-source

Configurer une alerte sur le drift :

# Alert specifique au drift
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
  name: drift-alerts
  namespace: flux-system
spec:
  providerRef:
    name: mattermost
  eventSeverity: info
  eventSources:
    - kind: Kustomization
      name: "*"
  inclusionList:
    - ".*drift.*"

Sécurité des pipelines

Actions epinglees par hash

Ne jamais utiliser un tag mutable (@v4) en production — epingler par hash SHA :

# Mauvais : le tag v4 peut etre modifie par le mainteneur
- uses: actions/checkout@v4

# Bon : hash immuable, verifiable
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1

Provenance SLSA

Générer une attestation de provenance pour chaque artefact :

- name: Generate SLSA provenance
  run: |
    # Signer l'image avec cosign
    cosign sign --key env://COSIGN_PRIVATE_KEY \
      ${REGISTRY}/${IMAGE_NAME}:${TAG}

    # Generer l'attestation de provenance
    cosign attest --key env://COSIGN_PRIVATE_KEY \
      --predicate provenance.json \
      --type slsaprovenance \
      ${REGISTRY}/${IMAGE_NAME}:${TAG}

Verification cosign dans Flux

Flux peut vérifier la signature cosign des images avant de les deployer :

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: myapp-production
  namespace: flux-system
spec:
  interval: 5m
  prune: true
  sourceRef:
    kind: GitRepository
    name: manifests
  path: ./overlays/production
  verify:
    provider: cosign
    secretRef:
      name: cosign-public-key

Least-privilege runners

Privilege Runner CI Flux controllers
Root Non Non
Privileged Non Non
Host network Non Non
Host PID Non Non
Capabilities Aucune (DROP ALL) Aucune
Read-only rootfs Oui Oui
Non-root user Oui (UID 1000+) Oui

Dependabot / Renovate pour les actions

Maintenir les actions et les images de base a jour :

# .gitea/renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "gitea-actions": {
    "enabled": true,
    "pinDigests": true
  },
  "dockerfile": {
    "enabled": true
  }
}

Disaster recovery

Flux v2 backup

Flux se gère lui-même via GitOps : les manifestes Flux sont dans le dépôt Git. Le backup principal est donc le dépôt Git lui-même.

# Exporter l'etat actuel de Flux
flux export --all > flux-backup-$(date +%Y%m%d).yaml

# Exporter les sources
flux export source git --all >> flux-backup-$(date +%Y%m%d).yaml

# Exporter les Kustomizations
flux export kustomization --all >> flux-backup-$(date +%Y%m%d).yaml

# Exporter les HelmReleases
flux export helmrelease --all >> flux-backup-$(date +%Y%m%d).yaml
# Sauvegarder les secrets Flux (cles SSH, SOPS, tokens)
kubectl -n flux-system get secrets -o yaml > flux-secrets-backup.yaml

# Stocker dans Vault (pas sur le filesystem)
vault kv put secret/backup/flux \
  secrets=@flux-secrets-backup.yaml

rm flux-secrets-backup.yaml

Git est le backup

Tant que le dépôt Git fleet-infra est intact, Flux peut etre reinstalle sur un nouveau cluster avec flux bootstrap. Les secrets (cles SSH, SOPS) doivent cependant etre sauvegardes separement car ils ne sont pas dans Git.

Gitea Actions history retention

Configurer la retention des logs et artefacts :

# /etc/gitea/app.ini
[actions]
ENABLED = true

# Retention des logs (defaut : 365 jours)
LOG_RETENTION_DAYS = 365

# Retention des artefacts (defaut : 90 jours)
ARTIFACT_RETENTION_DAYS = 90

# Taille maximale des artefacts
MAX_ARTIFACT_SIZE = 512M

Restauration

Composant Procedure de restauration RTO estime
Gitea Actions Restaurer Gitea (DB + config), re-enregistrer les runners < 30 min
Flux v2 flux bootstrap + restaurer secrets depuis Vault < 15 min
Runners Redeployer via systemd/Kubernetes, re-enregistrer < 10 min
Secrets (Vault) Restaurer Vault depuis snapshot < 20 min

Troubleshooting

Runner ne se connecte pas

# 1. Verifier la connectivite reseau
curl -v https://git.example.com/api/v1/version

# 2. Verifier le token d'enregistrement
# Les tokens expirent — en regenerer un si necessaire

# 3. Verifier les logs du runner
journalctl -u gitea-runner -f

# 4. Verifier le statut dans Gitea
curl -s -H "Authorization: token ${GITEA_TOKEN}" \
  "https://git.example.com/api/v1/admin/runners" | jq .

Image pull failures

# 1. Verifier l'acces au registre depuis le runner
podman login harbor.example.com

# 2. Verifier que l'image existe
curl -s "https://harbor.example.com/v2/org/myapp/tags/list" \
  -H "Authorization: Basic ${AUTH}"

# 3. Verifier les credentials dans les secrets CI
# Les robot accounts Harbor expirent-ils ?

# 4. Verifier les network policies (le runner peut-il joindre Harbor ?)
kubectl -n ci-runners exec -it runner-pod -- curl -v https://harbor.example.com

Flux reconciliation bloquee

# 1. Verifier le statut des Kustomizations
flux get kustomizations

# 2. Voir les evenements detailles
flux events

# 3. Verifier le statut des sources Git
flux get sources git

# 4. Forcer un refresh depuis Git
flux reconcile source git manifests

# 5. Forcer la reconciliation de la Kustomization
flux reconcile kustomization myapp-production --with-source

# 6. Verifier les logs du kustomize-controller
kubectl -n flux-system logs -l app=kustomize-controller --tail=50

# 7. Verifier les logs du source-controller (clone Git)
kubectl -n flux-system logs -l app=source-controller --tail=50

# 8. Suspendre/reprendre si necessaire
flux suspend kustomization myapp-production
flux resume kustomization myapp-production

OOM dans les builds

# 1. Verifier les limites memoire du runner
podman stats --no-stream

# 2. Augmenter les limites si necessaire
# Dans config.yaml du runner :
# container.options: --memory=8g

# 3. Optimiser le build
# - Utiliser des builds multi-stages
# - Reduire le nombre de layers
# - Eviter COPY . . (utiliser .dockerignore)
# - Paralleliser moins (GOMAXPROCS=2, npm --max-old-space-size)

# 4. Verifier si le cache est correctement configure
# Un cache absent force un re-telechargement complet a chaque build

Pipeline timeout

# 1. Verifier le timeout configure
# runner.timeout dans config.yaml (defaut : 3h)

# 2. Identifier l'etape lente
# Verifier les logs du run dans Gitea UI > Actions > Run > Steps

# 3. Causes frequentes :
# - Test d'integration qui attend une DB non demarree
# - Push d'image lent (bande passante reseau)
# - Build sans cache (tout retelecharge a chaque run)
# - Deadlock dans les tests

Checklist de mise en production

Élément Verifie Remarque
Runners déployés et enregistres [ ] Minimum 2 pour la redondance
Flux bootstrap effectue [ ] flux check passe
GitRepository connecte au depot manifests [ ] Cle SSH deploy en lecture seule
Secrets chiffres via SOPS [ ] Cle age sauvegardee dans Vault
Network policies appliquees [ ] Runners isoles, flux-system protege
Branch protection activee [ ] CI requis avant merge
Drift detection active [ ] Feature gate DetectDrift=true
Notifications configurees [ ] Provider + Alert sur erreurs
Monitoring en place [ ] Grafana dashboards, metriques Flux
Backup secrets Flux dans Vault [ ] Cles SSH + SOPS
Documentation a jour [ ] ADR, runbook, contacts