¿Limitar de forma segura los libros de jugadas de Ansible a una sola máquina?

227

Estoy usando Ansible para algunas tareas simples de administración de usuarios con un pequeño grupo de computadoras. Actualmente, tengo mis playbooks configurados hosts: ally mi archivo de hosts es solo un grupo con todas las máquinas enumeradas:

# file: hosts
[office]
imac-1.local
imac-2.local
imac-3.local

Me he encontrado frecuentemente teniendo que apuntar a una sola máquina. El ansible-playbookcomando puede limitar jugadas como esta:

ansible-playbook --limit imac-2.local user.yml

Pero eso parece algo frágil, especialmente para un libro de jugadas potencialmente destructivo. Dejar fuera la limitbandera significa que el libro de jugadas se ejecutará en todas partes. Dado que estas herramientas solo se usan ocasionalmente, parece que vale la pena tomar medidas para una reproducción infalible, por lo que no atacaremos accidentalmente algo dentro de unos meses.

¿Existe alguna práctica recomendada para limitar las ejecuciones del libro de jugadas a una sola máquina? Idealmente, los libros de jugadas deben ser inofensivos si se omiten algunos detalles importantes.

joemaller
fuente

Respuestas:

209

Resulta que es posible ingresar un nombre de host directamente en el libro de jugadas, por lo que ejecutar el libro de jugadas hosts: imac-2.localfuncionará bien. Pero es un poco torpe.

Una mejor solución podría ser definir los hosts del libro de jugadas utilizando una variable y luego pasar una dirección de host específica a través de --extra-vars:

# file: user.yml  (playbook)
---
- hosts: '{{ target }}'
  user: ...

Ejecutando el libro de jugadas:

ansible-playbook user.yml --extra-vars "target=imac-2.local"

Si {{ target }}no está definido, el libro de jugadas no hace nada. Un grupo del archivo hosts también se puede pasar si es necesario. En general, esto parece una forma mucho más segura de construir un libro de jugadas potencialmente destructivo.

Libro de jugadas dirigido a un solo host:

$ ansible-playbook user.yml --extra-vars "target=imac-2.local" --list-hosts

playbook: user.yml

  play #1 (imac-2.local): host count=1
    imac-2.local

Libro de jugadas con un grupo de anfitriones:

$ ansible-playbook user.yml --extra-vars "target=office" --list-hosts

playbook: user.yml

  play #1 (office): host count=3
    imac-1.local
    imac-2.local
    imac-3.local

¡Olvidar definir hosts es seguro!

$ ansible-playbook user.yml --list-hosts

playbook: user.yml

  play #1 ({{target}}): host count=0
joemaller
fuente
52
Esto se puede resolver en 1.5.3 con--limit office[0]
NG.
44
La variable necesita ser citada - es decir: '{{ target }}'- de acuerdo con docs.ansible.com/…
Limbo Peng
99
Esta es una respuesta "a prueba de fallas", a diferencia de otras: si deja de lado algo, no hará nada. Ejecutar en 'solo' un host con Ansible 1.7 run_oncetodavía podría ser destructivo, por lo que no es una buena idea.
RichVel
44
Si desea un comando más corto, -ees el equivalente de--extra-vars
William Turrell
1
Si su configuración ansible requiere que los hosts no puedan estar vacíos o indefinidos, entonces usar una variable combinada con un filtro jinja funciona, como:hosts: "{{ target | default('no_hosts')}}"
Zach Weg
178

También hay un pequeño y lindo truco que te permite especificar un solo host en la línea de comando (o múltiples hosts, supongo), sin un inventario intermedio:

ansible-playbook -i "imac1-local," user.yml

Tenga en cuenta la coma ( , ) al final; esto indica que es una lista, no un archivo.

Ahora, esto no lo protegerá si accidentalmente pasa un archivo de inventario real, por lo que puede no ser una buena solución para este problema específico. ¡Pero es un truco útil para saber!

Tybstar
fuente
2
Eso es increíble. Regularmente uso el indicador -l, que funciona con etc / ansible / hosts (que se completa con la API de descubrimiento EC2), pero a veces realmente solo necesito una sola máquina. ¡Gracias!
Vic
3
¿Debería este truco hacer uso del archivo hosts? Estoy usando anfitriones como un inventario dinámico para nuestro sistema AWS EC2 y se devuelve: skipping: no hosts matched. ¿Quizás este truco ya no funciona desde que --limitfunciona?
hamx0r
1
Este truco no me funcionó. Pero esto funcionó: $ ansible-playbook -kK --limit=myhost1 myplaybook.yml. Ver la respuesta de Marwan.
Donn Lee el
2
Cabe mencionar que para que esto funcione, los anfitriones deben estar configurados allen la (s) obra (s) - esto me llevó un tiempo descubrir ...
Remigius Stalder
83

Este enfoque se cerrará si se proporciona más de un host al verificar la variable play_hosts . El módulo de falla se usa para salir si no se cumple la condición de host único. Los siguientes ejemplos usan un archivo de hosts con dos hosts alice y bob.

