Integration¶
Vue d'ensemble des integrations¶
Le service CI/CD s'integre avec l'ensemble du catalogue de services pour former une chaîne logicielle complète.
graph TD
Vault["Vault<br/>(secrets)"] --> CI
Harbor["Harbor<br/>(registre)"] --> CI
Sonar["SonarQube<br/>(qualite)"] --> CI
Trivy["Trivy<br/>(securite)"] --> CI
CI["Gitea Actions (CI)<br/>Vault inject → Build → Test → SonarQube → Trivy → Push"] --> CD
CD["Flux v2 (CD)<br/>SOPS secrets | GitRepository → Kustomization → Cluster | Metrics → Grafana"] Secrets depuis Vault¶
Gitea Actions : variables chiffrees + Vault¶
Les secrets peuvent etre injectes de deux manières dans les workflows Gitea Actions.
Méthode 1 — Variables CI (simple) :
Stocker les secrets dans Gitea (Settings > Secrets) et les referencer dans le workflow :
steps:
- name: Login to Harbor
run: |
podman login harbor.example.com \
-u ${{ secrets.HARBOR_USER }} \
-p ${{ secrets.HARBOR_PASSWORD }}
Méthode 2 — Injection directe depuis Vault (recommande) :
steps:
- name: Fetch secrets from Vault
run: |
export VAULT_ADDR="https://vault.example.com"
export VAULT_TOKEN=$(vault write -field=token \
auth/jwt/login role=ci-runner \
jwt="${ACTIONS_ID_TOKEN_REQUEST_TOKEN}")
# Recuperer les secrets
HARBOR_USER=$(vault kv get -field=username secret/ci/harbor)
HARBOR_PASS=$(vault kv get -field=password secret/ci/harbor)
# Masquer dans les logs
echo "::add-mask::${HARBOR_PASS}"
podman login harbor.example.com \
-u "${HARBOR_USER}" -p "${HARBOR_PASS}"
Flux v2 : secrets chiffres avec SOPS¶
Flux v2 supporte nativement le chiffrement des secrets dans Git via SOPS (Mozilla). Les secrets sont chiffres dans le dépôt et dechiffres par le kustomize-controller au moment de l'application.
# Configurer SOPS avec age (recommande)
# .sops.yaml dans le depot de manifestes
creation_rules:
- path_regex: .*\.enc\.yaml$
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Chiffrer un secret
sops --encrypt --in-place overlays/production/secret.enc.yaml
# Le secret chiffre est commite dans Git en toute securite
git add overlays/production/secret.enc.yaml
git commit -m "chore: add encrypted database secret"
git push
# Kustomization avec decryption SOPS
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
decryption:
provider: sops
secretRef:
name: sops-age # Secret contenant la cle age
# Creer le secret contenant la cle de dechiffrement
cat age-key.txt | kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin
Alternative : External Secrets Operator¶
Pour les secrets stockes dans Vault et non dans Git, utiliser l'External Secrets Operator (ESO) :
# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-secrets
namespace: app-production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: myapp-secrets
creationPolicy: Owner
data:
- secretKey: DATABASE_URL
remoteRef:
key: secret/data/myapp/production
property: database_url
- secretKey: API_KEY
remoteRef:
key: secret/data/myapp/production
property: api_key
# cluster-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
serviceAccountRef:
name: external-secrets
namespace: external-secrets
SOPS vs External Secrets Operator
- SOPS : secrets chiffres dans Git, dechiffres par Flux au deploy. Ideal pour les secrets applicatifs geres par l'equipe dev (tout est dans Git)
- ESO : secrets restes dans Vault, synchronises vers des Kubernetes Secrets. Ideal pour les secrets d'infrastructure geres par l'equipe ops
Push d'images vers Harbor¶
Configuration du projet Harbor¶
# Creer un projet dans Harbor pour les images CI
curl -s -X POST \
-H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)" \
-H "Content-Type: application/json" \
-d '{
"project_name": "org",
"public": false,
"storage_limit": 53687091200,
"metadata": {
"auto_scan": "true",
"severity": "high",
"prevent_vul": "true"
}
}' \
"https://harbor.example.com/api/v2.0/projects"
Robot account pour le CI¶
# Creer un robot account avec permissions push uniquement
curl -s -X POST \
-H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-pusher",
"duration": -1,
"level": "project",
"permissions": [{
"namespace": "org",
"kind": "project",
"access": [
{"resource": "repository", "action": "push"},
{"resource": "repository", "action": "pull"},
{"resource": "tag", "action": "create"}
]
}]
}' \
"https://harbor.example.com/api/v2.0/projects/org/robots"
Workflow avec push Harbor¶
- name: Push to Harbor
run: |
IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
podman push "${IMAGE}"
# Tag latest si branche main
if [ "${GITHUB_REF}" = "refs/heads/main" ]; then
podman tag "${IMAGE}" "${REGISTRY}/${IMAGE_NAME}:latest"
podman push "${REGISTRY}/${IMAGE_NAME}:latest"
fi
Quality gate avec SonarQube¶
Integration dans le workflow¶
- name: SonarQube Analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: https://sonar.example.com
run: |
sonar-scanner \
-Dsonar.projectKey=myapp \
-Dsonar.sources=. \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.token=${SONAR_TOKEN} \
-Dsonar.qualitygate.wait=true \
-Dsonar.qualitygate.timeout=300
Quality gate comme condition de merge¶
Configurer un webhook SonarQube vers Gitea pour mettre a jour le statut du commit :
# Proprietes du projet SonarQube (sonar-project.properties)
sonar.projectKey=myapp
sonar.projectName=My Application
sonar.sources=src
sonar.tests=tests
sonar.language=go
sonar.go.coverage.reportPaths=coverage.out
sonar.qualitygate.wait=true
Quality gate bloquante
Avec sonar.qualitygate.wait=true, le step échoue si le quality gate n'est pas passe. Combine avec la protection de branche Gitea, cela empeche le merge de code qui ne respecte pas les standards de qualité.
Scan de sécurité avec Trivy¶
Scan d'image dans le pipeline¶
- name: Scan image with Trivy
run: |
trivy image \
--exit-code 1 \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--format table \
--output trivy-report.txt \
${REGISTRY}/${IMAGE_NAME}:${TAG}
- name: Upload Trivy report
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-report
path: trivy-report.txt
Scan du filesystem (IaC, dependencies)¶
- name: Scan filesystem
run: |
# Scan des dependances (go.sum, package-lock.json, etc.)
trivy fs --exit-code 1 --severity HIGH,CRITICAL .
# Scan des fichiers IaC (Dockerfile, Kubernetes manifests)
trivy config --exit-code 1 --severity HIGH,CRITICAL .
Trivy integre dans Harbor¶
Harbor peut scanner automatiquement les images à chaque push (configure dans le projet Harbor avec auto_scan: true). Le pipeline peut alors vérifier le résultat :
- name: Check Harbor scan result
run: |
# Attendre le scan Harbor
sleep 30
VULN=$(curl -s \
-H "Authorization: Basic ${HARBOR_AUTH}" \
"https://harbor.example.com/api/v2.0/projects/org/repositories/myapp/artifacts/${TAG}?with_scan_overview=true" \
| jq '.scan_overview."application/vnd.security.vulnerability.report; version=1.1".severity')
if [ "${VULN}" = "\"Critical\"" ]; then
echo "Critical vulnerabilities found — blocking deployment"
exit 1
fi
Notifications Flux v2¶
Flux dispose d'un notification-controller intégré qui gère les alertes et les webhooks entrants.
Provider Mattermost¶
# clusters/production/notifications/provider-mattermost.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: mattermost
namespace: flux-system
spec:
type: mattermost
channel: cd-alerts
address: https://mattermost.example.com/hooks/xxxx
Alertes sur echec de reconciliation¶
# clusters/production/notifications/alert-cd.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: cd-alerts
namespace: flux-system
spec:
providerRef:
name: mattermost
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
- kind: HelmRelease
name: "*"
- kind: GitRepository
name: "*"
summary: "Flux reconciliation failure"
Alertes sur tous les événements (info + error)¶
# clusters/production/notifications/alert-all.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: cd-all-events
namespace: flux-system
spec:
providerRef:
name: mattermost
eventSeverity: info
eventSources:
- kind: Kustomization
name: myapp-production
- kind: HelmRelease
name: myapp
Receiver pour webhooks entrants (Gitea)¶
Le Receiver permet à Gitea de notifier Flux d'un changement immédiatement, sans attendre le poll :
# clusters/production/notifications/receiver-gitea.yaml
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: gitea-webhook
namespace: flux-system
spec:
type: generic
secretRef:
name: receiver-token
resources:
- kind: GitRepository
name: manifests
# Creer le secret du receiver
kubectl -n flux-system create secret generic receiver-token \
--from-literal=token=$(head -c 32 /dev/urandom | base64)
# Recuperer l'URL du webhook
kubectl -n flux-system get receiver gitea-webhook -o jsonpath='{.status.webhookPath}'
# Resultat : /hook/xxxxxxxxxxxxxxxxx
# Configurer ce path comme webhook dans Gitea :
# Repo > Settings > Webhooks > Add > POST https://flux-webhook.example.com/hook/xxx
Notifications CI sur echec¶
Mattermost sur echec¶
# Dans le workflow Gitea Actions
- name: Notify Mattermost on failure
if: failure()
run: |
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{
"channel": "ci-alerts",
"username": "Gitea CI",
"icon_url": "https://gitea.io/images/gitea.png",
"text": "### :red_circle: Pipeline echoue\n| Detail | Valeur |\n|---|---|\n| Repo | '"${{ github.repository }}"' |\n| Branche | '"${{ github.ref_name }}"' |\n| Commit | '"${{ github.sha }}"' |\n| Auteur | '"${{ github.actor }}"' |\n| [Voir le pipeline]('"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"') |"
}' \
"${{ secrets.MATTERMOST_WEBHOOK_URL }}"
Notification mail sur echec¶
- name: Send email on failure
if: failure()
run: |
curl -s --url "smtps://smtp.example.com:465" \
--ssl-reqd \
--mail-from "ci@example.com" \
--mail-rcpt "${{ github.event.pusher.email }}" \
--user "ci@example.com:${{ secrets.SMTP_PASSWORD }}" \
-T - <<EOF
From: CI Pipeline <ci@example.com>
To: ${{ github.event.pusher.email }}
Subject: [CI] Pipeline echoue — ${{ github.repository }}
Le pipeline CI a echoue sur le commit ${{ github.sha }}.
Branche : ${{ github.ref_name }}
Voir : ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
EOF
Dashboards Grafana¶
Metriques a collecter¶
| Metrique | Source | Indicateur |
|---|---|---|
| Duree du pipeline | Gitea Actions | Temps moyen build-to-deploy |
| Taux de succès | Gitea Actions | % de pipelines verts |
| Fréquence de déploiement | Flux v2 | Nombre de reconciliations par jour |
| MTTR (Mean Time To Repair) | Flux v2 | Temps entre echec et correction |
| Drift detection | Flux v2 | Nombre de corrections de drift par jour |
Flux v2 expose des metriques Prometheus¶
Les controllers Flux exposent des metriques Prometheus nativement :
# ServiceMonitor pour Flux v2
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: flux-system
namespace: flux-system
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: flux
endpoints:
- port: http-prom
interval: 30s
Metriques Flux cles¶
| Metrique Prometheus | Description |
|---|---|
gotk_reconcile_duration_seconds | Duree de reconciliation par ressource |
gotk_reconcile_condition | Condition de chaque ressource (Ready, Stalled...) |
controller_runtime_reconcile_total | Nombre total de reconciliations |
controller_runtime_reconcile_errors_total | Nombre d'erreurs de reconciliation |
Dashboard Grafana recommande¶
Importer le dashboard communautaire Flux : ID 16714 (Flux Cluster Stats).
Pour Gitea Actions, créer un dashboard custom base sur les webhooks Gitea ou l'API :