Migração de container autônomo para containers web e de dados separados

Olá Jay @pfaffman, obrigado por este post e por outros sobre este tópico de “dois contêineres”, incluindo os escritos de Sam sobre o assunto.

Pergunta:

Temos tentado configurar dois contêineres como você menciona: um contêiner para data e outro para web-only, e temos enfrentado vários obstáculos ao tentar fazê-lo funcionar no macOS.

Mas, antes de nos preocuparmos em depurar essa “configuração de dois contêineres” no macOS ou no Ubuntu, gostaríamos de ter certeza de que estamos fazendo isso pelo motivo certo.

O motivo pelo qual queremos fazer essa “dança dos dois contêineres” é para que o site não fique fora do ar quando reconstruímos o aplicativo web, por exemplo, ao instalar um plugin. Além disso, quando ajustamos um plugin desenvolvido internamente; notamos que, às vezes, a única maneira de garantir que nossas alterações funcionem é reconstruir (essa é uma história para outro dia). Também tenho tido dificuldades para configurar um ambiente de desenvolvimento web “rápido e amigável” à minha satisfação; mas isso é outro tópico para outro dia.

Então, minha pergunta é: a configuração de “dois contêineres” minimiza significativamente o tempo de inatividade quando a parte “web-only” do aplicativo é reconstruída?

Essa é a maneira correta de pensar sobre isso, não é mesmo?

Quando instalamos um plugin ou fazemos um ajuste nele, precisamos reconstruir apenas o arquivo yml “web-only” e não o yml de dados?

Vindos de um ambiente LAMP, as alterações em plugins podem e são feitas principalmente em tempo de execução no site ao vivo (sem tempo de inatividade, a menos que cometamos algum erro). Também viemos de algumas aplicações web VueJS onde construímos no desktop e, em seguida, apenas fazemos o upload e colocamos a nova aplicação no lugar, havendo praticamente nenhum tempo de inatividade durante a atualização/atualização da parte VueJS do site. No entanto, com o Discourse, temos tempo de inatividade, o que não queremos (nem mesmo alguns segundos).

A solução de “dois contêineres” mostra melhorias significativas no tempo de inatividade quando (1) reconstruímos o aplicativo (para plugins, ajustes de código, etc.) ou (2) restauramos a partir de um backup completo?

Sinto que vou ser “criticado” (novamente) por fazer essa pergunta, pois estamos procurando uma maneira de executar o Discourse em produção e fazer alterações com tempo de inatividade quase zero, e ainda não encontramos uma maneira de fazer coisas que são tão fáceis de fazer com uma aplicação LAMP ou VueJS (por exemplo).

Daí a luta / interesse no método de “dois contêineres”, que ainda não conseguimos colocar em funcionamento.

Obrigado!

Sim. O contêiner web existente continua em execução enquanto o novo contêiner está sendo construído. O tempo de inatividade, portanto, é apenas o tempo necessário para iniciar o novo servidor web, o que normalmente leva menos de um minuto, embora de forma alguma seja uma proposta de tempo de inatividade zero. Se você deseja tempo de inatividade zero, precisa de um proxy reverso à frente que permita que o novo contêiner seja iniciado e comece a funcionar antes que você desligue o antigo. (E se as migrações de banco de dados para o novo contêiner quebrarem algo para o antigo, você terá tempo de inatividade, a menos que passe por outras manobras).

Não há diferença na restauração a partir de backup.

4 curtidas

Obrigado, Jay @pfaffman,

Você é realmente um recurso valioso de primeira linha aqui, sem dúvida!

O que você acha dessa ideia talvez maluca (baseada no meu entendimento ainda limitado):

Configurar nginx como um proxy reverso na parte frontal; conforme este tutorial:

Em seguida, ter dois diretórios / instâncias com o discourse_docker (standalone) configurado, por exemplo:

  1. /var/discourse1
  2. /var/discourse2

Em ambas as instâncias, configure o discourse_docker (standalone) para escutar em um socket diferente, modificando este modelo em cada instância:

 - "templates/web.socketed.template.yml"

