Introduction
GitLab CI/CD est bien plus qu'un outil qui exécute des scripts après un push. Ses capacités avancées permettent aux équipes de modéliser des flux de livraison complexes, de protéger les valeurs sensibles, de publier avec confiance et de récupérer rapidement lorsqu'un pipeline échoue. Cet article parcourt les concepts avancés qui comptent en production : architecture des exécuteurs, contrôle des pipelines avec les DAG et les règles, environnements et sécurité des déploiements, gestion des artefacts et du cache, analyses de sécurité et diagnostic des échecs. Chaque section inclut des commandes concrètes, des exemples de configuration, les résultats attendus et des conseils de récupération.
Ce contenu s'adresse aux développeurs, consultants DevOps et équipes techniques de startups qui comprennent déjà la syntaxe de base du fichier .gitlab-ci.yml et ont exécuté au moins un pipeline. L'accent est mis sur la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, garder les secrets hors des journaux et de la configuration, vérifier chaque résultat et savoir comment revenir en arrière en cas de problème.
Inventaire des versions et de l'environnement
Avant de modifier un paramètre avancé, sachez exactement ce que vous exécutez. Notez la version de l'instance GitLab, les versions des exécuteurs, les types d'exécuteurs et les détails d'infrastructure pertinents. Cela évite de diagnostiquer le mauvais composant et rend les décisions de récupération précises.
Vérifier la version de l'instance GitLab
Exécutez un appel API en lecture seule depuis un poste de travail ayant accès au réseau à l'instance GitLab. Remplacez <https://gitlab.example.com> par l'URL de votre instance et <votre-jeton-d-acces-personnel> par un jeton disposant au moins du périmètre read_api.
curl --header "PRIVATE-TOKEN: <votre-jeton-d-acces-personnel>" "<https://gitlab.example.com/api/v4/version>"
Résultat attendu pour GitLab 16.10 :
{
"version": "16.10.2-ee",
"revision": "a1b2c3d4e5f"
}
Si l'API renvoie 401 Unauthorized, vérifiez la portée et l'expiration du jeton. Ne collez jamais le jeton dans un document partagé ; utilisez un gestionnaire de secrets ou une variable d'environnement.
Identifier les exécuteurs et les exécuteurs
Listez les exécuteurs enregistrés sur un projet. Accédez à Réglages > CI/CD > Runners dans l'interface web, ou utilisez l'API :
curl --header "PRIVATE-TOKEN: <votre-jeton-d-acces-personnel>" "<https://gitlab.example.com/api/v4/projects/<id_projet>/runners"
La réponse attendue inclut les identifiants des exécuteurs, leurs descriptions, leur statut en ligne et les types d'exécuteurs (shell, docker, kubernetes, etc.). Notez l'exécuteur pour chaque exécuteur, car les fonctionnalités avancées comme Docker-in-Docker, la planification des pods Kubernetes et le comportement du cache en dépendent.
Inventorier les dépendances
Pour un pipeline de production typique, notez les éléments suivants dans un runbook ou une page wiki :
- Version de l'instance GitLab (par exemple, 16.10.2-ee)
- Version de l'exécuteur (
gitlab-runner --versionsur chaque hôte d'exécuteur) - Type d'exécuteur pour chaque exécuteur (par exemple,
docker,kubernetes) - Version de Docker sur les hôtes d'exécuteurs (
docker --version) - Version du cluster Kubernetes si vous utilisez l'exécuteur Kubernetes (
kubectl version --short) - Services externes utilisés par les jobs (dépôts d'artefacts, fournisseurs cloud, points de terminaison de surveillance)
Maintenez cet inventaire à jour après les mises à niveau. Une incompatibilité de version connue entre GitLab et les exécuteurs peut provoquer des échecs subtils, comme des mots-clés de configuration non reconnus ou une incompatibilité de cache.
Architecture des exécuteurs et sélection des exécuteurs
GitLab Runner exécute les jobs. Choisir le bon exécuteur affecte l'isolation, les performances et la sécurité. Les exécuteurs les plus courants pour les configurations avancées sont Docker, Docker Machine (obsolète mais encore présent), Kubernetes et Shell.
Exécuteur Docker avec mode privilégié
Pour les jobs qui doivent construire des images de conteneurs, Docker-in-Docker (DinD) est courant mais nécessite une configuration minutieuse. L'exécuteur doit être enregistré avec --docker-privileged et le job doit définir DOCKER_TLS_CERTDIR de manière appropriée.
Exemple d'enregistrement de l'exécuteur :
gitlab-runner register \
--non-interactive \
--url "<https://gitlab.example.com>" \
--registration-token "<jeton-d-enregistrement-de-l-executeur>" \
--executor "docker" \
--docker-image "docker:24.0.5" \
--docker-privileged \
--docker-volumes "/certs/client"
Dans .gitlab-ci.yml :
build-image:
image: docker:24.0.5
services:
- docker:24.0.5-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t registry.example.com/myapp:$CI_COMMIT_SHA .
- docker push registry.example.com/myapp:$CI_COMMIT_SHA
Résultat attendu : le job s'exécute dans un conteneur docker:24.0.5, démarre un conteneur DinD frère et les commandes docker se connectent au démon DinD via des certificats TLS montés dans /certs/client.
Note de sécurité : DinD avec mode privilégié donne au conteneur de construction des capacités élevées sur l'hôte de l'exécuteur. Préférez utiliser kaniko ou buildah pour des constructions sans privilège dans des environnements d'exécuteurs partagés, ou utilisez un pool d'exécuteurs dédié pour les jobs DinD.
Exécuteur Kubernetes
L'exécuteur Kubernetes crée un pod pour chaque job. La configuration avancée inclut des sélecteurs de nœuds, des limites de ressources et le mappage de comptes de service. Exemple de section config.toml de l'exécuteur :
[[runners]]
executor = "kubernetes"
[runners.kubernetes]
namespace = "gitlab-runners"
poll_timeout = 600
cpu_limit = "1"
memory_limit = "2Gi"
[runners.kubernetes.node_selector]
"node-role.kubernetes.io/ci" = "true"
[runners.kubernetes.volumes]
[[runners.kubernetes.volumes.host_path]]
name = "docker-sock"
mount_path = "/var/run/docker.sock"
host_path = "/var/run/docker.sock"
Cette configuration planifie les jobs uniquement sur des nœuds étiquetés node-role.kubernetes.io/ci=true, limite chaque pod de job à 1 CPU et 2 Go de mémoire, et monte le socket Docker de l'hôte pour les jobs qui nécessitent la gestion de conteneurs. Monter le socket Docker affaiblit l'isolation ; évitez-le sauf si cela est strictement nécessaire et comprenez les implications de sécurité.
Pour vérifier qu'un exécuteur Kubernetes fonctionne, exécutez un job simple et inspectez les événements du pod :
kubectl get pods -n gitlab-runners -l job-name=<nom-du-job>
kubectl describe pod <nom-du-pod> -n gitlab-runners
Recherchez un pull d'image réussi, le démarrage du conteneur et aucun événement lié aux ressources comme FailedScheduling ou OOMKilled.
Exécuteur Shell pour des charges de travail spécialisées
L'exécuteur Shell exécute les jobs directement sur l'hôte de l'exécuteur. Il convient lorsque les jobs nécessitent l'accès à des outils, du matériel ou des systèmes de fichiers spécifiques à l'hôte. Enregistrez avec :
gitlab-runner register --executor shell --url "<https://gitlab.example.com>" --registration-token "<jeton>"
Dans .gitlab-ci.yml, un job utilisant l'exécuteur Shell peut exécuter un script local :
deploy-local:
script:
- /usr/local/bin/deploy.sh --env staging
tags:
- shell-runner
Parce que les jobs Shell héritent de l'environnement de l'hôte de l'exécuteur, ils peuvent accidentellement laisser des artefacts, exposer des secrets ou affecter d'autres processus. Préférez les exécuteurs Docker ou Kubernetes pour l'isolation, sauf s'il y a une forte exigence d'accès à l'hôte.
Contrôle des pipelines : DAG, needs et rules
Les pipelines avancés utilisent des graphes acycliques dirigés (DAG) pour exécuter des jobs en parallèle lorsque cela est sûr, et des règles (rules) pour décider quand un job doit s'exécuter. Cela réduit la durée du pipeline et évite les travaux inutiles.
Utiliser needs pour créer un DAG
Par défaut, les jobs s'exécutent séquentiellement par étapes. Le mot-clé needs permet à un job de démarrer dès que ses dépendances spécifiées sont terminées, en ignorant l'ordre des étapes.
stages:
- build
- test
- deploy
build-app:
stage: build
script: make build
test-unit:
stage: test
needs: ["build-app"]
script: make test-unit
test-integration:
stage: test
needs: ["build-app"]
script: make test-integration
deploy-staging:
stage: deploy
needs: ["test-unit", "test-integration"]
script: make deploy-staging
Dans cet exemple, test-unit et test-integration dépendent uniquement de build-app et peuvent s'exécuter en parallèle. deploy-staging attend que les deux tests réussissent, pas l'ensemble de l'étape test. Le DAG résultant réduit le temps total du pipeline lorsque les tests sont indépendants.
Comportement attendu : si test-unit échoue, test-integration continue car il n'a pas besoin de test-unit. Seul deploy-staging est bloqué. C'est une différence clé par rapport à l'exécution par étapes, où un échec dans un job bloquerait toute l'étape.
Exécution conditionnelle avec rules
Le mot-clé rules est le remplacement moderne de only et except. Il évalue les conditions dans l'ordre et s'arrête à la première correspondance.
deploy-production:
stage: deploy
script: make deploy-production
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
when: manual
allow_failure: false
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
when: auto
- when: never
Interprétation :
- Les poussées vers
maincréent un job de déploiement manuel qui doit être cliqué dans l'interface web. - Les tags correspondant à la version sémantique (par exemple,
v1.2.3) déclenchent un déploiement automatique. - Tous les autres cas ignorent le job.
Erreur courante : oublier le when: never final. Sans lui, les conditions non appariées font que le job est inclus avec le comportement par défaut, entraînant souvent des exécutions inattendues.
Pipelines enfants dynamiques
Pour les monorepos ou les flux de travail complexes, générez des pipelines enfants dynamiquement à partir d'un fichier de configuration produit par un job.
generate-config:
stage: build
script:
- ./generate-pipeline-config.sh > generated-config.yml
artifacts:
paths:
- generated-config.yml
child-pipeline:
stage: test
trigger:
include:
- artifact: generated-config.yml
job: generate-config
Le job generate-config écrit un fragment valide de .gitlab-ci.yml dans generated-config.yml. Le job child-pipeline consomme cet artefact et déclenche un pipeline enfant. Cela permet des définitions de pipeline par répertoire ou par service sans maintenir un fichier monolithique.
Pour déboguer la configuration générée, téléchargez l'artefact depuis la page du pipeline et exécutez gitlab-ci-lint si disponible localement, ou utilisez le point de terminaison de l'API CI Lint :
curl --header "PRIVATE-TOKEN: <jeton>" --header "Content-Type: application/json" --data '{"content": "<yaml collé>"}' "<https://gitlab.example.com/api/v4/ci/lint>"
Environnements, déploiements et sécurité des versions
Les environnements GitLab représentent où le code est déployé. L'utilisation avancée inclut des environnements protégés, l'approbation des déploiements et le retour en arrière via la réexécution d'anciens pipelines.
Définir un environnement
deploy-staging:
stage: deploy
environment:
name: staging
url: <https://staging.example.com>
script:
- kubectl apply -f staging.yaml
Après l'exécution du job, l'environnement staging apparaît dans Opérations > Environnements avec un lien vers l'URL. GitLab suit les déploiements, y compris qui a déployé, quel commit et quand.
Environnements protégés et approbations
Protégez un environnement de production en allant dans Réglages > CI/CD > Environnements protégés. Sélectionnez l'environnement et choisissez quels utilisateurs ou rôles peuvent déployer. Dans .gitlab-ci.yml, ces jobs ne peuvent être déployés que par des utilisateurs autorisés même si le pipeline s'exécute automatiquement.
Exemple : exiger une action manuelle d'un mainteneur pour les déploiements en production.
deploy-production:
stage: deploy
environment:
name: production
url: <https://prod.example.com>
script:
- ./deploy-production.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
allow_failure: false
Avec l'environnement protégé et limité aux mainteneurs, un développeur ne peut pas cliquer sur le job manuel ; seul un mainteneur le peut. Cela impose la séparation des tâches.
Stratégie de retour en arrière
GitLab ne revient pas automatiquement en arrière, mais vous pouvez réexécuter un job de déploiement précédent depuis la page de l'environnement. Alternativement, gardez un job de retour en arrière dédié :
rollback-production:
stage: deploy
environment:
name: production
action: stop
script:
- ./rollback-production.sh --to $PREVIOUS_VERSION
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
Définissez PREVIOUS_VERSION comme variable CI/CD avant de déclencher le job de retour en arrière. Pour les déploiements Kubernetes, kubectl rollout undo deployment/myapp est une commande de retour rapide qui peut être enveloppée dans un job.
Artefacts, mise en cache et dépendances
Les artefacts sont des fichiers générés par un job qui sont transmis aux jobs suivants, tandis que les caches servent aux dépendances qui doivent être réutilisées entre les pipelines pour gagner en rapidité. Une mauvaise utilisation entraîne un stockage gonflé ou des constructions incorrectes.
Gestion des artefacts
Définissez des artefacts avec expiration pour empêcher une croissance illimitée du stockage.
build-assets:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
reports:
junit: test-results.xml
Le répertoire dist/ est transmis aux jobs qui déclarent dependencies: [build-assets] ou à l'étape suivante par défaut. Le rapport JUnit apparaît dans les widgets de demande de fusion pour la visualisation des tests. Une expiration d'une semaine maintient le stockage gérable.
Pour nettoyer manuellement les anciens artefacts, utilisez l'API :
curl --request DELETE --header "PRIVATE-TOKEN: <jeton>" "<https://gitlab.example.com/api/v4/projects/<id_projet>/artifacts"
Cela supprime tous les artefacts du projet. À utiliser avec prudence ; il n'y a pas de granularité par artefact dans ce point de terminaison.
Bonnes pratiques de cache
Mettez en cache les dépendances coûteuses à récupérer, comme les paquets npm ou Maven. Utilisez une clé qui change lorsque les dépendances changent.
build-node:
stage: build
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
script:
- npm ci
La clé de cache change chaque fois que package-lock.json change, garantissant une invalidation correcte du cache. Ne mettez pas en cache les sorties de construction destinées à être des artefacts ; utilisez artifacts pour celles-ci.
Les exécuteurs distribués nécessitent un cache partagé. Configurez un serveur de cache pour les exécuteurs Docker et Kubernetes, ou utilisez un stockage cloud comme S3. Dans config.toml :
[runners.cache]
Type = "s3"
[runners.cache.s3]
ServerAddress = "s3.amazonaws.com"
AccessKey = "<cle-d-acces>"
SecretKey = "<cle-secrete>"
BucketName = "gitlab-runner-cache"
Stockez la clé d'accès et le secret dans des variables d'environnement ou un gestionnaire de secrets, pas en clair dans config.toml. GitLab Runner prend en charge AccessKey et SecretKey comme variables d'environnement.
Sécurité : secrets, analyses et bonnes pratiques
Le CI/CD avancé doit gérer les secrets en toute sécurité et intégrer les analyses de sécurité sans ralentir excessivement le développement.
Gestion des secrets
Ne mettez jamais de secrets dans .gitlab-ci.yml ni ne les committez au dépôt. Utilisez des variables CI/CD avec protection et masquage.
Créez une variable dans Réglages > CI/CD > Variables. Marquez-la comme protégée si elle ne doit être disponible que sur les branches et tags protégés, et masquée si elle doit être cachée des journaux de job.
Dans .gitlab-ci.yml :
deploy:
script:
- echo "Deploying with token ${DEPLOY_TOKEN}"
Si DEPLOY_TOKEN est masquée, la sortie affiche [MASKED] lorsque la valeur apparaîtrait. Évitez d'utiliser des secrets dans les arguments de commande où la liste des processus pourrait les exposer (par exemple, ps aux). Préférez les variables d'environnement et les secrets basés sur des fichiers.
Pour une gestion plus avancée des secrets, intégrez avec HashiCorp Vault en utilisant le mot-clé vault (GitLab 15.0+). Exemple :
job:
id_tokens:
ID_TOKEN:
aud: <https://gitlab.example.com>
secrets:
DATABASE_PASSWORD:
vault: secret/data/production/db/password
file: false
Cela nécessite de configurer un serveur Vault et l'instance GitLab pour lui faire confiance. Le secret est injecté dans l'environnement du job, sans être stocké dans GitLab.
Modèles d'analyse de sécurité
GitLab fournit SAST, l'analyse des dépendances, la détection des secrets, l'analyse des conteneurs et DAST. Incluez-les en utilisant include et template.
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
stages:
- test
sast:
stage: test
variables:
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
Exécutez ces jobs dans les pipelines de demande de fusion pour détecter les vulnérabilités avant la fusion. Les résultats apparaissent dans le widget de demande de fusion et le tableau de bord de sécurité.
Examinez rapidement les résultats. Les faux positifs sont courants ; configurez des exclusions et des seuils de gravité selon l'appétit au risque de votre équipe.
Modes de défaillance et récupération
Même avec une configuration avancée, les pipelines échouent. Savoir diagnostiquer et récupérer est essentiel.
Défaillance courante : job bloqué en attente
Si un job reste en attente, vérifiez qu'un exécuteur avec des tags correspondants est en ligne. Dans le journal du job, les premières lignes montrent le processus de sélection de l'exécuteur.
Commandes de diagnostic :
gitlab-runner list
systemctl status gitlab-runner
Si l'exécuteur est hors ligne, redémarrez-le :
sudo systemctl restart gitlab-runner
Si l'exécuteur est en ligne mais ne prend pas de jobs, vérifiez que les tags du job correspondent aux tags de l'exécuteur. Un job avec le tag docker ne sera pas pris par un exécuteur taggé shell.
Récupération : modifiez le tag du job pour correspondre à un exécuteur existant, ou enregistrez un nouvel exécuteur avec le tag requis.
Défaillance courante : la construction Docker échoue avec Cannot connect to the Docker daemon
Cela se produit lorsque le service DinD n'est pas disponible ou que DOCKER_TLS_CERTDIR est mal configuré.
Vérifiez le journal du job pour les lignes de démarrage du service. Dans le script du job, ajoutez un test de connectivité :
docker info
La sortie attendue inclut la version du serveur et le pilote de stockage. Si cela échoue, assurez-vous que l'exécuteur a --docker-privileged et que le job inclut services: - docker:dind et variables: DOCKER_TLS_CERTDIR: "/certs".
Pour des alternatives sans privilège, remplacez DinD par kaniko :
build:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination registry.example.com/myapp:$CI_COMMIT_SHA
Cela élimine le besoin de mode privilégié.
Défaillance courante : échec ou expiration du téléversement d'artefacts
Si un job en aval signale des artefacts manquants, vérifiez les chemins d'artefacts et l'expiration du job en amont. Des chemins incorrects ou des liens symboliques peuvent entraîner des archives d'artefacts vides.
Dans le journal du job en amont, recherchez « Uploading artifacts... » et vérifiez les fichiers listés. Utilisez ls -la dans le script pour confirmer que les fichiers existent.
Si les artefacts ont expiré, augmentez expire_in ou réexécutez le pipeline. Pour stocker définitivement les artefacts, utilisez le registre de paquets GitLab ou un dépôt d'artefacts externe au lieu des artefacts CI.
Stratégie de récupération : retour en arrière du pipeline
Pour les pipelines de déploiement, les environnements GitLab permettent de réexécuter les jobs de déploiement précédents. Sur la page de l'environnement, sélectionnez le déploiement souhaité et cliquez sur « Rollback » (si disponible). Cela réexécute le job avec les mêmes variables que le déploiement d'origine. Assurez-vous que les scripts de retour en arrière sont idempotents.
Liste de contrôle opérationnelle
Avant de modifier une configuration GitLab CI/CD avancée, complétez la liste de contrôle suivante :
- Notez la version de l'instance GitLab et les versions des exécuteurs (
gitlab-runner --version). - Inventoriez les exécuteurs et leurs capacités (privilégiés, Kubernetes, shell).
- Confirmez que tous les secrets sont stockés dans des variables CI/CD protégées et masquées, pas dans le dépôt.
- Examinez le fichier
.gitlab-ci.ymlexistant pour les définitionsrules,needsetenvironment. - Assurez-vous que les artefacts ont des valeurs
expire_inappropriées et que les caches ont des clés correctes. - Vérifiez les tags des exécuteurs et leur disponibilité pour chaque job.
- Validez la configuration du pipeline en utilisant l'API CI Lint avant de pousser les modifications.
- Définissez des signaux d'échec : quels journaux ou métriques indiquent un déploiement échoué ?
- Préparez une procédure de retour en arrière et testez-la d'abord dans un environnement de préproduction.
- Limitez la modification à un seul composant à la fois avec une étape de vérification claire.
Exemple de validation avant modification en utilisant l'API CI Lint :
curl --header "PRIVATE-TOKEN: <jeton>" --header "Content-Type: application/json" --data '{"content": "<yaml collé>"}' "<https://gitlab.example.com/api/v4/ci/lint>"
Sortie attendue : {"valid":true,"errors":[]} avec des avertissements optionnels.
Après avoir appliqué la modification, surveillez la première exécution du pipeline. Comparez la durée, les statuts des jobs et les tailles d'artefacts avec les exécutions précédentes. Si quelque chose se casse, annulez la modification en utilisant le contrôle de version, pas en patchant dans l'interface.
Conclusion
Les fonctionnalités avancées de GitLab CI/CD donnent aux équipes le pouvoir de construire des pipelines de livraison sophistiqués, mais ce pouvoir exige une discipline opérationnelle. Les exécuteurs doivent être sélectionnés pour l'isolation et les capacités ; les DAG de pipeline et les règles doivent être conçus pour la rapidité et la sécurité ; les environnements doivent être protégés et réversibles ; les artefacts et les caches doivent être gérés pour l'efficacité et la justesse ; et les analyses de sécurité doivent être intégrées sans créer de bruit.
Commencez par une amélioration à faible risque. Par exemple, introduisez needs pour paralléliser des jobs de test indépendants dans un pipeline de préproduction. Notez la durée du pipeline avant et après, vérifiez que les artefacts et les dépendances restent corrects, puis adoptez progressivement des modèles plus avancés.
Un flux de travail GitLab CI/CD fiable rend les échecs visibles, protège les valeurs sensibles, limite les modifications à la portée prévue et définit des étapes de récupération avant qu'un incident ne survienne. Utilisez cet article comme référence pour auditer votre configuration actuelle et planifier des améliorations incrémentales avec confiance.