Resumo
A ideia deste post é mostrar como habilitar a comunicação de um aplicativo feito com React Native (executando localmente com a ajuda do expo) com um serviço backend containerizado com Docker. O serviço é uma aplicação feita com Spring Boot em Java, mas a dica é válida para um serviço em qualquer linguagem.
Este setup é apenas para ambientes de desenvolvimento. Não use esse tutorial como exemplo para implantação em ambientes de produção.
Descrição do problema
Antes de containerizar a aplicação Spring Boot no Docker, o aplicativo acessava as APIs normalmente através do IP da máquina (pode ser obtido com o comando ifconfig ou nas configurações de rede do SO) e a porta da aplicação Spring (por exemplo - 192.168.0.9:8000). Isso só funcionava porque ambos serviços estavam na mesma rede.
O problema surgiu quando o serviço do Spring Boot foi containerizado. Não fazia mais sentido utilizar o IP da máquina + porta da aplicação, pois agora essa comunicação dependeria de como o Docker e sua rede foram configurados e se há proxy reverso no caminho (como Nginx, HAProxy, etc). Vamos ver na sequência ambos os cenários.
Solução
Vamos supor que nossa aplicação Spring (chamada neste exemplo de ‘my_image/my_app’) executa na porta 8002 e que temos o seguinte arquivo docker-compose.yml para subir o serviço:
| |
O parâmetro ports no docker compose é um mapeamento entre uma porta no host e uma porta no container. A porta 8002 (da aplicação Spring containerizada) está mapeada na porta 8001 do host. Então para acessar esse serviço localmente, basta digitar no browser:
| |
Para acessar esse serviço a partir de um app executando na rede local, basta modificar o ’localhost’ pelo endereço IP da máquina que está hospedando o Docker.
No Ubuntu, você pode usar os comandos ip a ou ifconfig para descobrir o IP privado (podem aparecer vários, basta tentar cada um deles), ou também ir em Settings -> Wi-fi -> Settings (da rede local) -> Details -> IPV4 Address, e obter o IP. No meu caso, é 192.168.100.17. Então, para acessar a API através do aplicativo, seria assim:
| |
Outro cenário possível é o de existir um proxy reverso (Nginx) em um container no host. Segue abaixo um arquivo de configuração de exemplo do Nginx (geralmente salvo em /etc/nginx/conf.d):
| |
E o respectivo docker-compose.yml, que agora inclui o Nginx:
| |
Para acessar o serviço app, será necessário passar pelo Nginx. Para acessar o Nginx, basta usar localhost:80 (ou IP_DO_HOST:80).
As configurações do Nginx apresentadas anteriormente farão um redirecionamento de chamadas do localhost:80 (ou do IP_DO_HOST:80) para o Nginx, que na sequência direciona a chamada para o app:8002. 8002 é a porta do serviço Spring Boot e o app é o hostname do container, que se traduz no IP do mesmo. Não é a porta 8001, pois essa é a porta que está mapeada no Host para acessar o serviço Spring Boot.
Então, de acordo com o exemplo acima, para acessar o serviço backend através do aplicativo rodando no emulador do Android, basta usar o IP da máquina + porta 80 nas chamadas de API no código do app.
Podemos ir além e testar as chamadas de API com o Nginx e com HTTPS (e redirecionar chamadas HTTP para HTTPS). Uma forma de fazer esse teste é com o openssl.
Os comandos abaixo geram o certificado necessário e copiam para o diretório que vamos usar como volume no container do Nginx. Uma boa referência sobre o assunto é essa resposta do Stackoverflow.
| |
Observe os parâmetros essenciais para este exemplo:
default_keyfile = localhost.key
commonName = localhost
subjectAltName = DNS:localhost,IP:10.0.2.2
Utilizamos aqui, além do localhost, o IP 10.0.2.2 porque é o IP que o emulador do Android utiliza para se comunicar com o localhost da máquina HOST.
Para testar o serviço no Browser, vamos precisar importar o certificado de autoridade. Cada browser tem um fluxo diferente para fazer isso.
Os passos no Google Chrome são os seguintes.
1- menu de opções do Chrome (três pontinhos no canto superior direito)
2- Settings (Configurações)
3- Privacy and Security (Privacidade e Segurança - menu esquerdo)
4- Security (Segurança - no grupo Privacy and Security)
5- Manage certificates (Gerenciar certificados)
6- Authorities (Autoridades - selecionar a aba)
7- Import (Importar)
8- Buscar o certificado gerado anteriormente, o localhost.crt, e dar ok
Adicionamos os volumes /etc/ssl/certs:/etc/ssl/certs e /etc/ssl/private:/etc/ssl/private no docker-compose.yml para que o container do Nginx possa acessar os certificados gerados.
| |
Por fim, vamos modificar o arquivo de configurações do Nginx conforme abaixo:
| |
Foi adicionado o path dos certificados que geramos anteriormente para o ’localhost’ para habilitar o acesso local por HTTPS. Ainda habilitamos um redirecionamento de chamadas HTTP para HTTPS no localhost. O ip:porta para acessar o serviço Spring é app:8002 em vez de app:8001, pois estamos acessando o container a partir da porta do serviço (8002) e não da porta mapeada no Host (8001).
Agora é só testar no browser: https://localhost. Deverá aparecer ao lado da URL a imagem do cadeado fechado mostrando que o site possui um certificado e tem uma conexão confiável.
Para testar no emulador do Android, vamos precisar fazer quatro ações.
1- adicionar o certificado localhost.crt no projeto do aplicativo em teste.
Para isso, abrir o projeto (Android) e adicionar o seguinte conteúdo em ./app/src/main/res/xml/network-security-config.xml
| |
2- adicionar o arquivo localhost.crt no diretório ./app/src/main/res/raw.
Se não existir esse diretório, criar o mesmo.
3- verificar se o arquivo em ./app/src/main/AndroidManifest.xml possui o trecho abaixo. Se não tiver, adicione-o. Ele habilita as configurações de segurança de rede através do arquivo network_security_config.xml.
| |
4- ajustar as urls das APIs no aplicativo de forma que elas usem agora o IP:porta https://10.0.2.2:80.
Ao executar o aplicativo no emulador, deverá ser possível acessar recursos com urls em HTTPS com um serviço executando localmente em um container.
Esse teste também pode ser feito com um Nginx executando no Host em vez de em um container. Poucas mudanças seriam necessárias…
Considerações Finais
Não entrei no mérito de boas práticas (com Docker, Nginx, openssl, etc) ou explicar o que cada coisa faz. Acredito que já existam bons tutoriais sobre isso. A ideia deste artigo foi a de apresentar dicas relacionadas a ambientes que envolvam várias coisas diferentes, que exigem um certo nível de conhecimento das ferramentas e dos conceitos por trás de tudo isso.
Se você deseja apoiar o meu trabalho de escrita sobre os mais diversos assuntos de tecnologia, compartilhe meus artigos em suas redes sociais. O objetivo é o de contribuir com o compartilhamento de experiências práticas em desenvolvimento de software e sobre tecnologia com a comunidade.
Valeu pessoal e até o próximo post !!