Gitlab: Déploiement sous Kubernetes

Introduction

Le 8 avril 2005, Linus Torvalds réalisait le tout premier commit de l’histoire de Git.

Le célébrissime développeur finlandais n’est pas qu’à l’origine de Linux, il est aussi le concepteur de l’outil de gestion de sources devenu le plus populaire au monde: Git.

Git a connu un tel succès qu’il a fini par éclipser bon nombre de ses concurrents et est devenu le système de version distribué le plus utilisé.

Rapide et décentralisé, Git est un incontournable du CICD allant jusqu’à être à la source du GitOps, une approche moderne de gestion des systèmes dont vous pourrez trouver une présentation ici à travers l’usage d’ArgoCD.

Git reste cependant un « moteur », une « base » qui peut se suffire à elle-même, mais qui est implémentée dans de nombreux outils tiers pour gagner en fonctionnalités et en capacités.

On va retrouver beaucoup de GUI permettant de visualiser l’historique des opérations Git ainsi que les branches associées.

Mais d’autres applications apportent encore bien d’autres caractéristiques pour aboutir à des suites complètes d’intégration et de déploiement.

C’est le cas de GitLab.

logo de gitlab

Cliquez sur l'image pour l'agrandir.

Origine et principes de GitLab

GitLab est une plateforme qui permet de gérer le cycle de vie entier d’un projet logiciel depuis le code source jusqu’au déploiement en production.

À la base, Gitlab est d’abord un outil de gestion de code basé sur Git, comme GitHub ou Bitbucket.

Il a été lancé en 2011 par Dmitriy Zaporozhets et Valery Sizov en opensource.

Gitlab est devenu une entreprise en 2014 avec un modèle « Open Core ».

C’est-à-dire que le cœur du produit reste open source est disponible au sein d’une version communautaire, tandis qu’il existe une version payante disposant de fonctionnalités avancées.

Au fil des versions, gitlab est d’ailleurs devenu beaucoup plus qu’une plateforme Git collaborative pour devenir une suite complète dédiée au CICD.

Il reste néanmoins possible d’utiliser GitLab comme simple repos de sources.

Chacun peut décider ou non d’utiliser l’ensemble des fonctionnalités proposées et de choisir de rester sur la version communautaire ou de basculer sur la version payante.

Voici ce que propose GitLab:

Fonction Description
📁 Git Repository Stockage de code avec Git, branches, commits, tags, etc
🔍 Code Review Pull/Merge requests, discussions sur le code, suggestions, approbations
⚙️ CI/CD intégrée permet de tester, construire et déployer automatiquement du code
📦 Packages Stockage de dépendances (Docker Registry, Maven, npm…).
🔐 Sécurité Scans de vulnérabilités, policies, secrets management…
📈 Monitoring & Analytics Tableaux de bord, suivi de perf, cycle time…
📅 Gestion de projet Issues, épics, kanban boards, milestones.
🔐 Gestion des accès Utilisateurs, groupes, droits par projet.
🚀 GitOps-ready Intégration facile avec Flux ou ArgoCD, ou GitLab Agent for Kubernetes

Les options de sécurités, incluant des notions d’audits restent des avantages de la version entreprise.

GitLab existe aussi en SaaS, c’est d’ailleurs une offre que je vous encourage à utiliser dès que possible. Un plan « Free Tier » gratuit est même disponible et peut déjà satisfaire de petites équipes (inférieures à 5).

Offre GitLab SaaS

Cliquez sur l'image pour l'agrandir.

La version en tant que service (SaaS) est prête à l’emploi sur-le-champ, ce qui vous épargne les tracas liés à la gestion de la plateforme. Malheureusement, pour une raison qui m’échappe encore, GitLab peine à proposer un hébergement en Europe.

À la date d’écriture de l’article, Gitlab.com repose sur des instances hébergées chez Google Cloud Platfom dans la région us-east1.

Le hic c’est qu’en ce moment, l’heure est à la méfiance. Selon l’activité de votre entreprise, il n’est pas toujours facile de convaincre le juridique et cyber de déployer des ressources en dehors du territoire européen.

A priori il est possible d’avoir des instances GitLab SaaS sur le Vieux Continent, mais du peu que j’aie pu en apprendre, ça reste une approche particulière demandant de s’engager de manière importante avec GitLab.

Par conséquent, avec beaucoup de regrets, et bien que je sois moi-même utilisateur de Gitlab SaaS, j’ai dû me résoudre à apprendre à le déployer dans un environnement onprem...et bien sûr sur Kubernetes😊.

C’est l’objectif de cet article, décrire une installation de GitLab sous Kubernetes via Helm. Ce que je pensais basique et rapide c’est finalement révélé être une belle galère.

Composants dans GitLab

La difficulté provient du fait que Gitlab repose sur plusieurs composants. Certains peuvent être installés et utilisés indépendamment de GitLab. D’autres sont directement liés à l’outil.

Voici un tableau qui résume la situation:

