Criando um ambiente de desenvolvimento para o Ansible

A alguns dias atrás nosso mestre @gomex abriu uma thread no twitter sobre como é estar desenvolvendo infraestrutura hoje em dia pelo time de Ops Link e como isso é libertador, e de fato, é uma sensação sem igual para um Sysadmin.

Como várias pessoas comentaram eu também não pude deixar de participar. :D,

Na ocasião por coincidência estava desenvolvendo uma imagem de container docker para o ansible!

Mas por quê?

A ideia e criar um ambiente centralizado com o ansible instalado sem a necessidade de “sujar” o seu sistema operacional corrente com várias ferramentas que usamos durante o desenvolvimento de módulos, roles, tasks e tudo que permeia o desenvolvimento.

Nesse artigo vou escrever/descrever uma necessidade própria, então tome esse artigo como um guia e adapte para sua necessidade beleza?! :D

Vamos criar uma estrutura de arquivos e diretórios para desenvolver o projeto.

Crie um diretório qualquer e dentro desse diretório monte a estrutura de arquivos e diretórios proposto abaixo.

mkdir new_hosts

No final teremos algo assim.

new_hosts master 3d ➜ tree -L 5
.
├── Dockerfile
├── Makefile
├── ansible.cfg
├── inventory
│   ├── group_vars
│   │   └── all.yaml
│   └── invetory.ini
├── requirements.txt
├── roles
│   └── common
│       ├── handlers
│       │   └── main.yaml
│       ├── tasks
│       │   └── main.yaml
│       └── templates
│           ├── 11-hardening.conf.j2
│           └── ntp.conf.j2
├── setup.yaml
└── ssh-keys
    └── ssh-keygen.sh

8 directories, 12 files

Requisitos

  • Máquina Virtual
    • Ubuntu Server
    • SSH

Dockerfile

Com o dockerfile vamos construir a imagem docker com o ansible instalado para centralizar a execução e desenvolvimento do projeto de automação.

FROM python:3.7-alpine

COPY requirements.txt .

RUN apk add vim make sshpass openssh gcc g++ libffi-dev openssl openssl-dev \
  && adduser --disabled-password --gecos "" ansible \
  && pip install -r requirements.txt

USER ansible

WORKDIR /ansible

ENTRYPOINT [ "sh" ]

Makefile

Com o make criamos uma forma de centralizar e automatizar os comandos que utilizamos repetidas vezes ao longo do desenvolvimento.

.PHONY: help
.DEFAULT_GOAL := help

help:
	@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2, $$3, $$4, $$5}'

build: ## Make image for use terraform
	@docker build --no-cache -t fabianoflorentino/ansible .

run: ## Start Terraform
	@docker run -it --name ansible -v ${PWD}:/ansible --entrypoint "" fabianoflorentino/ansible sh

rm: ## Remove container
	@docker container rm -f ansible

rmi: ## Remove untagged images
	@docker rmi -f $(docker images | grep "^<none>" |cut -d" " -f50)

ansible-test: ## Test of ansible roles to apply
	@ansible-playbook -i inventory/invetory.ini -u supervisor -b -e ssh_connection_user=supervisor setup.yaml -C

ansible-run: ## Test of ansible roles to apply
	@ansible-playbook -i inventory/invetory.ini -u supervisor -b -e ssh_connection_user=supervisor setup.yaml

ansible.cfg

Nesse arquivo determinamos alguns parametros para melhorar a execução do ansible.

[defaults]
gathering = smart
forks               = 5
callback_whitelist  = timer, mail, profile_tasks
host_key_checking   = False
fact_caching_connection = /tmp/facts_cache
fact_caching = jsonfile
fact_caching_timeout = 7200
gather_subset=!hardware

error_on_missing_handler = True
sudo_flags = -H -S -n

[ssh_connection]
ssh_args     = -C -o ControlMaster=auto -o ControlPersist=18000
control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r
pipelining   = True

inventory

Diretório onde configuramos os hosts que serão gerenciados pelo ansible.

mkdir -p inventory/group_vars
├── inventory
│   ├── group_vars

all.yaml

Aqui determinamos algumas variáveis que será utilizada pelo group de hosts [all] que fica no arquivo inventory.ini

vim inventory/group_vars/all.yaml
├── inventory
│   ├── group_vars
│   │   └── all.yaml
---
packages:
  to_install:
    - vim
    - tree
    - netcat
    - tcpdump
    - nmap
    - ntp
  to_remove:
    -

services:
  to_enable:
    - ntp
  to_disable:

ntp_servers:
  - "server 0.br.pool.ntp.org"
  - "server 1.br.pool.ntp.org"
  - "server 2.br.pool.ntp.org"
  - "server 3.br.pool.ntp.org"

hardening:
  net_ipv6_conf_all_disable_ipv6: 1
  net_ipv6_conf_default_disable_ipv6: 1
  net_ipv6_conf_lo_disable_ipv6: 1
  net_ipv4_conf_all_accept_source_route: 0
  ipv4_conf_all_forwarding: 0
  net_ipv4_conf_all_accept_redirects: 0
  net_ipv4_conf_all_secure_redirects: 0
  net_ipv4_conf_all_send_redirects: 0
  net_ipv4_conf_all_rp_filter: 0
  net_ipv4_icmp_echo_ignore_all: 0

inventory.ini

Arquivo com os hosts e grupo de hosts que o ansible irá gerenciar.

vim invetory/inventory.ini
├── inventory
│   └── invetory.ini
[all]

vm-1 ansible_host=192.168.7.100

requirements.txt

Lista com os módulos que serão instalados junto com o ansible para o funcionamento adequado.

# Note: this requirements.txt file is used to specify what dependencies are
# needed to make the package run rather than for deployment of a tested set of
# packages.  Thus, this should be the loosest set possible (only required
# packages, not optional ones, and with the widest range of versions that could
# be suitable)
crypto
cryptography
ansible

roles

Diretório com o conjunto de roles/tasks que o ansible irá aplicar nos hosts gerenciados.

├── roles
│   └── common
│       ├── handlers
│       │   └── main.yaml
│       ├── tasks
│       │   └── main.yaml
│       └── templates
│           ├── 11-hardening.conf.j2
│           └── ntp.conf.j2
mkdir -p roles/common/{tasks,handlers,templates}

O diretório common é a role e conjunto de tasks que iremos utilizar para executar o projeto no host(vm) de desenvolvimento.

tasks - main.yaml

Conjunto de tasks que será executada nos hosts gerenciados pelo ansible.

  • “Update System”
  • “Install Essential Packages”
  • “Enable Essential Services”
  • “Configure NTP service”
  • “Hardening - Kernel Parameters”
vim roles/common/tasks/main.yaml
├── roles
│   └── common
│       ├── tasks
│       │   └── main.yaml
---
- name: "Update System"
  package:
    state: latest
    name: "*"

- name: "Install Essential Packages"
  apt:
    state: present
    name: "{{ item }}"
  loop: "{{ packages.to_install }}"

- name: "Enable Essential Services"
  apt:
    state: present
    name: "{{ item }}"
  loop: "{{ services.to_enable }}"

- name: "Configure NTP service"
  template:
    src: ntp.conf.j2
    dest: /etc/ntp.conf
    owner: root
    mode: 0644
  notify: "Restart NTP service"

- name: "Hardening - Kernel Parameters"
  template:
    src: 11-hardening.conf.j2
    dest: /etc/sysctl.d/11-hardening.conf
    owner: root
    group: root
    mode: 0644
  notify: "Apply Kernel Parameters"

templates

Arquivos templates com variáveis para criar os arquivos de configuração dinamicamente.

├── roles
│   └── common
│       └── templates
│           ├── 11-hardening.conf.j2
│           └── ntp.conf.j2
vim roles/common/templates/11-hardening.conf.j2
  • 11-hardening.conf

# {{ ansible_managed }}

ipv4.conf.all.forwarding = {{ hardening.ipv4_conf_all_forwarding}}
net.ipv4.conf.all.accept_source_route = {{ hardening.net_ipv4_conf_all_accept_source_route}}
net.ipv4.conf.all.accept_redirects = {{ hardening.net_ipv4_conf_all_accept_redirects }}
net.ipv4.conf.all.secure_redirects = {{ hardening.net_ipv4_conf_all_secure_redirects }}
net.ipv4.conf.all.send_redirects = {{ hardening.net_ipv4_conf_all_send_redirects }}
net.ipv4.conf.all.rp_filter = {{ hardening.net_ipv4_conf_all_rp_filter }}
net.ipv4.icmp_echo_ignore_all = {{ hardening.net_ipv4_icmp_echo_ignore_all }}
net.ipv6.conf.all.disable_ipv6 = {{ hardening.net_ipv6_conf_all_disable_ipv6 }}
net.ipv6.conf.default.disable_ipv6 = {{ hardening.net_ipv6_conf_default_disable_ipv6}}
net.ipv6.conf.lo.disable_ipv6 = {{ hardening.net_ipv6_conf_lo_disable_ipv6 }}
  • ntp.conf

vim roles/common/templates/ntp.conf.j2
#
# {{ ansible_managed }}
#
# For more information about this file, see the man pages
# ntp.conf(5), ntp_acc(5), ntp_auth(5), ntp_clock(5), ntp_misc(5), ntp_mon(5).

driftfile /var/lib/ntp/drift

# Permit time synchronization with our time source, but do not
# permit the source to query or modify the service on this system.
restrict default nomodify notrap nopeer noquery

# Permit all access over the loopback interface.  This could
# be tightened as well, but to do so would effect some of
# the administrative functions.
restrict 127.0.0.1
restrict ::1

# Hosts on local network are less restricted.
#restrict 192.168.1.0 netmask 255.255.255.0 nomodify notrap

# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
{% for server in ntp_servers %}
{{ server }}
{% endfor %}

logfile /var/log/ntp.log

#broadcast 192.168.1.255 autokey        # broadcast server
#broadcastclient                        # broadcast client
#broadcast 224.0.1.1 autokey            # multicast server
#multicastclient 224.0.1.1              # multicast client
#manycastserver 239.255.254.254         # manycast server
#manycastclient 239.255.254.254 autokey # manycast client

# Enable public key cryptography.
#crypto

includefile /etc/ntp/crypto/pw

# Key file containing the keys and key identifiers used when operating
# with symmetric key cryptography.
keys /etc/ntp/keys

# Specify the key identifiers which are trusted.
#trustedkey 4 8 42

# Specify the key identifier to use with the ntpdc utility.
#requestkey 8

# Specify the key identifier to use with the ntpq utility.
#controlkey 8

# Enable writing of statistics records.
#statistics clockstats cryptostats loopstats peerstats

# Disable the monitoring facility to prevent amplification attacks using ntpdc
# monlist command when default restrict does not include the noquery flag. See
# CVE-2013-5211 for more details.
# Note: Monitoring will not be disabled with the limited restriction flag.
disable monitor

handlers

Tarefas configuradas para serem re-executadas caso tenha alguma alteração nas configurações das tasks.

├── roles
│   └── common
│       ├── handlers
│       │   └── main.yaml
vim roles/common/handlers/main.yaml
---
- name: "Restart NTP service"
  systemd:
    name: ntp
    state: restarted
    enabled: true

- name: "Apply Kernel Parameters"
  shell: |
    sysctl --system
  register: kernel_parameters

setup.yaml

Playbook que executa o conjuntos de roles que será aplicado nos hosts(vm’s) gerenciados pelo ansible.

new_hosts master 3d ➜ tree -L 5
.
├── setup.yaml
vim new_hosts/setup.yaml
---
- name: "Prepare new Server"
  hosts:
    - all
  roles:
    - {role: common, tags: common}

ssh-keys

O ansible faz o gerenciamentos dos hosts através de comunicação ssh para todos os sistemas que o suportam.

A título de curiosidade no windows ele cria uma comunicação do tipo RPC(Chamada de Procedimento Remoto)

Criei esse script para gerar o par de chaves ssh que o ansible irá utilizar no container para gerenciar os hosts de destino.

new_hosts master 3d ➜ tree -L 5
.
└── ssh-keys
    └── ssh-keygen.sh
vim ssh-keys/ssh-keygen.sh
#!/usr/bin/env sh

mkdir /home/ansible/.ssh

