Loading search data...

Les environnements d’exécution d’Ansible

Les environnements d’exécution Ansible sont là pour vous aider à écrire et à exécuter des playbooks quel que soit le contexte. Ils définissent donc des environnements portables et partageables pour exécuter des tâches Ansible.

Ces environnements ont été créés pour faciliter le développement de tâches d’automatisation et de contenu Ansible destinés à être exécutés dans AWX , Ansible Tower ou sur Ansible Automation Platform, et cela de manière cohérente. Mais nous allons voir que nous pouvons les utiliser aussi dans nos chaines de CI/CD.

Principes

L’utilisation de code Ansible possédant des dépendances autres que celles par défaut peuvent être délicat à mettre en place et à maintenir. Les environnements d’exécution d’Ansible se basent donc sur des images OCI dans lequel sont déposés Ansible Core, les collections et les dépendances python nécessaires à l'exécution d’un Code Ansible.

Un outil spécifique, ansible-builder est mis à disposition pour faciliter la création de ces images. Cet outil utilise l’image de base Red Hat Enterprise Linux pour y installer tout cet environnement d’exécution.

Un autre outil, ansible-runner permet lui l’exécution du code Ansible dans ces environnements avec des moyens d’isolation et de protection de ceux-ci.

Voyons comment mettre en œuvre ces environnements d’exécution avec ces deux outils que sont ansible-builder et ansible-runner.

Ansible-builder

Comme dit plus haut, ansible-builder est un outil qui automatise le processus de création d’environnements d’exécution.

Installation d’ansible-builder

Bien sûr, il est nécessaire d’avoir installé docker ou podman sur la machine de build. Comme le reste de la collection Ansible ansible-builder est écrit en python et s’installe donc avec pip.

pip install ansible-builder --user

Je préfère installer ces outils au niveau user plutôt que système d’où l’utilisation de l’option --user.

Fichier de définition d’ansible-builder

ansible-builder utilise un fichier de définition pour décrire ce qu’il doit contenir. Ce fichier est écrit en YAML et se nomme execution-environment.yml dont voici un exemple :

---
version: 1

build_arg_defaults:
  EE_BASE_IMAGE: 'quay.io/ansible/ansible-runner:stable-2.10-devel'

ansible_config: 'ansible.cfg'

dependencies:
  galaxy: requirements.yml
  python: requirements.txt
  system: bindep.txt

additional_build_steps:
  prepend: |
    RUN whoami
    RUN cat /etc/os-release    
  append:
    - RUN echo This is a post-install command!
    - RUN ls -la /etc

build_arg_defaults : permet de définir les images de base, EE_BASE_IMAGE et de build EE_BUILDER_IMAGE qui seront utilisé pour construire l’environnement d’exécution. ANSIBLE_GALAXY_CLI_COLLECTION_OPTS permet de passer un flag -pre s’il est nécessaire d’installer des pre-release de collections.

ansible_config : permet d’inclure un fichier de configuration d’Ansible

dependencies : permet de définir les collections galaxy, les librairies python et les packages systèmes à installer.

additional_build_steps : permet d’ajouter des commandes s’exécutant avant (prepend) et/ou après (append) la phase d’installation des outils Ansible.

Construction d’un environnement d’exécution

Pour retrouver les images, il suffit de se rendre sur cette page de quay.io.

repository docker images ansible-builder

En cliquant sur l’icône de téléchargement, vous pourrez générer la commande docker pull avec son tag ou son SHA (ma préférence).

docker pull quay.io/ansible/ansible-runner@sha256:1fa60288b1686946783e0ac864a29fa97dfcf0d5776ca6fda77c41fe8f880587

Je vais prendre comme exemple la création d’une image permettant d’utiliser la collection k8s pour construire et piloter un cluster kubernetes.

---
version: 1

build_arg_defaults:
  EE_BASE_IMAGE: 'quay.io/ansible/ansible-runner@sha256:1fa60288b1686946783e0ac864a29fa97dfcf0d5776ca6fda77c41fe8f880587'

ansible_config: 'ansible.cfg'

dependencies:
  galaxy: requirements.yml
  python: requirements.txt

additional_build_steps:
  prepend: |
        RUN cat /etc/os-release

Le fichier ansible.cfg :