Composant Rôle principal Disponible en GitLab CE (gratuite) Requiert GitLab EE (payante)
PostgreSQL Base de données principale oui non
Redis Cache, sessions, files d’attente oui non
Sidekiq Tâches en arrière-plan (emails, indexation, etc...) oui non
GitLab Runner Exécution des jobs CI/CD oui (limitations possibles) non
GitLab Shell Accès Git par SSH, hooks Git oui non
Gitaly Accès optimisé aux dépôts Git oui non
Praefect Réplication / sharding de Gitaly (HA) non oui
Workhorse Proxy Web avancé (uploads, Git over HTTP) oui non
NGINX Reverse proxy intégré oui non
Container Registry Stockage local d’images Docker oui non
KAS (GitLab Agent K8s) Communication sécurisée avec Kubernetes (GitOps) non oui
Advanced CI (Child pipelines, rules, cache avancé, etc...) CI/CD avancée partiel oui
SAST / DAST Secrets Analyse sécurité du code (AppSec) non oui
Epics / Roadmaps Vision produit et gestion multi-projets non oui
Compliance Audit logs Traçabilité et politiques réglementaires non oui
Observabilité (Prometheus, Grafana) Monitoring intégré oui non
Cluster HA Haute disponibilité (base, Gitaly, Redis, etc...) Uniquement manuel oui
MinIO (backend S3) Stockage objet compatible S3 (artifacts, uploads, etc,lgc...) oui non

Certains modules, tels que la base de données PostgreSQL ou Redis, peuvent être déployés indépendamment, puis déclarés dans la configuration de GitLab afin d’y être enregistrés. D’autres, comme Minio peuvent être optionnels, dépendant de l’usage ou non des fonctions associées dans GitLab.

Dans le cas d’une installation sous K8S avec Helm, l’inconvénient principal demeure la volonté du chart de déployer par défaut tous ces composants (et même d’autres comme certmanager…).

Je ne reviendrais pas en détail sur le fonctionnement de Helm, j’ai déjà pu en parler à de nombreuses reprises. Je vous invite à lire cet article ainsi que mon tutoriel sur Jenkins dans lequel je reviens sur quelques grands principes de Helm.

Si vous cherchez à aller au plus rapide, avec aucune option particulière lors de votre appel à helm, vous allez vous retrouver avec pléthore d’objets et le risque de rentrer en conflit avec des composants déjà en place sur votre cluster.

Je vais donc essayer de donner un exemple d’installation de Gitlab « customisée » tout en restant basée sur Helm.

Bien entendu, mon cas d’usage n’est pas forcément le vôtre, mais cela vous donnera peut-être des idées et vous aidera pour déployer votre propre instance en lien avec vos besoins.

Cible et schéma d'architecture

Voici le schéma cible de ce qui va être présenté dans l’article.

Schéma d'architecture

Cliquez sur l'image pour l'agrandir.

Installation

Pour démarrer, on va déjà dédier un namespace à gitlab, toujours sur mon modèle de nomenclature, soit ici: prd-gitlab-lan.

kubectl create ns prd-gitlab-lan
Création du namespace

Cliquez sur l'image pour l'agrandir.

Gestion du storage

On poursuit par le stockage.

Il est nécessaire de créer trois espaces:

  • Un volume persistant pour la base de données PostgreSQL: il est nécessaire d’avoir un stockage rapide.
  • Un volume persistant pour Redis: il est également nécessaire d’avoir un stockage rapide.
  • Un volume persistant pour Gitaly en charge des repos GIT: on peut s’orienter dans un stockage réseau type NFS.

Stockage pour PostgreSQL & Redis

La création de ces volumes pose une première difficulté. En réalité, lorsqu’on utilise Helm avec GitLab, le chart principal fera appel à d’autres charts , tels que ceux de PostgreSQL et de Redis. Pour ces derniers il est possible de préciser un PersistentVolumeClaim (pvc) existant pour forcer l’usage de PersistentVolume(pv) qui serait déjà présent. Pour Gitaly, seule une StorageClass (sc) est supportée.

Si vous n’êtes pas à l’aise avec ces notions, je vous invite à parcourir la partie dédiée au stockage de mon cookbook K8S ainsi que la lecture de cet article dédié au CSI NFS/SMB.

Certains pourraient, à juste titre, me dire qu’on peut simplement travailler avec des storageClass et éviter de forcer des pv existants. C’est vrai, et c’est d’ailleurs ainsi qu’il est conseillé de procéder en entreprise.

Mais dans mon lab, j’ai besoin de préciser mes cibles de storage avec précision.

Il n’est finalement pas si exotique que ça que de vouloir éviter un provisionnement dynamique de ses pvs au sein d’une sc

Pour ce tutoriel, j’utilise mon node Kubernetes physique servant à la fois de control plane et de worker. C’est le même que j’ai utilisé pour Jenkins et pour l’usage d’un GPU sous K8S.

J’ai tendance à conserver mon cluster principal multi nodes pour mes applications « classique » et dédier un autre cluster (ici avec un seul node...pour l’instant) pour mon outillage. Le premier cluster ayant souvent besoin des composants du second pour déployer mes ressources. En cas de soucis c’est toujours intéressant d’avoir sa plateforme de CICD en dehors du socle dédié à l’exécution des applicatifs.

Tout ça pour dire que je vais plutôt consacrer des espaces locaux à mon serveur pour y stocker les données PostgreSQL et Redis.

Pour ça je déclare deux répertoires associés à un ssd nvme ( n’hésitez pas à lire la présentation sur la configuration du serveur que j’utilise ici) sudo mkdir -p /mnt/datastores/prddtsnme001/localpv/prd-gitlab-lan/postgresql sudo mkdir -p /mnt/datastores/prddtsnme001/localpv/prd-gitlab-lan/redis