ssh-keygen -t rsa -N '' -f ./ssh-keys/ansible -C ansible@dev

cp -rf ./ssh-keys/ansible /home/ansible/.ssh/id_rsa
cp -rf ./ssh-keys/ansible.pub /home/ansible/.ssh/id_rsa.pub

chmod 400 /home/ansible/.ssh/id_rsa
chmod 644 /home/ansible/.ssh/id_rsa.pub

Ufa!

Depois de montar toda a nossa estrutura, acredito que temos algo assim

.
├── Dockerfile
├── Makefile
├── ansible.cfg
├── inventory
│   ├── group_vars
│   │   └── all.yaml
│   └── invetory.ini
├── requirements.txt
├── roles
│   └── common
│       ├── handlers
│       │   └── main.yaml
│       ├── tasks
│       │   └── main.yaml
│       └── templates
│           ├── 11-hardening.conf.j2
│           └── ntp.conf.j2
├── setup.yaml
└── ssh-keys
    └── ssh-keygen.sh

Build

No mesmo nível onde está o nosso dockerfile vamos construir a imagem com o ansible instalado.

Lembra do Makefile? então, vamos utilizar bastente ele agora.

make help

<>

make build

Esse commando está fazendo o mesmo que:

docker build --no-cache -t fabianoflorentino/ansible .

Melhor né? :D, automação da automação!

e assim para todos os comandos que iremos utilizar, para verificar a sintaxe dos comandos volta lá no arquivo Makefile e veja o que cada comando está executando beleza? :D

No final se tudo ocorreu bem teremos a seguinte mensagem:

Successfully built 3894bf22ae0d
Successfully tagged fabianoflorentino/ansible:latest

<>

Ansible

Com a imagem pronta vamos iniciar o container para utilizamos o ansible de forma isolada em um container mas interagindo com o nosso projeto.

make run

<>

Como de dentro do container estamos conseguindo acessar os arquivos do projeto?

A forma como estamos executando o container. Na execução estamos montando o diretório corrente dentro do container e acessamos o container através do shell sh.

docker run -it --name ansible -v ${PWD}:/ansible --entrypoint "" fabianoflorentino/ansible sh

SSH

De dentro do container execute o script que cria o par de chaves ssh para comunicação com os hosts.

sh ssh-keys/ssh-keygen.sh

<>

Transfira a chave ssh publica para o(s) host(s) que serão gerenciados.

ssh-copy-id -i /home/ansible/.ssh/id_rsa.pub supervisor@192.168.7.100

<>

Teste a comunicação com o(s) host(s)

ansible -i inventory/invetory.ini all -u supervisor -b -m ping

<>

Deploy da VM

Com a comunicação estabelecida podemos aplicar a(s) roles que foi desenvolvida nesse projeto no caso a role common.

make ansible-test

Esse comando irá fazer o teste de execução das tasks.

<>

O que estiver em amarelo o ansible irá executar a alteração o que estiver em verde é porque a ação esperada já foi executada ou está de acordo com a task.

Feito o teste, podemos aplicar realmente as alterações no host(s)

make ansible-run

<>

Idempotência

Uma boa prática é sempre buscar Idempotência na execução da sua gerencia de configuração, uma vez aplicada aquele conjunto de configuração ele só poderá ser modificado caso aja modificação no código do seu projeto, do contrário não importa quantas vezes você executar o ansible, ele sempre terá que aplicar as mesmas configurações previamente configuradas.

make ansible-run

<>

Demo

<

Conclusão

Como mencionanei no começo do artigo o @gomex iniciou um thread muito bacana sobre como nós antigos Sysadmin(Ops) agora SRE’s, DevOps Engineer, Eng. DevOps e afins não importa o nome, temos a satisfação de estar entregando infraestrutura como código, criando ambiente de desenvolvimento como o citado ao longo desse artigo.

É muito legal ver uma área admirada por muitos no mundo de tecnologia se transformar radicalmente. Bom,espero ajudar bastante gente com esse artigo.

Ah! ia me esquecendo, parte do que demonstrei aqui com o Makefile aprendi pertubando muito o @igorsouza vlw d++ mano :D!

Projeto: http://bit.ly/2Ns2ZRs

comments powered by Disqus