Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Neste artigo, mostrarei como configurar AbertoDaylight para trabalhar com equipamentos de rede, e também mostrar como usar Postman e simples RESTCONF pedidos, este equipamento pode ser controlado. Não trabalharemos com hardware, mas implantaremos pequenos laboratórios virtuais com um único roteador usando Vrnetlab sobre Ubuntu LTS 20.04.

Mostrarei primeiro as configurações detalhadas usando o exemplo de um roteador Juniper vMX 20.1R1.11, e então comparamos com a configuração Cisco xRV9000 7.0.2.

Conteúdo

  • Conhecimento requerido
  • Часть 1: discutir brevemente OpenDaylight (doravante ODL), Postman и Vrnetlab e por que precisamos deles
  • Часть 2: descrição do laboratório virtual
  • Часть 3: customizar AbertoDaylight
  • Часть 4: customizar Vrnetlab
  • Часть 5: usando Postman conectar roteador virtual (Junípero vMX) para ODL
  • Часть 6: obter e alterar a configuração do roteador usando Postman и ODL
  • Часть 7: adicionar Cisco xRV9000
  • Conclusão
  • PS
  • Bibliografia

Conhecimento requerido

Para que o artigo não vire uma folha, omiti alguns detalhes técnicos (com links para literatura onde você pode ler sobre eles).

Nesse sentido, ofereço tópicos que seria bom (mas quase não necessário) saber antes de ler:

Parte 1: alguma teoria

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

  • Uma plataforma SDN aberta para gerenciar e automatizar todos os tipos de redes, suportada por Fundação Linux
  • Java dentro
  • Com base no nível de abstração de serviço orientado a modelo (MD-SAL)
  • Usa modelos YANG para gerar automaticamente APIs RESTCONF para dispositivos de rede

O módulo principal para gerenciamento de rede. É por meio dele que nos comunicaremos com os dispositivos conectados. Gerenciado através de sua própria API.

Você pode ler mais sobre o OpenDaylight aqui.

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

  • ferramenta de teste de API
  • Interface simples e fácil de usar

No nosso caso, estamos interessados ​​nele como um meio de enviar solicitações REST para a API OpenDaylight. Você pode, é claro, enviar solicitações manualmente, mas no Postman tudo parece muito claro e atende perfeitamente aos nossos propósitos.

Para aqueles que querem cavar: muitos materiais de treinamento foram escritos nele (por exemplo).

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

  • Ferramenta para implantar roteadores virtuais no Docker
  • Suporta: Cisco XRv, Juniper vMX, Arista vEOS, Nokia VSR, etc.
  • Open Source

Um instrumento muito interessante, mas pouco conhecido. No nosso caso, vamos usá-lo para executar Juniper vMX e Cisco xRV9000 em um Ubuntu 20.04 LTS normal.

Você pode ler mais sobre isso em página do projeto.

Parte 2: Laboratório

Neste tutorial, vamos configurar o seguinte sistema:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Как это работает

  • Junípero vMX sobe em Estivador recipiente (por meio Vrnetlab) e funciona como o roteador virtual mais comum.
  • ODL conectado ao roteador e permite controlá-lo.
  • Postman lançado em uma máquina separada e através dela enviamos comandos ODL: para conectar / remover o roteador, alterar a configuração, etc.

Comentário sobre o dispositivo do sistema

Junípero vMX и ODL exigem muitos recursos para sua operação estável. Apenas um vMX pede 6 Gb de RAM e 4 núcleos. Portanto, decidiu-se mover todos os "pesos pesados" para uma máquina separada (Heulett Packard Enterprise MicroServer ProLiant Gen8, Ubuntu 20.04 LTS). O roteador, claro, não "voa" nele, mas o desempenho é suficiente para pequenos experimentos.

Parte 3: Configurar o OpenDaylight

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

A versão atual do ODL no momento da redação deste artigo é Magnesium SR1

1) Instale Java Open JDK 11 (para uma instalação mais detalhada aqui)

ubuntu:~$ sudo apt install default-jdk

2) Encontre e baixe a versão mais recente ODL por isso
3) Descompacte o arquivo baixado
4) Vá para o diretório resultante
5) Lançamento ./bin/karaf

