Introdução
A automação com modelos mantém uma única origem enquanto produz configurações a partir das entradas de cada host. Você renderizará arquivos a partir de variáveis e fatos, transformará um cabeçalho com um filtro Jinja integrado, escolherá um ramo condicional e carregará uma configuração nginx validada por meio de um handler. Solicitações HTTP reais e um candidato inválido rejeitado mostrarão a diferença entre texto de origem, arquivos gerenciados e comportamento do serviço ativo.
Renderizar variáveis de host e fatos no conteúdo gerenciado
Nesta etapa, você criará um modelo de página e renderizará arquivos reais distintos a partir da mesma origem nos dois destinos.
Os nós preparados têm nginx instalado como pré-requisito. Seu trabalho é renderizar e aplicar configurações, em vez de instalar o pacote novamente. Os modelos são renderizados no controlador usando o contexto de variáveis de cada destino e depois transferidos aos destinos gerenciados.
Entre no projeto do controlador e crie um diretório templates. mkdir -p cria o diretório se necessário, sem falhar quando ele já existe:
cd /home/labex/project
mkdir -p templates
Defina entradas tipadas de host. O ambiente e a porta de escuta diferem, e os valores maintenance_mode sem aspas são 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
Expressões Jinja dentro de chaves duplas inserem valores no texto. O sufixo .j2 é uma convenção útil para origens de modelos, não uma exigência para o nome do arquivo de destino gerenciado. Crie uma origem que use o nome atual no inventário, a variável de ambiente e o fato descoberto da distribuição:
cat > templates/page.txt.j2 <<'JINJA'
host={{ inventory_hostname }}
environment={{ environment_label }}
distribution={{ ansible_facts.distribution }}
JINJA
Use o módulo template para renderizar. Copy normalmente transferiria os bytes da origem sem interpretar essas expressões Jinja. Colete fatos antes de usar o valor de distribuição e declare explicitamente a propriedade e as permissões gerenciadas:
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
Renderize as duas páginas gerenciadas:
ansible-playbook templates.yml --syntax-check
ansible-playbook templates.yml
Leia os arquivos reais nos destinos:
ansible all -m ansible.builtin.command -a 'cat /var/www/template-site/index.html'
Cada página deve identificar seu próprio host, o ambiente development/production correspondente e distribution=Ubuntu. O controlador ainda tem uma origem reutilizável, enquanto as duas saídas gerenciadas diferem.
Acrescentar um filtro integrado e um ramo condicional de configuração
Nesta etapa, você renderizará uma configuração nginx válida que altera o comportamento HTTP conforme as entradas de cada host.
Um filtro Jinja transforma um valor usando o símbolo de barra vertical. upper é um filtro integrado que converte texto em maiúsculas; não é um plugin personalizado. As instruções de controle Jinja usam delimitadores de porcentagem e chave. O ramo if/else/endif abaixo seleciona um cabeçalho de manutenção com base na entrada booleana.
Crie um modelo completo de configuração nginx. A porta, o cabeçalho de ambiente e o cabeçalho de manutenção vêm de dados por host. As configurações root e index servem a página que você já renderizou:
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
Acrescente as tarefas de configuração e serviço e o handler nomeado de recarga ao playbook existente. O comando validate da tarefa template testa um candidato renderizado temporário (%s) antes de substituir a configuração gerenciada. Uma configuração válida alterada notifica o handler; a tarefa service comum mantém o nginx em execução.
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
Aplique o playbook completo:
ansible-playbook templates.yml
A tarefa de configuração deve alterar os dois hosts e acionar o handler de recarga. Inspecione web1 na porta 8081 e web2 na porta 8082. curl -i inclui os cabeçalhos de resposta junto ao corpo:
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 deve retornar X-Environment: DEVELOPMENT e X-Maintenance: enabled; web2 deve retornar X-Environment: PRODUCTION e X-Maintenance: disabled. Os dois corpos mantêm a página correta por host. As datas dos cabeçalhos variam; compare os valores baseados em dados e o status de sucesso.
Alterar entradas e rejeitar um candidato inválido
Nesta etapa, você atualizará uma entrada persistente sem editar os modelos, observará o resultado ativo real e comprovará que a validação protege a configuração funcional.
Altere o ambiente salvo de web1 para staging e seu modo de manutenção para false, mantendo a porta existente:
cat > host_vars/web1.yml <<'YAML'
environment_label: staging
service_port: 8081
maintenance_mode: false
YAML
Aplique os mesmos modelos de origem:
ansible-playbook templates.yml
A página e a configuração de web1 devem mudar, notificando seu handler de recarga; web2 deve permanecer inalterado. Inspecione web1 novamente:
ansible web1 -m ansible.builtin.command -a 'curl --fail --silent --show-error -i http://127.0.0.1:8081/'
Seu corpo deve mostrar environment=staging, com X-Environment: STAGING e X-Maintenance: disabled. Os modelos continuaram reutilizáveis quando a entrada mudou.
Agora teste a rejeição de um candidato. cp salva a origem válida no controlador para que possa ser restaurada. Substitua apenas o modelo 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
Espera-se que a próxima execução do playbook falhe na validação do candidato. O || do shell executa a mensagem apenas após uma saída diferente de zero, permitindo que este exercício de falha intencional continue:
ansible-playbook templates.yml || printf 'Expected candidate validation rejection\n'
Leia a falha da tarefa: o nginx deve rejeitar o candidato temporário. A configuração gerenciada funcional deve permanecer no lugar, portanto os dois serviços ativos ainda devem responder corretamente:
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 restaura a origem válida salva para seu nome de arquivo original. Execute o playbook completo mais uma vez:
mv templates/nginx.conf.j2.good templates/nginx.conf.j2
ansible-playbook templates.yml
Os dois hosts devem informar changed=0, failed=0 e unreachable=0, sem handler de recarga agendado. A validação protegeu a configuração ativa sem tornar uma cópia de um candidato inválido o estado final desejado.
Resumo
Você renderizou variáveis de hosts e fatos com o módulo template, usou o filtro integrado upper e um ramo booleano Jinja e aplicou uma configuração validada por meio de um handler. Uma entrada alterada produziu uma saída ativa diferente sem regravar as origens. Um candidato inválido foi rejeitado antes da substituição, e os modelos válidos restaurados convergiram sem alterações extras.


