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

CONT.

👈 ANTERIOR: BECOME

O que fazer quando é necessário repetir determinada ação mais de uma vez ??? Automatizar, é claro, você diria … Tudo bem, já entendi que pegou o espírito da coisa 🙃 E fico deveras feliz por isso, diga-se de passagem! Mas, o que eu quis dizer é como fazê-lo em pormenores? Como executar valendo-se da lógica da programação? Há duas respostas válidas, contudo uma é mais inteligente e menos trabalhosa, e a outra é mais lenta (para não desmerecê-la 😬 haha) e braçal. A primeira delas seria utilizar um monte de IF’s e ELSE’s para cada entrada, interação ou condição. Em contrapartida, a segunda seria criar um laço (daí seu nome loop, em inglês) com uma variável “contador” interna. Essa controlaria a execução do mesmo em N vezes, e até que a condição X fosse satisfeita. Resultado? Um código mais bonito, limpo, enxuto, com menos linhas, e mais otimizado.

No contexto do Ansible, ou de qualquer outra ferramenta de automação, tais ações “repetitivas” abrangeriam desde adicionar vários usuários, replicar uma configuração em um pool de máquinas, até mudar a propriedade/permissão (ou dono) de uma série de arquivos e pastas, por exemplo. Para tal o ansible nos fornece duas palavras-chave: loop e with_<lookup> (lookup em português pode ser traduzido como busca, pesquisa, chave ou valor).

Antes de expor e debater os tópicos a seguir, cabe aqui, e quero destacar algumas notas acerca do recurso loop :

  • Foi introduzido a partir do Ansible 2.5
  • Está presente em todas as versões sucessoras (2.5+)
  • Ainda não substitui completamente o with_<lookup>, embora a ideia seja exatamente essa no futuro.
  • Na maioria dos casos recomenda-se, e satisfaz muito bem, o uso do loop . Somente em situações extremamente específicas o with_<lookup> dá pleno suporte, o que acaba “vencendo” seu concorrente amigo, e portanto o uso dele é o único meio viável para solucionar.
  • Não está obsoleta ou tão pouco depreciada a sintaxe do with_lookup, continuando assim totalmente válida, embora isso possa mudar no médio-longo prazo.
  • O loop encontra-se em constante mudança visando principalmente seu aperfeiçoamento. Para atualizações e alterações do mesmo, por favor consultar o CHANGELOG https://github.com/ansible/ansible/tree/devel/changelogs

02-a. Comparativo entre loop e with_*

As únicas palavras-chave capazes de complementar o with_ são aquelas que estão disponíveis em https://docs.ansible.com/ansible/latest/plugins/lookup.html#lookup-plugins Ou seja, na prática, qualquer uma delas quando utilizadas correspondem ao asterisco (presente no título desta sessão). Para refrescar a memória, * é conhecido também como o caractere-curinga na computação, especificamente quando falamos em interpretadores de comandos nos sistemas operacionais.

A palavra loop é o mesmo que usar a expressão with_list, sendo a primeira uma melhor escolha para loops que não possuem tanta complexidade. Porém, o loop não aceita como entrada nenhuma ‘string’. Veja mais em https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#query-vs-lookup

Genericamente, e simplificando bastante, qualquer with_* pode facilmente ser trocado por loop. Claro que salva as devidas proporções e ressalvas, todas essas descritas aqui https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#migrating-to-loop

02-b. Loops Tradicionais (ou padrão/modelos)

! ! ! Interagindo sobre uma lista simples ! ! !

Se uma task precisa ser executada mais de uma vez em determinado host, recomenda-se usar como padrão o próprio loop, assim este agiria de acordo e sobre uma lista de strings, por exemplo. Em outras palavras, defina a lista diretamente, de uma vez só e já estando dentro da task em questão:

- name: Add users and give them root powers
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
    group: "wheel"
  loop:
     - victor
     - viktor
     - vitor
     - vicrlda

O código acima ☝ seria equivalente ao de baixo 👇 (só que mais elegante! e com menos tec-tec-tec-tec 🔉💻💬)

- name: Add user vitor
  ansible.builtin.user:
    name: "vitor"
    state: present
    groups: "wheel"

- name: Add user vicrlda
  ansible.builtin.user:
    name: "vicrlda"
    state: present
    groups: "wheel"

