Annexe 3: Automatisation du déploiement du cluster avec Vagrant et Ansible

Note : A l'origine, mon brevet devait intégrer une automatisation du déploiement des cluster Docker Swarm. Toutefois, ce mécanisme s'est montré inutile tant le déploiement du cluster Docker Swarm est une tâche rapide à effectuer. Ce mécanisme ne trouverait de plus sont utilité que dans le cadre d'une infrastructure permettant le déploiement de cluster Docker et non le déploiement d'application sur un cluster Docker. De plus, les outils de gestion d'hyperviseur intègre de plus en plus souvent des outils permettant le déploiement de cluster Docker. J'ajouterais sont usage en tant que banc de test pour le déploiement d'applications n'est plus nécessaire puisqu'il est possible de déployer en une quinzaine de minutes un cluster Docker avec un seul hôte sur n'importe quelle machine Linux. Il retrouverait cette utilité s'il était possible d'utiliser Vagrant pour déployer directement des machines virtuelles via l'infrastructure Open Nebula de SIPR. J'ajouterais également que l'installation des outils nécessaires au banc de test (Vagrant, Virtual Box ou un autre Hyperviseur) sont plus long que l'installation de Docker lui-même sur la machine. J'ai toutefois laissé les notes que j'avais prises pour cette partie de mon travail en annexe afin qu'elles ne soient pas perdues pour un futur usage.

Présentation des outils utilisés

Vagrant

Introduction et concepts

D'après la Wikipedia1 :

Vagrant est un logiciel libre et open-source pour la création et la configuration des environnements de développement virtuel. Il peut être considéré comme un wrapper autour de logiciels de virtualisation comme VirtualBox.

[...] Vagrant n'impose plus VirtualBox, mais fonctionne également avec d'autres logiciels de virtualisation tels que VMware, et prend en charge les environnements de serveurs comme Amazon EC2, à condition d'utiliser une "box" prévue pour le système de virtualisation choisi.

[...] Vagrant fournit un support natif des conteneurs Docker à l'exécution, au lieu d'un système d'exploitation entièrement virtualisé. Cela permet de réduire la charge en ressources puisque Docker utilise des conteneurs Linux légers.

virt-manager et terminal exécutant un cluster kubeadm sous ubuntu

Fig.: Instanciation d’un cluster de 3 machines avec Vagrant et Libvirt.

Concepts clés de Vagrant :

  • Box : image de machine virtuelle pour Vagrant. Elles peuvent être créées sur mesure, mais le plus simple est d’en télécharger une depuis le hub de Vagrant2
  • Vagrantfile : c’est le fichier qui décrit les machines virtuelles à instancier, un Vagrantfile permet les opérations suivantes sur les machines :
    • Configuration : nombre de cpu, RAM, configuration du réseau, partage de fichiers (via NFS)…
    • Provisionning : le system de provisionning de Vagrant permet d’exécuter des opérations sur les machines avec différent fournisseurs : shell, file, ansible, docker, salt, puppet…
    • Déploiement : Vagrant permet le déploiement d’application vers un hébergeur (Heroku, (S)FTP…)
  • Provider : fournisseur utilisé pour l’instanciation des machines virtuelles (Virtualbox, Libvirt, VMWare, Amazon EC2, LXC, Docker…)

Hyperviseur

Pour instancier ses machines virtuelles, Vagrant nécessite la présence d’un hyperviseur sur les machines cibles.

Généralement c’est Virtualbox qui est utilisé. Virtualbox est un gestionnaire de machine virtuelle bien connu et disponible pour de nombreuses plateformes.

Toutefois, le support officiel de Virtualbox (appartenant à Oracle) a été abandonné dans les dernières versions de Debian pour lui préférer des solutions totalement libres : l’API de virtualisation Libvirt3 4 et l’hyperviseur QEMU-KVM.

libvirt

Fig.: libvirt est une couche d’abstraction entre orchestrateurs de machines virtuelles et hyperviseurs.

Note :

  • Libvirt peut être appelée depuis de nombreux orchestrateurs (Vagrant, OpenStack…) et supporte de nombreux hyperviseurs (Xen, KVM, LXC…)
  • Libvirt et KVM peuvent être utilisés pour déployer des machines virtuelles dans l’environnement OpenNebula 5

