Acesse seus serviços Docker Compose por meio de nomes fáceis de lembrar

“Pilha, pilha, matriz!”
Você sabia que pode disponibilizar contêineres de aplicativos da web Docker por meio de nomes simples, em vez de números de porta difíceis de lembrar?
Ursos venceram Battlestar Galactica
O Docker Compose torna possível ativar todo um ambiente de desenvolvimento em sua máquina local com apenas um único comando, mesmo que esse ambiente consista em uma dúzia de front-ends, back-ends e serviços de apoio diferentes.
Como desenvolvedor, não basta que o sistema funcione – você também precisa de alguma forma de interagir com ele. A maneira mais fácil de fazer isso é expondo as portas do contêiner, para que você possa, por exemplo, acessar sua aplicação web em http://localhost:8080 ou sua instância do Elasticsearch em http://localhost:9200.
Aqui está um anúncio de serviço público: não precisa ser assim. Você pode (pedir à IA) usar nomes de domínio que realmente façam sentido, mesmo para o desenvolvimento local.
O problema
Primeiro, deixe-me ilustrar qual problema estamos tentando resolver aqui. Para simplificar, usarei hashicorp/http-echo em vez de um aplicativo da web real. http-echo é um pequeno servidor web que pode ser configurado para retornar uma string estática.
No compose.yaml abaixo, eu configurei http-echo para retornar a string Bears e porta encaminhada 5678 do host, já que é isso que http-echo escuta por padrão:
image: hashicorp/http-echo
command: ("-text", "Bears")
Enquanto este projeto do Compose está em execução, abrindo http://localhost:5678 no seu navegador deve mostrar uma página da web simples contendo o texto Bears.
Um projeto típico de desenvolvimento web terá mais do que apenas um contêiner em execução. Além de um frontend, você pode ter uma API ou algo como o Storybook.
Vamos adicionar um segundo http-echo instância para nosso projeto Compose. Desta vez, faremos com que retorne a string Beets. Porque porto 5678 já está tomado pelo bears container, escolheremos “aleatoriamente” outro número de porta. Neste caso, fui para 5679.
image: hashicorp/http-echo
command: ("-text", "Bears")
image: hashicorp/http-echo
command: ("-text", "Beets")
Agora, temos dois servidores web rodando nas portas 5678 e 5679retornando respectivamente as strings Bears e Beets.
Esta configuração obviamente funciona bem o suficiente para pequenos projetosmas sofre de vários problemas:
- Isto força você a usar números de porta, mas a maioria das pessoas não é boa em lembrar sequências numéricas, especialmente se elas forem mais longas (por exemplo
15672,27017,61616) ou parecidos entre si (80,8080,8081,8888etc.). - Todos os aplicativos da web são servidos diretamente em
localhost. Não há nada no endereço da web que diga a você (ou ao navegador) algo sobre o que está sendo servido. Na verdade, a mesma porta pode ser reutilizada em projetos, potencialmente causando problemas difíceis de depurar com cookies, entre outras coisas.
Seria bom se esses aplicativos da web pudessem ser disponibilizados por meio de endereços mais fáceis de lembrar e que realmente tivessem alguma relação com os aplicativos aos quais se referem.
Uma solução básica
Este é um problema bastante comum e, conseqüentemente, existem várias maneiras comuns de resolvê-lo.
A maioria envolve algum tipo de proxy reverso, onde os aplicativos da web não são mais expostos diretamente, mas apenas por meio de um servidor de “ponto de entrada” que encaminha solicitações para o aplicativo da web correto, geralmente com base no (sub)domínio e/ou no caminho da solicitação.
O compose.yaml abaixo usa nginxproxy/nginx-proxy como proxy reverso. Este contêiner lida com todas as solicitações de porta 80 (a porta HTTP padrão), que inclui não apenas localhostmas o mais importante também é tudo o que está abaixo dele, como example.localhost e longer.example.localhost.
Para fazer o proxy encaminhar solicitações para o contêiner certo, basta adicionar um VIRTUAL_HOST variável contendo o nome de domínio desejado para seu environment.
image: nginxproxy/nginx-proxy
- /var/run/docker.sock:/tmp/docker.sock:ro
image: hashicorp/http-echo
command: ("-text", "Bears")
VIRTUAL_HOST: bears.localhost
image: hashicorp/http-echo
command: ("-text", "Beets")
VIRTUAL_HOST: beets.localhost
Agora você pode simplesmente ir para http://bears.localhost e http://beets.localhost para ver os textos Bears e Beets respectivamente. Chega de números de porta!
Bom saber
É certo que o exemplo acima é um pouco artificial e seus requisitos podem ser diferentes dos meus. Aqui estão algumas coisas que você pode precisar.
Roteamento baseado em caminho
Com nginxproxy/nginx-proxyvários contêineres podem compartilhar o mesmo nome de host, desde que escutem caminhos diferentes, que você pode definir usando o comando VIRTUAL_PATH variável de ambiente:
image: nginxproxy/nginx-proxy
- /var/run/docker.sock:/tmp/docker.sock:ro
image: hashicorp/http-echo
command: ("-text", "What kind of bear is best?")
VIRTUAL_HOST: bears.localhost
image: hashicorp/http-echo
command: ("-text", "Black bears are the best.")
VIRTUAL_HOST: bears.localhost
Grandes solicitações HTTP
Fora da caixa, nginxproxy/nginx-proxy permite apenas solicitações HTTP de até 1 MB. Se precisar oferecer suporte a uploads de arquivos ou outros tipos de cargas de solicitação grandes, você precisará substituir manualmente o nginx client_max_body_size.
A maneira idiomática de fazer isso é criando um arquivo de configuração personalizado que define o client_max_body_size para o valor desejado (ou desativa totalmente o limite) e montando-o no contêiner:
image: nginxproxy/nginx-proxy
- ./path/to/your/proxy.conf:/etc/nginx/conf.d/proxy.conf:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
Alternativamente, você pode simplesmente substituir o ponto de entrada para que o arquivo de configuração seja criado dinamicamente durante a inicialização:
image: nginxproxy/nginx-proxy
echo 'client_max_body_size 0;' > /etc/nginx/conf.d/proxy.conf
&& exec /app/docker-entrypoint.sh forego start -r
- /var/run/docker.sock:/tmp/docker.sock:ro
Domínios não locaishost
Todos os exemplos até agora usaram *.localhost endereços. Você pode ficar tentado a usar algum outro domínio, mas localhost tem alguns benefícios importantes em relação a muitas alternativas comuns:
-
.localhosté um nome de domínio de uso especial com uma finalidade específica. Nunca será causar conflitos com domínios reais, agora ou no futuro. .localhosté automaticamente resolvido para127.0.0.1para que você não precise editar manualmente seu/etc/hostsarquivo.- Navegadores modernos tratam
.localhostdiferentemente de outros domínios. Você terá menos problemas com cookies, WebSockets e certificados.
Eu recomendaria que você “namespace” seus hosts virtuais para evitar conflitos com outros projetos. Por exemplo, se você trabalha para uma empresa chamada Contosouse hosts virtuais como foo.contoso.localhost e api.foo.contoso.localhost.
Rede interna
É importante ter em mente que o proxy reverso existe para tornar os contêineres mais facilmente acessíveis a partir do hospedar.
Os contêineres dentro de um projeto Docker Compose não podem alcançar outros contêineres por meio do proxy reverso, apenas por meio do nome de serviço interno (por exemplo, http://bears).
Porque localhost por definição resolve para a máquina local, você não pode nem mesmo fazer com que seus contêineres se conectem ao proxy modificando seu /etc/hosts arquivo, já que a maioria dos clientes HTTP não se preocupará em verificá-lo.
Se você realmente quer usar .localhost endereços dentro do seu contêiner também, você pode executar um proxy reverso dentro de o contêiner que encaminha todas as solicitações de *.localhost para http://proxyque encaminhará a solicitação para o contêiner correto:
&& echo 'http://*.localhost' >> proxy.Caddyfile
&& echo 'reverse_proxy http://proxy' >> proxy.Caddyfile
CMD ("bash", "-c", "caddy run --config proxy.Caddyfile & exec npm run dev")
Vários projetos
Provavelmente, você tem mais de um projeto Docker Compose que deseja executar simultaneamente. Vamos supor que você tenha dois projetos, bears e bsgcada um com seu compose.yaml:
Ainda precisamos de um proxy reverso que escute na porta 80 e encaminhe solicitações para os contêineres certos, mas agora os contêineres fazem parte de dois projetos diferentes que normalmente estariam completamente isolados um do outro. Além disso, ainda queremos que cada projeto do Compose possa funcionar de forma independente.
A solução mais limpa é mover o proxy reverso para seu próprio projeto do Compose. Este projecto não deverá apenas declarar a proxy serviço, mas também o proxy rede:
image: nginxproxy/nginx-proxy
- /var/run/docker.sock:/tmp/docker.sock:ro
O compose.yaml arquivos para bears e bsg pode ser bastante mínimo:
image: hashicorp/http-echo
command: ("-text", "Bears")
image: hashicorp/http-echo
command: ("-text", "Battlestar Galactica")
Você pode notar dois problemas principais aqui:
- Todos os três projetos agora declaram um mapeamento portuário
80:5678. Isso nos permite iniciar cada um desses projetos de forma independente e torná-los acessíveis emhttp://localhostsem número de porta, mas não ao mesmo tempo. - Não dizemos mais ao Docker como conectar esses projetos, então nosso código legível por humanos
.localhostendereços não funcionam mais.
Podemos corrigir ambas as regressões adicionando um compose.proxy.yaml arquivo que adiciona configuração específica de proxy ao bears e bsg projetos. A estrutura de diretórios resultante poderia ser assim:
O compose.proxy.yaml arquivos para bsg e bears faça quatro coisas:
- Declare uma dependência em uma rede Docker externa chamada
proxy. Se você tentar iniciar qualquer um desses projetos antes doproxyprojeto, eles se recusarão a começar. - Defina o
VIRTUAL_HOST. Poderíamos ter deixado isso no originalcompose.yamlarquivos, mas isso é um pouco mais limpo. - O
bearsebsgos serviços devem estar vinculados aoproxyrede. Isso permite que o proxy reverso os encontre. - Desative os mapeamentos de portas definidos no original
compose.yamlarquivos, já que eles não são mais necessários (e teriam causado um conflito de porta de qualquer maneira).
VIRTUAL_HOST: bears.localhost
VIRTUAL_HOST: bsg.localhost
Configurar tudo agora requer duas etapas.
Primeiro, inicie o proxy projeto. Caso contrário, as etapas abaixo falharão com a seguinte mensagem de erro: network proxy declared as external, but could not be found.
Em seguida, inicie todos os projetos restantes com o -f (--file) que informa explicitamente ao Docker Compose quais configurações ele deve usar:
docker compose -f compose.yaml -f compose.proxy.yaml up -d
Agora, você deve ser capaz de acessar ambos http://bears.localhost e http://bsg.localhost através do seu navegador!
Tempere a gosto
Dependendo do seu caso de uso, você pode querer fazer algumas modificações nesta configuração:
- Se você nunca precisar executar esses projetos (localmente ou em CI) sem o proxy, sinta-se à vontade para simplificar a configuração mesclando nosso software personalizado
compose.proxy.yamlpara o normalcompose.yaml. - Se você não quiser cometer o
compose.proxy.yamlarquivos, você deve adicioná-los a um.gitignorearquivo. - Alternativamente, se você renomeá-lo para
compose.override.yamla configuração será aplicada automaticamente sobre o normalcompose.yamlarquivo, então você não precisa usar-fmais ao iniciar projetos.