.
.

.
.

! ! ! Interagindo sobre uma lista de hashes ! ! !

Caso tenha uma lista de hashes para trabalhar, utilize referências no formato de chave:subchave dentro do loop.

- name: Add multiple users
  ansible.builtin.user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop:
    - { name: 'victor', groups: 'wheel' }
    - { name: 'viktor', groups: 'root' }

! ! ! Interagindo sobre um dictionary ! ! !

Neste caso, simplesmente use o dict2items https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#dict-filter :

- name: Using dict2items
  ansible.builtin.debug:
    msg: "{{ item.key }} - {{ item.value }}"
  loop: "{{ tag_data | dict2items }}"
  vars:
    tag_data:
      Environment: dev
      Application: payment

02-c. Registrando variáveis com um loop

Esse artifício é comumente usado na lógica da programação, em termos genéricos, e desde sempre, remontando ao seu início e concepção. Naturalmente, por tabela, também é observado nas linguagens. Em outras palavras, o que temos é, basicamente um evento partilhado ocorrendo tanto no macro quanto no micro ecossistema. Respeitando, é claro, as particularidades de cada um (limitações, capacidades e sintaxe).

E com os playbooks não poderia ser diferente. Somos capazes de armazenar a saída de um loop dentro de uma variável. Por exemplo,

- name: Register loop output as a variable
  ansible.builtin.shell: "echo {{ item }}"
  loop:
    - "apple"
    - "banana"
  register: echo

Loops que vem logo após a variável registrada, com o intuito de inspecionar eventuais resultados, talvez se assemelhem ao trecho a seguir:

- name: Fail if return code is not 0
  ansible.builtin.fail:
    msg: "The command ({{ item.cmd }}) did not have a 0 return code"
  when: item.rc != 0
  loop: "{{ echo.results }}"

Por padrão, o comportamento da variável durante a execução/interação do loop é sempre armazenar o valor do item atual, indiferente se são números, strings ou caracteres.

- name: Place the result of the current item in the variable
  ansible.builtin.shell: echo "{{ item }}"
  loop:
    - one
    - two
  register: echo
  changed_when: echo.stdout != "one"

02-d. Loops Complexos

! ! ! LISTAS ANINHADAS ! ! !

Aninhado(a), conceitualmente restrito ao universo da programação, implica dizer que um dado elemento está contido em outro elemento. Se preferir mais sinônimos, seria o mesmo que alojado, hospedado, instalado. Isso compreende desde funções, sub-rotinas, até vetores e matrizes. Exemplo: um vetor de tamanho três [0, 1, 2] cujas posições na memória são na verdade ponteiros para outros três vetores distintos. Talvez o mais importante aqui, e que vale destacar, é o tamanho da jurisprudência do primeiro ou o limite imposto ao segundo … ❓❓❓ Certo, certo, eu explico. Vamos imaginar que os sujeitos (elementos) em questão sejam rotinas e sub-rotinas. Então, partindo do pressuposto que apresentei, uma sub-rotina “filha” somente poderia ser chamada para rodar durante o programa única e exclusivamente por sua rotina “mãe”. Caso uma rotina “madrasta”, por exemplo, tentasse invocar essa mesma sub-rotina “filha”, não haveria êxito algum. Isso porque, de antemão, a primeira (madrasta) nunca iria enxergar a segunda (filha) pois ela apenas fica visível para a terceira (mãe). Isso é o que determinados autores chamam de “fazer sentido localmente”, e que é considerada uma forma de encapsulamento.

Ufa! 🥴 Agora sim, voltando ao contexto dos playbooks, e finalizando esse tópico antes de passar para o próximo (…)

Em situações de listas aninhadas recomenda-se o uso de expressões Jinja2 para melhor tratá-las. Como segue abaixo, um loop que combina várias listas:

- name: Give users access to multiple databases
  community.mysql.mysql_user:
    name: "{{ item[0] }}"
    priv: "{{ item[1] }}.*:ALL"
    append_privs: yes
    password: "foo"
  loop: "{{ ['alice', 'bob'] |product(['clientdb', 'employeedb', 'providerdb'])|list }}"

! ! ! INVENTÁRIOS ! ! !