Installer Vagrant et libvirt ou VirtualBox

L’idéal est d’installer la dernière version de VirtualBox et Vagrant depuis les sites officiels,

  • La version de VirtualBox fournie dans les dépôts de la distribution Ubuntu 18.04 est tout à fait suffisante
  • La version de Vagrant fournie par les dépôts d’Ubuntu 18.04 est dépassée et rentrent en conflit avec les plugins officiels, il est donc nécessaire de télécharger la version officielle depuis le site de Vagrant
  • Installer le plugin docker-compose pour vagrant via vagrant plugin install vagrant-docker-compose6. Ce plugin donne accès à une commande de provisionnement pour docker-compose dans les fichiers Vagrantfile. Toutefois, il est tout à fait possible d'installer docker-compose le shell7
  • Pour utiliser libvirt au lieu de virtualbox :
    • Installer l'environnement libvirt, le gestionnaire de machine virtuelle virt-manager et l'hyperviseur QEMU-KVM : sudo apt install QEMU-KVM libvirt-daemon-system virt-manager
    • Installer les headers de libvirt (et les outilsnécessaires pour compiler le plugin libvirt pour vagrant) : sudo apt install build-essentials libvirt-dev
    • Installer le plugin libvirt pour vagrant : vagrant plugin install vagrant-libvirt

Utilisation de Vagrant :

# on recupere un vagrantfile definissant la box
vagrant init "ubuntu/xenial64"
# on crée la machine avec virtualbox pour libvirt, utiliser l'option --provider=libvirt
vagrant up --provider=virtualbox
# on se connecte à la machine en ssh
vagrant ssh
# on detruit la box
vagrant destroy
Exemple de configurations de machines virtuelles dans Vagrant pour vmware, virtualbox et libvirt
# Configure CPU & RAM per settings in machines.yml (Fusion)
srv.vm.provider 'vmware_fusion' do |vmw|
  vmw.vmx['memsize'] = machine['ram']
  vmw.vmx['numvcpus'] = machine['vcpu']
  if machine['nested'] == true
    vmw.vmx['vhv.enable'] = 'TRUE'
  end #if machine['nested']
end # srv.vm.provider 'vmware_fusion'

# Configure CPU & RAM per settings in machines.yml (VirtualBox)
srv.vm.provider 'virtualbox' do |vb, override|
  vb.memory = machine['ram']
  vb.cpus = machine['vcpu']
  override.vm.box = machine['box']['vb']
end # srv.vm.provider 'virtualbox'

# Configure CPU & RAM per settings in machines.yml (Libvirt)
srv.vm.provider 'libvirt' do |lv,override|
  lv.memory = machine['ram']
  lv.cpus = machine['vcpu']
  override.vm.box = machine['box']['lv']
  if machine['nested'] == true
    lv.nested = true
  end # if machine['nested']
end # srv.vm.provider 'libvirt'

Le fichier machine.yml contient les informations de configuration de chacune des machines, par exemple pour la machine manager :

- box:
    vmw: "generic/ubuntu1604"
    vb: "ubuntu/xenial64"
    lv: "generic/ubuntu1604"
  name: "manager"
  nics:
    - type: "private_network"
      ip_addr: "192.168.100.100"
  ram: "2048"
  vcpu: "1"

Ansible

Introduction et concepts

Ansible est un système d’exécution automatique de tâches sur des parcs de machines. Il est en particulier utilisé pour la mise en place du déploiement continu ou la mise à jour d’infrastructure.

  • Playbook: définitions de tâches ansible
  • Inventaire: liste les machines sur lesquelles les tâches ansible seront exécutées
  • Rôles: permettent de regrouper les tâches et dépendances de tâches ansible afin de les réutiliser et de les partager

Le principal avantage de Ansible est qu’il ne requiert pas d’agent spécifique sur les hôtes distants.

Ansible sera utilisé pour initialiser le cluster Docker Swarm sur les différents hôtes :

  • configurer et initialiser un ou plusieurs managers
  • configurer et initialiser les workers

Étapes du déploiement à automatiser

