Ansible

Automatise ton infrastructure

whoami

Agenda

Objectif

Automatiser l'installation et la configuration de son infrastructure

Périmètre d'action

Pourquoi automatiser ?

Un outil simple

Comment ça fonctionne ?

Comment ça fonctionne ?

Idempotent

TASK: [common/base | Start ntp service] *************
ok: [front-1]

TASK: [common/base | Set ulimits] *******************
changed: [front-1]

....

PLAY RECAP *************************************************

front-1 : ok=12   changed=9    unreachable=0    failed=0

Concepts de bases

Concepts de bases

Inventaire

Liste de machines, groupes et variables

Inventaire statique

prod-front-1    ansible_ssh_host="123.42.0.1"
prod-front-2    ansible_ssh_host="123.42.0.2"
prod-api-1      ansible_ssh_host="123.42.0.3"
prod-api-2      ansible_ssh_host="123.42.0.4"

[frontservers]
prod-front-1
prod-front-2

[appservers]
prod-api-1
prod-api-2

[appservers:vars]
ssl_certs="blurb.space.pem"

[all:children]
frontservers
appservers

[all:vars]
ansible_ssh_user="admin"
ansible_ssh_private_key_file="/ops/ssh/admin.id_rsa"

Playbooks

Tâche

Appel d'un module avec des paramètres

- name: MongoDB | Create user
  user: name="{{ mongodb_user }}" group="{{ mongodb_group }}"
    shell=/bin/bash
- name: Base | Install base packages
  apt: name="{{ item }}" state=present
  with_items:
    - ntp
    - htop
    - zsh
    - curl
    - apt-transport-https
    - fail2ban

Exemple de playbook pour installer haproxy

# install-haproxy.yml
---
- hosts: webservers
  sudo: yes
  vars:
    haproxy_version: 1.5.8-3+deb8u1
  tasks:
    - name: haproxy | Install haproxy
      apt: name="haproxy={{ haproxy_version }}" state=present

    - name: haproxy | Start haproxy
      service: name=haproxy state=started
> ansible-playbook -i inventory/prod install-haproxy.yml

Templates de fichiers de configuration - Jinja2

Fichier dynamiquement générés en fonction de ...
>

Templates Jinja2

# ansible/playbooks/roles/ha-api/tasks/main.yml
- name: ha-api | Configure haproxy
  template: 
    src="haproxy.cfg.j2"
    dest="/etc/haproxy/haproxy.cfg"
    owner=root mode=644
# ansible/playbooks/roles/ha-api/template/haproxy.cfg.j2
...
listen stats :6789
    mode http
    stats enable
    stats hide-version
    stats realm HAProxy-Statistics
    stats uri /
    stats auth admin:{{ haproxy_admin_password }}
...
# ansible/inventory/integ
[ha-api-servers:vars]
haproxy_admin_password="UyY5LHwE7GfgVouOXFkoVdZ"

Handlers

Tâche exécutée seulement si une autre tâche 'changed' et 'notify'

# ansible/playbooks/roles/ha-api/tasks/main.yml
---
- name: ha-api | Configure haproxy
  template: src=haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg
  notify: reload haproxy
# ansible/playbooks/roles/ha-api/handlers/main.yml
---
- name: reload haproxy
  service: name=haproxy state=reloaded

Installation

> apt-get install python pip

> pip install ansible==1.9.4

Exécution

> ansible $group -i $inventory -m $module -a "$parameters"

> ansible-playbook -i $inventory $playbook $options
> ansible api-servers -i inventory/integ -m command -a "docker ps"

> ansible-playbook -i inventory/prod playbooks/all.yml -t common

Organisation en rôles

ansible/playbooks/roles/base/haproxy
├── defaults
│   └── main.yml
├── files
│   └── default
├── hanlers
│   └── main.yml
├── tasks
│   └── main.yml
└── templates
    └── haproxy.cfg.j2
