ANSIBLE SERIES: h.t.wrt* … tasks, plays e books: conditionals

CONT.

👈 ANTERIOR: WHERE TASKS RUN ?

Condicionais, em bom português, são aquelas famosas palavrinhas que aprendemos (lógica da programação) com o intuito de:

  • parar momentaneamente o fluxo do programa;
  • desviar a sequência ou conteúdo de variável para um bloco de código que faz checagem;
  • chamar uma função ou sub-rotina;
  • capturar exceções e tratá-las;
  • exibir informações ou mensagens ao usuário;
  • dentre outras tarefas.

Pois bem, alguns exemplos dessas palavras que normalmente usamos para tais propósitos: SE, SENÃO, ENTÃO, ENQUANTO, PARA, E, OU, NÃO (!not) …

No casos dos playbooks, nossos objetivos vão desde: (a) executar diferentes tasks que dependem do valor de um fato (dado de um host remoto), variável ou resultado anterior (b) atribuir valor a uma variável baseando-se em outra variável precedente (c) definir e criar novos grupos de hosts em cima de alguns critérios de correspondência.

Uma pequena ressalva da documentação oficial:

Ansible usa testes e filtros Jinja2 em condicionais. O Ansible oferece suporte a todos os testes e filtros padrão e também adiciona alguns exclusivos

https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#playbooks-conditionals

E uma pequena nota de rodapé:

Existem muitas opções para controlar o fluxo de execução no Ansible. Você pode encontrar mais exemplos de condicionais com suporte em https://jinja.palletsprojects.com/en/master/templates/#comparisons

https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#playbooks-conditionals

Devido ao sumário de tamanho “razoável” que esta seção apresenta na wiki oficial, tentarei ao máximo fazer breves explanações dos códigos e talvez pule algumas delas, seja porque são muito raras de utilizar ou porque fogem muito dos nossos objetivos, ok?

Figura 01. Sumário da seção (docs.ansible.com)

04-a. Condicionais básicas com WHEN

A unidade mais básica para uma condicional, chamada por alguns de “declaração mais simples”, é essencialmente aplicada a uma única tarefa por vez. Na prática, isso quer dizer que, quando se cria uma tarefa, seguida de uma declaração when, um teste será executado. Essa cláusula when na verdade é uma expressão Jinja2 bruta e sem chaves duplas. Quando se roda uma task ou playbook o que acontece é que o Ansible valida esse teste para todos os hosts remotos. Se qualquer um deles “passar na prova”, o valor retornado é true, e então a task é executada. Para ilustrar, um exemplo:

tasks:
  - name: Configure SELinux to start mysql on any port
    ansible.posix.seboolean:
      name: mysql_connect_any
      state: true
      persistent: yes
    when: ansible_selinux.status == "enabled"
    # all variables can be used directly in conditionals without double curly braces

! ! ! BASEADAS EM ANSIBLE_FACTS ! ! !

Às vezes, ao invés de executar tarefas, simplesmente queremos o oposto. Em outras palavras, desejamos pulá-las e passar adiante. Isso pode ser feito através dos fatos, ou melhor, por meio dos atributos (dados) de um node específico. IP, SO, filesystem, status, são apenas alguns para citar.

Com as condicionais baseadas em fatos, é possível:

  • instalar pacotes somente para determinadas versões ou famílias de um sistema operacional
  • pular configurações de firewall para hosts internos à rede
  • fazer uma limpeza em sistemas de arquivos muito cheios

Veja quais fatos estão disponíveis em cada host, acrescentando uma simples tarefa do tipo debug:

- name: Show facts available on the system
  ansible.builtin.debug:
    var: ansible_facts

Exemplo 1 x 1: uma condicional para um fato

tasks:
  - name: Shut down Debian flavored systems
    ansible.builtin.command: /sbin/shutdown -t now
    when: ansible_facts['os_family'] == "Debian"

Exemplo N x N: múltiplas condicionais para múltiplos fatos

tasks:
  - name: Shut down CentOS 6 and Debian 7 systems
    ansible.builtin.command: /sbin/shutdown -t now
    when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
          (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")

Outra forma, um pouco mais abreviada:

tasks:
  - name: Shut down CentOS 6 systems
    ansible.builtin.command: /sbin/shutdown -t now
    when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "6"

https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#commonly-used-facts

! ! ! BASEADAS EM VARIÁVEIS ! ! !

Você também pode criar condicionais com base em variáveis definidas nos playbooks ou inventários. Como as condicionais requerem entrada booleana (um teste deve ser avaliado como True para acionar a condição), você deve aplicar o | Filtro booleano para variáveis não booleanas, como variáveis de string com conteúdo como ‘sim’, ‘ativado’, ‘1’ ou ‘verdadeiro’.

https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#conditionals-based-on-variables

Defina suas variáveis exatamente assim:

vars:
  epic: true
  monumental: "yes"

Agora execute o código a seguir:

tasks:
    - name: Run the command if "epic" or "monumental" is true
      ansible.builtin.shell: echo "This certainly is epic!"
      when: epic or monumental | bool

    - name: Run the command if "epic" is false
      ansible.builtin.shell: echo "This certainly isn't epic!"
      when: not epic

Verá que o Ansible roda uma delas e pula a outra!

! ! ! BASEADAS EM LOOPS ! ! !

Processa a condição separadamente para cada item presente no laço. Isso ocorre para que você possa executar a tarefa em alguns elementos e ignorá-la em outros. Por exemplo:

tasks:
    - name: Run with items greater than 5
      ansible.builtin.command: echo {{ item }}
      loop: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5

Sobre uma lista:

- name: Skip the whole task when a loop variable is undefined
  ansible.builtin.command: echo {{ item }}
  loop: "{{ mylist|default([]) }}"
  when: item > 5

Sobre um dicionário:

- name: The same as above using a dict
  ansible.builtin.command: echo {{ item.key }}
  loop: "{{ query('dict', mydict|default({})) }}"
  when: item.value > 5

CONTINUA (…)

Próximo post >>> Blocks

REFERÊNCIAS:

https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html#playbooks-conditionals

ANSIBLE SERIES: h.t.wrt* … tasks, plays e books: where tasks run ???

CONT.

👈 ANTERIOR: LOOPS

Ao executar um playbook, o comportamento que se espera do Ansible é:

  1. antes, reunir todos os fatos referentes aos nodes ( gather_facts )
  2. depois, rodar as tarefas especificadas ( playbook ) somente nas máquinas-alvo que estão expressas ou de acordo com a linha “hosts” , presente no próprio main.yml, test.yml por exemplo.

Dito isso, quatro caminhos estão abertos ao admin responsável (você, eu). São eles:

  • delegar tarefas a uma máquina em particular
  • delegar tarefas a um grupo específico
  • delegar fatos a uma série de hosts ou grupos
  • executar um playbook inteiro localmente

03-a. Tasks que NÃO podem ser delegadas !!!

A execução de algumas pouquíssimas tarefas está restrita apenas ao controlador, e essas sempre serão rodadas única e exclusivamente nele, não importando o que você faça. include , add_hosts , e debug integram essa lista, não podendo de forma nenhuma serem delegadas a terceiros.

03-b. Delegando tasks . . .

Agora sim meus caros colegas 😉, tarefas que podem ser delegadas para nodes remotos. Trecho a seguir retirado da documentação oficial:

Se você deseja executar uma tarefa em um host com referência a outros hosts, use a palavra-chave delegate_to em uma tarefa. Isso é ideal para gerenciar nós em um pool com carga balanceada ou para controlar janelas de interrupção. Você pode usar a delegação com a palavra-chave serial para controlar o número de hosts em execução ao mesmo tempo

https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#delegating-tasks
---
- hosts: webservers
  serial: 5

  tasks:
    - name: Take out of load balancer pool
      ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

    - name: Actual steps would go here
      ansible.builtin.yum:
        name: acme-web-stack
        state: latest

    - name: Add back to load balancer pool
      ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

** 127.0.0.1 : significa que será rodado na máquina corrente, ou seja, localmente naquela que executa o Ansible!

03-c. Delegando fatos . . .

Delegar tarefas do Ansible é como delegar tarefas no mundo real – seus mantimentos pertencem a você, mesmo que outra pessoa os entregue em sua casa. Da mesma forma, quaisquer fatos reunidos por uma tarefa delegada são atribuídos por padrão ao inventory_hostname (o host atual), não ao host que produziu os fatos (o delegado ao host). Para atribuir fatos coletados ao host delegado em vez do host atual, defina delegate_facts como true.

https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#delegating-facts
---
- hosts: app_servers

  tasks:
    - name: Gather facts from db servers
      ansible.builtin.setup:
      delegate_to: "{{ item }}"
      delegate_facts: true
      loop: "{{ groups['dbservers'] }}"

Esta tarefa reúne fatos para as máquinas no grupo dbservers e atribui os fatos a essas máquinas, mesmo que a play seja direcionado ao grupo app_servers. Desta forma, você pode pesquisar hostvars [‘dbhost1’] [‘ansible_default_ipv4’] [‘endereço’], mesmo que dbservers não tenham feito parte da peça ou deixados de fora usando –limit.

https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#delegating-facts

03-d. Playbooks locais . . .

Pode ser útil usar um playbook localmente em um host remoto, ao invés de conectar por SSH. Isso pode ser usado para garantir a configuração de um sistema, colocando um playbook em um crontab.

https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#local-playbooks
ansible-playbook playbook.yml --connection=local
---
- hosts: 127.0.0.1
  connection: local

CONTINUA (…)

Próximo post >>> Conditionals

REFERÊNCIAS:

https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#playbooks-delegation