Il s'agira d'effectuer les tâches suivantes

  1. Configurer les services Docker sur le manager et les workers
  2. Initialiser le cluster sur le manager
  3. Initialiser le cluster sur les workers
  4. Lancer les conteneurs sur le manager : portainer/portainer-ce et registry:2
  5. Lancer le conteneur portainer/agent sur les workers
  6. Générer le proxy nginx sur le manager et le publier sur le registry
  7. Déployer le proxy nginx sur les workers depuis le registry

Dans un premier temps, le partage des fichiers entre les hôtes pourra être fait à travers le système de fichier de la machine de test (via Vagrant et Virtualbox). Mais par la suite, il devra être déployé sur le cluster lui-même via NFS.

Les outils que j’ai choisis d’utiliser sont :

  • Vagrant pour instancier les machines virtuelles de mon infrastructure de test avec Virtualbox et libvirt + QEMU-KVM comme hyperviseur pour héberger les machines virtuelles
  • Ansible pour l’automatisation de l’exécution de tâches sur les machines virtuelles

Jenkins

Jenkins est un outil dédier au déploiement et à l’intégration continue…

Déploiement d’un cluster Docker Swarm avec Ansible sur le banc de test

Structure du banc de test vagrant

infrastructure vagrant

Fig.: Schéma de l’infrastructure virtuelle de test

Fichier de configuration pour mettre en place un cluster Docker Swarm

Le cluster est composé de 3 nœuds : 1 manager et 2 workers. J’ai choisi d’utiliser la version stable de Debian pour les 3 machines virtuelles. L’hyperviseur utilisé est virtualbox, mais il peut être adapté très simplement pour libvirt.

workers=[
  {
    :hostname => "worker-1",
    :ip => "192.168.100.11",
  },
  {
    :hostname => "worker-2",
    :ip => "192.168.100.12",
  }
]

manager={
  :hostname => "manager",
  :ip => "192.168.100.10",
}

Vagrant.configure(2) do |config|

  config.vm.box = "debian/stretch64"

  # Install docker-py for ansible automation on all hosts
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get install -y python python-pip
    pip install docker-py
SHELL

  workers.each do |machine|

    config.vm.define machine[:hostname] do |node|

      node.vm.hostname = machine[:hostname]
      node.vm.network "private_network", ip: machine[:ip], name: "ens5"

      node.vm.provider "virtualbox" do |vb|
        vb.memory = 1024
        vb.cpus = 2
      end

      node.vm.provision "docker"

    end
  end

  config.vm.define manager[:hostname] do |node|

    node.vm.hostname = manager[:hostname]
    node.vm.network "private_network", ip: manager[:ip], name: "ens5"

    node.vm.provider "virtualbox" do |vb|
      vb.memory = 1024
      vb.cpus = 2
    end

    node.vm.provision :docker,
      images: ["portainer/portainer-ce", "registry:2"]

    node.vm.provision :docker_compose

    # Initialize swarm cluster
    node.vm.provision "ansible" do |ansible|
      ansible.playbook = "playbooks/swarm.yml"
      ansible.limit = "all"
      ansible.extra_vars = {
        swarm_iface: "ens5"
      }
      ansible.groups = {
        "manager" => ["manager"],
        "worker"  => ["worker-[1:2]"],
      }
    end

  end
end

Ce fichier initialise les hôtes avec les fonctionnalités suivantes :

  • sur tous les hôtes :
    • installation de docker-py pour l’automatisation via ansible
  • workers :
    • installation de docker
  • manager
    • installation de docker et ajout des images portainer/portainer-ce et registry:2
    • ansible pour l’initialisation du cluster

Automatisation de l’initialisation du cluster avec Ansible

L’initialisation du cluster dans le fichier Vagrant se fait dans la configuration du manager :

node.vm.provision "ansible" do |ansible|
    ansible.playbook = "playbooks/swarm.yml"
    ansible.limit = "all"
    ansible.extra_vars = {
      swarm_iface: "eth1"
    }
    ansible.groups = {
      "manager" => ["manager"],
      "worker"  => ["worker-[1:2]"],
    }
end

Attention : l'interface réseau swarm_iface à utiliser pour le cluster est spécifiée dans les variables de Ansible.

Ce code appelle un fichier playbook ansible swarm.yml (source) qui va effectuer les tâches suivantes :

  1. la première liste de tâches crée deux groupes à partir de la liste des nœuds manager du cluster : swarm_manager_operational qui contient les nœuds manager déjà actifs et swarm_manager_bootstrap qui contient les nœuds manager à activer
  2. la seconde liste effectue la même opération pour les nœuds worker et crée les groupes swarm_worker_operational et swarm_worker_bootstrap
  3. si aucun nœud ne se trouve dans le groupe swarm_manager_operational , le premier nœud du groupe swarm_manager_bootstrap est activé
  4. sur le premier nœud manager actif swarm_manager_operational[0] on récupère les tokens nécessaires pour que les autres noeuds rejoignent le cluster, soit comme manager (swarm_manager_token), soit comme worker (swarm_worker_token). on récupère agalement la liste des adresses IP des managers
  5. on ajoute les nœuds manager encore inactif au cluster
  6. on ajoute les nœuds worker inactif au cluster
---
# determine the status of each manager node and break them
# into two groups:
#   - swarm_manager_operational (swarm is running and active)
#   - swarm_manager_bootstrap (host needs to be joined to the cluster)
- hosts: manager
  become: true
  tasks:
  
    - name: determine swarm status
      shell: >
        docker info --format \{\{.Swarm.LocalNodeState\}\}
      register: swarm_status
	
    - name: create swarm_manager_operational group
      add_host:
        hostname: "{{ item }}"
        groups: swarm_manager_operational
      with_items: "{{ ansible_play_hosts | default(play_hosts) }}"
      when: "'active' in hostvars[item].swarm_status.stdout_lines"
      run_once: true
	
    - name: create swarm_manager_bootstrap group
      add_host:
        hostname: "{{ item }}"
        groups: swarm_manager_bootstrap
      with_items: "{{ ansible_play_hosts | default(play_hosts) }}"
      when: "'active' not in hostvars[item].swarm_status.stdout_lines"
      run_once: true

# determine the status of each worker node and break them
# into two groups:
#   - swarm_worker_operational (host is joined to the swarm cluster)
#   - swarm_worker_bootstrap (host needs to be joined to the cluster)
- hosts: worker
  become: true
  tasks:
    - name: determine swarm status
      shell: >
        docker info --format \{\{.Swarm.LocalNodeState\}\}
      register: swarm_status
	
    - name: create swarm_worker_operational group
      add_host:
        hostname: "{{ item }}"
        groups: swarm_worker_operational
      with_items: "{{ ansible_play_hosts | default(play_hosts) }}"
      when: "'active' in hostvars[item].swarm_status.stdout_lines"
      run_once: true
	
    - name: create swarm_worker_bootstrap group
      add_host:
        hostname: "{{ item }}"
        groups: swarm_worker_bootstrap
      with_items: "{{ ansible_play_hosts | default(play_hosts) }}"
      when: "'active' not in hostvars[item].swarm_status.stdout_lines"
      run_once: true

# when the swarm_manager_operational group is empty, meaning there
# are no hosts running swarm, we need to initialize one of the hosts
# then add it to the swarm_manager_operational group
- hosts: swarm_manager_bootstrap[0]
  become: true
  tasks:
    - name: initialize swarm cluster
      shell: >
        docker swarm init
        --advertise-addr={{ swarm_iface | default('eth0') }}:2377
      when: "'swarm_manager_operational' not in groups"
      register: bootstrap_first_node

    - name: add initialized host to swarm_manager_operational group
      add_host:
        hostname: "{{ item }}"
        groups: swarm_manager_operational
      with_items: "{{ ansible_play_hosts | default(play_hosts) }}"
      when: bootstrap_first_node | changed

# retrieve the swarm tokens and populate a list of ips listening on
# the swarm port 2377
- hosts: swarm_manager_operational[0]
  become: true
  vars:
    iface: "{{ swarm_iface | default('eth0') }}"
  tasks:
    - name: retrieve swarm manager token
      shell: docker swarm join-token -q manager
      register: swarm_manager_token

    - name: retrieve swarm worker token
      shell: docker swarm join-token -q worker
      register: swarm_worker_token

    - name: populate list of manager ips
      add_host:
        hostname: "{{ hostvars[item]['ansible_' + iface]['ipv4']['address'] }}"
        groups: swarm_manager_ips
      with_items: "{{ ansible_play_hosts | default(play_hosts) }}"