# ansible/playbooks/api-servers.yml
---
- hosts: api-servers
  sudo: yes
  gather_facts: false

  roles:
    - { role: base/docker,    tags: ["base", "docker"] }
    - { role: base/docker-py, tags: ["base", "docker-py"] }
    - { role: api-server,     tags: ["api-server"] }

Démo

Utilisations avancées

Vault

Chiffrage symétrique des variables

# Creation
> ansible-vault create inventory/group_vars/prod.yml

# Edition
> ansible-vault edit inventory/group_vars/prod.yml
# Password prompt
> ansible-playbook site.yml --ask-vault-pass

# Password file/script
> ansible-playbook --vault-password-file "~/.vault_pass.py"

452 modules

Templates Jinja2

# ansible/playbooks/roles/ha-api/templates/haproxy.cfg.j2
...
{% if env == "internal" %}
  stats socket /run/haproxy/admin.sock mode 660 level admin
{% endif %}
...
# ansible/playbooks/roles/ha-api/templates/haproxy.cfg.j2
...
backend apiservers
  http-check expect status 200
  
  # server prod-api-1 149.202.168.198:8080 check
  # server prod-api-2 149.202.168.199:8080 check
  
  # Loop on apiservers group
  {% for h in groups['apiservers'] -%}
  server {{ hostvars[h]['inventory_hostname'] }} \
            {{ hostvars[h]['ansible_ssh_host'] }}:8080 check
  {% endfor %}
  
...

Tips - Options utiles

# Filtrage par tags
-t tag1,tag2

# Filtrage par machines
-l node1,node2

# Vérification des tâches exécutées
--list-tasks

# Vérification des machines impactées
--list-hosts

# Dry-run
--check --diff

# Mode verbose
-vvvv

Tricks

Debugger des variables

- debug: msg="Debug var truc: {{ truc }}"

Tagguer pour n'exécuter qu'une tâche

- name: rsyslog | Update apt
  apt: update_cache=yes cache_valid_time=3600
  tags: ["base", "apt-update"]

Tips

register, ignore_errors, failed_when, when

tasks:
  - name: Check presence of the metrics daemon
    shell: ls /etc/init.d/metricsd
    register: metricsd_present
    ignore_errors: yes
    failed_when: "'No such file' in metricsd_present.stderr"
    tags: [metrics-daemon]

  - name: Register metrics daemon apt source list
    shell: wget -O /etc/apt/sources.list.d/metrics.list http://dl.myrepo.com/debian/metrics.list
    when: metricsd_present|failed
    tags: [metrics-daemon]

Module - notification

- name: ActoFront | Deploy | HipChat notification
  hipchat:
    token="{{ hipchat_token }}"
    room="ops"
    color="yellow"
    from="ansible-bot"
    msg="[{{ env }}] {{ inventory_hostname }}
      {{ actofront_name}}-{{ actofront_version_todeploy.stdout }}
      - DEPLOYED (Open)"
    msg_format="html"
    notify="yes"
  ignore_errors: yes

Module - uri

- name: Register machine to the monitoring platform
  uri:
    url="{{ monitoring_url }}//api/v0/devices"
    method=POST
    HEADER_X-Auth-Token="{{ monitoring_api_token }}"
    HEADER_Content-Type="application/json"
    body='{
      "hostname":"{{ inventory_hostname }}.{{ admin_dns_zone }}",
      "community":"{{ monitoring_community }}"
    }'
    status_code="201"

Inventaire dynamique

  > inv.sh --list
  {
    "apiservers": ["api-1", "api-2"],
    "frontservers": ["front-1", "front-2"]
  }

  > inv.sh --host front-1
  {
    "name": "front-1",
    "ansible_ssh_user": "admin",
    "ansible_ssh_host": "123.42.0.1",
    "ansible_ssh_private_key_file": "/ops/ssh/admin.id_rsa"
  }

Démo

Questions