kvm – ajouter de l’espace disque à chaud

En train d’importer un blog avec 36000 entrées à partir d’un fichier de WRX de 54 Mo, je voyais l’espace disque se remplir à vue d’oeil ! à 92% munin commençait à m’envoyer des mails d’alerte.

Pas de panique, comme j’utilise kvm et lvm, l’ajout d’espace disque peut se faire à chaud. La difficulté était que je devais tout faire en shell distant.

J’ai ainsi réussi à sauver mon processus d’import en ajoutant un disque et augmenter le volume logique mais le disque virtuelle était au mauvais format: 20 Go virtuel et 20 Go sur l’hôte même si il etait vide.

Finalement voici la méthode pour ajouter à chaud de l’espace que une VM en train de staurer.

Créer un nouveau disque

Se mettre sur l’hôte et en tant que root en remplaçant staging-2.qcow par c3 qu2 vous voulez:

qemu-img create -f qcow2 /var/lib/libvirt/images/staging-2.qcow2 20G

Ne pas utiliser les option de preallocation

Attacher le disque à la VM

C’est là que réside la subtilité pour que la VM croire avoir à faire à un disque de 20G même si le fichier ne fait que quelques centaines de kilos. La commande qemu-img info permet de voir les propriétés du disque virtuel.

/lib/libvirt/images# qemu-img info staging-2.qcow2
image: test.qcow2
file format: qcow2
virtual size: 20G (21474836480 bytes)
disk size: 196K
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false



On va attacher le disque à la VM en précisant bien le driver qcow2

# virsh attach-disk staging /var/lib/libvirt/images/staging-2.qcow2 vdb --driver qemu --subdriver qcow2 --targetbus virtio --persistent --config --live

Remplacer vdb par un identifiant de device disponible dans la VM qui s’appelle ici staging.

En tant que root sur le VM, vérifier la présence du disque et qu’il a la bonne taille

# fdisk -l

Disque /dev/vdb : 20 GiB, 21474836480 octets, 41943040 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets

Ajouter le disque /dev/vdb dans le volume group à étendre., ici staging-vg

# vgextend staging-vg /dev/vdb

Ajouter tout l’espace au volume logique, ici /dev/staging-vg/root

# lvm lvextend -l +100%FREE /dev/staging-vg/root

Redimensionner le système de fichier qui se trouve ici dans /dev/mapper/staging--vg-root

resize2fs -p /dev/mapper/staging--vg-root

Docker – résultat de migration VM vers conteneurs

Les premiers resultats se confirment: le passage aux conteneurs docker me permet de faire de economies drastiques de ressources !

Avec 15 VM (soit 15 applications) qui occupaient 17 Go de RAM mon systeme hôte montrait des signes de saturation. J’ai déjà migré 7 applications et cela se traduit par un du nombre de service (conteneur docker) consequent car une application est constituée par exemple d’un service wordpress et d’un service de base de données. Actuellement pour 7 applications j’ai 17 conteneurs mais qui tiennent dans 4 Go de RAM.

Ceci me permet d’avoir une prod et une preprod. J’ai 14 applications dans 2 VM pour 8 Go de RAM totale.

Réduction du nombre de VM

Si au démarrage du projet j’ai ajouté 2 VM pour accueillir la production et la préproduction. La transformation progressive des anciennes VM de prod en conteneur à porté ses fruits.

La capture suivante montre bien l’empreinte mémoire des VM diminuer au fur et à mesure de leur disparition. Actuellement les VM occupent 14 Go contre 17 Go avant. Le gain semble faible en réalité il est très important…. car j’ai aujourd’hui le double d’application: une prod et une preprod. Je suis donc virtuellement passé de 34 Go à 14 Go !

Multiplication des conteneurs

Quand on observe le contenu de la VM de production, on voit que la transformation de 7 application qui tenaient dans 8 VM à cause d’une VM dédié à Elasricsearch pour un dzq blog, a donné naissance à 16 conteneurs. Il faut ajouter le gitlab-runner pour effectuer les déploiements.

On remarque que les conteneurs consomment à peine 300 Mo chacun sauf celui d’Elasticsearch. On comprend alors la gabegie d’avoir une VM dédiée par application.

Charge mémoire de l’hôte