Nesta etapa ODL deve começar e nos encontraremos no console (a porta 8181 é usada para acesso externo, que usaremos mais tarde).

Em seguida, instale Recursos de EADprojetado para trabalhar com protocolos NETCONF и RESTCONF. Para fazer isso no console ODL nós executamos:

opendaylight-user@root> feature:install odl-netconf-topology odl-restconf-all

Esta é a configuração mais simples. ODL concluído. (Para mais detalhes, consulte aqui).

Parte 4: Configurando o Vrnetlab

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Preparação do sistema

Antes da instalação Vrnetlab você precisa instalar os pacotes necessários para sua operação. Como Estivador, git, sshpass:

ubuntu:~$ sudo apt update
ubuntu:~$ sudo apt -y install python3-bs4 sshpass make
ubuntu:~$ sudo apt -y install git
ubuntu:~$ sudo apt install -y 
    apt-transport-https ca-certificates 
    curl gnupg-agent software-properties-common
ubuntu:~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
ubuntu:~$ sudo add-apt-repository 
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu 
   $(lsb_release -cs) 
   stable"
ubuntu:~$ sudo apt update
ubuntu:~$ sudo apt install -y docker-ce docker-ce-cli containerd.io

Instalando o Vrnetlab

Para instalar Vrnetlab clone o repositório correspondente do github:

ubuntu:~$ cd ~
ubuntu:~$ git clone https://github.com/plajjan/vrnetlab.git

Ir para o diretório vrnetlab:

ubuntu:~$ cd ~/vrnetlab

Aqui você pode ver todos os scripts necessários para executar. Observe que um diretório correspondente foi criado para cada tipo de roteador:

ubuntu:~/vrnetlab$ ls
CODE_OF_CONDUCT.md  config-engine-lite        openwrt           vr-bgp
CONTRIBUTING.md     csr                       routeros          vr-xcon
LICENSE             git-lfs-repo.sh           sros              vrnetlab.sh
Makefile            makefile-install.include  topology-machine  vrp
README.md           makefile-sanity.include   veos              vsr1000
ci-builder-image    makefile.include          vmx               xrv
common              nxos                      vqfx              xrv9k

Crie uma imagem do roteador

Cada roteador suportado Vrnetlab, tem seu próprio procedimento de configuração exclusivo. Quando Junípero vMX só precisamos carregar o arquivo .tgz com o roteador (você pode baixá-lo em Site Oficial) para o diretório vmx e execute o comando make:

ubuntu:~$ cd ~/vrnetlab/vmx
ubuntu:~$ # Копируем в эту директорию .tgz архив с роутером
ubuntu:~$ sudo make

Construindo uma imagem vMX levará cerca de 10-20 minutos. É hora de ir tomar um café!

Por que tanto tempo, você pergunta?

Tradução responder autor a esta pergunta:

"Isso ocorre porque a primeira vez que o VCP (Control Plane) é iniciado, ele lê um arquivo de configuração que determina se ele será executado como um VRR VCP no vMX. Anteriormente, esse lançamento era feito durante a inicialização do Docker, mas isso significava que o VCP sempre foi reiniciado uma vez antes do roteador virtual ficar disponível, resultando em um longo tempo de inicialização (cerca de 5 minutos) -opção privilegiada, isso significa que o qemu funciona sem aceleração de hardware KVM e, portanto, a compilação leva muito tempo. Durante este processo, muitos logs são gerados, então pelo menos você pode ver o que está acontecendo. não tão assustador porque criamos uma imagem uma vez, mas lançamos muitas."

Depois você pode ver a imagem do nosso roteador em Estivador:

ubuntu:~$ sudo docker image list
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
vrnetlab/vr-vmx     20.1R1.11           b1b2369b453c        3 weeks ago         4.43GB
debian              stretch             614bb74b620e        7 weeks ago         101MB

Iniciar contêiner vr-vmx

Começamos com o comando:

ubuntu:~$ sudo docker run -d --privileged --name jun01 b1b2369b453c

A seguir, podemos ver informações sobre contêineres ativos:

