Un lab de formation Ansible DevOps avec Docker

Présentation Ansible

Ansible est un logiciel libre appartenant à Redhat, pour configurer, assurer la gestion de configuration de serveurs. Ansible n’utilise aucun agent pour la gestion des noeuds (Agentless), Ansible utilise uniquement ssh. Il faut que Python soit installé sur l’ensemble des noeuds pour pouvoir être géré par Ansible.

Ansible peut configurer des serveurs en ligne de commandes, soit à partir de fichier de définition principalement au format yaml, c’est pourquoi on parle d’infrastructure as code : la mise en oeuvre, l’installation, la vie d’un serveur est codé dans un fichier. Ainsi, le déploiement d’un serveur est idempotents : signifie qu’une opération a le même effet qu’on l’applique une ou plusieurs fois.

Les principales alternatives à Ansible sont :

  • Chef
  • Puppet
  • Saltstack

Dans ce premier article de découverte d’Ansible (d’autres tutos Ansible suivront), je crée un environnement Docker composé de 3 containers qui seront des noeuds gérés par Ansible :

  • Docker Debian
  • Docker Ubuntu
  • Docker Centos

Plus un container Docker Ansible, qui permet de disposer et de ne pas installer Ansible sur son poste.

Création d’un container Docker embarquant Ansible