La gestion de la mémoire des VM est telle que une VM ne rend jamais à l’hôte la mémoire allouée. On peut par une action manuelle diminuer la taille de la mémoire mais cela ne rend pas la mémoire au système. Donc ce qui est alloué au bénéfice de l’overcommit finira par générer du swap. L’extinction des VM au fur et à mesure montre bien la diminution de la charge mémoire.

Charge mémoire de la VM docker

La VM de prod avec 4 Go exécute sans broncher tous les conteneurs au prix d’un overcommit important. Toutefois puisque les services ne sont pas utilisés en même temps, le swap n’a pas encore eu lieu mais le système est à sa limite. Je ne vais pas pouvoir décemment rester avec 4 Go de RAM par VM.

Docker – premier résultats de migration VM vers conteneurs

Le 23 mai dernier je me lançais dans la transformation de mes VM en conteneurs docker. Cette migration est un changement de paradigme et j’ai voulu de plus mettre en oeuvre de l’integration et du deploiement continu comme fondations de ma nouvelle infrastructure.

Ces prérequis ont un peu retardé les resultats visibles que cette transformation avait comme promesse:

  1. moins de VM
  2. moins d’empreinte mémoire

Souvenez-vous je partais de 15 VM qui offraient differents services (blog….) consommant 17Go de RAM en total et un système hôte qui était en overcommit avec du swap.

Nombre de VM

J’ai ajouté 2 VM: une production et une production. A ce jour j’ai dockerisé 5 VM donc le nombre total de VM est en baisse: -3. Mais il faut bien voir que je gagne également en terme de « robustesse » car j’ai une preprod qui n’existait pss avant. Dès la 2e VM dockerisée j’étais à l’équilibre.

Pour comparer réellement c’était comme si j’avais 15×2 VM (prod et prepod) et que maintenant j’ai ajouté 2 VM et retiré 5×2 VM. Donc au final j’ai virtuellement fait -8 en nombre de VM. Avec une préprod avant j’aurais été à l’équilibre dès la 1er VM dockerisée.

Empreinte memoire

Les 2 VM de prod et preprod consomment chacune 4 Go de RAM tandis que les 5 VM supprimées utilisaient au total 8 Go. Donc je suis à l’équilibre.

Je dispose, avec la même quantité de mémoire, d’une préprod pour mes 5 services conteneurisés.

On remarque que l’overcommit du système hôte a baissé dès que les 8Go de VM ont été enlevés. En revanche dans la VM de production qui héberge les 5 applications (ex-VM) sous forme de services dockers, les 4 Go sont bien remplis et semblent être juste

On voit de overcommit mais pas vraiment de swap. Ce résultat est encourageant et montre bien que la conteneurisation permet de mieux partager les ressources sans le gaspillage lié à la multiplication des OS dans le paradigme du tout virtualisé.

Je vais donc poursuivre l’expérience de mise sous docker et voir quand la VM de prod va craquer.

Docker – ma premiere VM dockerisée

Ce billet est le premier de mon blog édité dans sa version dockerisé. Enfin ! Depuis plus d’un mois je me lançai dans la migration de mes VM en conteneur docker. Je suis passé par l’étape préliminaire de la mise en place d’un outillage d’intégration continue et déploiement continue. La solution que j’ai trouvée est basée sur Gitlab et me permet d’avoir une grade grande agilité dans cette phase de migration.

Ce matin mon blog professionnel est passé de la version VM turnkey linux à une version docker. J’ai fais l’impasse sur la création d’une images personnalisée afin d’avoir rapidement un résultat qui fonctionne.

Je vais maintenant migrer rapidement toutes mes VM wordpress en conteneur docker et voir le gain obtenu en terme de ressources utilisées sur mon systeme hôtes.

Docker – Mise a jour du gitlab-runner dockerisé

Dans mon infra j’utilise gitlab-runner dockerisé pour déployer. Comme j’entrevois de devoir modifier souvent la configuration du service gitlab-runner, il me faut la mettre sous intégration continue au moins et voire en déploiement continue dans un second temps.

Un runner qui s’arrête lui-même

En intégrant le fichier de configuration standard de mon projet blog, le déploiement en environnement staging commence par arrêter le service et là:

docker-compose version 1.24.0, build 0aa59064
docker-py version: 3.7.2
CPython version: 3.6.8
OpenSSL version: OpenSSL 1.1.0j 20 Nov 2018
$ echo "deploy to staging"
deploy to staging
$ docker-compose down
Stopping runner_docker_1 ...
ERROR: Job failed (system failure): aborted: terminated