Création des répertoires locaux

Cliquez sur l'image pour l'agrandir.

Il est évident que les données deviennent dépendantes du node…mais, comme je n’ai qu’un serveur dans mon cluster (qui finalement n'en ai pas), ce n’est pas un souci.

Ces deux emplacements vont pouvoir être rattachés à ma storage class sc-local-storage-prddtsnme001. C’est une sc très basique, déployée lors de mon tutoriel sur l’usage d’un gpu sous K8S.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: sc-local-storage-prddtsnme001
provisioner: kubernetes.io/no-provisioner  # Indique qu'il n'y a pas de provisionnement dynamique
volumeBindingMode: WaitForFirstConsumer

Je vais déclarer d’abord un pv via le fichier 01-pv-gitlab-postgresql.yml pour faire pointer vers le dossier dédié à la base PostgreSQL:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-gitlab-postgresql
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: postgresql
spec:
  capacity:
    storage: 30Gi 
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: sc-local-storage-prddtsnme001
  local:
    path: /mnt/datastores/prddtsnme001/localpv/prd-gitlab-lan/postgresql
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - prdk8sctp001

On y retrouve la sc sc-local-storage-prddtsnme001 et l’emplacement créé pour l’occasion.

Classiquement, on associe un pvc à ce pv à travers le fichier 02-pvc-gitlab-postgresql.yml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-gitlab-postgresql
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: postgresql
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: sc-local-storage-prddtsnme001
  resources:
    requests:
      storage: 30Gi
    

On procède de la même manière pour Redis, avec d’abord le pv déclaré dans 03-pv-gitlab-redis.yml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-gitlab-redis
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: redis
spec:
  capacity:
    storage: 10Gi 
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: sc-local-storage-prddtsnme001
  local:
    path: /mnt/datastores/prddtsnme001/localpv/prd-gitlab-lan/redis
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - prdk8sctp001
    

Puis le pvc déclaré dans 04-pvc-gitlab-redis.yml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-gitlab-redis
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: redis
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: sc-local-storage-prddtsnme001
  resources:
    requests:
      storage: 10Gi
    

En plaçant ces quatre fichiers dans un dossier «storage», il suffit de jouer la commande:

kubectl apply -f storage/
Création des pvc/pv

Cliquez sur l'image pour l'agrandir.

pour que tous les objets soient inscrits sur K8S.

On peut d’ailleurs vérifier les pvc avec la commande:

kubectl get pvc -n prd-gitlab-lan
Controle des pvc

Cliquez sur l'image pour l'agrandir.

En l’état, ils ne sont pas montés, car la sc utilisée ne monte les volumes que s’ils sont demandés par un pod…et ce n’est pas encore le cas.

Stockage pour Gitaly

Il demeure le cas de Gitaly.

Pour ce volume, on va passer par NFS, mais, comme le déploiement par Helm ne supporte que l’appel à une sc et impose un déploiement dynamique, je dois d’abord créer cette sc sc-nfs-default. Voici sa description, telle qu’elle est écrite dans le fichier 05-sc-nfs-default.yml:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: sc-nfs-default
provisioner: nfs.csi.k8s.io
parameters:
  # Le serveur NFS
  server: 192.168.10.152
  # L'export contenant la racine des dossiers ISO
  share: /Volume1/nfsshare/rubikub.coolcorp.priv/storageclass/default
reclaimPolicy: Retain
volumeBindingMode: Immediate

Cette sc se base sur le csi NFS dont vous pouvez lire la présentation et l’installation dans cet article.

Elle exploite un export NFS de mon NAS. Dans mon cas c’est un Terramaster, mais peu importe. Ce qui est important c’est que, si vous avez vous aussi un NAS de ce type (Synology Like) attention au paramètre de mappage des users. Il faut absolument qu’il soit en No Mapping. Sinon, vous vous exposez à des problèmes.

Configuration NAS

Cliquez sur l'image pour l'agrandir.

On applique ensuite la sc:

kubectl apply -f 05-sc-nfs-default.yml
Création de la storage class NFS

Cliquez sur l'image pour l'agrandir.

Stockage object: usage de Minio

Il reste un prérequis au storage. Celui-ci est en lien avec Minio. En effet, Minio fournit un storage object via l’API S3. Dans mon cas j’ai déjà déployé cette solution. Vous pouvez lire l’article associé.

Gitlab peut avoir besoin de Minio pour traiter des artefacts de grande tailles et notamment pour utiliser Git Lfs (Large File Storage).

Git LFS est une extension de Git qui permet de gérer de gros fichiers dans un dépôt sans alourdir son historique.

Mon objectif est d’utiliser mon instance minio existante.

Il suffit de se connecter à l’interface, puis de commencer par y déclarer un bucket. Celui-ci sera dédié à GitLab.

Création du bucket

Cliquez sur l'image pour l'agrandir.

On crée ensuite une politique pol-wr-gitlab qui disposera de tous les droits sur ce bucket.

Création de la policy

Cliquez sur l'image pour l'agrandir.

On crée ensuite un utilisateur nommé usr-gitlab qui hérite de cette politique.

Création du user et association de la policy

Cliquez sur l'image pour l'agrandir.

Il suffit enfin de créer une clef rattachée à ce compte. C’est cette clef (nom et secret) qui va pouvoir être utilisée par GitLab pour exploité le bucket créé. Il est important de garder la sortie obtenue.

Création de la clef API pour l'accès au bucket

Cliquez sur l'image pour l'agrandir.

Les secrets

Accès à Minio

Cette sortie justement va être la première occasion de créer un secret Kubernetes pour être exploité par GitLab.

On créer un secret sec-gitlab-minio, qui va contenir l’id de la clef Minio fraichement obtenu ainsi que les informations de connexion au bucket.

Voici la manière interactive de le faire:

kubectl create secret generic sec-gitlab-minio -n prd-gitlab-lan \
  --from-literal=config='{
    "provider": "AWS",
    "region": "minio",
    "endpoint": "https://minioapi.coolcorp.priv",
    "path_style": true,
    "bucket": "gitlab",
    "aws_access_key_id": "id_clef",
    "aws_secret_access_key": "secret_clef",
    "insecure": true
  }'    
