Skip to main content

L'autoscaling des gitlab runners sur Openstack

31 juil. 2019

Nous aidons pas mal de nos clients à mettre en place une CI (intégration continue) et/ou un CD (déploiement continu). Vous avez certainement ressenti ce plaisir que de voir les machines enchaîner les tests sans action humaine une fois le système en place !

Ce plaisir est généralement suivi d'une tonne de problèmes / ajustements car petit à petit tout l'équipe de dev y migre. Et tout le monde y va de sa modification, de son amélioration, ... bref la solution prend de l'ampleur.

Dans ces conditions, il y a bien un problème que vous allez rencontrer rapidement, c'est le temps passé à attendre la fin d'un pipeline.

Il existe beaucoup de solutions pour optimiser la charge de votre CI, mais je ne vais vous parler ici que de l'option la plus simple : augmenter le nombre de gitlab runners afin de dépiler au plus vite les jobs en attente.

Et comme on aime bien tout ce qui est virtualisation chez Enix, et que l'autoscaling n'a pas de secret pour nous, je vous propose de monter la solution sur OpenStack.

Avant d'aller plus loin, je me suis basé sur des articles en anglais disponibles ici et ici mais qui n'étaient pas forcément complets ou à jour, d'où l'idée de ce billet de blog.

Gitlab, runners et docker-machine

Dans le fonctionnement standard, un gitlab runner s'enregistre auprès votre instance gitlab puis consomme les jobs en attente. On peut donc avoir plusieurs gitlab runners qui fonctionnent en parallèle. Il existe aussi l'option concurrent pour qu'un runner lance plusieurs jobs en parallèle.

Les runners lancent les jobs en passant par un executor. Il y en a plusieurs disponibles. Par exemple, on peut lancer le job directement sur la machine où tourne le runner (via shell), ou bien dans un conteneur (via docker). Il existe une liste conséquente d'executors. Vous noterez qu'il existe notamment docker-machine.

Docker machine, pour ceux qui ne connaissent pas, est un outil qui existe depuis les débuts de Docker, et qui a pris son essor lorsque Docker Desktop n'existait pas encore. Cet outil permet de gérer votre parc de machines faisant tourner docker engine, et de configurer votre client docker (la commande que vous utilisez d'habitude) pour se connecter directement vers une machine de ce parc. Vous obtenez donc le contrôle d'une machine virtuelle ou physique qui peut être bien éloignée.

OpenStack

La bonne nouvelle, c'est que docker-machine possède un driver pour créer des machines sur OpenStack. La mauvaise, c'est qu'il ne supporte que l'API v2 d'OpenStack, et que cette version 2 n'est plus disponible depuis la release Queen d'OpenStack.

Il existe heureusement une astuce qui permet de faire fonctionner l'ensemble. Mais voyons déjà ce que la documentation officielle nous suggère :

