Bucle ansible a través de un número variable de hostvars

Bucle ansible a través de un número variable de hostvars

Estoy intentando obtener nombres de host y direcciones IP de una lista de hosts ingresada por el usuario y enviar esa información a un servidor central. El principal problema con el que me encuentro es que la cantidad de hosts puede variar considerablemente. Por ejemplo, en la primera ejecución, un usuario puede ingresar 1 nombre de host, en la segunda ejecución ingresar 30 y en la siguiente ingresar 5. Quiero poder usar un único libro de jugadas ya sea que un usuario ingrese 1 o 100 hosts.

Los nombres de host se recopilan a través de un mensaje de "variable adicional" cuando se ejecuta una plantilla de Ansible Tower:

client_hosts: 'host1,host2'

a los que luego se hace referencia en el libro de jugadas:

- name: Gather client information
  hosts: 
  - "{{ client_hosts | default(omit) }}"  
  tasks:    
    - name: Grab client hostname
      shell: cat /etc/hostname
      register: client_hostname

    - name: Grab client IP address
      shell: hostname -i | sed -n '1 p'
      register: client_ip

y más abajo en el libro de jugadas, quiero agregar esas IP + nombres de host a un archivo en un servidor central específico (el nombre de host del servidor no cambia):

- name: Update server
  hosts: central.server  
  tasks:  
    - name: Update client host list
      lineinfile:
        path: /path/to/file
        line: "{{ hostvars['client_hosts']['client_ip'] }} - {{ hostvars['client_hosts']['client_hostname'] }}"

Lo anterior funciona bien para un solo host, pero ¿cómo puedo recorrer el registro de variables cuando se especifica más de un host (por ejemplo, client_hostname[1,2,*]?) y actualizar el servidor con esos valores cuando no sé cómo ¿Cuántos hosts se ingresarán con anticipación?

Respuesta1

Este caso de uso consta de tres partes: 1) Gestionar el inventario, 2) Recopilarnombre_host_clienteyip_clientey 3) Informar al servidor central.

1. Gestionar el inventario

Hay muchas opciones sobre cómo gestionar el inventario y recopilarnombre_host_clienteyip_cliente. Por ejemplo, utiliceAgregar rangos de hostssi planea crear cientos de hosts con nombres simples como host001, hosts002, ..., host999. Por ejemplo, en el inventario siguiente agregueservidor_central, localhost por simplicidad y cien hosts en el grupoprueba

shell> cat inventory/01-hosts 
central_server ansible_host=localhost

[test]
host[001:100]

[test:vars]
ansible_connection=ssh
ansible_user=admin
ansible_become=yes
ansible_become_user=root
ansible_become_method=sudo
ansible_python_interpreter=/usr/local/bin/python3.8

Pruebe brevemente el inventario

- hosts: all
  gather_facts: false
  tasks:
    - debug:
        var: ansible_play_hosts|length
      run_once: true

dar resumido

  ansible_play_hosts|length: '101'

Ejecute el siguiente comando si desea mostrar el inventario completo

shell> ansible-inventory -i inventory --list --yaml

Luego hay muchas opciones sobre cómo seleccionar los hosts. VerPatrones: segmentación por hosts y grupos. Por ejemplo,límiteel inventario a hosts o grupos particulares. Pruébelo con el sencillo manual a continuación

shell> cat pb1.yml
- hosts: all
  gather_facts: false
  tasks:
    - debug:
        var: inventory_hostname

dar

shell> ansible-playbook -i inventory pb1.yml -l host001,host002

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

TASK [debug] *********************************************************************************
ok: [host001] => 
  inventory_hostname: host001
ok: [host002] => 
  inventory_hostname: host002

PLAY RECAP ***********************************************************************************
host001: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host002: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Utilice el complemento de inventarioconstruidosi desea limitar el inventario a un grupo más grande de hosts. Ver

shell> ansible-doc -t inventory constructed

Por ejemplo, la variable adicionalcontar_hostsse utiliza en el siguiente ejemplo para crear el grupomi grupoque comprende hosts limitados por el valor de esta variable

shell> cat inventory/02-constructed.yml
plugin: constructed
strict: true
use_extra_vars: true
compose:
  my_group_count: count_hosts|default(0)
groups:
  my_group: inventory_hostname[-2:]|int < my_group_count|int

Pruébalo

shell> ansible-playbook -i inventory pb.yml -e count_hosts=10 -l my_group

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

TASK [debug] *********************************************************************************
ok: [host001] => 
  ansible_play_hosts|length: '11'

