Ansible es una herramienta de automatización reconocida por ser sencilla y potente a la vez. Según mi experiencia, puedo decir que esto se debe principalmente al lenguaje que utiliza: YAML, y a tener una arquitectura sin agentes o “agentless”.
YAML y los componentes de Ansible
“YAML is a human friendly data serialization standard for all programming languages” (Source: http://yaml.org/)
Esto signifíca que es verdaderamente fácil de entender y comenzar a trabajar con YAML. Por ejemplo:
- hosts: webserver
tasks:
- package: apache
state: latest
Este “playbook” dice que el host webserver que tiene 1 tarea: instalar el paquete más actual de Apache, usando el “module” package.
Bastante sencillo no?
Para revisar que tan potente puede ser Ansible, se puede revisar el índice de Modules: http://docs.ansible.com/ansible/modules_by_category.html
Para lograr reusabilidad: estas tareas pueden ser agrupadas como “roles”, que son una compilación de tareas que buscan cumplir un objetivo común. Por ejemplo: un rol Java para instalar JDK en tu nodo.
Estos son los componentes principales de Ansible: Playbooks, Modules, and Roles.
Arquitectura “Agentless”
Esto significa que no necesitas un “ansible-client” en tus nodos para ejecutar tareas. Tu solo necesitarías un “master” que diga que tareas ejecutar en tus nodos. Esto es muy importante comparado con otras herramientas, donde necesitas un “***-client” para poder interpretar y ejecutar los comandos: https://www.ansible.com/benefits-of-agentless-architecture
Es verdad que no necesitas un cliente con Ansible, pero requires de algunos paquetes. Pero estos paquetes son SSH y otros relacionados a Python, que son bastante comunes: http://docs.ansible.com/ansible/intro_installation.html#managed-node-requirements
Ansible también mantiene un enfoque “push”, donde el “master” envía comandos a los nodos. Esto también diferente a otras herramientas que están basadas en un enfoque “push”, donde los nodos piden los comandos a un “master”. Aunque este enfoque es opcional con Ansible: http://docs.ansible.com/ansible/playbooks_intro.html#ansible-pull
Por último, hay una funcionalidad que quiero mencionar: Tipo de Conexión. Por defecto, Ansible se basa en SSH para ejecutar comandos en los nodos, pero hay casos en los que SSH no es una opción o no se necesita: por ejemplo para ejecutar comandos localmente, o en Windows, o en Docker.
En estos casos, la opción de tipo de conexión permite que tus “playbook” se ejecuten usando WinRM si tu nodo es Windows, o ejecutarlos localmente, o utilizar comandos Docker Exec.
Vamos a ver un poco de código:
He implementedo un role de Ansible para instalar Java hace algún tiempo: https://github.com/jeqo/ansible-role-java
Solo para explicar que hace, vemos el archivo de tareas principal:
---
- debug:
msg: "This Java Provider will be installed: {{ java_provider }}"
- include: install-{{ java_provider }}.yml
- include: set-java-home.yml
Primero muestra un mensaje con el tipo de proveedor: variable “java_provider” y luego definir la variable de entorno: JAVA_HOME.
Este rol también tiene una carpeta de pruebas “tests”, con un “playbook” con algunas pruebas:
- name: test install openjdk jdk 8 on centos 7
hosts: test01
roles:
- role: java
java_provider: openjdk
java_version: 8
java_type: jdk
- name: test install openjdk jre 8 on centos 7
hosts: test02
roles:
- role: java
java_provider: openjdk
java_version: 8
java_type: jre
# more tests...
Y para ejecutar las pruebas utilizo Vagrant y VirtualBox:
Vagrant.configure(2) do |config|
config.vm.provision "ansible" do |ansible|
ansible.playbook = "test.yml"
ansible.galaxy_role_file = "roles.yml"
end
config.vm.define "test01" do |node|
node.vm.box = "jeqo/ansible-centos7"
end
config.vm.define "test02" do |node|
node.vm.box = "jeqo/ansible-centos7"
end
# more test nodes...
end
Ejecutemos la prueba de instalación de OpenJDK 8 en Centos:
vagrant up test01
...
PLAY [test install openjdk jdk 8 on centos 7] **********************************
TASK [setup] *******************************************************************
ok: [test01]
TASK [java : debug] ************************************************************
ok: [test01] => {
"msg": "This Java Provider will be installed: openjdk"
}
TASK [java : include] **********************************************************
included: /home/jeqo/dev/jeqo/ansible-role-java/tests/roles/java/tasks/install-openjdk.yml for test01
TASK [java : set_fact] *********************************************************
skipping: [test01]
TASK [java : set_fact] *********************************************************
ok: [test01]
TASK [java : set_fact] *********************************************************
skipping: [test01]
TASK [java : set_fact] *********************************************************
ok: [test01]
TASK [java : install openjdk (debian)] *****************************************
skipping: [test01]
TASK [java : install openjdk (redhat)] *****************************************
Pero que pasa si quiero ejecutar este rol en Docker? Necesito configurar SSH para hacerlo utilizando el modo de conexión por defecto. Esto es considerado un anti-patrón: https://jpetazzo.github.io/2014/06/23/docker-ssh-considered-evil/
Pero, desde la versión 2.0 de Ansible que tiene el tipo de conexión Docker incluido en la instalación. Así que hice algunas pruebas: https://github.com/jeqo/poc-ansible-docker
En este repositorio tengo un “playbook” que crea el contenedor:
- hosts: 127.0.0.1
connection: local
tasks:
- name: my container
docker:
name: poccontainer
image: centos
command: sleep infinity
state: started
Aquí utilizo el tipo de tipo de conexión local para ejecutar comandos localmente.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a5e49bd032be centos "sleep infinity" About an hour ago Up About an hour poccontainer
Una vez que tenga el contenedor en ejecución, se puede ejecutar el provisionamiento:
- hosts: poccontainer
connection: docker
pre_tasks:
- package: name=sudo
- command: "sed -i -e \"s/Defaults requiretty.*/ #Defaults requiretty/g\" /etc/sudoers"
roles:
- role: java
java_provider: openjdk
java_type: jdk
java_version: 8
Las “pre-tasks” se necesitan para configurar el paquete “sudo” y poder configurar “tty” en el contenedos. Esto se requiere cuando ejecutar un “playbook” con el parámetro: “become”, que ejecuta un comando como “sudo”. Luego se ejecuta el rol como en cualquier nodo:
$ ansible-playbook provisioning.yml -vvvv
Using /home/jeqo/dev/jeqo/poc-ansible-docker/ansible.cfg as config file
Loaded callback default of type stdout, v2.0
2 plays in provisioning.yml
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ESTABLISH DOCKER CONNECTION FOR USER: None
<poccontainer> EXEC ['/usr/bin/docker', 'exec', '-i', u'poccontainer', '/bin/sh', '-c', '/bin/sh -c \'( umask 22 &&
mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1459355431.02-32251179247729 `" &&
echo "` echo $HOME/.ansible/tmp/ansible-tmp-1459355431.02-32251179247729 `" )\'']
<poccontainer> PUT /tmp/tmpNCOaxi TO /root/.ansible/tmp/ansible-tmp-1459355431.02-32251179247729/setup
<poccontainer> EXEC ['/usr/bin/docker', 'exec', '-i', u'poccontainer', '/bin/sh', '-c', u'/bin/sh -c \'LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1459355431.02-32251179247729/setup;
rm -rf "/root/.ansible/tmp/ansible-tmp-1459355431.02-32251179247729/" > /dev/null 2>&1\'']
ok: [poccontainer]
Conclusiones
-
Estos ejemplos muestran cual versátil es Ansible, usando roles y tipos de conexión. Pero hay más plataformas donde Ansible puede ser utilizada, como AWS: https://aws.amazon.com/blogs/apn/getting-started-with-ansible-and-dynamic-amazon-ec2-inventory-management/ y otras plataformas en la nube: http://docs.ansible.com/ansible/list_of_cloud_modules.html
-
Una posible pregunta puede ser: ¿Puede Ansible reemplazar los Dockerfile? Puede ser, depende de ti. Los Dockerfile son bastante sencillos y solo funcionan en Docker. Los Dockerfile también tiene una característica interesante que crea una imagen en cada paso de ejecución, lo que hace que la distribución de imágenes sea más sencilla. Esto falta aún en Ansible, donde los comandos se ejecutan sobre un contenedor en ejecución. En Ansible también está faltando las opciones de “commit” y “push” en Docker, pero esto se puede reemplazar facilmente así:
- hosts: 127.0.0.1
connection: local
tasks:
- name: commit
command: docker commit poccontainer
Sin embargo, Ansible también tiene un módulo para ejecutar Dockerfiles: http://docs.ansible.com/ansible/docker_image_module.html
Espero que esto les ayude a utilizar Ansible y Docker.