mirroir readonly du Gitit wiki.evolix.org (attention, ne rien commiter/merger sur ce dépôt) https://wiki.evolix.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1313 lines
39 KiB

3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. ---
  2. title: Howto Ansible : automatisation d'infrastructure
  3. categories automation
  4. ---
  5. * Documentation : <http://docs.ansible.com/>
  6. [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.
  7. 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.
  8. ## Installation
  9. Nous utilisons actuellement Ansible 2.2 (version proposée en Debian 9) :
  10. ~~~
  11. # apt install ansible sshpass
  12. $ ansible --version
  13. ansible 2.2.1.0
  14. config file = /etc/ansible/ansible.cfg
  15. configured module search path = Default w/o overrides
  16. ~~~
  17. > Note : Pour Debian 8, Ansible 2.2 est disponible dans les backports :
  18. >
  19. > ~~~
  20. > # apt install ansible -t jessie-backports ansible
  21. > ~~~
  22. Ansible peut exécuter des actions sur des machines distantes. Mais certains logiciels sont requis :
  23. * *Debian 6* et supérieur : `# apt-get install --no-install-recommends python python-apt dbus sudo`
  24. * *Debian 4 / 5* : utiliser le module [raw](https://docs.ansible.com/ansible/raw_module.html) d'Ansible
  25. * *OpenBSD* : voir **[pré-requis pour OpenBSD](#pré-requis-openbsd)**
  26. * *FreeBSD* : `# pkg install python`
  27. ## Utilisation de base
  28. Configuration minimale :
  29. ~~~
  30. $ cat ~/.ansible.cfg
  31. [defaults]
  32. inventory = $HOME/.ansible/hosts
  33. [ssh_connection]
  34. #ssh_args = -o ControlMaster=no -o ControlPersist=no
  35. ssh_args = -o ControlMaster=auto -o ControlPersist=300s
  36. pipelining = True
  37. ~~~
  38. Exemples d'utilisation basique sur sa machine en local :
  39. ~~~
  40. $ ansible localhost -m ping
  41. localhost | SUCCESS => {
  42. "changed": false,
  43. "ping": "pong"
  44. }
  45. ~~~
  46. Ou sur une machine distante :
  47. ~~~
  48. $ echo mon-serveur >> ~/.ansible/hosts
  49. $ ssh-copy-id mon-serveur
  50. $ ansible mon-serveur -i $HOME/.ansible/hosts -m ping --one-line --forks 1
  51. mon-serveur | SUCCESS => {"changed": false, "ping": "pong"}
  52. $ ansible "mon-*" -m command --args "date"
  53. mon-serveur | SUCCESS | rc=0 >>
  54. jeudi 26 mai 2016, 23:16:01 (UTC+0200)
  55. ~~~
  56. ## Les éléments d'Ansible
  57. 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 -m mon_module`.
  58. 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.
  59. 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).
  60. 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.
  61. 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.
  62. ### modules
  63. <http://docs.ansible.com/ansible/list_of_all_modules.html>
  64. Un module est comme une bibliothèque. Il constitue une couche d'abstraction par rapport au shell et commandes sous-jacentes. C'est cette couche qui permet l'idempotence et le fonctionnement sur plusieurs plateformes.
  65. Les modules disposent de certains comportements et fonctionnalités communs : indication de succès/erreurs/changements, gestion des [variables](http://docs.ansible.com/ansible/playbooks_variables.html), des [conditions](http://docs.ansible.com/ansible/playbooks_conditionals.html), des [boucles](http://docs.ansible.com/ansible/playbooks_loops.html), des états de sortie…
  66. Pour avoir la liste des modules utilisables : `ansible-doc -l`
  67. Voici quelques exemples de modules que nous utilisons :
  68. * Module [command](http://docs.ansible.com/ansible/command_module.html) :
  69. ~~~{.yaml}
  70. - command: date
  71. ~~~
  72. Ce module ne permet que l'exécution de commandes simple (pas de pipe…) mais en échange il vérifie les commandes et les assainit pour limiter les injections.
  73. * Module [shell](http://docs.ansible.com/ansible/shell_module.html) :
  74. ~~~{.yaml}
  75. - shell: cat foo.txt | grep bar
  76. ~~~
  77. Ce module permet en revanche d'exécuter arbitrairement et sans contrôle toute commande, au sein d'un shell lancé pour l'occasion.
  78. * Module [file](http://docs.ansible.com/ansible/file_module.html) :
  79. ~~~{.yaml}
  80. - file:
  81. path: /etc/cron.daily/apticron
  82. state: absent
  83. ~~~
  84. * Module [copy](http://docs.ansible.com/ansible/copy_module.html) :
  85. ~~~{.yaml}
  86. - copy
  87. src: files/foo
  88. dest: /etc/bar
  89. owner: root
  90. group: root
  91. mode: "0644"
  92. ~~~
  93. * Module [replace](http://docs.ansible.com/ansible/replace_module.html) :
  94. ~~~{.yaml}
  95. - replace:
  96. dest: /etc/ssh/sshd_config
  97. regexp: '^(Match User ((?!{{ name }}).)*)$'
  98. replace: '\1,{{ name }}'
  99. ~~~
  100. * Module [lineinfile](http://docs.ansible.com/ansible/lineinfile_module.html) :
  101. ~~~{.yaml}
  102. - lineinfile:
  103. dest: /etc/evocheck.cf
  104. insertafter: EOF
  105. line: "IS_APTICRON=0"
  106. regexp: "^IS_APTICRON="
  107. ~~~
  108. *Note:* _replace_ vs _lineinfile_ ? Le fonctionnement exact de _replace_ et de _lineinfile_ peut être déroutant. Voici quelques constatations :
  109. * avec _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 pour remplacer au lieu d'insérer !
  110. * avec _lineinfile_, sauf cas tordus, l'argument _regexp_ doit matcher l'argument _line_ (sinon il va insérer la valeur de _line_ à chaque exécution !)
  111. * _lineinfile_ va d'abord évaluer si _regexp_ matche et remplacer la dernière occurrence ; si _regexp_ ne matche pas, il ajoute alors _line_ (sans d'autre condition… même si elle existe déjà)
  112. * _replace_ va remplacer uniquement si _regex_ est matché, comme la commande _sed_
  113. * avec _lineinfile_, si l'on veut utiliser une référence (`\1`) dans _line_, ça donne une erreur, il faut utiliser _replace_
  114. * avec _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_).
  115. * Module [blockinfile](http://docs.ansible.com/ansible/blockinfile_module.html) :
  116. ~~~{.yaml}
  117. - blockinfile:
  118. dest: /etc/apache2/envvars
  119. block: |
  120. ## Set umask for writing by Apache user.
  121. ## Set rights on files and directories written by Apache
  122. ~~~
  123. * Module [ini_file](http://docs.ansible.com/ansible/ini_file_module.html) :
  124. ~~~{.yaml}
  125. - ini_file:
  126. dest: /root/.my.cnf
  127. section: client
  128. option: user
  129. value: root
  130. mode: "0640"
  131. ~~~
  132. Ce module permet de facilement d'ajouter/modifier/supprimer des valeurs dans des fichiers INI, dans la bonne section, sans se soucier de la syntaxe.
  133. * Module [user](http://docs.ansible.com/ansible/user_module.html) :
  134. ~~~{.yaml}
  135. - user:
  136. state: present
  137. name: "jdoe"
  138. comment: 'John Doe'
  139. shell: /bin/bash
  140. groups: adm
  141. append: yes
  142. password: '$6$k/Fg76xH'
  143. ~~~
  144. * Module [group](http://docs.ansible.com/ansible/group_module.html) :
  145. ~~~{.yaml}
  146. - group:
  147. state: present
  148. name: "foobarcorp"
  149. gid: "1042"
  150. ~~~
  151. * Module [stat](http://docs.ansible.com/ansible/stat_module.html) :
  152. ~~~{.yaml}
  153. - stat:
  154. path: /etc/sudoers.d/foo
  155. register: foo_sudoers_file
  156. ~~~
  157. * Module [apt](http://docs.ansible.com/ansible/apt_module.html) :
  158. ~~~{.yaml}
  159. - apt:
  160. name: '{{ item }}'
  161. state: latest
  162. update_cache: yes
  163. cache_valid_time: 3600
  164. with_items:
  165. - vim
  166. - htop
  167. ~~~
  168. Ce module fait partie d'une courte liste de modules pour lesquels l'utilisation d'une boucle (avec `with_items` par exemple) ne provoque pas l'exécution séquentielle et répétée du module. Dans l'exemple ci-dessus le module utilisera "apt" intelligemment.
  169. * Module [apt_repository](http://docs.ansible.com/ansible/apt_repository_module.html) :
  170. ~~~{.yaml}
  171. - name: exemple
  172. apt_repository:
  173. repo: "deb https://artifacts.elastic.co/packages/5.x/apt stable main"
  174. filename: elastic
  175. state: present
  176. ~~~
  177. L'indication "filename" permet de référencer le dépôt dans `/etc/apt/sources.list.d/<filename>.list`.
  178. * Module [mysql_user](http://docs.ansible.com/ansible/mysql_user_module.html) :
  179. ~~~{.yaml}
  180. - mysql_user:
  181. name: mysqladmin
  182. password: my_password
  183. priv: "*.*:ALL,GRANT"
  184. state: present
  185. config_file: /root/.my.cnf
  186. update_password: on_create
  187. ~~~
  188. Lorsqu'une réplication est en place, on peut choisir de ne pas propager l'action dans les binlogs, avec l'option `sql_log_bin: no`.
  189. * module [mysql_variables](http://docs.ansible.com/ansible/latest/mysql_variables_module.html)
  190. ~~~{.yaml}
  191. - mysql_variables:
  192. variable: read_only
  193. value: 1
  194. ~~~
  195. Cela permet d'exécuter une commande du type "SET GLOBAL read_only = 1;" de manière idempotente.
  196. * module [htpasswd](http://docs.ansible.com/ansible/latest/htpasswd_module.html)
  197. ~~~{.yaml}
  198. - htpasswd:
  199. path: /etc/nginx/htpasswd_phpmyadmin
  200. name: jdoe
  201. password: 'PASSWORD'
  202. owner: root
  203. group: www-data
  204. mode: "0640"
  205. ~~~
  206. Il nécessite la bibliothèque Python "passlib", installable sous Debian grace au paquet "python-passlib".
  207. * Module [sysctl](http://docs.ansible.com/ansible/sysctl_module.html) :
  208. ~~~{.yaml}
  209. - name: exemple
  210. sysctl:
  211. name: vm.max_map_count
  212. value: 262144
  213. sysctl_file: /etc/sysctl.d/elasticsearch.conf
  214. ~~~
  215. * Module [alternatives](http://docs.ansible.com/ansible/alternatives_module.html) :
  216. ~~~{.yaml}
  217. - alternatives:
  218. name: editor
  219. path: /usr/bin/vim.basic
  220. ~~~
  221. * Module [service](http://docs.ansible.com/ansible/service_module.html) :
  222. ~~~{.yaml}
  223. - name: exemple pour redémarrer un service (compatible avec sysvinit, systemd…)
  224. service: nginx
  225. state: restarted
  226. ~~~
  227. * Module [openbsd_pkg](http://docs.ansible.com/ansible/openbsd_pkg_module.html) :
  228. ~~~{.yaml}
  229. - openbsd_pkg:
  230. name: "{{ item }}"
  231. state: present
  232. with_items:
  233. - wget
  234. - vim--no_x11
  235. ~~~
  236. * module [timezone](http://docs.ansible.com/ansible/timezone_module.html) :
  237. ~~~{.yaml}
  238. - timezone:
  239. name: Europe/Paris
  240. ~~~
  241. Si systemd est présent, le module utilise `timedatectl`.
  242. Sinon, sur Debian il utilise "/etc/timezone" et reconfigure le paquet "tzdata".
  243. * module [git](http://docs.ansible.com/ansible/latest/git_module.html) :
  244. ~~~{.yaml}
  245. - git:
  246. repo: https://forge.evolix.org/evoadmin-web.git
  247. dest: /home/evoadmin/www
  248. version: master
  249. update: yes
  250. ~~~
  251. Pour avoir plus d'infos sur un module :
  252. ~~~
  253. # ansible-doc shell
  254. > SHELL
  255. The [shell] module takes the command name followed by a li
  256. space-delimited arguments. It is almost exactly like the
  257. (…)
  258. ~~~
  259. > *Note* : c'est pratique pour avoir la documentation exacte pour votre version d'Ansible. En effet, celle du site correspond à la dernière version et n'indique pas toujours toutes les différences.
  260. ### playbook
  261. <http://docs.ansible.com/ansible/playbooks.html>
  262. Un playbook va ensuite dérouler des actions qui seront organisées en _tasks_, [roles](#roles) et [handlers](#handlers).
  263. Exemple de playbook simple :
  264. ~~~{.yaml}
  265. ---
  266. - hosts: all
  267. tasks:
  268. - shell: echo hello World
  269. # vim:ft=ansible:
  270. ~~~
  271. Un playbook plus évolué :
  272. ~~~{.yaml}
  273. ---
  274. - hosts: all
  275. gather_facts: yes
  276. become: yes
  277. vars_files:
  278. - 'vars/main.yml'
  279. vars:
  280. external_roles: ~/GIT/ansible-roles
  281. external_tasks: ~/GIT/ansible-public/tasks
  282. pre_tasks:
  283. - name: Minifirewall is stopped (temporary)
  284. service:
  285. name: minifirewall
  286. state: stopped
  287. roles:
  288. - "{{ external_roles }}/minifirewall"
  289. post_tasks:
  290. - include: "{{ external_tasks }}/commit_etc_git.yml"
  291. vars:
  292. commit_message: "Ansible run firewall.yml"
  293. handlers:
  294. - name: restart minifirewall
  295. service:
  296. name: minifirewall
  297. state: restarted
  298. # vim:ft=ansible:
  299. ~~~
  300. On lance des playbooks ainsi :
  301. ~~~
  302. $ ansible-playbook PLAYBOOK.yml --limit HOSTNAME --forks 1
  303. $ ansible-playbook PLAYBOOK_WITH_SUDO.yml --limit HOSTNAME --ask-become-pass
  304. ~~~
  305. 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) :
  306. * `-vvvv` : très verbeux (utile notamment pour debug SSH quand on a une erreur _unreachable_)
  307. * `-k` / `--ask-pass` : demande le mot de passe pour la connexion SSH
  308. * `-K` / `--ask-become-pass` : demande le mot de passe pour l'escalade (via sudo, su, doas…)
  309. * `-l` / `--limit HOSTNAME` : limite la connexion à un ou plusieurs serveurs (attention, par défaut c'est *all*, cf `/etc/ansible/hosts`)
  310. * `-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
  311. * `-i` / `--inventory FILENAME/DIRNAME` : utiliser le fichier ou le dossier d'inventaire fournit en paramètre
  312. * `-i` / `--inventory "example.com,"` : utilise un inventaire dynamique définie en paramètre (doit être un tableau)
  313. * `-D` / `--diff` : montre un diff des changements effectués par les templates
  314. #### Limiter l'exécution à certaines machines
  315. Quelques exemples d'utilisation de l'option `--limit` (ou `l`) :
  316. * limiter aux groupes _www_ et _sql_ (qui peuvent être indifféremment des groupes ou des serveurs) :
  317. ~~~
  318. $ ansible-playbook -l "www:sql" playbook.yml
  319. ~~~
  320. * limiter aux serveurs _foo-www01_, _foo-lb01_, _foo-filer_… :
  321. ~~~
  322. $ ansible-playbook -l "foo-*" playbook.yml
  323. ~~~
  324. * limiter aux 10 premiers serveurs de l'inventaire (utile pour faire par paquets) :
  325. ~~~
  326. $ ansible-playbook -l "*[0:9]" playbook.yml
  327. ~~~
  328. * puis à ceux restants :
  329. ~~~
  330. $ ansible-playbook -l "*[10:]" playbook.yml
  331. ~~~
  332. Il est de toute façon préférable de ne pas mettre `all` dans le champs `hosts` dans le playbook pour éviter un oubli.
  333. ### handlers
  334. Les **handlers** sont des actions définies dans un playbook, qui ne sont exécutées que dans certains cas.
  335. On utilise l'option `notify` au sein d'un module pour évoquer un handler. Celui-ci ne sera exécuté que si un module a effectivement provoqué un changement.
  336. L'usage classique est de recharger un service après une modification de configuration : si la modification est réalisée => le service est rechargé, si la modification est déjà effectuée => aucune action.
  337. Par défaut, l'exécution effective des handlers se fait **une seule fois** à la fin du playbook, quel que soit le nombre de fois où il a été demandé pendant l'exécution.
  338. Exemple :
  339. ~~~{.yaml}
  340. tasks:
  341. - name: copy Apache configuration
  342. copy: (…)
  343. notify: Restart Apache
  344. handlers:
  345. - name: Restart Apache
  346. service:
  347. name: apache2
  348. state: restarted
  349. ~~~
  350. 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 :
  351. ~~~{.yaml}
  352. - meta: flush_handlers
  353. ~~~
  354. > *Note* : n'importe quel module peut être utilisé comme handler.
  355. ### roles
  356. <http://docs.ansible.com/ansible/playbooks_roles.html>
  357. 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 :
  358. ~~~
  359. foo
  360. ├── defaults
  361. │   └── main.yml
  362. ├── files
  363. ├── handlers
  364. │   └── main.yml
  365. ├── meta
  366. │   └── main.yml
  367. ├── README.md
  368. ├── tasks
  369. │   └── main.yml
  370. ├── templates
  371. ├── tests
  372. │   ├── inventory
  373. │   └── test.yml
  374. └── vars
  375. └── main.yml
  376. ~~~
  377. Cette structure permet à Ansible de retrouver automatiquement les fichiers et de les rendre disponibles dans l'exécution du rôle.
  378. À titre d'exemple, voici des rôles Ansible que nous utilisons : <https://forge.evolix.org/projects/ansible-roles/repository>
  379. ### inventory
  380. <http://docs.ansible.com/ansible/intro_inventory.html>
  381. La partie **inventory** correspond à la description de l'inventaire des serveurs à configurer et inclus un mécanisme de configuration individuelle et par groupe.
  382. Il 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 ranger dans des groupes.
  383. Exemple:
  384. ~~~
  385. hostname.internal
  386. [httpservers]
  387. machine[01:57].example.com
  388. http.example.com:2222
  389. [dbservers]
  390. machine12.example.com
  391. machine50.example.com
  392. m[a:o]chine52.example.com
  393. alias ansible_port=2222 ansible_host=192.0.2.42
  394. [client]
  395. host1 http_port=80 maxRequestsPerChild=808 # des variables qui seront automatiquement auto-completées liées à cet host
  396. [commercant]
  397. mercerie
  398. chapeautier
  399. [commercant:vars]
  400. ntp_server=ntp.mercerie.example.com
  401. proxy=proxy.mercerie.example.com
  402. ~~~
  403. * `hostname.internal` : serveur présent dans aucun groupe
  404. * `[httpservers]` : le nom du groupe (pour les serveurs http). Les noms de hosts qui suivent appartiendront à ce groupe
  405. * `machine[01:57].example.com` : on peut indiquer une [pseudo-]expression régulière - ici ajoutera les machines _machine01.example.com_, _machine02.example.com_, _machine03.example.com__machine57.example.com_
  406. * `HOSTNAME:2222` : ansible se connecte par ssh, et _HOSTNAME_ a un port SSH d'écoute différent qui est 2222
  407. * `[dbservers]` : groupe pour les serveurs de base de données
  408. * `machine50.example.com` : cette machine est déjà présente dans le groupe _httpservers_, mais sera aussi accessible à partir du groupe _dbservers_
  409. * `alias ansible_port=2222 ansible_host=192.0.2.42` : la machine _alias_ n'a pas un vrai FQDN mais pointera vers _192.0.2.42_ 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)
  410. * `host1 http_port=80 maxRequestsPerChild=808` : des variables qui seront automatiquement disponibles pour les actions sur _host1_
  411. * `[commercant:vars]` : des variables qui seront liées au groupe _commercant_.
  412. On peut aussi créer des groupes de groupes en utilisant `:children`
  413. 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>
  414. Les variables propres à Ansible : <http://docs.ansible.com/ansible/intro_inventory.html#list-of-behavioral-inventory-parameters>
  415. ### variables
  416. Les variables sont un élément clé de la configuration des playbooks et roles. Exemple :
  417. ~~~{.yaml}
  418. vars:
  419. ip: 192.0.2.42
  420. conf_file: /etc/foo.conf
  421. tasks:
  422. - command: echo {{ ip }} >> {{ conf_file }}
  423. ~~~
  424. 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)) :
  425. * role defaults
  426. * inventory vars
  427. * inventory group_vars
  428. * inventory host_vars
  429. * playbook group_vars
  430. * playbook host_vars
  431. * host facts
  432. * play vars
  433. * play vars_prompt
  434. * play vars_files
  435. * registered vars
  436. * set_facts
  437. * role and include vars
  438. * block vars (only for tasks in block)
  439. * task vars (only for the task)
  440. * extra vars (always win precedence)
  441. 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 celles d'un serveur en particulier dans un fichier du nom du serveur. Voici l'arborescence conseillée :
  442. ~~~
  443. └── inventory
  444.    ├── hosts # fichier d'inventaire
  445.    ├── group_vars # dossier regrouppant …
  446. │   └── group1.yml # … les variables du groupe "group1"
  447. │   └── group2.yml # … les variables du groupe "group2"
  448.   └── host_vars # dossier regrouppant …
  449.    └── hostname1.yml # … les variables du serveur "hostname1"
  450.    └── hostname2.yml # … les variables du serveur "hostname2"
  451. ~~~
  452. Les groupes sont définis dans le fichier d'[inventaire](http://docs.ansible.com/ansible/intro_inventory.html).
  453. ### Tags
  454. <https://docs.ansible.com/ansible/playbooks_tags.html>
  455. Les tags permettent de ranger/trier chaque tâche ou rôle dans une catégorie.
  456. ~~~{.yaml}
  457. - name: Coucou
  458. debug:
  459. msg: "Saloute!"
  460. tags: message
  461. ~~~
  462. On peut également utiliser les tags pour limiter/exclure des tâches :
  463. ~~~
  464. $ ansible-playbook (…) --skip-tags "message"
  465. ~~~
  466. On peut aussi n'exécuter que certains tags :
  467. ~~~
  468. $ ansible-playbook (…) --tags "configuration,packages"
  469. ~~~
  470. > *Note* : on peut également _taguer_ des rôles `include`.
  471. ### Register
  472. `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 retournées par le module.
  473. Pour `shell`, on a le droit à `.stdout`, `.stderr`, `.rc`… mais cela dépend des valeurs de retour du module.
  474. Il est possible de consulter le contenu détaillé de la variable avec `debug` :
  475. ~~~{.yaml}
  476. - stat:
  477. path: /etc/passwd
  478. register: st
  479. - debug:
  480. var: st
  481. - fail:
  482. msg: "Whoops! file ownership has changed"
  483. when: st.stat.pw_name != 'root'
  484. ~~~
  485. Pour certains modules, `register` est presque un passage obligatoire pour une utilisation cohérente des éléments (stat…).
  486. ### Vault
  487. <http://docs.ansible.com/ansible/latest/playbooks_vault.html>
  488. Un Vault permet d'avoir un fichier protégé par un mot de passe.
  489. Pour éditer un Vault nommé `foo.yml` (utilise l'éditeur configuré) :
  490. ~~~
  491. # ansible-vault edit foo.yml
  492. ~~~
  493. Pour consulter un Vault (sortie standard) :
  494. ~~~
  495. # ansible-vault view foo.yml
  496. ~~~
  497. Pour modifier le mot de passe d'un vault :
  498. ~~~
  499. # ansible-vault rekey foo.yml
  500. ~~~
  501. Pour créer un vault vide :
  502. ~~~
  503. # ansible-vault create bar.yml
  504. ~~~
  505. Pour créer un vault sur un fichier clair :
  506. ~~~
  507. # ansible-vault encrypt baz.yml
  508. ~~~
  509. Pour retirer le chiffrement d'un fichier chiffré :
  510. ~~~
  511. # ansible-vault decrypt baz.yml
  512. ~~~
  513. Pour utiliser vault, il faut préciser l'option `--ask-vault-pass` avec les commandes `ansible` ou `ansible-playbook`.
  514. ## Configuration
  515. <https://docs.ansible.com/ansible/intro_configuration.html>
  516. La configuration est lue dans l'ordre suivant :
  517. * `ANSIBLE_CONFIG` (variable d'environnement)
  518. * `./ansible.cfg`
  519. * `~/.ansible.cfg`
  520. * `/etc/ansible/ansible.cfg`
  521. ### ansible.cfg
  522. Quelques options qui peuvent être utiles :
  523. * `display_args_to_stdout` : mettre à `True` si on veut voir tout le contenu du _tasks_ executé pour chaque étape écrit sur _stdout_
  524. * `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)_
  525. * `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_)
  526. * `force_color` : mettre à `1` pour forcer la couleur
  527. * `forks` : le nombre de processus en parallèle possible lors déploiement du script Ansible sur nombreux _hosts_
  528. * `hosts` : accès vers les _hosts_ par défaut (`all`)
  529. * `private_key_file` : le chemin pour la clé pem
  530. * `remote_port` : le port SSH par défaut (`22`)
  531. * `remote_user` : l'utilisateur pour la connexion SSH par défaut (`root`)
  532. * `retry_files_enabled` : mettre à `True` pour la création de fichier `.retry` après un échec d'Ansible, pour reprendre le travail précédent - ajouté en argument dans l'appel de la commande
  533. ## Jinja
  534. Ansible utilise la bibliothèque [Jinja2](http://jinja.pocoo.org/docs/2.10/templates/) pour ses templates, ses filtres, ses conditions…
  535. * fusionner et dédoublonner 2 listes :
  536. ~~~
  537. a: [1, 2, 3]
  538. b: [3, 4, 5]
  539. c: a | union(b) | unique
  540. ~~~
  541. Il existe plein de [filtres sur les listes](http://docs.ansible.com/ansible/latest/playbooks_filters.html#list-filters) ; `union`, `intersect`, `difference`, `unique`, `sort`
  542. * liste avec valeur par défaut (variable vide, indéfinie ou liste vide)
  543. ~~~
  544. a: []
  545. c: a | default([1, 2], true)
  546. ~~~
  547. C'est le second paramètre (`true`) qui permet à `default()` d'agir lorsque la variable `a` n'est pas seulement nulle ou indéfinie, mais aussi en cas de chaîne vide, tableau vide…
  548. * boucler sur un attribut d'un dictionnaire
  549. On veut par exemple créer les groupes des utilisateurs du dictionnaire suivant :
  550. ~~~
  551. users:
  552. user1:
  553. name: user1
  554. groups: group1
  555. user2:
  556. name: user2
  557. groups: group2
  558. user3:
  559. name: user3
  560. groups: group1
  561. ~~~
  562. On va donc faire une boucle avec la liste des groupes définit dans l'attribut "groups" :
  563. ~~~
  564. - name: "Create secondary groups"
  565. group:
  566. name: "{{ item }}"
  567. with_items: "{{ users.values() | map(attribute='groups') | list | unique }}"
  568. ~~~
  569. ## Erreurs
  570. Les messages d'erreurs ne sont pas le point fort d'Ansible. Il n'est pas toujours clair de savoir si c'est un soucis de syntaxe YAML, un problème de sémantique d'Ansible ou une erreur dans l'utilisation de Jinja2. De plus, Ansible tente de faire des recommandations, mais elles sont des fois plus déroutantes qu'éclairantes. En voici quelques unes que nous avons rencontrées.
  571. ### unbalanced jinja2 block or quotes
  572. ~~~
  573. fatal: [HOSTNAME]: FAILED! => {"failed": true, "reason": "error while splitting arguments, either an unbalanced jinja2 block or quotes"}
  574. ~~~
  575. Bien vérifier la syntaxe : cela peut être un guillemet mal fermé (ou mélange simples/doubles guillemets), ou encore histoire de crochet devenant une parenthèse…
  576. ### Missing required arguments
  577. ~~~
  578. fatal: [HOSTNAME]: FAILED! => {"changed": false, "failed": true, "msg": "missing required arguments: section"}
  579. ~~~
  580. Le message est assez clair, donc bien relire la doc du module sur Ansible pour ajouter les arguments obligatoires pour ce module.
  581. ### Requires stdlib json or simplejson module
  582. ~~~
  583. fatal: [HOSTNAME]: FAILED! => {"changed": false, "failed": true, "msg": "Error: ansible requires the stdlib json or simplejson module, neither was found!"}
  584. ~~~
  585. ~~~
  586. # apt install python-simplejson
  587. ~~~
  588. ## Astuces
  589. ### Vérifier un playbook
  590. * Vérifier la syntaxe :
  591. ~~~
  592. $ ansible-playbook --syntax-check my-experimental-playbook.yml
  593. ~~~
  594. Voir <http://www.yamllint.com/>
  595. * vérifier les actions qui vont être faites (mode `dry-run`) sans rien exécuter :
  596. ~~~
  597. $ ansible-playbook --check my-experimental-playbook.yml
  598. ~~~
  599. > *Note* : certaines actions ne sont pas exécutées en mode "check", cela peut donc perturber celles qui sont bassées dessus.
  600. * avoir le diff des fichiers modifiés (ne marche pas avec les modules `replace`/`lineinfile` à priori) :
  601. ~~~
  602. $ ansible-playbook --check --diff my-experimental-playbook.yml
  603. ~~~
  604. ### Stopper l'éxecution du code
  605. Pour par exemple, stopper le code à un moment pour lire les valeurs d'une variables
  606. ~~~{.yaml}
  607. - debug:
  608. var: foo
  609. - command: /bin/false
  610. ~~~
  611. ou
  612. ~~~{.yaml}
  613. - debug:
  614. var: foo
  615. - fail:
  616. msg: "FAIL"
  617. ~~~
  618. ou
  619. ~~~{.yaml}
  620. - debug:
  621. var: foo
  622. - pause:
  623. ~~~
  624. ### Lancement tâches hosts asynchrone
  625. 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.
  626. ~~~{.yaml}
  627. - hosts: all
  628. (…)
  629. strategy: free
  630. ~~~
  631. > *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.
  632. ### Fréquence des hosts
  633. 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.
  634. * `Fork` pour le nombre d'hôtes simultanés (modifiable dans le fichier _ansible.cfg_ - mettre une valeur importante > centaine).
  635. * `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é.
  636. ### Cowsay
  637. <https://support.ansible.com/hc/en-us/articles/201957877-How-do-I-disable-cowsay->
  638. Si la commande `cowsay` est disponible sur votre machine, vous verrez un message à la fin :
  639. ~~~
  640. ____________________
  641. < NO MORE HOSTS LEFT >
  642. --------------------
  643. \ ^__^
  644. \ (oo)\_______
  645. (__)\ )\/\
  646. ||----w |
  647. || ||
  648. ____________
  649. < PLAY RECAP >
  650. ------------
  651. \ ^__^
  652. \ (oo)\_______
  653. (__)\ )\/\
  654. ||----w |
  655. || ||
  656. ~~~
  657. Pour le désactiver : `export ANSIBLE_NOCOWS=1`
  658. Disponible aussi dans la conf du fichier `/etc/ansible/ansible.cfg`
  659. ### Conditions dans fichier jinja2
  660. <http://jinja.pocoo.org/docs/dev/templates/#builtin-tests>
  661. ~~~
  662. {% if python_is_installed is defined %}
  663. Ansible devrait marcher -pardi!
  664. {% endif %}
  665. ~~~
  666. Voir la doc pour plus de détails : <http://jinja.pocoo.org/docs/dev/>
  667. ### Lire une entrée au clavier
  668. <https://docs.ansible.com/ansible/playbooks_prompts.html>
  669. 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 :
  670. ~~~{.yaml}
  671. vars_prompt:
  672. - name: 'prenom'
  673. prompt: 'Quel est votre prénom ?'
  674. private: no
  675. tasks:
  676. - debug:
  677. var: prenom
  678. ~~~
  679. Malheureusement pour le moment, cela doit se situer avant `tasks`.
  680. 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) :
  681. ~~~{.yaml}
  682. - include: './tasks/autre.yml'
  683. vars:
  684. prenom_de_autre: prenom
  685. ~~~
  686. ### Exécuter un playbook en mode interactif
  687. <https://docs.ansible.com/ansible/playbooks_startnstep.html>
  688. ~~~
  689. $ ansible-playbook playbook.yml --step
  690. ~~~
  691. ### Ne pas lancer une commande shell si le fichier existe
  692. 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.
  693. Le corollaire est possible avec l'argument `removes` qui empêche l'exécution si le fichier n'existe pas.
  694. Ces arguments sont disponibles pour certains modules (comme `shell`).
  695. C'est beaucoup plus simple et rapide que de tester le fichier par le module `stat` juste avant.
  696. ### Lancer tâche sur machine précise (voire localhost)
  697. <https://docs.ansible.com/ansible/playbooks_delegation.html#delegation>
  698. ~~~{.yaml}
  699. - name: /etc/hosts
  700. command: cat /etc/hosts
  701. register: tmp
  702. delegate_to: localhost
  703. - debug:
  704. var: tmp.stdout
  705. ~~~
  706. Pour une exécution locale, on peut aussi utiliser l'attribut `local_action`.
  707. ### Ne lancer tâche qu'une seule fois
  708. <https://docs.ansible.com/ansible/playbooks_delegation.html#run-once>
  709. ~~~{.yaml}
  710. - name: Début installation, envoie email
  711. run_once: true
  712. (…)
  713. ~~~
  714. 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.
  715. ### Appliquer une tâche à une liste (tableau) -> boucle
  716. #### with_items
  717. ~~~{.yaml}
  718. - name: Manger les fruits
  719. command: eat '{{ item }}'
  720. with_items:
  721. - Apple
  722. - Orange
  723. - Strawberry
  724. - Mango
  725. ~~~
  726. Par exemple pour l'installation de plusieurs nouveaux paquets :
  727. ~~~{.yaml}
  728. ---
  729. - hosts: localhost
  730. tasks:
  731. - apt:
  732. name: '{{ item }}'
  733. state: present
  734. with_items:
  735. - cmatrix
  736. - tetrinet-server
  737. - tetrinet-client
  738. - xtel
  739. - xtell
  740. ~~~
  741. Même si il y aura plusieurs paquets installés, cela ne comptera que pour *un* changement (`changed=1`).
  742. Cette tâche appellera un par un les éléments de la liste (présents dans `with_items`) pour le module.
  743. #### with_nested
  744. Pour croiser les éléments des items :
  745. ~~~{.yaml}
  746. tasks:
  747. - include: "./ajout_utilisateur_sur_machine.yml"
  748. vars:
  749. user: "{{ item[0] }}"
  750. server: "{{ item[1] }}"
  751. with_nested:
  752. - [ 'alice', 'bob' ]
  753. - [ 'machine1', 'machine2', 'machine-backup' ]
  754. ~~~
  755. Cela a pour effet d'exécuter l'inclusion pour `alice` pour chacune des 3 machines, puis pour `bob` pour chacune des 3 machines.
  756. #### with_dict
  757. Avec hash :
  758. ~~~{.yaml}
  759. users:
  760. bob:
  761. name: Bob
  762. uid: 1000
  763. home: /home/bob
  764. alice:
  765. name: Alice
  766. uid: 1001
  767. home:
  768. tasks:
  769. - user:
  770. name: "{{ item.key }}"
  771. comment: "{{ item.value.name }}"
  772. uid: "{{ item.value.uid }}"
  773. home: "{{ item.value.home }}"
  774. with_dict: "{{ users }}"
  775. ~~~
  776. #### [with_first_found](http://docs.ansible.com/ansible/playbooks_loops.html#finding-first-matched-files)
  777. Permet de prendre le premier fichier trouvé :
  778. ~~~{.yaml}
  779. - name: Copy HAProxy configuration
  780. template:
  781. src: "{{ item }}"
  782. dest: /etc/haproxy/haproxy.cfg
  783. force: yes
  784. with_first_found:
  785. - "haproxy.cfg/{{ inventory_hostname }}"
  786. - "haproxy.cfg/{{ host_group }}"
  787. - "aproxy.cfg/default"
  788. ~~~
  789. 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.
  790. ### Se connecter sous un autre utilisateur UNIX
  791. 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.
  792. ~~~
  793. $ ansible-playbook -u michu -k play.yml
  794. ~~~
  795. ### Éviter que la commande shell n'indique d'élement 'changed'
  796. Sur tous les modules, chaque tâche retourne un statut sur son résultat :
  797. * `ok` : aucune modification n'a été nécessaire
  798. * `changed` : une modification a eu lieu par rapport à l'état précédent (droits fichiers…)
  799. * `failed` : une erreur s'est produite
  800. Pour des modules comme `shell`, `command`… Ansible ne peut savoir si un changement a eu lieu ou pas. Il indique alors toujours `changed`.
  801. Il est possible de forcer le statut du changement :
  802. ~~~{.yaml}
  803. - command: date
  804. changed_when: False
  805. ~~~
  806. ### Voir variables disponibles
  807. ~~~
  808. $ ansible -m setup <hostname>
  809. HOSTNAME | SUCCESS => {
  810. "ansible_facts": {
  811. (…)
  812. "ansible_architecture": "x86_64",
  813. "ansible_bios_date": "12/01/2006",
  814. "ansible_bios_version": "VirtualBox",
  815. "ansible_cmdline": {
  816. "BOOT_IMAGE": "/boot/vmlinuz-3.16.0-4-amd64",
  817. "quiet": true,
  818. "ro": true,
  819. "root": "UUID=37de3cbb-3f28-48d2-a4eb-c893a2f2fbc3"
  820. },
  821. "ansible_date_time": {
  822. "date": "2016-05-06",
  823. "day": "06",
  824. "epoch": "1462546886",
  825. "hour": "17",
  826. (…)
  827. },
  828. "ansible_default_ipv4": {
  829. (…)
  830. }
  831. ~~~
  832. ~~~
  833. $ ansible -m debug -a "var=hostvars['hostname']" localhost
  834. ~~~
  835. Pour récupérer toutes les adresses MAC des machines :
  836. ~~~{.yaml}
  837. ---
  838. - hosts: all
  839. gather_facts: true
  840. tasks:
  841. - debug:
  842. var: ansible_eth0.macaddress
  843. ~~~
  844. que l'on pourra combiner par exemple avec un pipe en ligne de commande :
  845. ~~~
  846. $ ansible-playbook mac_address.yml | grep ansible_eth0.macaddress | sed 's/^\s*"ansible_eth0.macaddress": "\(.*\)"/\1/'
  847. ~~~
  848. Il est possible aussi d'accéder aux variables d'environnement shell :
  849. ~~~
  850. "{{ lookup('env','HOME') }}"
  851. ~~~
  852. ### Pré-requis OpenBSD
  853. Voici les étapes nécessaires à l'utilisation d'Ansible sur des serveurs OpenBSD.
  854. Installer _Python_ :
  855. ~~~
  856. # pkg_add -z python-2 sudo
  857. ~~~
  858. et surcharger la variable `ansible_python_interpreter` dans le fichier _inventory_ :
  859. ~~~
  860. [openbsd]
  861. serveur.example.com
  862. [openbsd:vars]
  863. ansible_python_interpreter=/usr/local/bin/python2.7
  864. ~~~
  865. ### Ansible Vault via GPG
  866. Afin de ne pas avoir à taper son mot de passe Vault à chaque utilisation, on peut stocker son mot de passe Vault dans un fichier chiffré par [GPG](HowtoGPG). Au préalable, il faut configurer GPG pour utiliser l'[agent GPG](HowtoGPG#agent-gpg).
  867. Ensuite, créer le script suivant dans `~/bin/open_vault.sh`
  868. ~~~
  869. #!/bin/sh
  870. gpg --quiet --batch --use-agent --decrypt ~/.ansible/vault.gpg
  871. ~~~
  872. Rendre ce script exécutable :
  873. ~~~
  874. chmod +x ~/bin/open_vault.sh
  875. ~~~
  876. Configurer Ansible pour utiliser ce script comme source du mot de passe Ansible Vault dans `~/.ansible.cfg` :
  877. ~~~
  878. [defaults]
  879. vault_password_file= ~/bin/open_vault.sh
  880. ~~~
  881. Stocker le mot de passe Ansible Vault dans un fichier chiffré via GPG :
  882. ~~~
  883. echo "VAULT_PASSWORD" | gpg -e -o ~/.ansible/vault.gpg
  884. ~~~
  885. Ansible va maintenant automatiquement déchiffrer les fichiers Vault via votre agent GPG et le fichier `~/.ansible/vault.gpg`.
  886. ### Git diff pour fichier vault
  887. Les diff de fichier chiffrés avec ansible-vault ne sont pas lisibles par défaut car ils s'appliquent sur le contenu chiffré des fichiers et non pas sur le contenu réel.
  888. On peux modifier cela, en modifiant sa config GIT dans son fichier **~/.gitconfig** :
  889. ~~~
  890. [diff "ansible-vault"]
  891. textconv = ansible-vault view
  892. cachetextconv = false
  893. ~~~
  894. Et en appliquant cette config au fichier vault dans ses dépôts Git dans le fichier **.gitattributes** :
  895. ~~~
  896. vars/evolinux-secrets.yml diff=ansible-vault
  897. ~~~
  898. ### Comparer des versions
  899. Dans le cas où on ne veut pas faire la même chose suivant la version
  900. sur lequelle on exécute la tâche, on peut utiliser
  901. [version_compare](https://docs.ansible.com/ansible/latest/playbooks_tests.html#version-comparison).
  902. Un cas concret :
  903. ~~~{.yaml}
  904. - name: Install monitoring-plugins on OpenBSD 5.6 and later
  905. openbsd_pkg:
  906. name: monitoring-plugins
  907. state: present
  908. when: ansible_distribution_version | version_compare("5.6",'>=')
  909. - name: Install nagios-plugins on OpenBSD before 5.6
  910. openbsd_pkg:
  911. name: nagios-plugins
  912. state: present
  913. when: ansible_distribution_version | version_compare("5.6",'<')
  914. ~~~
  915. ### Erreur : /usr/local/bin/python2.7: not found
  916. Si vous obtenez une erreur du type :
  917. ~~~
  918. $ ansible -m ping foo
  919. foo | FAILED! => {
  920. "changed": false,
  921. "failed": true,
  922. "module_stderr": "/bin/sh: 1: /usr/local/bin/python2.7: not found\n",
  923. "module_stdout": "",
  924. "msg": "MODULE FAILURE"
  925. }
  926. ~~~
  927. Pour une raison inconnue, Ansible détecte mal le chemin vers Python.
  928. Vous pouvez le forcer en utilisant l'option `-e 'ansible_python_interpreter=/usr/bin/python2.7'`.
  929. ### Export HTML d'un playbook
  930. Pour enregistrer la sortie d'exécution d'un playbook dans un fichier HTML (en gardant les couleurs et les warnings qui vont normalement sur la sortie d'erreur), on peut utiliser le paquet `aha` :
  931. ~~~
  932. $ ANSIBLE_FORCE_COLOR=true ansible-playbook playbook.yml 2>&1 | aha --black > output.html
  933. ~~~
  934. ## Exemples
  935. Voir [/HowtoAnsible/Exemples]().
  936. ## Ressources utiles
  937. * [Documentation officielle](http://docs.ansible.com/ansible/) (voir notamment la partie [Best Practices](http://docs.ansible.com/ansible/playbooks_best_practices.html))
  938. * 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)
  939. * [Ansible 101 - on a Cluster of Raspberry Pi 2s](https://www.youtube.com/watch?v=ZNB1at8mJWY)
  940. * 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))
  941. * [How Twitter uses Ansible](https://www.youtube.com/watch?v=fwGrKXzocg4) (AnsibleFest 2014)
  942. * [Orchestration with Ansible at Fedora Project](http://fr.slideshare.net/AdityaPatawari/ansible-33223245)