# join the manager hosts not yet initialized to the swarm cluster
- hosts: swarm_manager_bootstrap:!swarm_manager_operational
  become: true
  vars:
    token: "{{ hostvars[groups['swarm_manager_operational'][0]]['swarm_manager_token']['stdout'] }}"
  tasks:
    - name: join manager nodes to cluster
      shell: >
        docker swarm join
        --advertise-addr={{ swarm_iface | default('eth0') }}:2377
        --token={{ token }}
        {{ groups['swarm_manager_ips'][0] }}:2377

# join the worker hosts not yet initialized to the swarm cluster
- hosts: swarm_worker_bootstrap
  become: true
  vars:
    token: "{{ hostvars[groups['swarm_manager_operational'][0]]['swarm_worker_token']['stdout'] }}"
  tasks:
    - name: join worker nodes to cluster
      shell: >
        docker swarm join
        --advertise-addr={{ swarm_iface | default('eth0') }}:2377
        --token={{ token }}
        {{ groups['swarm_manager_ips'][0] }}:2377

On installe aussi Docker-Py afin de pouvoir automatiser certaines tâches Docker via les plugins Ansible :

# Installation de pyopenssl (on suppose que Python Pip est déjà installé)
- hosts: all
  become: true
  tasks:
  - name: Install pyopenssl
    command: pip install pyopenssl

Lancement de l’infrastructure

# instanciation des vm
vagrant up
# après instanciation des vm
vagrant ssh manager

La commande docker node ls permet de lister les hôtes du swarm et de vérifier leur statut :

vagrant@manager:~$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
p02s2kqxm09ks721k4glwlnzc *   manager             Ready               Active              Leader              19.03.2
6wdzbrh8kdh0gf9doed3unj8e     worker-1            Ready               Active                                  19.03.2
x6a0vfv2rrg9lkh0z2refk5rw     worker-2            Ready               Active                                  19.03.2

Finalisation de l’infrastructure

Les images docker Portainer et le Registry sont déployés via le fichier Vagrantfile, mais elles pourraient l’être via Ansible.

Que ce soit le cas où non, il reste encore à instancier ces 2 services.

Mise en place de Portainer

Pour Portainer, l’opération est plutôt simple :

# launch portainer on the first manager node
- hosts: swarm_manager_operational[0]
  become: true
  tasks:
    - name: portainer_create_volume
      shell: >
        docker volume create portainer_data
      run_once: true
    - name: portainer_run_container
      shell: >
        docker run -d
        -p 8000:8000 -p 9000:9000
        --restart=unless-stopped
        --name portainer
        -v /var/run/docker.sock:/var/run/docker.sock
        -v portainer_data:/data
        portainer/portainer-ce
      run_once: true

Déploiement de Swarmpit

À la main :

git clone https://github.com/swarmpit/swarmpit -b master
docker stack deploy -c swarmpit/docker-compose.yml swarmpit

Avec l'installeur :

docker run -it --rm \
  --name swarmpit-installer \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  swarmpit/install:1.9

Version Ansible :

Mise en place du Registry

L’idée était d’expérimenter le déploiement d'un Registry sur le manager du cluster tel que cela avait été réalisé sur l’infrastructure de démonstration. Toutefois, cela s’est avéré délicat à réaliser : le sécuriser avec un certificat SSL s’est révélé un peu complexe dans les hôtes déployés via Vagrant.

L’utilisation du Registry n’étant pas requise pour la suite, je préfère la laisser de côté pour le moment. De plus ce problème n'existera pas lors du déploiement d’une véritable infrastructure puisque les machines se verront attribuer un fqdn ! Pour contourner le problème, un moyen simple est de déclarer un Registry non sécurisé.

Note : Afin de ne pas multiplier les hôtes inutilement, le Registry est instancié ici sur le manager, mais il pourrait l’être sur un hôte dédié.

Utiliser un Registry sécurisé

L’automatisation de la mise en place du Registry dans l'infrastructure Vagrant est un peu plus complexe. En particulier parce que son utilisation nécessite : un certificat ssl8 et, surtout, un FQDN dédié.

