Capítulo 4. Inventario: Describiendo tus servidores
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Hasta ahora, hemos estado trabajando con un solo servidor (o host, como lo llama Ansible). El inventario más sencillo es una lista de nombres de host separados por comas, que puedes hacer incluso sin servidor:
$ ansible all -i 'localhost,' -a date
En realidad, vas a gestionar varios hosts. El conjunto de hosts que Ansible conoce se denomina inventario. En este capítulo aprenderás a describir un conjunto de hosts como inventario de Ansible, creando un inventario que contenga varias máquinas.
Tu archivo ansible.cfg debe parecerse al Ejemplo 4-1, que habilita explícitamente todos los complementos del inventario.
Ejemplo 4-1. ansible.cfg
[defaults] inventory = inventory [inventory] enable_plugins = host_list, script, auto, yaml, ini, toml
En este capítulo, utilizaremos un directorio llamado inventario para los ejemplos de inventario. El inventario de Ansible es un objeto muy flexible: puede ser un archivo (en varios formatos), un directorio o un ejecutable, y algunos ejecutables se incluyen como plug-ins. Los plug-ins de inventario nos permiten apuntar a fuentes de datos, como tu proveedor de la nube, para compilar el inventario. Un inventario puede almacenarse por separado de tus libros de jugadas. Esto significa que puedes crear un directorio de inventario para utilizarlo con Ansible en la línea de comandos, con hosts que se ejecuten en Vagrant, Amazon EC2, Google Cloud Platform o Microsoft Azure, ¡o donde quieras!
Nota
Serge van Ginderachter es la persona que más sabe de inventario Ansible. Consulta su blog para obtener información detallada.
Inventario/Archivos
La forma predeterminada de describir tus hosts en Ansible es listarlos en archivos de texto, llamados archivos hosts de inventario. La forma más sencilla es simplemente una lista de nombres de host en un archivo llamado hosts, como se muestra en el Ejemplo 4-2.
Ejemplo 4-2. Un fichero de inventario muy sencillo
frankfurt.example.com helsinki.example.com hongkong.example.com johannesburg.example.com london.example.com newyork.example.com seoul.example.com sydney.example.com
Ansible añade automáticamente un host al inventario por defecto: localhost. Entiende que localhost
se refiere a tu máquina local, con la que interactuará directamente en lugar de conectarse por SSH.
Preliminares: Varias máquinas Vagrant
Para hablar de inventario, necesitarás interactuar con varios hosts. Configuremos Vagrant para que aparezcan tres hosts. Los llamaremos, de forma poco imaginativa, vagrant1
, vagrant2
, y vagrant3
.
Antes de crear un nuevo archivo Vagrant para este capítulo, asegúrate en de que destruyes tu(s) máquina(s) virtual(es) existente(s) ejecutando lo siguiente:
$ vagrant destroy --force
Si no incluyes la opción --force
, Vagrant te pedirá que confirmes que quieres destruir cada máquina virtual incluida en el archivo Vagrantfile.
A continuación, crea un nuevo archivo Vagrantfile parecido al del Ejemplo 4-3.
Ejemplo 4-3. Vagrantfile con tres servidores
VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # Use the same key for each machine config.ssh.insert_key = false config.vm.define "vagrant1" do |vagrant1| vagrant1.vm.box = "ubuntu/focal64" vagrant1.vm.network "forwarded_port", guest: 80, host: 8080 vagrant1.vm.network "forwarded_port", guest: 443, host: 8443 end config.vm.define "vagrant2" do |vagrant2| vagrant2.vm.box = "ubuntu/focal64" vagrant2.vm.network "forwarded_port", guest: 80, host: 8081 vagrant2.vm.network "forwarded_port", guest: 443, host: 8444 end config.vm.define "vagrant3" do |vagrant3| vagrant3.vm.box = "centos/stream8" vagrant3.vm.network "forwarded_port", guest: 80, host: 8082 vagrant3.vm.network "forwarded_port", guest: 443, host: 8445 end end
Vagrant, a partir de la versión 1.7, utiliza por defecto en una clave SSH diferente para cada host. El Ejemplo 4-3 contiene la línea para volver al comportamiento anterior de utilizar la misma clave SSH para cada host:
config.ssh.insert_key = false
Utilizar la misma clave en cada host simplifica nuestra configuración de Ansible porque podemos especificar una única clave SSH en la configuración.
Por ahora, supongamos que cada uno de estos servidores puede ser potencialmente un servidor web, así que el Ejemplo 4-3 asigna los puertos 80 y 443 dentro de cada máquina Vagrant a un puerto de la máquina local.
Deberíamos poder hacer aparecer las máquinas virtuales ejecutando lo siguiente:
$ vagrant up
Si todo va bien, el resultado debería ser algo parecido a esto:
Bringing machine 'vagrant1' up with 'virtualbox' provider... Bringing machine 'vagrant2' up with 'virtualbox' provider... Bringing machine 'vagrant3' up with 'virtualbox' provider... ... vagrant1: 80 (guest) => 8080 (host) (adapter 1) vagrant1: 443 (guest) => 8443 (host) (adapter 1) vagrant1: 22 (guest) => 2222 (host) (adapter 1) ==> vagrant1: Running 'pre-boot' VM customizations... ==> vagrant1: Booting VM... ==> vagrant1: Waiting for machine to boot. This may take a few minutes... vagrant1: SSH address: 127.0.0.1:2222 vagrant1: SSH username: vagrant vagrant1: SSH auth method: private key ==> vagrant1: Machine booted and ready! ==> vagrant1: Checking for guest additions in VM... ==> vagrant1: Mounting shared folders... vagrant1: /vagrant => /Users/bas/code/ansible/ansiblebook/ansiblebook/ch03
A continuación, necesitamos saber qué puertos de la máquina local se asignan al puerto SSH (22) dentro de cada máquina virtual. Recuerda que podemos obtener esa información ejecutando lo siguiente:
$ vagrant ssh-config
El resultado debería ser algo parecido a esto:
Host vagrant1 HostName 127.0.0.1 User vagrant Port 2222 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile /Users/lorin/.vagrant.d/insecure_private_key IdentitiesOnly yes LogLevel FATAL Host vagrant2 HostName 127.0.0.1 User vagrant Port 2200 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile /Users/lorin/.vagrant.d/insecure_private_key IdentitiesOnly yes LogLevel FATAL Host vagrant3 HostName 127.0.0.1 User vagrant Port 2201 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile /Users/lorin/.vagrant.d/insecure_private_key IdentitiesOnly yes LogLevel FATAL
Gran parte de la información de ssh-config
es repetitiva y puede reducirse. La información que difiere por host es que vagrant1
utiliza el puerto 2222, vagrant2
utiliza el puerto 2200 y vagrant3
utiliza el puerto 2201.
Ansible utiliza tu cliente SSH local por defecto, lo que significa que entenderá cualquier alias que configures en tu archivo de configuración SSH. Por lo tanto, utilizamos un alias comodín en el archivo ~/.ssh/config:
Host vagrant* Hostname 127.0.0.1 User vagrant UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile ~/.vagrant.d/insecure_private_key IdentitiesOnly yes LogLevel FATAL
Modifica tu archivo inventario/hosts para que tenga este aspecto:
vagrant1 ansible_port=2222 vagrant2 ansible_port=2200 vagrant3 ansible_port=2201
Ahora, asegúrate de que puedes acceder a estas máquinas. Por ejemplo, para obtener información sobre la interfaz de red de vagrant2
, ejecuta lo siguiente:
$ ansible vagrant2 -a "ip addr show dev enp0s3"
El resultado debería ser algo parecido a esto:
vagrant2 | CHANGED | rc=0 >> 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 02:1e:de:45:2c:c8 brd ff:ff:ff:ff:ff:ff inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3 valid_lft 86178sec preferred_lft 86178sec inet6 fe80::1e:deff:fe45:2cc8/64 scope link valid_lft forever preferred_lft forever
Parámetros del Inventario de Comportamiento
Para describir nuestras máquinas Vagrant en el archivo de inventario de Ansible, tuvimos que especificar explícitamente el puerto (2222, 2200 o 2201) al que debía conectarse el cliente SSH de Ansible. Ansible denomina a este tipo de variables parámetros de inventario de comportamiento, y hay varios de ellos que puedes utilizar cuando necesites anular los valores predeterminados de Ansible para un host (consulta la Tabla 4-1).
Nombre | Por defecto | Descripción |
---|---|---|
ansible_host |
Nombre del anfitrión | Nombre de host o dirección IP para SSH |
ansible_port |
22 | Puerto a SSH |
ansible_user |
$USUARIO | Usuario para SSH como |
ansible_password |
(Ninguno) | Contraseña a utilizar para la autenticación SSH |
ansible_connection |
inteligente | Cómo se conectará Ansible al host (consulta la sección siguiente) |
ansible_ssh_private_key_file |
(Ninguno) | Clave privada SSH a utilizar para la autenticación SSH |
ansible_shell_type |
sh | Shell a utilizar para los comandos (consulta la sección siguiente) |
ansible_python_interpreter |
/usr/bin/python | Intérprete de Python en el host (consulta la sección siguiente) |
ansible_*_interpreter |
(Ninguno) | Como ansible_python_interpreter para otras lenguas (consulta la sección siguiente) |
Para algunas de estas opciones, el significado es obvio por el nombre, pero otras requieren más explicaciones:
ansible_connection
-
Ansible admite varios transportes, que son mecanismos que Ansible utiliza para conectarse al host. El transporte por defecto,
smart
, comprobará si el cliente SSH instalado localmente admite una función llamadaControlPersist
. Si el cliente SSH es compatible conControlPersist
, Ansible utilizará el cliente SSH local. Si no, el transporte inteligente volverá a utilizar una biblioteca de cliente SSH basada en Python llamada Paramiko. ansible_shell_type
-
Ansible funciona estableciendo conexiones SSH con máquinas remotas y luego invocando guiones. Por defecto, Ansible asume que el shell remoto es el shell Bourne ubicado en /bin/sh, y generará los parámetros de línea de comandos adecuados que funcionen con él. Crea directorios temporales para almacenar estos scripts.
Ansible también acepta
csh
,fish
, y (en Windows)powershell
como valores válidos para este parámetro. Ansible no funciona con shells restringidos. ansible_python_interpreter
-
Ansible necesita conocer la ubicación del intérprete de Python en la máquina remota. Puede que quieras cambiar esto para elegir una versión que funcione para ti. La forma más sencilla de ejecutar Ansible con Python 3 es instalarlo con pip3 y establecer esto:
ansible_python_interpreter="/usr/bin/env python3"
ansible_*_interpreter
-
Si utilizas un módulo personalizado que no está escrito en Python, puedes utilizar este parámetro para especificar la ubicación del intérprete (como /usr/bin/ruby). Trataremos este tema en el Capítulo 12.
Cambiar los valores por defecto de los parámetros de comportamiento
Puedes anular algunos de los valores por defecto de los parámetros de comportamiento en el archivo de inventario, o puedes anularlos en la sección defaults
del archivo ansible.cfg(Tabla 4-2). Considera dónde cambias estos parámetros. ¿Son los cambios una elección personal, o se aplican a todo tu equipo? ¿Una parte de tu inventario necesita una configuración diferente? Recuerda que puedes configurar las preferencias SSH en el archivo ~/.ssh/config.
Parámetro del inventario de comportamiento | opción ansible.cfg |
---|---|
ansible_port |
remote_port |
ansible_user |
remote_user |
ansible_ssh_private_key_file |
ssh_private_key_file |
ansible_shell_type |
executable (véase el párrafo siguiente) |
La opción de configuración de ansible.cfg executable
no es exactamente igual que el parámetro de inventario de comportamiento ansible_shell_type
. El ejecutable especifica la ruta completa del intérprete de comandos a utilizar en la máquina remota (por ejemplo, /usr/local/bin/fish). Ansible buscará el nombre base de esta ruta (en este caso fish) y lo utilizará como valor por defecto para ansible_shell_type
.
Grupos y Grupos y Grupos
Normalmente queremos realizar acciones de configuración en grupos de hosts, en lugar de en un host individual. Ansible define automáticamente un grupo llamado all
(o *
), que incluye todos los hosts del inventario. Por ejemplo, podemos comprobar si los relojes de las máquinas están aproximadamente sincronizados ejecutando lo siguiente:
$ ansible all -a "date"
o
$ ansible '*' -a "date"
La salida en el sistema de Bas tiene este aspecto:
vagrant2 | CHANGED | rc=0 >> Wed 12 May 2021 01:37:47 PM UTC vagrant1 | CHANGED | rc=0 >> Wed 12 May 2021 01:37:47 PM UTC vagrant3 | CHANGED | rc=0 >> Wed 12 May 2021 01:37:47 PM UTC
Podemos definir nuestros propios grupos en el archivo hosts de inventario. Ansible utiliza el formato de archivo .ini para los archivos hosts de inventario; agrupa los valores de configuración en secciones.
He aquí cómo especificar que nuestros hosts vagrant estén en un grupo llamado vagrant
, junto con los otros hosts de ejemplo mencionados al principio del capítulo:
frankfurt.example.com helsinki.example.com hongkong.example.com johannesburg.example.com london.example.com newyork.example.com seoul.example.com sydney.example.com [vagrant] vagrant1 ansible_port=2222 vagrant2 ansible_port=2200 vagrant3 ansible_port=2201
Podríamos listar alternativamente los hosts Vagrant al principio y luego también en grupo, así:
frankfurt.example.com helsinki.example.com hongkong.example.com johannesburg.example.com london.example.com newyork.example.com seoul.example.com sydney.example.com vagrant1 ansible_port=2222 vagrant2 ansible_port=2200 vagrant3 ansible_port=2201 [vagrant] vagrant1 vagrant2 vagrant3
Puedes utilizar los grupos de la forma que te convenga: pueden solaparse o estar anidados, como quieras. El orden no importa, salvo por la legibilidad humana.
Ejemplo: Implementación de una aplicación Django
Imagina que eres responsable de la implementación de una aplicación web basada en Django que procesa trabajos de larga duración. La aplicación necesita soportar los siguientes servicios:
-
La propia aplicación web Django, ejecutada por un servidor HTTP Gunicorn
-
Un servidor web NGINX, que se situará delante de Gunicorn y servirá activos estáticos
-
Una cola de tareas Celery que ejecutará trabajos de larga duración en nombre de la aplicación web
-
Una cola de mensajes RabbitMQ que sirve de backend para Celery
-
Una base de datos Postgres que sirve como almacén persistente
En capítulos posteriores, trabajaremos con un ejemplo detallado de implementación de este tipo de aplicación basada en Django, aunque nuestro ejemplo no utilizará Celery ni RabbitMQ. Por ahora, necesitamos desplegar esta aplicación en tres entornos diferentes: producción (el real), staging (para pruebas en hosts a los que nuestro equipo tiene acceso compartido) y Vagrant (para pruebas locales).
Cuando realizamos la implementación en producción, queremos que todo el sistema responda con rapidez y fiabilidad, por lo que hacemos lo siguiente:
-
Ejecuta la aplicación web en varios hosts para mejorar el rendimiento y coloca un equilibrador de carga delante de ellos
-
Ejecuta servidores de cola de tareas en varios hosts para mejorar el rendimiento
-
Pon Gunicorn, Celery, RabbitMQ y Postgres en servidores separados
-
Utiliza dos hosts Postgres: uno primario y otro réplica
Suponiendo que tengamos un equilibrador de carga, tres servidores web, tres colas de tareas, un servidor RabbitMQ y dos servidores de bases de datos, son 10 hosts con los que tenemos que lidiar(Figura 4-1).
Para nuestro entorno de ensayo, queremos utilizar menos hosts que en producción para ahorrar costes, ya que va a ver mucha menos actividad que en producción. Digamos que decidimos utilizar sólo dos hosts para la puesta en escena; pondremos el servidor web y la cola de tareas en un host de puesta en escena, y RabbitMQ y Postgres en el otro.
Para nuestro entorno local Vagrant, decidimos utilizar tres servidores: uno para la aplicación web, otro para una cola de tareas y otro que contendrá RabbitMQ y Postgres.
El Ejemplo 4-4 muestra un archivo de inventario de muestra que agrupa los servidores por entorno (producción, staging, Vagrant) y por función (servidor web, cola de tareas, etc.).
Ejemplo 4-4. Archivo de inventario para la implementación de una aplicación Django
[production] frankfurt.example.com helsinki.example.com hongkong.example.com johannesburg.example.com london.example.com newyork.example.com seoul.example.com sydney.example.com tokyo.example.com toronto.example.com [staging] amsterdam.example.com chicago.example.com [lb] helsinki.example.com [web] amsterdam.example.com seoul.example.com sydney.example.com toronto.example.com vagrant1 [task] amsterdam.example.com hongkong.example.com johannesburg.example.com newyork.example.com vagrant2 [rabbitmq] chicago.example.com tokyo.example.com vagrant3 [db] chicago.example.com frankfurt.example.com london.example.com vagrant3
Podríamos haber enumerado primero todos los servidores en la parte superior del archivo de inventario, sin especificar un grupo, pero no es necesario, y eso habría alargado aún más este archivo.
Ten en cuenta que sólo tenemos que especificar una vez los parámetros del inventario de comportamiento para las instancias Vagrant.
Alias y puertos
Hemos descrito así nuestros hosts Vagrant:
[vagrant] vagrant1 ansible_port=2222 vagrant2 ansible_port=2200 vagrant3 ansible_port=2201
Los nombres vagrant1
, vagrant2
y vagrant3
son alias. No son los nombres de host reales, sólo nombres útiles para referirse a estos hosts. Ansible resuelve los nombres de host utilizando el inventario, tu archivo de configuración SSH, /etc/hosts y DNS. Esta flexibilidad es útil en el desarrollo, pero puede ser causa de confusión.
Ansible también admite el uso de la sintaxis <hostname>:<port>
al especificar hosts , por lo que podríamos sustituir la línea que contiene vagrant1
por 127.0.0.1:2222
(Ejemplo 4-5).
Ejemplo 4-5. Esto no funciona
[vagrant] 127.0.0.1:2222 127.0.0.1:2200 127.0.0.1:2201
Sin embargo, en realidad no podemos ejecutar lo que ves en el Ejemplo 4-5. La razón es que el inventario de Ansible sólo puede asociar un único host a 127.0.0.1, por lo que el grupo Vagrant sólo contendría un host en lugar de tres.
Grupos de Grupos
Ansible también te permite definir grupos que estén formados por otros grupos. Por ejemplo, como tanto los servidores web como los servidores de cola de tareas necesitarán Django y sus dependencias, podría ser útil definir un grupo django
que contenga ambos. Añadirías esto al archivo de inventario:
[django:children] web task
Ten en cuenta que la sintaxis cambia cuando especificas un grupo de grupos, en lugar de un grupo de hosts. Esto es para que Ansible sepa que debe interpretar web
y task
como grupos y no como hosts.
Anfitriones numerados (mascotas frente a ganado)
El archivo de inventario que viste en el Ejemplo 4-4 parece complejo. Describe 15 hosts, lo que no parece un gran número en este mundo nublado y escalable. Sin embargo, tratar con 15 hosts en el archivo de inventario puede ser engorroso, porque cada host tiene un nombre de host completamente distinto.
A Bill Baker, de Microsoft, se le ocurrió distinguir entre tratar a los servidores como mascotas y tratarlos como ganado.1 A las mascotas les damos nombres distintivos y las tratamos y cuidamos como individuos; al ganado, sin embargo, nos referimos a él por un número de identificación y lo tratamos como ganado.
El enfoque "ganado" de los servidores es mucho más escalable, y Ansible lo soporta bien al admitir patrones numéricos. Por ejemplo, si tus 20 servidores se llaman web1.ejemplo.com, web2.ejemplo.com, etc., puedes especificarlos así en el archivo de inventario:
[web] web[1:20].example.com
Si prefieres que tenga un cero a la izquierda (como web01.ejemplo.com), especifícalo en el rango, así:
[web] web[01:20].example.com
Ansible también admite el uso de caracteres alfabéticos para especificar rangos. Si quieres utilizar la convención web-a.ejemplo.com, web-b.ejemplo.com, etc., para tus 20 servidores, puedes hacerlo en:
[web] web-[a:t].example.com
Hosts y Variables de Grupo: Dentro del Inventario
Recuerda cómo podemos especificar parámetros de comportamiento 1 inventario para hosts Vagrant:
vagrant1 ansible_host=127.0.0.1 ansible_port=2222 vagrant2 ansible_host=127.0.0.1 ansible_port=2200 vagrant3 ansible_host=127.0.0.1 ansible_port=2201
Esos parámetros son variables que tienen un significado especial para Ansible. También podemos definir nombres de variables arbitrarios y valores asociados en los hosts. Por ejemplo, podríamos definir una variable llamada color
y establecerle un valor para cada servidor:
amsterdam.example.com color=red seoul.example.com color=green sydney.example.com color=blue toronto.example.com color=purple
Entonces podríamos utilizar esta variable en un libro de jugadas, como cualquier otra variable. Personalmente, sus autores no suelen asociar variables a hosts concretos. En cambio, solemos asociar variables a grupos.
Volviendo a nuestro ejemplo de Django, la aplicación web y el servicio de cola de tareas necesitan comunicarse con RabbitMQ y Postgres. Supondremos que el acceso a la base de datos Postgres está asegurado tanto en la capa de red (de modo que sólo la aplicación web y la cola de tareas puedan acceder a la base de datos) como mediante nombre de usuario y contraseña. RabbitMQ sólo está asegurado por la capa de red.
Para configurarlo todo, puedes
-
Configura los servidores web con el nombre de host, el puerto, el nombre de usuario y la contraseña del servidor Postgres principal, y el nombre de la base de datos.
-
Configura las colas de tareas con el nombre de host, el puerto, el nombre de usuario y la contraseña del servidor Postgres primario, y el nombre de la base de datos.
-
Configura los servidores web con el nombre de host y el puerto del servidor RabbitMQ.
-
Configura las colas de tareas con el nombre de host y el puerto del servidor RabbitMQ.
-
Configura el servidor Postgres primario con el nombre de host, el puerto y el nombre de usuario y contraseña del servidor Postgres réplica (sólo producción).
Esta información de configuración varía según el entorno, por lo que tiene sentido definirlas como variables de grupo en los grupos de producción, staging y Vagrant. El Ejemplo 4-6 muestra una forma de hacerlo en el archivo de inventario. (En el Capítulo 8 se analiza una forma mejor de almacenar las contraseñas).
Ejemplo 4-6. Especificar variables de grupo en el inventario
[all:vars] ntp_server=ntp.ubuntu.com [production:vars] db_primary_host=frankfurt.example.com db_primary_port=5432 db_replica_host=london.example.com db_name=widget_production db_user=widgetuser db_password=pFmMxcyD;Fc6)6 rabbitmq_host=johannesburg.example.com rabbitmq_port=5672 [staging:vars] db_primary_host=chicago.example.com db_primary_port=5432 db_name=widget_staging db_user=widgetuser db_password=L@4Ryz8cRUXedj rabbitmq_host=chicago.example.com rabbitmq_port=5672 [vagrant:vars] db_primary_host=vagrant3 db_primary_port=5432 db_name=widget_vagrant db_user=widgetuser db_password=password rabbitmq_host=vagrant3 rabbitmq_port=5672
Observa cómo las variables de grupo están organizadas en secciones denominadas [<group name>:vars]
. Además, hemos aprovechado el grupo all
(que, como recordarás, Ansible crea automáticamente) para especificar variables queno cambian entre hosts.
Variables de host y de grupo: En sus propios archivos
El archivo de inventario es un lugar razonable para poner variables de host y grupo si no tienes demasiados hosts. Pero a medida que tu inventario aumenta, se hace más difícil gestionar las variables de esta forma. Además, aunque las variables de Ansible pueden contener booleanos, cadenas, listas y diccionarios, en un archivo de inventario sólo puedes especificar booleanos y cadenas.
Ansible ofrece un enfoque más escalable para hacer un seguimiento de las variables de host y grupo: puedes crear un archivo de variables distinto para cada host y cada grupo. Ansible espera que estos archivos de variables estén en formato YAML.
Busca archivos de variables de host en un directorio llamado host_vars y archivos de variables de grupo en un directorio llamado group_vars. Ansible espera que estos directorios estén en el directorio que contiene tus libros de jugadas o en el directorio adyacente a tu archivo de inventario. Si tienes ambos directorios, el primero (el directorio de los libros de jugadas) tiene prioridad.
Por ejemplo, si Lorin tiene un directorio que contiene sus playbooks en /home/lorin/playbooks/ con un directorio de inventario y un archivo hosts en /home/lorin/inventory/hosts, debe poner variables para el host amsterdam . example .com en el archivo /home/lorin/inventory/host_vars/amsterdam.example.com y variables para el grupo de producción en el archivo /home/lorin/inventory/group_vars/production (como se muestra en el Ejemplo 4-7).
Ejemplo 4-7. group_vars/production
--- db_primary_host: frankfurt.example.com db_primary_port: 5432 db_replica_host: london.example.com db_name: widget_production db_user: widgetuser db_password: 'pFmMxcyD;Fc6)6' rabbitmq_host: johannesburg.example.com rabbitmq_port: 5672 ...
También podemos utilizar diccionarios YAML para representar estos valores, como se muestra en el Ejemplo 4-8.
Ejemplo 4-8. group_vars/production, con diccionarios
--- db: user: widgetuser password: 'pFmMxcyD;Fc6)6' name: widget_production primary: host: frankfurt.example.com port: 5432 replica: host: london.example.com port: 5432 rabbitmq: host: johannesburg.example.com port: 5672 ...
Si elegimos los diccionarios YAML, accedemos a las variables con notación de puntos, así:
"{{ db.primary.host }}"
También podemos acceder a las variables del diccionario así:
"{{ db['primary']['host'] }}"
Compáralo con la forma en que accederíamos a ellos de otro modo:
"{{ db_primary_host }}"
Si queremos separar aún más las cosas, Ansible nos permite definir group_vars/production como un directorio en lugar de como un archivo. Podemos colocar en él varios archivos YAML que contengan definiciones de variables. Por ejemplo, podríamos poner las variables relacionadas con la base de datos en un archivo y las variables relacionadas con RabbitMQ en otro archivo, como se muestra en los Ejemplos 4-9 y 4-10.
Ejemplo 4-9. group_vars/production/db
--- db: user: widgetuser password: 'pFmMxcyD;Fc6)6' name: widget_production primary: host: frankfurt.example.com port: 5432 replica: host: london.example.com port: 5432 ...
Ejemplo 4-10. group_vars/production/rabbitmq
--- rabbitmq: host: johannesburg.example.com port: 6379 ...
A menudo es mejor empezar de forma sencilla, en lugar de dividir las variables en demasiados archivos. En equipos y proyectos más grandes, el valor de los archivos separados aumenta, ya que muchas personas pueden necesitar tirar de los archivos y trabajar en ellos al mismo tiempo.
Inventario dinámico
Hasta este punto, hemos estado especificando explícitamente todos nuestros hosts en nuestro archivo de inventario de hosts. Sin embargo, es posible que tengas un sistema externo a Ansible que realice un seguimiento de tus hosts. Por ejemplo, si tus hosts se ejecutan en Amazon EC2, entonces EC2 rastrea la información sobre tus hosts por ti. Puedes recuperar esta información a través de la interfaz web de EC2, su API de consulta o herramientas de línea de comandos como awscli
. Otros proveedores de nubes tienen interfaces similares.
Si gestionas tus propios servidores utilizando un sistema de aprovisionamiento automatizado como Cobbler o Ubuntu Metal as a Service (MAAS), entonces tu sistema ya lleva un registro de tus servidores. O tal vez tengas una de esas elegantes bases de datos de gestión de la configuración (CMDB) donde reside toda esta información.
No querrás duplicar manualmente esta información en tu archivo hosts, porque a la larga ese archivo no coincidirá con tu sistema externo, que es la verdadera fuente de información sobre tus hosts. Ansible admite una función llamada inventario dinámico que te permite evitar esta duplicación.
Si el archivo de inventario está marcado como ejecutable, Ansible asumirá que se trata de un script de inventario dinámico y ejecutará el archivo en lugar de leerlo.
Nota
Para marcar un archivo como ejecutable , utiliza el comando chmod +x
. Por ejemplo
$ chmod +x vagrant.py
Plug-ins de inventario
Ansible viene con varios ejecutables que pueden conectarse a varios sistemas en la nube, siempre que instales los requisitos y configures la autenticación. Estos complementos suelen necesitar un archivo de configuración YAML en el directorio de inventario, así como algunas variables de entorno o archivos de autenticación.
Para ver la lista de plug-ins disponibles:
$ ansible-doc -t inventory -l
Para ver documentación y ejemplos específicos de los plug-ins:
$ ansible-doc -t inventory <plugin name>
La Interfaz para un Script de Inventario Dinámico
Un script de inventario dinámico de Ansible debe admitir dos banderas de línea de comandos:
-
--host=<hostname>
para mostrar los detalles del anfitrión -
--list
para listar grupos
También debe devolver la salida en formato JSON con una estructura específica que Ansible pueda interpretar.
Mostrar detalles del anfitrión
Para obtener los detalles del host individual, Ansible llamará a a un script de inventario con el argumento --host=
:
$ ansible-inventory -i inventory/hosts --host=vagrant2
Nota
Ansible incluye un script que funciona como un script de inventario dinámico para el inventario estático proporcionado con el argumento de la línea de comandos -i
: ansible-inventory
.
La salida debe contener cualquier variable específica del anfitrión, incluidos los parámetros de comportamiento, así
{ "ansible_host": "127.0.0.1", "ansible_port": 2200, "ansible_ssh_private_key_file": "~/.vagrant.d/insecure_private_key", "ansible_user": "vagrant" }
La salida es un único objeto JSON; los nombres son los nombres de las variables y los valores son los valores de las variables.
Listado de grupos
Los scripts dinámicos de inventario deben ser capaces de listar todos los grupos y detalles sobre los hosts individuales. En el repositorio de GitHub que acompaña a este libro, hay un script de inventario para los hosts de Vagrant llamado vagrant.py. Ansible lo llamará así para obtener una lista de todos los grupos:
$ ./vagrant.py --list
De la forma más sencilla, el resultado podría ser el siguiente:
{"vagrant": ["vagrant1", "vagrant2", "vagrant3"]}
Esta salida es un único objeto JSON; los nombres son nombres de grupos de Ansible, y los valores son matrices de nombres de host.
Como optimización de, el comando --list
puede contener los valores de las variables de host para todos los hosts, lo que ahorra a Ansible la molestia de hacer una invocación separada a --host
para recuperar las variables de los hosts individuales.
Para aprovechar esta optimización, el comando --list
debe devolver una clave llamada _meta
que contenga las variables de cada host, de esta forma:
"_meta": { "hostvars": { "vagrant1": { "ansible_user": "vagrant", "ansible_host": "127.0.0.1", "ansible_ssh_private_key_file": "~/.vagrant.d/insecure_private_key", "ansible_port": "2222" }, "vagrant2": { "ansible_user": "vagrant", "ansible_host": "127.0.0.1", "ansible_ssh_private_key_file": "~/.vagrant.d/insecure_private_key", "ansible_port": "2200" }, "vagrant3": { "ansible_user": "vagrant", "ansible_host": "127.0.0.1", "ansible_ssh_private_key_file": "~/.vagrant.d/insecure_private_key", "ansible_port": "2201" } }
Escribir un guión de inventario dinámico
Una de las características útiles de Vagrant es que puedes ver qué máquinas se están ejecutando actualmente utilizando el comando vagrant status
. Suponiendo que tengamos un archivo Vagrant con el aspecto del Ejemplo 4-3, si ejecutamos vagrant status
, la salida sería como la del Ejemplo 4-11.
Ejemplo 4-11. Salida de vagrant status
$ vagrant status Current machine states: vagrant1 running (virtualbox) vagrant2 running (virtualbox) vagrant3 running (virtualbox) This environment represents multiple VMs. The VMs are all listed above with their current state. For more information about a specific VM, run 'vagrant status NAME'.
Como Vagrant ya lleva la cuenta de las máquinas por nosotros, no hace falta que las enumeremos en un archivo de inventario de Ansible. En su lugar, podemos escribir un script de inventario dinámico que consulte a Vagrant qué máquinas se están ejecutando. Una vez que hayamos configurado un script de inventario dinámico para Vagrant, aunque modifiquemos nuestro archivo Vagrant para ejecutar diferentes números de máquinas Vagrant, no necesitaremos editar un archivo de inventario Ansible.
Vamos a trabajar con un ejemplo de creación de un script de inventario dinámico que recupere los detalles sobre los hosts desde Vagrant. Nuestro script de inventario dinámico necesitará invocar el comando vagrant status
. La salida que se muestra en el Ejemplo 4-11 está diseñada para que la lean los humanos. Podemos obtener una lista de hosts en ejecución en un formato que sea más fácil de parsear para los ordenadores con la bandera --machine-readable
, de esta forma:
$ vagrant status --machine-readable
El resultado es el siguiente:
1620831617,vagrant1,metadata,provider,virtualbox 1620831617,vagrant2,metadata,provider,virtualbox 1620831618,vagrant3,metadata,provider,virtualbox 1620831619,vagrant1,provider-name,virtualbox 1620831619,vagrant1,state,running 1620831619,vagrant1,state-human-short,running 1620831619,vagrant1,state-human-long,The VM is running. To stop this VM%!(VAGRANT_COMMA) you can run `vagrant halt` to\nshut it down forcefully%!(VAGRANT_COMMA) or you can run `vagrant suspend` to simply\nsuspend the virtual machine. In either case%!(VAGRANT_COMMA) to restart it again%!(VAGRANT_COMMA)\nsimply run `vagrant up`. 1620831619,vagrant2,provider-name,virtualbox 1620831619,vagrant2,state,running 1620831619,vagrant2,state-human-short,running 1620831619,vagrant2,state-human-long,The VM is running. To stop this VM%!(VAGRANT_COMMA) you can run `vagrant halt` to\nshut it down forcefully%!(VAGRANT_COMMA) or you can run `vagrant suspend` to simply\nsuspend the virtual machine. In either case%!(VAGRANT_COMMA) to restart it again%!(VAGRANT_COMMA)\nsimply run `vagrant up`. 1620831620,vagrant3,provider-name,virtualbox 1620831620,vagrant3,state,running 1620831620,vagrant3,state-human-short,running 1620831620,vagrant3,state-human-long,The VM is running. To stop this VM%!(VAGRANT_COMMA) you can run `vagrant halt` to\nshut it down forcefully%!(VAGRANT_COMMA) or you can run `vagrant suspend` to simply\nsuspend the virtual machine. In either case%!(VAGRANT_COMMA) to restart it again%!(VAGRANT_COMMA)\nsimply run `vagrant up`. 1620831620,,ui,info,Current machine states:\n\nvagrant1 running (virtualbox)\nvagrant2 running (virtualbox)\nvagrant3 running (virtualbox)\n\nThis environment represents multiple VMs. The VMs are all listed\nabove with their current state. For more information about a specific\nVM%!(VAGRANT_COMMA) run `vagrant status NAME`
Para obtener detalles sobre una máquina Vagrant concreta, digamos, vagrant2
, ejecutaríamos lo siguiente:
$ vagrant ssh-config vagrant2
El resultado es el siguiente:
Host vagrant2 HostName 127.0.0.1 User vagrant Port 2200 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile /Users/lorin/.vagrant.d/insecure_private_key IdentitiesOnly yes LogLevel FATAL
Nuestro script de inventario dinámico tendrá que llamar a estos comandos, analizar los resultados y generar el JSON adecuado. Podemos utilizar la biblioteca Paramiko para analizar la salida de vagrant ssh-config
. En primer lugar, instala la biblioteca Paramiko de Python con pip:
$ pip3 install --user paramiko
Aquí tienes una sesión interactiva de Python que muestra cómo utilizar la biblioteca Paramiko para hacer esto:
$ python3 >>> import io >>> import subprocess >>> import paramiko >>> cmd = ["vagrant", "ssh-config", "vagrant2"] >>> ssh_config = subprocess.check_output(cmd).decode("utf-8") >>> config = paramiko.SSHConfig() >>> config.parse(io.StringIO(ssh_config)) >>> host_config = config.lookup("vagrant2") >>> print (host_config) {'hostname': '127.0.0.1', 'user': 'vagrant', 'port': '2200', 'userknownhostsfile': '/dev/null', 'stricthostkeychecking': 'no', 'passwordauthentication': 'no', 'identityfile': ['/Users/bas/.vagrant.d/insecure_private_key'], 'identitiesonly': 'yes', 'loglevel': 'FATAL'}
El Ejemplo 4-12 muestra nuestro script vagrant.py completo.
Ejemplo 4-12. vagrant.py
#!/usr/bin/env python3 """ Vagrant inventory script """ # Adapted from Mark Mandel's implementation # https://github.com/markmandel/vagrant_ansible_example import argparse import io import json import subprocess import sys import paramiko def parse_args(): """command-line options""" parser = argparse.ArgumentParser(description="Vagrant inventory script") group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--list', action='store_true') group.add_argument('--host') return parser.parse_args() def list_running_hosts(): """vagrant.py --list function""" cmd = ["vagrant", "status", "--machine-readable"] status = subprocess.check_output(cmd).rstrip().decode("utf-8") hosts = [] for line in status.splitlines(): (_, host, key, value) = line.split(',')[:4] if key == 'state' and value == 'running': hosts.append(host) return hosts def get_host_details(host): """vagrant.py --host <hostname> function""" cmd = ["vagrant", "ssh-config", host] ssh_config = subprocess.check_output(cmd).decode("utf-8") config = paramiko.SSHConfig() config.parse(io.StringIO(ssh_config)) host_config = config.lookup(host) return {'ansible_host': host_config['hostname'], 'ansible_port': host_config['port'], 'ansible_user': host_config['user'], 'ansible_private_key_file': host_config['identityfile'][0]} def main(): """main""" args = parse_args() if args.list: hosts = list_running_hosts() json.dump({'vagrant': hosts}, sys.stdout) else: details = get_host_details(args.host) json.dump(details, sys.stdout) if __name__ == '__main__': main()
Dividir el inventario en varios archivos
Si quieres tener tanto un archivo de inventario normal como un script de inventario dinámico (o, en realidad, cualquier combinación de archivos de inventario estáticos y dinámicos), sólo tienes que ponerlos todos en el mismo directorio y configurar Ansible para que utilice ese directorio como inventario. Puedes hacerlo mediante el parámetro inventory
en ansible.cfg o utilizando la bandera -i
en la línea de comandos. Ansible procesará todos los archivos y fusionará los resultados en un único inventario.
Esto significa que puedes crear un directorio de inventario para utilizarlo con Ansible en la línea de comandos con hosts que se ejecuten en Vagrant, Amazon EC2, Google Cloud Platform, Microsoft Azure, ¡o donde los necesites!
Por ejemplo, la estructura de directorios de Bas tiene este aspecto:
- inventario/aws_ec2.yml
- inventario/azure_rm.yml
- inventario/grupo_vars/vagrant
- inventario/group_vars/staging
- inventario/grupo_vars/producción
- inventario/anfitriones
- inventario/vagrant.py
Añadir entradas en tiempo de ejecución con add_host y group_by
Ansible te permitirá añadir hosts y grupos al inventario durante la ejecución de un libro de jugadas. Esto es útil cuando se gestionan clusters dinámicos, como Redis Sentinel.
añadir_host
El módulo add_host
añade un host al inventario; esto es útil si utilizas Ansible para aprovisionar nuevas instancias de máquinas virtuales dentro de una nube de infraestructura como servicio.
Invocar el módulo tiene el siguiente aspecto:
- name: Add the host add_host name: hostname groups: web,staging myvar: myval
Especificar la lista de grupos y variables adicionales es opcional.
Aquí tienes el comando add_host
en acción, creando una nueva máquina Vagrant y configurándola:
--- - name: Provision a Vagrant machine hosts: localhost vars: box: centos/stream8 tasks: - name: Create a Vagrantfile command: "vagrant init {{ box }}" args: creates: Vagrantfile - name: Bring up the vagrant machine command: vagrant up args: creates: .vagrant/machines/default/virtualbox/box_meta - name: Add the vagrant machine to the inventory add_host: name: default ansible_host: 127.0.0.1 ansible_port: 2222 ansible_user: vagrant ansible_private_key_file: > .vagrant/machines/default/virtualbox/private_key - name: Do something to the vagrant machine hosts: default tasks: # The list of tasks would go here - name: ping ping: ...
Nota
El módulo add_host
añade el host sólo durante la ejecución del libro de jugadas. No modifica tu archivo de inventario.
Cuando aprovisionamos dentro de nuestros libros de jugadas, nos gusta dividirlo en dos jugadas. La primera se ejecuta en localhost
y aprovisiona los hosts, y la segunda los configura.
Ten en cuenta que utilizamos el argumento creates: Vagrantfile
en esta tarea:
- name: Create a Vagrantfile command: "vagrant init {{ box }}" args: creates: Vagrantfile
Esto indica a Ansible que si el archivo Vagrantfile está presente, no es necesario volver a ejecutar el comando. Asegurarse de que el comando (potencialmente no idempotente) se ejecuta sólo una vez es una forma de conseguir la idempotencia en un libro de jugadas que invoque al módulo command
. Lo mismo se hace con el módulo de comandos vagrant up
.
agrupar_por
El módulo group_by
de Ansible te permite crear nuevos grupos mientras se ejecuta un libro de jugadas. Cualquier grupo que crees se basará en el valor de una variable que se haya establecido en cada host, a la que Ansible se refiere como hecho.(El capítulo 5 trata los hechos con más detalle).
Si la recopilación de datos de Ansible está activada, Ansible asociará un conjunto de variables a un host. Por ejemplo, la variable ansible_machine
será i386
para máquinas x86 de 32 bits y x86_64
para máquinas x86 de 64 bits. Si Ansible interactúa con una mezcla de hosts de este tipo, podemos crear grupos i386
y x86_64
con la tarea.
Si preferimos agrupar nuestros hosts por distribución de Linux (por ejemplo, Ubuntu o CentOS), podemos utilizar el hecho ansible_fact.distribution
:
- name: Create groups based on Linux distribution group_by: key: "{{ ansible_facts.distribution }}"
En el Ejemplo 4-13, utilizamos group_by
para crear grupos separados para nuestros hosts Ubuntu y CentOS, luego utilizamos el módulo apt
para instalar paquetes en Ubuntu y el módulo yum
para instalar paquetes en CentOS.
Ejemplo 4-13. Crear grupos ad hoc según la distribución de Linux
--- - name: Group hosts by distribution hosts: all gather_facts: true tasks: - name: Create groups based on distro group_by: key: "{{ ansible_facts.distribution }}" - name: Do something to Ubuntu hosts hosts: Ubuntu become: true tasks: - name: Install jdk and jre apt: update_cache: true name: - openjdk-11-jdk-headless - openjdk-11-jre-headless - name: Do something else to CentOS hosts hosts: CentOS become: true tasks: - name: Install jdk yum: name: - java-11-openjdk-headless - java-11-openjdk-devel
Conclusión
Esto es todo sobre el inventario de Ansible. Es un objeto muy flexible que ayuda a describir tu infraestructura y la forma en que quieres utilizarla. El inventario puede ser tan sencillo como un archivo de texto o tan complejo como puedas manejar.
En el capítulo siguiente se explica cómo utilizar las variables.
1 Este término ha sido popularizado por Randy Bias, de Cloudscaling.
Get Ansible: Up and Running, 3ª Edición now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.