Lorsque les jobs GitLab CI/CD n'arrivent pas à atteindre les dépôts de paquets, les magasins d'artefacts, les registres de conteneurs ou les API internes, la cause racine est souvent le réseau. DNS, ports, routage, proxys, TLS et pare-feu peuvent se comporter différemment à l'intérieur d'un runner que sur une station de développement. Ce guide propose des étapes pratiques et sûres pour diagnostiquer et corriger ces différences avec des exemples reproductibles. Vous créerez un petit job de diagnostic en lecture seule, comparerez les sorties aux résultats attendus et appliquerez des changements ciblés sans risquer d'affecter d'autres parties de votre pipeline.

L'approche est simple : capturer un inventaire clair, exécuter un test pilote étroit, observer le chemin réseau et corriger uniquement ce que vos preuves justifient. En gardant le pilote petit et mesurable, vous pouvez itérer rapidement et réduire le retravail.

## Inventaire des versions et de l'environnement

Avant de toucher à la configuration, répondez à ces questions. Un inventaire cohérent évite de poursuivre la mauvaise couche.

### Ce qu'il faut enregistrer

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Élément</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Exemple de valeur</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Édition et version de GitLab</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">GitLab.com SaaS ou autogéré 17.x</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Version de GitLab Runner</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">17.x (exécuteur shell, Docker ou Kubernetes)</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">OS et architecture du runner</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ubuntu 22.04 x86_64 ; Windows Server 2022</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Type d&#39;exécuteur</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">shell, docker, kubernetes</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Zone réseau</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">LAN bureau, VLAN 120 DC, namespace k8s ci</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Résolveurs DNS utilisés par les jobs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">10.0.0.10, 10.0.0.11</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Paramètres de proxy dans les jobs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">http_proxy, https_proxy, NO_PROXY</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">IP de sortie attendue(s)</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">IP NAT 203.0.113.45 (exemple construit)</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Propriétaires du pare-feu et fenêtre de changement</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">NetSec d&#39;astreinte, quotidien 16h00-17h00</td></tr></tbody>
</table>
</div>

### Comment le collecter (exemples Linux)

