RousselTMLEARNING

Automation

Ansible: going further (testing, quality, CI/CD and performance)

Industrialize Ansible: ansible-lint, yamllint, Molecule, continuous integration, collections, execution environments, mitogen and performance optimization.

1. Ensuring code quality #

Linters and tests.

yamllint and ansible-lint #

pip install ansible-lint yamllint molecule molecule-plugins[docker]

yamllint .
ansible-lint                              # analyse tout le projet
ansible-lint --profile production site.yml
ansible-lint --fix                        # corrige automatiquement ce qui peut l'être

# .ansible-lint
profile: production
skip_list:
  - yaml[line-length]

ansible-lint flags, for example, modules without an FQCN (apt instead of ansible.builtin.apt), tasks without a name, using shell instead of a module, or missing file permissions.

Testing a role with Molecule #

Quality pipeline
Each step blocks the next one if it fails.
cd roles/nginx
molecule init scenario default --driver-name docker

# molecule/default/molecule.yml
driver: { name: docker }
platforms:
  - name: instance
    image: geerlingguy/docker-debian12-ansible:latest
    pre_build_image: true
provisioner: { name: ansible }
verifier: { name: ansible }

molecule test           # create -> converge -> idempotence -> verify -> destroy
molecule converge       # appliquer seulement
molecule login          # se connecter dans le conteneur pour déboguer
# molecule/default/verify.yml
- name: Vérifier
  hosts: all
  tasks:
    - name: nginx répond
      ansible.builtin.uri: { url: http://localhost, status_code: 200 }
    - name: Le service est activé
      ansible.builtin.service_facts:
    - ansible.builtin.assert:
        that: ansible_facts.services['nginx.service'].state == 'running'
Good to know: Molecule runs the role twice and fails if the second pass contains changes: this is the idempotence test.

2. Continuous integration and delivery #

A complete pipeline.

GitLab CI example #

# .gitlab-ci.yml
stages: [lint, test, deploy-preprod, deploy-prod]

lint:
  stage: lint
  image: python:3.12
  script:
    - pip install ansible ansible-lint yamllint
    - yamllint . && ansible-lint

molecule:
  stage: test
  image: docker:27
  services: [docker:27-dind]
  script:
    - pip install molecule molecule-plugins[docker] ansible
    - cd roles/nginx && molecule test

deploy-preprod:
  stage: deploy-preprod
  script:
    - ansible-galaxy install -r requirements.yml
    - ansible-playbook site.yml -i inventories/preprod --check --diff
    - ansible-playbook site.yml -i inventories/preprod

deploy-prod:
  stage: deploy-prod
  when: manual
  only: [main]
  script:
    - ansible-playbook site.yml -i inventories/prod --vault-password-file <(echo "$VAULT_PASSWORD")

3. Reproducibility: collections and execution environments #

Pinning dependencies.

Collections and requirements #

ansible-galaxy collection list
ansible-galaxy collection install -r requirements.yml -p ./collections
ansible-galaxy collection build                 # packager sa propre collection
ansible-galaxy collection init mon_ns.ma_collection

Execution environments #

An Execution Environment (EE) is a container image containing Ansible, its collections and the Python/system dependencies. The same EE runs on the developer's workstation and in AWX: no more « it works on my machine ».

pip install ansible-builder ansible-navigator
cat > execution-environment.yml <<'EOF'
version: 3
images:
  base_image:
    name: quay.io/fedora/fedora:40
dependencies:
  ansible_core: { package_pip: ansible-core }
  ansible_runner: { package_pip: ansible-runner }
  galaxy: requirements.yml
  python: requirements.txt
EOF
ansible-builder build -t registry.lab/ee-infra:1.0
ansible-navigator run site.yml -i inventories/lab --eei registry.lab/ee-infra:1.0 --mode stdout

4. Performance #

Speeding up thousands of hosts.

ansible.cfg settings #

[defaults]
forks = 50
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
callbacks_enabled = profile_tasks, timer

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=120s
retries = 3
ansible-playbook site.yml          # avec profile_tasks, affiche les tâches les plus lentes
# TASKS RECAP *****************************
# Installer les paquets ................. 41.20s
# Déployer le template .................. 3.12s

Execution strategies #

- hosts: all
  strategy: free         # chaque hôte avance à son rythme (sans attendre les plus lents)
  gather_facts: false
  tasks:
    - ansible.builtin.package: { name: htop, state: present }
      async: 300         # lancer en tâche de fond
      poll: 0
Common mistake: strategy: free is not suited to ordered deployments: a task may run on some hosts before it has finished elsewhere.
Good practice: Measure before optimizing: enable profile_tasks and tackle the 3 longest tasks (often fact gathering and package installations).

Related articles

Ansible: the inventory (static, dynamic, groups and variables)

Understand and master the Ansible inventory: INI and YAML files, groups, host and group variables, patterns, …

Ansible: ad-hoc commands

Run immediate actions on a fleet of servers with ansible: the ping, command, shell, copy, apt, service and se…

Ansible: structuring your code with roles

Create, organize and reuse Ansible roles: directory layout, defaults, tasks, handlers, templates, dependencie…

Keep learning

Support us

♥ SUPPORT US

Support RousselTM Learning

Courses, articles, labs and certificates stay free with no intrusive ads. A voluntary contribution, of any amount you choose, pays for hosting, lab environments and new content.

Contribute with PayPal Any amount · card or PayPal accountLearn more
QR code PayPal
Or scan this QR code with your phone

Thank you, every contribution counts!