wiki/HowtoAnsible.md

1049 lines
32 KiB
Markdown
Raw Normal View History

2017-01-13 15:47:03 +01:00
---
title: Howto Ansible : automatisation d'infrastructure
2017-03-03 12:06:36 +01:00
categories automation
2017-01-13 15:47:03 +01:00
---
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Documentation : <http://docs.ansible.com/>
2016-12-29 11:25:39 +01:00
2017-03-01 16:53:56 +01:00
2017-03-01 19:40:54 +01:00
[Ansible](https://www.ansible.com/) est un outil d'automatisation de configuration et gestion de serveurs : il permet le déploiement de logiciels et l'exécution de tâches via une connexion SSH.
2016-12-29 11:25:39 +01:00
2017-03-01 19:40:54 +01:00
Ansible fonctionne sans agent sur les serveurs (*agent-less*) et selon le concept d'**[idempotence](https://fr.wikipedia.org/wiki/Idempotence)** : on décrit l'état d'un serveur et des actions seront exécutées dans le but de rendre le serveur conforme à cette description. On pourra relancer Ansible plusieurs fois, l'état final reste le même : seules les actions nécessaires seront exécutées.
## Installation
2016-12-29 11:25:39 +01:00
Ansible peut exécuter des actions sur des serveurs distants sous :
2017-03-01 19:40:54 +01:00
* *Debian 6* et supérieur : `# apt-get install python-apt dbus sudo`
* *Debian 4 / 5* : utiliser le module [raw](https://docs.ansible.com/ansible/raw_module.html) d'Ansible
2017-03-03 12:40:10 +01:00
* *OpenBSD* : voir **[pré-requis pour OpenBSD](#Pré-requis-OpenBSD)**
* *FreeBSD* : `# pkg install python`
2017-01-13 15:47:03 +01:00
Nous utilisons actuellement Ansible 2.0.2 (disponible via <http://pub.evolix.net/jessie/)> sous Debian 8 :
2016-12-29 11:25:39 +01:00
~~~
# apt install --allow-unauthenticated ansible=2.0.2.0-1~bpo8+1
$ ansible --version
ansible 2.0.2.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
2017-03-03 09:00:50 +01:00
~~~
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
## Utilisation de base
Configuration minimale :
~~~
2016-12-29 11:25:39 +01:00
$ cat ~/.ansible.cfg
[defaults]
inventory = $HOME/.ansible/hosts
[ssh_connection]
#ssh_args = -o ControlMaster=no -o ControlPersist=no
ssh_args = -o ControlMaster=auto -o ControlPersist=300s
pipelining = True
2017-03-03 09:00:50 +01:00
~~~
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
Exemples d'utilisation basique :
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
~~~
$ ansible localhost --module ping
localhost | SUCCESS => {
"changed": false,
2016-12-29 11:25:39 +01:00
"ping": "pong"
}
2017-03-03 09:00:50 +01:00
$ echo HOSTNAME >> ~/.ansible/hosts && ssh-copy-id HOSTNAME && ansible HOSTNAME -i $HOME/.ansible/hosts --module ping --one-line
HOSTNAME | SUCCESS => {"changed": false, "ping": "pong"}
$ ansible HOSTNAME --module command --args "date"
2016-12-29 11:25:39 +01:00
HOSTNAME | SUCCESS | rc=0 >>
jeudi 26 mai 2016, 23:16:01 (UTC+0200)
~~~
2017-01-13 15:47:03 +01:00
2017-03-03 09:00:50 +01:00
## Les éléments d'Ansible
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
L'élément de base d'Ansible est le [module](#modules) : on peut exécuter une tâche (installation de paquets, copie de fichiers, etc.) en exécutant simplement `ansible --module`.
Pour regrouper plusieurs tâches, on utilise un [playbook](#playbook) : un fichier en syntaxe YAML qui va lister une succession de modules avec des arguments.
Au sein d'un playbook, on dispose d'options pratiques comme les [handlers](#handlers) : ils permettent le déclenchement d'une commande sous certaines conditions (redémarrage d'un service par exemple).
Si l'on veut organiser de façon poussée les différentes tâches, on utilisera des [roles](#roles) : il s'agit simplement d'inclure dans un playbook des fichiers YAML respectant une structure conventionnelle.
Enfin, pour s'exécuter sur un ensemble de machines, Ansible a besoin d'un [inventory](#inventory) : c'est la liste des serveurs potentiellement concernés.
2016-12-29 11:25:39 +01:00
2017-03-03 12:06:36 +01:00
### modules
2016-12-29 11:25:39 +01:00
<http://docs.ansible.com/ansible/list_of_all_modules.html>
2017-03-03 09:00:50 +01:00
Le module est le garant de l'idempotence : Ansible gérera le succès de l'exécution et indiquera si une vérification ou action concrète a été effectuée. (TODO: à enrichir).
Pour avoir la liste des modules utilisables : `ansible-doc -l`
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
Voici quelques exemples de modules que nous utilisons :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [command](http://docs.ansible.com/ansible/command_module.html) :
2017-01-03 11:20:35 +01:00
~~~{.yaml}
2017-01-13 15:47:03 +01:00
- command: date
~~~
* Module [shell](http://docs.ansible.com/ansible/shell_module.html) :
~~~{.yaml}
- shell: cat foo.txt | grep bar
~~~
* Module [file](http://docs.ansible.com/ansible/file_module.html) :
~~~{.yaml}
- file:
path: /etc/cron.daily/apticron
state: absent
~~~
* Module [copy](http://docs.ansible.com/ansible/copy_module.html) :
~~~{.yaml}
- copy
src: files/foo
dest: /etc/bar
owner: root
group: root
mode: "644"
~~~
2017-03-03 12:06:36 +01:00
* Module [replace](http://docs.ansible.com/ansible/replace_module.html) :
~~~{.yaml}
- replace:
dest: /etc/ssh/sshd_config
regexp: '^(Match User ((?!{{ name }}).)*)$'
replace: '\1,{{ name }}'
~~~
2017-01-13 15:47:03 +01:00
* Module [lineinfile](http://docs.ansible.com/ansible/lineinfile_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- lineinfile:
dest: /etc/evocheck.cf
insertafter: EOF
line: "IS_APTICRON=0"
regexp: "^IS_APTICRON="
~~~
2017-03-03 12:06:36 +01:00
> *Note:* replace vs lineinfile ? Le fonctionnement exacte de `replace` et de `lineinfile` est potentiellement déroutant. Voici quelques constatations :
> * avec le module `lineinfile`, si l'argument `regexp` n'est pas matché… il insère quand même la ligne ! `regexp` n'est pas une condition pour l'insertion mais une condition pour remplacer au lieu d'insérer !
> * avec le module `lineinfile`, sauf cas tordus, l'argument `regexp` doit matcher l'argument `line` (sinon il va insérer la valeur de `line` à chaue exécution !)
> * le module `lineinfile` va d'abord évaluer si `regexp` matche et remplacer la dernière occurrence, puis si `regexp` ne matche pas il ajoute alors `line` (sans d'autre condition… même si elle existe déjà)
> * le module `replace` va remplacer uniquement si `regex` est matché, comme la commande `sed`
> * avec le module `lineinfile`, si l'on veut utiliser une référence (`\1`) dans `line`, ça donne une erreur, il faut utiliser `replace`
> * avec le module `lineinfile`, l'argument `backrefs: yes` sert à utiliser une référence au sein de l'argument `regexp` (et non pas au sein de l'argument `line`).
2017-01-13 15:47:03 +01:00
* Module [blockinfile](http://docs.ansible.com/ansible/blockinfile_module.html) :
~~~{.yaml}
- blockinfile:
dest: /etc/apache2/envvars
block: |
## Set umask for writing by Apache user.
## Set rights on files and directories written by Apache
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [ini_file](http://docs.ansible.com/ansible/ini_file_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- ini_file:
dest: /root/.my.cnf
section: client
option: user
value: root
mode: "640"
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [user](http://docs.ansible.com/ansible/user_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- user:
2016-12-29 11:25:39 +01:00
state: present
2017-01-13 15:47:03 +01:00
name: "{{ name }}"
2016-12-29 11:25:39 +01:00
comment: 'John Doe'
shell: /bin/bash
groups: adm
append: yes
password: '$6$k/Fg76xH'
2017-01-13 15:47:03 +01:00
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [stat](http://docs.ansible.com/ansible/stat_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- stat:
path: /etc/sudoers.d/foo
2016-12-29 11:25:39 +01:00
register: foo_sudoers_file
2017-01-13 15:47:03 +01:00
~~~
* Module [apt](http://docs.ansible.com/ansible/apt_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- apt:
name: '{{ item }}'
state: latest
update_cache: yes
cache_valid_time: 3600
with_items:
- vim
- htop
~~~
* Module [apt_repository](http://docs.ansible.com/ansible/apt_repository_module.html) :
~~~{.yaml}
- name: exemple
2016-12-29 11:25:39 +01:00
apt_repository:
2017-01-13 15:47:03 +01:00
repo: "deb http://mirror.evolix.org/jessie stable main"
state: present
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [mysql_user](http://docs.ansible.com/ansible/mysql_user_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- mysql_user:
2016-12-29 11:25:39 +01:00
name: mysqladmin
password: my_password
priv: "*.*:ALL,GRANT"
state: present
config_file: /root/.my.cnf
update_password: on_create
2017-01-13 15:47:03 +01:00
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [sysctl](http://docs.ansible.com/ansible/sysctl_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
- name: exemple
2017-01-13 15:47:03 +01:00
sysctl:
name: vm.max_map_count
value: 262144
sysctl_file: /etc/sysctl.d/elasticsearch.conf
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [alternatives](http://docs.ansible.com/ansible/alternatives_module.html) :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- alternatives:
name: editor
path: /usr/bin/vim.basic
~~~
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
* Module [service](http://docs.ansible.com/ansible/service_module.html) :
~~~{.yaml}
- name: exemple pour redémarrer un service (compatible avec sysvinit, systemd…)
service: nginx
state: restarted
2016-12-29 11:25:39 +01:00
~~~
2017-03-03 12:06:36 +01:00
Pour avoir plus d'infos sur un module :
~~~
# ansible-doc shell
> SHELL
The [shell] module takes the command name followed by a li
space-delimited arguments. It is almost exactly like the
(…)
~~~
2017-03-03 12:06:36 +01:00
*Note* : c'est pratique pour avoir la documentation exacte pour votre version d'Ansible. En effet, celle du site corespond à la dernière version et n'indique pas toujours toutes les différences.
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
### playbook
2017-03-03 12:40:10 +01:00
Un playbook va ensuite dérouler des _actions_ qui seront organisées en _tasks_, [roles](#roles) et [handlers](#handlers).
2017-03-03 09:00:50 +01:00
2017-03-03 12:40:10 +01:00
Exemple de playbook simple :
2017-03-03 09:00:50 +01:00
~~~{.yaml}
---
- hosts: all
tasks:
- shell: echo hello World
# vim:ft=ansible:
~~~
2017-03-03 12:40:10 +01:00
Un playbook plus évolué :
2017-03-03 09:00:50 +01:00
~~~{.yaml}
---
- hosts: all
gather_facts: yes
become: yes
vars_files:
- 'vars/main.yml'
vars:
external_roles: ~/GIT/ansible-roles
external_tasks: ~/GIT/ansible-public/tasks
pre_tasks:
- name: Minifirewall is stopped (temporary)
service:
name: minifirewall
state: stopped
roles:
- "{{ external_roles }}/minifirewall"
post_tasks:
- include: "{{ external_tasks }}/commit_etc_git.yml"
vars:
commit_message: "Ansible run firewall.yml"
handlers:
- name: restart minifirewall
service:
name: minifirewall
state: restarted
# vim:ft=ansible:
~~~
2017-03-03 12:06:36 +01:00
On lance ainsi le playbook :
~~~
$ ansible-playbook PLAYBOOK.yml --limit HOSTNAME --forks 1
$ ansible-playbook PLAYBOOK_WITH-SUDO.yml --limit HOSTNAME --ask-become-pass
~~~
Options utiles pour [ansible-playbook](https://manpages.debian.org/cgi-bin/man.cgi?query=ansible-playbook&apropos=0&sektion=0&manpath=Debian+unstable+sid&format=html&locale=en) :
* `-vvvv` : très verbeux (utile notamment pour debug SSH quand on a une erreur _unreachable_)
* `-k` / `--ask-pass` : demande le mot de passe pour la connexion SSH
* `-K` / `--ask-become-pass` : demande le mot de passe pour l'escalade (via sudo, su, doas…)
* `-l` / `--limit HOSTNAME` : limite la connexion à un ou plusieurs serveurs (attention, par défaut c'est *all*, cf `/etc/ansible/hosts`)
* `-f` / `--forks N` : nombre de process lancés en parallèle (par défaut 5)… peut être utile de mettre à 1 pour ne pas paralléliser
#### Limiter l'exécution à certaines machines
2017-03-03 12:40:10 +01:00
Quelques exemples d'utilisation de l'option `--limit` (ou l`) :
* limiter aux groupes _www_ et _sql_ (qui peuvent être indifféremment des groupes ou des serveurs) :
2017-03-03 12:06:36 +01:00
~~~
$ ansible-playbook -l "www:sql" playbook.yml
~~~
* limiter aux serveurs _foo-www01_, _foo-lb01_, _foo-filer_… :
~~~
$ ansible-playbook -l "foo-*" playbook.yml
~~~
* limiter aux 10 premiers serveurs de l'inventaire (utile pour faire par paquets) :
~~~
$ ansible-playbook -l "*[0:9]" playbook.yml
~~~
2017-03-03 12:40:10 +01:00
* puis à ceux restants :
2017-03-03 12:06:36 +01:00
~~~
$ ansible-playbook -l "*[10:]" playbook.yml
~~~
Il est de toute façon préférable de ne pas mettre `all` dans le champs `hosts` dans le playbook pour éviter un - _possible_ - oubli de "limite" de machines.
2017-03-03 09:00:50 +01:00
### handlers
Les **handlers** ne sont exécutées que si une action a fait la demande, via la directive `notify` disponible pour tous les modules.
L'enregistrement du handler se fait seulement lorsque l'action a provoqué un changement.
L'exécution effective du handler se fait **une seule fois** en fin de "play", quel que soit le nombre de fois où il a été demandé pendant l'exécution.
Les handlers servent le plus souvent à redémarrer des services. Exemple :
~~~{.yaml}
tasks:
- name: copy Apache configuration
copy: (…)
notify: Restart Apache
handlers:
- name: Restart Apache
service:
name: apache2
state: restarted
~~~
Dans des rôles longs, nous conseillons de purger les handlers de temps en temps (en fin de groupe d'action). En effet, si un playbook est interrompu les handlers ne sont pas forcément exécutés alors que l'action qui les a déclenchés a bien eu lieu. On insère alors l'action suivante :
~~~{.yaml}
- meta: flush_handlers
~~~
### roles
Lorsqu'on a besoin d'utiliser des fichiers ou _templates_ à copier, des variables avec des valeurs par défaut, des handlers… on peut organiser tout cela dans un **role** en respectant la structure conventionnelle suivante :
~~~
foo
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
└── main.yml
~~~
### inventory
<http://docs.ansible.com/ansible/intro_inventory.html>
La partie **inventory** correspond à la description de l'inventaire des serveurs à configurer et inclus un mécanisme de configuration individuelle et par groupe.
Permet d'indiquer la liste des machines concernées par Ansible (peut être limité lors de l'exécution de la commande par l'option `-l`) et de pouvoir les regrouper dans des groupes.
Exemple:
~~~
hostname.internal
[httpservers]
machine[01:57].example.com
http.example.com:2222
[dbservers]
machine12.example.com
machine50.example.com
m[a:o]chine52.example.com
alias ansible_port=2222 ansible_host=192.168.1.50
[client]
host1 http_port=80 maxRequestsPerChild=808 # des variables qui seront automatiquement auto-completées liées à cet host
[commercant]
mercerie
chapeautier
[commercant:vars]
ntp_server=ntp.mercerie.example.com
proxy=proxy.mercerie.example.com
~~~
* `hostname.internal` : serveur présent dans aucun groupe
* `[httpservers]` : le nom du groupe (pour les serveurs http). Les noms de hosts qui suivent appartiendront à ce groupe
* `machine[01:57].evolix.net` : on peut indiquer une [pseudo-]expression régulière - ici ajoutera les machines _machine01.evolix.net_, _machine02.evolix.net_, _machine03.evolix.net__machine57.evolix.net_
* `http.evolix.net:2222` : ansible se connecte par ssh, et _http.evolix.net_ a un port SSH d'écoute différent qui est 2222
* `[dbservers]` : groupe pour les serveurs de base de données
* `machine50.example.com` : cette machine est déjà présente dans le groupe _httpservers_, mais sera aussi accessible à partir du groupe _dbservers_
* `alias ansible_port=2222 ansible_host=192.168.1.50` : la machine _alias_ n'a pas un vrai FQDN mais pointera vers _192.168.1.50_ car on a indiqué des variables propres à Ansible. Il est existe aussi `ansible_connection` (local ou ssh) ou `ansible_user` (le nom de l'utilisateur de la machine distante avec lequel Ansible se connectera en ssh)
* `host1 http_port=80 maxRequestsPerChild=808` : des variables qui seront automatiquement disponibles pour les actions sur _host1_
* `[commercant:vars]` : des variables qui seront liées au groupe _commercant_.
On peut aussi créer des groupes de groupes en utilisant `:children`
On peut aussi découper le fichier "inventory" selon les groupes et les variables : <http://docs.ansible.com/ansible/intro_inventory.html#splitting-out-host-and-group-specific-data>
Les variables propres à Ansible : <http://docs.ansible.com/ansible/intro_inventory.html#list-of-behavioral-inventory-parameters>
### variables
2016-12-29 11:25:39 +01:00
Les variables sont un élément clé de la configuration des playbooks et roles. Exemple :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
vars:
ip: 31.170.9.129
conf_file: /etc/foo.conf
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
tasks:
- command: echo {{ ip }} >> {{ conf_file }}
2016-12-29 11:25:39 +01:00
~~~
Les variables peuvent être définies à de multiples niveaux, chacun ayant une certaine précédence (extrait de la [documentation](http://docs.ansible.com/ansible/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)) :
* role defaults
* inventory vars
* inventory group_vars
* inventory host_vars
* playbook group_vars
* playbook host_vars
* host facts
* play vars
* play vars_prompt
* play vars_files
* registered vars
* set_facts
* role and include vars
* block vars (only for tasks in block)
* task vars (only for the task)
* extra vars (always win precedence)
2017-01-13 15:47:03 +01:00
Pour gérer de nombreuses variables dans un projet, on peut stocker toutes celles qui correspondent à un groupe de serveur dans un fichier portant le nom du groupe, ainsi que toutes celle d'un serveur en particulier dans un fichier du nom du serveur. Voici l'arborescence conseillée :
2016-12-29 11:25:39 +01:00
~~~
2017-01-13 15:47:03 +01:00
└── inventory
   ├── group_vars
│   └── group1.yml
│   └── group2.yml
  └── host_vars
   └── hostname1.yml
   └── hostname2.yml
2016-12-29 11:25:39 +01:00
~~~
2017-01-13 15:47:03 +01:00
Les groupes sont définis dans le fichier d'[inventaire](http://docs.ansible.com/ansible/intro_inventory.html).
2016-12-29 11:25:39 +01:00
### Tags
<https://docs.ansible.com/ansible/playbooks_tags.html>
2017-03-03 12:06:36 +01:00
Les tags permettent de ranger/trier chaque tâche ou rôle dans une catégorie.
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
- name: Coucou
2017-01-13 15:47:03 +01:00
debug:
msg: "Saloute!"
2016-12-29 11:25:39 +01:00
tags: message
~~~
2017-03-03 12:06:36 +01:00
On peut également utiliser les tags pour limiter/exclure des tâches :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
2017-01-13 15:47:03 +01:00
$ ansible-playbook (…) --skip-tags "message"
2016-12-29 11:25:39 +01:00
~~~
2017-03-03 12:06:36 +01:00
On peut aussi n'éxecuter que certains tags :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
2017-01-13 15:47:03 +01:00
$ ansible-playbook (…) --tags "configuration,packages"
2016-12-29 11:25:39 +01:00
~~~
2017-03-03 12:06:36 +01:00
Note : on peut également _taguer_ des rôles `include`.
2016-12-29 11:25:39 +01:00
### Register
`register` est un attribut d'action que l'on peut rajouter pour tout type de tâche et qui initialisera la variable (par le nom donné) avec les valeurs retour du module.
Pour `shell`, on a le droit à `.stdout`, `.stderr`, `.rc`… Mais cela dépend bien des valeurs de retour du module.
Il est possible de consulter le contenu détaillé de la variable avec `debug` :
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
~~~{.yaml}
- stat:
path: /etc/passwd
2016-12-29 11:25:39 +01:00
register: st
2017-01-13 15:47:03 +01:00
- debug:
var: st
2016-12-29 11:25:39 +01:00
2017-01-13 15:47:03 +01:00
- fail:
msg: "Whoops! file ownership has changed"
2016-12-29 11:25:39 +01:00
when: st.stat.pw_name != 'root'
~~~
Pour certains modules, `register` est presque un passage obligatoire pour une utilisation cohérente des éléments (stat…).
2016-12-29 11:25:39 +01:00
2017-03-03 09:00:50 +01:00
2016-12-29 11:25:39 +01:00
## Configuration
<https://docs.ansible.com/ansible/intro_configuration.html>
La configuration est lue dans l'ordre suivant :
2017-01-13 15:47:03 +01:00
* `ANSIBLE_CONFIG` (variable d'environnement)
* `./ansible.cfg`
* `~/.ansible.cfg`
* `/etc/ansible/ansible.cfg`
2016-12-29 11:25:39 +01:00
### ansible.cfg
Options utiles (TODO : à revoir) :
2017-01-13 15:47:03 +01:00
* `display_args_to_stdout` : mettre à `True` si on veut voir tout le contenu du _tasks_ executé pour chaque étape écrit sur _stdout_
* `display_skipped_hosts` : mettre à `False` si on ne veut pas voir affiché sur _stdout_ l'information d'un _task_ qui n'est pas exécuté _(le nom de variable est confu - mais il s'agit bien de l'affichage du task)_
* `error_on_undefined_vars` : mettre à `True` pour être sûr que le script Ansible s'arrête si une variable n'est pas définie (alors qu'il y a utilisation de cette dernière dans une _task_)
2017-01-13 15:47:03 +01:00
* `force_color` : mettre à `1` pour forcer la couleur
* `forks` : le nombre de processus en parallèle possible lors déploiement du script Ansible sur nombreux _hosts_
2017-01-13 15:47:03 +01:00
* `hosts` : accès vers les _hosts_ par défaut (`all`)
* `private_key_file` : le chemin pour la clé pem
2017-01-22 22:30:37 +01:00
* `remote_port` : le port SSH par défaut (`22`)
* `remote_user` : l'utilisateur pour la connexion SSH par défaut (`root`)
2017-01-13 15:47:03 +01:00
* `retry_files_enabled` : mettre à `True` pour la création de fichier `.retry` après une failure de ansible pour reprendre le travail précédent - ajouté en argument dans l'appel de la commande
2016-12-29 11:25:39 +01:00
## Erreurs
### unbalanced jinja2 block or quotes
~~~
fatal: [test.evolix.net]: FAILED! => {"failed": true, "reason": "error while splitting arguments, either an unbalanced jinja2 block or quotes"}
~~~
Vérifier bien la syntaxe du document qui est référé pour cette erreur. Cela peut être un guillemet mal fermé (ou mélange simples/doubles guillemets), ou encore histoire de crochet devenant une parenthèse…
2016-12-29 11:25:39 +01:00
### UNREACHABLE!
~~~
fatal: [test.evolix.net]: UNREACHABLE! => {"changed": false, "msg": "SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue", "unreachable": true}
~~~
2017-01-22 22:30:37 +01:00
Malheureusement, cela arrive souvent sur certaines machines (une ?), mais pas de solution pour le moment -> boire du café - beaucoup de café.
2016-12-29 11:25:39 +01:00
Du côté de la machine distante, dans le fichier `/var/log/auth.log`:
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
14:56:29 localhost sshd[19915]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=dual.evolix.net user=service
14:56:31 localhost sshd[19915]: Accepted password for service from 192.168.4.137 port 42502 ssh2
14:56:31 localhost sshd[19915]: pam_unix(sshd:session): session opened for user service by (uid=0)
14:56:31 localhost systemd-logind[641]: New session 181 of user service.
14:56:32 localhost sshd[19915]: pam_unix(sshd:session): session closed for user service
14:56:32 localhost systemd-logind[641]: Removed session 181.
~~~
Et quand ça marche - la session ne se referme pas, et est réutilisé par les exécutions playbook suivantes:
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
14:58:57 localhost sshd[20339]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=dual.evolix.net user=service
14:59:00 localhost sshd[20339]: Accepted password for service from 192.168.4.137 port 42511 ssh2
14:59:00 localhost sshd[20339]: pam_unix(sshd:session): session opened for user service by (uid=0)
14:59:00 localhost systemd-logind[641]: New session 182 of user service.
(…)
2016-12-29 11:25:39 +01:00
15:01:08 localhost sshd[23034]: Received disconnect from 192.168.4.137: 11: disconnected by user
15:01:08 localhost sshd[23024]: pam_unix(sshd:session): session closed for user service
15:01:08 localhost systemd-logind[641]: Removed session 182.
~~~
Mais si tentative de déploiement Ansible juste après la session supprimée (par ex la 182), c'est à ce moment là qu'on se retrouve avec des *UNREACHABLE* -> savoir pourquoi sshd supprime la session ssh…
2016-12-29 11:25:39 +01:00
### Missing required arguments
~~~
fatal: [test.evolix.net]: FAILED! => {"changed": false, "failed": true, "msg": "missing required arguments: section"}
~~~
Le message est assez clair, donc bien relire la doc du module sur Ansible, et toujours ajouter les arguments obligatoires pour ce module.
2016-12-29 11:25:39 +01:00
### Requires stdlib json or simplejson module
~~~
fatal: [lenny.evolix.net]: FAILED! => {"changed": false, "failed": true, "msg": "Error: ansible requires the stdlib json or simplejson module, neither was found!"}
~~~
~~~
# apt install python-simplejson
~~~
## Astuces
### Vérifier un playbook
* Vérifier la syntaxe :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
$ ansible-playbook --syntax-check my-experimental-playbook.yml
~~~
<http://www.yamllint.com/>
2017-01-22 22:30:37 +01:00
* vérifier les actions qui vont être faite s(mode `dry-run`) sans rien exécuter :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
$ ansible-playbook --check my-experimental-playbook.yml
~~~
2017-01-22 22:30:37 +01:00
*Note* : certaines actions ne sont pas exécutées en mode "check", cela peut donc perturber celles qui sont bassées dessus.
* avoir le diff des fichiers modifiés (ne marche pas avec les modules `replace`/`lineinfile` à priori) :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
$ ansible-playbook --check --diff my-experimental-playbook.yml
~~~
### Stopper l'éxecution du code
Pour par exemple, stopper le code à un moment pour lire les valeurs d'une variables
~~~{.yaml}
- debug:
2017-01-22 22:30:37 +01:00
var: foo
2016-12-29 11:25:39 +01:00
- command: /bin/false
~~~
ou
~~~{.yaml}
- debug:
2017-01-22 22:30:37 +01:00
var: foo
- fail:
2017-01-22 22:30:37 +01:00
msg: "FAIL"
2016-12-29 11:25:39 +01:00
~~~
ou
~~~{.yaml}
- debug:
2017-01-22 22:30:37 +01:00
var: foo
2016-12-29 11:25:39 +01:00
- pause:
~~~
### Lancement tâches hosts asynchrone
Pour éviter que les différentes tâches s'appliquent une par une sur tout les hosts impliqués par l'exécution du playbook, on peut utiliser l'option `strategy` à la valeur `free` pour que chaques tâches sur un host puisse continuer dès la fin de son exécution sans attendre l'état des autres hosts concernés en cours.
2016-12-29 11:25:39 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
- hosts: all
(…)
2016-12-29 11:25:39 +01:00
strategy: free
~~~
2017-01-22 22:30:37 +01:00
*Note*: ne plus se fier au texte `host changed` après texte de la tâche, car il pourrait s'agir d'une autre tâche affichée plus en haut dans le texte de l'historique.
2016-12-29 11:25:39 +01:00
### Fréquence des hosts
2017-01-22 22:30:37 +01:00
Lors de l'exécution d'un play, on peut indiquer une fréquence sur le nombre d'hôtes concernés par l'éxecution du playbook.
2016-12-29 11:25:39 +01:00
* `Fork` pour le nombre d'hôtes simultanés (modifiable dans le fichier _ansible.cfg_ - mettre une valeur importante > centaine).
2016-12-29 11:25:39 +01:00
* `serial` en en-tête contenant une valeur numérique qui représente le nombre de machines pour chaque tour d'éxecution de playbook, ou un pourcentage par rapport à la liste inventory concerné.
### Cowsay
Si la commande `cowsay` est disponible sur votre machine, vous verrez un message à la fin :
2016-12-29 11:25:39 +01:00
~~~
____________________
< NO MORE HOSTS LEFT >
--------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
____________
< PLAY RECAP >
------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
~~~
Pour le désactiver : `export ANSIBLE_NOCOWS=1`
2016-12-29 11:25:39 +01:00
Disponible aussi dans la conf du fichier `/etc/ansible/ansible.cfg`
<https://support.ansible.com/hc/en-us/articles/201957877-How-do-I-disable-cowsay->
### Conditions dans fichier jinja2
~~~
{% if python_is_installed is defined %}
Ansible devrait marcher -pardi!
{% endif %}
~~~
2016-12-29 11:25:39 +01:00
<http://jinja.pocoo.org/docs/dev/templates/#builtin-tests>
Voir la doc pour plus de détails : <http://jinja.pocoo.org/docs/dev/>
2016-12-29 11:25:39 +01:00
### Lire une entrée au clavier
S'il manque une valeur pour la suite du script, soit on le gère en mettant une erreur, ou une valeur par défaut, mais sinon on peut aussi demander une saisie clavier :
2016-12-29 11:25:39 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
vars_prompt:
- name: 'prenom'
prompt: 'Quel est votre prénom ?'
private: no
tasks:
- debug:
2017-01-22 22:30:37 +01:00
var: prenom
2016-12-29 11:25:39 +01:00
~~~
2017-01-03 11:20:35 +01:00
2017-03-03 12:06:36 +01:00
Malheureusement pour le moment, cela doit se situer avant `tasks`.
2016-12-29 11:25:39 +01:00
Si on veut utiliser cette variable dans une tâche, il faut simplement utiliser le nom de la variable, et si on veut l'utiliser (explicitement) pour un play ne se trouvant pas dans le même fichier (donc ici la variable dans autre.yml s'appelera _prenom_de_autre_ et non prenom) :
2017-01-03 11:20:35 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
- include: './tasks/autre.yml'
vars:
prenom_de_autre: prenom
~~~
<https://docs.ansible.com/ansible/playbooks_prompts.html>
### Exécuter un playbook en mode interactif
2016-12-29 11:25:39 +01:00
~~~
$ ansible-playbook playbook.yml --step
2016-12-29 11:25:39 +01:00
~~~
<https://docs.ansible.com/ansible/playbooks_startnstep.html>
### Ne pas lancer une commande shell si le fichier existe
2017-01-22 22:30:37 +01:00
En indiquant l'argument `creates` indiquant le chemin de fichier lors de l'utilisation du module shell, cette tâche ne s'exécutera que si le fichier indiqué par `creates` n'existe pas.
Le corollaire est possible avec l'argument `removes` qui empêche l'exécution si le fichier n'existe pas.
2017-01-22 22:30:37 +01:00
Ces arguments sont disponibles pour certains modules (comme `shell`).
2016-12-29 11:25:39 +01:00
2017-01-22 22:30:37 +01:00
C'est beaucoup plus simple et rapide que de tester le fichier par le module `stat` juste avant.
2016-12-29 11:25:39 +01:00
### Lancer tâche sur machine précise (voire locale)
2016-12-29 11:25:39 +01:00
~~~
- name: /etc/hosts
command: cat /etc/hosts
2016-12-29 11:25:39 +01:00
register: tmp
delegate_to: localhost
- debug:
2017-01-22 22:30:37 +01:00
var: tmp.stdout
2016-12-29 11:25:39 +01:00
~~~
Pour une exécution locale, on peut aussi utiliser l'attribut `local_action`.
2016-12-29 11:25:39 +01:00
<https://docs.ansible.com/ansible/playbooks_delegation.html#delegation>
### Ne lancer tâche qu'une seule fois
2016-12-29 11:25:39 +01:00
~~~
- name: Début installation, envoie email
run_once: true
(…)
2016-12-29 11:25:39 +01:00
~~~
Si cet attribut est utilisé avec `delegate_to`, alors cette machine sera la seule à exécuter cette tâche. Sinon, c'est la première dans la liste de l'inventaire.
2016-12-29 11:25:39 +01:00
<https://docs.ansible.com/ansible/playbooks_delegation.html#run-once>
### Appliquer une tâche à une liste (tableau) -> boucle
#### with_items
~~~{.yaml}
2016-12-29 11:25:39 +01:00
- name: Manger les fruits
command: eat '{{ item }}'
2016-12-29 11:25:39 +01:00
with_items:
- Apple
- Orange
- Strawberry
- Mango
2016-12-29 11:25:39 +01:00
~~~
2017-01-22 22:30:37 +01:00
Par exemple pour l'installation de plusieurs nouveaux paquets :
2017-01-03 11:20:35 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
---
- hosts: localhost
tasks:
- apt:
name: '{{ item }}'
state: present
with_items:
- cmatrix
- tetrinet-server
- tetrinet-client
- xtel
- xtell
2016-12-29 11:25:39 +01:00
~~~
2017-01-03 11:20:35 +01:00
2017-01-22 22:30:37 +01:00
Même si il y aura plusieurs paquets installés, cela ne comptera que pour *un* changement (`changed=1`).
Cette tâche appellera un par un les éléments de la liste (présents dans `with_items`) pour le module.
2016-12-29 11:25:39 +01:00
#### with_nested
Pour croiser les éléments des items :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
tasks:
- include: "./ajout_utilisateur_sur_machine.yml"
vars:
user: "{{ item[0] }}"
server: "{{ item[1] }}"
with_nested:
- [ 'alice', 'bob' ]
- [ 'machine1', 'machine2', 'machine-backup' ]
2016-12-29 11:25:39 +01:00
~~~
2017-01-03 11:20:35 +01:00
Cela a pour effet d'exécuter l'inclusion pour `alice` pour chacune des 3 machines, puis pour `bob` pour chacune des 3 machines.
2016-12-29 11:25:39 +01:00
#### with_dict
Avec hash :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
users:
bob:
name: Bob
uid: 1000
home: /home/bob
alice:
name: Alice
uid: 1001
home:
tasks:
- user:
name: "{{ item.key }}"
comment: "{{ item.value.name }}"
uid: "{{ item.value.uid }}"
home: "{{ item.value.home }}"
2016-12-29 11:25:39 +01:00
with_dict: "{{ users }}"
~~~
2017-01-22 22:30:37 +01:00
#### [with_first_found](http://docs.ansible.com/ansible/playbooks_loops.html#finding-first-matched-files)
Permet de prendre le premier fichier trouvé :
~~~{.yaml}
- name: Copy HAProxy configuration
template:
src: "{{ item }}"
dest: /etc/haproxy/haproxy.cfg
force: yes
with_first_found:
- haproxy.cfg/{{ inventory_hostname }}
- haproxy.cfg/{{ host_group }}
- haproxy.cfg/default
~~~
2017-01-22 22:32:42 +01:00
De cette manière, si un fichier portant le nom du serveur en cours existe, il sera utilisé, sinon on cherche un fichier du nom du groupe du serveur et enfin on cherche un fichier par défaut, valable pour tous les serveurs qui n'ont pas de configuration spécifique ou de groupe.
2017-01-22 22:30:37 +01:00
2016-12-29 11:25:39 +01:00
### Se connecter sous un autre utilisateur UNIX
Par défaut, l'utilisateur se connectant sur le serveur distant l'utilisateur UNIX courant. On peut soit le préciser dans le fichier de conf principal d'Ansible avec `remote_user: michu`, dans l'inventaire pour un groupe ou un serveur précis ou encore en l'indiquant en argument lors de l'éxecution du playbook.
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
$ ansible-playbook -u michu -k play.yml
2016-12-29 11:25:39 +01:00
~~~
2017-01-22 22:30:37 +01:00
### Éviter que la commande shell n'indique d'élement 'changed'
2016-12-29 11:25:39 +01:00
2017-01-22 22:30:37 +01:00
Sur tous les modules, chaque tâche retourne un statut sur son résultat :
2016-12-29 11:25:39 +01:00
2017-01-22 22:30:37 +01:00
* `ok` : aucune modification n'a été nécessaire
* `changed` : une modification a eu lieu par rapport à l'état précédent (droits fichiers…)
* `failed` : une erreur s'est produite
2016-12-29 11:25:39 +01:00
2017-01-22 22:30:37 +01:00
Pour des modules comme `shell`, `command`… Ansible ne peut savoir si un changement a eu lieu ou pas. Il indique alors toujours `changed`.
Il est possible de forcer le statut du changement :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
- command: date
changed_when: False
2016-12-29 11:25:39 +01:00
~~~
Ou donner une condition
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
- shell: cat > {{ fichier }} < /dev/null
2016-12-29 11:25:39 +01:00
changed_when: {{ fichier.stats.exist }}
~~~
### Voir variables disponibles
~~~
$ ansible -m setup <hostname>
service.evolix.net | SUCCESS => {
"ansible_facts": {
(…)
2016-12-29 11:25:39 +01:00
"ansible_architecture": "x86_64",
"ansible_bios_date": "12/01/2006",
"ansible_bios_version": "VirtualBox",
"ansible_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-3.16.0-4-amd64",
"quiet": true,
"ro": true,
"root": "UUID=37de3cbb-3f28-48d2-a4eb-c893a2f2fbc3"
},
"ansible_date_time": {
"date": "2016-05-06",
"day": "06",
"epoch": "1462546886",
"hour": "17",
(…)
2016-12-29 11:25:39 +01:00
},
"ansible_default_ipv4": {
(…)
2016-12-29 11:25:39 +01:00
}
~~~
~~~
$ ansible -m debug -a "var=hostvars['hostname']" localhost
~~~
Pour récupérer toutes les adresses MAC des machines :
2017-01-03 11:20:35 +01:00
~~~{.yaml}
2016-12-29 11:25:39 +01:00
---
- hosts: all
gather_facts: true
tasks:
- debug:
var: ansible_eth0.macaddress
2016-12-29 11:25:39 +01:00
~~~
que l'on pourra combiner par exemple avec un pipe en ligne de commande :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
2017-01-03 11:20:35 +01:00
$ ansible-playbook mac_address.yml | grep ansible_eth0.macaddress | sed 's/^\s*"ansible_eth0.macaddress": "\(.*\)"/\1/'
2016-12-29 11:25:39 +01:00
~~~
Il est possible aussi d'accéder aux variables d'environnement shell :
2017-01-03 11:20:35 +01:00
2016-12-29 11:25:39 +01:00
~~~
"{{ lookup('env','HOME') }}"
~~~
2017-03-03 12:40:10 +01:00
### Pré-requis OpenBSD
2017-03-01 19:40:54 +01:00
Voici les étapes nécessaires à l'utilisation d'Ansible sur des serveurs OpenBSD.
2017-03-03 12:40:10 +01:00
Installer _Python_ :
~~~
2017-03-03 15:12:12 +01:00
# pkg_add -z python-2 sudo
~~~
2017-03-01 19:40:54 +01:00
et surcharger la variable `ansible_python_interpreter` dans le fichier _inventory_ :
~~~
[openbsd]
serveur.example.com
[openbsd:vars]
2017-03-03 15:53:45 +01:00
ansible_python_interpreter=/usr/local/bin/python2.7
~~~
2016-12-29 11:25:39 +01:00
## Ressources utiles
* [Documentation officielle](http://docs.ansible.com/ansible/) (voir notamment la partie [Best Practices](http://docs.ansible.com/ansible/playbooks_best_practices.html))
* Vidéos [ansible-best-practices](https://www.ansible.com/ansible-best-practices) et [ansible-tips-and-tricks](https://www.ansible.com/ansible-tips-and-tricks)
2016-12-29 11:25:39 +01:00
* [Ansible 101 - on a Cluster of Raspberry Pi 2s](https://www.youtube.com/watch?v=ZNB1at8mJWY)
* Sysadmin Casts (épisodes [43](https://sysadmincasts.com/episodes/43-19-minutes-with-ansible-part-1-4), [45](https://sysadmincasts.com/episodes/45-learning-ansible-with-vagrant-part-2-4), [46](https://sysadmincasts.com/episodes/46-configuration-management-with-ansible-part-3-4) et [47](https://sysadmincasts.com/episodes/47-zero-downtime-deployments-with-ansible-part-4-4))
* [How Twitter uses Ansible](https://www.youtube.com/watch?v=fwGrKXzocg4) (AnsibleFest 2014)
* [Orchestration with Ansible at Fedora Project](http://fr.slideshare.net/AdityaPatawari/ansible-33223245)