Então, em resumo, nós simplesmente reconstruímos a produção (em algum momento tranquilo) para rodar em um container diferente, escutando em um socket diferente (nginx.https.sock2). Assim, não há conflito de sockets; o que podemos construir também em modo standalone (com o objetivo de eliminar a necessidade de dois containers, data e web-only).

Por exemplo (para discussão / ilustração), em web.socketed.template.yml no discourse1:

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from unix:;

e no discourse2:

 - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock2;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock2 ssl http2;
       set_real_ip_from unix:;

No entanto, em vez de deixar o modelo do discourse fazer a mágica, nós simplesmente trocamos manualmente os sockets em /etc/nginx/conf.d/discourse.conf e reiniciamos o nginx. Assim, removeríamos a diretiva replace: no modelo web.socketed.template.yml.

Nesta configuração proposta (talvez ideia maluca), podemos ter dois containers standalone escutando em dois sockets diferentes (sem conflito) e simplesmente configurar o nginx para se conectar ao socket que desejamos e reiniciar o nginx.

Isso parece claro, fácil e talvez útil (durante um período lento de zero novas postagens na instância ao vivo) para aqueles que podem não querer (ou precisar) da complexidade de dois containers (data e web-only) por uma única instância do Discourse (app).

Claro, a configuração mais robusta (do ponto de vista dos dados), no entanto, para a perfeição em sites movimentados seria a solução de “dois containers”, pois quereríamos as instâncias data e web-only (agora escutando em dois sockets diferentes, sock e sock2).

Na solução de “dois containers” com o front-end nginx, a “configuração padrão” é fazer com que ambos os containers web-only escutem no mesmo socket, de modo que ambos não possam rodar ao mesmo tempo; mas se (por exemplo apenas) fizéssemos com que escutassem em sockets diferentes, ambos poderiam rodar ao mesmo tempo e poderíamos apenas usar o arquivo de configuração do nginx (e um reinício do nginx) para alternar entre os dois.

Essa é a compreensão correta?

Estou começando a (lentamente, mas espero que com certeza) entender isso?

Obrigado!

Nota de Acompanhamento Apenas: Tenho a configuração de “dois containers” funcionando em um dos meus Macs de desktop:

Screen Shot 2020-04-11 at 12.41.24 PM

As únicas ressalvas na nossa instalação foram a necessidade de criar manualmente esses diretórios (e definir a propriedade e as permissões), pois esses diretórios não são criados por algum motivo pelos scripts:

~discourse/discourse/shared/data
~discourse/discourse/shared/web-only

e, claro, no início tentei com uma senha em branco para o banco de dados, e isso não funcionou (as instruções dizem para definir uma senha, mas eu estava apenas experimentando).

Em seguida, configurarei o front-end nginx e tentarei a migração para essa configuração com websocket para o app web-only.

Isso é muita informação para absorver. Mas não há motivo para ter dois diretórios do Discourse. Basta criar vários arquivos yml no diretório containers. Dê a eles o nome que quiser.

2 curtidas

Obrigado por confirmar. É exatamente assim que configuramos (diretório único) após testarmos hoje.

Tudo correu bem na configuração de 2 containers (2CC), mas estou com dificuldades na configuração do proxy reverso nginx no macOS.

Não consigo estabelecer uma conexão funcional com o socket de domínio Unix no diretório /shared, mesmo que o socket seja acessível fora do container. Testei com nginx, Python e socat (para testes). Sempre recebo o erro 61: conexão recusada… hmmmm

Fiquei preso no “conexão recusada” o dia todo!!

Amanhã é outro dia.

Tinha uma pequena dúvida.
Se tivéssemos apenas uma configuração de contêiner (apenas ‘app.yml’) e executássemos o comando ./launcher bootstrap app,
osso site/front-end pararia ou não?

Se sim, por que ‘bootstrap web-only’ não para nosso site?

Se não, qual seria o benefício de uma configuração com dois contêineres, em termos de economia de tempo ao reconstruir nosso contêiner? Em outras palavras, se podemos manter nosso site em execução mesmo enquanto estamos inicializando nosso único contêiner, por que precisaríamos ter dois contêineres separados?

1 curtida

Não, se você criar um novo arquivo .yml, chame-o, por exemplo, new_image.

