Ansible es una herramienta que facilita la automatización de diferentes etapas en la infrastructura de las Tecnologías de Información(TI), por ejemplo en el provisionamiento de servidores, en la administración de configuraciones, en el despliegue de aplicaciones, orquestación de actvidades entre otras cosas.
Ansible fue diseñado para modelar la infrastructura de TI de una manera descriptiva teniendo en consideracion multiples capas. Esto es mucho más común en ambientes empresariales.
Una de las caracteristicas más populares de Ansible es que no necesita un agente, ni tampoco necesita una infrastructura de seguridad adicional para su funcionamiento, aunado a esto utiliza un lenguaje muy sencillo(YAML) para definir las acciones a realizar. La simplicidad es una de las cosas que caracteriza a Ansible.
Ansible es el nombre de un "sistema de comunicación inmediato" ficticio basado en un libro "El Juego de Ender"(Ender's Game) escrito por Orson Scott Card.
Ansible es el hijo de Michael DeHann Creador de otras herramientas conocidas tales como Clobber, Func. Fue creado en 2012 aunque su influencia viene de muchos dias antes influenciado por diferentes situaciones y diferentes proyectos como Puppet y sus limitaciones en ese momento. Hoy en día Ansible es parte de Red Hat, despues de que ésta comprara AnsiblerWorks. Michael ya no forma parte de Ansible.
+----------------+
| +----------------+
+-----------------------------+ | | +----------------+
| | | | | |
| Ansible Server | ssh +-| | +-------------+---+
| | | | | |
+-------+ +--------+ +-------+ +--------------> +--| | Host N |
|| | | | | || +--+ |
|| | | | | || | |
|| Inv | | Play- | | Roles|| +-----------------+
|| | | books | | ||
|| | | | | ||
+-------+ +--------+ +-------|
| +----------+ +-------+
| | Custom | | |
| | Modules | | Vault |
+-+----------+--------+-------+
Ansible se guía por 3 ideas principales:
- La complejidad mata la productividad (Complexity Kills Productivity)
- Optimiza la legibilidad (Optimize For Readability)
- Piensa de manera declarativa (Think Declaratively)
- Nombra cada tarea
- Usa estilo
yaml map
y no llaves - Usar
debug
es aceptable, pero utiliza unverbosity
adecuado - Evita interacción lo más que se pueda
- Utiliza ansible-lint
- Molecule o alguno otro proceso que permita hacer validaciones del código
apt-get update && apt-get install ansible
$(type -P dnf yum | head -1) install ansible
pacman -S ansible
# local
pip install ansible --user
# full
pip install ansible
# latest
pip install git+https://github.com/ansible/ansible.git@devel
https://gist.github.com/tonyskapunk/433762caf6669a2685aa686b487c4ea3
Doc: http://docs.ansible.com/ansible/latest/intro_inventory.html
mail.example.com
10.7.5.3
[webservers]
foo.example.com
bar.example.com
10.8.6.4
[dbservers]
one.example.com:2222
two.example.com
three.example.com
10.9.8.7
[streamingservers]
stream[a:z].example.com
all:
hosts:
mail.example.com
10.7.5.3
children:
webservers:
hosts:
foo.example.com:
bar.example.com:
10.8.6.4:
dbservers:
hosts:
one.example.com: 2222
two.example.com:
three.example.com:
10.9.8.7:
Doc: http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html
Algunas de las infrastructuras soportadas en ansible para producir un inventario dinamico: EC2/Eucalyptus, Rackspace Cloud, OpenStack.
¿Creando tú propio script para el inventario? Doc: http://docs.ansible.com/ansible/latest/dev_guide/developing_inventory.html
cat > inv.sh <<EOF
{
"databases": {
"hosts": ["host1.example.com", "host2.example.com"],
"vars": {
"a": true
}
},
"webservers": ["host2.example.com", "host3.example.com"],
"atlanta": {
"hosts": ["host1.example.com", "host4.example.com", "host5.example.com"],
"vars": {
"b": false
},
"children": ["marietta", "5points"]
},
"marietta": ["host6.example.com"],
"5points": ["host7.example.com"]
}
EOF
chmod u+x inv.sh
Usando ansible
ansible localhost -m ping
ansible localhost -bK --become-method=sudo -m ping
ansible localhost -m setup
ansible localhost -m command -a 'whoami'
ansible localhost -b -m command -a 'whoami'
ansible localhost -bK --become-method=sudo -m command -a 'whoami'
ansible localhost -bKm user -a "name=deleteme uid=1234 state=present"
ansible localhost -bKm user -a "name=deleteme uid=1234 state=absent remove=yes"
ANSIBLE_*
variables de ambiente./ansible.cfg
~/.ansible.cfg
/etc/ansible/ansible.cfg
[defaults]
inventory = ./inventory
remote_user = nonroot
nocows = 1
forks = 5
transport = ssh
host_key_checking = False
#private_key_file = ~/.ssh/id_rsa_example
[privilege_escalation]
become = False
become_method = sudo
[ssh_connection]
control_path = ~/.ssh/mux-%%r@%%h:%%p
#pipelining = True
Tips:
ansible-config dump
ansible-config dump --only-changed
Playbooks son archivos con grupos de tareas definidas, incluye los servidores que se van a utilizar para su ejecución. Mediante los playbooks es como se logra el despliegue y la orquestación de configuraciones en servidores.
Unidad minima de acción en Ansible
- Playbook para crear un usuario en el host
foo.example.com
- Playbook
play_crea_usuario.yml
:--- - name: Crear usuario hosts: foo.example.com tasks: - name: Crea usuario nonroot user: name: nonroot uid: 1234 state: present group: admin ...
- Ejecutando playbook:
# Verificación de sintaxis ansible-playbook play_crea_usuario.yml --syntax-check # Ejecución de playbook ansible-playbook play_crea_usuario.yml
-
Playbook
play_instala_sshkeys.yml
:--- - name: Instalando ssh-keys become: "yes" hosts: all gather_facts: "no" vars: username: nonroot sshkey_path1: "~{{ username }}/.ssh/id_rsa_example.pub" sshkey_path2: "~/ansible/rax/id_rsa_ansible_test.pub" tasks: - name: Instalando ssh-key para {{ username }} authorized_key: user: "{{ username }}" state: present key: "{{ lookup('file', sshkey_path1 ) }}"
-
Ejecutando playbook:
# Verificación de sintaxis ansible-playbook play_instala_sshkeys.yml --syntax-check # Ejecución de playbook ansible-playbook play_instala_sshkeys.yml
En el playbook anterior se puede ver el uso de dos variables, definidas bajo la sección: vars
vars:
username: nonroot
sshkey_path: "~{{ username }}/.ssh/id_rsa_example.pub"
# :)
vars:
nombre_valido: ":)"
nombrevalido0: ":)"
arreglo0:
- "elemento0"
- "elemento1"
- "elemento2"
diccionario1:
llave0: "valor0"
llave1: "valor1"
# :(
vars:
nombre-invalido: ":("
nombre.invalido: ":("
-
Creando un playbook para probar
play_mensajes.yml
:--- - name: Probando variables hosts: localhost become: "no" gather_facts: "yes" vars: mi_mensaje: inicial: "hola" final: "adios" sabor: - "mundo" - "cruel" tasks: - name: Primer mensaje de {{ ansible_user_id }} debug: msg: "{{ mi_mensaje.inicial }} {{ mi_mensaje.sabor[0] }}" - name: Ultimo mensaje en {{ ansible_hostname }} debug: msg: "{{ mi_mensaje.final }} {{ mi_mensaje.sabor[0] }} {{ mi_mensaje.sabor[1] }}" ...
-
Ejecutando playbook:
# Verificación de sintaxis ansible-playbook play_mensaje.yml --syntax-check # Listando las tareas(tasks) ansible-playbook play_mensaje.yml --list-tasks # Ejecución de playbook ansible-playbook play_mensaje.yml
Esta es una manera de definir variables dentro de la ejecución, éstos registros son dinámicos:
- Playbook
play_registro.yml
:--- - name: Registro become: "no" hosts: localhost gather_facts: "yes" tasks: - name: Muestra IP para RH-based command: last register: cmd_last - name: Muestra el último login debug: msg: "{{ cmd_last.stdout_lines[0] }}" ...
-
Playbook
play_cond.yml
:--- - name: Condicionales become: "no" hosts: localhost gather_facts: "yes" tasks: - name: Muestra IP para RH-based debug: msg: "Tu IP por omisión es: {{ ansible_default_ipv4.address }}" when: ansible_os_family|lower in ['redhat'] - name: Muestra la Hora para debian-based debug: msg: "La hora es: {{ ansible_date_time.iso8601 }}" when: ansible_os_family|lower in ['debian'] ...
- Ejecutando playbook:
# Verificación de sintaxis ansible-playbook play_cond.yml --syntax-check # Listando las tareas(tasks) ansible-playbook play_cond.yml --list-tasks # Ejecución de playbook (verbose) ansible-playbook play_cond.yml -v
Doc: http://docs.ansible.com/ansible/latest/playbooks_loops.html
-
Playbook:
play_loop.yml
:--- - name: Bucles hosts: localhost gather_facts: "no" vars: usuarios: - sysad - dba - developer tasks: - name: Lista usarios command: echo {{ item }} loop: "{{ usuarios }}" ...
-
Playbook
play_create_rax.yml
:--- - name: Creando servidores en Rackspace hosts: localhost gather_facts: "no" tasks: - name: Crea 3 servidores local_action: module: rax credentials: ~/ansible/rax/.raxkey name: web%02d.example.com flavor: general1-1 image: Ubuntu 16.04 LTS (Xenial Xerus) (PVHVM) state: present count: 3 count_offset: 0 exact_count: yes group: test wait: yes region: IAD key_name: ansible_test register: rax - name: Dame la info debug: msg: > hostname: {{ item.name }} IP: {{ item.accessIPv4 }} root: {{ item.rax_adminpass }} id: {{ item.id }} with_items: "{{ rax.success }}" when: not rax.failed - name: Define hostnames set_fact: ips: "{{ ips|default([]) + [ item['accessIPv4'] ] }}" with_items: "{{ rax.success }}" when: not rax.failed - name: IPs debug: msg: "{{ ips }}" ...
-
Ejecutando playbook:
# Verificación de sintaxis ansible-playbook play_create_rax.yml --syntax-check # Listando los servidores(hosts) ansible-playbook play_create_rax.yml --list-hosts # Ejecución de playbook ansible-playbook play_create_rax.yml
El uso de plantillas/templates es generalmente usado para crear configuraciones/archivos similares que son re-utilizados cambiando algunas partes de ellos. Ansible hace uso de jinja2 un motor de plantillas para python.
Ejemplo:
-
Template "ansible_cfg.j2
[defaults] stdout_callback={{ stdout_callback|default("default") }}
-
Playbook usando el template:
play_ansible_cfg.yml
--- - name: Create ansible config hosts: localhost become: no vars: stdout_callback: yaml tasks: - name: Create local ansible cfg template: src: templates/ansible_cfg.j2 dest: "{{ lookup('env','HOME') }}/.ansible.cfg" ...
Se usan para tener estructuras de código similares a try/except/else/finally
o simplemente para agrupar tareas. Ejemplo
- Simulando "Try/Catch":
play_try.yml
- name: Optional packages
block:
- name: Install optional packages
package:
name: "{{ list_of_optional_packages }}"
rescue:
- name: Report error to API
uri:
url: "{{ url_of_api }}"
user: "{{ username }}"
password: "{{ password }}"
method: POST
body:
msg: "Optional packages failed"
force_basic_auth: yes
status_code: 201
body_format: json
- Como agrupación de código:
play_block.yml
- name: Check connectivity
block:
- name: Check port 443
wait_for:
host: "{{ target_server }}"
port: 443
timeout: 5
register: target_port_443
ignore_errors: "yes"
- name: Exit when unreachable
fail:
msg: "Unable to connect to {{ target_server }}"
failed_when: target_port_443.failed
when: check_connectivity is defined
become: "no"
Los roles es un conjunto de playbooks con una estructura predefinida
role_name
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Mediante estos Ansible se vuelve una herramienta que puede ser extendida y ajustada a cada necesidad, está es la lista de los diferentes tipos de plugins que hay:
- Action
- Become
- Cache
- Callback
- Cliconf
- Connection
- Filter
- HTTPAPI
- Inventory
- Lookup
- Test
- Vars
Una de sus principales características es que en su mayoría se ejecutan del lado del nodo de control o "control node"
De manera implicita hemos hecho uso de ellos. Cada tarea ejecuta algún modulo, algunos ejemplos:
- ping
- setup
- user
- package
- uri
La lista es extensa, más de 2500 modulos: https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html
Una de sus principales características es que los modulos se ejecutan en el nodo objetivo o "target node".
ansible-lint
revisa el código de los playbooks. Sugiere cambios en base a mejores prácticas
Es parte de ansible pero tiene que instalarse de manera independiente.
pip install --user ansible-lint
Es un una heramienta que permite establecer un conjunto de pasos para llevar a cabo la validación de código de Ansible.
k4ch0 creó una muy buena presentación al respecto.