Table des matières
Ansible 1.7.2 (Debian Jessie)
https://github.com/lorin/ansible-quickref
production # inventory file for production servers stage # inventory file for stage environment group_vars/ all # here we assign variables to particular groups boston # "" host_vars/ hostname1 # if systems need specific variables, put them here xyz.boston.example.com # "" site.yml # master playbook webservers.yml # playbook for webserver tier dbservers.yml # playbook for dbserver tier roles/ common/ # this hierarchy represents a "role" defaults/ main.yml # variables par defaut du module, supplantées par tout le reste tasks/ # main.yml # <-- tasks file can include smaller files if warranted handlers/ # main.yml # <-- handlers file templates/ # <-- files for use with the template resource ntp.conf.j2 # <------- templates end in .j2 files/ # bar.txt # <-- files for use with the copy resource foo.sh # <-- script files for use with the script resource vars/ # main.yml # <-- variables associated with this role -> valeurs spécifiques, dont on a besoin d'être sur, suplpanté seulement par '-e' meta/ # main.yml # <-- role dependencies webtier/ # same kind of structure as "common" was above, done for the webtier role monitoring/ # "" fooapp/ # ""
Exemple de fichier d'inventaire
Groupé par localisation (enfants) et par rôle (parents).
# file: production [atlanta-webservers] www-atl-1.example.com www-atl-2.example.com [boston-webservers] www-bos-1.example.com www-bos-2.example.com [atlanta-dbservers] db-atl-1.example.com db-atl-2.example.com [boston-dbservers] db-bos-1.example.com # webservers in all geos [webservers:children] atlanta-webservers boston-webservers # dbservers in all geos [dbservers:children] atlanta-dbservers boston-dbservers # everything in the atlanta geo [atlanta:children] atlanta-webservers atlanta-dbservers # everything in the boston geo [boston:children] boston-webservers boston-dbservers
Exemple de variables d'inventaire
# file: group_vars/atlanta ntp: ntp-atlanta.example.com backup: backup-atlanta.example.com # file: group_vars/webservers apacheMaxRequestsPerChild: 3000 apacheMaxClients: 900 # file: group_vars/all ntp: ntp-boston.example.com backup: backup-boston.example.com # file: host_vars/db-bos-1.example.com foo_agent_port: 86 bar_agent_port: 99
site.yml - Un point d'entrée pour inclure les autres playbooks
# file: site.yml - include: webservers.yml - include: dbservers.yml
playbook : fait correspondre un groupe à des roles
# file: webservers.yml - hosts: webservers roles: - common - webtier
Exemple de fichier de tasks pour un role
# file: roles/common/tasks/main.yml - name: be sure ntp is installed yum: pkg=ntp state=installed tags: ntp - name: be sure ntp is configured template: src=ntp.conf.j2 dest=/etc/ntp.conf notify: - restart ntpd tags: ntp - name: be sure ntpd is running and enabled service: name=ntpd state=running enabled=yes tags: ntp
Exemple de handler
# file: roles/common/handlers/main.yml - name: restart ntpd service: name=ntpd state=restarted
Exemples
ansible-playbook -i production site.yml ansible-playbook -i production site.yml --tags ntp ansible-playbook -i production webservers.yml ansible-playbook -i production webservers.yml --limit boston ansible-playbook -i production webservers.yml --limit boston[0-10] ansible-playbook -i production webservers.yml --limit boston[10-20] ansible boston -i production -m ping ansible boston -i production -m command -a '/sbin/reboot'
Confirm what task names would be run if I ran this command and said “just ntp tasks”
ansible-playbook -i production webservers.yml --tags ntp --list-tasks
Confirm what hostnames might be communicated with if I said “limit to boston”
ansible-playbook -i production webservers.yml --limit boston --list-hosts
Information préalable
Si Red Hat Enterprise Linux (TM), CentOS, Fedora, Debian, or Ubuntu : prendre les derniers packages
Prérequis
- serveur : Python 2.6 or 2.7
- noeuds : ssh/sftp, Python 2.4 avec python-simplejson ou 2.5 ⇐ Python < 3 (attention à la version par défaut, si besoin, rensiegner la variable “ansible_python_interpreter”)
on peut bootstraper un noeud pour installer python 2.X en mode “raw” : ansible myhost –sudo -m raw -a “yum install -y python2 python-simplejson”
améliorations/changments notables sur les versions de Ansible
- 1.9 : ANSIBLE_HOSTS désuet, voir ANSIBLE_INVENTORY
- 2.x : fonctions plus puissantes pour les boucles
Installation
apt-get install ansible ansible-doc sudo sed --in-place "s/^remote_port/#remote_port/g" /etc/ansible/ansible.cfg iceweasel /usr/share/doc/ansible-doc/html/index.html
Par défaut, Ansible utilise l'authentification SSH par clef.
Par défaut, le serveur central se connecte sur les noeuds et lance des commandes/scripts. Mais cela peut être inversé (voir ansible-pull).
Premiers pas
variables d'environnement
BASEDIR="${HOME}/ansible" INVENTORY=${BASEDIR}/localhost LIMIT="localhost" PARALLELISM=5
création du répertoire de référence
if [ ! -d ${BASEDIR} ]; then mkdir ${BASEDIR} fi
initialisation de l'inventaire avec l'hôte local
echo "localhost ansible_connection=local" > ${INVENTORY}
test avec un simple ls
ansible --inventory=${INVENTORY} --limit=${LIMIT} --forks=${PARALLELISM} --module-name=shell --args="ls" all
Mettre plus de machines dans un inventaire
Le nom de fichier correspond a l'environnement. On peut utiliser des sections [<nom_du_groupe>] pour creer des groupes d'hotes (conseille : par emplacement geographique).
RQ : on peut faire apparaitre un meme hote dans plusieurs groupes, mais attention a la determination des variables et leurs précédences !
RQ : mettre au carre ~/.ssh/config au prealable, et copier la clef publique SSH sur les serveurs !
Exemple
Host plop.liberasys.com Hostname plop.liberasys.com Port 2222 User root
Exemple de fichier d'inventaire
mail.example.com [webservers] foo.example.com bar.example.com [dbservers] one.example.com two.example.com three.example.com
Exemple d'utilisation d'ansible avec des machines de test
BASEDIR="${HOME}/ansible" INVENTORY=${BASEDIR}/test LIMIT="" PARALLELISM=5
Pinger les machines :
ansible --inventory=${INVENTORY} --limit=${LIMIT} --forks=${PARALLELISM} --module-name=ping all
Voir si l'option UseDns est bien positionnee :
ansible --inventory=${INVENTORY} --limit=${LIMIT} --forks=${PARALLELISM} --module-name=command --args="bash -c 'cat /etc/ssh/sshd_config | grep -i dns'" all
Choses a voir
Si vous utilisez sudo, configurez le pour ne pas demander de mot de passe, et mettre en plus l'argument “–sudo” dans la ligne de commande ansible. Si besoin de spécifier un autre utilisateur, utiliser “–sudo-user=”.
Si vous ne voulez plus être embêté avec les vérifications de clefs connues des servuer SSH, éditer /etc/ansible/ansible.cfg et modifier : “host_key_checking = False”. Il y a aussi une variable d'environnement prévue : “export ANSIBLE_HOST_KEY_CHECKING=False”
Si vous voulez optimiser le temps de connexion SSH aux serveurs (à faire sur chaque serveur) : sudo echo “UseDNS no” » /etc/ssh/sshd_config && service ssh stop && sleep 3 && service ssh start
Si vous voulez tracer les changements de configuration, utilisez un système de gestion de version sur le répertoire de configuration ansible !
Inventaire
But : définir les hosts, des groupes de machines.
mail.example.com [webservers] foo.example.com bar.example.com [dbservers] one.example.com two.example.com three.example.com [targets] localhost ansible_connection=local other1.example.com ansible_connection=ssh ansible_ssh_user=mpdehaan other2.example.com ansible_connection=ssh ansible_ssh_user=mdehaan Groupe de Groupe : [southeast:children] atlanta raleigh Avec des patterns : [webservers] www[01:50].example.com Avec des variables de groupes : [southeast:vars] some_server=foo.southeast.example.com halon_system_timeout=30 self_destruct_countdown=60 escape_pods=2 Avec des variables par host : [atlanta] host1 http_port=80 maxRequestsPerChild=808 host2 http_port=303 maxRequestsPerChild=909
Les variables dans l'inventaire
On peut les définir par hote, par groupe ([<nom_du_groupe>:vars]) ou par groupe de groupes (mais attention, cette derniere methode ne fonctionne pas avec ansible, seulement avec ansible-playbook).
Exemple :
[atlanta] host1 http_port=80 maxRequestsPerChild=808
Certaines variables configurent le comportement d'interraction avec les hotes (on pourrait se passer de la configuration ssh_config) :
ansible_ssh_host ansible_ssh_port ansible_ssh_user ansible_connection ansible_ssh_private_key_file ansible_shell_type ansible_python_interpreter ansible\_\*\_interpreter
Mais ! La bonne pratique veut que l'on sépare les variables “métier” du fichier d'inventaire. Hiérarchie conseillée :
${BASEDIR}/group_vars/groupe_1 ${BASEDIR}/group_vars/groupe_2 ${BASEDIR}/host_vars/host_a
RQ : ce sont alors des fichiers YAML
Example :
ntp_server: acme.example.org database_server: storage.example.org
Inventaire automatique
RQ : on peut faire de l'inventaire automatique / dynamique, avec cobbler ou AWS EC2 par exemple, en passant par un script d'inventaire :
file:///usr/share/doc/ansible-doc/html/intro_dynamic_inventory.html
RQ : si l'option –inventory est utilisée avec un répertoire, toutes les sources présentes sont prises en compte (même les scripts .py).
Scripts d'inventaire automatique
BSD Jails Digital Ocean Google Compute Engine Linode OpenShift OpenStack Nova Red Hat's SpaceWalk Vagrant (not to be confused with the provisioner in vagrant, which is preferred) Zabbix
Patterns : choix des hostes à traiter
- tout : 'all' ou '*'
- host :
one.example.com
one.example.com:two.example.com 192.168.1.50 - hosts : 192.168.1.* *.example.com *.com - nom d'un groupe : - opérations booléennes avec les groupes : groupe1:groupe2 : OR (union) groupe1:!groupe2 : AND NOT (différence) groupa1:&groupe2 : ET (intersection) on peut combiner : webservers:dbservers:&staging:!phoenix (précédence de gauche à droite) - mix : one*.com:dbservers ~(web|db).*\.example\.com - limit flag : ansible-playbook site.yml --limit datacenter2
had-hoc commands
Utilisé pour les petites tâches qui n'ont pas besoin d'être réutiliser.
Ex :
rebooter les machines : ansible atlanta -a “/sbin/reboot” -f 10
copier un fichier : ansible webservers -m file -a “dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan”
Polling :
lancer en arrière plan :
ansible all -B 3600 -a "/usr/bin/long_running_operation --do-stuff" (arrière plan) ansible all -m async_status -a "jid=123456789" (status manuel) ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff" (polling)
Fichier de configuration Ansible
Ordre de prise en compte des paramètres :
- ANSIBLE_CONFIG (an environment variable) - voir : constants.py
- ansible.cfg (in the current directory)
- .ansible.cfg (in the home directory)
- /etc/ansible/ansible.cfg
Chaine de caractères de tag de config par ansible : ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}
Erreur si variable non renseignée : #error_on_undefined_vars=False ⇒ attention, par défaut à False (erreur silencieuse)
Logging : log_path = /var/log/ansible.log =⇒ par defaut, pas de logging !
Timeout SSH : par défaut à 10, peut être diminué sauf systèmes fortement chargés
Pipelining : a voir pour améliorer les perfs, par défaut à false, mais requiert des modification sur la configuration de sudo si il est utilisé (require tty)
Exemple de fichier personnalisé (~/.ansible.cfg)
[defaults] log_path = ~/ansible/log/ansible.log timeout = 1 [ssh_connection] pipelining = True [accelerate] accelerate_port = 5099 accelerate_timeout = 30 accelerate_connect_timeout = 1.0 accelerate_multi_key = yes
Windows support
Via powershell en natif. Requiert pywinrm sur la machine de contrôle :
pip install http://github.com/diyan/pywinrm/archive/master.zip#egg=pywinrm
Gestion de paquets via chocolatey : https://chocolatey.org
Il y a de smodules spécifiques windows (ex : ACLs).
Il faut toujours une machine de contrôle linux (et cygwin n'est pas supporté).
Playbooks
Langage de configuration, déploiement et orchestration.
Playbooks : on utilise des 'Modules' dans des 'Tasks' assemblées dans des 'Plays' contenus dans des 'Playbooks'.
Syntaxe = YAML
RQ : Ansible utilise des caractères spéciaux en interne. Si ces caractères sont utilisés pour la syntaxe YAML, il faut les escaper dans le playbook : [] {} : > | /. En fait Ansible a sa surcouche à YAML.
- Un playbook est composé de plusieurs 'plays'.
- Un 'play' fait concorder des groupes de machines avec des rôles bien définis, représentés par des 'tasks'. Basiquement, ce sont des appels à des modules Ansible.
Exemple :
- hosts: webservers
vars:
http_port: 80 max_clients: 200 remote_user: root tasks: - name: ensure apache is at the latest version yum: pkg=httpd state=latest - name: write the apache config file template: src=/srv/httpd.j2 dest=/etc/httpd.conf notify: - restart apache - name: ensure apache is running service: name=httpd state=started handlers: - name: restart apache service: name=httpd state=restarted
- Les 'tasks' sont exécutées dans l'ordre. Si une tâche échoue sur une hote, ce dernier est retiré de la liste d'exécution du playbook.
- Une 'task' execute un module avec des arguments spécifiques.
- Les modules sont idempotents. Cela signifie que l'on peut les lancer plusieurs fois sans effet de bord. Ils amènent les systèmes vers un état prédéfini.
- Le champ 'name' est purement pour la sortie humaine.
- La ligne d'action (par défaut : module) peut être divisée en plusieurs lignes
Système évènementiel : notify/handler, example :
ATTENTION BUG https://github.com/ansible/ansible/issues/11025
- name: template configuration file
template: src=template.j2 dest=/etc/foo.conf
notify: - restart memcached - restart apache
handlers: - name: restart memcached service: name=memcached state=restarted - name: restart apache service: name=apache state=restarted
- Les handlers sont toujours lancés dans l'ordre de rédaction
- Les handlers sont gérés dans : ‘pre_tasks’, ‘roles’, ‘tasks’, and ‘post_tasks’
Pour exécuter (flusher) les handlers immédiatement :
tasks: - shell: some tasks go here - meta: flush_handlers - shell: some other tasks
Lancement d'un playbook :
ansible-playbook playbook.yml -f 10
RQ : on peut inverser le sens de connection d'ansible, et lancer ansible-pull dans une crontab et l'asservir à un outil de gestion de version
TIPS :
Lister les hotes cibles :
ansible-playbook playbook.yml --list-hosts
vérifier la syntaxe :
ansible-playbook --syntax-check playbook.yml
- On peut appeler des fichiers de 'tasks' au besoin afin de segmenter les choses. Sinon il y a les 'roles'
- On peut inclure des 'plays' depuis d'autres playbooks.
Playbook inclusion
Abstractions réutilisables, combinant des fichiers d'inclusion.
Le but d'un 'play' dans un 'playbook' est de faire correspondre un groupe de machines à plusieurs 'roles'.
Fichier de 'tasks' à inclure :
# possibly saved as tasks/foo.yml
- name: placeholder foo
command: /bin/foo
- name: placeholder bar
command: /bin/bar
Inclusion d'un fichier de 'tasks' :
tasks:
- include: tasks/foo.yml
On peut donner des paramètres à des inclusions :
tasks: - include: wordpress.yml user=timmy - include: wordpress.yml user=alice - include: wordpress.yml user=bob tasks: - { include: wordpress.yml, user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }
Autre syntaxe pour le passage de variables :
tasks: - include: wordpress.yml vars: remote_user: timmy some_list_variable: - alpha - beta - gamma
On peut aussi le faire avec les handlers :
# this might be in a file like handlers/handlers.yml - name: restart apache service: name=apache state=restarted handlers: - include: handlers/handlers.yml
L'inclusion peut être utilisée avec les playbooks :
- name: this is a play at the top level of a file
hosts: all
remote_user: root tasks: - name: say hi tags: foo shell: echo "hi..." - include: load_balancers.yml - include: webservers.yml - include: dbservers.yml
MAIS :
- on ne peut pas faire de substitution de variable
- on ne peut pas conditionner le chemin d'un fichier inclu
- voir fonction 'when'
Playbooks roles
Les roles sont juste une couche d'automatisation pour les directives d'inclusion.
Roles : outils d'organisation, définition de composants réutilisables et utilisant une structure de fichiers déterminée
Ex de structure :
site.yml webservers.yml fooservers.yml roles/ common/ files/ templates/ tasks/ handlers/ vars/ meta/ webservers/ files/ templates/ tasks/ handlers/ vars/ meta/
Utilisation des 'roles' dans un playbook :
- hosts: webservers
roles:
- common
- webservers
Pour chaque 'role' x :
- If roles/x/tasks/main.yml exists, tasks listed therein will be added to the play
- If roles/x/handlers/main.yml exists, handlers listed therein will be added to the play
- If roles/x/vars/main.yml exists, variables listed therein will be added to the play
- If roles/x/meta/main.yml exists, any role dependencies listed therein will be added to the list of roles (1.3 and later)
- Any copy tasks can reference files in roles/x/files/ without having to path them relatively or absolutely
- Any script tasks can reference scripts in roles/x/files/ without having to path them relatively or absolutely
- Any template tasks can reference files in roles/x/templates/ without having to path them relatively or absolutely
- Any include tasks can reference files in roles/x/tasks/ without having to path them relatively or absolutely
RQ : on peut configurer le chemin des 'roles' ('roles_path').
On peut donner des variables aux roles :
- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir: '/opt/a', port: 5000 }
- { role: foo_app_instance, dir: '/opt/b', port: 5001 }
On peut conditionner les roles :
- hosts: webservers
roles:
- { role: some_role, when: “ansible_os_family == 'RedHat'” }
On peut assigner des 'tags' aux roles :
- hosts: webservers
roles:
- { role: foo, tags: [“bar”, “baz”] }
RQ : les 'roles' sont executes avant les 'tasks' du playbook dans un 'play'. Mais on peut ordonner les choses :
- hosts: webservers
pre_tasks: - shell: echo 'hello'
roles: - { role: some_role }
tasks: - shell: echo 'still busy'
post_tasks: - shell: echo 'goodbye'
RQ : dans ce cas, les 'tags', si utilisés, doivent être présents également dans pre_tasks and post_tasks
Il est possible de définir un jeu de variables par défaut dans : defaults/main.yml
Les dépendances entre 'roles' gèrent automatiquement les séquences. Les dépendances sont rédigées dans le fichier meta/main.yml du répertoire du role. Dans ce fichier, ce sont les roles de dependance amont (antérieure, aka pré-conditions) :
dependencies: - { role: common, some_parameter: 3 } - { role: apache, port: 80 } - { role: postgres, dbname: blarg, other_parameter: 12 } - { role: '/path/to/common/roles/foo', x: 1 }
Les roles de dependance amont ne sont executés qu'une seule fois. On peut changer ce comportement au besoin (par exemple si on se sert d'une variable en condition), avec la directive 'allow_duplicates: yes' en début de meta/main.yml
RQ : on peut inclure des modules développés dans un 'role' afin de le distribuer.
RQ : avec cette méthode on peut patcher des modules existant
roles/ my_custom_modules/ library/ module1 module2
Repository de roles : https://galaxy.ansible.com/
Variables
nommage : lettres (minuscules), chiffres et '_' c'est tout !
Variables dans les inventaires : non recommandé
Variables en ligne dans un playbook :
- hosts: webservers
vars:
http_port: 80
Variables dans les roles ou les fichiers inclus.
Jinja 2
Substitution des variables : nom_de_variable
Jinja2 permet des choses comme les boucles ou les donctions dans les templstes. Mais il n'est pas souhaitable de les utiliser dans les playbooks.
Jinja2 permet de filtrer, reformater et tansformer les expressions (voir : http://jinja.pocoo.org/docs/dev/templates/#builtin-filters).
Exemple pour le débugging :
{{ some_variable | to_nice_json }} {{ some_variable | to_nice_yaml }}
Pour les filtres, Jinja2 utilise le pipe : <nom_de_variable>|<nom_filtre>(“<variable_du_filtre>”)
Exemple :
{{ listx|join(', ') }}
Voir : http://jinja.pocoo.org/docs/dev/templates/#builtin-filters
Affectation par défaut :
{{ some_variable | default(5) }}
Avec Ansible :
var|match : renvoie vrai si la variable correspond à une valeur travail sur les listes : {{ list1 | unique }} {{ list1 | union(list2) }} {{ list1 | intersect(list2) }} {{ list1 | difference(list2) }} {{ list1 | symmetric_difference(list2) }} {{ list | join(" ") }} random : {{ 59 |random}} * * * * root /script/from/cron {{ ['a','b','c']|random }} => 'c' {{ 100 |random(step=10) }} => 70 {{ 100 |random(1, 10) }} => 31 {{ 100 |random(start=1, step=10) }} => 51 basename : {{ path | basename }} dirname : {{ path | dirname }} expansion chemin contenant ~ : {{ path | expanduser }} base64 : {{ encoded | b64decode }} {{ decoded | b64encode }} md5sum : {{ filename | md5 }} casting : some_string_value | bool regexp match : when: url | match("http://example.com/users/.*/resources/.*") regexp search : when: url | search("/users/.*/resources/.*") regexp replace : {{ 'foobar' | regex_replace('^f.*o(.*)$', '\\1') }} casting : <nom_variable>|<type>, ex : ansible_lsb.major_release|int
Les filtres sont améliorés sans cesse avec les versions et on peut en implémenter.
Attention aux caractères spéciaux de YAML : il faut escaper : [] {} : > | /
Facts
Relevé d'informations de l'hote distant = “locally supplied user values”.
On peut les afficher avec : ansible hostname -m setup
On les utilise comme des variables : ansible_hostname
Souvent utilisé dans les pré-conditions ('when')
On peut les utiliser pour créer des groupes dynamiques d'hotes ('group_by').
Si on a pas besoin des facts, on peut les désactiver (ce la accélère l'exécution) :
- hosts: whatever
gather_facts: no
On peut aussi créer des 'facts' dynamiquement, en utilisant le répertoire local des hotes : /etc/ansible/facts.d/preferences.fact
On peut aussi créer des modules spécifiques de 'facts'.
Voir doc pour l'application.
{{ ansible_eth0["ipv4"]["address"] }} {{ ansible_eth0.ipv4.address }}
On peut ajouter un 'fact' dans un 'task' : set_fact
Example setting host facts using key=value pairs
- set_fact: one_fact="something" other_fact="{{ local_var * 2 }}"
Example setting host facts using complex arguments
- set_fact: one_fact: something other_fact: "{{ local_var * 2 }}"
As of 1.8, Ansible will convert boolean strings ('true', 'false', 'yes', 'no') to proper boolean values when using the key=value syntax, however it is still recommended that booleans be set using the complex argument style:
- set_fact:
one_fact: true
other_fact: false
Registered variables
La sortie d'un module peut produire une variable utilisable.
Utiliser “-v” lors du lancement d'un playbook pour voir les valeurs utilisables. Exemple :
- hosts: web_servers
tasks:
- shell: /usr/bin/foo register: foo_result ignore_errors: True - shell: /usr/bin/bar when: foo_result.rc == 5
Magic variables and other hosts variables access
On peut accéder aux variables des autres hosts : hostvars
{{ hostvars['test.example.com']['ansible_distribution'] }}
Pour lister les groupes qui incluent un hote : group_names
{% if 'webserver' in group_names %} # some part of a configuration file that only applies to webservers {% endif %}
Pour lister les hotes et les groupes de l'inventaire : groups
{% for host in groups['app_servers'] %} # something that applies to all app servers. {% endfor %}
{% for host in groups['app_servers'] %} {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }} {% endfor %}
Pour récupérer le FQDN tel que définit dans l'inventaire : inventory_hostname
Pour le hostnmae de l'inventaire : inventory_hostname_short
Pour avoir la liste des hosts concernés par le 'play' courrant : play_hosts
Pour le répertoire et le fichier d'inventaire : inventory_dir / inventory_file
Misc
Fichier de variables :
- hosts: all
remote_user: root
vars: favcolor: blue vars_files: - /vars/external_vars.yml tasks: - name: this is just a placeholder command: /bin/echo foo
Le fichier de variables est un simple dictionnaire YAML :
# in the above example, this would be vars/external_vars.yml somevar: somevalue password: magic
On peut avoir des fichiers de variables par host, par groupe (voir patterns).
Passer des variables via la ligne de commande :
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo" ansible-playbook release.yml --extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}' ansible-playbook release.yml --extra-vars "@some_file.json"
On peut donner des conditions aux inclusions de fichiers de variables :
- hosts: all
remote_user: root
vars_files: - "vars/common.yml" - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
tasks:
- name: make sure apache is running
service: name=apache state=running
Mais il faut facter ou ohai installés avant !
On gère ça normalement avec le 'group_by'.
On peut charger des variables dynamiquement dans une task: include_vars:
# Conditionally decide to load in variables when x is 0, otherwise do not. - include_vars: contingency_plan.yml when: x == 0 # Load a variable file based on the OS type, or a default if not found. - include_vars: "{{ item }}" with_first_found: - "{{ ansible_distribution }}.yml" - "{{ ansible_os_family }}.yml" - "default.yml"
ATTENTION : with_first_found cherche dans le répertoire files !!!
Précédence des variables :
- -e variables always win
- then comes “most everything else”
- then comes variables defined in inventory
- then comes facts discovered about a system
- then “role defaults”, which are the most “defaulty” and lose in priority to everything.
Bien utiliser les fichier de variables :
# file: /etc/ansible/group_vars/all # this is the site wide default ntp_server: default-time.example.com
# file: /etc/ansible/group_vars/boston ntp_server: boston-time.example.com
# file: /etc/ansible/host_vars/xyz.boston.example.com ntp_server: override.example.com
Les groupes enfant supplantent les groupes parents.
Les hosts supplantent toujours leur groupes.
Supplanter les variables d'un role : appel avec parametres :
roles: - { name: apache, http_port: 8080 }
Conditionals
when
Utilisé comme clause dans une 'task'
Utilise les expressions jinja2
tasks: - name: "shutdown Debian flavored systems" command: /sbin/shutdown -t now when: ansible_os_family == "Debian" tasks: - command: /bin/false register: result ignore_errors: True - command: /bin/something when: result|failed - command: /bin/something_else when: result|success - command: /bin/still/something_else when: result|skipped tasks: - shell: echo "This certainly isn't epic!" when: not epic tasks: - command: echo {{ item }} with_items: [ 0, 2, 4, 6, 8, 10 ] when: item > 5
Inclusion conditionnée (roles et taks) :
- include: tasks/sometasks.yml
when: “'reticulating splines' in output”
- hosts: webservers roles: - { role: debian_stock_config, when: ansible_os_family == 'Debian' }
- hosts: all
remote_user: root
vars_files: - "vars/common.yml" - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ] tasks: - name: make sure apache is running service: name={{ apache }} state=running
# for vars/CentOS.yml apache: httpd somethingelse: 42
Templates conditionnnés :
- name: template a file
template: src=item dest=/etc/myapp/foo.conf
with_first_found: - files: - {{ ansible_distribution }}.conf - default.conf paths: - search_location_one/somedir/ - /opt/other_location/somedir/
Exemple contruit avec le multiligne sur register :
- name: registered variable usage as a with_items list
hosts: all
tasks: - name: retrieve the list of home directories command: ls /home register: home_dirs - name: add home dirs to the backup spooler file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link with_items: home_dirs.stdout_lines # same as with_items: home_dirs.stdout.split()
Loops
Simple : with_items - name: add several users user: name={{ item }} state=present groups=wheel with_items: - testuser1 - testuser2
Liste inclue : with_items: somelist
Items type liste :
- name: add several users
user: name=item.name state=present groups=item.groups
with_items: - { name: 'testuser1', groups: 'wheel' } - { name: 'testuser2', groups: 'root' }
Boucle dans une boucle : with_nested
- name: here, 'users' contains the above list of employees
mysql_user: name=item_0 priv=item_1.*:ALL append_privs=yes password=foo
with_nested: - users - [ 'clientdb', 'employeedb', 'providerdb' ]
Parsing de liste depuis un fichier de variables : with_dict
users: alice: name: Alice Appleworth telephone: 123-456-7890 bob: name: Bob Bananarama telephone: 987-654-3210 tasks: - name: Print phone records debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})" with_dict: users
Boucle sur des fichiers : with_fileglob
Tous les fichiers d'un seul répertoire, non récursif, correspondant à un motif.
- hosts: all
tasks:
# first ensure our target directory exists - file: dest=/etc/fooapp state=directory # copy each file over that matches the given pattern - copy: src={{ item }} dest=/etc/fooapp/ owner=root mode=600 with_fileglob: - /playbooks/files/fooapp/*
RQ : si chemin relatif pour with_fileglob, la base est : roles/<rolename>/files
Listes parallèles: with_together
alpha: [ 'a', 'b', 'c', 'd' ] numbers: [ 1, 2, 3, 4 ] tasks: - debug: msg="{{ item.0 }} and {{ item.1 }}" with_together: - alpha - numbers
Boucle sur des sous-elements : with_subelements, lookup
users: - name: alice authorized: - /tmp/alice/onekey.pub - /tmp/alice/twokey.pub - name: bob authorized: - /tmp/bob/id_rsa.pub - user: name={{ item.name }} state=present generate_ssh_key=yes with_items: users - authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'" with_subelements: - users - authorized
Séquence numérique : with_sequence
# create a series of directories with even numbers for some reason - file: dest=/var/stuff/{{ item }} state=directory with_sequence: start=4 end=16 stride=2
On peut formater en plus, faire de l'hexa, …
Choix aléatoire : with_random_choice
- debug: msg=item
with_random_choice:
- “go through the door”
- “drink from the goblet”
- “press the red button”
- “do nothing”
Boucle simple : until (+ retries, delay)
- action: shell /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1 retries: 5 delay: 10
Premier fichier trouvé : with_first_found
- name: some configuration template
template: src=item dest=/etc/file.cfg mode=0444 owner=root group=root
with_first_found: - files: - "{{inventory_hostname}}/etc/file.cfg" paths: - ../../../templates.overwrites - ../../../templates - files: - etc/file.cfg paths: - templates
Boucle sur les résutlats d'un programme sur l'hote : with_lines
- name: Example of looping over a command result
shell: /usr/bin/frobnicate item
with_lines: /usr/bin/frobnications_per_host --param {{ inventory_hostname }}
MAIS on est sensé plutot utiliser les inventaires dynamiques ou register :
- name: Example of looping over a REMOTE command result
shell: /usr/bin/something
register: command_result - name: Do something with each result shell: /usr/bin/something_else --param {{ item }} with_items: command_result.stdout_lines
Boucle avec l'index des entrées d'une liste : with_indexed_items
- name: indexed loop demo
debug: msg=“at array position item.0 there is a value item.1”
with_indexed_items: some_list
Boucle avec des listes mises à plat : with_flattened
# file: roles/foo/vars/main.yml packages_base: - [ 'foo-package', 'bar-package' ] packages_apps: - [ ['one-package', 'two-package' ]] - [ ['red-package'], ['blue-package']] - name: flattened loop demo yum: name={{ item }} state=installed with_flattened: - packages_base - packages_apps
Les boucles et les registres : register
- shell: echo “item”
with_items:
- one
- two
register: echo
C'est très détaillé :
{ "changed": true, "msg": "All items completed", "results": [ { "changed": true, "cmd": "echo \"one\" ", "delta": "0:00:00.003110", "end": "2013-12-19 12:00:05.187153", "invocation": { "module_args": "echo \"one\"", "module_name": "shell" }, "item": "one", "rc": 0, "start": "2013-12-19 12:00:05.184043", "stderr": "", "stdout": "one" }, { "changed": true, "cmd": "echo \"two\" ", "delta": "0:00:00.002920", "end": "2013-12-19 12:00:05.245502", "invocation": { "module_args": "echo \"two\"", "module_name": "shell" }, "item": "two", "rc": 0, "start": "2013-12-19 12:00:05.242582", "stderr": "", "stdout": "two" } ] }
Inspection :
- name: Fail if return code is not 0
fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code" when: item.rc != 0 with_items: echo.results
Fonctions de gestion complexe
Tâches en parallèle
Combien de hosts doivent être gérés en parallèle au maximum : serial
Cela permet de forcer l'exécution d'un 'play' sur n hosts avant d epasser aux prochaines 'plays'
Pourcentage d'échec
Permet d'arrêter une execution en cas de problèmes répétés :
- hosts: webservers
max_fail_percentage: 30
serial: 10
Continue tant qu'il n'y a pas plus de 3 échecs sur 10 machines.
Execution sur un autre host : delegate_to
Particulièrement utile pour la gestion des cibles de load-balancer, la gestion des fenêtres de hors-service (outage) : delegate_to et local_action :
- hosts: webservers
serial: 5
tasks: - name: take out of load balancer pool command: /usr/bin/take_out_of_pool {{ inventory_hostname }} delegate_to: 127.0.0.1 - name: take out of load balancer pool v2 : shorthand local_action: command /usr/bin/take_out_of_pool {{ inventory_hostname }} - name: actual steps would go here yum: name=acme-web-stack state=latest - name: add back to load balancer pool command: /usr/bin/add_back_to_pool {{ inventory_hostname }} delegate_to: 127.0.0.1
Autre exemple : rsync vers une cible :
tasks: - name: recursively copy files from management server to target local_action: command rsync -a /path/to/files {{ inventory_hostname }}:/path/to/target/
Lancer une task une seule fois : run_once
- command: /opt/application/upgrade_db.py
run_once: true
delegate_to: web01.example.org
Ou sur un host particulier :
- command: /opt/application/upgrade_db.py
when: inventory_hostname == webservers[0]
Lancement local :
Variables d'environnement
En déclarer : environment
- hosts: all
remote_user: root
tasks: - apt: name=cobbler state=installed environment: http_proxy: http://proxy.example.com:8080
En variable
- hosts: all
remote_user: root
# here we make a variable named "proxy_env" that is a dictionary vars: proxy_env: http_proxy: http://proxy.example.com:8080 tasks: - apt: name=cobbler state=installed environment: proxy_env
En variable dans un fichier
# file: group_vars/boston ntp_server: ntp.bos.example.com backup: bak.bos.example.com proxy_env: http_proxy: http://proxy.bos.example.com:8080 https_proxy: http://proxy.bos.example.com:8080
Gestion d'erreurs et de changement
Ignorer le code de retour : ignore_errors: yes
- name: this will not be counted as a failure
command: /bin/false
ignore_errors: yes
Récupération particulière de l'état d'une commande : failed_when
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result failed_when: "'FAILED' in command_result.stderr"
Etat de changement particulier : changed_when
tasks: - shell: /usr/bin/billybass --mode="take me to the river" register: bass_result changed_when: "bass_result.rc != 2" # this will never report 'changed' status - shell: wall 'beep' changed_when: False
Lookups
Sert à utiliser des informations externes (filesystem, centres de stockages externes, services, templates).
Sous forme de plugins.
Fait sur la machine locale.
Contenu de fichiers : contents
- hosts: all
vars:
contents: "{{ lookup('file', '/etc/foo.txt') }}" tasks: - debug: msg="the value of foo.txt is {{ contents }}"
Génération de mot de passes et enregistrement dans un fichier : password
- hosts: all
tasks:
# create a mysql user with a random password: - mysql_user: name={{ client }} password="{{ lookup('password', 'credentials/' + client + '/' + tier + '/' + role + '/mysqlpassword length=15') }}" priv={{ client }}_{{ tier }}_{{ role }}.*:ALL # create a mysql user with a random password using only ascii letters: - mysql_user: name={{ client }} password="{{ lookup('password', '/tmp/passwordfile chars=ascii_letters') }}" priv={{ client }}_{{ tier }}_{{ role }}.*:ALL # create a mysql user with a random password using only digits: - mysql_user: name={{ client }} password="{{ lookup('password', '/tmp/passwordfile chars=digits') }}" priv={{ client }}_{{ tier }}_{{ role }}.*:ALL # create a mysql user with a random password using many different char sets: - mysql_user: name={{ client }} password="{{ lookup('password', '/tmp/passwordfile chars=ascii_letters,digits,hexdigits,punctuation') }}" priv={{ client }}_{{ tier }}_{{ role }}.*:ALL
RQ : le module Vault permet d'enregistrer les mots de passes en chiffré
Recupérer des informations des hosts
- hosts: all
tasks:
- debug: msg=“lookup_env_home is an environment variable”
- debug: msg=“item is a line from the result of this command”
with_lines:
- cat /etc/motd
- debug: msg=“lookup_pipe_date is the raw result of running this command”
- debug: msg=“6379_somekey is value in Redis for somekey”
- debug: msg=“lookup_dnstxt_example.com is a DNS TXT record for example.com”
- debug: msg=“lookup_template_._some_template.j2 is a value from evaluation of this template”
Assigner des variables avec des lookup
vars: motd_value: "{{ lookup('file', '/etc/motd') }}" tasks: - debug: msg="motd value is {{ motd_value }}"
Prompt
Récpérer une entrée utilisateur : vars_prompt
- hosts: all
remote_user: root
vars: from: "camelot" vars_prompt: name: "what is your name?" quest: "what is your quest?" favcolor: "what is your favorite color?"
Avec valeur par défaut : vars_prompt, name:, prompt:, default:
vars_prompt: - name: "release_version" prompt: "Product release version" default: "1.0"
En cachant le texte saisi : private:
vars_prompt: - name: "some_password" prompt: "Enter password" private: yes
Avec hachage (requiert Passlib)
vars_prompt: - name: "my_password2" prompt: "Enter password2" private: yes encrypt: "md5_crypt" confirm: yes salt_size: 7
Tags
Permet de lancer une partie seulement d'un playbook.
Tags est supporté dans les plays, les tasks, les roles et les includes : tags:
tasks: - yum: name={{ item }} state=installed with_items: - httpd - memcached tags: - packages - template: src=templates/src.j2 dest=/etc/foo.conf tags: - configuration roles: - { role: webserver, port: 5000, tags: [ 'web', 'foo' ] }
- include: foo.yml tags=web,foo
(l'include ajoute le tag sur toutes les tashs inclues)
ansible-playbook example.yml --tags "configuration,packages" ansible-playbook example.yml --skip-tags "notification"
Vault
ansible-vault create foo.yml => demande un mot de passe ansible-vault edit foo.yml ansible-vault rekey foo.yml bar.yml baz.yml ansible-vault encrypt foo.yml bar.yml baz.yml ansible-vault decrypt foo.yml bar.yml baz.yml ansible-playbook site.yml --ask-vault-pass => lance un playbook qui appelle des fichiers chiffrés (avec le même mot de passe) ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt ansible-playbook site.yml --vault-password-file ~/.vault_pass.py
Modules
En ligne de commande : -m
ansible webservers -m service -a "name=httpd state=started" ansible webservers -m ping ansible webservers -m command -a "/sbin/reboot -t now"
En playbook :
- name: reboot the servers
action: command /sbin/reboot -t now
- name: reboot the servers
command: /sbin/reboot -t now
Les modules renvoient du JSON.
Ils sont idempotent : ils vont éviter de faire des changements si ce n'est pas nécessaire, on peut les lancer plusieurs fois sans problème.
Documentation en ligne de commande :
ansible-doc <nom_du_module>
Modules par catégorie :
file:///usr/share/doc/ansible-doc/html/modules_by_category.html
Tous les modules :
file:///usr/share/doc/ansible-doc/html/list_of_all_modules.html
Divers
Assertions
- assert: { that: “ansible_os_family != 'RedHat'” }
- assert:
that:
- “'foo' in some_command_result.stdout”
- “number_of_the_counting == 3”
Echec avec message d'erreur personnalisé
# Example playbook using fail and when together - fail: msg="The system may not be provisioned according to the CMDB status." when: cmdb_status != "to-be-staged"
Debug
# Example that prints the loopback address and gateway for each host - debug: msg="System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}" - debug: msg="System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}" when: ansible_default_ipv4.gateway is defined - shell: /usr/bin/uptime register: result - debug: var=result - name: Display all variables/facts known for a host debug: var=hostvars[inventory_hostname]
Mode fireball
Utilise un module qui démarre un démon ZaroMQ temporaire.
Pause dans un playbook : pause
# Pause for 5 minutes to build app cache. - pause: minutes=5 # Pause until you can verify updates to an application were successful. - pause: # A helpful reminder of what to look out for post-update. - pause: prompt="Make sure org.foo.FooOverload exception is not present"
Attendre une condition : wait
# wait 300 seconds for port 8000 to become open on the host, don't start checking for 10 seconds - wait_for: port=8000 delay=10 # wait until the file /tmp/foo is present before continuing - wait_for: path=/tmp/foo # wait until the string "completed" is in the file /tmp/foo before continuing - wait_for: path=/tmp/foo search_regex=completed # wait until the lock file is removed - wait_for: path=/var/lock/file.lock state=absent # wait until the process is finished and pid was destroyed - wait_for: path=/proc/3466/status state=absent # Wait 300 seconds for port 22 to become open and contain "OpenSSH", don't start checking for 10 seconds - local_action: wait_for port=22 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10
Stratégies de test
Ajouter le flag '-check' permet de voir si Ansible a beaucoup de changements à faire (mais ne change rien).
Dans le playbook, ajouter always_run
:
roles: - webserver tasks: - script: verify.sh always_run: True
Modules utiles pour les tests
tasks: - wait_for: host={{ inventory_hostname }} port=22 delegate_to: localhost - action: uri url=http://www.example.com return_content=yes register: webpage - fail: msg='service is not happy' when: "'AWESOME' not in webpage.content" - shell: /usr/bin/some-command --parameter value register: cmd_result - assert: that: - "'not ready' not in cmd_result.stderr" - "'gizmo enabled' in cmd_result.stdout" - stat: path=/path/to/something register: p - assert: that: - p.stat.exists and p.stat.isdir
Exemple de garde-fou : apply_testing_checks
- hosts: webservers
serial: 5
pre_tasks: - name: take out of load balancer pool command: /usr/bin/take_out_of_pool {{ inventory_hostname }} delegate_to: 127.0.0.1 roles: - common - webserver - apply_testing_checks post_tasks: - name: add back to load balancer pool command: /usr/bin/add_back_to_pool {{ inventory_hostname }} delegate_to: 127.0.0.1
Syntaxe YAML
Le fichier commence par “—”
Tous les membres d'une liste commencent par “-” :
# A list of tasty fruits - Apple - Orange - Strawberry - Mango
Tous les membres d'un dictionnaire (table de hachage) sont représentés par clef: valeur :
# An employee record name: Example Developer job: Developer skill: Elite
On peut aussi représenter les doctionnaires sous une forme abrégée :
# An employee record {name: Example Developer, job: Developer, skill: Elite}
Les booléens sont représentés de manière multiple :
create_key: yes needs_agent: no knows_oop: True likes_emacs: TRUE uses_cvs: false
Conseils
Lors des rolling releases, utiliser le paramètre “serial” : permet de conditionner au nombre d'hosts
en état avant de passer au 'play' suivant
Toujours mentionner l'état souhaité pour l'application d'un module : state=present
Grouper l'inventaire par roles.
Pour les différentes version d'OS, utiliser le group-by qui crée des groupes dynamiques :
# talk to all hosts just so we can learn about them - hosts: all tasks: - group_by: key={{ ansible_distribution }} # now just on the CentOS hosts... - hosts: CentOS gather_facts: False tasks: - # tasks that only happen on CentOS go here
Les modules spécifiques à un playbook doivent être mis dans ./library/
Utiliser les lignes vides pour donner de l'air aux documents.
Utiliser # pour commenter
Toujours nommer les 'tasks'
KISS
Utiliser un gestionnaire de versions sur l'arborescence Ansible.
Exemples concrets
Exemple concret : file:/usr/share/doc/ansible-doc/html/guide_rolling_upgrade.html Exemples : https://github.com/ansible/ansible-examples Jinja2 : http://jinja.pocoo.org/docs/dev/templates/ debops : http://debops.org/ playbook super complet : https://gist.github.com/webstandardcss/9d1a293914d972399712 ===== Utilisation ===== ==== lancement ==== ansible-playbook ./install.yml –inventory-file=./test ==== lancement limité à un groupe ou à un host ==== ansible-playbook ./install.yml –inventory-file=./prod –limit=nommachine ==== limite en plus sur un tag ==== ansible-playbook install.yml -t staging -i prod -l ruche.alocean.com ==== facts ==== ansible -i prod -m setup ruche.alocean.com