Si en nuestro playbook realizamos una escalada de privilegios en los nodos target y tenemos a su vez handlers que se ejecutan en local con "delegate_to: localhost", éstos intentarán a su vez realizar escalada de privilegios en el nodo de control. Hay dos problemas: por un lado el handler no tiene por qué requerir una escalada y por otro las credenciales de become pueden no coincidir en la máquina de control, por lo que la ejecución del handler fallará.
Para ilustrarlo con un ejemplo, este es un playbook sencillo que simplemente reinicia el servidor Apache y manda un correo notificando en cuántos servidores se ha realizado la tarea:
---
- hosts: "{{ server | default('none') }}"
become: yes
tasks:
- name: Restart Apache Web Server
service:
name: apache2
state: restarted
notify: email-notification
handlers:
- name: email-notification
mail:
host: smtp.gmail.com
port: 587
username: mail@example.com
password: *******
from: SYSTEM
to: example <mail@example.com>
subject: Ansible-Report Apache Web Server restarted
body: |-
Apache Web Server restarted in:
{% for item in ansible_play_hosts_all %}
- Server: {{ item }}
{% endfor %}
delegate_to: localhost
run_once: true
La instrucción "become: yes" aplica a todas las acciones -tasks y handlers- del playbook. El handler en concreto delega a localhost (máquina control) e intenta realizar una escalada de privilegios con la contraseña que introdujimos con become. Si nuestro nodo de control no tiene la misma contraseña de root, fallará la ejecución del handler.
ansible-playbook main.yml -e "server=jota-node01" --ask-become -vv
Aquí el error de ejecución:
RUNNING HANDLER [email-notification] *************************************************************************************************************************************************************************
task path: /ansible/playbooks/apache/restart-httpd/main.yml:13
fatal: [jota-node01 -> localhost]: FAILED! => {"changed": false, "module_stderr": "su: Authentication failure\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}
Tenemos varias opciones:
ansible_become_pass para el host en el inventario.localhost ansible_connection=local ansible_become_pass=test123
Y en el handler poner la opción "delegate_facts: True"
handlers:
- name: email-notification
mail:
host: smtp.gmail.com
port: 587
username: mail@example.com
password: *******
from: SYSTEM
to: example <mail@example.com>
subject: Ansible-Report Apache Web Server restarted
body: |-
Apache Web Server restarted in:
{% for item in ansible_play_hosts_all %}
- Server: {{ item }}
{% endfor %}
delegate_to: localhost
delegate_facts: True
run_once: true
"become: no"
handlers:
- name: email-notification
become: no
mail:
host: smtp.gmail.com
port: 587
username: mail@example.com
password: *******
from: SYSTEM
to: example <mail@example.com>
subject: Ansible-Report Apache Web Server restarted
body: |-
Apache Web Server restarted in:
{% for item in ansible_play_hosts_all %}
- Server: {{ item }}
{% endfor %}
delegate_to: localhost
run_once: true
Lanzamos:
ansible-playbook main.yml -e "server=jota-node01" --ask-become -vv
Ahora la ejecución finaliza sin errores:
RUNNING HANDLER [email-notification] ********************************************************************
task path: /ansible/playbooks/apache/restart-httpd/main.yml:13
ok: [jota-node01 -> localhost] => {"changed": false, "msg": "Mail sent successfully", "result": {}}