En effet le job s’execute dans un runner géré par le démon (service) gitlab-runner. En arrêtant le service, on arrête le runner et donc le job.

Un gitlab-runner bootstrap

Le gitlab-runner dockerisé sert à déployer les services. Si lui-même est un service déployable comme un autre, il faut donc un gitlab-runner de démarrage dédié. Ce dernier n’a pas intérêt à être dockerisé car on ne pourrait pas le mettre à jour comme les autres services.

Il faut donc avoir un gitlab-runner dit de bootstrap qui sera une installation système native. Ce dernier enregistera un runner marqué bootstrap qui communiquera avec le docker engine via le lancement d’un conteneur docker-compose.

Mise à jour du service gitlab conteneurisé

La mise à jour du service gitlab promettait de poser le même problème. Si un runner l’arrête, la constructions va s’arrêter. Voilà que je risquaitns d’atteindre la limite de mon infra à base de gitlab pour déployer !

Il en est rien ! Le runner peut stoper le service gitlab via docker-compose. A ce moment gitlab ne répond plus mais le runner continue de s’exécuter et redémarre le service gitlab. Gitlab est de nouveau disponible et reçoit le résultat du job.

Donc je peux mettre a jour mon service Gitlab via gitlab-runner. Pour cela j’utilise le gitlab-runner conteneurisé mais avec un runner docker utilisant la socket de l’hôte. Dans le schéma plus haut on voit que le gitlab-runner dockerisé gère 2 runner:

  1. l’un utilisant Dind pour l’intégration continue
  2. l’autre par socket binding pour le déploiement automatique du service gitlab conteneurisé

Tag de job

J’utilise les tag pour sectionnelle le bon runner en fonction du stage du job. Chaque runner est caractérisé par une combinaisons de tag qui le définit de manière unique.

Voici le fichier docker-compose.yml du service GitLab:

image:     
    name: docker/compose:1.24.0
    entrypoint: [""]
  
before_script:
    - docker info
    - docker-compose version

stages:
    - build
    - test
    - staging
    - production

run_gitlab:
    stage: test
    tags:
        - dind
        - infra
        - regular
        
    variables:
        # For non-Kubernetes executors, we use tcp://docker:2375/
        DOCKER_HOST: tcp://docker:2375/
        # When using dind, it's wise to use the overlayfs driver for
        # improved performance.
        DOCKER_DRIVER: overlay2
    services:
        - docker:dind
    script:
        - echo "run blog for testing"
        - docker-compose up -d
        - docker-compose ps

deploy_to_staging:
    stage: staging
    tags:
        - staging
        - docker
        - regular
    script:
        - echo "deploy to staging"
        - docker-compose down
        - docker-compose up -d
    only:
        - master


deploy_to_infra:
    stage: production
    tags: 
        - infra
        - docker
        - regular
    script: 
        - echo "deploy to dev infra"
        - docker-compose down
        - docker-compose up -d   
    only:
        - master
    when: manual

Dans le cas où le service est le gitlab-runner conteneurisé lui-même, il faut que le job utilise le gitlab-runner natif (que je qualifie de bootstrap). Voici le fichier docker-compose.yml du service gitlab-runner:

image:     
    name: docker/compose:1.24.0
    entrypoint: [""]
  
before_script:
    - docker info
    - docker-compose version

stages:
    - build
    - test
    - staging
    - production

run_runner:
    stage: test
    tags:
        - dind
        - infra
        - regular
        
    variables:
        # For non-Kubernetes executors, we use tcp://docker:2375/
        DOCKER_HOST: tcp://docker:2375/
        # When using dind, it's wise to use the overlayfs driver for
        # improved performance.
        DOCKER_DRIVER: overlay2
    services:
        - docker:dind
    script:
        - echo "run blog for testing"
        - docker-compose up -d
        - docker-compose ps

deploy_to_staging:
    stage: staging
    tags:
        - staging
        - docker
        - bootstrap
    script:
        - echo "deploy to staging"
        - docker-compose down
        - docker-compose up -d
    only:
        - master

deploy_to_production:
    stage: production
    tags: 
        - production
        - docker
        - bootstrap
    script: 
        - echo "deploy to production"
        - docker-compose down
        - docker-compose up -d   
    only:
        - master
    when: manual