FROM alpine
RUN     set -x \
        && apk update \
        && apk upgrade \
        && apk add --no-cache ansible \
        && mkdir /etc/ansible \
        && mkdir /root/.ssh \
        && rm -rf /var/cache/apk/*
ENV ANSIBLE_HOST_KEY_CHECKING=False
ENTRYPOINT ["ansible"]

Création des containers Docker Debian, Ubuntu, Centos pour Ansible

Les Dockerfiles qui m’ont servi à contruire ces 3 containers sont disponibles dans le repo GitHub.

Mise en oeuvre du LAB Ansible avec Docker

Pour facilité la prise en main du tutorial, j’ai écrit un script d’init, qui prend en charge les tâches suivantes :

  • Suppression des instances existantes des 3 containers Debian, Ubuntu et Centos
  • Création d’une clé RSA pour la connexion de Ansible sans mot de passe ssh
  • Démarrage de 3 instances Docker Debian, Docker Ubuntu et Docker Centos
  • Injection de la clé ssh dans chaque instance Docker avec ssh-copy-id (dans cette phase le password est demandé : toor)
  • Création du fichier d’inventaires des hosts pour Ansible

#!/bin/bash

source ./box.sh

box "Remove existing containers" "blue" "red"
docker rm -f debian1 ubuntu1 centos1
box "Remove existing ssh key file" "blue" "red"
rm ./id_rsa
box "Create new container : Debian" "green" "blue"
docker run --name debian1 --hostname debian1 -d -v /etc/localtime:/etc/localtime:ro itwars/debian-ansible
box "Create new container : Ubuntu" "green" "blue"
docker run --name ubuntu1 --hostname ubuntu1 -d -v /etc/localtime:/etc/localtime:ro itwars/ubuntu-ansible
box "Create new container : Centos" "green" "blue"
docker run --name centos1 --hostname centos1 -d -v /etc/localtime:/etc/localtime:ro itwars/centos-ansible
box "Create new RSA ssh key" "green" "blue"
ssh-keygen -t rsa -b 4096 -C "$(whoami)@$(hostname)-$(date)" -f ./id_rsa -q -N ""
box "Copy ssh key on each container -- password is 'toor'" "yellow" "purple"
docker ps --format "{{.Names}}" | grep "debian\|ubuntu\|centos" | xargs -i docker inspect -f "{{ .NetworkSettings.IPAddress }}" {} | xargs -i ssh-copy-id -i ./id_rsa root@{}
box "Create Ansible hosts file" "green" "blue"
echo [debian] > hosts
docker ps --format "{{.Names}}" | grep "debian" | xargs -i docker inspect -f "{{ .NetworkSettings.IPAddress }}" {} | xargs -i echo {} ansible_user=root >> hosts
echo [ubuntu] >> hosts
docker ps --format "{{.Names}}" | grep "ubuntu" | xargs -i docker inspect -f "{{ .NetworkSettings.IPAddress }}" {} | xargs -i echo {} ansible_user=root >> hosts
echo [centos] >> hosts
docker ps --format "{{.Names}}" | grep "centos" | xargs -i docker inspect -f "{{ .NetworkSettings.IPAddress }}" {} | xargs -i echo {} ansible_user=root >> hosts

Je vais vérifier que l’ensemble des noeuds sont joignables par Ansible avec la commande suivante. Voici quelques explications sur les paramètres :

  • Ansible doit avoir un fichier hosts d’inventaire (généré par le script d’init)
  • Le container Ansible doit possèder le clé privé pour se connecter sur les noeuds (générée par le script d’init)
  • Ansible est invoqué avec les paramètres all -m ping, on applique le module ping à l’ensemble (all) des noeuds

docker run -it --rm -v `pwd`/hosts:/etc/ansible/hosts -v `pwd`/id_rsa:/root/.ssh/id_rsa itwars/ansible-cli all -m ping

Le résultat de la commande :

172.17.0.3 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
172.17.0.4 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
172.17.0.2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Installation d’un service avec Ansible

La commande suivante ressemble à la précédente, sauf les paramètres invoqués pour démarrer Ansible. Je demande d’utiliser le module apt sur le noeud Debian, le service à installer est nginx, s’il est déjà présent, Ansible ne fera rien, sinon, il installe le service :

docker run -it --rm -v `pwd`/hosts:/etc/ansible/hosts -v `pwd`/id_rsa:/root/.ssh/id_rsa itwars/ansible-cli debian -m apt -a "name=nginx state=present"

Démarrage du service avec Ansible

Je demande à Ansible d’appliquer au noeud debian le démarrage du service nginx :

docker run -it --rm -v `pwd`/hosts:/etc/ansible/hosts -v `pwd`/id_rsa:/root/.ssh/id_rsa itwars/ansible-cli debian -m service -a "name=nginx state=started"

Je vérifie que Nginx est bien démarré dans le noeud Debian avec la commande curl, voici à réponse :

curl 172.17.0.2

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Obtenir le setup d’un noeud avec Ansible

docker run -it --rm -v `pwd`/hosts:/etc/ansible/hosts -v `pwd`/id_rsa:/root/.ssh/id_rsa itwars/ansible-cli debian -m setup

La reponse du noeud Debian interrogé par Ansible :

172.17.0.2 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.17.0.2"
        ], 
        "ansible_all_ipv6_addresses": [], 
        "ansible_apparmor": {
            "status": "disabled"
        }, 
        "ansible_architecture": "x86_64", 
        "ansible_bios_date": "04/05/2017", 
        "ansible_bios_version": "YB1006", 
        "ansible_cmdline": {
            "BOOT_IMAGE": "/boot/vmlinuz-4.13.0-16-lowlatency", 
            "ro": true, 
            "root": "/dev/mapper/node1--vg-root"
        }, 
        "ansible_date_time": {
            "date": "2017-11-15", 
            "day": "15", 
            "epoch": "1510764362", 
            "hour": "17", 
            "iso8601": "2017-11-15T16:46:02Z", 
            "iso8601_basic": "20171115T174602353298", 
            "iso8601_basic_short": "20171115T174602", 
            "iso8601_micro": "2017-11-15T16:46:02.353599Z", 
            "minute": "46", 
            "month": "11", 
            "second": "02", 
            "time": "17:46:02", 
            "tz": "CET", 
            "tz_offset": "+0100", 
            "weekday": "Wednesday", 
            "weekday_number": "3", 
            "weeknumber": "46", 
            "year": "2017"
        }, 
        "ansible_default_ipv4": {
            "address": "172.17.0.2", 
            "alias": "eth0", 
            "broadcast": "global", 
            "gateway": "172.17.0.1", 
            "interface": "eth0", 
            "macaddress": "02:42:ac:11:00:02", 
            "mtu": 1500, 
            "netmask": "255.255.0.0", 
            "network": "172.17.0.0", 
            "type": "ether"
        }, 
        "ansible_default_ipv6": {}, 
        "ansible_devices": {
            "mmcblk0": {
                "holders": [], 
                "host": "", 
                "model": null, 
                "partitions": {}, 
                "removable": "0", 
                "rotational": "0", 
                "sas_address": null, 
                "sas_device_handle": null, 
                "scheduler_mode": "cfq", 
                "sectors": "60620800", 
                "sectorsize": "512", 
                "size": "28.91 GB", 
                "support_discard": "4194304", 
                "vendor": null
            }, 
            "mmcblk0boot0": {
                "holders": [], 
                "host": "", 
                "model": null, 
                "partitions": {}, 
                "removable": "0", 
                "rotational": "0", 
                "sas_address": null, 
                "sas_device_handle": null, 
                "scheduler_mode": "cfq", 
                "sectors": "8192", 
                "sectorsize": "512", 
                "size": "4.00 MB", 
                "support_discard": "4194304", 
                "vendor": null
            }, 
            "mmcblk0boot1": {
                "holders": [], 
                "host": "", 
                "model": null, 
                "partitions": {}, 
                "removable": "0", 
                "rotational": "0", 
                "sas_address": null, 
                "sas_device_handle": null, 
                "scheduler_mode": "cfq", 
                "sectors": "8192", 
                "sectorsize": "512", 
                "size": "4.00 MB", 
                "support_discard": "4194304", 
                "vendor": null
            }, 
            "mmcblk0rpmb": {
                "holders": [], 
                "host": "", 
                "model": null, 
                "partitions": {}, 
                "removable": "0", 
                "rotational": "0", 
                "sas_address": null, 
                "sas_device_handle": null, 
                "scheduler_mode": "cfq", 
                "sectors": "8192", 
                "sectorsize": "512", 
                "size": "4.00 MB", 
                "support_discard": "4194304", 
                "vendor": null
            }
        }, 
        "ansible_distribution": "Debian", 
        "ansible_distribution_major_version": "9", 
        "ansible_distribution_release": "stretch", 
        "ansible_distribution_version": "9.2", 
        "ansible_dns": {
            "nameservers": [
                "8.8.8.8", 
                "8.8.4.4"
            ]
        }, 
        "ansible_domain": "", 
        "ansible_effective_group_id": 0, 
        "ansible_effective_user_id": 0, 
        "ansible_env": {
            "HOME": "/root", 
            "LOGNAME": "root", 
            "MAIL": "/var/mail/root", 
            "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 
            "PWD": "/root", 
            "PYTHONPATH": "/tmp/ansible_Kb74HG/ansible_modlib.zip", 
            "SHELL": "/bin/bash", 
            "SHLVL": "1", 
            "SSH_CLIENT": "172.17.0.5 42916 22", 
            "SSH_CONNECTION": "172.17.0.5 42916 172.17.0.2 22", 
            "SSH_TTY": "/dev/pts/0", 
            "TERM": "xterm", 
            "USER": "root", 
            "_": "/bin/sh"
        }, 
        "ansible_eth0": {
            "active": true, 
            "device": "eth0", 
            "features": {}, 
            "ipv4": {
                "address": "172.17.0.2", 
                "broadcast": "global", 
                "netmask": "255.255.0.0", 
                "network": "172.17.0.0"
            }, 
            "macaddress": "02:42:ac:11:00:02", 
            "mtu": 1500, 
            "promisc": false, 
            "speed": 10000, 
            "type": "ether"
        }, 
        "ansible_fips": false, 
        "ansible_form_factor": "Desktop", 
        "ansible_fqdn": "debian1", 
        "ansible_gather_subset": [
            "hardware", 
            "network", 
            "virtual"
        ], 
        "ansible_hostname": "debian1", 
        "ansible_interfaces": [
            "lo", 
            "eth0"
        ], 
        "ansible_kernel": "4.13.0-16-lowlatency", 
        "ansible_lo": {
            "active": true, 
            "device": "lo", 
            "features": {}, 
            "ipv4": {
                "address": "127.0.0.1", 
                "broadcast": "host", 
                "netmask": "255.0.0.0", 
                "network": "127.0.0.0"
            }, 
            "mtu": 65536, 
            "promisc": false, 
            "type": "loopback"
        }, 
        "ansible_machine": "x86_64", 
        "ansible_machine_id": "40beb5eb909e171860ceee669da56e1d", 
        "ansible_memfree_mb": 386, 
        "ansible_memory_mb": {
            "nocache": {
                "free": 1422, 
                "used": 504
            }, 
            "real": {
                "free": 386, 
                "total": 1926, 
                "used": 1540
            }, 
            "swap": {
                "cached": 0, 
                "free": 1979, 
                "total": 1979, 
                "used": 0
            }
        }, 
        "ansible_memtotal_mb": 1926, 
        "ansible_mounts": [
            {
                "device": "/dev/mapper/node1--vg-root", 
                "fstype": "ext4", 
                "mount": "/usr/share/zoneinfo/UTC", 
                "options": "ro,relatime,errors=remount-ro,data=ordered,bind", 
                "size_available": 20539748352, 
                "size_total": 27839827968, 
                "uuid": "N/A"
            }, 
            {
                "device": "/dev/mapper/node1--vg-root", 
                "fstype": "ext4", 
                "mount": "/etc/resolv.conf", 
                "options": "rw,relatime,errors=remount-ro,data=ordered,bind", 
                "size_available": 20539748352, 
                "size_total": 27839827968, 
                "uuid": "N/A"
            }, 
            {
                "device": "/dev/mapper/node1--vg-root", 
                "fstype": "ext4", 
                "mount": "/etc/hostname", 
                "options": "rw,relatime,errors=remount-ro,data=ordered,bind", 
                "size_available": 20539748352, 
                "size_total": 27839827968, 
                "uuid": "N/A"
            }, 
            {
                "device": "/dev/mapper/node1--vg-root", 
                "fstype": "ext4", 
                "mount": "/etc/hosts", 
                "options": "rw,relatime,errors=remount-ro,data=ordered,bind", 
                "size_available": 20539748352, 
                "size_total": 27839827968, 
                "uuid": "N/A"
            }
        ], 
        "ansible_nodename": "debian1", 
        "ansible_os_family": "Debian", 
        "ansible_pkg_mgr": "apt", 
        "ansible_processor": [
            "GenuineIntel", 
            "Intel(R) Atom(TM) x5-Z8350  CPU @ 1.44GHz", 
            "GenuineIntel", 
            "Intel(R) Atom(TM) x5-Z8350  CPU @ 1.44GHz", 
            "GenuineIntel", 
            "Intel(R) Atom(TM) x5-Z8350  CPU @ 1.44GHz", 
            "GenuineIntel", 
            "Intel(R) Atom(TM) x5-Z8350  CPU @ 1.44GHz"
        ], 
        "ansible_processor_cores": 4, 
        "ansible_processor_count": 1, 
        "ansible_processor_threads_per_core": 1, 
        "ansible_processor_vcpus": 4, 
        "ansible_product_name": "Z83 II", 
        "ansible_product_serial": "To be filled by O.E.M.", 
        "ansible_product_uuid": "03000200-0400-0500-0006-000700080009", 
        "ansible_product_version": "To be filled by O.E.M.", 
        "ansible_python": {
            "executable": "/usr/bin/python", 
            "has_sslcontext": true, 
            "type": "CPython", 
            "version": {
                "major": 2, 
                "micro": 13, 
                "minor": 7, 
                "releaselevel": "final", 
                "serial": 0
            }, 
            "version_info": [
                2, 
                7, 
                13, 
                "final", 
                0
            ]
        }, 
        "ansible_python_version": "2.7.13", 
        "ansible_real_group_id": 0, 
        "ansible_real_user_id": 0, 
        "ansible_selinux": false, 
        "ansible_service_mgr": "sshd", 
        "ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMNckbv7yDRDX4WsLIqMARNK1c1TGcUI7IC3skULzSaljPycWXn3xYjQMNtcAwKmCize/FDJcxgKcZvs8rNxka4=", 
        "ansible_ssh_host_key_ed25519_public": "AAAAC3NzaC1lZDI1NTE5AAAAIMr2/KYk/41EC5JFIONgPPP0B8Me3Jl2i9v74dbm7F6r", 
        "ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABAQCnHsRdzKxaiSRIV54JPWBXEq6p2VJdgMHp/tfTUxCkf6hrsP1J/6HoWw7bDvmdLsObt2SXVqpBn6U8wlbW0eF9lt/rpFgtYVZRe0RTJtQBAI42GO05KU/p/ekpEfT4UiPPA5OOP3p+R83wBkHBGvDft1py5LLGg5n3r8adzn1Z+BKe5bWuX4xqY+2qNHSdqQzWMZRnticyJi5k0EqgSbNskusc/SzAaBkg4J4p66R5i5bhTVl/9PosiUKwdAHoDZr99TYRQtPt5EtD/DRJ7+St3v+RVAwSJhXoqOEVUVdWODuWcmB2oNta2sFlk/R6nkulnuH3VrJBRUFNUunoN8Nl", 
        "ansible_swapfree_mb": 1979, 
        "ansible_swaptotal_mb": 1979, 
        "ansible_system": "Linux", 
        "ansible_system_vendor": "AZW", 
        "ansible_uptime_seconds": 632536, 
        "ansible_user_dir": "/root", 
        "ansible_user_gecos": "root", 
        "ansible_user_gid": 0, 
        "ansible_user_id": "root", 
        "ansible_user_shell": "/bin/bash", 
        "ansible_user_uid": 0, 
        "ansible_userspace_architecture": "x86_64", 
        "ansible_userspace_bits": "64", 
        "ansible_virtualization_role": "guest", 
        "ansible_virtualization_type": "docker", 
        "module_setup": true
    }, 
    "changed": false
}

Conclusion de l’épisode 1 sur Ansible

Voilà, comment simplement se former à Ansible avec un LAB Docker. Vous pouvez adapter les 3 containers Docker qui sont disponibles sur le repository Github. La prochaine fois, je rentrerai dans des configurations avancées avec Ansible.