O processo de bootstrap não inicia nem para nenhum serviço, quando configurado corretamente. As imagens bootstrap não estão em execução. É por isso que são chamadas de “bootstrap”.

No entanto, você precisa criar um novo arquivo yml porque precisa criar uma nova imagem com um novo nome de imagem.

Então, com um novo nome de imagem, a nova imagem bootstrap ainda não está em execução. Ela apenas foi “bootstrapada”.

Você pode construir a nova imagem com um novo nome (não app) e a parte da construção será concluída. Vamos chamar isso de “new_image”.

Então, se quisermos substituir uma imagem em execução, vamos chamar isso de “old_image”, você pode fazer o seguinte:

./launcher stop old_image; ./launcher start new_image

No seu caso:

./launcher bootstrap new_image          #imagem de dados e imagem web "app" em execução
./launcher stop app; ./launcher start new_image

Como o contêiner de dados já está construído, você economiza tempo (1) não construindo a imagem de dados e (2) não há tempo de inatividade ao reconstruir a imagem web, porque você pode construir isso (bootstrapar a imagem) enquanto as outras imagens estão em execução.

Isso é muito mais rápido; mas não é tão rápido quanto usar um proxy reverso na frente dos contêineres.

Na sua pergunta original, você tem uma imagem em execução chamada app. Se você tentar bootstrapar essa imagem chamada app novamente, estará reconstruindo a mesma imagem (nome). Isso não vai economizar nenhum tempo, como seus instintos lhe disseram.

Está claro agora?

Se não, por favor, pergunte. Todos podem aprender; e aprender é emocionante. Isso é (na verdade) fácil de entender, mas leva um pouco de tempo se você é novo nesses conceitos.

2 curtidas

Para ser honesto, não entendi nada. Tentei, mas falhei.

Minha pergunta era simples.
Se temos uma configuração com 2 containers e fazemos o ‘bootstrap’ (não rebuild) do nosso container ‘web-only’, nosso container web atual continua funcionando (porque nosso site aparece como funcionando/ok).

E se tivermos uma configuração com um único container, como ‘app.yml’, e fizermos o bootstrap desse container ‘app’, nosso site continuaria funcionando?

E, se a explicação for simples, por que seria assim?

1 curtida

Talvez você devesse contratar alguém para te ajudar?

1 curtida

Sem problemas.

Era apenas uma pequena dúvida. Uma parte dela, talvez, pudesse ser respondida com ‘sim/não’.

Não é nada grande.

1 curtida

Minha sugestão é que você simplesmente se divirta tentando por conta própria.

Na verdade, leva menos tempo para tentar por conta própria em seu próprio ambiente de teste do que fazer uma pergunta e aguardar uma resposta em um fórum.

Você obterá a resposta à sua pergunta se tentar; por isso, deve testar essas coisas em um cenário de teste para não quebrar seu aplicativo de produção :slight_smile:

O Discourse é de código aberto e gratuito para download e configuração, graças à generosidade dos co-fundadores. Isso significa que você pode e deve aproveitar esse código aberto e criar, destruir, recriar e destruir aplicativos de teste livremente (quantas vezes quiser).

Se você não estiver disposto a dedicar algum esforço a tarefas básicas de administração de sistemas, @codinghorror teve uma ótima sugestão sobre contratar talentos locais aqui.

1 curtida

Sim. (a menos que o bootstrap migre o banco de dados de tal forma que o contêiner em execução não possa mais usá-lo). Você terá um tempo de inatividade enquanto o contêiner antigo é desligado e o novo está sendo inicializado.

Não. Você não pode ter dois processos de banco de dados acessando os mesmos arquivos ao mesmo tempo.

3 curtidas

Ah!!
Obrigado por esclarecer isso.

1 curtida

Então, ao manter o banco de dados fora do container que você está construindo, é possível criar um novo container web que interage com o banco de dados, enquanto o outro container web continua funcionando.

1 curtida

Dúvida rápida sobre essa abordagem: Como proceder com os Rebuilds nessa configuração?

Supondo que partamos da configuração com 2 containers do zero, adicionada pelo @pfaffman, teremos dois: “app”: “data” e “web_only”.