ubuntu:~$ sudo docker container list
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS                                                 NAMES
120f882c8712        b1b2369b453c        "/launch.py"        2 minutes ago       Up 2 minutes (unhealthy)   22/tcp, 830/tcp, 5000/tcp, 10000-10099/tcp, 161/udp   jun01

Conectando ao roteador

O endereço IP da interface de rede do roteador pode ser obtido com o seguinte comando:

ubuntu:~$ sudo docker inspect --format '{{.NetworkSettings.IPAddress}}' jun01
172.17.0.2

Por padrão Vrnetlab cria um usuário no roteador vrnetlab/VR-netlab9.
Conectando com ssh:

ubuntu:~$ ssh [email protected]
The authenticity of host '172.17.0.2 (172.17.0.2)' can't be established.
ECDSA key fingerprint is SHA256:g9Sfg/k5qGBTOX96WiCWyoJJO9FxjzXYspRoDPv+C0Y.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '172.17.0.2' (ECDSA) to the list of known hosts.
Password:
--- JUNOS 20.1R1.11 Kernel 64-bit  JNPR-11.0-20200219.fb120e7_buil
vrnetlab> show version
Model: vmx
Junos: 20.1R1.11

Isso conclui a configuração do roteador.

Recomendações de instalação para roteadores de vários fornecedores podem ser encontradas em projeto github nos respectivos diretórios.

Parte 5: Carteiro - conecte o roteador ao OpenDaylight

instalação do carteiro

Para instalar, basta baixar o aplicativo por isso.

Conectando um roteador ao ODL

vamos criar PUT solicitar:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

  1. Cadeia de consulta:
    PUT http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01
  2. Corpo da solicitação (guia Corpo):
    <node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
    <node-id>jun01</node-id>
    <host xmlns="urn:opendaylight:netconf-node-topology">172.17.0.2</host>
    <port xmlns="urn:opendaylight:netconf-node-topology">22</port>
    <username xmlns="urn:opendaylight:netconf-node-topology">vrnetlab</username>
    <password xmlns="urn:opendaylight:netconf-node-topology">VR-netlab9</password>
    <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>
    <schema-cache-directory xmlns="urn:opendaylight:netconf-node-topology">jun01_cache</schema-cache-directory>
    </node>
  3. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin. Isso é necessário para acessar o ODL:
    Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab
  4. Na guia Cabeçalhos, você precisa adicionar dois cabeçalhos:
    • Aceitar aplicativo/xml
    • Aplicativo de tipo de conteúdo/xml

Nosso pedido foi feito. Nós enviamos. Se tudo foi configurado corretamente, então devemos retornar o status "201 Created":

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

O que esse pedido faz?

Criamos nó dentro ODL com os parâmetros do roteador real que queremos acessar.

xmlns="urn:TBD:params:xml:ns:yang:network-topology"
xmlns="urn:opendaylight:netconf-node-topology"

Estes são espaços de nomes internos XML (Espaço para nome XML) para ODL de acordo com o qual ele cria node.

Além disso, respectivamente, o nome do roteador é ID do nó, endereço do roteador - hospedeiro etc.

A linha mais interessante é a última. Diretório de cache de esquema cria um diretório onde todos os arquivos são baixados Esquema YANG roteador conectado. Você pode encontrá-los em $ODL_ROOT/cache/jun01_cache.

Verificando a conexão do roteador

vamos criar ENTRE solicitar:

  1. Cadeia de consulta:
    GET http://10.132.1.202:8181/restconf/operational/network-topology:network-topology/topology/topology-netconf/
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Nós enviamos. Deve receber um status de "200 OK" e uma lista de todos os suportados pelo dispositivo Esquema YANG:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

comentário: Para ver este último, no meu caso foi necessário esperar cerca de 10 minutos após a execução PUTaté que todos esquema YANG descarregar em ODL. Até este ponto, ao realizar este ENTRE consulta exibirá o seguinte:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Excluir o roteador

vamos criar EXCLUIR solicitar:

  1. Cadeia de consulta:
    DELETE http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Parte 6: Alterar a configuração do roteador

Obtendo a configuração

vamos criar ENTRE solicitar:

  1. Cadeia de consulta:
    GET http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Nós enviamos. Deverá receber o status "200 OK" e a configuração do roteador:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Criar uma configuração

