====== 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 ==== **[[https://github.com/ansible/ansible/issues/11025|ATTENTION BUG]]** # 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 [] 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 ([: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 : {{ }}\\ 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 : |("")\\ 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 : |, 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//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="{{ lookup('redis_kv', 'redis://localhost: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 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