Création du secret pour la connexion à minio

Cliquez sur l'image pour l'agrandir.

Support de la CA privée

Deuxième secret à créer, celui qui va contenir le certificat public associé à ma CA (certificate authority). En effet, comme c’est le cas dans beaucoup d’infrastructure, j’utilise une CA interne, en l’occurrence celle de Microsoft, pour générer mes propres certificats.

De base, cette CA n’est pas reconnue des images des composants Gitlab, il faudra donc que je l’ajoute au sein des CA de confiance reconnus par GitLab.

Je récupère donc le certificat public de ma CA et je l’enregistre dans un secret:

kubectl create secret generic sec-gitlab-cacert --from-file=ca.crt=ca-coolcorp.crt -n prd-gitlab-lan
Création du secret pour le CA

Cliquez sur l'image pour l'agrandir.

Enfin un dernier secret est nécessaire. Celui-ci va servir à stocker le mot de passe du compte de service pour l’accès à mon Active Directory, utilisé pour interroger mon annuaire par GitLab.

Cela va me permettre d’exploiter des comptes de mon domaine pour me connecter à GitLab. Encore faut -il que GitLab dispose d’un accès à l’AD pour le faire.

Je crée donc dans mon AD un compte de service svc-gitlab-readad.

J’enregistre le mot de passe associé dans un secret sec-gitlab-ldap:

kubectl create secret generic sec-gitlab-ldap --from-literal=password='password' -n prd-gitlab-lan

Les ingress

On bascule maintenant sur les ingress.

Les ingress vont se rattacher aux Services K8S pour permettre des accès externes au cluster à travers un Ingress Controler.

Toute cette logique est décrite dans cette partie de mon cookbook Kubernetes, où je présente notamment la solution Traefik.

À l’origine, l’installation de GitLab avec Helm va automatiquement configurer tous les ingress nécessaires en tentant d’installer un Ingress Controler (NGINX).

L’installation par défaut va même jusqu’à déployer certmanager pour automatiser l’obtention de tous les certificats associés.

J’ai déjà présenté certmanager sur ce site. N’hésitez pas à parcourir l’article.

Dans mon cas CertManager et un ingress controler (Traefik) sont déjà présents sur K8S.

Je ne souhaite donc pas redéployer ces composants et préfère gérer moi-même la création des ingress.

Nous allons avoir besoin de trois ingress (et de trois certificats rattachés).

Ces trois ingress vont couvrir des URL dont l’enregistrement DNS doit bien entendu pointer vers le serveur Kubernetes. Dans le cas d’un usage avec plusieurs nœuds, comme c’est le cas pour mon cookbook K8S, l’IP doit pointer vers un équilibreur de charge situé devant les nœuds hébergeant une instance Traefik.

Le premier ingress va correspondre à l’URL gitlab.coolcorp.priv et va se retrouver rattaché au futur service gitlab-webservice-default sur le port 8181. Cela va permettre un accès à la GUI de GitLab depuis l’extérieur.

Voici le fichier 01-ing-gitlab-default.yaml dans lequel on retrouve le certificat et l’ingress.

    ---
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: cert-gitlab-default
      namespace: prd-gitlab-lan
      labels:
        environment: prd
        network: lan
        application: gitlab
        tier: default
    spec:
      commonName: gitlab.coolcorp.priv
      secretName: sec-gitlab-cert
      privateKey:
        algorithm: RSA
        size: 4096
      dnsNames:
        - gitlab.coolcorp.priv
      issuerRef:
        kind: ClusterIssuer
        name: clusterissuer-acme2certifier
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ing-gitlab-default
      namespace: prd-gitlab-lan
      labels:
        environment: prd
        network: lan
        application: gitlab
        tier: default
      annotations:
        traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
        ingressClassName: traefik-lan
        traefik.ingress.kubernetes.io/router.tls: "true"
    spec:
      ingressClassName: traefik-lan
      tls:
      - hosts:
        - gitlab.coolcorp.priv
        secretName: sec-gitlab-cert
      rules:
      - host: gitlab.coolcorp.priv
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: gitlab-webservice-default
                port:
                  number: 8181
 

Le certificat s’obtient via l’issuer clusterissuer-acme2certifier. Tous ces principes vous sont expliqués ici.

