Generar y validar plantillas de configuración

AnsibleBeginner
Practicar Ahora

Introducción

La automatización con plantillas mantiene un único origen mientras produce configuración a partir de las entradas de cada host. Generarás archivos desde variables y datos recopilados, transformarás una cabecera con un filtro integrado de Jinja, elegirás una rama condicional y cargarás configuración validada de nginx mediante un handler. Las solicitudes HTTP reales y un candidato inválido rechazado mostrarán la diferencia entre el texto de origen, los archivos administrados y el comportamiento activo del servicio.

Generar contenido administrado con variables de host y datos recopilados

En este paso, crearás una plantilla de página y generarás archivos reales distintos a partir del mismo origen en ambos destinos.

Los nodos preparados tienen nginx instalado como requisito previo. Tu trabajo es generar y aplicar configuración, en lugar de instalar otra vez el paquete. Las plantillas se procesan en el controlador usando el contexto de variables de cada destino y después se transfieren a los destinos administrados.

Entra en el proyecto del controlador y crea un directorio templates. mkdir -p crea el directorio si es necesario sin fallar cuando ya existe:

cd /home/labex/project
mkdir -p templates

Define entradas tipadas de los hosts. El entorno y el puerto de escucha difieren, y los valores maintenance_mode sin comillas son booleanos YAML:

cat > host_vars/web1.yml <<'YAML'
environment_label: development
service_port: 8081
maintenance_mode: true
YAML
cat > host_vars/web2.yml <<'YAML'
environment_label: production
service_port: 8082
maintenance_mode: false
YAML

Las expresiones Jinja entre llaves dobles insertan valores en el texto. El sufijo .j2 es una convención útil para los archivos de origen de plantillas, no un requisito del nombre del archivo administrado de destino. Crea un origen que use el nombre del host actual en el inventario, la variable del entorno y el dato descubierto de distribución:

cat > templates/page.txt.j2 <<'JINJA'
host={{ inventory_hostname }}
environment={{ environment_label }}
distribution={{ ansible_facts.distribution }}
JINJA

Usa el módulo template para generar el contenido. Copy normalmente transferiría los bytes del origen sin interpretar estas expresiones Jinja. Recopila los datos antes de consumir el valor de distribución y declara propiedad y permisos administrados explícitos:

cat > templates.yml <<'YAML'
- name: Render host-specific website content
  hosts: all
  gather_facts: true
  become: true
  tasks:
    - name: Prepare the rendered site directory
      ansible.builtin.file:
        path: /var/www/template-site
        state: directory
        owner: root
        group: root
        mode: "0755"

    - name: Render each host's website content
      ansible.builtin.template:
        src: templates/page.txt.j2
        dest: /var/www/template-site/index.html
        owner: root
        group: root
        mode: "0644"
YAML

Genera ambas páginas administradas:

ansible-playbook templates.yml --syntax-check
ansible-playbook templates.yml

Lee los archivos reales de los destinos:

ansible all -m ansible.builtin.command -a 'cat /var/www/template-site/index.html'

Cada página debería identificar su propio host, el entorno development/production correspondiente y distribution=Ubuntu. El controlador sigue teniendo un origen reutilizable, mientras que las dos salidas administradas difieren.

Añadir un filtro integrado y una rama condicional de configuración

En este paso, generarás una configuración válida de nginx que cambie el comportamiento HTTP según las entradas de cada host.

Un filtro Jinja transforma un valor mediante el símbolo de barra vertical. upper es un filtro integrado que convierte texto a mayúsculas; no es un complemento personalizado. Las instrucciones de control Jinja usan delimitadores de porcentaje y llaves. La rama if/else/endif siguiente selecciona una cabecera de mantenimiento según la entrada booleana.

Crea una plantilla completa de configuración de nginx. El puerto, la cabecera de entorno y la cabecera de mantenimiento proceden de datos por host. Los ajustes root e index sirven la página que ya has generado:

cat > templates/nginx.conf.j2 <<'JINJA'
events {}
http {
    server_tokens off;
    server {
        listen {{ service_port }};
        root /var/www/template-site;
        index index.html;
        add_header X-Environment "{{ environment_label | upper }}" always;
{% if maintenance_mode %}
        add_header X-Maintenance "enabled" always;
{% else %}
        add_header X-Maintenance "disabled" always;
{% endif %}
    }
}
JINJA