deploy_to_infra:
    stage: production
    tags: 
        - infra
        - docker
        - bootstrap
    script: 
        - echo "deploy to dev infra"
        - docker-compose down
        - docker-compose up -d   
    only:
        - master
    when: manual

Docker – deploiement avec gitlab-runner

On a réussi à mettre en place des constructions conteneurisées avec docker in docker dans l’article précédent. Le fait d’utiliser docker in docker permet de rendre la construction repétable puisque même l’outil de construction (ici docker) est sous forme se conteneur. On ne depend pas d’une installation préalable sur le système qui exécute la construction. Nous définissons dans le mécanisme de construction le systeme de construction lui-même.

L’autre avantage est d’isoler chaque construction. Ainsi on peut avoir plusieurs constructions d’un même projet en parallèle sans avoir a ce soucier d’un même port exposé sur l’hôte. Pas besoin d’avoit un port par construction: si cela reste possible par branche, 2 commit successifs sur une même branche vont utiliser le même port donc il faut séquencer les construction etc. Dind permet d’avoir l’esprit tranquille.

Déploiement continu

Dans mon environnement d’exécution, une application disons le blog de cerf-volant est distanciée une seule fois. Je pourrais être tenté de n’avoir qu’une application instanciée autant de fois qu’il y a de blog par exemple. Mais chaque blog wordpress est légèrement différents avec des plugins différents et cette personnalisation est souvent longue donc il est plus judicieux d’avoir un service déjà personnalisé avec des images déjà personnalisées. Finalement je préfère avoir autant d’application (docker-compose.yml) qu’autrefois j’avais de VM.

La conséquence est que le déploiement est plus simple. Un application ne se deploie qu’une fois donc son port exposé sur l’hôte (la VM de production) lui est dédié. Dans ce cas docker in docker ne sert plus à rien. On gagne à passer par le démon docker de l’hôte pour lancer les services.

C’est pourquoi selon l’étape (stage) de la construction j’utilise dind ou pas. J’identifie 4 étapes essentielles:

  1. build
  2. test
  3. staging
  4. production

Les 2 premiers doivent se faire en environnent isolé c’est-à-dire avec docker in docker tandis que le 3e consiste en un déploiement automatisé en environnement de preprod et le dernier est un déploiement manuel en production.

Outils de déploiement

Le deploiement consite à lancer un service docker sur la VM de prepod ou de prod. Est-ce que les outils déjà rencontré jusque jusque-là peuvent répondre à ce besoin ? docker CLI, docker-compose et gitlab-runner sont-ils suffisants ?

Le lancement du service consiste en:

  1. rapatrier la définition du service sur le serveur de prod ou de preprod
  2. lancer le service avec docker-compose

L’étape 1 revient à rapatrier une copie des sources et a seconde étape revient utiliser docker-compose sur le demon docker de la VM de prod ou preprod.