On enchaine avec le second certificat et le second ingress. Cette fois-ci, pour l’URL registry.coolcorp.priv et déclarée dans le fichier 02-ing-gitlab-registry.yaml.

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: cert-gitlab-registry
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: registry
spec:
  commonName: registry.coolcorp.priv
  secretName: sec-gitlab-cert-reg
  privateKey:
    algorithm: RSA
    size: 4096
  dnsNames:
    - registry.coolcorp.priv
  issuerRef:
    kind: ClusterIssuer
    name: clusterissuer-acme2certifier
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ing-gitlab-registry
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: registry
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
    ingressClassName: traefik-lan
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  ingressClassName: traefik-lan
  tls:
  - hosts:
    - registry.coolcorp.priv
    secretName: sec-gitlab-cert-reg
  rules:
  - host: registry.coolcorp.priv
    http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: gitlab-registry
                port:
                  number:  5000

Cet ingress est rattaché au Service de gitlab-registry pour nous permettre par la suite d’enregistrer des images de conteneurs. (on s’aventure au-delà de Git).

Enfin le troisième ingress (et le certificat rattaché) couvre l’URL kas.coolcorp.priv. On utilise pour cela le fichier 03-ing-gitlab-kas.yaml:

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: cert-gitlab-kas
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: kas
spec:
  commonName: kas.coolcorp.priv
  secretName: sec-gitlab-cert-kas
  privateKey:
    algorithm: RSA
    size: 4096
  dnsNames:
    - kas.coolcorp.priv
  issuerRef:
    kind: ClusterIssuer
    name: clusterissuer-acme2certifier
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ing-gitlab-kas
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: kas
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
    ingressClassName: traefik-lan
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  ingressClassName: traefik-lan
  tls:
  - hosts:
    - kas.coolcorp.priv
    secretName: sec-gitlab-cert-kas
  rules:
  - host: kas.coolcorp.priv
    http:
        paths:
          - path: "/k8s-proxy/"
            pathType: Prefix
            backend:
              service:
                name: gitlab-kas
                port:
                  number: 8154
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: gitlab-kas
                port:
                  number: 8150

Je suis moins à l’aise avec ce dernier. Il est censé couvrir le « KAS (GitLab Agent K8s) ». C’est un composant qui permet une connexion bidirectionnelle et sécurisée entre GitLab et un cluster Kubernetes, via un agent déployé dans le cluster. Cela revient à faire du gitops, mais pour cela j’utilise plutôt ArgoCD…en plus il semblerait que le KAS soit réservé à la version payante de Gitlab.

Quoi qu’il en soit, je déclare tout de même l’ingress pour d’éventuels besoins futurs.

Je place ces trois fichiers yamls dans un dossier ingress et j’applique la commande: kubectl apply -f ingress/

Création des ingress

Cliquez sur l'image pour l'agrandir.

Par conséquent, les ingress ont été enregistrés et les certificats seront prêts à être utilisés une fois que GitLab et ses composants seront en place.

C’est d’ailleurs enfin le moment de s’occuper de l’installation de gitlab à proprement parler.

Déploiement avec Helm

Comme on passe par helm, on commence par ajouter le repos:

helm repos add gitlab https://charts.gitlab.io/ helm repos update
Ajout des repos Helm

Cliquez sur l'image pour l'agrandir.

Avant de se lancer tête baissée, et, comme j’ai déjà pu le faire avec Jenkins, on va déjà explorer le contenu des valeurs par défaut proposées par le chart Helm Gitlab.

Pour cela, je crée comme à mon habitude mon dossier helm et j’exporte les valeurs par défaut dans un fichier nommé default-value.yml. helm show values gitlab/gitlab > helm/default-value.yml

Extraction des valeurs par défault

Cliquez sur l'image pour l'agrandir.

La sortie est conséquente et le fichier de valeurs très long. On y retrouve tous les composants nommés avec leur configuration.

Fichier helm par défault

Cliquez sur l'image pour l'agrandir.

Il va être nécessaire de s’inspirer de ce résultat pour concevoir son propre fichier custom-values.yml.

Voici son contenu:

global:
  certificates:
    #on indique le certificat qui contient le certificat de la CA privée
    customCAs:
      - secret: sec-gitlab-cacert
  common:
    labels: {}
  #On fixe la version à utilisé en partant sur une "Community Edition (ce)"  
  edition: ce  
  #a l'heure de rédaction de l'article c'est la version 17.10.3 la plus récente"  
  gitlabVersion: "17.10.3"
  #on fixe les urls
  hosts:
    domain: coolcorp.priv
    https: true
    gitlab:
      name: gitlab.coolcorp.priv
    registry:
      name: registry-gitlab.coolcorp.priv
      https: false
    kas:
      name: kas-gitlab.coolcorp.priv
    ssh: gitlab.coolcorp.priv   
  #On désactive l'usage de certmanager  
  ingress:
    configureCertmanager: false  
    enabled: false
  #On désactive le déploiement de minio mais on déclare la configuration associée pour utiliser l'instance minio déja en place  
  minio:
    enabled: false
  appConfig:
    object_store:
      enabled: true
      proxy_download: true
      connection:
        #On utilise le secret créér précédemment pour déclarer la conf minio
        secret: sec-gitlab-minio
        key: config
      storage:
        provider: AWS
        region: "minio"  
        endpoint: "https://minioapi.coolcorp.priv" 
        bucket: "gitlab"  
        path_style: true  
        insecure: true # Désactive la vérification SSL
    #Configuration de LDAP pour l'accès à l'AD    
    ldap:
      enabled: true
      servers:
        main:
          label: 'Active Directory'
          host: 'PRDMUTWIN501.coolcorp.priv'  #adresse du contrôleur AD
          port: 636                 # 636 pour LDAPS, 389 pour LDAP
          uid: 'sAMAccountName'     # identifiant AD (souvent `sAMAccountName`)
          bind_dn: 'CN=svc-gitlab-readad,OU=SVC,OU=USR,DC=coolcorp,DC=priv'  # utilisateur de bind
          password:
            secret: sec-gitlab-ldap
            key: password
          encryption: 'simple_tls' 
          verify_certificates: false
          smartcard_auth: false
          active_directory: true
          allow_username_or_email_login: true
          lowercase_usernames: false
          base: 'DC=coolcorp,DC=priv'
          group_base: 'OU=GRP,DC=coolcorp,DC=priv'
          admin_group: 'CN=GRP-APP-GITLAB-ADM,OU=PRD,OU=APP,OU=GRP,DC=coolcorp,DC=priv'
          external_groups: []
          sync_ssh_keys: false    
  kas:
    enabled: true
    service:
      apiExternalPort: 8153 
    tls:
      enabled: false
      verify: true
  gitaly:
    tls:
      enabled: false

#On s'assure que certmanager ne soit pas déployé 
certmanager:
  installCRDs: false
  install: false

#On s'assure que l'ingress controler nginx ne soit pas déployé
nginx-ingress:
  enabled: false

prometheus:
  install: false

#Configuration de Redis  
redis:
  master:
    persistence:
      enabled: true
      #on précise bien le PVC existant pour qu'il soit utilisé par redis
      existingClaim: pvc-gitlab-redis
  install: true
  image:
    tag: "6.2.16-debian-12-r1"
  auth:
    existingSecret: gitlab-redis-secret
    existingSecretKey: redis-password
    usePasswordFiles: true
  architecture: standalone
  cluster:
    enabled: false
  metrics:
    enabled: true
    image:
      tag: "1.46.0-debian-11-r8"

#Configuration de postgre
postgresql:
  install: true  
  image:
    tag: 14.16.0
  primary:
    persistence:
      #on précise bien le PVC existant pour qu'il soit utilisé par postgre
      existingClaim: pvc-gitlab-postgresql
      enabled: true
    initdb:
      scriptsConfigMap: '{{ include "gitlab.psql.initdbscripts" $}}'
    extraVolumeMounts:
      - name: custom-init-scripts
        mountPath: /docker-entrypoint-preinitdb.d/init_revision.sh
        subPath: init_revision.sh
    podAnnotations:
      postgresql.gitlab/init-revision: "1"
  metrics:
    enabled: false
   
gitlab-runner:
  certsSecretName: sec-gitlab-cacert
  install: true
  rbac:
    create: true
  runners:
    locked: false
    secret: "nonempty"
    #On customize la conf du runner pour inclure le CA privée
    config: |
      [[runners]]
        tls-ca-file = "/certs/ca.crt"
        [runners.kubernetes]
        image = "ubuntu:22.04"
        {{- if .Values.global.minio.enabled }}
        [runners.cache]
          Type = "s3"
          Path = "gitlab-runner"
          Shared = true
          [runners.cache.s3]
            ServerAddress = {{ include "gitlab-runner.cache-tpl.s3ServerAddress" . }}
            BucketName = "runner-cache"
            BucketLocation = "us-east-1"
            Insecure = false
            {{ end }}
  volumeMounts:
    - name: runner-ca-cert
      mountPath: /certs/ca.crt
      subPath: ca.crt
  #On réutilise le secret disposant du certificat du CA pour le monter dans le conteneur du runner
  volumes:
    - name: runner-ca-cert
      secret:
        secretName: sec-gitlab-cacert
        defaultMode: 0444    

#Pas d'installation de traefik
traefik:
  install: false
 
gitlab:
  toolbox:
    replicas: 1
    antiAffinityLabels:
      matchLabels:
        app: gitaly
  #Configuration de Gitaly
  gitaly:
    persistence:
      #On utilise la storage class NFS défini précédemment
      storageClass: sc-nfs-default
      size: 80Gi
    securityContext:
      fsGroup: 1000
    

J’ai essayé de commenter au mieux pour vous indiquer les éléments configurés.

Globalement, en dehors des ingress, on reprend tous les objets créés précédemment, depuis les pvc, la sc jusqu’aux secrets.

Cela permet de préciser les éléments pour le stockage persistant des données, la configuration de LDAPS (Lightweight Directory Access Protocol Secure). Attention, on parle bien d’un accès à l’AD sur le port 636 pour une communication sécurisée, il est nécessaire que l’AD soit configuré en conséquence.

Il est enfin temps de procéder à l’installation du produit:

helm upgrade — -install gitlab gitlab/gitlab --namespace prd-gitlab-lan — -values helm/custom-values.yml
Déploiement de Gitlab avec Helm

Cliquez sur l'image pour l'agrandir.

On observe en sortie de commande un ensemble d’avertissements. Par exemple, que l’usage de la base postegreSQL et Redis intégrées est avant tout la à des fins de tests. Gitlab recommande de privilégier une instance PostgreSQL tierce et une base Redis déployées en dehors de l’installation de Gitlab par Helm.