Mais uma opção para uso dos loops no Ansible ocorre quando se trata de arquivos de inventário. Isso nos possibilita varrê-los ou construí-los sem sair do laço. E melhor, não é obrigatório usar todo o arquivo e sim apenas uma parte dele, caso prefira assim. Para tal, basta informar a palavra-chave loop com a variável ansible_play_batch ou a variável groups

- name: Show all the hosts in the inventory
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ groups['all'] }}"

- name: Show all the hosts in the current play
  ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ ansible_play_batch }}"

02-e. Adicionando controles aos loops

(A partir da versão 2.1+)

(Palavra-chave: loop_control)

! ! ! LIMITANDO A TELA/SAÍDA DO LOOP ! ! !

Para estruturas de dados muito complexas, executar um laço pode ser algo extenso, e consequentemente, a saída gerada (console) se torna igualmente enorme. Evite dores de cabeça e olhos cansados utilizando a diretriz label com loop_control :

- name: Create servers
  digital_ocean:
    name: "{{ item.name }}"
    state: present
  loop:
    - name: server1
      disks: 3gb
      ram: 15Gb
      network:
        nic01: 100Gb
        nic02: 10Gb
        ...
  loop_control:
    label: "{{ item.name }}"

! ! ! Pausa dentro de um loop ! ! !

Controle o tempo, aqui medido em segundos, entre a execução de cada item dentro de uma loop task (tarefa repetitiva). Use a diretriz pause com a palavra loop_control

# main.yml
- name: Create servers, pause 3s before creating next
  community.digitalocean.digital_ocean:
    name: "{{ item }}"
    state: present
  loop:
    - server1
    - server2
  loop_control:
    pause: 3

! ! ! Definindo nomes internos e externos ao loop com loop_var ! ! !

Você pode aninhar duas tarefas de loop usando include_tasks. No entanto, por padrão, Ansible define o item de variável de loop para cada loop. Isso significa que o loop interno aninhado sobrescreverá o valor do item do loop externo. Você pode especificar o nome da variável para cada loop usando loop_var com loop_control:

# main.yml
- include_tasks: inner.yml
  loop:
    - 1
     - 2
     - 3
  loop_control:
    loop_var: outer_item

# inner.yml
- name: Print outer and inner items
  ansible.builtin.debug:
    msg: "outer item={{ outer_item }} inner item={{ item }}"
  loop:
    - a
     - b
     - c

CONTINUA (…)

Próximo post >>> WHERE TASKS RUN?

REFERÊNCIAS:

https://docs.ansible.com/core.html

https://docs.ansible.com/ansible/latest/user_guide/index.html

https://docs.ansible.com/ansible/latest/user_guide/index.html#writing-tasks-plays-and-playbooks

https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#playbooks-loops

ANSIBLE SERIES: h.t.wrt* … tasks, plays e books: como escrevê-los?

* Sigla para: < How To Write >

Recapitulando o que vimos até o momento acerca do Ansible:

  • Teoria
    • DevOps: conceito, história, curiosidades ✅
    • Por que automatizar com ansible? ✅
    • 5 conceitos fundamentais ✅
  • Prática
    • Linha de comando, hello.YML e Ad-Hoc ✅
    • Web console: AWX/TOWER ✅
    • Como executar um JOB graficamente em tempo real ✅

Poxa vida! 😲 Eu diria um bocado não é mesmo? Sendo ou não, o que importa é que a estrada ainda se encontra a nossa frente e aos poucos, devagar, mas com passos firmes vamos progredindo no ROADMAP de um DEVOPS. Portanto, e acho que todos já estão acostumados com as minhas deixas (ou chamados) para iniciar um POST do blog … Hey Ho! Let’s go!

Muito bem jovens, ao que interessa então. Se você já buscou material, ou até mesmo cursou algum tipo de preparatório para o Ansible, as chances do professor ter abordado logo no começo (imediatamente após o briefing/overview) esses itens que vou mostrar é muito grande. Em outras palavras, e simplificando, acredito que tenha sido dessa forma: 1. o que é ansible? / 2. definições e termos / 3. prompt, módulos e comandos ad-hoc / para finalmente chegar em 4. como escrever playbooks. Por último é que eles mostram o AWX, boas práticas, roles, hosts windows, network e paramiko, etc. Mas e daí Victor? Eu sei, não há nada de errado com isso. Cada um ministra e monta seu plano de aulas da maneira que achar melhor aos estudantes. E tudo bem, ok. Mas no nosso caso, vocês e eu, quis inverter um pouco as coisas e adiantar logo a parte gráfica do ansible, ou seja, o tower/awx. Na verdade o meu intuito era “cativar” a todos apresentando de cara as duas faces do mesmo: linha de comando e interface web. Assim cada pessoa poderia escolher a melhor forma de trabalhar, de acordo com seu gosto e afinidade. Gosta de terminal? Ótimo, vá de CLI e seja feliz! Não, prefiro botões, gráficos e menus. Maravilha, pegue a GUI e curta a vida! O que quero dizer aqui é não existe método certo ou errado, melhor ou pior. Não, pelo contrário, há vantagens e desvantagens em cada um deles. Cabe tão somente a você pesar os prós e contras e escolher sua forma de operar. Lembrando sempre que é possível combiná-las tá? Por exemplo, tal tarefa é mais rápido e comodo se fizer via bash, já aquela outra é necessário maior controle de usuários e permissões, então farei por meio da web. Entenderam? Ou enrolei demais? 🤔

POOL DE INFORMAÇÕES

https://docs.ansible.com/

Desde sempre admirei empresas de tecnologia, bem antes de entrar na faculdade. Talvez por isso que enveredei para este ramo. Google, Microsoft, IBM, Facebook, e tantas outras (ora gigantes, ora um pouco mais modestas) foram alguns exemplos de modelo a perseguir, almejar e quem sabe um dia entrar no time… Uma vez imerso nesse grande oceano, chamado TI, passei a ter bastante contato com Linux e suas distros. Além de ter curtido, comecei a ficar de olho, meio que acompanhar notícias e bastidores das empresas idealizadoras/responsáveis. Foi assim que fiquei maravilhado com todo o trabalho por trás de cada uma dessas distribuições. Digo, não apenas o resultado final: o lançamento periódico de novas versões. Mas sim o suporte, divulgação, comunidades, fóruns oficiais, documentação, e afins. Também vale um destaque especial, o principal “cartão de visita” dessas empresas: os websites. Falo sério, você já acessou a Canonical, Red Hat, Suse e System76 ?? É verdadeiro deleite, e de encher os olhos, devido a tanto primor e cuidado com o design e layout. São muito bonitos, sem exceção!!!

E como prova disso, trago a seguir uma captura de tela da página INDEX da documentação do ansible. Podemos dizer que ela é uma espécie de hub central, uma vitrine, para orientar sobre tudo que o mesmo é capaz, e como fazer. Confiram:

Figura 01. Docs INDEX page

Acima devidamente circulado está o nosso foco: a documentação base, ou seja, relacionada ao núcleo, chamado de ansible-core. Pelas próximas semanas vamos nos debruçar sobre a sintaxe, lógica, melhores práticas, organização de pastas e arquivos, reuso de código, otimização, e muito mais. Para isso, irei sempre pontuar já no título do POST em questão, o que vamos aprender nele. Ex: loops, variáveis, templates, roles, e assim por diante.

Qualquer dúvida ou lacuna que porventura tenha deixado, acessem o link e vão direto na fonte para saná-las. Até porque não pretendo abordar absolutamente tudo que está lá, ok? Senão o que estaria fazendo é um simples CTRL-C + CTRL-V do referido conteúdo. E isso não quero, e tão é algo ético. Resumindo: antes de ler aqui, recomendo dar uma estudada pela oficial, por favor. Embora prometo fazer o meu melhor para se aproximar ao máximo dos mestres da Red Hat 🎩

01-a. BECOME: acesso, usuários e permissões

Ansible utiliza meios de escalonamento de privilégios pré-existentes na máquina remota. Isso é o que eles chamam de become. Ele seria o equivalente ao su, sudo, runas e similares. O objetivo é tanto permitir quanto executar tarefas locais nos alvos (nodes), usando privilégios de root ou qualquer outra permissão já criada dentro do nó. O efeito prático disso é torna-se outro usuário¹ dentro do host² em questão, sendo o primeiro completamente diferente daquele que usou para entrar no segundo.

i ) Usando o become

É possível controlá-lo através de tasks, plays, variáveis de conexão ou linha de comando. Em caso de duas ou mais ao mesmo tempo (sim, isso é válido e às vezes comum) consulte atentamente as regras de precedência para as mesmas. Leia mais aqui: https://docs.ansible.com/ansible/latest/reference_appendices/general_precedence.html#general-precedence-rules

! ! ! Diretivas do become ! ! !

Estabeleça como definir o become, controlando o mesmo à nível de task ou play. Variáveis e diretivas são independentes entre si. Por exemplo, definir become_user não é a mesma coisa que “só” become.

become: defina para 'yes' e ative o escalonamento de privilégios
become_user : defina o usuário com os privilégios desejados. ou seja, a credencial que você pretende se tornar e não o usuário que fez login.
become_method : (à nível de play ou task) sobrescreve o método padrão definido em ansible.cfg
become_flags :(à nível de play ou task) permite o uso de flags específicas para uma tarefa ou play. bastante comum quando se quer mudar o usuário para 'nobody' em casos de 'shell' setado para 'nologin'

Por exemplo,

Gerenciar um serviço (daemon) que requer privilégios de root … Em um cenário onde o usuário conectado não é root … Neste caso, utilizamos o valor padrão de become_user, que é justamente ‘root’

- name: Ensure the httpd service is running
  service:
    name: httpd
    state: started
  become: yes

Executar algo como ‘nobody’ quando o shell (terminal) está configurado para ‘nologin’

- name: Run a command as nobody
  command: somecommand
  become: yes
  become_method: su
  become_user: nobody
  become_flags: '-s /bin/sh'

To specify a password for sudo, run ansible-playbook with --ask-become-pass (-K for short). If you run a playbook utilizing become and the playbook seems to hang, most likely it is stuck at the privilege escalation prompt. Stop it with CTRL-c, then execute the playbook with -K and the appropriate password.

! ! ! Variáveis de conexão ! ! !

Nós e grupos suportam diversas, e distintas, opções ‘become’. Em cada gerenciável existe a possibilidade de defini-las em um arquivo inventário, ou usá-las como se fossem variáveis comuns.

São elas,

ansible_become: em termos de finalidade, praticamente uma cópia da diretiva become. ou seja, define se o escalonamento de privilégio será utilizado ou não.
ansible_become_method: escolhe qual forma de privilégio será usada.
ansible_become_user: dita qual usuário você irá se tornar via método de escalonamento. não é a mesma coisa que ansible_become: yes
ansible_become_password: senha do usuário citado anteriormente. é possível, mas não aconselhável, passar segredos em texto plano. aprenda formas mais seguras em https://docs.ansible.com/ansible/latest/user_guide/vault.html#playbooks-vault
! ! ! Opções na linha de comando ! ! !
--ask-become-pass OU -K
Solicita a senha "privilegiada" ... É usada para todos os hosts
--become OU -b
Executa as instruções com o become ... Não necessariamente haverá sempre uma senha
--become-method=BECOME_METHOD
Método utilizado ... Padrão = SUDO ... Outros [su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl]
--become-user=BECOME_USER
Rode os comandos como este 'usuário' ... Padrão = ROOT ... Não é o mesmo que --become ou -b

ii ) RISCOS E LIMITAÇÕES DO BECOME

Intuitivo mas não perfeito, o become apresenta certas limitações em sua capacidade. O que gera algumas questões “interessantes” a serem avaliadas, para que não se transformem em “desagradáveis”. Todas elas podem ser estudadas no link a seguir:

https://docs.ansible.com/ansible/latest/user_guide/become.html#risks-and-limitations-of-become

  • Riscos de se tornar um usuário sem privilégios
  • Não compatível com todos os plug-ins de conexão
  • Apenas um método pode ser habilitado por host
  • O escalonamento de privilégios deve ser generalista
  • Talvez não seja possível acessar variáveis de ambiente preenchidas por pamd_systemd

CONTINUA (…)

Próximo post >>> LOOPS

REFERÊNCIAS:

https://docs.ansible.com/

https://docs.ansible.com/core.html

https://docs.ansible.com/ansible/latest/

https://docs.ansible.com/ansible/latest/user_guide/index.html

https://docs.ansible.com/ansible/latest/user_guide/index.html#writing-tasks-plays-and-playbooks

https://docs.ansible.com/ansible/latest/user_guide/become.html#become