Le Labo #18 | Jouons avec Docker et SaltStack (update 12/09/2016)

Pour commencer…

Cela fait un mois depuis mon dernier article et je commençais aussi à trouver le temps assez long.
En fait, au dela de chercher une idée d’article pertinente pour vous, je travaillais d’arrache pieds à trouver des solutions sur les tâches qui m’ont été confiées.

Tout cela pour dire que mes articles les plus récents m’ont été très utile…et celui-la aura pour objectif d’expliquer comment provisionner des conteneurs Docker à l’aide d’un outil de Config Management et j’ai décidé d’utiliser SaltStack au lieu d’Ansible (pour lequel j’avais déjà publier quelques articles).

Pourquoi Salt ?

En fait, Salt et Ansible sont très similaire dans la syntaxe des fichiers…ils sont tout les deux basés sur Python.
Mais Salt se décline en deux entités :

  • le maître, appelé salt-master
  • l’esclave, appelé salt-minion

Les minions publient des clés qu’ils transmettent au maître et ce dernier doit les accepter pour pouvoir appliquer les states ou les pillars (selon la configuration définie à votre niveau).

Et propose d’utiliser les plugins de Python pour la configuration des minions.

Comment ça s’installe ?

Sur le site officiel du développeur/mainteneur de la solution, il a un guide expliquant comment l’installer.
J’utilise souvent le mode bootstrap pour installer aussi bien le master que le minion

  • Pour le master :
    curl -o bootstrap_salt.sh -L https://bootstrap.saltstack.com sudo sh bootstrap_salt.sh -M -N git develop

  • Pour le minion :
    curl -o bootstrap_salt.sh -L https://bootstrap.saltstack.com sudo sh bootstrap_salt.sh git develop

Comment les configurer ?

En ce qui concerne la configuration, pour cet article, je ferai dans le plus basique :

  • modification du /etc/hosts pour y ajouter l’adresse IP du master (voir ci-dessous)

    xxx.xxx.xxx.xxx salt

  • Création d’un dossier /srv/salt qui contiendra les states sur le saltmaster

Une fois ceci effectué, je vais déjà pouvoir lancer les commandes suivantes, celles-ci me permettront par la suite d’intégrer la clé du minion au magasin de clé acceptées par le maître.

  • Sur le minion, pour démarrer le service
    systemctl start salt-minion

  • Sur le master, pour démarrer le service
    systemctl start salt-master

  • Sur le master, pour visualiser les clés en attente et les accepter
    salt-key et salt-key -A -y

Les states (update 08/09/2016)

La configuration des deux serveurs Salt étant terminée, nous allons pouvoir nous concentrer sur les states.

  • Python : pour les besoins de ce lab, je vais avoir besoin d’installer Python 3.5 (qui est un prérequis à docker-py).

  • Docker, la librairie docker-py (et les dépendances qui vont bien) et des states pour build des images et déployer les conteneurs ui y font appel.

Le State Python

Voici à quoi ressemble ce state :

python-install:
  pkg.installed:
    - pkgs:
      - epel-release
      - yum-plugin-priorities
      - centos-release-scl-rh
      - centos-release-scl
      - scl-utils
      - rh-python35
      - python-pip

Le State Docker

Voici à quoi il ressemble :

docker:
  file.managed:
    - name: /etc/yum.repos.d/docker.repo
    - contents:
      - '[dockerrepo]'
      - name=Docker Repository
      - baseurl=https://yum.dockerproject.org/repo/main/centos/7/
      - enabled=1
      - gpgcheck=1
      - gpgkey=https://yum.dockerproject.org/gpg
  pkg.installed:
    - pkgs:
      - iptables
      - ca-certificates
      - lxc
      - docker-engine
  service.running:
    - name: docker

dockerpy-install:
  pip.installed:
    - name: docker-py

Le fichier d’orchestration