[[runners]]
name = "Mon runner OpenStack”
url = "https://mon.gitlab.tld/"
token = "mon-token-gitlab-ci-runner"
executor = "docker+machine"
[runners.machine]
MachineDriver = "openstack"
MachineOptions = [
        "openstack-auth-url=https://ma.keystone.url:5000/v3",
        "openstack-flavor-id=<type-de-machine>",
        "openstack-image-name=<image-systeme>",
        "openstack-tenant-name=<mon-projet-openstack>",
        "openstack-domain-name=Default",
        "openstack-net-id=<mon-id-reseau>",
        "openstack-sec-groups=<mes-groupes-de-securite>",
        "openstack-username=<login>",
        "openstack-password=<mot-de-passe>",
        "openstack-keypair-name=<ma-paire-de-clefs-ssh>",
        "openstack-private-key-file=<ma-clef-privee-ssh>",
        "openstack-ssh-user=<mon-utilisateur-ssh>"

Vous reconnaîtrez les credentials (user, pass, auth-url), mais également le namespacing d'OpenStack, à savoir tenant et domain. Notez qu'aujourd'hui on parle plutôt de project et domain ...

En l'état vous obtiendrez ce genre de logs :

ERROR: Error creating machine: Error in driver during machine creation: Expected HTTP response code [200 204] when accessing [GET https://ma.keystone.url:5000/v2.0/tenants], but got 404 instead  driver=openstack name=runner-b099e1bc-master-privileged-1542787257-7b239e1a operation=create
ERROR: {"error": {"message": "(https://ma.keystone.url:5000/v2.0/tenants): The resource could not be found.", "code": 404, "title": "Not Found"}}  driver=openstack

L'idée est donc d'injecter les informations relative à l'API v3 via les variables d'environnements, car docker-machine utilise des outils OpenStack qui ... wait for it ... lisent l'environnement.

[[runners]]
name = "Mon runner OpenStack”
url = "https://mon.gitlab.tld/"
token = "mon-token-gitlab-ci-runner"
executor = "docker+machine"
environment = [
        "OS_PROJECT_NAME=mon-projet-openstack",
        "OS_IDENTITY_API_VERSION=3",
        "OS_USER_DOMAIN_NAME=Default"
        ]
[runners.machine]
MachineDriver = "openstack"
MachineOptions = [
        "openstack-auth-url=https://ma.keystone.url:5000/v3",
        "openstack-flavor-id=<type-de-machine>",
        "openstack-image-name=<image-systeme>",
        "openstack-tenant-id=<mon-id-de-projet-openstack>",
        "openstack-domain-name=Default",
        "openstack-net-id=<mon-id-reseau>",
        "openstack-sec-groups=<mes-groupes-de-securite>",
        "openstack-username=<login>",
        "openstack-password=<mot-de-passe>",
        "openstack-keypair-name=<ma-paire-de-clefs-ssh>",
        "openstack-private-key-file=<ma-clef-privee-ssh>",
        "openstack-ssh-user=<mon-utilisateur-ssh>"
        ]

Attention, notez aussi que openstack-tenant-name est remplacé par openstack-tenant-id, cela évite que docker-machine ne résolve le nom du tenant via un call API v2. Et il faut bien conserver openstack-domain-name en sus de la variable OS_USER_DOMAIN_NAME.

Autoscaling

Nous reste alors à configurer l'autoscaling. Il est possible de choisir le nombre mini de runner disponibles à tout instant, avec l'option de réduire ces valeurs en dehors des horaires de bureau. Je ne peux que vous conseiller la documentation gitlab sur le sujet qui est tout à fait claire et complète : autoscale.

Pour les impatients, voici un exemple de configuration :

  [runners.machine]
    IdleCount = 2
    IdleTime = 3600
    MachineDriver = "openstack"
    MachineName = "gitlab-runner-%s"
    MaxBuilds = 100
    OffPeakPeriods = [
    "* * * * * sat,sun *",
    "* * 0-8,21-23 * * mon-fri *"
    ]
    OffPeakTimezone = "Europe/Paris"
    OffPeakIdleCount = 1
    OffPeakIdleTime = 1800
    MachineOptions = [
    "openstack-auth-url=<caviardé>",
    "openstack-domain-name=Default",
    "openstack-username=gitlab",
    "openstack-password=<caviardé>",
    "openstack-tenant-id=d41899bd10e84d96b8944823680f4e5d",
    "openstack-flavor-id=4a72fc71-a657-4578-a3c8-44aa8af24ad0",
    "openstack-image-name=Ubuntu 18.04.1 (Bionic Beaver)",
    "openstack-net-id=c0441c58-15a0-46a9-98c2-2b782a718506",
    "openstack-ssh-user=ubuntu",
    "openstack-keypair-name=gitlab-runner",
    "openstack-private-key-file=/home/ubuntu/.ssh/gitlab.pem",
    "openstack-sec-groups=gitlab.enix.io"
    ]

Conclusion

Avec un gitlab-runner en 12.1.0, docker-machine en 0.16.1, et OpenStack en release Queen minimum, vous obtenez une intégration continue on-premises auto-scalable, de quoi envoyer du lourd niveau développement.

Vous noterez une limitation : on ne peut lancer que des containers Linux avec ce setup ?! Pas si sûr, les containers Windows via docker-machine c'est un sujet à creuser ... dans un prochain billet de blog !