Si vous préférez cette méthode, il faudra mettre à jour le fichier de valeur pour faire correspondre les éléments de connexions.

Dans mon cas, et pour mon usage, ce n’est pas très problématique.

Le déploiement de tous les composants va prendre un certain temps et il est possible que certains pods échouent à la première exécution, le temps que d’autres pods finissent de s’initialiser.

Vous pouvez surveiller le déploiement avec la commande:

kubectl get pod -n prd-gitlab-lan
Controle des pods après installation

Cliquez sur l'image pour l'agrandir.

Les pvc devraient maintenant être montés:

kubectl get pvc -n prd-gitlab-lan
Controle des pvc après installation

Cliquez sur l'image pour l'agrandir.

En plus des pvc créer manuellement, on voit bien le pvc supplémentaire repo-data-gitlab-gitaly-0 correspondant gitaly et reposant sur la sc sc-nfs-default.

Pour pouvoir se connecter à gitlab, vous pouvez récupérez le mot de passe root par défaut créer arbitrairement via la commande: kubectl get secret gitlab-gitlab-initial-root-password -n prd-gitlab-lan -o yaml

Récupération du password root

Cliquez sur l'image pour l'agrandir.

Attention, la valeur est en base64, il faut la convertir pour avoir le vrai mot de passe.

Premieres connexions et cas d'usage

On peut se connecter sur https://gitlab.coolcorp.priv avec ces identifiants.

Premieres connexion à la plateforme

Cliquez sur l'image pour l'agrandir.

En root vous avez les pleins pouvoirs sur la plateforme.

Accès à la GUI en root

Cliquez sur l'image pour l'agrandir.

Mais on peut également se connecter avec un compte AD puisqu’on n’a configuré LDAPS.

Accès avec un compte AD

Cliquez sur l'image pour l'agrandir.

Comme mon utilisateur est dans le bon groupe, je peux effectivement me connecter à GitLab en précisant cette fois-ci une connexion via Active Directory.

Une fois dans mon profil, je peux créer mon premier repos !.

Création d'un premier repo

Cliquez sur l'image pour l'agrandir.

Création d'un premier repo

Cliquez sur l'image pour l'agrandir.

Je peux directement y mettre des données depuis la GUI, mais mon objectif reste de pouvoir le faire depuis mon poste de travail, par exemple avec l’usage de tortoise GIT.

Pour faire cela, je dois d’abord déclarer ma clef publique SSH au niveau de mon profil.

Déclaration de ma clef ssh

Cliquez sur l'image pour l'agrandir.

Déclaration de ma clef ssh

Cliquez sur l'image pour l'agrandir.

À ce stade, je devrais pouvoir faire un « git clone » sur mon poste de travail, où Git est configuré avec ma clé privée…

Sauf que non….

Problématique de l'accès shell (ssh)

Il reste encore un dernier élément: l’accès au shell SSH de gitlab.

Losqu’on liste les pods rattachés au namespace prd-gitlab-lan, on note bien la présence de pod gitlab-gitlab-shell-***.

Pod Gitlab Shell

Cliquez sur l'image pour l'agrandir.

C’est à ces derniers qu’il faut s’adresser si l’on veut communier avec gitlab à travers SSH. Normalement, on utilise le port 22 pour cela mais en environnement conteneurisé, ce n’est pas si simple.

Quand vous déployez Gitlab sur un serveur sans passer par des conteneurs, GitLab s’intègre au composant SSH déjà présent sur ce serveur. En faite il vient modifier ce dernier pour inclure une redirection sur l’utilisateur technique « git » utilisé pour discuter avec les repos. Dans ce cas, vous conservez un accès SSH classique au serveur pour son administration tout en autorisant l’envoi et la réception de données pour Gitlab…en restant sur le port 22.

Lorsque Gitlab, et plus spécifiquement son composant GitLab Shell, sont conteneurisés, il ne peut plus accéder directement au serveur SSH du nœud sur lequel il s’exécute.

Il va falloir ruser et trouver une autre méthode.

Plusieurs choix sont possibles. L’idéal est d’avoir en front une solution de reverse proxy externe qui écoute sur le port 22 et qui redirige vers un autre port dédié à l’usage de GitLab Shell, par exemple le 2222.

C’est presque ainsi que je vais procéder, mais sans utiliser de reverse proxy externe.

On va déjà modifier Traefik pour lui ajouter un nouveau port d’entrée. En effet, classiquement, on utilise Traefik pour faire du loadbalancing à destination des Services K8S sur http/https, soit en L7.

Mais Traefik support également le loadbalacing TCP en L3.

Pour ça on reprend la conf de Traefik pour lui ajouter un nouveau point d’entrée. L’installation de Traefik est décrite dans la partie qui le concerne dans mon cookbook K8S.

Je ne reviendrais pas en détail dessus et vous invite à la lire pour mieux comprendre l’installation.

Dans notre cas de figure, on édite le fichier traefik-config-lan.yaml pour ajouter le support du port 2222.

Il faut également ajouter explicitement la surveillance du namespace prd-gitlab-lan, car en L3 il n’y a pas d’usage d’Ingress Class.

Modification de la configuration de traefik

Cliquez sur l'image pour l'agrandir.

On met à jour le déploiement de traefik.