[defaults]
strategy=linear
inventory_file = .vagrant/provisioners/ansible/inventory
pipelining = True
gathering = smart
fact_caching_connection = /tmp/facts_cache
fact_caching = jsonfile
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
PreferredAuthentications=publickey%

Le fichier requirements.yml :

collections:
  - name: community.general
    version: 3.8.0
  - name: kubernetes.core
    version: 2.2.0
  - name: ansible.posix
    version: 1.3.0

Le fichier requirements.txt

kubernetes==21.7.0

Construction de l’image :

ansible-builder build --tag=my-custom-ee --verbosity 2
Running command:
  docker build -f context/Dockerfile -t ansible-execution-env:latest context
Complete! The build context can be found at: /home/vagrant/Projets/veille/test-ansible-runner/ansible-builder/context

Lors de l’exécution de cette commande on voit qu’il crée un répertoire context dans lequel on retrouve le Dockerfile généré :

ARG EE_BASE_IMAGE=quay.io/ansible/ansible-runner@sha256:1fa60288b1686946783e0ac864a29fa97dfcf0d5776ca6fda77c41fe8f880587
ARG EE_BUILDER_IMAGE=quay.io/ansible/ansible-builder:latest

FROM $EE_BASE_IMAGE as galaxy
ARG ANSIBLE_GALAXY_CLI_COLLECTION_OPTS=
USER root

ADD _build/ansible.cfg ~/.ansible.cfg

ADD _build /build
WORKDIR /build

RUN ansible-galaxy role install -r requirements.yml --roles-path /usr/share/ansible/roles
RUN ansible-galaxy collection install $ANSIBLE_GALAXY_CLI_COLLECTION_OPTS -r requirements.yml --collections-path /usr/share/ansible/collections

FROM $EE_BUILDER_IMAGE as builder

COPY --from=galaxy /usr/share/ansible /usr/share/ansible

ADD _build/requirements.txt requirements.txt
RUN ansible-builder introspect --sanitize --user-pip=requirements.txt --write-bindep=/tmp/src/bindep.txt --write-pip=/tmp/src/requirements.txt
RUN assemble

FROM $EE_BASE_IMAGE
USER root
RUN cat /etc/os-release

COPY --from=galaxy /usr/share/ansible /usr/share/ansible

COPY --from=builder /output/ /output/
RUN /output/install-from-bindep && rm -rf /output/wheels

On remarque qu’il utilise le multi-stage pour limiter la taille de l’image qui est tout de même de 910 MB. Par contre, je trouve bête qu’il génère une image taguée latest et pas avec le numéro de version du fichier de configuration.

Plus loin avec ansible-builder

Je vous renvoie à sa documentation

Ansible-runner

Maintenant que nous avons généré notre environnement d’exécution voyons comment l’utiliser avec ansible-runner.

Installation d’ansible-runner

Comme pour ansible-builder on l’installe avec pip :

pip install ansible-runner --user

Lancement d’un playbook avec ansible-runner

Je vais utiliser des machines provisionnées avec vagrant. Je vais reprendre le Vagrantfile de mon projet CKASandbox où j’ai enlevé la partie provisioning Ansible (la partie configurant le cluster)

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|

  base_ip_str = "10.240.0.1"
  number_master = 1 # Number of master nodes kubernetes
  cpu_master = 2
  mem_master = 1792
  number_worker = 1 # Number of workers nodes kubernetes
  cpu_worker = 1
  mem_worker = 1024
  config.vm.box = "generic/ubuntu2004" # Image for all installations
  kubectl_version = "1.23.1-00"
  kube_version = "1.23.1-00"
  docker_version = "5:20.10.12~3-0~ubuntu-focal"


# Compute nodes
  number_machines = number_master + number_worker

  nodes = []
  (0..number_machines).each do |i|
    case i
      when 0
        nodes[i] = {
          "name" => "controller",
          "ip" => "#{base_ip_str}#{i}"
        }
      when 1..number_master
        nodes[i] = {
          "name" => "master#{i}",
          "ip" => "#{base_ip_str}#{i}"
        }
      when number_master..number_machines
        nodes[i] = {
          "name" => "worker#{i-number_master}",
          "ip" => "#{base_ip_str}#{i}"
        }
    end
  end

