Fecha: 2016-05-09 Tiempo de lectura: 5 minutos Categoría: Operaciones Tags: ansible / playbook
En un artículo anterior vimos qué era Ansible y como instalarlo, dejando su funcionamiento para el lector; Hay miles de tutoriales por internet, y muchos son mejores de los que pueda poner aquí. Sin embargo, hay algunas ideas que no son fáciles de ver juntas, así que aquí las dejo.
Si solo queremos los playbooks como una manera fácil de aprovisionar una máquina, nos conviene que no tenga un host asignado en el mismo, para poder pasar el objetivo por parámetro. Por ejemplo:
root@ansible:~# cat ping.yml
- hosts: '{{ target }}'
tasks:
- ping:
root@ansible:~#
En este caso hay que pasar un argumento extra como target, y en caso de no ponerlo, el playbook no haría nada. Con este truco, podemos ir variando el objetivo, siempre que esté en el fichero hosts.
root@ansible:~# ansible-playbook ping.yml
PLAY [{{ target }}] ***********************************************************
skipping: no hosts matched
PLAY RECAP ********************************************************************
root@ansible:~# ansible-playbook ping.yml --extra-vars "target=appservers"
PLAY [appservers] *************************************************************
GATHERING FACTS ***************************************************************
ok: [10.0.0.3]
ok: [10.0.0.4]
TASK: [ping ] *****************************************************************
ok: [10.0.0.4]
ok: [10.0.0.3]
PLAY RECAP ********************************************************************
10.0.0.3 : ok=2 changed=0 unreachable=0 failed=0
10.0.0.4 : ok=2 changed=0 unreachable=0 failed=0
root@ansible:~# ansible-playbook ping.yml --extra-vars "target=10.0.0.3"
PLAY [10.0.0.3] ***************************************************************
GATHERING FACTS ***************************************************************
ok: [10.0.0.3]
TASK: [ping ] *****************************************************************
ok: [10.0.0.3]
PLAY RECAP ********************************************************************
10.0.0.3 : ok=2 changed=0 unreachable=0 failed=0
root@ansible:~#
Aunque las máquinas y las redes asignadas a cada entorno sean variables, los grupos y funcionalidades son las mismas. Suponiendo que los playbooks actúen contra los grupos, variando el fichero de hosts podemos conseguir todos los entornos necesarios.
De hecho, podemos tener varios ficheros de hosts y especificarlos por parámetro en el momento de lanzar ansible-playbook. Por ejemplo, para el entorno de preproducción:
root@ansible:~# cat hosts-pre
[loadbalancer]
172.20.0.2
[appservers]
172.20.0.3
172.20.0.4
[dbservers]
172.20.0.5
root@ansible:~# ansible-playbook -i hosts-pre --list-hosts setup.yml
playbook: setup.yml
play #1 (loadbalancer): Setup load balancer... TAGS: []
pattern: [u'loadbalancer']
hosts (1):
172.20.0.2
play #2 (appservers): Setup application servers... TAGS: []
pattern: [u'appservers']
hosts (2):
172.20.0.3
172.20.0.4
play #3 (dbservers): Setup database servers... TAGS: []
pattern: [u'dbservers']
hosts (1):
172.20.0.5
root@ansible:~#
Y casi lo mismo para el entono de producción:
root@ansible:~# cat hosts-pro
[loadbalancer]
10.0.0.2
[appservers]
10.0.0.3
10.0.0.4
10.0.0.5
10.0.0.6
10.0.0.7
[dbservers]
10.0.0.8
10.0.0.9
10.0.0.10
root@ansible:~# ansible-playbook -i hosts-pro --list-hosts setup.yml
playbook: setup.yml
play #1 (loadbalancer): Setup load balancer... TAGS: []
pattern: [u'loadbalancer']
hosts (1):
10.0.0.2
play #2 (appservers): Setup application servers... TAGS: []
pattern: [u'appservers']
hosts (5):
10.0.0.5
10.0.0.4
10.0.0.7
10.0.0.6
10.0.0.3
play #3 (dbservers): Setup database servers... TAGS: []
pattern: [u'dbservers']
hosts (3):
10.0.0.9
10.0.0.8
10.0.0.10
root@ansible:~#
A veces nos conviene sacar la lista de hosts y de grupos de otro lugar, por ejemplo, de una base de datos corporativa. En estos casos, basta con saber que el fichero hosts puede ser ejecutable y se espera que devuelva un diccionario JSON de grupos, cada uno con una lista de los hosts que lo componen. Ansible va a ejecutar el script para sacar esa información.
Vamos a hacer un ejercicio de imaginación: supongamos este script saca los datos de algún sitio (LDAP, BBDD, una API de nuestra CMDB, …), y los saca en formato JSON:
root@ansible:~# cat hosts.py
#!/usr/bin/env python
import json
inventory = {
'loadbalancer': ['10.0.0.2'],
'appservers': ['10.0.0.3', '10.0.0.4'],
'dbservers': ['10.0.0.5'],
}
print json.dumps(inventory)
root@ansible:~# chmod 755 hosts.py
root@ansible:~# ./hosts.py
{"appservers": ["10.0.0.3", "10.0.0.4"], "loadbalancer": ["10.0.0.2"], "dbservers": ["10.0.0.5"]}
root@ansible:~#
Veamos que es capaz de sacar los grupos que le pidamos de forma fácil:
root@ansible:~# ansible -i hosts.py --list-hosts loadbalancer
hosts (1):
10.0.0.2
root@ansible:~# ansible -i hosts.py --list-hosts appservers
hosts (2):
10.0.0.3
10.0.0.4
root@ansible:~# ansible -i hosts.py --list-hosts dbservers
hosts (1):
10.0.0.5
root@ansible:~# ansible -i hosts.py --list-hosts all
hosts (4):
10.0.0.3
10.0.0.4
10.0.0.2
10.0.0.5
root@ansible:~#
En los playbooks podemos usar variables, bien sean de ejecución, o las que indiquemos nosotros. Esto puede jugar a nuestro favor en caso, por ejemplo, de querer desplegar ficheros distintos en cada servidor. Un ejemplo:
Supongamos que tenemos esta estructura de ficheros, con su fichero de hosts y su playbook:
root@ansible:~/multiple_webservers# tree
.
├── hosts
├── playbook.yml
└── webs
├── server1
│ ├── index.html
│ └── sitemap.xml
└── server2
├── adminer.php
└── index.php
3 directories, 6 files
root@ansible:~/multiple_webservers# cat hosts
[webservers]
server1
server2
root@ansible:~/multiple_webservers# cat playbook.yml
- hosts: webservers
gather_facts: false
tasks:
- copy: src=webs/{{ inventory_hostname }}/ dest=/var/www/
root@ansible:~/multiple_webservers#
Lanzamos el playbook para aprovisionar los ficheros web a los servidores:
root@ansible:~/multiple_webservers# ansible-playbook -i hosts playbook.yml
PLAY ***************************************************************************
TASK [copy] ********************************************************************
changed: [server1]
changed: [server2]
PLAY RECAP *********************************************************************
server1 : ok=1 changed=1 unreachable=0 failed=0
server2 : ok=1 changed=1 unreachable=0 failed=0
root@ansible:~/multiple_webservers#
Y fácilmente comprobamos que cada servidor tiene los suyos:
root@ansible:~/multiple_webservers# ssh root@server1 ls /var/www/
index.html
sitemap.xml
root@ansible:~/multiple_webservers# ssh root@server2 ls /var/www/
adminer.php
index.php
root@ansible:~/multiple_webservers#
Hay algunas variables que dependen de la máquina en la que se ejecutan. Aunque es posible definir estructuras condicionales en los playbooks, no escala. Para no ensuciar los playbooks, las podemos definir en el fichero de hosts. Así pues, cada grupo puede tener sus propias variables; pueden ser variables “nuestras” o variables que entienda ansible. Como ejemplo, un inventario con variables de acceso:
root@ansible:~# cat hosts
[slaves]
10.0.0.2
10.0.0.3
[slaves:vars]
ansible_user = ansible
ansible_ssh_pass = s3cr3t
ansible_become = true
ansible_become_method = sudo
ansible_become_user = root
ansible_become_pass = s3cr3t
root@ansible:~#
De esta forma, y de acuerdo con la documentación oficial de ansible, entraríamos con el usuario ansible para hacer seguidamente sudo para actuar con el usuario root.
root@ansible:~# ansible -i hosts -m command -a id slaves
10.0.0.3 | SUCCESS | rc=0 >>
uid=0(root) gid=0(root) grupos=0(root)
10.0.0.2 | SUCCESS | rc=0 >>
uid=0(root) gid=0(root) grupos=0(root)
root@ansible:~#