Como exemplo, vamos criar a seguinte configuração e modificá-la:

protocols {
    bgp {
        disable;
        shutdown;
    }
}

vamos criar POST solicitar:

  1. Cadeia de consulta:
    POST http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
  2. Corpo da solicitação (guia Corpo):
    <bgp xmlns="http://yang.juniper.net/junos/conf/protocols">
    <disable/>
    <shutdown>
    </shutdown>
    </bgp>
  3. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.
  4. Na guia Cabeçalhos, você precisa adicionar dois cabeçalhos:
    • Aceitar aplicativo/xml
    • Aplicativo de tipo de conteúdo/xml

Após o envio, devem receber o status "204 No Content"

Para verificar se a configuração foi alterada, você pode usar a consulta anterior. Mas, por exemplo, criaremos outro que exibirá informações apenas sobre os protocolos configurados no roteador.

vamos criar ENTRE solicitar:

  1. Cadeia de consulta:
    GET http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Depois de executar a solicitação, veremos o seguinte:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Alterar a configuração

Vamos alterar as informações sobre o protocolo BGP. Após nossas ações, ficará assim:

protocols {
    bgp {
        disable;
    }
}

vamos criar PUT solicitar:

  1. Cadeia de consulta:
    PUT http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
  2. Corpo da solicitação (guia Corpo):
    <protocols xmlns="http://yang.juniper.net/junos/conf/protocols">
    <bgp>
        <disable/>
    </bgp>
    </protocols>
  3. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.
  4. Na guia Cabeçalhos, você precisa adicionar dois cabeçalhos:
    • Aceitar aplicativo/xml
    • Aplicativo de tipo de conteúdo/xml

Usando o anterior ENTRE request, vemos as mudanças:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Excluir a configuração

vamos criar EXCLUIR solicitar:

  1. Cadeia de consulta:
    DELETE http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/jun01/yang-ext:mount/junos-conf-root:configuration/junos-conf-protocols:protocols
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Mediante chamada ENTRE solicitação com informações sobre os protocolos, veremos o seguinte:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Adição:

Para alterar a configuração não é necessário enviar o corpo da solicitação no formato XML. Isso também pode ser feito no formato JSON.

Para fazer isso, por exemplo, na consulta PUT para alterar a configuração, substitua o corpo da solicitação por:

{
    "junos-conf-protocols:protocols": {
        "bgp": {
            "description" : "Changed in postman" 
        }
    }
}

Não se esqueça de alterar os cabeçalhos na guia Cabeçalhos para:

  • Aceitar aplicativo/json
  • Aplicativo de tipo de conteúdo/json

Após o envio, obteremos o seguinte resultado (Observamos a resposta usando ENTRE solicitar):

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Parte 7: Adicionando o Cisco xRV9000

O que nós somos sobre Juniper, sim Juniper? Vamos falar sobre Cisco!
Encontrei xRV9000 versão 7.0.2 (uma fera que precisa de 8Gb de RAM e 4 núcleos. Não está disponível gratuitamente, então entre em contato Cisco) - vamos executá-lo.

Executando um contêiner

O processo de criação de um contêiner Docker praticamente não é diferente do Juniper. Da mesma forma, colocamos o arquivo .qcow2 com o roteador no diretório correspondente ao seu nome (neste caso, xrv9k) e executamos o comando make docker-image.

Após alguns minutos, vemos que a imagem foi criada:

ubuntu:~$ sudo docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
vrnetlab/vr-xrv9k   7.0.2               54debc7973fc        4 hours ago         1.7GB
vrnetlab/vr-vmx     20.1R1.11           b1b2369b453c        4 weeks ago         4.43GB
debian              stretch             614bb74b620e        7 weeks ago         101MB

Iniciamos o contêiner:

ubuntu:~$ sudo docker run -d --privileged --name xrv01 54debc7973fc

Depois de um tempo, vemos que o contêiner foi iniciado:

ubuntu:~$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                 PORTS                                                      NAMES
058c5ecddae3        54debc7973fc        "/launch.py"        4 hours ago         Up 4 hours (healthy)   22/tcp, 830/tcp, 5000-5003/tcp, 10000-10099/tcp, 161/udp   xrv01