Le fonctionnement de Salt diffère très peu de celui d’Ansible et de ses rôles, mais pour que le déploiement se fasse sans erreur, il faut un fichier d’orchestration appelé top.sls dans lequel on va ajouter les étapes du déploiement.
Dans le cas qui m’intéresse ici, le fichier contiendra ce qui suit :

base:
  '*':
    - python
    - docker*

Pour lancer le déploiement, deux voies sont à notre disposition :

  • L’automatique
  • La semi automatique

La commande de déploiement automatique est la suivante :
salt <minion> state.highstate
Celle-ci lance les commandes les unes après les autres et affiche le résultat une fois que la toute dernière commande est passée.

La commande de déploiement semi automatique est la suivante :
salt <minion> state.sls <etape>
Avant cette commande; je peux découper le processus de déploiement en étape (correspondante, biensûr, à celles inscrite dans le top.sls) et par conséquent, d’éviter de perdre trop de temps à rechercher les éventuelles erreurs.

Docker et Salt (update 12/09/2016)

Après avoir effectué de nombreux tests de création de conteneurs à l’aide de Salt, je suis arrivé à la conclusion suivante :
C’est tout sauf une bonne idée…surtout si on souhaite garder la main sur la création/suppression de conteneurs par la suite.

Création d’images et de conteneurs à la main

En fait, créer un conteneur via Salt n’offre pas le même niveau de contrôle que si le conteneur était créé directement à l’aide des commandes de Docker, tout simplement parce que le module docker-py (utilisé par Salt) ne s’appuie pas sur la dernière version de l’API de Docker.
A la place, il est recommandé de suivre les étapes suivantes :

  • Builder l’image : docker build -t <tag> .
  • créer le conteneur : docker build -ditP --add-host salt:<ip master> --hostname <hostname> --name <name> <image>:<tag> salt-minion &
  • Accepter la clé sur le salt-master: salt-key -a "*" -y

Création d’images et de conteneurs via Salt

Autant être clair tout de suite, j’aborde ce qui suit en vous prévenant que la création d’image fonctionne très bien…mais pas la création des conteneurs.
Le déploiement des conteneurs est possible…mais ne fonctionne pas correctement.
Je ne peux que recommander d’utiliser le docker-engine pour le déploiement de conteneurs…ou l’API si vous la maîtrisez à fond.

docker.pull

#!py
def run():
  config = {}
  config['pull-image'] = {
    'docker.pulled': [
      {'name': 'ubuntu'},
      {'tag': 'latest'},
      {'force': True}
    ]
  }
  return config

docker.build

#!py
def run():
  config = {}
  config['docker.build'] = {
    'docker.built' [
      {'path': '/root/'},
      {'name': '<nom de l'image>'},
      {'tag': '<tag de l'image>'},
      {'nocache': True},
      {'rm': True}
    ]
  }
  return config

docker.run

{ % set cnt = salt['pillar.get']('cnt') %}
{ % set host = salt['pillar.get']('host') %}
{ % set img = salt['pillar.get']('img') %}

build-container:
  docker.installed:
    - container: '{{cnt}}'
    - hostname: '{{host}}'
    - image: '{{img}}'
    - tty: True
    - detach: True
    - mem_limit: !!null
    - start: True

cmd.run:
    - name: docker start '{{cnt}}'

Pour chacun d’entre, la commande incluera les pillar, voir ci-dessous : salt "*" state.sls docker.build pillar="{'name':'test','tag':'latest','path':'/root/'}"
salt "*" state.sls docker.pull pillar="{'tag':'ubuntu:latest'}"
salt "*" state.sls docker.run pillar="{'cnt':'test','host':'test','img':'app:latest'}"

Conclusion

Même si les erreurs sont assez parlante, les journaux de logs d’erreurs sont parfois assez indigeste…et plus encore ceux de Salt…tout ça pour dire que mon prochain article aura probablement un lien avec la stack ELK.