NetDevOps - Construindo um Pipeline de Integração Contínua (CI)
Fala NetCoders… Neste artigo iremos dar continuidade nos temas que envolvem NetDevOps. Hoje de fato, iremos botar a mão na massa e implementar nosso pipeline de Integração Continua.
Iremos desmistificar o conceito por trás de Network Infrastructure As Code e montaremos nosso pipeline e integrar diversos serviços para montarmos uma estrutura poderosa de automação.
Hoje iremos mexer com Vrnetlab para lançar nosso ambiente de teste automatizado de Rede, esta ferramenta irá lançar as imagens via Docker contêiner.
Além do Vrnetlab e Docker, neste artigo você aprenderá a trabalhar com Git, GitHub e GitHub Actions.
Utilizaremos comandos Git para manipularmos nosso repositório local e remoto, iremos utilizar o GitHub para lançar nossa topologia baseada em código por meio deste repositório, e o GitHub Actions para montarmos nossa estrutura de build. Essa estrutura sempre ficará “buildavel” para que sejam entregues versões da esteira de testes contínuos.
Pegue seu café e vamos nessa!
Tópicos
- Por que Network Infrastructure As Code?
- Como funciona o Vrnetlab?
- Instalando o KVM
- Instalando o Docker
- Repositório clone Vrnetlab
- Construindo imagens com dispositivos virtuais
- Lançando dispositivos virtuais
- Funções bash auxiliares
- Configurando enlace entre contêineres
- Docker Compose
- Inicializando o GitHub Action
- Comandos Git básicos
- GitHub Runner auto-hospedado
- Execução de ação GitHub
- Recapitulação e resumo
Por que Network Infrastructure As Code?
Uma das tarefas mais importantes no desenvolvimento de software é o teste. E se pensarmos que a automação e programabilidade da rede também são um tipo de desenvolvimento de software, também precisamos do teste de rede.
Com uma plataforma de teste de rede sólida, podemos testar o código Ansible (yaml), código Python, netconf/restconf, modelos yang, etc.
Claro que também podemos testar CLI clássico e configurar laboratórios MPLS / EVPN / etc manualmente. Resumindo, um laboratório de teste de rede é importante.
Um resultado importante que podemos obter com uma plataforma de teste de rede é a iterabilidade. Com iterabilidade testamos quantas vezes precisarmos de uma forma muito fácil e automática.
Podemos testar uma implementação, verificar se funciona ou não, se não funcionar lançamos outro laboratório de teste e testamos novamente. Este loop de iteração é muito importante em qualquer processo de desenvolvimento de software.
Recapitulando, neste artigo irá ser construído um pipeline de integração continua onde será usado o Docker para executar duas imagens contêineres EOS Arista
. Iremos fazer o deploy de duas caixas virtuais e integra-las à um servidor Github utilizando o serviço de integração continua Github Action.
Como funciona o Vrnetlab?
Esta ferramenta permite executar routers virtuais dentro de contêineres Docker. Ótima ferramenta para montar ambientes de testes vinculados ao GitHub, onde é possível implementar pipelines de teste (CI) e implantação (CD).
Ao construir sua esteira CI, nenhum ser humano é necessário para ativar o ambiente e executar os testes ou validações necessárias.
Graças à e ferramenta, podemos facilmente emular diferentes roteadores e de diferentes fornecedores (Cisco, Juniper, Arista, Nokia, etc) como contêineres Docker, todos eles rodando na própria máquina local.
Vrnetlab é muito leve, é focado apenas em laboratórios pequenos com foco em pipelines automatizados. Você não instala nenhum tipo de software para emular as imagens dos routers, você utiliza ela através do docker.
Quem é responsável por emular de fato a imagem do router seria o KVM, ferramenta de virtualização, a imagem que é executada é do tipo Qemu.
Instalando KVM
Primeiro, certifique-se de que a virtualização está habilitada, “0” significa que não há suporte para virtualização no HW, “1” ou mais, é sinal que esta OK:
thiago@thiago:~$ egrep --count "vmx|smv" /proc/cpuinfo
Instalando as dependências de pacotes de requisitos do KVM:
thiago@thiago:~$ sudo apt-get install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
Adicione seu usuário aos grupos ‘“kvm” e “libvirt”. Faça logout e login depois para que as alterações tenham efeito:
thiago@thiago:~$ sudo adduser `id -un` kvm
Adicionando o usuário `thiago' ao grupo `kvm' ...
Adicionando usuário thiago ao grupo kvm
Concluído.
thiago@thiago:~$ sudo adduser `id -un` libvirt
O usuário 'thiago' já é um membro de 'libvirt'.
O comando descrito abaixo é para confirmar se o KVM está instalado e operacional. A saída vazia está OK, se algo deu errado, você obterá erros:
thiago@thiago:~$ virsh list --all
Id Name State
----------------------------------------------------
Instalando o Docker
Para saber os passos para instalação do Docker, fiz um post no qual explica os detalhes da instalação desta ferramenta. Você pode visualizar o seguinte artigo clicando aqui.
Repositório clone Vrnetlab
A maneira mais rápida de obter o Vrnetlab é cloná-lo de seu repositório:
thiago@thiago:~$ mkdir netdev
thiago@thiago:~$ cd netdev/
thiago@thiago:~/netdev$ git clone https://github.com/plajjan/vrnetlab.git
E é isso! Vrnetlab e todas as suas dependências estão instaladas e estamos prontos para começar!
Construindo imagens com dispositivos virtuais
Mencionei anteriormente que o Vrnetlab fornece scripts de construção para muitos roteadores virtuais de diferentes fornecedores. O que não conseguimos, entretanto, são as imagens reais.
As licenças que vêm com os aparelhos não permitem o reempacotamento e distribuição de outras fontes além dos canais oficiais. Teremos que obter imagens nós mesmos.
Se você não tem nenhuma imagem e deseja apenas acompanhar este post, você pode se cadastrar gratuitamente na Arista e baixar a imagem neste link:
Você precisará de duas imagens:
- Aboot-veos-serial-8.0.0.iso
- vEOS-lab-4.18.10M.vmdk
Elas estão destacadas em vermelho na captura de tela abaixo:
Depois de fazer o download das imagens, você deverá copiá-las para o diretório veos
dentro do diretório vrnetlab
. O resultado final deve corresponder à saída abaixo:
thiago@thiago:~/netdev/vrnetlab/veos$ ls
Aboot-veos-serial-8.0.0.iso docker Makefile README.md vEOS-lab-4.18.10M.vmdk
Com os arquivos no lugar, estamos prontos para iniciar a construção da imagem Docker executando o make comando dentro do diretório:
thiago@thiago:~/netdev/vrnetlab/veos$ make
Se tudo funcionou corretamente, agora você deve ter uma nova imagem Docker disponível localmente. Você pode confirmar isso executando o comando docker images
:
thiago@thiago:~/netdev/vrnetlab/veos$ docker images vrnetlab/vr-veos
REPOSITORY TAG IMAGE ID CREATED SIZE
vrnetlab/vr-veos 4.18.10M 12915790e700 About a minute ago 889MB
Opcionalmente, você pode renomear a imagem, para torná-lo mais curto ou se quiser enviá-la para o registro do Docker local:
thiago@thiago:~/netdev/vrnetlab/veos$ docker tag vrnetlab/vr-veos:4.18.10M veos:4.18.10M
Agora nossa imagem tem 02 nomes diferentes:
thiago@thiago:~/netdev/vrnetlab/veos$ docker images | grep 4.18.10
veos 4.18.10M 12915790e700 5 minutes ago 889MB
vrnetlab/vr-veos 4.18.10M 12915790e700 5 minutes ago 889MB
Você pode excluir com segurança o nome padrão se desejar:
thiago@thiago:~/netdev/vrnetlab/veos$ docker rmi vrnetlab/vr-veos:4.18.10M
Untagged: vrnetlab/vr-veos:4.18.10M
thiago@thiago:~/netdev/vrnetlab/veos$
thiago@thiago:~/netdev/vrnetlab/veos$ docker images | grep 4.18.10
veos 4.18.10M 12915790e700 7 minutes ago 889MB
Lançando dispositivos virtuais
Até aqui, todas as peças estão no lugar para que possamos executar nosso primeiro router virtual no Docker!
O comando docker run -d --name veos1 --privileged veos:4.18.10M
executa o contêiner.
Isso diz ao Docker para iniciar um novo contêiner em segundo plano usando a imagem que construímos. O argumento --privileged
é exigido pelo KVM, o argumento --name
fornece nosso nome escolhido para o contêiner e o último argumento veos:4.18.10M
é o nome da imagem.
Aqui está o comando em ação:
thiago@thiago:~/netdev/vrnetlab/veos$ docker run -d --name veos1 --privileged veos:4.18.10M
1a83898943fd3f16cb132868994cce4906b9ec1bacb5d18694a409ab24ce3eb7
thiago@thiago:~/netdev/vrnetlab/veos$
thiago@thiago:~/netdev/vrnetlab/veos$ docker ps | grep veos1
1a83898943fd veos:4.18.10M "/launch.py" 16 seconds ago Up 16 seconds (health: starting) 22/tcp, 80/tcp, 443/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp veos1
Se tudo funcionar, devemos obter imediatamente o ID do contêiner. Depois disso, podemos verificar se o contêiner está rodando com o comando docker ps
.
No nosso caso, o contêiner está ativo, mas ainda não está pronto, isso ocorre porque temos (health: starting)
na saída do comando docker ps
.
Vrnetlab cria imagens Docker com um script de verificação de integridade que nos permite verificar se o contêiner está totalmente ativo e pronto para a ação.
Com o vEOS, geralmente leva cerca de 3 minutos para que o contêiner esteja totalmente estável:
thiago@thiago-ThinkPad-T430:~/netdev/vrnetlab/veos$ docker ps | grep veos1
1a83898943fd veos:4.18.10M "/launch.py" 2 minutes ago Up 2 minutes (unhealthy) 22/tcp, 80/tcp, 443/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp veos1
Aqui vamos nós, podemos ver que nosso dispositivo virtual está totalmente instalado e totalmente estável. Então o que vem depois? Provavelmente deveríamos entrar no dispositivo e brincar, certo?
Espere, mas como fazemos isso? Leia mais para descobrir!
Funções bash auxiliares
Vrnetlab vem com algumas funções úteis definidas no arquivo [vrnetlab.sh](http://vrnetlab.sh)
. Se você usar bash
como shell, poderá carregá-los com o comando . vrnetlab.sh
ou source vrnetlab.sh
. Se isso não funcionar, você terá que consultar o manual do seu shell:
thiago@thiago:~/netdev/vrnetlab$ ls | grep vrnet
. vrnetlab.sh
thiago@thiago:~/netdev/vrnetlab$ . vrnetlab.sh
Uma vez que seu shell carregou as funções, você deve ter acesso ao comando vrcons CONTAINER_NAME
- Conecta-se à console do seu dispositivo virtual.
thiago@thiago-ThinkPad-T430:~/netdev/vrnetlab$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1a83898943fd veos:4.18.10M "/launch.py" 27 minutes ago Up 27 minutes (healthy) 22/tcp, 80/tcp, 443/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp veos1
thiago@thiago:~/netdev/vrnetlab$
thiago@thiago:~/netdev/vrnetlab$ vr_mgmt_ip veos1
172.17.0.2
thiago@thiago:~/netdev/vrnetlab$
thiago@thiago:~/netdev/vrnetlab$ vrcons veos1
Trying 172.17.0.2...
Connected to 172.17.0.2.
Escape character is '^]'.
localhost#
ssh CONTAINER_NAME [USERNAME]
- loga no dispositivo via ssh. Se USERNAME
não for fornecido, o nome de usuário é admin
e será usado com a senha padrão sendo VR-netlab9
.
Identificando usuários dentro do router:
localhost# who
Line User Host(s) Idle Location
* 1 con 0 admin idle 00:00:42 -
Bom, olhem isso, estamos dentro e tudo parece estar em ordem!
Costumo fazer exatamente isso, iniciar um contêiner, testar alguns comandos e pronto. É surreal a rapidez com que você pode rodar um dispositivo de teste e se livrar dele quando terminar.
Quase nada está envolvido na configuração. Você só precisa esperar alguns minutos e está tudo pronto para você.
Ótimo, construímos um contêiner com uma imagem vEOS, conseguimos executar alguns comandos. Mas, embora isso possa ser ótimo para criar rapidamente alguns comandos, queremos mais, queremos conectar dispositivos virtuais entre si.
Configurando enlace entre contêineres
Antes de qualquer coisa, vamos preparar o segundo contêiner para que esteja pronto quando precisarmos:
thiago@thiago:~/netdev/vrnetlab$ docker run -d --name veos2 --privileged veos:4.18.10M
b056ba58096647f882885421dd7b68d287c68d7993906a0fdeff21191222415f
Depois iremos verificar o endereço da interface de mngt dos dois routers, vale ressaltar que as duas interfaces de mngt podem estar na mesma rede, não tem problema:
thiago@thiago:~/netdev/vrnetlab$ vr_mgmt_ip veos1
172.17.0.2
thiago@thiagothiago@thiago-ThinkPad-T430:~/netdev/vrnetlab$ vr_mgmt_ip veos2
172.17.0.3
Para criar conexões entre dispositivos, precisamos construir uma imagem Docker especial chamada vr-xcon
. Os contêineres que usam esta imagem fornecerão conectividade entre nossas rotas virtuais.
Para construir esta imagem, navegue até o vr-xcon
. Assim que estiver no diretório, inserir o comando make
. Os scripts dentro das dependências do Vrnetlab farão o resto.
thiago@thiago:~/netdev/vrnetlab/vr-xcon$ ls
ci-builder-image CONTRIBUTING.md Makefile nxos sros vqfx vr-xcon
CODE_OF_CONDUCT.md csr makefile.include openwrt topology-machine vr-bgp vsr1000
common git-lfs-repo.sh makefile-install.include README.md veos vrnetlab.sh xrv
config-engine-lite LICENSE makefile-sanity.include routeros vmx vrp xrv9k
thiago@thiago:~/netdev/vrnetlab/vr-xcon$
thiago@thiago:~/netdev/vrnetlab/vr-xcon$ make
E para confirmar que a imagem já está disponível:
thiago@thiago:~/netdev/vrnetlab/vr-xcon$:~/netdev/vrnetlab/vr-xcon$ docker images | grep xcon
vrnetlab/vr-xcon latest 9d2298bb92b0 4 minutes ago 147MB
Precisamos apenas mudar seu nome vr-xcon
para fazê-lo funcionar com scripts Vrnetlab.
thiago@thiago:~/netdev/vrnetlab/vr-xcon$ docker tag vrnetlab/vr-xcon:latest vr-xcon
thiago@thiago:~/netdev/vrnetlab/vr-xcon$
thiago@thiago:~/netdev/vrnetlab/vr-xcon$ docker images | grep "^vr-xcon"
vr-xcon latest 9d2298bb92b0 6 minutes ago 147MB
Em seguida, iremos configurar o enlace virtual entre o contêiner veos1 e veos2:
thiago@thiago:~/netdev/vrnetlab$ cd vr-xcon/
thiago@thiago:~/netdev/vrnetlab/vr-xcon$ docker run -d --name vr-xcon1 --link veos1 --link veos2 vr-xcon --p2p veos1/2--veos2/2
d8c3399e7b6c946cb47f12f78776d40adf1e9a4b0641ad8cfd43c64a3dcd1548
Abaixo está o detalhamento do comando:
docker run -d
- Isso diz ao Docker para executar o contêiner em segundo plano.
--name vr-xcon1
- Queremos que nosso contêiner tenha um nome, você pode chamá-lo de algo diferente, se quiser.
--link veos1 --link veos2vr-xconveos1veos2
- Aqui, dizemos ao Docker para se conectar a contêineres e, isso permite que eles descubram e conversem entre si.
vr-xcon1
A segunda referênciavr-xcon
é o nome da imagem a ser executada.
- Aqui, dizemos ao Docker para se conectar a contêineres e, isso permite que eles descubram e conversem entre si.
--p2p veos1/2--veos2/2
- Finalmente, temos argumentos que são passados para o contêiner. Aqui, pedimos uma conexão ponto a ponto entre a porta 2 do contêiner veos1 e também a porta 2 do contêiner veos2.
- A porta 1 é mapeada para a porta de gerenciamento, de forma que a porta 2 será Ethernet1 dentro do roteador virtual.
Parece promissor, o contêiner está ativo e os logs estão vazios, portanto, nenhum erro foi relatado.
thiago@thiago:~/netdev/vrnetlab$ docker logs vr-xcon
Espero que agora você possa ver como tudo se encaixa. Para confirmar se o contêiner está instalado e funcionando, executaremos o comando docker ps
para verificarmos os registros do contêiner:
thiago@thiago:~/netdev/vrnetlab$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d8c3399e7b6c vr-xcon "/xcon.py --p2p veos…" 22 minutes ago Up 22 minutes vr-xcon1
75c2ffdec332 veos:4.18.10M "/launch.py" 26 minutes ago Up 26 minutes (healthy) 22/tcp, 80/tcp, 443/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp veos2
b056ba580966 veos:4.18.10M "/launch.py" 27 minutes ago Up 27 minutes (healthy) 22/tcp, 80/tcp, 443/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp veos1
E os mesmos links criados usando três instâncias de vr-xcon
contêiner. Caso temos mais roteadores, podemos rodar diversas instâncias de contêiner interligando essas instâncias em p2p. Segue o formato como exemplo:
docker run -d --name vr-xcon1 --link veos1 --link veos2 vr-xcon --p2p veos1/2--veos2/2
docker run -d --name vr-xcon2 --link veos1 --link veos3 vr-xcon --p2p veos1/10--veos3/10
docker run -d --name vr-xcon3 --link veos2 --link veos3 vr-xcon --p2p veos2/5-veos3/5
Como estamos rodando 02 routers virtuais dentro de 02 contêineres, iremos deixar apenas a instância vr-xcon1
rodando, está instância conecta o router veos1
e veos2
.
Pronto, enlace entre os contêineres já estão configurados e estamos prontos para fazer login nos dispositivos e confirmar se nossa conexão recém-criada está ativa.
Vamos configurar os nomes de host para que a saída da verificação LLDP os inclua. Em seguida, iremos configurar os IPs e tentaremos fazer o ping entre os routers.
Iremos logar nos dois routers e alterar o hostname:
thiago@thiago:~/netdev/vrnetlab$ vrcons veos1
Trying 172.17.0.2...
Connected to 172.17.0.2.
Escape character is '^]'.
localhost#conf t
localhost(config)#host veos1
thiago@thiago:~/netdev/vrnetlab$ vrcons veos2
Trying 172.17.0.3...
Connected to 172.17.0.3.
Escape character is '^]'.
localhost#en
localhost#conf t
localhost(config)#host veos2
Agora iremos indexar um endereço IP na interface eth1
nos dois routers e colocando-os na mesma rede para que os dois routers virtuais possam se comunicarem:
veos1(config)#int eth1
veos1(config-if-Et1)#no switchport
veos1(config-if-Et1)#ip address 10.0.1.1/30
veos1(config-if-Et1)#end
veos1#
veos2(config)#int eth1
veos2(config-if-Et1)#no switchport
veos2(config-if-Et1)#ip address 10.0.1.2/30
veos2(config-if-Et1)#end
veos2#
veos2#sh lldp neighbors
Last table change time : 0:03:36 ago
Number of table inserts : 1
Number of table deletes : 0
Number of table drops : 0
Number of table age-outs : 0
Port Neighbor Device ID Neighbor Port ID TTL
Et1 veos1 Ethernet1 120
veos2#
veos2#ping 10.0.1.1
PING 10.0.1.1 (10.0.1.1) 72(100) bytes of data.
80 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=643 ms
80 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=209 ms
80 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=422 ms
80 bytes from 10.0.1.1: icmp_seq=4 ttl=64 time=319 ms
80 bytes from 10.0.1.1: icmp_seq=5 ttl=64 time=145 ms
--- 10.0.1.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 2341ms
rtt min/avg/max/mdev = 145.867/348.204/643.610/175.346 ms, ipg/ewma 585.485/488.423 ms
veos2#
Pronto, conseguimos instalar as imagens no Vrnetlab via Docker, conseguimos subir estas imagens no Docker e implantamos dois routers virtuais e executamos uma instância baseado apenas no link entre eles para que eles consigam se conectarem virtualmente.
Dessa forma, montamos nosso ambiente de lab virtual onde possamos rodar nossos pipelines de testes automatizados.
Finalmente, para remover links, iremos remover o contêiner usado para criar conexões:
thiago@thiago:~/netdev/vrnetlab$ docker rm -f vr-xcon1
vr-xcon1
Depois de terminar o laboratório, caso queira se livrar dos contêineres, você deve executar o comando docker rm -f
:
thiago@thiago:~/netdev/vrnetlab$ docker rm -f veos1 veos2
E com isso seus contêineres irão embora.
Docker Compose
Agora você deve saber como trazer os contêineres manualmente e como conectá-los. Pode ser um pouco enfadonho, porém, se você costuma usar a mesma topologia para teste. Por que não escrever algum tipo de receita de laboratório que possamos lançar com um único comando?
Bem, podemos fazer isso e faremos!
Existem muitas maneiras de fazer isso e, para nosso exemplo, usaremos o Docker Compose. Vamos criar um docker-compose.yml
arquivo que trará duas imagens virtuais e as conectam. Como bônus.
Dica: Se você não instalou o Docker Compose, você pode obtê-lo executando sudo apt install docker-compose
no Ubuntu. Outras distros também devem tê-lo disponível.
O Compose usa o docker-compose.yml
arquivo para definir os serviços a serem executados juntos.
Eu escrevi um desses arquivos que trará 2 dispositivos vEOS com um link entre eles:
thiago@thiago:~/netdev/dcompose/veos-lab$ cat compose.yml
---
version: "3"
services:
veos1:
image: veos:4.18.10M
container_name: veos1
privileged: true
ports:
- "9001:22"
network_mode: default
veos2:
image: veos:4.18.10M
container_name: veos2
privileged: true
ports:
- "9002:22"
network_mode: default
vr-xcon:
image: vr-xcon
container_name: vr-xcon1
links:
- veos1
- veos2
command: --p2p veos1/2--veos2/2
depends_on:
- veos1
- veos2
network_mode: default
Se for a primeira vez que você vê docker-compose.yml
, não se preocupe, estou dividindo para você abaixo.
- A primeira linha define a versão do formato Compose,
version: "3"
é bastante antigo, mas é amplamente suportado. - Na seção
services
, definimos 03 contêineres que desejamos lançar. Em primeiro lugar, definimos emservices
,veos1
.
veos1:
image: veos:4.18.10M
container_name: veos1
privileged: true
ports:
- "9001:22"
network_mode: default
veos1
- Nome do nosso serviço onde definimos contêiner.
image: veos:4.18.10Mveos:4.18.10M
- Queremos que nosso contêiner use imagem.
container_name: veos1
- Substitua o nome do contêiner padrão para facilitar a referência posterior.
privileged: true
- Precisamos do modo privilegiado para KVM.
ports:network_mode: default
- Dizemos ao Docker Compose para usar a porta 9001 do host para se conectar à porta 22 no contêiner.
- Pedimos ao Compose para usar a ponte Docker padrão, por padrão o Docker Compose criaria uma rede separada e o Vrnetlab não gosta disso.
A definição de
veos2
é a mesma, exceto que mapeamos a porta 22 para a porta 9002 no host.
- Finalmente, temos
vr-xcon
. Itens de nota aqui:links:-linkvr-xcon
- Equivalente ao argumento que usamos ao executar o contêiner manualmente. Os contêineres listados serão vinculados ao contêiner.
command: --p2p veos1/2--veos2/2depends_on:veos1veos2
- É assim que o comando é passado para o contêiner ao usar o Docker Compose.
- Dizemos ao Compose para aguardar o serviço listado, aqui veos1 e veos2 antes de iniciar o serviço vr-xcon.
Com tudo isso instalado, estamos prontos para lançar nosso laboratório virtual usando o comando docker-compose up -d
, executado no diretório que contém docker-compose.yml
. Opção -d
faz com que o Compose seja executado em segundo plano.
Para checarmos parâmetros do router que está sendo executado pelo contêiner Docker, iremos executar o seguinte comando docker inspect <name_conteiner>
:
:~/netdev/vrnetlab$ docker inspect veos1
[
{
"Id": "812a511d67acef259d32c0ff5c663875dce01e2d2f5333c2ab8a02f94e7fd1a0",
"Created": "2020-11-23T18:30:00.839862792Z",
"Path": "/launch.py",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 11953,
"ExitCode": 0,
"Error": "",
"StartedAt": "2020-11-23T18:30:01.536055525Z",
"FinishedAt": "0001-01-01T00:00:00Z",
"Health": {
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2020-11-23T23:58:46.083872099-03:00",
"End": "2020-11-23T23:58:46.30971068-03:00",
"ExitCode": 0,
"Output": "running\n"
},
{
"Start": "2020-11-23T23:59:16.326774884-03:00",
"End": "2020-11-23T23:59:16.530025261-03:00",
"ExitCode": 0,
"Output": "running\n"
},
{
"Start": "2020-11-23T23:59:46.547713439-03:00",
"End": "2020-11-23T23:59:46.717139617-03:00",
"ExitCode": 0,
"Output": "running\n"
},
{
"Start": "2020-11-24T00:00:16.750926346-03:00",
"End": "2020-11-24T00:00:16.890863143-03:00",
"ExitCode": 0,
"Output": "running\n"
},
{
"Start": "2020-11-24T00:00:46.913430867-03:00",
"End": "2020-11-24T00:00:47.064266142-03:00",
"ExitCode": 0,
"Output": "running\n"
}
]
}
},
"Image": "sha256:12915790e700351103bf29a327ed7c1834bdee80d1b51f701ef1f2da483e7e7d",
"ResolvConfPath": "/var/lib/docker/containers/812a511d67acef259d32c0ff5c663875dce01e2d2f5333c2ab8a02f94e7fd1a0/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/812a511d67acef259d32c0ff5c663875dce01e2d2f5333c2ab8a02f94e7fd1a0/hostname",
"HostsPath": "/var/lib/docker/containers/812a511d67acef259d32c0ff5c663875dce01e2d2f5333c2ab8a02f94e7fd1a0/hosts",
"LogPath": "/var/lib/docker/containers/812a511d67acef259d32c0ff5c663875dce01e2d2f5333c2ab8a02f94e7fd1a0/812a511d67acef259d32c0ff5c663875dce01e2d2f5333c2ab8a02f94e7fd1a0-json.log",
"Name": "/veos1",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "unconfined",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"Capabilities": null,
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": true,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": [
"label=disable"
],
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": null,
"ReadonlyPaths": null
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/e3e7cde68f2f3d9b77b5d0cc9bed1c5f280933e6026d4cd9782dbf107dfc010e-init/diff:/var/lib/docker/overlay2/58675c879eaf211b096100236b95eb628e42eaa0188487fdf49ed725da53fc71/diff:/var/lib/docker/overlay2/aef9c9cb1735e9d5506a97136ad8f48957ee74e5567950d39c339e7f4cc9ab57/diff:/var/lib/docker/overlay2/61908948e0b5f02c1c5f8cbf6585595698b7854cd1cbb957562b786f998431c0/diff:/var/lib/docker/overlay2/4964b80c7ffcab358a52aa13485530e5801527eaaecb88701007ac31f7205c38/diff:/var/lib/docker/overlay2/dd0db7422a822df51147fc3aa95916b4f750865111a6057a45cb5c7a56a2950c/diff",
"MergedDir": "/var/lib/docker/overlay2/e3e7cde68f2f3d9b77b5d0cc9bed1c5f280933e6026d4cd9782dbf107dfc010e/merged",
"UpperDir": "/var/lib/docker/overlay2/e3e7cde68f2f3d9b77b5d0cc9bed1c5f280933e6026d4cd9782dbf107dfc010e/diff",
"WorkDir": "/var/lib/docker/overlay2/e3e7cde68f2f3d9b77b5d0cc9bed1c5f280933e6026d4cd9782dbf107dfc010e/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "812a511d67ac",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"10000/tcp": {},
"10001/tcp": {},
"10002/tcp": {},
"10003/tcp": {},
"10004/tcp": {},
"10005/tcp": {},
"10006/tcp": {},
"10007/tcp": {},
"10008/tcp": {},
"10009/tcp": {},
"10010/tcp": {},
"10011/tcp": {},
"10012/tcp": {},
"10013/tcp": {},
"10014/tcp": {},
"10015/tcp": {},
"10016/tcp": {},
"10017/tcp": {},
"10018/tcp": {},
"10019/tcp": {},
"10020/tcp": {},
"10021/tcp": {},
"10022/tcp": {},
"10023/tcp": {},
"10024/tcp": {},
"10025/tcp": {},
"10026/tcp": {},
"10027/tcp": {},
"10028/tcp": {},
"10029/tcp": {},
"10030/tcp": {},
"10031/tcp": {},
"10032/tcp": {},
"10033/tcp": {},
"10034/tcp": {},
"10035/tcp": {},
"10036/tcp": {},
"10037/tcp": {},
"10038/tcp": {},
"10039/tcp": {},
"10040/tcp": {},
"10041/tcp": {},
"10042/tcp": {},
"10043/tcp": {},
"10044/tcp": {},
"10045/tcp": {},
"10046/tcp": {},
"10047/tcp": {},
"10048/tcp": {},
"10049/tcp": {},
"10050/tcp": {},
"10051/tcp": {},
"10052/tcp": {},
"10053/tcp": {},
"10054/tcp": {},
"10055/tcp": {},
"10056/tcp": {},
"10057/tcp": {},
"10058/tcp": {},
"10059/tcp": {},
"10060/tcp": {},
"10061/tcp": {},
"10062/tcp": {},
"10063/tcp": {},
"10064/tcp": {},
"10065/tcp": {},
"10066/tcp": {},
"10067/tcp": {},
"10068/tcp": {},
"10069/tcp": {},
"10070/tcp": {},
"10071/tcp": {},
"10072/tcp": {},
"10073/tcp": {},
"10074/tcp": {},
"10075/tcp": {},
"10076/tcp": {},
"10077/tcp": {},
"10078/tcp": {},
"10079/tcp": {},
"10080/tcp": {},
"10081/tcp": {},
"10082/tcp": {},
"10083/tcp": {},
"10084/tcp": {},
"10085/tcp": {},
"10086/tcp": {},
"10087/tcp": {},
"10088/tcp": {},
"10089/tcp": {},
"10090/tcp": {},
"10091/tcp": {},
"10092/tcp": {},
"10093/tcp": {},
"10094/tcp": {},
"10095/tcp": {},
"10096/tcp": {},
"10097/tcp": {},
"10098/tcp": {},
"10099/tcp": {},
"161/udp": {},
"22/tcp": {},
"443/tcp": {},
"5000/tcp": {},
"80/tcp": {},
"830/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"DEBIAN_FRONTEND=noninteractive"
],
"Cmd": null,
"Healthcheck": {
"Test": [
"CMD",
"/healthcheck.py"
]
},
"Image": "veos:4.18.10M",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": [
"/launch.py"
],
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "4cfbd9467fabc4d8c70debce49b5990c0c1d6313e95ba44179d6d082d3974bc9",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"10000/tcp": null,
"10001/tcp": null,
"10002/tcp": null,
"10003/tcp": null,
"10004/tcp": null,
"10005/tcp": null,
"10006/tcp": null,
"10007/tcp": null,
"10008/tcp": null,
"10009/tcp": null,
"10010/tcp": null,
"10011/tcp": null,
"10012/tcp": null,
"10013/tcp": null,
"10014/tcp": null,
"10015/tcp": null,
"10016/tcp": null,
"10017/tcp": null,
"10018/tcp": null,
"10019/tcp": null,
"10020/tcp": null,
"10021/tcp": null,
"10022/tcp": null,
"10023/tcp": null,
"10024/tcp": null,
"10025/tcp": null,
"10026/tcp": null,
"10027/tcp": null,
"10028/tcp": null,
"10029/tcp": null,
"10030/tcp": null,
"10031/tcp": null,
"10032/tcp": null,
"10033/tcp": null,
"10034/tcp": null,
"10035/tcp": null,
"10036/tcp": null,
"10037/tcp": null,
"10038/tcp": null,
"10039/tcp": null,
"10040/tcp": null,
"10041/tcp": null,
"10042/tcp": null,
"10043/tcp": null,
"10044/tcp": null,
"10045/tcp": null,
"10046/tcp": null,
"10047/tcp": null,
"10048/tcp": null,
"10049/tcp": null,
"10050/tcp": null,
"10051/tcp": null,
"10052/tcp": null,
"10053/tcp": null,
"10054/tcp": null,
"10055/tcp": null,
"10056/tcp": null,
"10057/tcp": null,
"10058/tcp": null,
"10059/tcp": null,
"10060/tcp": null,
"10061/tcp": null,
"10062/tcp": null,
"10063/tcp": null,
"10064/tcp": null,
"10065/tcp": null,
"10066/tcp": null,
"10067/tcp": null,
"10068/tcp": null,
"10069/tcp": null,
"10070/tcp": null,
"10071/tcp": null,
"10072/tcp": null,
"10073/tcp": null,
"10074/tcp": null,
"10075/tcp": null,
"10076/tcp": null,
"10077/tcp": null,
"10078/tcp": null,
"10079/tcp": null,
"10080/tcp": null,
"10081/tcp": null,
"10082/tcp": null,
"10083/tcp": null,
"10084/tcp": null,
"10085/tcp": null,
"10086/tcp": null,
"10087/tcp": null,
"10088/tcp": null,
"10089/tcp": null,
"10090/tcp": null,
"10091/tcp": null,
"10092/tcp": null,
"10093/tcp": null,
"10094/tcp": null,
"10095/tcp": null,
"10096/tcp": null,
"10097/tcp": null,
"10098/tcp": null,
"10099/tcp": null,
"161/udp": null,
"22/tcp": null,
"443/tcp": null,
"5000/tcp": null,
"80/tcp": null,
"830/tcp": null
},
"SandboxKey": "/var/run/docker/netns/4cfbd9467fab",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "fc7393c03a71a38774dfa3e7af807f40f702474b4263f391e08879de18bc3037",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "4d8c0864e23de306156f1541bdfc181ab4541b774e5190fe7dadb589f14607e6",
"EndpointID": "fc7393c03a71a38774dfa3e7af807f40f702474b4263f391e08879de18bc3037",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
]
Os dados mais importantes a serem analisados são os últimos, onde podemos visualizar por exemplo, a chave (IPAddress) e o valor (172.17.0.2) "IPAddress": "172.17.0.2"
onde seria o endereço para acessarmos via SSH.
Pronto, imagens devidamente executadas e rodando via Docker, agora iremos montar nossa estrutura de testes automatizados integrados com o Github Action.
Inicializando o GitHub Action
Para mostrar a vocês como integrar sua plataforma de laboratório local (neste caso, baseado em Vrnetlab), vamos usar este repositório simples que criei no GitHub:
Para termos uma ação no GitHub, precisaríamos ter um diretório .github/workflows/
e dentro deste diretório armazenar um arquivo do tipo fluxo de trabalho. Neste repositório contém o arquivo main.yml
:
thiago@thiago:~/hello-github-actions$ ls -l .github/workflows/
total 8
-rw-r--r-- 1 thiago thiago 678 nov 23 18:16 main.yml
E se verificarmos o arquivo main.yml
, veremos toda a magia neste arquivo:
thiago@thiago:~/hello-github-actions$ cat .github/workflows/main.yml
---
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: self-hosted # INFO AO GIT QUE ESTE FLUXO DE TRABALHO É EXECUTADO LOCAL
steps:
- uses: actions/checkout@v2 (TIRAR DÚVIDA SOBRE ESSA LINHA)
- name: Run playbook
uses: dawidd6/action-ansible-playbook@v2 (TIRAR DÚVIDA SOBRE ESSA LINHA)
with:
playbook: vrnetlab.yml
directory: ./
inventory: |
[all]
veos1 ansible_host="172.17.0.2" ansible_ssh_user="vrnetlab" ansible_ssh_pass="VR-netlab9" ansible_connection="network_cli" ansible_network_os="eos"
veos2 ansible_host="172.17.0.3" ansible_ssh_user="vrnetlab" ansible_ssh_pass="VR-netlab9" ansible_connection="network_cli" ansible_network_os="eos"
options: |
--verbose
Vamos verificar este arquivo com mais detalhes…
Há um nome de fluxo de trabalho, e também definimos em quais ramificações / ações do git esse fluxo de trabalho será executado:
name: CI
on: [push, pull_request]
Ponto importante: informamos ao GitHub que este fluxo de trabalho deve ser executado em nosso ambiente local. Posteriormente, instalaremos um executor GitHub auto-hospedado em nossa máquina:
runs-on: self-hosted
Para testar um Ansible Playbook simples, usamos a ação predefinida disponível para todos no GitHub Action Marketplace: dawidd6/action-ansible-playbook@v2
.
Iremos rodar um playbook simples apenas para teste da integração continua, segue o manual descrito abaixo:
:~/hello-github-actions$ cat vrnetlab.yml
---
- name: Simple GitHub Action playbook
hosts: ansible
vars: # Variável de conexão
ansible_connection: network_cli
ansible_network_os: eos
ansible_user: vrnetlab
ansible_ssh_pass: VR-netlab9
tasks:
- name: playbook task1
eos_command:
commands: show ip int brief
Comandos Git básicos
Nesta seção, iremos desmistificar alguns comandos básicos que iremos utilizar para interagir com o nosso repositório local e remoto:
git add -A
- É útil para você mudar algo ou alterar algum arquivo ou alocar algum arquivo ao diretório.
git commit -m "teste"
- valida a alteração dentro do repositório para sincronizar com o repositório na cloud.
Dica: commit
confirmará as alterações no repositório local, enquanto o push
enviará as alterações para um repositório remoto.
git pull origin master
- Valida a mudança dentro da branch master
git push origin master
- Sincroniza a mudança local para remoto
Dica: git push
, git pull
- Sincroniza o repositório local com seu repositório remoto associado . push
- aplicar alterações de local para remoto, pull
- aplicar alterações de remoto para local.
GitHub Runner auto-hospedado
Este recurso faz com que sua máquina em execução se conecta ao GitHub usando o aplicativo de execução auto-hospedado GitHub Actions.
O runner é apenas um programa simples escrito em .NET (mesmo na versão Linux) que é de código aberto. Ele é executado em sua máquina local como outro programa de usuário. Ele terá acesso a todas as bibliotecas, binários e ferramentas instaladas em sua máquina.
Por exemplo, se o seu código precisa executar o Ansible, então sua máquina deve ter instalado o Ansible, para que o executor possa usá-lo.
Para utilizarmos o recurso do runner de forma local, precisamos ir no repositório onde estão nossos arquivos, ir em settings - action - add runner
:
Após clicar em add runner
, é só seguir as instruções sugeridas pelo GitHub.
Download:
thiago@thiago:~$ mkdir actions-runner && cd actions-runner
thiago@thiago:~/actions-runner$
thiago@thiago:~/actions-runner$ curl -O -L https://github.com/actions/runner/releases/download/v2.274.2/actions-runner-linux-x64-2.274.2.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 665 100 665 0 0 1027 0 --:--:-- --:--:-- --:--:-- 1026
100 70.4M 100 70.4M 0 0 408k 0 0:02:56 0:02:56 --:--:-- 646k
thiago@thiago:~/actions-runner$
thiago@thiago:~/actions-runner$ tar xzf ./actions-runner-linux-x64-2.274.2.tar.gz
Configure:
thiago@thiago:~/actions-runner$ ./config.sh --url https://github.com/tporfirio/hello-github-actions --token AO3UA2D23JKRGAH2EDTHWMK7XQK2G
--------------------------------------------------------------------------------
| ____ _ _ _ _ _ _ _ _ |
| / ___(_) |_| | | |_ _| |__ / \ ___| |_(_) ___ _ __ ___ |
| | | _| | __| |_| | | | | '_ \ / _ \ / __| __| |/ _ \| '_ \/ __| |
| | |_| | | |_| _ | |_| | |_) | / ___ \ (__| |_| | (_) | | | \__ \ |
| \____|_|\__|_| |_|\__,_|_.__/ /_/ \_\___|\__|_|\___/|_| |_|___/ |
| |
| Self-hosted runner registration |
| |
--------------------------------------------------------------------------------
# Authentication
√ Connected to GitHub
# Runner Registration
Enter the name of runner: [press Enter for thiago]
This runner will have the following labels: 'self-hosted', 'Linux', 'X64'
Enter any additional labels (ex. label-1,label-2): [press Enter to skip]
√ Runner successfully added
√ Runner connection is good
# Runner settings
Enter name of work folder: [press Enter for _work]
√ Settings Saved.
thiago@thiago:~/actions-runner$ ./run.sh
√ Connected to GitHub
2020-11-23 19:11:50Z: Listening for Jobs
Se tudo tiver ok, você verá seu runner listado como idle
:
Execução de ação GitHub
Portanto, agora temos tudo pronto para testar o ambiente de laboratório: Os roteadores, o repositório GitHub e o fluxo de trabalho de ação e o Runner na máquina local. Assim que tivermos um push
/ commit
dentro do repositório do projeto após ser feita alguma alteração no playbook Ansible, poderemos ver como um pipeline CI, neste caso:
Recapitulação e resumo
OK, até agora tudo bem, vamos resumir as etapas que seguimos nesta postagem do blog:
- Configuramos um laboratório local simples usando vrnetlab e docker, com dois roteadores funcionando localmente.
- Nós criamos um repositório Github simples, com uma ação GitHub simples, baseado em Ansible.
- Instalamos um runner do GitHub na máquina local e vinculamos esse runner à ação do GitHub.
- Enviamos um commit para o repositório GiiHub e o GitHub lançou o Action, neste caso, uma “versão de show” foi executada com um Ansible Playbook
Portanto, com um pc / laptop de máquina local decente, você pode executar seu próprio ambiente de laboratório de rede e usá-lo de plataformas CI / CD como GitHub ou Gitlab e testar seu código com muita facilidade.
Nos próximos posts, farei uso desse ambiente para falar sobre o Desenvolvimento Orientado a Testes no campo NetDevOps.
Fique ligado.
Leave a Comment