Featured image of post Gérer les configs et secrets Docker Swarm avec Ansible

Gérer les configs et secrets Docker Swarm avec Ansible

Tutoriel simple pour déployer des configs ou des secrets dans Docker Swarm depuis un playbook Ansible

Ansible permet de créer, modifier ou supprimer des secrets (community.docker.docker_secret) et des configs (community.docker.docker_config) sur un cluster Docker Swarm de façon automatisée. Mais il y a quelques astuces à connaître avant de pouvoir les utiliser correctement dans un playbook reproductible.

Les modules docker_secret et docker_config

Voici les exemples fournis par la documentation Ansible pour créer un secret ou ou une config.

- name: Create secret foo (from a file on the control machine)
  community.docker.docker_secret:
    name: foo
    # If the file is JSON or binary, Ansible might modify it (because
    # it is first decoded and later re-encoded). Base64-encoding the
    # file directly after reading it prevents this to happen.
    data: "{{ lookup('file', '/path/to/secret/file') | b64encode }}"
    data_is_b64: true
    state: present
- name: Create config foo (from a file on the control machine)
  community.docker.docker_config:
    name: foo
    # If the file is JSON or binary, Ansible might modify it (because
    # it is first decoded and later re-encoded). Base64-encoding the
    # file directly after reading it prevents this to happen.
    data: "{{ lookup('file', '/path/to/config/file') | b64encode }}"
    data_is_b64: true
    state: present

Première info importante : le fichier source local sera décodé puis réencodé, il faut le transformer en base64 avant de l’envoyer, en utilisant le paramètre data_is_b64 pour qu’il soit correctement décodé à l’écriture.

Reproductibilité du playbook

On peut appliquer l’exemple sur un cas concret : le déploiement de Traefik sur un cluster swarm. Dans le playbook, on applique la tâche :

- name: Push config
  community.docker.docker_config:
    name: traefik
    # If the file is JSON or binary, Ansible might modify it (because
    # it is first decoded and later re-encoded). Base64-encoding the
    # file directly after reading it prevents this to happen.
    data: "{{ lookup('file', 'config/traefik.static.yml') | b64encode }}"
    data_is_b64: true
    state: present

Le 1er déploiement va bien se dérouler, mais le 2e va échouer avec ce message d’erreur :

<hostname> failed | msg: Error removing config traefik: 400 Client Error
  for http+docker://localhost/v1.41/configs/wqpa6u3jdcj5id6speqyf63oa:
  Bad Request ("rpc error: code = InvalidArgument desc = config 'traefik'
  is in use by the following service: traefik")

Le problème apparait à la fin du message : Docker ne permet pas de redéfinir une config ou un secret si un autre du même nom existe déjà. On pourrait avoir envie de rapidement supprimer puis recréer l’élement pendant le déploiement d’un nouveau service, mais il faudrait au préalable stopper l’existant, ce qui peut être gênant, voire absolument impossible selon les cas (mise en prod sans interruption de service).

Le playbook n’est plus reproductible.

Rotation de versions

Heureusement, Ansible propose une meilleure solution avec l’option rolling_versions qui n’apparait pas dans les exemples de la doc officielle. Concrètement, à chaque execution la tâche va créer une nouvelle config en suffixant le nom de base avec “_v1’, “_v2”, “_v3”, etc. Les nouveaux services peuvent démarrer directement avec la nouvelle version pendant que les anciens continuent d’utiliser la précédente. Simple.

En plus de ça, le module offre une autre option bien pratique : versions_to_keep. On peut indiquer le nombre d’anciennes configs à garder. Le ménage est fait à chaque exécution.

- name: Push config
  community.docker.docker_config:
    name: traefik
    data: "{{ lookup('file', 'config/traefik.static.yml') | b64encode }}"
    data_is_b64: true
    state: present
    rolling_versions: true
    versions_to_keep: 3
  register: traefik_cfg

Attention : il ne faut pas oublier de sauvegarder le résultat dans une variable (avec register) pour retrouver la bonne config au moment du déploiement.

Dans mon cas, je démarre directement depuis une stack.yml que j’ai envoyé sur le host en utilisant core.builtin.template. J’ai donc accès au dict traefik_cfg dans mon template, le nom final de la config crée est donc accessible avec {{ traefik_cfg.config_name }}.

version: "3.8"

configs:
  {{ traefik_cfg.config_name }}:
    external: true

services:
  proxy:
    image: traefik:2.9
    # [...]

    configs:
      - source: "{{ traefik_cfg.config_name }}"
        target: /etc/traefik/traefik.yml
        mode: 0400

    # [...]

Les options sont identiques pour le module community.docker.docker_secret.

Crédit photo : iMattSmart

Généré avec Hugo
Thème Stack conçu par Jimmy