Migration du GitLab Runner local vers AWS

1. Pourquoi changer d’architecture ?

Au départ, le GitLab Runner était exécuté sur une machine locale, jhennebo-devops.

Un GitLab Runner est un agent chargé d’exécuter les tâches définies dans un pipeline CI/CD. Lorsqu’un développeur pousse du code sur GitLab, le Runner peut lancer des tests, vérifier la configuration Terraform, construire une image Docker ou préparer le déploiement de l’infrastructure.

Avec un Runner local, l’exécution des pipelines dépend de la disponibilité et de la configuration de cette machine. Si elle est arrêtée, inaccessible ou mal configurée, les jobs peuvent échouer ou rester en attente.

L’objectif est donc de déplacer l’exécution des pipelines vers une instance EC2 dédiée dans AWS, afin de disposer d’un environnement d’exécution intégré à l’infrastructure cloud du projet.

2. Qu’est-ce qui change concrètement ?

On peut comparer le Runner à un ouvrier chargé d’exécuter les instructions du pipeline.

  • Avant : le Runner travaille sur une machine locale. Les pipelines dépendent de cette machine.
  • Après : le Runner travaille sur une instance EC2 hébergée dans AWS. Il peut accéder aux services nécessaires, dans la limite des autorisations qui lui sont accordées.

Le code source et la définition des pipelines restent dans GitLab. Seule la machine chargée d’exécuter les jobs change.

3. La nouvelle architecture

Le GitLab Runner est hébergé sur une instance EC2 placée dans un sous-réseau privé du VPC AWS.

L’instance ne possède pas d’accès direct entrant depuis Internet. Elle utilise une NAT Gateway pour établir les connexions sortantes nécessaires, notamment vers GitLab et les dépôts de paquets.

La NAT Gateway permet aux ressources du sous-réseau privé d’accéder à Internet sans leur attribuer d’adresse IP publique. Elle ne fournit pas, à elle seule, un accès d’administration à l’instance.

L’instance EC2 dispose également d’un rôle IAM qui définit les opérations AWS qu’elle est autorisée à effectuer. Ce rôle permet notamment de récupérer le jeton d’enregistrement du Runner dans AWS Systems Manager Parameter Store.

4. Installation automatisée avec Terraform

L’infrastructure est décrite avec Terraform. Le script user_data de l’instance EC2 automatise une partie de l’installation lors du premier démarrage.

Les principales étapes sont les suivantes :

  1. Mise à jour des paquets du système.
  2. Installation de Docker, de l’AWS CLI et des outils nécessaires.
  3. Récupération du jeton GitLab depuis AWS Systems Manager Parameter Store.
  4. Installation de GitLab Runner.
  5. Enregistrement du Runner auprès de GitLab.
  6. Démarrage du service GitLab Runner.

Le jeton est stocké dans Parameter Store sous forme de SecureString. Il n’est donc pas inscrit directement dans le code Terraform ni dans un fichier .tfvars.

L’accès au paramètre est contrôlé par IAM. Cette méthode limite l’exposition du secret, à condition que les permissions soient correctement configurées et que le jeton ne soit pas divulgué dans les journaux.

Point important : l’automatisation du premier démarrage peut échouer, par exemple si l’instance ne parvient pas à télécharger les paquets nécessaires. Dans ce cas, il faut consulter les journaux d’installation et corriger le problème avant de poursuivre. Le script user_data ne se relance pas automatiquement à chaque redémarrage de l’instance.

5. Accès à l’instance EC2 avec AWS Systems Manager

L’instance se trouve dans un sous-réseau privé et n’expose pas de port SSH entrant pour l’administration.

L’accès est assuré par AWS Systems Manager Session Manager. Cette solution permet d’ouvrir un terminal sur l’instance sans exposer directement SSH sur Internet et sans nécessiter d’adresse IP publique.

Son fonctionnement nécessite notamment :

  • un agent SSM opérationnel sur l’instance ;
  • un rôle IAM avec les permissions adaptées ;
  • une connectivité sortante vers les services Systems Manager.

Dans cette architecture, la NAT Gateway assure la connectivité sortante nécessaire.

Pour ouvrir une session, on utilise la commande suivante depuis un terminal disposant de l’AWS CLI et des autorisations requises :

aws ssm start-session \
  --target i-06e96e0f3ac4ca235 \
  --region eu-central-1

Une fois connecté, il est possible de vérifier l’état du service, d’examiner les journaux et de modifier la configuration du Runner.

6. Configuration du Runner et intégration avec GitLab

Le Runner utilise l’exécuteur Docker (Docker executor). Chaque job s’exécute dans un conteneur basé sur l’image définie dans le pipeline.

Dans le fichier .gitlab-ci.yml, le tag aws-private permet de sélectionner le Runner correspondant :

default:
  tags:
    - aws-private

Le Runner doit être en ligne, activé et autorisé à exécuter les jobs concernés. Si un job reste en attente (pending), il faut vérifier que son tag correspond à celui du Runner et que celui-ci est disponible.

La configuration du Runner est stockée dans :

/etc/gitlab-runner/config.toml