Ansible fournit un plugins pour gérer les certificats SSL. Ce plugin requiert l'installation d'une bibliothèque Python sur les hôtes :

# Installation de pyopenssl (on suppose que Python Pip est déjà installé)
- hosts: all
  become: true
  tasks:
  - name: Install pyopenssl
    command: pip install pyopenssl

Il est alors possible de générer le certificat nécessaire sur le manager :

# Création du certificat
- hosts: swarm_manager_operational[0]
  become: true
  tasks:
    - name: ca generate key
      openssl_privatekey:
        path: /etc/ssl/private/registry_ownca.key
        passphrase: ansible
        cipher: aes256
        size: 2048

    - name: registry generate csr
      openssl_csr:
        path: /etc/ssl/private/docker_registry.csr
        privatekey_path: /etc/ssl/private/docker_registry.key

    - name: registry generate self-signed crt
      openssl_certificate:
        path: /etc/ssl/private/docker_registry.crt
        csr_path: /etc/ssl/private/docker_registry.csr
        ownca_privatekey_path: /etc/ssl/private/registry_ownca.key
        ownca_privatekey_passphrase: ansible
        # We need to allow IPs for testing on the vagrant machines
        subject_alt_name:
          - ip:192.168.100.10
        provider: ownca

Mais impossible de faire fonctionner ce certificat en dehors de l'hôte local localhost.

Quelques pistes pour contourner ce problème :

  • générer le certificat (et sa clé) au préalable à la main sur une autre machine et l'injecter sur les hôtes
  • attribuer un fqdn au manager
    • en le définissant via le plugin DNS de Vagrant
    • en altérant les fichiers /etc/hosts avec Ansible
    • en altérant les fichiers /etc/hosts avec le plugin hostsupdater de Vagrant

Une fois généré, le certificat doit alors être copié dans les certificats reconnus par docker sur tous les hôtes. Cela peut se faire en utilisant la commande synchronize de ansible 9.

# Dans le code qui suit, swarm.manager est le fqdn attribué au registry 
- hosts: all
  tasks:
    - name: Create docker certificates folder
      shell: mkdir -p /etc/docker/certs.d/swarm.manager\:5000/
- hosts: swarm_manager_operational[0]
  tasks:
  	- name: Copy certificate in docker certificates folder
      copy:
        src: /etc/ssl/private/docker_registry.crt
        dest: /etc/docker/certs.d/swarm.manager\:5000/ca.crt
    - name: Transfer certificate from manager to workers
      synchronize:
        src: /etc/ssl/private/docker_registry.crt
        dest: /etc/docker/certs.d/swarm.manager\:5000/ca.crt

Une fois le certificat installé sur les hôtes, il reste à lancer le service du Registry :

# Lancement du registry sur le manager
- hosts: swarm_manager_operational[0]
  become: true
  tasks:
    - name: registry_run_registry
      shell: >
        docker run -d -p 5000:5000
        --restart=always
        -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/docker_registry.crt
        -e REGISTRY_HTTP_TLS_KEY=/certs/docker_registry.key
        -v /etc/ssl/private:/certs
        -v /var/lib/docker/registry:/var/lib/registry
        registry:2
      run_once: true
Utiliser un Registry non sécurisé

Bien que cela ne soit pas recommandé, Docker offre la possibilité d'utiliser un Registry non sécurisé

Créer le Registry

# Lancement du registry sur le manager
- hosts: swarm_manager_operational[0]
  become: true
  tasks:
    - name: registry_run_registry
      shell: >
        docker run -d -p 5000:5000
        --restart=always
        -v /var/lib/docker/registry:/var/lib/registry
        registry:2
      run_once: true

Il suffit alors de déclarer le registry dans la configuration de Docker sur chacun des hôtes du cluster (où 10.0.0.1 est l’IP du manager dans le cluster)

{
  "insecure-registries" : ["10.0.0.1:5000"]
}

Cette opération pourrait être réalisée via Ansible.

Configuration client server NFS

La création et le partage du point de montage NFS entre les hôtes peut se faire facilement avec Ansible10.

EN CONSTRUCTION

Inventaire des hôtes (peut-être généré directement à partir de l’inventaire des nœuds Docker) :

