Controlar a Execução de Playbooks Ansible no RHEL

Red Hat Enterprise LinuxBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá a controlar o fluxo de execução de playbooks do Ansible em um sistema Red Hat Enterprise Linux (RHEL). Você começará escrevendo um playbook que utiliza estruturas de controle fundamentais, incluindo loops para repetir tarefas de forma eficiente e condicionais para executar tarefas apenas quando critérios específicos são atendidos. Você também implementará handlers para acionar ações, como reinícios de serviços, apenas quando uma alteração ocorre, tornando sua automação mais inteligente e eficiente.

Com base nessas habilidades fundamentais, você explorará técnicas mais avançadas para gerenciar a execução de playbooks. Isso inclui o uso de instruções block e rescue para lidar com falhas de tarefas de forma graciosa e o emprego de changed_when e failed_when para obter controle granular sobre o status da tarefa. Para concluir o laboratório, você aplicará todos esses conceitos em um exercício prático para implantar um servidor web seguro, solidificando sua capacidade de criar automação Ansible robusta e confiável.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 89%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Escrever um Playbook com Loops e Condicionais

Nesta etapa, você aprenderá dois conceitos fundamentais no Ansible para controlar a execução de tarefas: loops e condicionais. Loops permitem que você repita uma tarefa várias vezes com valores diferentes, o que é altamente eficiente para tarefas como instalar vários pacotes ou criar vários usuários. Condicionais, usando a palavra-chave when, permitem que você execute uma tarefa apenas quando critérios específicos são atendidos, como o sistema operacional ser de uma versão específica ou um arquivo já existir.

Primeiro, vamos garantir que o Ansible esteja instalado em sua VM LabEx. Usaremos o gerenciador de pacotes DNF para isso.

sudo dnf install -y ansible-core

Você deverá ver uma saída indicando que ansible-core e suas dependências estão sendo instalados.

...
Installed:
  ansible-core-2.x.x-1.el9.x86_64
  ...
Complete!

Agora, vamos configurar nosso diretório de projeto. Todo o nosso trabalho para este laboratório ficará dentro de um diretório dedicado para manter as coisas organizadas.

cd ~/project
mkdir control-flow-lab
cd control-flow-lab

Um projeto Ansible precisa de um arquivo de inventário, que define os hosts que você deseja gerenciar. Para este laboratório, gerenciaremos a máquina local, localhost.

Crie um arquivo de inventário chamado inventory usando o editor nano:

nano inventory

Adicione a seguinte linha ao arquivo. Isso diz ao Ansible para executar o playbook em localhost e se conectar a ele diretamente em vez de usar SSH.

localhost ansible_connection=local

Salve o arquivo e saia do nano pressionando Ctrl+X, depois Y e Enter.

Em seguida, criaremos nosso primeiro playbook, playbook.yml, para demonstrar um loop. Este playbook instalará uma lista de ferramentas úteis de linha de comando.

nano playbook.yml

Insira o seguinte conteúdo YAML no editor. Este playbook define uma tarefa que usa o módulo ansible.builtin.dnf para instalar pacotes. A diretiva become: yes diz ao Ansible para executar tarefas com privilégios de sudo, o que é necessário para instalar pacotes. A palavra-chave loop fornece uma lista de nomes de pacotes. O Ansible executará esta tarefa uma vez para cada item na lista, substituindo o placeholder {{ item }} pelo nome do pacote atual.

---
- name: Install common tools
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

Salve e saia do editor. Agora, execute o playbook usando o comando ansible-playbook, especificando seu arquivo de inventário com o sinalizador -i.

ansible-playbook -i inventory playbook.yml

A saída mostrará a execução do playbook. O Ansible verificará cada pacote e o instalará se ele ainda não estiver presente. O PLAY RECAP no final resume os resultados.

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
changed: [localhost] => (item=git)
changed: [localhost] => (item=tree)
changed: [localhost] => (item=wget)

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

Agora, vamos modificar o playbook para incluir uma tarefa condicional. Adicionaremos uma tarefa que exibe uma mensagem, mas apenas se o sistema operacional for Red Hat Enterprise Linux. Este é um caso de uso comum para adaptar a automação a ambientes específicos.

Abra o arquivo playbook.yml novamente:

nano playbook.yml

Adicione as seguintes tarefas ao final do arquivo. A palavra-chave when avalia a expressão fornecida. ansible_facts['distribution'] é uma variável que o Ansible descobre automaticamente sobre o host gerenciado. A primeira tarefa será executada porque nosso ambiente é RHEL, e a segunda tarefa será pulada.

---
- name: Install tools and run conditional tasks
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

    - name: Show message on Red Hat systems
      ansible.builtin.debug:
        msg: "This system is a Red Hat family distribution."
      when: ansible_facts['distribution'] == "RedHat"

    - name: Show message on other systems
      ansible.builtin.debug:
        msg: "This system is NOT a Red Hat family distribution."
      when: ansible_facts['distribution'] != "RedHat"

Salve e saia do editor. Execute o playbook atualizado:

ansible-playbook -i inventory playbook.yml

Observe a saída com atenção. A tarefa de instalação de pacotes provavelmente relatará ok para todos os itens, pois eles já estão instalados. Mais importante, você verá a primeira mensagem de depuração impressa, enquanto a segunda é marcada como skipping.

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
ok: [localhost] => (item=git)
ok: [localhost] => (item=tree)
ok: [localhost] => (item=wget)

TASK [Show message on Red Hat systems] *****************************************
ok: [localhost] => {
    "msg": "This system is a Red Hat family distribution."
}

TASK [Show message on other systems] *******************************************
skipping: [localhost]

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

Você escreveu e executou com sucesso um playbook Ansible que usa loops para realizar ações repetitivas e condicionais para controlar a execução de tarefas com base nos fatos do sistema.

Implementar Handlers para Disparar Reinícios de Serviço

Nesta etapa, você aprenderá sobre os handlers do Ansible. Handlers são tarefas especiais que só são executadas quando "notificadas" por outra tarefa. Eles são tipicamente usados para ações que só devem ocorrer quando uma alteração foi feita, como reiniciar um serviço após seu arquivo de configuração ter sido atualizado. Essa abordagem é mais eficiente do que reiniciar um serviço a cada execução do playbook, pois garante que a ação seja tomada apenas quando necessário.

Criaremos um playbook que instala o servidor web Nginx, implanta uma página inicial personalizada e usa um handler para recarregar o Nginx apenas quando o conteúdo da página inicial muda.

Primeiro, vamos criar um novo diretório para este exercício para manter nosso projeto organizado.

cd ~/project
mkdir control-handlers-lab
cd control-handlers-lab

Como antes, precisamos de um arquivo de inventário para dizer ao Ansible onde executar o playbook.

nano inventory

Adicione a seguinte linha para especificar a máquina local.

localhost ansible_connection=local

Salve e saia do editor (Ctrl+X, Y, Enter).

Em seguida, precisamos de um arquivo para servir como a página inicial do nosso servidor web. Criaremos um diretório files para armazená-lo.

mkdir files

Agora, crie um arquivo simples index.html dentro do diretório files.

nano files/index.html

Adicione o seguinte conteúdo HTML:

<h1>Welcome to the Ansible Handler Lab!</h1>

Salve e saia do editor.

Agora, você criará o playbook deploy_nginx.yml. Este playbook realizará três ações principais: instalar o Nginx, copiar o arquivo index.html e definir um handler para recarregar o Nginx.

nano deploy_nginx.yml

Insira o seguinte conteúdo. Preste muita atenção à palavra-chave notify na tarefa "Copy homepage" e à seção handlers correspondente no final. A diretiva become: yes diz ao Ansible para executar tarefas com privilégios de sudo, o que é necessário para instalar pacotes e gerenciar serviços.

---
- name: Deploy Nginx with a handler
  hosts: localhost
  become: yes
  tasks:
    - name: Ensure Nginx is installed
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: Start and enable Nginx service
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Copy homepage
      ansible.builtin.copy:
        src: files/index.html
        dest: /usr/share/nginx/html/index.html
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded

Salve e saia do editor.

Agora, execute o playbook pela primeira vez.

ansible-playbook -i inventory deploy_nginx.yml

Você verá a saída mostrando que o Nginx foi instalado (ou já estava presente), o serviço Nginx foi iniciado e habilitado, o arquivo index.html foi copiado (status changed) e, importante, o handler foi notificado e executado ao final do play.

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Você pode verificar que o servidor web está em execução e servindo sua página personalizada usando curl.

curl http://localhost

A saída deve ser o conteúdo do seu arquivo index.html.

<h1>Welcome to the Ansible Handler Lab!</h1>

Agora, execute exatamente o mesmo playbook novamente sem fazer nenhuma alteração.

ansible-playbook -i inventory deploy_nginx.yml

Desta vez, observe a saída. A tarefa "Copy homepage" relatará ok em vez de changed, pois o arquivo no destino já corresponde à origem. A tarefa "Start and enable Nginx service" também relatará ok, pois o serviço já está em execução e habilitado. Como nenhuma tarefa notificou o handler, o handler não foi executado.

...
TASK [Copy homepage] ***********************************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Para ver o handler em ação novamente, vamos modificar o arquivo de origem index.html.

nano files/index.html

Altere o conteúdo para o seguinte:

<h1>The Handler Ran Again!</h1>

Salve e saia. Agora, execute o playbook mais uma vez.

ansible-playbook -i inventory deploy_nginx.yml

Como o arquivo de origem mudou, a tarefa "Copy homepage" relatará changed novamente, o que, por sua vez, notificará e executará o handler reload nginx.

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Verifique a alteração com curl mais uma vez.

curl http://localhost

Você deverá ver a mensagem atualizada.

<h1>The Handler Ran Again!</h1>

Este exercício demonstra o poder e a eficiência dos handlers para gerenciar o estado do serviço em resposta a alterações de configuração.

Gerenciar Falhas de Tarefas com Block e Rescue

Nesta etapa, você aprenderá como lidar com erros de forma elegante em seus playbooks Ansible. Por padrão, se qualquer tarefa falhar, o Ansible para de executar todo o playbook naquele host. Embora este seja um padrão seguro, às vezes você precisa de mais controle. Você explorará dois métodos para tratamento de erros: a diretiva simples ignore_errors e a estrutura mais poderosa block, rescue e always, que fornece uma maneira de tentar tarefas e definir ações de recuperação se elas falharem.

Primeiro, vamos criar um novo diretório para este exercício.

cd ~/project
mkdir control-errors-lab
cd control-errors-lab

Crie o arquivo inventory padrão para localhost.

nano inventory

Adicione o seguinte conteúdo:

localhost ansible_connection=local

Salve e saia do editor (Ctrl+X, Y, Enter).

Agora, vamos criar um playbook chamado playbook.yml que foi projetado para falhar. A primeira tarefa tentará instalar um pacote que não existe.

nano playbook.yml

Insira o seguinte conteúdo. Este playbook tenta instalar um pacote falso httpd-fake e, em seguida, um pacote real, mariadb-server.

---
- name: Demonstrate Task Failure
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt to install a non-existent package
      ansible.builtin.dnf:
        name: httpd-fake
        state: present

    - name: Install MariaDB server
      ansible.builtin.dnf:
        name: mariadb-server
        state: present

Salve e saia do editor. Agora, execute o playbook.

ansible-playbook -i inventory playbook.yml

Você verá a primeira tarefa falhar com uma mensagem de erro porque o pacote httpd-fake não pode ser encontrado. Crucialmente, o Ansible parará e a segunda tarefa, "Install MariaDB server", não será executada.

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "No match for argument: httpd-fake", "rc": 1, "results": []}

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

Agora, vamos usar block e rescue para lidar com essa falha de forma mais elegante. A palavra-chave block agrupa um conjunto de tarefas. Se qualquer tarefa dentro do block falhar, o Ansible pula o restante das tarefas no block e executa as tarefas na seção rescue. A seção always será executada independentemente de as seções block ou rescue terem sido bem-sucedidas ou falhado.

Modifique playbook.yml para usar essa estrutura.

nano playbook.yml

Substitua todo o conteúdo pelo seguinte. Aqui, tentamos instalar o pacote falso no block. Quando ele falha, a seção rescue será executada, instalando mariadb-server como uma etapa de recuperação. A seção always imprimirá uma mensagem no final.

---
- name: Handle Task Failure with Block and Rescue
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt primary task, with recovery
      block:
        - name: Attempt to install a non-existent package
          ansible.builtin.dnf:
            name: httpd-fake
            state: present
        - name: This task will be skipped
          ansible.builtin.debug:
            msg: "This message will not appear because the previous task fails."
      rescue:
        - name: Install MariaDB server on failure
          ansible.builtin.dnf:
            name: mariadb-server
            state: present
      always:
        - name: This always runs
          ansible.builtin.debug:
            msg: "The block has completed, either by success or rescue."

Salve e saia. Execute o playbook novamente.

ansible-playbook -i inventory playbook.yml

Observe a saída. A primeira tarefa no block falha como esperado. A segunda tarefa no block é pulada. O Ansible então passa para a seção rescue e instala com sucesso mariadb-server. Finalmente, a seção always é executada.

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => ...

TASK [This task will be skipped] ***********************************************
skipping: [localhost]

RESCUE START *******************************************************************

TASK [Install MariaDB server on failure] ***************************************
changed: [localhost]

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

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

Agora, vamos ver o que acontece quando o block é bem-sucedido. Edite o playbook e corrija o nome do pacote.

nano playbook.yml

Altere httpd-fake para um pacote real, httpd.

## ... (rest of the playbook)
block:
  - name: Attempt to install a valid package
    ansible.builtin.dnf:
      name: httpd ## Corrected from httpd-fake
      state: present
  - name: This task will now run
    ansible.builtin.debug:
      msg: "This message will now appear because the previous task succeeds."
## ... (rest of the playbook)

Salve e saia. Execute o playbook mais uma vez.

ansible-playbook -i inventory playbook.yml

Desta vez, ambas as tarefas no block são bem-sucedidas. Como o block foi concluído sem erros, a seção rescue é completamente pulada. A seção always ainda é executada, como o nome sugere.

...
TASK [Attempt to install a valid package] **************************************
changed: [localhost]

TASK [This task will now run] **************************************************
ok: [localhost] => {
    "msg": "This message will now appear because the previous task succeeds."
}

RESCUE START *******************************************************************
skipping rescue

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

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

Agora você usou com sucesso a estrutura block/rescue/always para criar um playbook robusto que pode lidar com falhas e executar ações de recuperação.

Controlar o Estado da Tarefa com changed_when e failed_when

Nesta etapa, você obterá um controle mais refinado sobre como o Ansible interpreta o resultado de suas tarefas. Você aprenderá sobre duas diretivas poderosas: changed_when e failed_when.

  • changed_when: Por padrão, módulos como ansible.builtin.command ou ansible.builtin.shell quase sempre relatam um estado "changed" (alterado), mesmo que o comando que executaram não tenha modificado o sistema. changed_when permite definir uma condição personalizada que determina se uma tarefa deve ser relatada como "changed". Isso é crucial para escrever playbooks idempotentes e para acionar handlers com precisão.
  • failed_when: Às vezes, um comando pode sair com um código de status diferente de zero (que o Ansible considera uma falha), mesmo quando o resultado é aceitável. failed_when permite substituir as condições de falha padrão, permitindo que seu playbook continue com base em critérios mais inteligentes, como a saída do comando ou um código de saída específico.

Vamos começar configurando um novo diretório de projeto.

cd ~/project
mkdir control-state-lab
cd control-state-lab

Crie o arquivo inventory padrão para localhost.

nano inventory

Adicione o seguinte conteúdo:

localhost ansible_connection=local

Salve e saia do editor (Ctrl+X, Y, Enter).

Usando changed_when

Primeiro, vamos ver como uma tarefa de comando se comporta por padrão. Criaremos um playbook que executa o comando date. Este comando simplesmente imprime a data e não altera o sistema, mas o módulo command o relatará como uma alteração.

Crie um novo playbook chamado playbook.yml.

nano playbook.yml

Insira o seguinte conteúdo:

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (default behavior)
      ansible.builtin.command: date

Salve e saia. Agora, execute o playbook.

ansible-playbook -i inventory playbook.yml

Observe na saída que a tarefa é relatada como changed=1, mesmo que nada no sistema tenha sido modificado.

...
TASK [Check local time (default behavior)] *************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    ...

Agora, vamos usar changed_when para dizer ao Ansible que este comando nunca altera o sistema. Modifique playbook.yml.

nano playbook.yml

Adicione changed_when: false à tarefa.

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (with changed_when)
      ansible.builtin.command: date
      changed_when: false

Salve e saia. Execute o playbook novamente.

ansible-playbook -i inventory playbook.yml

Desta vez, a tarefa relata ok e o resumo final mostra changed=0. Você substituiu com sucesso o comportamento padrão.

...
TASK [Check local time (with changed_when)] ************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

Usando failed_when

Em seguida, vamos explorar failed_when. Criaremos uma tarefa que verifica a existência de um arquivo que não está lá. O comando "falhará" por padrão.

Primeiro, crie um arquivo dummy para pesquisar.

echo "System is running" > status.txt

Agora, modifique playbook.yml para procurar a palavra "ERROR" neste arquivo. O comando grep sairá com um código de status 1 porque a palavra não é encontrada, o que o Ansible interpreta como uma falha.

nano playbook.yml

Substitua o conteúdo pelo seguinte:

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (will fail)
      ansible.builtin.command: grep ERROR status.txt

Salve e saia. Execute o playbook.

ansible-playbook -i inventory playbook.yml

Como esperado, a execução do playbook para com uma mensagem FAILED!.

...
TASK [Check for ERROR in status file (will fail)] ******************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["grep", "ERROR", "status.txt"], "delta": "...", "end": "...", "msg": "non-zero return code", "rc": 1, ...}
...

Isso não é o que queremos. A ausência de "ERROR" é uma condição de sucesso para nós. Podemos usar failed_when para redefinir o que constitui uma falha. Diremos ao Ansible para falhar apenas se o código de retorno do comando for maior que 1. Um código de retorno de 1 (padrão não encontrado) agora será considerado um sucesso. Também precisamos registrar o resultado da tarefa para inspecionar seu código de retorno (rc).

Modifique playbook.yml mais uma vez.

nano playbook.yml

Atualize o playbook com register e failed_when.

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (with failed_when)
      ansible.builtin.command: grep ERROR status.txt
      register: grep_result
      failed_when: grep_result.rc > 1
      changed_when: false

Também adicionamos changed_when: false porque grep é uma operação somente leitura e não altera o sistema.

Salve e saia. Execute o playbook final.

ansible-playbook -i inventory playbook.yml

Sucesso! A tarefa agora relata ok porque seu código de retorno foi 1, o que não atende à nossa nova condição de falha (rc > 1). O playbook é concluído com sucesso.

...
TASK [Check for ERROR in status file (with failed_when)] ***********************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

Agora você aprendeu como usar changed_when e failed_when para definir precisamente os estados de sucesso, alterado e falha de suas tarefas, levando a uma automação mais robusta e inteligente.

Implantar um Servidor Web Seguro Usando Controle de Tarefas

Nesta etapa final, você combinará todos os conceitos que aprendeu — loops, condicionais, handlers e tratamento de erros — para construir um playbook único e robusto. O objetivo é implantar o servidor web Apache (httpd), protegê-lo com mod_ssl, gerar um certificado SSL autoassinado e implantar uma página inicial personalizada. Este exercício prático espelha uma tarefa de automação do mundo real.

Primeiro, vamos configurar o diretório do projeto para este exercício final.

cd ~/project
mkdir control-review-lab
cd control-review-lab

Como sempre, crie um arquivo inventory para definir seu host de destino.

nano inventory

Adicione a entrada localhost:

localhost ansible_connection=local

Salve e saia do editor (Ctrl+X, Y, Enter).

Em seguida, precisamos de um diretório para armazenar os arquivos que nosso playbook implantará.

mkdir files

Agora, crie uma página inicial personalizada, index.html, dentro do diretório files.

nano files/index.html

Adicione o seguinte conteúdo HTML. Esta será a página servida pelo nosso servidor web seguro.

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

Salve e saia do editor.

Agora é hora de construir o playbook principal, deploy_secure_web.yml. Este playbook será mais complexo do que os anteriores, integrando múltiplos conceitos.

nano deploy_secure_web.yml

Insira o seguinte playbook completo. Leia os comentários dentro do código para entender como cada parte contribui para o objetivo geral.