Todas as operações de Rebuild e afins são direcionadas ao container “web_only”, mas fazemos algo com o container “data” ou o bootstrap do “web_only” cuida disso? (Estou perguntando porque estou fazendo alguns testes e o container de dados nunca é desligado em nenhum momento).

Usamos “três containers” e um proxy reverso nginx:

  1. data
  2. socket1
  3. socket2

Digamos que estamos executando atualmente o socket1 (o que você chama de ‘web-only’) e queremos rebuildar e testar algo. Vamos rebuildar o socket2 enquanto o socket1 continua rodando. Como cada um desses containers usa um socket de domínio Unix, ambos podem rodar ao mesmo tempo, pois os sockets compartilhados estão em arquivos (localizações) diferentes.

Então, digamos que o socket2 foi construído e está pronto para rodar.

Faço o seguinte:

ln -sf /var/discourse/shared/socket2/nginx.http.sock /var/run/nginx.http.sock

Agora estamos rodando em produção no socket2.

Se, por exemplo, houver um problema, posso simplesmente fazer o seguinte:

ln -sf /var/discourse/shared/socket1/nginx.http.sock /var/run/nginx.http.sock

e voltamos ao estado anterior, rodando no socket1.

Por diversão, adicionei um código ao perfil de login do bash, para que, ao fazer login no servidor, ele sempre informe qual socket (container) está rodando:

Last login: Fri May 15 09:39:39 2020 from 159.192.33.138
srw-rw---- 1 root docker  0 May  5 07:38 /var/run/docker.sock
lrwxrwxrwx 1 root root   44 May 15 09:16 /var/run/nginx.http.sock -> /var/discourse/shared/socket/nginx.http.sock

Espero que isso ajude.

Na minha opinião, isso (em geral) é o “caminho a seguir” (e é meu “padrão padrão”, em produção, não em staging ou dev), mas isso é só minha opinião, YMMV. Requer um pouco mais de trabalho para configurar, mas acho que vale muito a pena (em produção).


Aviso:


É uma boa ideia manter cópias dos seus arquivos yml de template originais em algum lugar seguro, porque os templates podem ser sobrescritos e isso pode ser um problema. Por isso, tendemos a “salvar todos os ymls”, “fazer pull primeiro” e então “verificar os ymls” antes de fazer o bootstrap (por excesso de cautela).

2 curtidas

Você não precisa reconstruir o contêiner de dados, a menos que haja uma atualização do PostgreSQL (como acabou de acontecer) ou do Redis (que ocorreu nos últimos seis meses ou mais).

Na maioria dos casos, basta substituir web_only por app nas instruções que você vê. Tenho anotações em Managing a Two-Container Installation - Documentation - Literate Computing Support

5 curtidas

Então, nessa abordagem, você tem duas instâncias web e usa a ociosa para testar coisas com os mesmos dados? Isso é interessante, com certeza vou experimentar assim que eu me estabilizar com toda essa estratégia de “tentar economizar espaço” :stuck_out_tongue:

Muito obrigado. Li seu artigo do início ao fim, só uma pequena dúvida: aqui você quis dizer data, certo? (ou seja, reconstruir data em vez de web_only), como diz no artigo? Ou talvez haja um truque diferente que eu esteja perdendo (por exemplo: quando há uma grande atualização, você precisa colocá-los juntos e depois separá-los novamente).

Sim, nós testamos, adicionamos e recriamos em um container baseado em socket enquanto executamos no outro.

Sim, ambos usam o mesmo container de dados. O container de dados não “se importa” com qual aplicativo da web ele “conversa”.

É muito simples, na verdade, assim que você entende a ideia básica de como executar os containers em sockets de domínio Unix compartilhados e não em portas TCP/IP expostas externamente.

Não achamos que isso ocupe muito espaço, mas, novamente, não executamos (em produção) com espaço limitado porque o espaço em disco não é caro.

1 curtida

Obrigado pelos detalhes. Acabei de testar dois “contêineres de aplicativo” apontando para um Data One em um ambiente de teste e funcionou perfeitamente.

No entanto, não consigo movê-lo para o PRD, pois não consigo recriar o Contêiner de Dados no PRD e não quero alterar nada sem ter isso resolvido. Ele apenas interrompe as operações com a mensagem discourse@discourse FATAL: terminando conexão devido a comando do administrador.