helm upgrade traefik-lan traefik/traefik -f traefik-config-lan.yaml -n inf-traefik-lan --set nodeSelector.traefik-lan=yes --set tolerations[0].key=node-role.kubernetes.io/control-plane --set tolerations[0].operator=Exists --set tolerations[0].effect=NoSchedule
Redéploiement de traefik

Cliquez sur l'image pour l'agrandir.

À noter qu’il faut également s’assurer que le firewall de l’OS sous-jacent servant à exécuter Traefik (ici Rocky Linux) autorise le port en question.

Dès lors Traefik écoute sur le port 2222 et est en mesure de rediriger le trafic associé vers un Service Kubernetes.

Pour faire cela, il ne peut pas s’appuyer sur un ingress classique, mais sur un objet custom propre à Traefik: un IngressRouteTCP (objet qui n’est pas associé à une ingress class d’où le besoin d’ajouter explicitement la surveillance du namespace prd-gitlab-lan dans la configuration de traefik).

Celui-ci est décrit dans le fichier 04-ing-gitlab-ssh.yaml:

---
apiVersion: "traefik.io/v1alpha1"
kind: IngressRouteTCP
metadata:
  name: ing-gitlab-ssh
  namespace: prd-gitlab-lan
  labels:
    environment: prd
    network: lan
    application: gitlab
    tier: ssh
spec:
  entryPoints:
    - ssh
  routes:
  - match: HostSNI(`*`)
    services:
    - name: gitlab-gitlab-shell
      port: 22
    

Ici, tout ce qui va arriver sur le port 2222 de Traefik va être redirigé vers le Service K8S gitlab-gitlab-shell sur le port 22.

On peut appliquer l’objet avec la commande:

kubectl apply -f 04-ing-gitlab-ssh.yaml
Application de L'ingressroutetcp

Cliquez sur l'image pour l'agrandir.

On peut vérifier cette configuration et la présence du nouveau port directement dans l’interface de Traefik.

Controle de traefik

Cliquez sur l'image pour l'agrandir.

Controle du routage

Cliquez sur l'image pour l'agrandir.

Il est à présent possible de tester le clonage du repos de démo sur mon poste de travail.

La première méthode consiste à passer par une commande git clone classique. Dans ce cas, on peut se permettre de jouer avec le fichier config de la configuration ssh de son poste. Qu’il s’agisse de Linux ou Windows, ce fichier est a positionner dans le répertoire personnel de son utilisateur dans un sous-dossier .ssh.

En précisant qu’on souhaite passer par le port 2222 pour l’host gitlab.coolcorp.priv, toutes nos commandes git utiliseront implicitement ce port sans qu’on n’ait à le préciser.

Host gitlab.coolcorp.priv
  Port 2222
Modification de la conf du client SSH

Cliquez sur l'image pour l'agrandir.

Par exemple via la commane git clone.

Clonage d'un repo en CLI

Cliquez sur l'image pour l'agrandir.

Si maintenant on souhaite passer par des outils plus graphiques comme l’indémodable Tortoise Git, il faudra modifier l’adresse récupérée depuis l’interface de gitlab, pour y’a ajouter le port 2222.

ssh://git@gitlab.coolcorp.priv:2222/vburgun/demo.git
Clonage d'un repo avec TortoiseGIT

Cliquez sur l'image pour l'agrandir.

À ce moment-là il devient également possible de communiquer avec le repos et d’y push des données, par exemple.

Push d'un fichier

Cliquez sur l'image pour l'agrandir.

Push d'un fichier

Cliquez sur l'image pour l'agrandir.

Conclusion

Gitlab est désormais déployé sous Kubernetes et parfaitement exploitable. L’usage de Helm pour installer ce type de plateforme est à travailler avant de se lancer dans une configuration par défaut.

GitLab est un logiciel devenu massif avec le temps, intégrant de nombreux composants internes et externes, il faut donc bien comprendre le rôle de chaque élément et configurer ses derniers selon l’usage souhaité.

Attention également à la consommation de ressources. Le cœur de l’application est développé en Ruby On Rails et hérite d’un fonctionnement spécifique (langage interprété) dont il faut tenir compte dans son calcul d’usage CPU et mémoire sur le cluster K8S. Idéalement, on aurait dû également travailler ce point dans le fichier de valeurs personnalisées transmis à Helm.

Cela reste complexe de proposer un modèle d’installation générique pour un outil aussi vaste que GitLab. C’est encore plus complexe sous forme de conteneurs et cela devient un véritable enjeu dans un cluster Kubernetes. Oui on peut rapidement arriver à déployer des éléments, mais encore faut-il que l’installation soit pérenne dans le temps et s’interface avec le reste des outils/applications présentes au sein d’un IT.

C’est pourquoi, si l’utilisation de l’offre SaaS de GitLab est impossible, prenez bien soin de comprendre quoi déployer et comment en vous projetant sur votre besoin final.

Gitlab est une formidable plateforme sur laquelle il est possible de construire toute une logique de CICD moderne et performante. Mais l’effort d’administration associé est proportionnel au niveau d’usage que vous souhaitez en faire.

Restez toujours cohérents avec vos besoins et ne copiez pas aveuglément les sorties d’un prompt d’IA auquel vous auriez demandé un guide d’installation… vous risqueriez de construire une usine sur du sable…