Conecte via ssh:

ubuntu@ubuntu:~$ ssh [email protected]
Password:

RP/0/RP0/CPU0:ios#show version
Mon Jul  6 12:19:28.036 UTC
Cisco IOS XR Software, Version 7.0.2
Copyright (c) 2013-2020 by Cisco Systems, Inc.

Build Information:
 Built By     : ahoang
 Built On     : Fri Mar 13 22:27:54 PDT 2020
 Built Host   : iox-ucs-029
 Workspace    : /auto/srcarchive15/prod/7.0.2/xrv9k/ws
 Version      : 7.0.2
 Location     : /opt/cisco/XR/packages/
 Label        : 7.0.2

cisco IOS-XRv 9000 () processor
System uptime is 3 hours 22 minutes

Conectando o roteador ao OpenDaylight

A adição ocorre de maneira completamente semelhante ao vMX. Só precisamos mudar os nomes.
PUT solicitar:
Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Ligue depois de um tempo ENTRE consulta para verificar se tudo está conectado:
Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Alterar a configuração

Vamos fazer a seguinte configuração:

!
router ospf LAB
 mpls ldp auto-config
!

vamos criar POST solicitar:

  1. Cadeia de consulta:
    POST http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/xrv01/yang-ext:mount/Cisco-IOS-XR-ipv4-ospf-cfg:ospf
  2. Corpo da solicitação (guia Corpo):
    {
        "processes": {
            "process": [
                {
                    "process-name": "LAB",
                    "default-vrf": {
                        "process-scope": {
                            "ldp-auto-config": [
                                null
                            ]
                        }
                    }
                }
            ]
        }
    }
  3. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.
  4. Na guia Cabeçalhos, você precisa adicionar dois cabeçalhos:
    • Aceitar aplicativo/json
    • Aplicativo de tipo de conteúdo/json

Após sua execução, deverão receber o status “204 No Content”.

Vamos verificar o que temos.
Para isso, criaremos ENTRE solicitar:

  1. Cadeia de consulta:
    GET http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/xrv01/yang-ext:mount/Cisco-IOS-XR-ipv4-ospf-cfg:ospf
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Após a execução, você deverá ver o seguinte:

Automação de serviços de rede ou como construir um laboratório virtual usando OpenDaylight, Postman e Vrnetlab

Para remover a configuração, use EXCLUIR:

  1. Cadeia de consulta:
    DELETE http://10.132.1.202:8181/restconf/config/network-topology:network-topology/topology/topology-netconf/node/xrv01/yang-ext:mount/Cisco-IOS-XR-ipv4-ospf-cfg:ospf
  2. Na guia Autorização, você deve definir o parâmetro Basic Auth e login/senha: admin/admin.

Conclusão

No total, como você deve ter notado, os procedimentos para conectar Cisco e Juniper ao OpenDaylight não diferem - isso abre um amplo campo de criatividade. Começando com o gerenciamento de configuração de todos os componentes de rede e terminando com a criação de suas próprias políticas de rede.
Neste tutorial, dei os exemplos mais simples de como você pode interagir com equipamentos de rede usando o OpenDaylight. Sem dúvida, as consultas dos exemplos acima podem ser muito mais complexas e configurar serviços inteiros com um clique do mouse - tudo é limitado apenas pela sua imaginação *

Para ser continuado ...

PS

Se de repente você já sabe de tudo isso ou, ao contrário, passou e mergulhou na alma do ODL, recomendo procurar o desenvolvimento de aplicativos no controlador ODL. Você pode começar por isso.

Experiências bem-sucedidas!

Referências

  1. Vrnetlab: emular redes usando KVM e Docker /Brian Linkletter
  2. Livro de receitas OpenDaylight / Mathieu Lemay, Alexis de Talhouet, et al
  3. Programação de rede com YANG / Benoît Claise, Loe Clarke, Jan Lindblad
  4. Aprendendo XML, Segunda Edição / Erik T. Ray
  5. DevOps eficaz / Jennifer Davis, Ryn Daniels

Fonte: habr.com

Adicionar um comentário