# Provision VM
  nodes.each do |node|
    config.vm.define node["name"] do |machine|
      machine.vm.hostname = node["name"]
      machine.vm.provider "libvirt" do |lv|
        if (node["name"] =~ /master/)
          lv.cpus = cpu_master
          lv.memory = mem_master
        else
          lv.cpus = cpu_worker
          lv.memory = mem_worker
        end
      end
      machine.vm.synced_folder '.', '/vagrant', disabled: true
      machine.vm.network "private_network", ip: node["ip"]
      machine.vm.provision "ansible" do |ansible|
        ansible.playbook = "playbooks/provision.yml"
        ansible.groups = {
          "masters" => ["master[1:#{number_master}]"],
          "workers" => ["worker[1:#{number_worker}]"],
          "kubernetes:children" => ["masters", "workers"],
          "all:vars" => {
            "base_ip_str" => "#{base_ip_str}",
            "kubectl_version" => "#{kubectl_version}",
            "kube_version" => "#{kube_version}",
            "docker_version" => "#{docker_version}",
            "number_master" => "#{number_master}"
          }
        }
      end
    end
  end
end

Lancement du playbook avec ansible-runner :

ansible-runner run ./ --inventory .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory --container-image my-custom-ee:latest -p playbooks/init-cluster.yml

PLAY [masters[0]] **************************************************************

TASK [Check that cluster is not yet initialized] *******************************
ok: [master1]

TASK [Launch kubeadm init] *****************************************************
changed: [master1]
...

PLAY RECAP *********************************************************************
controller                 : ok=12   changed=8    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
master1                    : ok=8    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
worker1                    : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Tout s’est passé de manière impeccable alors que je n’ai pas utilisé d’environnement virtuel ansible comme d’habitude. Ansible n’était pas accessible depuis le répertoire où je l’ai lancé.

Ce qui est intéressant, c’est de voir qu’il génère un dossier artifacts contenant tout ce qui s’est passé durant l’exécution du playbook. Et c’est là qu’entre jeu un autre outil ansible : ansible-navigator

Pour rappel cet outil permet de lancer et d’analyser les artifacts produits par ansible. D’ailleurs il existe des paramètres permettant d’indiquer quelle est l’image d’environnement d’exécution.

Le fichier ansible-navigator.yml

---
ansible-navigator:
  execution-environment:
    enabled: True
    # environment-variables:
    #   pass:
    #     - ONE
    #     - TWO
    #     - THREE
    #   set:
    #     KEY1: VALUE1
    #     KEY2: VALUE2
    #     KEY3: VALUE3
    image: my-custom-ee
    pull-policy: never
    # volume-mounts:
    # - src: "/test1"
    #   dest: "/test1"
    #   label: "Z"
    # container-options:
    # - "--net=host"

  ansible:
     inventories:
     - .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory
     playbook: playbooks/init-cluster.yml

  logging:
    level: debug

  editor:
    command: code-server {filename}
    console: false

  playbook-artifact:
    enable: True
    replay: artifacts/ansible_artifact.json
    save-as: artifacts/ansible_artifact.jsonl

Pour le moment je n’ai pas réussi à le faire fonctionner, mais dès que je trouve la solution, je la publierai. Mon erreur : Please enter a valid file path

Plus loin avec ansible-runner

Le gain que je vois tout de suite :

  • la simplification de la construction des nœuds d’exécution des playbooks
  • l'immutabilité des images produites

Je vois tout de suite l’intégration d'ansible-builder et d'ansible-runner dans mes chaines de CI/CD. On pourrait par exemple utiliser renovate couplé à du terraform pour construire un environnement où on testerait qu’on peut toujours construire notre infrastructure avec ces nouvelles versions de dépendances. Un vaste chantier.

ansible-runner regorge de paramètres qui permettent de sécuriser son fonctionnement, d’envoyer les artifacts sur des systèmes distants, de lancer des exécutions sur des machines distantes, de le lancer depuis le code source de vos applications python, …. Je vous renvoie à sa documentation

Voyons la mise en pratique des environnements d’exécution sur Ansible AWX.


Si vous avez apprécié cet article de blog, vous pouvez m'encourager à produire plus de contenu en m'offrant un café sur   Ko-Fi  . Vous pouvez aussi passer votre prochaine commande sur amazon, sans que cela ne nous coûte plus cher, via   ce lien  . Vous pouvez aussi partager le lien sur twitter ou linkedin via les boutons ci-dessous. Je vous remercie de votre soutien


Mots clés :

devops ansible tutorials infra as code formation ansible

Autres Articles


Commentaires: