Estruturando Playbooks Ansible Complexos no RHEL

AnsibleBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá técnicas essenciais para estruturar playbooks Ansible complexos no RHEL para criar automação mais gerenciável, escalável e reutilizável. Você progredirá de conceitos fundamentais para estratégias de organização avançadas, focando em como controlar precisamente em quais hosts sua automação é executada e como dividir playbooks grandes em componentes lógicos e modulares.

Você começará dominando a seleção de hosts, usando nomes de grupos básicos, curingas (wildcards), exclusões e operadores lógicos para direcionar nós específicos em seu inventário. Em seguida, explorará a modularização refatorando tarefas em arquivos separados usando include_tasks e import_tasks. Finalmente, você aprenderá a compor um fluxo de trabalho completo e multi-playbook com import_playbook, culminando na execução e verificação de seu projeto Ansible totalmente estruturado e modularizado.

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 94%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Selecionando Hosts com Padrões Básicos e de Curinga

Nesta etapa, você aprenderá os fundamentos de como direcionar hosts específicos em sua automação Ansible. O cerne disso é o arquivo de inventário Ansible, que lista os servidores que você gerencia, e a diretiva hosts dentro de um playbook, que especifica em quais desses servidores um conjunto de tarefas deve ser executado. Começaremos instalando o Ansible, criando um inventário e um playbook básicos e, em seguida, exploraremos como selecionar hosts usando nomes de grupos e padrões de curinga (wildcard).

Primeiro, vamos garantir que o Ansible esteja instalado em seu ambiente. O Ansible não vem instalado por padrão, portanto, você precisa instalá-lo usando o gerenciador de pacotes DNF. O pacote ansible-core fornece as ferramentas essenciais de linha de comando do Ansible, incluindo ansible-playbook. Execute o seguinte comando:

sudo dnf install -y ansible-core

Você deverá ver uma saída semelhante a esta:

...
Installed:
  ansible-core-2.16.x-x.el9.x86_64
  ...
Complete!

Em seguida, vamos criar um diretório dedicado para este exercício para manter nossos arquivos organizados. Todas as ações subsequentes nesta etapa ocorrerão dentro deste novo diretório:

mkdir -p ~/project/ansible_patterns
cd ~/project/ansible_patterns

Agora, crie um arquivo de inventário. Um inventário é um arquivo de texto que define os hosts e grupos de hosts sobre os quais os comandos, módulos e tarefas Ansible operam. Usaremos o formato INI por sua simplicidade.

Use o editor nano para criar um arquivo chamado inventory:

nano inventory

Adicione o seguinte conteúdo ao arquivo inventory. Isso define dois grupos, webservers e dbservers, cada um contendo dois hosts:

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.lab.net
db2.lab.net

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

Com nosso inventário pronto, vamos criar um playbook simples. Este playbook usará o módulo ansible.builtin.debug para exibir uma mensagem, confirmando em qual host a tarefa está sendo executada. Esta é uma ótima maneira de testar padrões de host sem fazer nenhuma alteração real no sistema.

Crie um novo arquivo chamado playbook.yml:

nano playbook.yml

Adicione o seguinte conteúdo YAML. Inicialmente, ele direciona todos os hosts no grupo webservers:

---
- name: Test Host Patterns
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Salve e saia do editor. Agora, execute o playbook usando ansible-playbook. A flag -i especifica nosso arquivo de inventário personalizado:

ansible-playbook playbook.yml -i inventory

A saída deve ser semelhante a esta:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Como você pode ver, apenas os hosts do grupo webservers foram direcionados. Agora, vamos modificar o playbook para usar um curinga (*). Curingas permitem uma correspondência de padrões mais flexível.

Edite playbook.yml e altere a linha hosts para hosts: "*.lab.net". Lembre-se de colocar padrões que contenham curingas entre aspas:

---
- name: Test Host Patterns
  hosts: "*.lab.net"
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Execute o playbook novamente:

ansible-playbook playbook.yml -i inventory

Você deverá ver uma saída semelhante a esta:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [db1.lab.net] => {
    "msg": "This task is running on db1.lab.net"
}
ok: [db2.lab.net] => {
    "msg": "This task is running on db2.lab.net"
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Desta vez, o play foi executado apenas em hosts cujos nomes terminam com .lab.net. Finalmente, vamos usar a palavra-chave especial all para direcionar todos os hosts definidos no inventário.

Edite playbook.yml uma última vez e altere a linha hosts para hosts: all:

---
- name: Test Host Patterns
  hosts: all
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Execute o playbook para ver o resultado:

ansible-playbook playbook.yml -i inventory

A saída mostrará todos os hosts sendo direcionados:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}
ok: [db1.lab.net] => {
    "msg": "This task is running on db1.lab.net"
}
ok: [db2.lab.net] => {
    "msg": "This task is running on db2.lab.net"
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

O playbook agora é executado em todos os quatro hosts do seu inventário, demonstrando o poder do padrão all.

Refinando a Seleção de Hosts com Exclusões e Operadores Lógicos

Nesta etapa, você avançará em suas habilidades de seleção de hosts aprendendo a usar exclusões e operadores lógicos. Esses recursos permitem um direcionamento altamente específico, o que é essencial ao gerenciar ambientes complexos. Você aprenderá a excluir hosts usando o operador ! (NOT) e a combinar grupos usando o operador & (AND). Continuaremos trabalhando com os arquivos inventory e playbook.yml da etapa anterior.

Primeiro, certifique-se de estar no diretório de trabalho correto:

cd ~/project/ansible_patterns

Para demonstrar efetivamente os operadores lógicos, precisamos criar alguma sobreposição em nossos grupos de hosts. Vamos editar o arquivo inventory para adicionar um novo grupo chamado production que contém um servidor web e um servidor de banco de dados:

nano inventory

Adicione o grupo [production] e seus membros ao final do arquivo:

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.lab.net
db2.lab.net

[production]
web1.example.com
db1.lab.net

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

Agora, vamos praticar a exclusão. O operador ! , que significa NOT, permite que você exclua um host ou grupo de uma seleção. Modifique seu playbook.yml para direcionar todos os hosts exceto aqueles no grupo dbservers:

nano playbook.yml

Atualize a linha hosts conforme mostrado abaixo. O padrão all,!dbservers seleciona todos os hosts e, em seguida, remove quaisquer que estejam no grupo dbservers:

---
- name: Test Host Patterns
  hosts: all,!dbservers
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Salve e saia do editor, em seguida, execute o playbook:

ansible-playbook playbook.yml -i inventory

Você deverá ver apenas os servidores web sendo direcionados:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Como esperado, apenas os hosts do grupo webservers foram direcionados.

Em seguida, vamos explorar o operador lógico AND. O operador & seleciona apenas os hosts que existem em ambos os grupos especificados (uma interseção). Vamos modificar o playbook para direcionar hosts que estão no grupo webservers E também no grupo production:

nano playbook.yml

Altere a linha hosts para webservers,&production:

---
- name: Test Host Patterns
  hosts: webservers,&production
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Salve e execute o playbook:

ansible-playbook playbook.yml -i inventory

Desta vez, apenas a interseção de ambos os grupos será direcionada:

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

A saída mostra corretamente que apenas web1.example.com foi direcionado, pois é o único host que é membro dos grupos webservers e production. Esses operadores fornecem controle preciso sobre quais hosts sua automação afeta.

Modularizando um Play com include_tasks e import_tasks

Nesta etapa, você aprenderá como estruturar projetos Ansible maiores dividindo-os em arquivos menores e reutilizáveis. À medida que os playbooks crescem, manter todas as tarefas em um único arquivo torna-se difícil de gerenciar. O Ansible fornece duas diretivas principais para isso: import_tasks e include_tasks. Ambas permitem que você inclua tarefas de outro arquivo.

  • import_tasks é estático. Ele é processado quando o playbook é analisado pela primeira vez pelo Ansible. Isso é melhor para partes estruturais e incondicionais do seu play.
  • include_tasks é dinâmico. Ele é processado durante a execução do play. Isso o torna adequado para uso com loops e condicionais.

Agora vamos refatorar nosso playbook para usar ambos. Primeiro, certifique-se de estar no diretório do projeto:

cd ~/project/ansible_patterns

Antes de prosseguir, vamos atualizar o arquivo de inventário para que os hosts apontem para localhost para este ambiente de laboratório. Isso permitirá que o playbook seja executado com sucesso:

nano inventory

Substitua o conteúdo pela seguinte configuração que mapeia os hosts de exemplo para localhost:

[webservers]
web1.example.com ansible_host=localhost ansible_connection=local
web2.example.com ansible_host=localhost ansible_connection=local

[dbservers]
db1.lab.net ansible_host=localhost ansible_connection=local
db2.lab.net ansible_host=localhost ansible_connection=local

Salve e saia do editor. Esta configuração usa ansible_host=localhost para redirecionar conexões para a máquina local e ansible_connection=local para evitar tentativas de conexão SSH.

Uma prática comum é armazenar arquivos de tarefas reutilizáveis em um subdiretório dedicado. Vamos criar um chamado tasks:

mkdir tasks

Agora, vamos criar um arquivo para tarefas de configuração comuns que podem se aplicar a muitos servidores. Colocaremos aqui uma tarefa para instalar o pacote do servidor web httpd:

nano tasks/web_setup.yml

Adicione o seguinte conteúdo. Observe que este arquivo é apenas uma lista de tarefas; ele não contém uma estrutura de play completa (como hosts: ou name:):

- name: Install the httpd package
  ansible.builtin.dnf:
    name: httpd
    state: present
  become: true

Salve e saia do nano. Em seguida, crie um segundo arquivo de tarefas para uma etapa de verificação simples:

nano tasks/verify_config.yml

Adicione esta tarefa de depuração a este arquivo:

- name: Display a verification message
  ansible.builtin.debug:
    msg: "Configuration tasks applied to {{ inventory_hostname }}"

Salve e saia do editor. Agora, vamos modificar o playbook.yml principal para usar esses novos arquivos de tarefas. Usaremos import_tasks para a configuração estática e include_tasks para a mensagem de verificação dinâmica:

nano playbook.yml

Substitua todo o conteúdo de playbook.yml pelo seguinte. Este playbook agora direciona o grupo webservers e usa os arquivos de tarefas modulares:

---
- name: Configure Web Servers
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Import web server setup tasks
      import_tasks: tasks/web_setup.yml

    - name: Include verification tasks
      include_tasks: tasks/verify_config.yml

Salve o arquivo e execute o playbook:

ansible-playbook playbook.yml -i inventory

Você deverá ver as tarefas modulares sendo executadas:

PLAY [Configure Web Servers] ***************************************************

TASK [Import web server setup tasks] *******************************************
imported: /home/labex/project/ansible_patterns/tasks/web_setup.yml

TASK [Install the httpd package] ***********************************************
changed: [web1.example.com]
changed: [web2.example.com]

TASK [Include verification tasks] **********************************************
included: /home/labex/project/ansible_patterns/tasks/verify_config.yml for web1.example.com, web2.example.com

TASK [Display a verification message] ******************************************
ok: [web1.example.com] => {
    "msg": "Configuration tasks applied to web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "Configuration tasks applied to web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Observe como a saída indica claramente quando as tarefas estão sendo importadas e incluídas de seus respectivos arquivos. Essa abordagem modular torna sua automação mais limpa e fácil de manter.

Compondo um Fluxo de Trabalho com import_playbook

Nesta etapa, você aprenderá a orquestrar playbooks inteiros para formar um fluxo de trabalho complexo usando import_playbook. Enquanto import_tasks e include_tasks servem para reutilizar listas de tarefas dentro de um único play, import_playbook opera em um nível mais alto. Ele permite que você crie um playbook mestre que executa outros playbooks autônomos em uma ordem específica. Esta é a maneira padrão de gerenciar automação em larga escala, como o provisionamento de uma pilha de aplicativos inteira.

Primeiro, vamos garantir que estamos no diretório correto e organizar nosso projeto para esta nova estrutura:

cd ~/project/ansible_patterns

É uma boa prática armazenar playbooks individuais e de componentes em um subdiretório dedicado. Vamos criar um diretório chamado playbooks:

mkdir playbooks

Agora, mova o playbook que criamos na última etapa, que configura servidores web, para este novo diretório. Renomeá-lo para ser mais descritivo também é uma boa ideia:

mv playbook.yml playbooks/web_configure.yml

No entanto, como movemos o playbook para um subdiretório, precisamos atualizar os caminhos relativos para os arquivos de tarefas. Os arquivos de tarefas ainda estão no diretório tasks/ em relação ao diretório principal do projeto, portanto, precisamos ajustar os caminhos:

nano playbooks/web_configure.yml

Atualize os caminhos no playbook para usar ../tasks/ em vez de tasks/:

---
- name: Configure Web Servers
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Import web server setup tasks
      import_tasks: ../tasks/web_setup.yml

    - name: Include verification tasks
      include_tasks: ../tasks/verify_config.yml

Salve e saia do editor.

Vamos testar o playbook corrigido para garantir que os caminhos estejam funcionando corretamente:

ansible-playbook playbooks/web_configure.yml -i inventory

Você deverá ver o playbook ser executado com sucesso com os caminhos corrigidos.

Em seguida, crie um novo playbook separado para configurar seus servidores de banco de dados. Este playbook direcionará o grupo dbservers e instalará o pacote mariadb:

nano playbooks/db_setup.yml

Adicione o seguinte conteúdo ao arquivo. Este é um play completo e independente:

---
- name: Configure Database Servers
  hosts: dbservers
  gather_facts: false
  tasks:
    - name: Install mariadb package
      ansible.builtin.dnf:
        name: mariadb
        state: present
      become: true

    - name: Display a confirmation message
      ansible.builtin.debug:
        msg: "Database server {{ inventory_hostname }} configured."

Salve e saia do editor. Agora você tem dois playbooks de componentes: um para servidores web e um para servidores de banco de dados.

Finalmente, crie um playbook "principal" de nível superior. Este arquivo não conterá hosts ou tarefas por si só. Sua única função é importar os outros playbooks na ordem correta para definir o fluxo de trabalho geral:

nano main.yml

Adicione o seguinte conteúdo. Isso cria um fluxo de trabalho que primeiro configura os servidores web e depois configura os servidores de banco de dados:

---
- name: Import the web server configuration play
  import_playbook: playbooks/web_configure.yml

- name: Import the database server configuration play
  import_playbook: playbooks/db_setup.yml

Salve e saia do nano. Agora você está pronto para executar todo o seu fluxo de trabalho executando o playbook main.yml:

ansible-playbook main.yml -i inventory

A saída mostrará ambos os playbooks sendo executados em sequência:

PLAY [Configure Web Servers] ***************************************************

TASK [Import web server setup tasks] *******************************************
imported: /home/labex/project/ansible_patterns/playbooks/../tasks/web_setup.yml

TASK [Install the httpd package] ***********************************************
ok: [web1.example.com]
ok: [web2.example.com]

TASK [Include verification tasks] **********************************************
included: /home/labex/project/ansible_patterns/playbooks/../tasks/verify_config.yml for web1.example.com, web2.example.com

TASK [Display a verification message] ******************************************
ok: [web1.example.com] => {
    "msg": "Configuration tasks applied to web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "Configuration tasks applied to web2.example.com"
}

PLAY [Configure Database Servers] **********************************************

TASK [Install mariadb package] *************************************************
changed: [db1.lab.net]
changed: [db2.lab.net]

TASK [Display a confirmation message] ******************************************
ok: [db1.lab.net] => {
    "msg": "Database server db1.lab.net configured."
}
ok: [db2.lab.net] => {
    "msg": "Database server db2.lab.net configured."
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.example.com           : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

A saída mostra claramente dois plays separados sendo executados em sequência, demonstrando como import_playbook compõe efetivamente um fluxo de trabalho maior a partir de partes menores e gerenciáveis.

Executando e Verificando o Playbook Modular Completo

Nesta etapa final, você executará o fluxo de trabalho modular completo que construiu e, mais importante, aprenderá a verificar se a automação atingiu o estado desejado nos sistemas de destino. Uma execução de playbook bem-sucedida é boa, mas confirmar o resultado é essencial para uma automação confiável.

Primeiro, certifique-se de estar no diretório principal do projeto:

cd ~/project/ansible_patterns

Antes de executar o playbook final, vamos visualizar a estrutura completa do projeto que você criou. O comando tree é excelente para isso. Se ele não estiver instalado, você pode adicioná-lo com dnf:

sudo dnf install -y tree
tree .

Você deverá ver uma estrutura como esta:

.
├── inventory
├── main.yml
├── playbooks
│   ├── db_setup.yml
│   └── web_configure.yml
└── tasks
    ├── verify_config.yml
    └── web_setup.yml

2 directories, 6 files

Essa estrutura, com um ponto de entrada principal (main.yml), arquivos de playbook separados e arquivos de tarefas reutilizáveis, é uma maneira escalável e mantenível de gerenciar projetos Ansible.

Agora, execute todo o fluxo de trabalho executando seu playbook main.yml de nível superior:

ansible-playbook main.yml -i inventory

Após a conclusão bem-sucedida do playbook, o próximo passo crucial é a verificação. Você precisa confirmar que o sistema está no estado pretendido. Nosso playbook foi projetado para instalar o pacote httpd em servidores web e o pacote mariadb em servidores de banco de dados. Como todas as tarefas neste laboratório são executadas em sua máquina local, podemos verificar sua instalação diretamente usando o comando rpm.

Primeiro, verifique se o pacote httpd foi instalado como parte da configuração do servidor web:

rpm -q httpd

Você deverá ver uma saída confirmando que o pacote está instalado:

httpd-2.4.xx-x.el9.x86_64

Em seguida, verifique a instalação do pacote mariadb da configuração do servidor de banco de dados:

rpm -q mariadb

Da mesma forma, você deverá ver a confirmação de que o mariadb está instalado:

mariadb-10.5.xx-x.el9.x86_64

Ver os nomes dos pacotes na saída confirma que seu playbook Ansible configurou o sistema com sucesso conforme pretendido. Agora você construiu, executou e verificou com sucesso um projeto Ansible modular do início ao fim.

Resumo

Neste laboratório, você aprendeu técnicas essenciais para estruturar playbooks Ansible complexos no RHEL. Você começou com os fundamentos da seleção de hosts, usando nomes de grupos básicos, curingas, exclusões e operadores lógicos para direcionar com precisão os nós definidos em um arquivo de inventário. O foco então mudou para a modularização, onde você praticou a divisão de plays grandes em componentes mais gerenciáveis e reutilizáveis, usando tanto include_tasks para inclusão dinâmica quanto import_tasks para inclusão estática.

Com base nessas habilidades, você aprendeu a compor um fluxo de trabalho completo e de múltiplos estágios, conectando playbooks individuais com import_playbook. O processo prático envolveu a instalação do Ansible, a criação de uma estrutura de projeto e a refatoração progressiva de um playbook simples em uma estrutura sofisticada de múltiplos arquivos. O laboratório culminou na execução do playbook composto final e na verificação de que todo o fluxo de trabalho automatizado foi executado com sucesso contra os hosts corretamente direcionados, demonstrando uma abordagem organizada e escalável para automação.