---
- name: Deploy a Secure Apache Web Server
  hosts: localhost
  become: yes
  vars:
    packages_to_install:
      - httpd
      - mod_ssl
    ssl_cert_path: /etc/pki/tls/certs/localhost.crt
    ssl_key_path: /etc/pki/tls/private/localhost.key

  tasks:
    - name: Stop nginx to free port 80
      ansible.builtin.systemd:
        name: nginx
        state: stopped
      ignore_errors: yes

    - name: Install httpd and mod_ssl packages
      ansible.builtin.dnf:
        name: "{{ packages_to_install }}"
        state: present

    - name: Generate self-signed SSL certificate if it does not exist
      ansible.builtin.command: >
        openssl req -new -nodes -x509
        -subj "/C=US/ST=None/L=None/O=LabEx/CN=localhost"
        -keyout {{ ssl_key_path }}
        -out {{ ssl_cert_path }}
      args:
        creates: "{{ ssl_cert_path }}"

    - name: Deploy custom index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      notify: restart httpd

    - name: Start and enable httpd service
      ansible.builtin.systemd:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: restart httpd
      ansible.builtin.systemd:
        name: httpd
        state: restarted

Vamos detalhar o que este playbook faz:

  • vars: Define variáveis para os pacotes a serem instalados e os caminhos para o certificado e chave SSL, tornando o playbook mais fácil de ler e manter.
  • Tarefa Stop Nginx: Para o serviço nginx da etapa anterior do laboratório para liberar a porta 80 para o Apache. Usa ignore_errors: yes caso o nginx não esteja em execução.
  • Tarefa de Instalação: Usa a variável packages_to_install para instalar tanto httpd quanto mod_ssl.
  • Tarefa de Geração de Certificado: Esta é uma tarefa chave. Ela usa o comando openssl para criar um certificado autoassinado. A diretiva args: { creates: ... } torna esta tarefa idempotente. O comando só será executado se o arquivo de certificado (/etc/pki/tls/certs/localhost.crt) ainda não existir.
  • Tarefa de Implantação da Página Inicial: Copia seu index.html personalizado. Crucialmente, ele usa notify: restart httpd para acionar o handler se o arquivo for alterado.
  • Tarefa de Início do Serviço: Usa o módulo systemd para iniciar e habilitar o serviço httpd após toda a configuração estar no lugar, garantindo que ele inicie na inicialização.
  • Handler: O handler restart httpd executa uma reinicialização do Apache usando systemd, que é acionada apenas quando um arquivo de configuração ou conteúdo é alterado.

Salve e saia do editor. Agora, execute seu playbook abrangente.

ansible-playbook -i inventory deploy_secure_web.yml

Na primeira execução, você deverá ver várias tarefas relatando changed, incluindo a parada do nginx, instalação de pacotes, geração de certificado, cópia de arquivo e início do serviço.

...
TASK [Start and enable httpd service] ******************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=6    changed=5    unreachable=0    failed=0    ...

Finalmente, verifique se seu servidor web seguro está funcionando. Primeiro teste a versão HTTP, depois a versão HTTPS com o sinalizador -k para ignorar avisos sobre o certificado autoassinado.

curl http://localhost

Você deverá ver o conteúdo da sua página inicial personalizada.

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

Você também pode testar a versão HTTPS:

curl -k https://localhost

Se você executar o playbook novamente, verá que nenhuma tarefa relata changed, e o handler não é executado, provando que seu playbook é idempotente.

Parabéns! Você construiu com sucesso um playbook Ansible prático e robusto que combina loops, variáveis, execução de comandos idempotentes e handlers para implantar um aplicativo seguro.

Resumo

Neste laboratório, você aprendeu a controlar a execução de playbooks Ansible em um sistema RHEL. Você começou configurando um ambiente de projeto básico, incluindo a instalação do Ansible e a criação de um arquivo de inventário. Em seguida, explorou estruturas fundamentais de fluxo de controle, usando loops para repetir eficientemente tarefas com diferentes entradas e condicionais com a instrução when para executar tarefas apenas sob circunstâncias específicas. Com base nisso, você implementou handlers para criar automações responsivas, como acionar uma reinicialização de serviço apenas quando seu arquivo de configuração foi modificado.

O laboratório também cobriu técnicas avançadas para gerenciar a execução de playbooks. Você aprendeu como construir playbooks mais robustos usando cláusulas block e rescue para lidar com falhas de tarefas de forma graciosa. Além disso, você obteve controle granular sobre os resultados das tarefas usando changed_when e failed_when para definir condições personalizadas de sucesso e falha. Finalmente, você consolidou todas essas habilidades aplicando-as a um cenário prático: a implantação de um servidor web seguro, demonstrando como combinar efetivamente loops, condicionais, handlers e tratamento de erros em um fluxo de trabalho de automação do mundo real.