PLAY RECAP ***********************************************************************************
host001: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ejecute el siguiente comando si desea mostrar el inventario completo

shell> ansible-inventory -i inventory -e count_hosts=10 --list --yaml

Con la ayuda delconstruidocomplemento, puede crear condiciones complejas. Por ejemplo, limite los hosts a un intervalo particular y cree el grupomi_grupo2

shell> cat inventory/02-constructed.yml
plugin: constructed
strict: true
use_extra_vars: true
compose:
  my_group_count: count_hosts|default(0)
  my_group_start: start_hosts|default(0)
  my_group_stop: stop_hosts|default(0)
groups:
  my_group1: inventory_hostname[-2:]|int < my_group_count|int
  my_group2: inventory_hostname[-2:]|int >= my_group_start|int and
             inventory_hostname[-2:]|int < my_group_stop|int

Pruébalo

shell> ansible-playbook -i inventory pb1.yml -e start_hosts=10 -e stop_hosts=15 -l my_group2

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

TASK [debug] *********************************************************************************
ok: [host010] => 
  inventory_hostname: host010
ok: [host011] => 
  inventory_hostname: host011
ok: [host012] => 
  inventory_hostname: host012
ok: [host013] => 
  inventory_hostname: host013
ok: [host014] => 
  inventory_hostname: host014

...

2. Recogernombre_host_clienteyip_cliente

Puedes usar el móduloconfiguracióno recopile los hechos por su cuenta. Por la portabilidadconfiguracióndebería ser preferible.

Ver el móduloconfiguraciónsobre cómo recopilar datos sobre hosts remotos. Por ejemplo, el siguiente manual recopila datos sobre elmáquinayredy crea las variablesnombre_host_clienteyip_cliente

shell> cat pb2.yml
- hosts: all
  gather_facts: false
    
  tasks:

    - setup:
        gather_subset:
          - machine
          - network

    - set_fact:
        client_hostname: "{{ ansible_hostname }}"
    - debug:
        var: client_hostname

    - debug:
        var: ansible_default_ipv4
    - debug:
        var: ansible_all_ipv4_addresses

    - set_fact:
        client_ip: "{{ ansible_all_ipv4_addresses|last }}"
    - debug:
        var: client_ip

dar

shell> ansible-playbook -i inventory -l host011 pb2.yml

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

TASK [setup] *********************************************************************************
ok: [host011]

TASK [set_fact] ******************************************************************************
ok: [host011]

TASK [debug] *********************************************************************************
ok: [host011] => 
  client_hostname: test_11

TASK [debug] *********************************************************************************
ok: [host011] => 
  ansible_default_ipv4: {}

TASK [debug] *********************************************************************************
ok: [host011] => 
  ansible_all_ipv4_addresses:
  - 10.1.0.61

TASK [set_fact] ******************************************************************************
ok: [host011]

TASK [debug] *********************************************************************************
ok: [host011] => 
  client_ip: 10.1.0.61

PLAY RECAP ***********************************************************************************
host011: ok=7    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

La estructura y el formato de los datos pueden diferir entre los sistemas operativos.


Puede recopilar los hechos por su cuenta. Por ejemplo, el siguiente libro de jugadas

shell> cat pb3.yml
- hosts: all
  gather_facts: false
    
  tasks:

    - name: Grab client hostname
      command: cat /etc/hostname
      register: out
    - set_fact:
        client_hostname: "{{ out.stdout }}"
    - debug:
        var: client_hostname

    - name: Grab client IP address
      shell: hostname -i | sed -n '1 p'
      register: out
    - set_fact:
        client_ip: "{{ out.stdout|split|last }}"
    - debug:
        var: client_ip

dar ejecución en Linux

shell> ansible-playbook -i inventory -l central_server pb3.yml

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

TASK [Grab client hostname] ******************************************************************
changed: [central_server]

TASK [set_fact] ******************************************************************************
ok: [central_server]

TASK [debug] *********************************************************************************
ok: [central_server] => 
  client_hostname: central_server

TASK [Grab client IP address] ****************************************************************
changed: [central_server]

TASK [set_fact] ******************************************************************************
ok: [central_server]

TASK [debug] *********************************************************************************
ok: [central_server] => 
  client_ip: 10.1.0.22

PLAY RECAP ***********************************************************************************
central_server: ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

El resultado de las utilidades puede diferir entre los sistemas operativos.


3) Informar al servidor central

Usa Jinja para crear la estructura. Ejecute la tarea una vez y deléguela alservidor_central