Les paramètres du Docker executor sont définis dans la section [runners.docker].

7. Dépannage rencontré pendant l’installation

La mise en service a nécessité plusieurs corrections avant que le job de construction d’image ne réussisse.

7.1. Problème de connectivité lors du premier démarrage

Le premier démarrage a rencontré des erreurs de téléchargement de paquets. Les journaux ont permis d’identifier un problème de connectivité sortante.

Il a fallu vérifier la route vers la NAT Gateway et l’accès aux dépôts nécessaires, puis terminer manuellement l’installation du Runner lorsque l’installation automatisée n’avait pas abouti.

7.2. Erreurs de namespaces avec BuildKit et Buildah

Le job de construction d’image a d’abord échoué avec BuildKit rootless, puis avec Buildah :

Operation not permitted

Ces erreurs étaient liées aux restrictions appliquées aux conteneurs, notamment lors de la création de user namespaces.

Le diagnostic a consisté à vérifier les paramètres du noyau, l’état de seccomp, AppArmor et le comportement des conteneurs de test.

7.3. Erreur lors de l’application des couches d’image

Après avoir résolu le premier blocage, Buildah a rencontré une autre erreur :

remount / ... permission denied

Des tests ont montré que la récupération et l’extraction de l’image Alpine réussissaient lorsque seccomp et AppArmor étaient désactivés pour le conteneur de test.

La configuration du Docker executor a alors été adaptée dans /etc/gitlab-runner/config.toml :

[runners.docker]
  privileged = false
  security_opt = ["seccomp:unconfined", "apparmor:unconfined"]

Cette configuration a permis au job build-docker-image de réussir.

Limite de sécurité : ces options réduisent les protections d’isolation des conteneurs exécutés par le Runner. Elles constituent ici une solution de dépannage validée par les tests, mais il est préférable de rechercher une configuration plus restrictive avant une utilisation durable, notamment en limitant les exceptions au Runner dédié à la construction d’images. Le mode privileged est resté désactivé.

8. Procédure de vérification et de redémarrage

Après une modification de la configuration, il faut vérifier que le Runner est toujours opérationnel.

Étape 1 — Vérifier le service

sudo systemctl status gitlab-runner --no-pager

Le service doit afficher active (running).

Étape 2 — Vérifier l’enregistrement auprès de GitLab

sudo gitlab-runner verify

Le résultat attendu indique que le Runner est valide.

Étape 3 — Redémarrer le service si nécessaire

sudo systemctl restart gitlab-runner
sudo systemctl status gitlab-runner --no-pager

Étape 4 — Examiner les journaux en cas de problème

sudo journalctl -u gitlab-runner -n 60 --no-pager

Ces journaux permettent de vérifier si le Runner récupère les jobs, transmet les résultats à GitLab ou rencontre une erreur.

Étape 5 — Tester depuis GitLab

Dans le projet GitLab, ouvrir Build → Pipelines, puis vérifier l’état du job build-docker-image.

Le test est concluant lorsque le job se termine avec le statut success. Il faut ensuite vérifier les autres jobs du pipeline, qui peuvent notamment être manuels.

9. Pourquoi utiliser cette architecture ?

Cette migration présente plusieurs avantages :

  • Automatisation : Terraform décrit l’infrastructure et facilite sa reproduction.
  • Intégration AWS : l’instance dispose d’une identité IAM pour accéder aux services autorisés.
  • Réduction de l’exposition réseau : l’instance est placée dans un sous-réseau privé.
  • Gestion des secrets : le jeton GitLab est conservé dans Parameter Store plutôt que dans le code.
  • Administration contrôlée : Session Manager permet d’administrer la machine sans ouvrir directement SSH.
  • Séparation des responsabilités : GitLab orchestre les pipelines tandis que l’instance EC2 exécute les jobs.

10. Limites et points d’amélioration

La configuration utilise une seule NAT Gateway afin de limiter les coûts. Ce choix est adapté à un laboratoire, mais crée une dépendance à cette passerelle et à sa zone de disponibilité. Si elle devient indisponible, le Runner peut perdre sa connectivité sortante même si l’instance EC2 fonctionne encore.

Par ailleurs, le Runner exécute du code provenant des pipelines. Son rôle IAM doit donc être limité aux permissions réellement nécessaires.

Enfin, les options seccomp:unconfined et apparmor:unconfined réduisent les protections du Docker executor. Une amélioration consiste à étudier une méthode de construction d’images qui fonctionne avec des restrictions de sécurité plus strictes, puis à valider cette configuration par des tests CI.

Conclusion

La migration du GitLab Runner local vers une instance EC2 privée permet de déplacer l’exécution des pipelines vers une infrastructure AWS déclarée avec Terraform.

La mise en service a nécessité de vérifier la connectivité réseau, l’installation du Runner, son enregistrement auprès de GitLab et les restrictions de sécurité de Docker. Après ces corrections, le job build-docker-image a été exécuté avec succès.

L’architecture est désormais fonctionnelle pour ce test. La prochaine étape consiste à vérifier le reste du pipeline et à améliorer le durcissement de sécurité du Runner avant de considérer la configuration comme définitive.

Retour en haut