Ansible

Automatização simplificada de SOs

Resumo em construção
Voltar

Material complementar:


Conceito:

Ferramenta Agentless para automatizar a gestão (Instalações, configurações, provisionamento, etc) de CD (Continuous Delivery) em diferentes SOs simultaneamente. Entre vários nodes (Nós-Máquinas), tem-se o master node (Controlador), onde o Ansible é usado, informando os demais nodes e respectivas credenciais/configurações. Para isso, recomenda-se SSH ativo nos demais nodes (workers ou Slaves - Controlados). O Ansible possui vários Módulos, que exercem funções específicas. Ansible Automation Controller, contendo o Ansible Tower, é a ferramenta oficial GUI do Ansible, recurso premium (AWX é a versão community).

  • Instalação: sudo apt install ansible
  • Após instalado, gerará em '/etc/ansible':
    • ansible.cfg: Configurações
    • hosts: Inventário, onde serão declarados os nodes
    • roles: Arquivo com configurações de tasks, templates, handlers e defaults (Variáveis padrão) pré-configuradas. Diretório composto dos diretórios citados e, dentro desses, arquivos .yml dos componentes
  • VSCode extensão oficial Ansible

Comandos:

    Sintaxes
  • Executar módulo em um host: ansible -m [module] -a [argumentos] [host]
  • Executar playbook: ansible-playbook -i [inventory_file] [playbook_file]
  • Listar hosts em determinado inventário: ansible-inventory -i [inventory_file] --list

  • Exemplos
  • Executar tarefas em host específico: ansible-playbook -i ansible-inventory -l hostname -vvv ansible-playbook.yml
  • Executar tarefas em playbook com tag específica: ansible-playbook -i ansible-inventory -l hostname -vvv –tags=”tag_name” ansible-playbook.yml
  • Pular tarefas em playbook com tag específica: ansible-playbook -i ansible-inventory -l hostname -vvv –skip-tags=”tag_name” ansible-playbook.yml
  • Executar tarefas em playbook a partir de tarefa específica: ansible-playbook -i ansible-inventory -l hostname -vvv –start-at-task=”task_name” ansible-playbook.yml

Playbook:

Arquivo YAML (nomePlaybook.yml) com informações para a gestão do Ansible nos nodes. Após criado o arquivo, executa-se o comando 'ansible-playbook nomePlaybook.yml'. Pode-se colocar vários códigos de playbooks dentro de um mesmo arquivo de playbook (O exemplo abaixo possui somente 1 código). O código abaixo instalará, via módulo 'package', o nginx nos workers (Após executado, pode-se testar com 'curl ipWorker'):


- name: Instalar o nginx
    hosts: distros
    become: yes #executar como sudo
    tasks:
    - name: Instalar servidor web
        package:
        name: nginx
        state: present #present: Instalado, absent: Não deseja no servidor, latest: Instalar última versão

Playbook para transferir arquivos:

Via módulo copy, copiar arquivo do master para Worker(s). Criar arquivo 'teste.txt' no master node e, após isso, criar arquivo 'playbook1.yml', com o seguinte conteúdo abaixo. Após criado o arquivo, executá-lo com 'ansible playbook playbook1.yml'. Pode-se conferir se o arquivo encontra-se nos workers com 'ansible all -m shell -a "cat /etc/teste.txt" '.


- name: Playbook de transferir arquivos
hosts: all
tasks:
- name: Atualizar arquivo 'teste.txt'
become: true
copy:
src: teste.txt
dest: /etc/teste.txt

Playbook para criar usuários:

Via módulo user. Criar arquivo playbook2.yml, com o seguinte conteúdo abaixo. Após criado o arquivo, executá-lo com 'ansible playbook playbook2.yml'. Pode-se verificar se o usuário fora criado com 'ansible all -m shell -a "getent passwd ubsocial" '.


- name: Playbook para criação de usuários
hosts: all
vars:
- server_name: servidor01
- user_name: ubsocial
- conf_file: /opt/app/app.conf
tasks:
- name: Criando o usuário
become: true
user:
name: {{ user_name }}

Playbook com variáveis externas:

O playbook anterior possuia variáveis internas. Para utilizar variáveis externas, criaremos arquivo 'config.yml', com o seguinte conteúdo abaixo. Pode-se, também, criar variáveis diretamente via comando, exemplo 'ansible-playbook nomePlaybook.yml -e username=ubsocial'.


config.yml:
server_name: servidor01
username: ubsocial

playbook2b.yml:
- name: Playbook 2 para criação de usuários
hosts: all
vars_files:
- config.yml
tasks:
- name: Criando o usuário
become: true
user:
name: {{ username }}

Facts:

Informações coletadas pelo Ansible quando executado em workers. Com isso, pode-se refinar comandos (Ex: Verificar se o worker é Ubuntu e, se sim, execute o 'apt update').


ansible debian -m setup #Mostrará informações dos hosts do grupo
ansible debian -m setup -a "filter=ansible_distribution" #Com filtragem de informações

Templates:

  • Job template: Conjunto definido pelo arquivo de inventário, projeto de aplicação e playbook relacionada;
  • Workflow template: Agrupamento de job templates, organizados de forma ordenada, possibilitando, com isso, execução de vários templates, ordenadamente, via único workflow template.

Loops:

Utilizar loops em playbooks para repetição de tasks (Playbook 3b abaixo). Após criado o arquivo, executá-lo com 'ansible-playbook playbook3.yml'. Pode-se verificar a instalação dos pacotes no CentOS com 'ansible centos -m shell -a "rpm -qa | egrep 'wget|vim|tree'" '. No Debian, pode-se verificar com 'ansible debian -m shell -a "dpkg -l | egrep 'wget|git|figlet'" '.


playbook3.yml:
- name: Instalar pacotes no CentOS
  hosts: centos
  vars:
    - packages:
        - wget
        - vim
        - tree
  tasks:
    - name: Instalar pacotes
      become: true
      yum:
        name: "{{ packages }}"

playbook3b.yml:
- name: Instalar pacotes no Debian
  hosts: debian
  tasks:
    - name: Instalar pacotes Debian
      become: true
      apt:
        name: "{{ item }}"
      loop:
        - wget
        - git
        - figlet

Condicionais (when):

Pode-se utilizar condicionais 'when' para aplicar filtros em playbooks, conforme abaixo.


- name: Instalar pacotes
  hosts: all
  tasks:
    - name: Instalar pacotes
      become: true
      apt:
        name: "{{ item }}"
      loop:
        - wget
        - git
        - figlet
      when: ansible_distribution == "Debian"


Exemplos de playbooks:


frontend.yml:
---
- name: Implantar frontend
  hosts: frontend
  become: yes
  vars:
    frontend_deploy_dest: /srv/frontend/
  tasks:
  - name: Instalar servidor web Nginx
    package:
      name: nginx
      state: present
  - name: Copiar os artefatos de frontend
    copy:
      src: app-src/public/
      dest: "{{ frontend_deploy_dest }}"
      setype: httpd_sys_content_t
    notify:
    - Reiniciar o serviço do Nginx
  - name: Serviço do Nginx deve estar habilitado
    service:
      name: nginx
      enabled: yes
      state: started
  - name: Configura o Nginx para o frontend
    template: #Pegar arquivo de template e enviá-lo ao destino
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
      owner: root
      group: root
    notify:
    - Reiniciar o serviço do Nginx
  handlers:
  - name: Reiniciar o serviço do Nginx #O name do handlers precisa ser idêntico ao notify
    service:
      name: nginx
      state: restarted

backend.yml:
---
- name: Implantar backend
  hosts: backend
  become: yes
  roles: #Importar roles abaixo
  - backend

Exemplo de conteúdo em 'roles':
roles/backend/defaults/main.yml:
---
backend_deno_version: v1.2.0
backend_required_packages:
- unzip
backend_deploy_dest: /srv/backend
backend_src_path: app-src/api/
backend_entrypoint: app.ts
backend_runtime_args: --allow-read --allow-net
backend_bind_address: 0.0.0.0
backend_bind_port: 8080

roles/backend/handlers/main.yml:
---
- name: Reiniciar serviço de backend
  systemd:
    name: deno-backend.service
    state: restarted
    daemon_reload: yes

roles/backend/tasks/main.yml:
---
- name: Instalando dependências (pacotes)
  package:
    name: "{{ backend_required_packages }}"
    state: present
- name: Instalando ambiente de execução Deno (binário)
  unarchive:
    src: "https://github.com/denoland/deno/releases/download/{{ backend_deno_version }}/deno-x86_64-unknown-linux-gnu.zip"
    dest: /usr/local/bin
    remote_src: yes
  notify:
  - Reiniciar serviço de backend
- name: Copiar artefatos de backend para diretório de final
  copy:
    src: "{{ backend_src_path }}"
    dest: "{{ backend_deploy_dest }}"
  notify:
  - Reiniciar serviço de backend
- name: Configurar backend para ambiente produtivo
  template:
    src: env.j2
    dest: "{{ backend_deploy_dest }}/.env"
  notify:
  - Reiniciar serviço de backend
- name: Configurar systemd como supervisor do backend
  template:
    src: systemd-unit.j2
    dest: /etc/systemd/system/deno-backend.service
    owner: root
    group: root
  notify:
  - Reiniciar serviço de backend
- name: Serviço precisa estar ativo e inicializado
  systemd:
    name: deno-backend.service
    enabled: yes
    daemon_reload: yes

roles/backend/templates/systemd-unit.js:
# {{ ansible_managed }}

[Unit]
Description=Deno Backend Service

[Service]
WorkingDirectory={{ backend_deploy_dest }}
ExecStart=/usr/local/bin/deno run {{ backend_runtime_args }} {{ backend_entrypoint }}
Restart=always

[Install]
WantedBy=multi-user.target

roles/backend/templates/env.j2:
# {{ ansible_managed }}

HOST={{ backend_bind_address }}
PORT={{ backend_bind_port }}


playbook_remove.yml:
---
- name: Remover componentes
  hosts: all
  become: yes
  gather_facts: no
  tasks:
  - name: Parar os serviços
    service:
      name: "{{ item }}"
      state: stopped
      enabled: no
    loop:
    - deno-backend
    - nginx
    ignore_errors: yes
  - name: Remover dependências (pacotes)
    package:
      name:
      - nginx
      - unzip
      state: absent
  - name: Remover dependências (binarios)
    file: #Forma para remover arquivo
      path: /usr/local/bin/deno
      state: absent
  - name: Remove vestígios do Nginx
    file:
      path: /etc/nginx
      state: absent
  - name: Remover configuração systemd
    file:
      path: /etc/systemd/system/deno-backend.service
      state: absent
  - name: Remover diretórios de implantação
    file:
      path: "{{ item }}"
      state: absent
    loop:
    - /srv/frontend
    - /srv/backend

playbook_validate.yml:
---
- name: Verifica componente frontend
  hosts: frontend
  gather_facts: no
  vars:
    frontend_bind_port: 80
  tasks:
  - name: Executando requisição HTTP
    uri: #Verificar se frontend subiu, e retornar o h1 em caso de falha
      url: "http://{{ ansible_host }}:{{ frontend_bind_port }}"
      return_content: yes
    register: response
    failed_when: "'<h1>Youtube Chapter Extractor</h1>' not in response.content"
    retries: 3
    delay: 3
    until: response.status == 200

- name: Verifica componente backend
  hosts: backend
  vars:
    backend_bind_port: 8080
  gather_facts: no
  tasks:
  - name: Executando requisição HTTP
    uri:
      url: "http://{{ ansible_host }}:{{ backend_bind_port }}/api/v1/video-chapters/notreal"
    register: response
    retries: 3
    delay: 3
    until: response.status == 200


Laboratório prático:

Ambiente Vagrant composto por master node (Ubuntu - 'ansible') e 2 workers (Debian e CentOS - 'machine01 e machine02').

    Preparo
  1. Instalar requisitos: sudo apt install virtualbox vagrant
  2. Baixar Vagrantfile (Github acima)
  3. Executar: vagrant up
    Configuração
  1. Acessar master node: vagrant ssh ansible
  2. Executar: sudo apt update && sudo apt -y install wget curl ansible
  3. Se não existir, criar '/etc/ansible', com arquivos 'hosts', 'ansible.cfg' e pasta 'roles'
  4. Configurar workers: sudo nano /etc/ansible/hosts, apagar todo conteúdo e informar:
    
    [all:vars]
    ansible_python_interpreter=/usr/bin/python
    
    [debian]
    machine01.ubsocial
    
    [centos]
    machine02.ubsocial
    
    [linuxservers]
    machine01.ubsocial
    machine02.ubsocial
    
  5. Em sudo nano /etc/ansible/ansible.cfg:
    • Descomentar linha 'host_key_checking = False'
    • Alterar linha 'private_key_file = /vagrant/key'
  6. Sair do arquivo e criar chave de autenticação: ssh-keygen -q -t rsa -f /vagrant/key -N ''
  7. Copiar conteúdo de cat /vagrant/key.pub, acessar os Workers (vagrant ssh machine01...), usar nano .ssh/authorized_keys e colar a chave na última linha
  8. Verificar conexão no master node: ansible all -m ping

Elaborado por Mateus Schwede
ubsocial.github.io