Or ces fonctions sont remplies par gitlab-runner. Il suffit simplement de l’utiliser avec un job (construction) qui n’utilise pas dind mais le démon docker de l’hôte. On va utiliser la même configuration qu’en Integration: un executor de type docker. Il suffit pour cela de configurer le runner pour passer par le partage de socket (monter la socket de l’hôte comme socket docker du conteneur de construction (docker-compose).

Finalement la VM de prod ou de preprod n’aura comme prérequis que l’installation d’un docker engine et gitlab-runner dockerisé dans lequel on va créer le runner de déploiement.

Les aspects de sécurités devront être investigués: authentification du serveur gitlab voire du client gitlab-runner

Installation du serveur de préprOD

Il faut créer une VM avec un partitionnement LVM2 afi de pouvoir étendre l’espace disque de la VM si besoin. Ma configuration de départ est une VM à 4 Go de RAM et 20 Go de disque avec comme OS une débian 9.9.0.

  1. Installer docker selon la documentation officielle
  2. Installer docker-compose pour pouvoir installer le gitlab-runner de déploiement (même si on pourrait s’en passer et utiliser une version conteneurisé de docker-compose !)
  3. Télécharger la configuration du service du gitlab-runner sur la machine de préprod. Dans mon cas comme elle se trouve sur mon dépot git, je fais un clone….
version: "3.7"
services:
    docker:
        image: 'gitlab/gitlab-runner:alpine'
        restart: always
        volumes:
            - '/srv/gitlab-runner/config:/etc/gitlab-runner'
            - '/var/run/docker.sock:/var/run/docker.sock'

Configuration du runner de déploiement

Lancer le service gitlab-runner par la commande suivante dans le répertoire où se trouve le fichier docker-compose.yml, disons que ce répertoire s’appelle runner

docker-compose up -d

Voilà le serveur de préprod est presque prêt. Il ne reste qu’a instancier le runner dédié au déploiement dans cet environnement de préprod. Le conteneir gitlab-runner est un service qui prend en charge la création des runner.

Si le répertoire du fichier docker-compose.yml s’appelle runner alors le conteneur s’appelle runner_docker_1. On va s’y connecter par

docker exec -it runner_docker_1 bash

Puis entrer la commande:

gitlab-runner register

Les différents paramètres ont été vus dans le billet précédent et ne changent pas. En revanche le tag devra être renseigné à une valeur qui permettra de spécifier que le runner que l’on est en train de créer est dédié à la preprod. Utilisons par exemple le tag staging.

Quitter le shell bash pour revenir sur le shell initial de notre VM de préprod. Comme on persiste la configuration du gitlab-runner sur l’hôte, on va pouvoir aller la modifier pour relier la socket de docker du conteneur créé par le runner à la socket l’hôte. C’est par ce biais que le docker CLI (de l’image docker ou docker-compose) va communiquer avec le docker engine de l’hôte.

nano /srv/gitlab-runner/config/config.toml

Dans la section volumes apporter la modification suivante:

volumes = ["/var/run/docker.sock:/var/run/docker.sock","/cache"]

INSTLLATION DU SERVEUR DE PRODUCTION

Les actions sont identiques à l’exception qu’il faut utiliser un autre tag disons production à la place de staging.

Configuration du build

Le fichier .gitlab-ci.yml de mon projet blog prend finalement alors la forme ci-après. J’ai utilisé une combinaison de tag plus précise afin de spécifier les runner adaptés à chaque étape. Les tag peuvent être modifiés dans l’interface administrateur de gitlab.

 image:
    name: docker/compose:1.24.0
    entrypoint: [""]



 before_script:
   - docker info
   - docker-compose version

 stages:
   - build
   - test
   - staging
   - production
   
 

 run_blog:
   stage: test
   tags:
     - docker
     - dind
   variables:
    # For non-Kubernetes executors, we use tcp://docker:2375/
    DOCKER_HOST: tcp://docker:2375/
    # When using dind, it's wise to use the overlayfs driver for
    # improved performance.
    DOCKER_DRIVER: overlay2

   services:
     - docker:dind     
   script:
     - echo "run blog for testing"
     - docker-compose up -d
     - docker-compose ps

 #verify_running:
 #  stage: test
 #  tags: 
 #  image: golang # because I know it has curl installed
 #  script:
 #     - sleep 120
 #     - curl -v http://docker:8180
                         
    
 deploy_to_staging:
    stage: staging
    tags:
        - staging
        - docker
    script:
        - echo "deploy to staging"
        - docker-compose down
        - docker-compose up -d
    only:
    - master        
 
 deploy_to_production:
    stage: production
    tags:
        - production
        - docker
    script:
        - echo "deploy to production"
        - docker-compose down
        - docker-compose up -d
    only:
    - master
    when: manual

La prochaine étape sera de mettre sous déploiement continue le conteneur initial gitlab-runner qui a dû être installé manuellement. Ensuite je m’attacherai à la personnalisation de l’image wordpress et ce sera l’occasion de creuser les mise en place du proxy de registry et de relier les dockers engines (de ma machine hôte et ceux des VM de préprod et de prod) à mon propre registry.

Docker – gitlab-runner conteneurisé et dind

J’ai choisi de mettre en place l’intégration continue avec un gitlab-runner conteneurisé et Docker in docker (Dind) pour lancer les commandes docker.

L’avantage du gitlab-runner conteneurisé est de ne pas se soucier de l’installation sur le système hôte qui effectue la construction. Gitlab-runner est un processus client qui communique avec le serveur gitlab afin de récupérer les constructions à faire.

Docker in docker permet de démarrer un démon docker (docker engine) vierge auquel va se connecter le docker CLI (ou docker-compose) pour lancer les conteneurs. Les 2 grosses flèches dans le schémas suivant signifient « lance un processus »

Gitlab-runner en service

Le démon gitlab-runner est lancé en service docker via docker-compose. Dans un repertoire disons runner créer le fichier docker-compose.yml suivant:

version: "3.7"
services:
    docker:
        image: 'gitlab/gitlab-runner:alpine'
        restart: always
        volumes:
            - '/srv/gitlab-runner/config:/etc/gitlab-runner'
            - '/var/run/docker.sock:/var/run/docker.sock'

Le premier volume permet de rendre persistant la configuration tandis que le second permet de se connecter au démon docker de l’hôte. Ainsi le gitlab-runner va pouvoir lancer des conteneurs frères via docker CLI.

Lancer le service par

docker-compose up -d

Il faut ensuite connecter le runner avec gitlab. Pour cela la documentation du gitlab-runner conteneurisé propose de lancer un autre conteneur temporaire qui partage le même répertoire de config persistant sur l’hôte. A par de montrer que les conteneurs peuvent partager un même repertoire, je trouve que cela complique la compréhension. Il est plus simple de se connecter au conteneur que l’on vient de lancer et d’y effectuer la tâche d’administration consistant à enregistrer un nouveau runner.

le démon gitlab-runner peut gérer plusieurs runner.

Determiner le nom du conteneur en faisant un

docker ps

La sortie doit contenir une ligne de la forme

746a1ebc7908 gitlab/gitlab-runner:alpine "/usr/bin/dumb-init …" 3 days ago Up 3 days runner_docker_1

Disposant de cette information, on peut lancer un shell dans le conteneur par

docker exec -it runner_docker_1 bash

On dispose alors d’un shell en tant que root. On doit y entrer la commande:

gitlab-runner register -n --url \n

https://gitlab.bressure.net \n

--registration-token xxxxxxx-yyyyyyy \n

--executor docker \n

--description 'docker runner' \n

--docker-image 'docker:18.09.06' \n

--docker-privileged

et renseigner les informations obligatoires suivantes

  • serveur gitlab: https://gitlab.bressure.net
  • token de connexion: voir dans l’admin du serveur gitlab pour obtenir un token pour un runner partagé
  • type de d’executor: docker
  • image par defaut: docker:18.09.06
  • utiliser le mode privilégié

Construction docker-compose

L’application est un blog worpdress composée d’un service wordpress et d’un service mysql. Pour mémoire voici son fichier docker-compose.yml

version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     volumes:
       - www_data:/var/www/html
     ports:
       - "8180:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}
    www_data: {}

Comme il n’y a pas d’utilisation d’image docker personnalisées, la construction se limite la phase de test pour vérifier que l’application démarre. On utilise donc l’image docker/compose pour lancer principalement la commande docker-compose up

Voici le fichier CI de gitlab pour le projet blog

 image:
    name: docker/compose:1.24.0
    entrypoint: [""]



 before_script:
   - docker info
   - docker-compose version

 

 run_blog:
   stage: test
   tags:
     - docker
   variables:
    # For non-Kubernetes executors, we use tcp://docker:2375/
    DOCKER_HOST: tcp://docker:2375/
    # When using dind, it's wise to use the overlayfs driver for
    # improved performance.
    DOCKER_DRIVER: overlay2

   services:
     - docker:dind     
   script:
     - echo "run blog for testing"
     - docker-compose up -d
     - docker-compose ps

                         
    

Performances

Ce premier essai avec Dind révèle une première caractéristique. Chaque lancement d’une construction semble utiliser un nouveau conteneur docker. En tout cas il n’y a aucune persistance des images téléchargées. Ainsi à chaque exécution déclenchée par une modification de source (ici le fichier docker-compose.yml) les images de wordpress et mysql sont téléchargées. Avec ma connexion cela prend 5 minutes à chaque fois !

L’utilisation d’un proxy par le démon docker (dockerd) pourra être une solution que je mettrai en œuvre dans le prochain billet qui sera aussi l’occasion de faire du déploiement automatisé en environnement de préprod.

 

Docker – CI/CD avec GitLab

Nous avons un Gitlab offrant les services de dépôts de sources et d’images docker mis en place dans le billet Docker – Registry avec GitLab. L’étape suivante sera de mettre en oeuvre l’integration continue et par la suite le deploiement continu.

Répétablité de la construction

La construction doit être un processus répétable. Même en conservant les sources et les dependances, le système exécutant la construction n’est pas à l’abri de changement. Ainsi le maven doit être figé, ses outils et paramétrages également. Cela permet de reproduire la construction ailleur. C’est là que docker rentre sur la scène : on va dockeriser la construction.

Docker pour construire

Docker permet de mettre en boîte une application (code, dependance et paramétrage). Cela assure une exécution identique où que le code s’executera ensuite.

Comme notre but est de lancer des commandes docker dans la phase de construction, docker doit lancer du docker. D’un point de vue technique l’integration continue doit lancer un conteneur docker qui est lui-même docker.

Une double couche

C’est là où on entrevoit le changement de paradigme dans l’approche de l’exécution d’un programme. Le principe n’est pas nouveau et on peut faire une analogie avec Java.

Analogie avec Java

Java offre une promesse d’exécution identique quelque soit la platforme. Pourvu que l’on ait une JVM. Si le but de mon application est d’executer du code Java en entrée, il est sensé d’écrire une JVM en Java. Ainsi l’exécution d’un programme devient indépendante de la JVM hôte puisque celle-ce exécute une JVM. Pourquoi est-ce mieux ? pourquoi ne pas simplement exécuter le code java en entrée sur la JVM hôte ? Parce que le code java en entrée va avoit les effets de bord de la JVM hôte comme par exemple les librairies endorsed…

Performances

L’idée n’est pas nouvelle mais là où l’émulation d’une JVM par une JVM va entrainer des lenteurs car la seconde JVM sera exécutée par un code Java contrairement à la JVM hôte qui est en code natif du système et qui exécute la JVM émulée, avec Docker on a beaucoup moins ce problème.

En effet l’application dans le conteneur s’exécute à vitesse réelle sur le noyau de l’hôte. L’isolation se fait par le noyau de l’hôte via des techniques d’isolation du système de fichiers (chroot), de la memoire et des processus (cgroup) etc… docker c’est de l’isolation de processus et non pas de la virtualisation de materiel.

Docker in docker

Il existe une image docker de docker appelée Docker in docker (dind). L’objectif sera donc de faire en sorte que GitLab lance les constructions via ce docker conteneurisé.

Gitlab et le CI décentralisé

L’implémentation de l’intégration continue dans GitLab utilise un mecanisme client-serveur. Des processus clients appelés Gitlab-runner s’executent sur une machine de construction: le serveur hébergeant GitLab ou une tout autre machine.

Le client gitlab-runner peut au choix et entre autre d’executer

  1. une commande shell quelconque
  2. ou bien une commande docker via Dind

Le second exemple est un cas particulier où le gitlab-runner exécute une commande de construction via docker. Au lieu d’utiliser docker pour lancer un maven conteneurisé, par exemple, on va utiliser docker pour lancer un docker conteneurisé.

Il permet également de factoriser l’installation de docker utilisé. Toutes les constructions, quel que soit leur lieu, se feront de la même manière puisque conteneurisées !

L’installation du gitlab-runner sur le système qui va exécuter la construction peut elle-même être conteneurisée. Cette solution me paraît interessante dans l’optique de migrer à terme tous les composants de l’atelier logiciel (Gitlab, registry, gitlab-runner) sur une VM à conteneurs dédiée indépendante de ma machine hôte afin de réserver à cette dernière l’unique fonction de station de travail.

Au prochain billet, la mise en œuvre !

Docker – Registry avec GitLab

La registry est un dépôt d’images docker. Son utilisation est centrale dans l’architecture que je veux metttre en place comme indiqué dans Docker – un pas vers la registry. Il restait le choix d’utiliser Gitlab en tant que Registry ou bien l’implementation conteneur registry de docker même.

Sauvegarde et sécurité

Le registry sera un élément de l’infrastructure et en tant que tel il devra être sauvegardé. Or le service Gitlab est déjà defini avec un mapping vers le systèmes de fichier hôte. Sauvegarder ce montage permet de sauvegarder toutes les données de Gitlab: sources et registry !

De plus l’utilisation du registery impose de s’y connecter en HTTPS. Le client docker (docker CLI) est configuré ainsi par défaut. Je vais garder ce paramétrage qui sera fort utile dans le cas où je voudrais déplacer mon registry ailleur que sur localhost.

Il faut donc mettre en place un frontal pour faire le TLS, or mon service gitlab est déjà dernier un proxy SSL.

Enfin mon besoin de limiter les droits des accès de la production et de la préprod en lecture au registry et dépôt de source est déjà pris en charge par des mécanismes de clé et de jeton de déploiement.

Toutes ces considérations me font choisir l’utilisation du registry intégré à Gitlab.

Mise en oeuvre

Sa mise en oeuvre est très simple. Le fichier docker-compose.yml devient:

version: "3.7"
services:

 web:
   image: 'gitlab/gitlab-ce:latest'
   restart: always
   hostname: 'gitlab.bressure.net'
   environment:
     GITLAB_OMNIBUS_CONFIG: |
       external_url 'https://gitlab.bressure.net'
       nginx['listen_port'] = 80
       nginx['listen_https'] = false

       registry_external_url 'https://registry.bressure.net'
       registry_nginx['listen_portex'] = 80
       registry_nginx['listen_https'] = false
       # Add any other gitlab.rb configuration here, each on its own line

       gitlab_rails['gitlab_shell_ssh_port'] = 2222
   ports:
      - '8080:80'    
      - '2222:22'
      
   volumes:
     - '/srv/gitlab/config:/etc/gitlab'
     - '/srv/gitlab/logs:/var/log/gitlab'
     - '/srv/gitlab/data:/var/opt/gitlab'

Il suffit déclarer l’url externe du registry pour que gitlab affiche le lien dans l’interface du projet. Voici le schéma d’architecture :

Il permet l’utilisation suivante:

  1. sur la machine hôte on développe avec un éditeur
  2. COMMIT en local
  3. exécute en local et dans le cas particulier qui me concerne on lance un conteneur dit de DEV en local
  4. on verifie le fonctionnement attendu (test)
  5. on pousse les source sur GitLab par un PUSH
  6. on pousse eventulement l’image dans le registry

On remarque la mise à disposition de la preprod et à la prod des livrables se fait par 2 biais selon le type de livrable:

  • repository Git (Gitlab) pour les applications afin d’obtenir leur composition en terme de services ie en terme d’images. Il s’agit concrètement récupérer les fichiers docker-compose.yml
  • registry docker (Gitlab) pour les images. Je fais finir par y arriver quand les images actuelles ne me suffiront plus…

CI /CD

On remarque que les étapes 3,4 et 6 pourraient être effectuées par l’Integration Continue (IC). En effet une fois poussée sur Gitlab, le code doit passer des tests. Si ces derniers sont concluants, un livrable doit être produit (jar, ear, image docker….) et dans la cas d’application on peut même déployer directement en preprod.

Cela ouvre la voie vers l’exploration de la construction.

A suivre dans le prochain billet

Docker – Un pas vers la registry

Dans l’article précédent Docker – le réseau je suis arrivé à la conclusion de mettre en place 2 VM hébergeant chacune une infrastructure de service complète. L’une devra être la copie de l’autre et servira d’étape de validation préproduction avant d’installer réellement en production.

Afin d’assurer la prédictibilité de le l’exécution, ces 2 VM ne devront faire aucune construction d’image mais utiliser des images pré-construites mises à disposition dans ce que Docker appelle une registry dont Docker Hub est un exemple.

Registry autohebergée

Dans le schéma ci-dessus Gitlab sert de dépôt Git mais les VM de prod et preprod ne devront pas s’en servir que pour rapatrier les définitions des applications (docker-compose.yml) et ces dernières ne devront pas comporter des instructions de construction d’images mais uniquement des references vers les images récupérées dans le registry privé.

Je ne sais pas encore comment interdire cela. Peut-être en mettant des controle lors du push des fichiers de docker-compose.yml et en interdisant aux machines de prod et preprod d’accéder aux répertoires autres que ceux des applications au sein du dépôt Git.

Automatisation des constructions

Gitlab est également une plateforme d’intégration continue et de déploiement continu. Je vais pouvoir m’en servir pour construire mes images docker et déployer les applications en preprod. En investiguant le sujet j’ai decouvert que Gitlab est aussi une registry… à voir si je passe tout sur Gitlab ou bien je garde le registry de docker.

La suite au prochain billet…