GitLab CI: Distributed Runner Caches mit MinIO

Malte Müns

Wir sind große GitLab Fans und versuchen jeden Tag aufs Neue die Möglichkeiten von GitLab bestmöglich zu nutzen. Bei immer mehr Projekten mit aktiviertem GitLab CI ist unser Shared Runner kürzlich an seine Grenzen gestoßen. 

Ziel war es mehrere Runner auf unterschiedlichen virtuellen und oder physischen Maschinen betreiben zu können.
Sind erst mehrere selbstgehostete Runner auf verschiedenen Servern installiert, schlagen plötzlich einige CI-Jobs fehl. Der Grund dafür ist, dass die GitLab Runner den Cache nur auf der entsprechenden Maschine miteinander teilen können.

Soll der Cache zwischen mehreren Runnern, die sich auf verschiedenen Servern befinden, geteilt werden, kann S3 bei Amazon Web Services (AWS) verwendet werden.
Die Verwendung von Amazon S3 kann aufgrund der Anzahl der an AWS gestellten Anfragen und der Menge der übertragenen Daten teuer werden.


Login-Screen von MinIO


Zum Glück steht mit MinIO ein hochperformanter und S3-kompatibler Object Storage zur Verfügung. MinIO ist Open Source, kostenfrei und u. a. über Docker Hub direkt als Docker Image verfügbar.
Zum Betrieb unseres MinIO verwenden wir einen Server aus der Hetzner Cloud mit hochverfügbaren SSD-Block Storage.

Sollen die an MinIO übertragenen Daten verschlüsselt übertragen werden, müssen entweder eigene Zertifikate generiert werden oder ein Reverse Proxy mit SSL-Terminierung eingesetzt werden. Wir nutzen traefik als Reverse Proxy, der Let’s Encrypt Zertifikate automatisch ausstellen und verlängern kann.

docker-compose.yml

version: '3.5'
services:
  minio:
    image: minio/minio:latest
    volumes:
     - minio-vol:/data
    networks:
    - traefik-proxy
    environment:
      MINIO_ACCESS_KEY: {YOUR_MINIO_ACCESS_KEY}
      MINIO_SECRET_KEY: {YOUR_MINIO_SECRET_KEY}
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.minio.rule=Host(`minio.{your-domain.de}`)"
      - "traefik.http.routers.minio.entrypoints=https"
      - "traefik.http.routers.minio.tls=true"
      - "traefik.http.routers.minio.tls.certresolver=letsencrypt"
    command:  server /data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3
    restart: always

volumes:
  minio-vol:

networks:
  traefik-proxy:
    external: true


Benutzeroberfläche von MinIO mit Bucket für GitLab



Wenn MinIO installiert ist, muss noch die Konfiguration der GitLab Runner angepasst werden. Dazu wird die jeweilige config.toml wie folgt angepasst:

[[runners]]
  ...
  [runners.cache]
    Type = "s3"
    Path = "{your/path/inside/the/bucket}"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "minio.{your-domain.de}"
      AccessKey = "{YOUR_MINIO_ACCESS_KEY}"
      SecretKey = "{YOUR_MINIO_SECRET_KEY}"
      BucketName = "runner"
      Insecure = false


Wichtig ist das Shared-Flag zu aktivieren, damit die Runner den Cache gemeinsam teilen. Sollte MinIO nicht auf Port 443 laufen, kann an die ServerAddress noch der richtige Port getrennt durch einen Doppelpunkt angehängt werden.

Nach einem Neustart der Runner sollte der Cache (falls er verwendet wird) in euer MinIO S3 Storage heruntergeladen und später hochgeladen werden.


MinIO regelmäßig aufräumen

Bei einer Verwendung der Docker-Runner wird das Host-System nach und nach u. a. mit den Cache-Volumes zugemüllt. Eine einfache Lösung ist ein Cronjob mit `docker system prune --all`, dieser Ansatz erfasst nun jedoch nicht mehr die Cache-Inhalte im MinIO.

Um einfach mit MinIO interagieren zu können, gibt es den MinIO Client mit dem wir regelmäßig die Buckets leeren wollen.
Dank des Docker Image vom MinIO Client geht auch das Löschen von Buckets einfach:

docker run --rm --entrypoint=/bin/sh minio/mc -c "mc config host add minio https://minio.{your-domain.de}:443 {YOUR-ACCESS-KEY} {YOUR-SECRET-KEY} && mc rb --force --dangerous minio/{your-bucket-name} && mc mb minio/{your-bucket-name}

Im ersten Schritt wird unser MinIO zum Client hinzugefügt, es bekommt den Namen `minio`. Anschließend löschen wir einen Bucket mit `mc rb` und erstellen den Bucket sofort neu.
Den Befehl müsst ihr nur noch regelmäßig via Cronjob laufen lassen und schon bleibt auch die Speicherauslastung eures MinIO überschaubar.

Viel Spaß mit euren GitLab Runnern mit shared Cache!

Links: