Ansible

Automatização simplificada de SOs
Voltar

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
  • Github dos materiais: Acesse

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