Configurando Ansible para uso com equipamentos Juniper - parte 1
Tags: ansible, freebsd, juniper
Published: 2022-02-17 12:02:24

Nesse primeiro post, vou tentar mostrar como subir um Ansible para execução de tarefas em um roteador Juniper.


**Ansible**

Pra quem não sabe o Ansible é uma ferramenta de automação desenvolvida pela RedHat e é escrita em Python.
 
O Ansible nos possibilita executar automação de diversas tarefinhas chatas do dia a dia de nossa infraestrutura, como instalar pacotes em servidores, fazer atualizações de SO, bem como em nosso caso, configurarmos roteadores Juniper.

Ele utiliza como padrão de linguagem o YAML, parafraseando a Wikipédia sobre o que é a linguagem:

_" YAML é um formato de serialização (codificação de dados) de dados legíveis por humanos inspirado em linguagens como XML, C, Python, Perl, assim como o formato de correio eletrônico especificado pela RFC 2822"_

Trocando em miúdos, é uma espécie de linguagem bem simples e de fácil entendimento, para quem não sabe programar uma linha, igual é o meu caso :)

Quem quiser ir mais a fundo sobre Ansible, tem esse link [aqui](https://www.ansible.com/).



**Configuração e instalação do Ansible no FreeBSD 13**

Bom após a explicação breve sobre o Ansible, vamos botar a mão na massa e fazer o Ansible rodar.

Para subir esse ambiente, optei por utilizar o FreeBSD 13.0, hoje é o SO que mais venho utilizando, em função de sua reconhecida estabilidade e pacotes binários sempre bem atualizados.

Nada impede que você execute o Ansible em Linux ou até mesmo em contêiners Docker, a própria Juniper disponibiliza uma imagem em Docker já pronta com Ansible e o módulo deles chamado PyEZ, dá pra baixar a imagem do contêiner deles [aqui](https://hub.docker.com/r/juniper/pyez-ansible/).

Aqui temos o handbook do [FreeBSD](https://docs.freebsd.org/pt-br/books/handbook/) para consulta.


**1 - Instalando o Ansible no servidor**

Vou instalar a versão 2.10.15 que roda com python 3.8

```
[root@ansible ~]# pkg search Ansible
ansible-sshjail-1.1.0.35       Ansible connector for remote jails
py38-ansible-4.7.0             Radically simple IT automation
py38-ansible-base-2.10.15      Radically simple IT automation
py38-ansible-core-2.11.6       Radically simple IT automation
py38-ansible-iocage-g20200327,1 Ansible module for iocage
py38-ansible-kld-g20200803,1   Ansible module to load kernel modules or update /boot/loader.conf
py38-ansible-lint-5.3.1        Checks playbooks for sub-optimal practices and behaviour
py38-ansible-runner-2.0.2      Extensible embeddable ansible job runner
py38-ansible-sysrc-g20200803_1,1 Ansible module to set sysvars in rc.conf
py38-ansible2-2.9.27           Radically simple IT automation
[root@ansible ~]# pkg install py38-ansible-base-2.10.15
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
All repositories are up to date.
The following 15 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        libyaml: 0.2.5
        py38-Babel: 2.9.1
        py38-Jinja2: 3.0.1
        py38-ansible-base: 2.10.15
        py38-cffi: 1.15.0
        py38-cryptography: 3.3.2
        py38-markupsafe: 2.0.1
        py38-packaging: 21.3
        py38-pycparser: 2.21
        py38-pyparsing: 3.0.6
        py38-pytz: 2021.3,1
        py38-resolvelib05: 0.5.5_1
        py38-setuptools: 57.0.0
        py38-six: 1.16.0
        py38-yaml: 5.4.1

Number of packages to be installed: 15

The process will require 56 MiB more space.
9 MiB to be downloaded.

Proceed with this action? [y/N]: y
```

**2 - Criando um usuário ansible no SO para uso em separado do root**

Por questão de organização, prefiro não rodar o Ansible dentro do usuário e da estrutura de diretórios do usuário root, com isso vou criar um usuário chamado ansible em meu servidor.

```
[root@ansible ~]# pw useradd -n ansible -s /usr/local/bin/bash -m /home/ansible -d /home/ansible
```
No comando acima, adicionamos o usuário ansible, com o shell em bash e apontando que o diretório particular irá ficar em /home/ansible


**3 - Inserindo no grupo wheel, para execução de comandos com sudo**

Aqui inserimos o usuário ansible no grupo wheel, para execução de comandos com sudo caso haja necessidade

```
[root@ansible ~]# pw group mod wheel -m ansible
```

**4 - Liberar grupo wheel para execução de comandos via sudo**

Caso não haja o visudo instalado, instale via pkg

```
root@ansible:~ # pkg install sudo
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
All repositories are up to date.
The following 1 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        sudo: 1.9.8p2

Number of packages to be installed: 1

The process will require 7 MiB more space.
1 MiB to be downloaded.

Proceed with this action? [y/N]: y
```


Aqui executamos o comando visudo para editar o arquivo /usr/local/etc/sudoers

```
[root@ansible ~]# visudo
```

Vá até a linha abaixo e a descomente

```
## Same thing without a password
%wheel ALL=(ALL) NOPASSWD: ALL
```
Com a liberação acima, todos os usuários que estão no grupo wheel, poderão executar comandos sudo sem solicitação de senha, fiz isso para caso queiramos rodar algum comando com privilégio de root. Dessa forma é possível utilizar o sudo sem senha, logado com o usuário ansible.

**5 - Gerando uma chave ssh para conexão nos roteadores**

Com o usuário ansible criado, devemos gerar uma chave ssh para nosso uso, para gerarmos a chave usamos o comando ssh-keygen no terminal

OBS: Nos campos onde ele solicitará senha, só apertar **"Enter"** nos dois casos.

```
[ansible@ansible ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ansible/.ssh/id_rsa):
Created directory '/home/ansible/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ansible/.ssh/id_rsa.
Your public key has been saved in /home/ansible/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:USG3FTsdBvBgyu7sf5cqnEJjhRFCgRzfhkLkKehlYks ansible@ansible
The key's randomart image is:
+---[RSA 2048]----+
|   o+++.o.Bo+oo  |
| . ooo =.* = + . |
|.E.o+ o *o. + .  |
|+ =. . o...  .   |
| o      S.       |
|       o+        |
|       ooo .   . |
|       .. + . o  |
|        .o.o.o   |
+----[SHA256]-----+
[ansible@ansible ~]$ ls -lha .ssh/
total 16
drwx------  2 ansible  ansible   512B Feb 16 08:59 .
drwxr-xr-x  3 ansible  ansible   512B Feb 16 08:59 ..
-rw-------  1 ansible  ansible   1.8K Feb 16 08:59 id_rsa
-rw-r--r--  1 ansible  ansible   397B Feb 16 08:59 id_rsa.pub
```


**6 - Instalação da coleção de módulos para Juniper e bibliotecas Python**

Para o Ansible enviar comandos para o Juniper, é necessário que façamos uso da coleção de módulos feitas para Junos
A coleção de módulos pode ser vista [aqui](https://docs.ansible.com/ansible/latest/collections/junipernetworks/junos/index.html)

Aqui instalamos a coleção Juniper.junos
```
[ansible@ansible ~]$ ansible-galaxy install Juniper.junos
[ansible@ansible ~]$ ansible-galaxy collection install junipernetworks.junos
```

Vamos também ter que usar o comando pip do Python, no meu caso não tinha, então instalei assim:

```
[ansible@ansible ~]$ pkg install py38-pip
```

Agora vamos instalar algumas bibliotecas Python que complementam a conexão com o Juniper

Instalamos a biblioteca junos-eznc
```
[ansible@ansible ~]$ pip install junos-eznc
```

Instalamos a biblioteca ncclient, para uso em conjunto com o módulo netconf

```
[ansible@ansible ~]$ pip install ncclient
```

Instalamos a biblioteca jxmlease para tratamento de estruturas xml

```
[ansible@ansible ~]$ pip install jxmlease
```


**7 - Criação e configuração do arquivo ansible.cfg**

Agora que já temos o Ansible instalado, usuário e chave ssh configurada, bibliotecas python e o módulo do Juniper instalado, vamos configurar o arquivo ansible.cfg dentro do diretório em nosso usuário.
O arquivo ansible.cfg estando em nosso diretório home sobrescreve qualquer config que tenha em outro local do servidor

```
[ansible@ansible ~]$ vim .ansible.cfg
```

Segue exemplo de config:

```
[defaults]

#--- General settings
ansible_forks                   = 5                             ; Quantidade de processos.
ansible_log_path                = /home/ansible/log/ansible.log          ; Arquivo de log.
ansible_module_name             = command                       ; Modulo padrao.
ansible_executable              = /usr/local/bin/bash         ; Shell padrao.
ansible_managed                 = Ansible managed               ; Permite utilizacao de strings, timestamp em suas playbok/tasks.
gathering                       = explicit                                              ; Não coletar dados por padrão
retry_files_enabled             = False                                                 ; Não criar arquivos .retry para execuções com erro
interpreter_python              = /usr/local/bin/python3.8


#--- Files/Directory settings
inventory               = /home/ansible/hosts            ; Arquivo de Hosts do ansible.
ansible_library                 = /home/ansible/my_modules/         ; Diretorio onde contém os módulos do ansible.
ansible_remote_tmp              = /home/ansible/tmp                ; Onde serao armazenados os arquivos temporarios nos hosts de destino (inventory).
ansible_local_tmp               = /home/ansible/tmp                ; Diretorio temporario local.
ansible_roles_path              = /home/ansible/roles            ; Diretorio padrao de roles do ansible.

#--- Users settings
ansible_remote_user             = ansible                          ; Usuario padrao - se nao especificado.
#sudo_user               = root                         ; Usuario sudo padrao. Sera descontinuado na prox versao
ansible_ask_pass                = no                            ; Perguntar por padrao a senha durante a execucao das tasks.
ansible_ask-sudo_pass           = no                            ; Similar ao ask_pass.

#--- SSH settings
ansible_remote_port             = 22                            ; Porta padrao de conexao remota (SSH).
ansible_timeout                 = 10                            ; SSH Timeout.
ansible_host_key_checking       = False                         ; Validacao de chave SSH durante a conexao
ansible_ssh_executable          = /usr/bin/ssh                  ; Binario ssh. Utiliza-se a variavel ansible_ssh_executable.
ansible_private_key_file        = /home/ansible/.ssh/id_rsa                 ; Private key

#[persistent_connection]
#log_messages = True

[privilege_scalation]

ansible_become                  = True                          ; Permite elevacao de privilegio.
ansible_become_method           = sudo                          ; Metodo padrao.
ansible_become_user             = root                          ; Usuario padrao.
ansible_become_ask_pass         = False                         ; Perguntar a senha.

[ssh_connection]

ansible_scp_if_ssh              = smart                         ; Executa sftp e se nao conseguir tenta com scp (Padrao).
ansible_transfer_method         = smart                         ; Ordem de execucao: sftp --> scp (padrao).
ansible_retries                 = 3                             ; Tempo para nova tentativa de conxao com um host.
```

**8 - Criando estrutura de diretórios de execução do Ansible**

```
[ansible@ansible ~]$ touch hosts # arquivo com os roteadores que vamos conectar
[ansible@ansible ~]$ mkdir -p roles/mx80 # Diretório das tarefas, arquivos e variáveis que vamos usar
[ansible@ansible ~]$ mkdir roles/mx80/tasks # Diretório que vai conter as playbooks de tarefa
[ansible@ansible ~]$ mkdir roles/mx80/vars # Diretório para uso de variáveis
[ansible@ansible ~]$ touch prov-mx80.yml # Arquivo de provisionamento que chama determinados hosts e executa a chamada da playbook que está em determinada "role"

```


**Configurando roteador Juniper MX80 para receber conexões do Ansible**

**1 - Criação do usuario ansible no roteador Juniper**

Anteriormente criamos uma chave ssh para o usuário ansible, agora vamos até o roteador Juniper, criar o usuário ansible e inserir a sua chave pública, para que seja possível a execução das tarefas a partir do servidor do ansible sem ter que ficar inserindo senha a todo momento.


```
cristian@mx80> edit #Entrando no modo de edição
Entering configuration mode

[edit]
cristian.cardoso@mx80# set system login user ansible class super-user #Aqui informamos que o usuário ansible terá poderes para modificações no sistema

[edit]
cristian@mx80# set system login user ansible authentication ssh-rsa "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC25kzZcmpdjXxy0qhuRaj/YlBSyTIe+lrvjkjo9L3MNz0AiB1cjyon9xjcJ7DCqdQSn9Ki/0z1da0gb/k+6HIEIANypmtkdapqyKPHVABJPZZ0aU1eWykJ19iJUrAxCr4/dkiIDsamG5NDUPYby8NJoP3zvfnnBGJlNWbginYF530DG1UMMMD7CnMygM6Tl+xJWJSc4ksfuJY9k0NSsAayccrT2Fhd4hnwJQIyWzg+49EsZxpF5QBPVNjZojkf5IR9Aecu+6fDPrmziGIRFxdilgAmus7tzH65+ZBXtFClC5sTNQKlAQil4LON7VtmoubWcAYYfSnhw/uAbVhozKqr ansible@ansible" 

cristian@mx80# commit and-quit #commitamos as mudanças e saímos 
commit complete
Exiting configuration mode

cristian@mx80> quit

```

**2 - Teste de acesso via SSH do roteador**

Depois de criarmos o usuário ansible no roteador, vamos efetuar o teste de acesso ssh padrão 

```
[root@ansible ~]# su - ansible #entrando no usuário Ansible
[ansible@ansible ~]$ ssh mx80
The authenticity of host 'mx80 (192.168.10.100)' can't be established.
ECDSA key fingerprint is SHA256:RDbRCUn2F0axeYJi5UZVTcFPaQPFC6P0i07SmqTD2NU.
No matching host key fingerprint found in DNS.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'mx80' (ECDSA) to the list of known hosts.
--- JUNOS 19.4R3-S6.1 built 2021-09-25 06:29:31 UTC
ansible@mx80>
```

É possível fazer com que o Ansible ignore checagem de chave de host, só inserir a seguinte config no .ansible.cfg, no grupo [defaults] da configuração

```
[defaults]
host_key_checking = False
```

**3 - Ativação do netconf no roteador Juniper**

Para que seja possível utilizarmos os módulos do Ansible, é necessário configuramos o serviço de netconf no Juniper, já que ele é a ponte de conexão entre os módulos python utilizados pelo Ansible e o Junos que roda no roteador

Para quem não sabe o netconf ou network protocol configuration é um módulo para configuração e gerenciamento utilizado nas plataformas de SO da Juniper

```
ansible@mx80> edit
Entering configuration mode

[edit]
ansible@mx80# set system services netconf ssh

[edit]
ansible@mx80# commit and-quit
```



**Configuração do Ansible para executar a playbook**

Com o Ansible instalado e o roteador configurado, vamos configurar alguns arquivos que estão no diretório do Ansible, para criamos nossa primeira playbook.

**1 - Inserindo o roteador no arquivo de hosts**

Primeiramente vamos editar o arquivo de hosts que está na home do ansible, no arquivo vamos criar o grupo de hosts chamado Juniper, inserindo o roteador chamado mx80.

```
[juniper]
mx80
```
Obs: Eu acabei utilizando um hostname chamado mx80, mas o arquivo também aceita IP's, para que seja possível utilizar nomes, ou se insere um IP atrelado ao hostname em /etc/hosts ou dá pra configurar um serviço de DNS local que resolva os IP's para os hostnames inseridos.


**2 - Criando arquivo de provisionamento da playbook**

No arquivo de provisionamento, informamos o grupo de hosts que será invocado, se vai tentar capturar dados do host/hosts de destino e qual estrutura de 'roles' onde estão as variáveis e conjunto de tarefas que serão invocadas para execução.

```
---
- hosts: juniper # Aqui informamos que estamos fazendo a chamada dos hosts do grupo juniper
  gather_facts: no # O módulo de buscar variáveis no Junos não é funcional em nosso caso
  roles:
    - mx80 #Aqui informamos que a role mx80, que é onde estão as tarefas e variáveis relacionadas ao roteadores mx80 serão invocadas e executadas.
```


**3 - Inserindo variáveis de conexão**

Vamos criar o arquivo roles/mx80/vars/main.yml, onde vamos inserir os parâmetros de conexão do Juniper.

Criamos o arquivo roles/mx80/vars/main.yml e inserimos o seguinte

```
---
# Módulo de conexão com o Junos via netconf
ansible_connection: ansible.netcommon.netconf
ansible_network_os: junipernetworks.junos.junos
ansible_user: ansible
```


**Criando playbook de teste** 

**1 - Criando uma playbook de execução básica para teste**

Vamos criar um arquivo chamado roles/mx80/tasks/main.yml e criar as seguintes tarefas de execução:

```
- name: Verifica versão do Junos # Nome da tarefa
  junipernetworks.junos.junos_command: #Modulo a ser utilizado
    commands: # Comandos a serem enviados para o roteador
    - show version # Comando a ser executado
  register: junos_version #Variável que registra a saida do comando

- debug: var=junos_version.stdout # Debug que joga para nosso terminal a saida do comando
```

Foi criada uma tarefa que utiliza o módulo junos_command, onde é possível executar comandos de **"show *"** no roteador Juniper e registrei o retorno em uma variável no momento da execução do comando.
Na segunda tarefa, executei o módulo de debug do Ansible, para jogar na tela o retorno do comando show version


**2 - Rodando a playbook de execução e observando o resultado**

```
[ansible@ansible-noc ~]$ ansible-playbook -i hosts prov-mx80.yml

PLAY [mx80] ************************************************************************************************************
TASK [mx80 : Verifica versão do Junos] *********************************************************************************
ok: [mx80]

TASK [mx80 : debug] ****************************************************************************************************
ok: [mx80-03-ita001] => {
    "junos_version.stdout": [
        "Hostname: mx80\nModel: mx5-t\nJunos: 19.4R3-S6.1\nJUNOS Base OS boot [19.4R3-S6.1]\nJUNOS Base OS Software Suite [19.4R3-S6.1]\nJUNOS Crypto Software Suite [19.4R3-S6.1]\nJUNOS Packet Forwarding Engine Support (MX80) [19.4R3-S6.1]\nJUNOS Web Management [19.4R3-S6.1]\nJUNOS Online Documentation [19.4R3-S6.1]\nJUNOS SDN Software Suite [19.4R3-S6.1]\nJUNOS Services Application Level Gateways [19.4R3-S6.1]\nJUNOS Services COS [19.4R3-S6.1]\nJUNOS Services Jflow Container package [19.4R3-S6.1]\nJUNOS Services Stateful Firewall [19.4R3-S6.1]\nJUNOS Services NAT [19.4R3-S6.1]\nJUNOS Services RPM [19.4R3-S6.1]\nJUNOS Services SOFTWIRE [19.4R3-S6.1]\nJUNOS Services Captive Portal and Content Delivery Container package [19.4R3-S6.1]\nJUNOS Macsec Software Suite [19.4R3-S6.1]\nJUNOS Services Crypto [19.4R3-S6.1]\nJUNOS Services IPSec [19.4R3-S6.1]\nJUNOS Services RTCOM [19.4R3-S6.1]\nJUNOS Services SSL [19.4R3-S6.1]\nJUNOS Services TCP-LOG [19.4R3-S6.1]\nJUNOS DP Crypto Software Software Suite [19.4R3-S6.1]\nJUNOS py-base-powerpc [19.4R3-S6.1]\nJUNOS py-base2-powerpc [19.4R3-S6.1]\nJUNOS py-extensions-powerpc [19.4R3-S6.1]\nJUNOS py-extensions2-powerpc [19.4R3-S6.1]\nJUNOS jsd [powerpc-19.4R3-S6.1-jet-1]\nJUNOS Kernel Software Suite [19.4R3-S6.1]\nJUNOS Routing Software Suite [19.4R3-S6.1]"
    ]
}

PLAY RECAP *************************************************************************************************************
mx80            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

Com os passos acima, vimos como criar uma estrutura em Ansible no FreeBSD, configuramos acesso via netconf no roteador Juniper e rodamos uma tarefa bem básica via playbook do Ansible.



Na próxima teleaula do telecurso 2000, vamos aprender como enviar comandos para configuração e arquivos de template.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rymwms00utlw7qow2dpg.png)