- Version du runner et exécuteur (exécuter sur l'hôte du runner) :

gitlab-runner --version
cat /etc/gitlab-runner/config.toml 

- Configuration DNS visible pour les jobs (à l'intérieur du job) :

 cat /etc/resolv.conf || true
getent hosts gitlab.com || true 

- Variables d'environnement liées au proxy (à l'intérieur du job) :

 ( env | grep -iE '^(http|https|no)_proxy=' || true ) | sort 

- Chemins réseau (sur l'hôte du runner ou dans le job si autorisé) :

 ip addr show
ip route show
ss -tupan | head -n 50 

### Extraits pour runner Windows

- Résolution DNS :

 Resolve-DnsName example.com 

- Accessibilité du port :

 Test-NetConnection example.com -Port 443 
 Documentez ces constats avec les identifiants de job afin de pouvoir comparer les exécutions réussies et celles en échec.

## Chemin de configuration sûr

La méthode la plus sûre pour dépanner consiste à introduire un petit job de diagnostic en lecture seule qui sonde uniquement les points de terminaison qui vous intéressent. Évitez les modifications globales tant que le job n'a pas prouvé ce qui est cassé.

## 1) Ajouter un job de diagnostic

Le job suivant utilise des outils Linux standards. Il ne modifie pas l'état du système et sort avec un code non nul en cas d'échec évident.

# .gitlab-ci.yml (exemple construit)
stages:
 - verify

net-verify:
 stage: verify
 image: alpine:3.20 # pour l'exécuteur docker ; retirer 'image' pour l'exécuteur shell
 variables:
 # Définir les proxys ici seulement si votre environnement l'exige
 # http_proxy: "http://proxy.internal:3128"
 # https_proxy: "http://proxy.internal:3128"
 # NO_PROXY: "registry.internal,.svc,.cluster.local,10.0.0.0/8,127.0.0.1,localhost"
 before_script:
 - apk add --no-cache bind-tools curl openssl iproute2 busybox-extras
 script:
 - set -euxo pipefail
 - echo "== DNS =="
 - cat /etc/resolv.conf || true
 - dig +short registry.internal || true
 - echo "== Accessibilité TCP 443 =="
 - nc -vz -w5 registry.internal 443 || true
 - echo "== Négociation TLS et SNI =="
 - echo | openssl s_client -connect registry.internal:443 -servername registry.internal -brief 2>/dev/null || true
 - echo "== HTTP HEAD avec chronométrage =="
 - curl -fsSILv https://registry.internal -o /dev/null -w 'code=%{http_code} connect=%{time_connect} ttfb=%%{time_starttransfer}\n'
 - echo "== Routes et adresses =="
 - ip addr show || true
 - ip route show || true 
 Notes :

- Remplacez registry.internal par votre cible réelle (magasin d'artefacts, registre, API).

- Si vous utilisez un exécuteur shell sur un hôte verrouillé, installez les outils équivalents via le gestionnaire de paquets de l'OS et lancez-les dans le script du job.

- Gardez les secrets hors des lignes de commande. Utilisez des variables CI masquées lorsque l'authentification est requise pour les vérifications HTTP(S).

## 2) Vérifier le DNS en premier

Une grande partie des échecs de connectivité CI proviennent de problèmes de résolution de noms. Validez :

- Le job résout les mêmes noms que votre poste de travail.

- Les types de réponse (A vs AAAA) sont cohérents avec votre réseau.

- Le domaine de recherche ne réécrit pas les noms de manière inattendue.

Diagnostics :

getent hosts internal.api.local || true
dig +search internal.api A AAAA +timeout=2 +tries=1 || true 
 Si des réponses IPv6 sont retournées mais que votre réseau ne transporte pas l'IPv6 de bout en bout, privilégiez l'IPv4 pendant l'investigation :

curl -4 -fsSIL https://internal.api.local 

## 3) Valider les ports et le TLS

- Ports : confirmez que le port cible est ouvert depuis le job.

 nc -vz -w3 registry.internal 443 || true 

- TLS : confirmez le SNI, la chaîne de certificats et le protocole. Recherchez les erreurs de vérification de certificat ou des émetteurs inattendus (par exemple, un proxy d'interception d'entreprise).

 echo | openssl s_client -connect registry.internal:443 -servername registry.internal -showcerts 2>/dev/null | awk '/Server certificate/ {p=1} p{print}' 
 Si une autorité de certification d'entreprise signe les certificats, installez-la dans l'environnement du runner plutôt que de désactiver la vérification. Par exemple, sur des runners basés Debian/Ubuntu :

# Écrire corporate-ca.crt via une variable CI protégée ou une image préconfigurée, puis :
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates 
 Évitez de désactiver la vérification TLS globalement. Si vous devez contourner temporairement la vérification pour confirmer que seule la confiance pose problème, limitez-le à une seule commande et rétablissez dès que l'AC est installée.

## 4) Inspecter le routage et le NAT

- Confirmez qu'une route par défaut existe et est correcte :

ip route show 

- Tracez le chemin et repérez les trous noirs :

 traceroute -n registry.internal || true 

- Si votre organisation utilise le NAT, demandez à NetSec quelle IP de sortie votre runner utilise afin qu'ils puissent consulter les journaux du pare-feu. Gardez ce processus en dehors du job CI ; ne comptez pas sur des appels à des endpoints externes "what-is-my-ip" depuis CI.

## 5) Gérer les proxys délibérément

 Si vos jobs doivent traverser un proxy, définissez les variables d'environnement au niveau le plus étroit qui fonctionne :

variables:
 http_proxy: "http://proxy.internal:3128"
 https_proxy: "http://proxy.internal:3128"
 NO_PROXY: "registry.internal,.svc,.cluster.local,10.0.0.0/8,127.0.0.1,localhost" 
 Conseils :

- NO_PROXY accepte des suffixes de domaine comme .example.com . Listez les noms d'hôtes internes et les adresses de boucle locale pour éviter de proxyfier les services locaux.

- Si seuls certains jobs ont besoin du proxy, définissez les variables au niveau du job, pas globalement.

- Certains outils lisent les variantes minuscules et majuscules ; définissez les deux en cas de doute.

## 6) Notes spécifiques à l'exécuteur

- Exécuteur shell : le job partage le réseau de l'hôte. Les pare-feu de l'hôte (ufw, nftables, Pare-feu Windows) affectent directement la connectivité.

- Exécuteur Docker : le job s'exécute derrière un pont défini par l'utilisateur. La résolution de noms, le MTU et le NAT sortant sont contrôlés par le pont Docker et l'hôte. Si vous devez ajouter des racines de confiance supplémentaires ou des correspondances d'hôtes, privilégiez la création d'image ou la configuration du runner plutôt que des drapeaux ad-hoc.

- Exécuteur Kubernetes : Les NetworkPolicies peuvent bloquer le trafic sortant. Vérifiez les règles de sortie du namespace, les politiques DNS et si le cluster utilise un proxy ou une passerelle de sortie.

## Vérification et diagnostics

Après chaque modification, relancez le job de diagnostic et comparez ses sorties aux attentes ci-dessous. Gardez les différences petites et centrées sur le point de terminaison que vous essayez d'atteindre.

## Résultats attendus par couche

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Couche</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Exemple sain</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">DNS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">dig</code> retourne A/AAAA pour la cible ; <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">getent hosts</code> affiche les IP attendues</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">TCP</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">nc -vz host 443</code> signale succeeded ; pas de timeout</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">TLS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">openssl s_client</code> montre une chaîne valide vers une racine de confiance ; le SNI correspond</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">HTTP</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">curl -I</code> retourne 200/302/401 selon la politique du point de terminaison ; les champs de temps sont faibles</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Route</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">ip route</code> affiche une route par défaut via la passerelle correcte ; <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">traceroute</code> progresse</td></tr></tbody>
</table>
</div>

## Exemples de sorties saines (exemples construits)

- DNS :

$ dig +short registry.internal
10.50.12.34 

- TCP :

 $ nc -vz -w3 registry.internal 443
registry.internal (10.50.12.34:443) open 

- TLS :

 $ echo | openssl s_client -connect registry.internal:443 -servername registry.internal -brief
Protocol : TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Server certificate
 subject=CN = registry.internal
 issuer=CN = Corp-Root-CA
 Verification: OK 

- Chronométrage HTTP :

 $ curl -fsSILv https://registry.internal -o /dev/null -w 'code=%{http_code} connect=%{time_connect} ttfb=%%{time_starttransfer}\n'
< HTTP/1.1 200 OK
code=200 connect=0.020 ttfb=0.085 
 Si les sorties ne correspondent pas aux attentes, revenez au Chemin de configuration sûr et ajustez uniquement la couche concernée.

## Modes d'échec et récupération

Cette section liste les symptômes courants, leurs causes probables et des correctifs sûrs que vous pouvez annuler.

## Symptômes courants et correctifs ciblés

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Symptôme</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Cause probable</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Premier correctif sûr</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">getent hosts</code> expire</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Mauvais serveur DNS ou UDP/53 bloqué</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Orienter le runner vers les bons résolveurs ; vérifier avec <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">dig</code></td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">nc</code> vers 443 expire</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pare-feu bloquant la sortie ou proxy requis</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ajouter les variables proxy au niveau du job ou demander une règle de pare-feu</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Échec de vérification TLS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">AC d&#39;entreprise manquante ou interception TLS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Installer l&#39;AC dans le magasin de confiance du runner ; éviter de désactiver la vérification</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">curl</code> résout seulement en IPv6</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">DNS à vue partagée, IPv6 non routé</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Privilégier l&#39;IPv4 (<code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">-4</code>) ou corriger la vue DNS pour CI</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">traceroute</code> s&#39;arrête au saut 1</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pare-feu local ou route par défaut incorrecte</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Corriger le pare-feu hôte ; confirmer la route par défaut et la passerelle</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">TTFB HTTP lent</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Limitation du proxy ou perte de paquets</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Contourner le proxy pour les hôtes internes via <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">NO_PROXY</code> ; réessayer en dehors des heures de pointe</td></tr></tbody>
</table>
</div>

## Annulation et récupération

- .gitlab-ci.yml :

- Gardez toutes les modifications de diagnostic dans un job verify dédié.

- Pour annuler, supprimez uniquement ce job ou les variables que vous avez introduites.

- Magasin de confiance du runner :

- Avant d'ajouter une AC d'entreprise, sauvegardez les répertoires de confiance (par exemple, /etc/ssl/certs et /usr/local/share/ca-certificates sur Debian/Ubuntu).

- Pour annuler, supprimez le fichier AC ajouté et lancez update-ca-certificates .

- Modifications de proxy :

- Supprimez les variables proxy au niveau du job pour revenir au comportement antérieur.

- Paramètres de l'exécuteur Docker ou Kubernetes :

- Sauvegardez la configuration du runner avant modifications.

- Revenez au fichier de configuration précédent et redémarrez le runner si les changements n'aident pas.

Relancez toujours le job de diagnostic après une annulation pour confirmer le retour à la ligne de base connue.

## Liste de contrôle opérationnelle

Utilisez cette liste pour assurer un dépannage cohérent et à faible risque.

- Inventaire

- Enregistrez les versions de GitLab et du Runner, l'exécuteur, l'OS, les résolveurs DNS et les variables proxy.

- Capturez l'IP de sortie attendue auprès de NetSec si applicable.

- Préparer les diagnostics

- Ajoutez un job verify qui exécute dig , nc , openssl s_client , curl , ip route .

- Limitez la portée aux points de terminaison ciblés. Aucun secret en ligne de commande.

- Exécuter et observer

- Sauvegardez les logs du job. Comparez les réponses DNS, l'état TCP, la chaîne TLS, les codes HTTP et les temps.

- Notez si les résultats diffèrent entre runners ou environnements.

- Appliquer des correctifs ciblés

- DNS : corrigez les résolveurs ou la politique DNS pour le runner.

- Ports : demandez des règles de sortie pare-feu ; testez avec nc .

- TLS : installez l'AC d'entreprise dans le magasin de confiance du runner.

- Proxys : définissez http_proxy / https_proxy / NO_PROXY au niveau du job.

- Routage : corrigez la route par défaut ou le pare-feu hôte, puis refaites traceroute .

- Vérifier et documenter

- Relancez le job verify ; confirmez que les sorties répondent aux attentes.

- Enregistrez ce qui a changé et conservez le job verify pour de futures vérifications.

- Annuler si nécessaire

- Revenir uniquement sur les changements que vous avez introduits.

- Confirmer la ligne de base en relançant le job verify.

## Conclusion

Le réseau à l'intérieur de GitLab CI/CD diffère des ordinateurs portables des développeurs de manière subtile mais importante. En capturant un inventaire d'environnement clair, en introduisant un job de diagnostic étroit et en validant chaque couche dans l'ordre (DNS, ports, TLS, routage, proxys, pare-feu), vous pouvez isoler rapidement les causes racines et les corriger avec un risque minimal. Gardez le job de diagnostic à portée de main dans votre dépôt pour vérifier de nouveaux environnements, runners ou points de terminaison. Lorsque les problèmes réapparaissent, suivez la liste de contrôle pour reproduire les résultats, appliquer des changements ciblés et confirmer les résultats.