shell> cat pb4.yml
- hosts: all
  gather_facts: false
    
  tasks:

    - setup:
        gather_subset:
          - machine
          - network
    - set_fact:
        client_hostname: "{{ ansible_hostname }}"
        client_ip: "{{ ansible_all_ipv4_addresses|last }}"

    - copy:
        dest: /tmp/test_host_ip.txt
        content: |
          {% for host in ansible_play_hosts %}
          {{ hostvars[host]['client_hostname'] }} - {{ hostvars[host]['client_ip'] }}
          {% endfor %}
      run_once: true
      delegate_to: central_server

dar

shell> ansible-playbook -i inventory -l host011,host013 pb4.yml

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

TASK [setup] *********************************************************************************
ok: [host013]
ok: [host011]

TASK [set_fact] ******************************************************************************
ok: [host011]
ok: [host013]

TASK [copy] **********************************************************************************
changed: [host011 -> central_server(localhost)]

PLAY RECAP ***********************************************************************************
host011: ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host013: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

El libro de jugadas creó el archivo enservidor_central

shell> cat /tmp/test_host_ip.txt 
test_11 - 10.1.0.61
test_13 - 10.1.0.63

Utilice el móduloarchivo de líneasi desea agregar líneas al archivo. El siguiente libro de jugadas es idempotente.

shell> cat pb5.yml
- hosts: all
  gather_facts: false
    
  tasks:

    - setup:
        gather_subset:
          - machine
          - network
    - set_fact:
        client_hostname: "{{ ansible_hostname }}"
        client_ip: "{{ ansible_all_ipv4_addresses|last }}"

    - lineinfile:
        path: /tmp/test_host_ip.txt
        line: |-
          {{ hostvars[item]['client_hostname'] }} - {{ hostvars[item]['client_ip'] }}
      loop: "{{ ansible_play_hosts }}"
      run_once: true
      delegate_to: central_server

No habrá cambios cuando lo ejecute repetidamente en los mismos hosts.

shell> ansible-playbook -i inventory -l host011,host013 pb5.yml

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

TASK [setup] *********************************************************************************
ok: [host011]
ok: [host013]

TASK [set_fact] ******************************************************************************
ok: [host011]
ok: [host013]

TASK [lineinfile] ****************************************************************************
ok: [host011 -> central_server(localhost)] => (item=host011)
ok: [host011 -> central_server(localhost)] => (item=host013)

PLAY RECAP ***********************************************************************************
host011: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host013: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Se agregarán nuevas líneas al archivo si el libro de jugadas se ejecuta en nuevos hosts.


shell> ansible-playbook -i inventory -l central_server pb5.yml

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

TASK [setup] *********************************************************************************
ok: [central_server]

TASK [set_fact] ******************************************************************************
ok: [central_server]

TASK [lineinfile] ****************************************************************************
changed: [central_server] => (item=central_server)

PLAY RECAP ***********************************************************************************
central_server: ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

La nueva línea se agregó al archivo.

shell> cat /tmp/test_host_ip.txt 
test_11 - 10.1.0.61
test_13 - 10.1.0.63
central_server - 10.1.0.184

Respuesta2

Puede recorrer la variable client_hosts usando la directiva with_items en su libro de jugadas. Luego puede hacer referencia a cada host individual utilizando la variable item en el bucle.

A continuación se muestra un ejemplo de cómo puede modificar su libro de estrategias para manejar múltiples hosts:

- name: Gather client information
  hosts: "{{ client_hosts | default(omit) }}"  
  tasks:
    - name: Grab client hostname and IP address
      shell: |
        hostname -i | sed -n '1 p' > /tmp/client_ip
        cat /etc/hostname > /tmp/client_hostname
      register: gather_client_info
      become: true

    - name: Set client hostname and IP address as variables
      set_fact:
        client_hostname: "{{ hostvars[item]['gather_client_info'].stdout_lines[1] }}"
        client_ip: "{{ hostvars[item]['gather_client_info'].stdout_lines[0] }}"
      with_items: "{{ client_hosts | default(omit) }}"

- name: Update server
  hosts: central.server  
  tasks:
    - name: Update client host list
      lineinfile:
        path: /path/to/file
        line: "{{ client_ip }} - {{ client_hostname }}"
      with_items: "{{ client_hosts | default(omit) }}"

Este manual recorrerá cada host en la variable client_hosts y recopilará el nombre de host y la dirección IP utilizando el módulo shell. Luego establecerá estos valores como variables usando el módulo set_fact. Finalmente, recorrerá la variable client_hosts nuevamente y usará el módulo lineinfile para actualizar el archivo en el servidor central con el nombre de host y la dirección IP de cada host.

Espero que esto ayude.

información relacionada