user.yml (libro de jugadas)

---
- hosts: all
  tasks:
    - name: Check for single host
      fail: msg="Single host check failed."
      when: "{{ play_hosts|length }} != 1"
    - debug: msg='I got executed!'

Ejecute el libro de jugadas sin filtros de host

$ ansible-playbook user.yml
PLAY [all] ****************************************************************
TASK: [Check for single host] *********************************************
failed: [alice] => {"failed": true}
msg: Single host check failed.
failed: [bob] => {"failed": true}
msg: Single host check failed.
FATAL: all hosts have already failed -- aborting

Ejecute el libro de jugadas en un solo host

$ ansible-playbook user.yml --limit=alice

PLAY [all] ****************************************************************

TASK: [Check for single host] *********************************************
skipping: [alice]

TASK: [debug msg='I got executed!'] ***************************************
ok: [alice] => {
    "msg": "I got executed!"
}
Marwan Alsabbagh
fuente
1
Sin duda la mejor, --limites el camino a seguir
berto
77
play_hostsestá en desuso en Ansible 2.2 y se reemplaza con ansible_play_hosts. Para ejecutar en un host sin necesidad --limit, puede usar when: inventory_hostname == ansible_play_hosts[0].
Trevor Robinson el
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{ play_hosts|length }} == ''en Ansible 2.8.4.
Thomas
32

Hay en mi humilde opinión una forma más conveniente. De hecho, puede solicitar de forma interactiva al usuario las máquinas a las que desea aplicar el libro de jugadas gracias a vars_prompt:

---

- hosts: "{{ setupHosts }}"
  vars_prompt:
    - name: "setupHosts"
      prompt: "Which hosts would you like to setup?"
      private: no
  tasks:
    […]
Buzut
fuente
2
Muy genial. Esto también tiene la ventaja de que el libro de jugadas no es específico del archivo de inventario.
Erfan
2
Gracias por la edición! De hecho, me preguntaba por qué la entrada se trataba por defecto como "estilo de contraseña". Me lo había perdido en los documentos :)
Buzut
¿Se pueden configurar los hosts var desde la línea de comandos para eliminar el aviso con este libro de jugadas?
andig
1
@andig with --extra-varsy una var normal en tu libro de jugadas ...
Buzut
En realidad, no pude hacer que esto funcione, parece que {{ hosts }}se evalúa antes de ingresar el valor, ¿o hay algún truco especial?
Remigius Stalder
18

Para ampliar la respuesta de joemailer, si desea tener la capacidad de coincidencia de patrones para hacer coincidir cualquier subconjunto de máquinas remotas (tal como lo hace el ansiblecomando), pero aún así quiere que sea muy difícil ejecutar accidentalmente el libro de jugadas en todas las máquinas, esto es lo que se me ocurrió:

El mismo libro de jugadas que en la otra respuesta:

# file: user.yml  (playbook)
---
- hosts: '{{ target }}'
  user: ...

Tengamos los siguientes hosts:

imac-10.local
imac-11.local
imac-22.local

Ahora, para ejecutar el comando en todos los dispositivos, debe establecer explícitamente la variable de destino en "todos"

ansible-playbook user.yml --extra-vars "target=all"

Y para limitarlo a un patrón específico, puede establecer target=pattern_here

o, alternativamente, puede salir target=ally agregar el --limitargumento, por ejemplo:

--limit imac-1*

es decir. ansible-playbook user.yml --extra-vars "target=all" --limit imac-1* --list-hosts

lo que resulta en:

playbook: user.yml

  play #1 (office): host count=2
    imac-10.local
    imac-11.local
deadbeef404
fuente
Este es el patrón que he seguido en ansible-django-postgres-nginx
Ajoy
13

Realmente no entiendo cómo todas las respuestas son tan complicadas, la forma de hacerlo es simplemente:

ansible-playbook user.yml -i hosts/hosts --limit imac-2.local --check

El checkmodo le permite ejecutar en modo de ejecución en seco, sin realizar ningún cambio.

knocte
fuente
77
Probablemente porque al preguntarte sobre las respuestas, te perdiste la pregunta, que pedía una forma de evitar que se ejecute cuando los parámetros se omiten por error. Sugirió agregar más parámetros, lo que va en contra del requisito.
techraf
2
ah, claro, pero si la gente me vota, podría ser porque son novatos Ansible (como yo era cuando escribí mi respuesta) que ni siquiera saben sobre la bandera --check, así que supongo que esto todavía es útil en cuanto a documentación, ya que esta pregunta puede ser muy fácil de encontrar
tocado el
6

Los usuarios de AWS que usan la secuencia de comandos de inventario externo EC2 pueden simplemente filtrar por ID de instancia:

ansible-playbook sample-playbook.yml --limit i-c98d5a71 --list-hosts

Esto funciona porque el script de inventario crea grupos predeterminados .

Franco
fuente
44
Opción: el límite no está limitado a EC2 y puede usarse para alojar / agrupar nombres de su inventario. Gracias.
martinezdelariva
5

