Proxy Reverso Com Docker E Nginx

Fazia tempo que eu não escrevia nada sobre tecnologia, hoje vou falar um pouquinho sobre docker. Meu objetivo é resolver um problema que acredito ser bastante comum no mundo dos containers.

O problema

O problema que estamos falando são múltiplos containers dockers rodando em um mesmo host, porem, com DNS diferentes.

No meu caso por exemplo, esse problema acontece no ambiente de testes da Easy Taxi. Estamos implementando um novo ambiente de testes onde, na primeira vez que um feature-branch é commitado, ele ganha um ambiente de testes só dele, com bancos, APIs, cache e tudo mais. A cada novo commit no feature-branch, um novo deploy é executado nesse ambiente, disponibilizando assim a nova versão para quem quiser testar.

A aplicação tem um docker-compose.yml para ajudar a subir o ambiente completo, mas e o DNS, como o usuário vai chegar até o container?

Pra piorar, quem já trabalhou com docker sabe que a cada novo deploy, os ips das maquinas mudam e dessa maneira, a manutenção do DNS fica mais difícil.

Ai que entra o proxy reverso:

Um proxy reverso é um servidor de rede geralmente instalado para ficar na frente de um servidor Web. Todas as conexões originadas externamente são endereçadas para um dos servidores Web através de um roteamento feito pelo servidor proxy, que pode tratar ele mesmo a requisição ou, encaminhar a requisição toda ou parcialmente a um servidor Web que tratará a requisição.

Um proxy reverso repassa o tráfego de rede recebido para um conjunto de servidores, tornando-o a única interface para as requisições externas. Por exemplo, um proxy reverso pode ser usado para balancear a carga de um cluster de servidores Web. O que é exatamente o oposto de um proxy convencional que age como um despachante para o tráfego de saída de uma rede, representando as requisições dos clientes internos para os servidores externos a rede a qual o servidor proxy atende.

Wikipedia - Proxy reverso

No nosso caso, o proxy reverso vai servir para não precisarmos gerenciar manualmente o DNS para os containers. E ainda ganhamos “inteiramente de grátis”, o balanceamento de carga.

Ferramentas

Bom vamos usar:

  • Docker para provisionar nossos containers.
  • Nginx para simular uma aplicação web e para fazer o proxy reverso.
  • curl para fazer requests na nossa “aplicação web”.
  • Docker Compose para escalar nossas aplicações.
  • E uma tesoura sem ponta.

Acho que todas as ferramentas acima são de conhecimento de todos, então não vou explicar como instalar nem com usar nenhuma delas, se tiver alguma dúvida pode deixar comentários abaixo.

A little piece of magic

Vamos usar também uma imagem a docker image ‘jwilder/nginx-proxy’, acho que vale uma explicação sobre ela.

Essa imagem é o que vai fazer nosso proxy reverso funcionar. Ela usa o docker-gen para recriar um nginx.conf para o nosso proxy reverso a cada vez que um container é criado ou deletado.

Acho que a explicação de toda essa magica pode ficar para o criador das duas ferramentas, ele fez um blog post explicando passo a passo como as ferramentas funcionam: jwilder/nginx-proxy

Chega de bla bla bla, vamos por a mão na massa

Bom, pra começar, vamos subir um container nginx simples que vai imitar uma aplicação web:

1
$ docker run -d -p 81:80 --name web_app nginx

Podemos ver que nossa “applicação web” esta respondendo na porta 81:

1
$ curl -I localhost:81

Se quiser, pode verificar que o request chegou no nosso container:

1
$ docker logs web_app

Agora vamos ao proxy reverso. Primeira coisa que vamos fazer é parar e deletar o nosso container de web_app e subir o nosso proxy reverso:

1
2
$ docker stop web_app && docker rm web_app
$ docker run -d -p 80:80 --name reverse-proxy -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy

Este container usa o docker-gen para verificar cada container que é executado, quando ele identifica que tem um novo container, ou que o container foi parado, ele atualiza o nginx template com o virtual host esperado.

Vamos subir algumas “aplicações web”:

1
2
3
$ docker run -d --name webapp1 -e VIRTUAL_HOST=webapp.com nginx
$ docker run -d --name webapp2 -e VIRTUAL_HOST=outrowebapp.com nginx
$ docker run -d --name webapp3 -e VIRTUAL_HOST=maisumwebapp.com nginx

Repare que nossas aplicações web, tem uma variável de ambiente que é usada pelo proxy reverso para atualizar o template do nginx.

Falta somente um passo para podermos acessar nossos webapps, o DNS. No nosso caso, vamos colocar entradas no nosso /etc/hosts. Edite o seu /etc/hosts e inclua as entradas:

1
2
3
127.0.0.1 webapp.com
127.0.0.1 outrowebapp.com
127.0.0.1 maisumwebapp.com

Tudo pronto, podemos ver que nossos webapps estão respondendo pelo dns:

1
2
3
$ curl -I webapp.com
$ curl -I outrowebapp.com
$ curl -I maisumwebapp.com

Vamos simular um deploy novo de um dos webapps e verificar que mesmo com a mudança de IP, tudo continua funcionando:

1
2
3
4
$ docker inspect -f '' webapp1
$ docker stop webapp1 && docker start webapp1
$ docker inspect -f '' webapp1
$ curl -I webapp.com

Perfeito, mesmo com a mudança de IP, continuamos acessando nosso app sem precisar mudar nenhum DNS.

Escalando

Como eu disse, ganhamos inteiramente de graça o balanceamento de carga. Vamos criar um docker-compose para facilitar a nossa vida, mas antes, vamos limpar a casa:

1
2
3
4
$ docker stop webapp1 && docker rm webapp1
$ docker stop webapp2 && docker rm webapp2
$ docker stop webapp3 && docker rm webapp3
$ docker stop reverse-proxy && docker rm reverse-proxy

Crie um arquivo com o nome de docker-compose.yml com o seguinte conteúdo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
webapp1:
  image: nginx
  environment:
    VIRTUAL_HOST: webapp.com

webapp2:
  image: nginx
  environment:
    VIRTUAL_HOST: outrowebapp.com

proxyreverso:
  image: jwilder/nginx-proxy
  ports:
    - "80:80"
  volumes:
    - /var/run/docker.sock:/tmp/docker.sock:ro

Inicie o proxy reverso:

1
$ docker-compose up -d proxyreverso

Escale sua aplicação:

1
$ docker-compose scale webapp1=2 webapp2=2

Consuma sua aplicação:

1
$ curl -I webapp.com

Tá legal, mas como sei que os dois containers estão recebendo requests? Fácil, faça alguns requests e na sequencia veja o log do docker-compose, que vai mostrar qual container processou o seu request:

1
$ docker-compose logs webapp1

Bom, é isso, espero que tenha sido útil, qualquer dúvida, crítica ou sugestão, estou a disposição.