[nfs_server]
10.0.0.1

[nfs_clients]
10.0.0.2
10.0.0.3

Template de configuration du server NFS exports.j2 :

# /etc/exports: the access control list for filesystems which may be exported
#               to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/dockerdata            10.0.0.1/24(rw,no_subtree_check,no_root_squash,async)

Sur le manager :

---
- hosts: nfs_server
  remote_user: ubuntu
  sudo: yes

  tasks:
    - name: Create mountable dir
      file: path=/share state=directory mode=777 owner=root group=root

    - name: make sure the mount drive has a filesystem
      filesystem: fstype=ext4 dev={{ mountable_share_drive | default('/dev/xvdb') }}

    - name: set mountpoints
      mount: name=/share src={{ mountable_share_drive | default('/dev/xvdb') }} fstype=auto opts=defaults,nobootwait dump=0 passno=2 state=mounted

    - name: Ensure NFS utilities are installed.
      apt: name={{ item }} state=installed update_cache=yes
      with_items:
        - nfs-common
        - nfs-kernel-server

    - name: copy /etc/exports
      template: src=exports.j2 dest=/etc/exports owner=root group=root

    - name: restart nfs server
      service: name=nfs-kernel-server state=restarted

Sur les clients :

- hosts: nfs_clients
  remote_user: ubuntu
  sudo: yes

  tasks:
    - name: Ensure NFS common is installed.
      apt: name=nfs-common state=installed update_cache=yes

    - name: Create mountable dir
      file: path=/nfs state=directory mode=777 owner=root group=root

    - name: set mountpoints
      mount: name=/nfs src={{hostvars[groups['nfs_server'][0]]['ansible_eth0']['ipv4']['address']}}:/share fstype=nfs opts=defaults,nobootwait dump=0 passno=2 state=mounted

Proxy sur les workers

Étapes du Déploiement

  1. Générer les configurations des sites (Ansible)
  2. Build de l’image si nécessaire (Docker Compose)
  3. Déploiement du service (Docker Swarm)

Mise à jour des configurations

  1. Générer les configurations des sites (Ansible)
  2. Mettre à jour les services (Docker Swarm)

Template :

# templates/nginx_site_template.j2
upstream {{ stack_name }} {

    server {{ item }};
}

server {

    listen       80;
    server_name  {{ stack_name }}.{{ host_name }};
    
    client_max_body_size 128M;

    location / {

        proxy_pass http://{{ stack_name }};
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port 443;
        proxy_set_header X-NGINX-UPSTREAM {{ stack_name }};
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
        proxy_set_header Host $host;
    }
}

Playbook pour re-générer et recharger la configuration des proxies.

---
# Reload services for the proxy
- hosts: manager
  become: true
  tasks:
    - name: get all services connected to the proxy network
      shell: >
        docker network inspect -f {{.Id}} UCLouvainProxy
      register: proxy_services
    - name: check directory exists
      stat:
        path: ../sites
      register: sites_dir
    - name: find existing configurations
      find:
        paths: ../sites
        patterns: "*.conf"
      register: files_to_delete
    - name: delete existing configurations
      file:
        path: "{{ item.path }}"
        state: absent
      with_items: "{{ files_to_delete.files }}"
    - name: generate new configurations
      template:
      	src: ../templates/nginx_site_template.j2
      	dest: ../sites/{{ stack_name }}.conf
      with_items: proxy_services.output.lines
      vars:
        host_name: apps.sisg.ucl.ac.be
        stack_name: {{ item | regex_replace('^(.+)_(.+)$', '\\1') }}
      when: item != "frontend_proxy"
    - name: reload proxy service containers
      shell: >
        docker service update --force frontend_proxy
      

Note: version complète de l'update de service, si la version simplifiée ne fonctionne pas avec la version de Docker installée :

    - name: get id of proxy service
      shell: >
        docker stack services frontend --filter "name=frontend_proxy" --format "{{.ID}}"
      register: proxy_service_id
    - name: reload proxy service containers
      shell: >
        docker service update --force {{ proxy_service_id.output }}

Pour une utilisation hors de Vagrant