Tenemos algunos libros de jugadas genéricos que pueden ser utilizados por una gran cantidad de equipos. También tenemos archivos de inventario específicos del entorno, que contienen múltiples declaraciones de grupo.

Para obligar a alguien que llama a un libro de jugadas a especificar un grupo contra el cual correr, colocamos una entrada ficticia en la parte superior del libro de jugadas:

[ansible-dummy-group]
dummy-server

Luego incluimos la siguiente verificación como primer paso en el libro de jugadas compartido:

- hosts: all
  gather_facts: False
  run_once: true
  tasks:
  - fail:
      msg: "Please specify a group to run this playbook against"
    when: '"dummy-server" in ansible_play_batch'

Si el servidor ficticio aparece en la lista de hosts con los que está programado ejecutar este libro de jugadas (ansible_play_batch), la persona que llamó no especificó un grupo y la ejecución del libro de jugadas fallará.

mcdowellstl
fuente
ansible_play_batchenumera solo el lote actual, por lo que cuando se usa el procesamiento por lotes, esto todavía no es seguro. Es mejor usar ansible_play_hostsen su lugar.
Thomas
Aparte de eso, este truco parece ser el más simple y cercano a lo que se le pidió; Lo estoy adoptando!
Thomas
4

Desde la versión 1.7 ansible tiene la opción run_once . La sección también contiene una discusión de varias otras técnicas.

Berend de Boer
fuente
4

Esto muestra cómo ejecutar los libros de jugadas en el servidor de destino.

Esto es un poco más complicado si desea utilizar una conexión local. Pero esto debería estar bien si usa una variable para la configuración de hosts y en el archivo hosts crea una entrada especial para localhost.

En (todos) los libros de jugadas tienen los hosts: línea establecida en:

- hosts: "{{ target | default('no_hosts')}}"

En el archivo de hosts de inventario, agregue una entrada para el host local que establece la conexión como local:

[localhost]
127.0.0.1  ansible_connection=local

Luego, en la línea de comandos, ejecute comandos para establecer explícitamente el objetivo, por ejemplo:

$ ansible-playbook --extra-vars "target=localhost" test.yml

Esto también funcionará cuando use ansible-pull:

$ ansible-pull -U <git-repo-here> -d ~/ansible --extra-vars "target=localhost" test.yml

Si olvida establecer la variable en la línea de comando, el comando generará un error de forma segura (¡siempre que no haya creado un grupo de hosts llamado 'no_hosts'!) Con una advertencia de:

skipping: no hosts matched

Y como se mencionó anteriormente, puede apuntar a una sola máquina (siempre que esté en su archivo de hosts) con:

$ ansible-playbook --extra-vars "target=server.domain" test.yml

o un grupo con algo como:

$ ansible-playbook --extra-vars "target=web-servers" test.yml
bailey86
fuente
0

Tengo un script de envoltura llamado provision que te obliga a elegir el objetivo, por lo que no tengo que manejarlo en otro lugar.

Para aquellos que son curiosos, uso ENV vars para las opciones que usa mi archivo vagrant (agregando el argumento ansible correspondiente para sistemas en la nube) y dejo pasar el resto de los argumentos ansibles. Cuando estoy creando y aprovisionando más de 10 servidores a la vez, incluyo un reintento automático en servidores fallidos (siempre y cuando se esté progresando, descubrí que al crear aproximadamente 100 servidores a la vez, a menudo algunos fallaban la primera vez )

echo 'Usage: [VAR=value] bin/provision [options] dev|all|TARGET|vagrant'
echo '  bootstrap - Bootstrap servers ssh port and initial security provisioning'
echo '  dev - Provision localhost for development and control'
echo '  TARGET - specify specific host or group of hosts'
echo '  all - provision all servers'
echo '  vagrant - Provision local vagrant machine (environment vars only)'
echo
echo 'Environment VARS'
echo '  BOOTSTRAP - use cloud providers default user settings if set'
echo '  TAGS - if TAGS env variable is set, then only tasks with these tags are run'
echo '  SKIP_TAGS - only run plays and tasks whose tags do not match these values'
echo '  START_AT_TASK - start the playbook at the task matching this name'
echo
ansible-playbook --help | sed -e '1d
    s#=/etc/ansible/hosts# set by bin/provision argument#
    /-k/s/$/ (use for fresh systems)/
    /--tags/s/$/ (use TAGS var instead)/
    /--skip-tags/s/$/ (use SKIP_TAGS var instead)/
    /--start-at-task/s/$/ (use START_AT_TASK var instead)/
'
iheggie
fuente
0

Una solución ligeramente diferente es utilizar la variable especial ansible_limitque es el contenido de la --limitopción CLI para la ejecución actual de Ansible.

- hosts: "{{ ansible_limit | default(omit) }}"

No es necesario definir una variable adicional aquí, solo ejecute el libro de jugadas con la --limitbandera.

ansible-playbook --limit imac-2.local user.yml
Manolo
fuente