Añade las tareas de configuración y servicio, junto con el handler de recarga con nombre, al playbook existente. El comando validate de la tarea template prueba un candidato generado temporal (%s) antes de sustituir la configuración administrada. Una configuración válida con cambios notifica al handler; la tarea service normal mantiene nginx en ejecución.

cat >> templates.yml <<'YAML'

    - name: Render and validate the nginx candidate before replacement
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: "0644"
        validate: /usr/sbin/nginx -t -c %s
      notify: Reload nginx

    - name: Keep the rendered site service running
      ansible.builtin.service:
        name: nginx
        state: started

  handlers:
    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
YAML

Aplica el playbook completo:

ansible-playbook templates.yml

La tarea de configuración debería cambiar ambos hosts y activar el handler de recarga. Inspecciona web1 en el puerto 8081 y web2 en el puerto 8082. curl -i incluye las cabeceras de respuesta junto con el cuerpo:

ansible web1 -m ansible.builtin.command -a 'curl --fail --silent --show-error -i http://127.0.0.1:8081/'
ansible web2 -m ansible.builtin.command -a 'curl --fail --silent --show-error -i http://127.0.0.1:8082/'

Web1 debería devolver X-Environment: DEVELOPMENT y X-Maintenance: enabled; web2 debería devolver X-Environment: PRODUCTION y X-Maintenance: disabled. Ambos cuerpos conservan la página correcta por host. Las fechas de las cabeceras varían; compara los valores basados en datos y el estado satisfactorio.

Cambiar entradas y rechazar un candidato inválido

En este paso, actualizarás una entrada persistente sin editar las plantillas, observarás el resultado activo real y demostrarás que la validación protege la configuración funcional.

Cambia el entorno guardado de web1 a staging y su modo de mantenimiento a false, conservando su puerto existente:

cat > host_vars/web1.yml <<'YAML'
environment_label: staging
service_port: 8081
maintenance_mode: false
YAML

Aplica las mismas plantillas de origen:

ansible-playbook templates.yml

La página y la configuración de Web1 deberían cambiar, notificando a su handler de recarga; web2 debería permanecer sin cambios. Inspecciona web1 otra vez:

ansible web1 -m ansible.builtin.command -a 'curl --fail --silent --show-error -i http://127.0.0.1:8081/'

Su cuerpo debería mostrar environment=staging, con X-Environment: STAGING y X-Maintenance: disabled. Las plantillas siguieron siendo reutilizables al cambiar la entrada.

Ahora prueba el rechazo de candidatos. cp guarda el origen válido del controlador para poder restablecerlo. Sustituye únicamente la plantilla de nginx por texto deliberadamente inválido:

cp templates/nginx.conf.j2 templates/nginx.conf.j2.good
printf 'invalid nginx configuration;\n' > templates/nginx.conf.j2

Se espera que la siguiente ejecución del playbook falle en la validación del candidato. El || de la shell ejecuta el mensaje únicamente después de una salida distinta de cero, permitiendo continuar este ejercicio de fallo intencionado:

ansible-playbook templates.yml || printf 'Expected candidate validation rejection\n'

Lee el fallo de la tarea: nginx debería rechazar el candidato temporal. La configuración administrada funcional debe permanecer en su lugar, por lo que ambos servicios activos deberían seguir respondiendo correctamente:

ansible web1 -m ansible.builtin.command -a 'curl --fail --silent --show-error -i http://127.0.0.1:8081/'
ansible web2 -m ansible.builtin.command -a 'curl --fail --silent --show-error -i http://127.0.0.1:8082/'

mv restablece el origen válido guardado con su nombre de archivo original. Ejecuta el playbook completo una vez más:

mv templates/nginx.conf.j2.good templates/nginx.conf.j2
ansible-playbook templates.yml

Ambos hosts deberían informar changed=0, failed=0 y unreachable=0, sin ningún handler de recarga programado. La validación protegió la configuración activa sin convertir una copia de un candidato inválido en el estado final deseado.

Resumen

Has generado contenido con variables de host y datos recopilados mediante el módulo template, usado el filtro integrado upper y una rama booleana de Jinja y aplicado configuración validada mediante un handler. Las entradas modificadas produjeron salidas activas distintas sin reescribir los orígenes. Un candidato inválido se rechazó antes de sustituir la configuración y las plantillas válidas restablecidas convergieron sin cambios adicionales.