Il n'est actuellement pas possible d’utiliser Vagrant pour instancier des machines dans OpenNebula. Afin de pouvoir automatiser la mise en place du cluster sur une infrastructure en dehors de Vagrant, plusieurs étapes supplémentaires sont donc nécessaires.

En effet, nous nous sommes basés sur le mécanisme de provisionning de Vagrant pour installer de nombreuses dépendances dont docker et docker-compose de manière « implicite ».

Playbooks pour l’installation des services

Un playbook Ansible supplémentaire permettra de le faire playbooks/baseinstall.yml :

---
# Installation des packages de base nécessaires
- hosts: all
  become: true
  tasks:

  - name: Install packages that allow apt to be used over HTTPS
    apt:
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - apt-transport-https
      - ca-certificates
      - curl
      - gnupg-agent
      - software-properties-common
      - python-apt
      - git

Un playbook Ansible supplémentaire permettra de le faire playbooks/dockerinstall.yml :

---
# Installation de docker et docker-py sur des hôtes Debian et Ubuntu
- hosts: all
  become: true
  tasks:
  - name: Add an apt signing key for Docker
    apt_key:
      url: https://download.docker.com/linux/debian/gpg
      state: present
     when: ansible_distribution == 'Debian'

  - name: Add apt repository for stable version
    apt_repository:
      repo: deb [arch=amd64] https://download.docker.com/linux/debian {{ansible_lsb.release}} stable
      state: present
    when: ansible_distribution == 'Debian'
  
  - name: Add an apt signing key for Docker
    apt_key:
      url: https://download.docker.com/linux/ubuntu/gpg
      state: present
     when: ansible_distribution == 'Ubuntu'

  - name: Add apt repository for stable version
    apt_repository:
      repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ansible_lsb.release}} stable
      state: present
    when: ansible_distribution == 'Ubuntu'

  - name: Install docker and its dependecies
    apt:
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - docker-ce
      - docker-ce-cli
      - containerd.io
    notify:
      - docker status

On installe aussi les paquets Python requis pour Ansible playbooks/pythoninstall.yml:

---
# Installation de Python Pip et des paquets nécessaires pour l’automatisation avec Ansible
- hosts: all
  become: true
  tasks:
  - name: Install Python and PIP and pip dependencies
    apt:
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - python
      - python-pip
      
  - name: Install pyopenssl
    command: pip install pyopenssl
    
  - name: Install docker-py
    command: pip install docker-py

Inventaires et groupes ansibles

Dans le cas de l'automatisation avec Vagrant, le plugin vagrant-ansible se chargeait de définir les machines sur lesquelles effectuer le provisionning. Pour une utilisation grandeur nature, il faudra les définir dans les fichiers de configuration de Ansible.


1

Vagrant https://fr.wikipedia.org/wiki/Vagrant

2

Vagrantbox.es http://www.vagrantbox.es/

3

Site officiel https://libvirt.org/

4

« Documentation Ubuntu sur libvirt » https://ubuntu.com/server/docs/virtualization-libvirt

5

« Vagrant OpenNebula Tutorial » https://github.com/marcindulak/vagrant-opennebula-tutorial-centos7

6

« Vagrant Docker-Compose plugin » https://github.com/leighmcculloch/vagrant-docker-compose

7

voir https://ermaker.github.io/blog/2015/11/18/install-docker-and-docker-compose-on-vagrant.html et https://docs.docker.com/compose/install/ pour la marche à suivre

8

Private registry in swarm https://codeblog.dotsandbrackets.com/private-registry-swarm/ et Create your own docker registry https://www.frakkingsweet.com/create-your-own-docker-registry/

9

StackOverflow – How to copy files between two nodes using ansible https://stackoverflow.com/questions/25505146/how-to-copy-files-between-two-nodes-using-ansible

10

Setup NFS Server and Client Using Ansible https://advishnuprasad.com/blog/2016/03/29/setup-nfs-server-and-client-using-ansible/

11

« Wikipedia – Libvirt » https://fr.wikipedia.org/wiki/Libvirt

12

openssl_certificate – Generate and/or check OpenSSL certificates https://docs.ansible.com/ansible/latest/modules/openssl_certificate_module.html

13

« Share Compose configurations between files and projects » https://docs.docker.com/compose/extends/