Compose: Existe uma maneira de atrasar a inicialização do contêiner para oferecer suporte a serviços dependentes com um tempo de inicialização mais longo

Criado em 5 ago. 2014  ·  314Comentários  ·  Fonte: docker/compose

Eu tenho um contêiner MySQL que leva um pouco de tempo para inicializar, pois precisa importar dados.

Eu tenho um contêiner Alfresco que depende do contêiner MySQL.

No momento, quando uso fig, o serviço Alfresco dentro do contêiner Alfresco falha ao tentar se conectar ao contêiner MySQL ... aparentemente porque o serviço MySQL ainda não está escutando.

Existe uma maneira de lidar com esse tipo de problema na Fig?

Comentários muito úteis

Sim, eu estaria interessado em algo assim - pretendia postar sobre isso antes.

O menor padrão de impacto que posso imaginar que consertaria esse caso de uso para nós seria o seguinte:

Adicione "wait" como uma nova chave em fig.yml, com semântica de valor semelhante ao link. Docker trataria isso como um pré-requisito e esperaria até que este contêiner fosse encerrado antes de continuar.

Portanto, meu arquivo docker seria semelhante a:

db:
  image: tutum/mysql:5.6

initdb:
  build: /path/to/db
  link:
    - db:db
  command: /usr/local/bin/init_db

app:
  link:
    - db:db
  wait:
    - initdb

Ao executar o aplicativo, ele iniciará todos os contêineres de link e, em seguida, executará o contêiner de espera e só avançará para o contêiner de aplicativo real depois que o contêiner de espera (initdb) for encerrado. O initdb executaria um script que espera que o banco de dados esteja disponível, então executa qualquer inicialização / migração / qualquer coisa, então sai.

Esse é o meu pensamento, de qualquer maneira.

Todos 314 comentários

No trabalho, envolvemos nossos serviços dependentes em um script que verifica se o link ainda está ativo. Eu sei que um dos meus colegas também estaria interessado nisso! Pessoalmente, acho que é uma preocupação do contêiner esperar que os serviços estejam disponíveis, mas posso estar errado :)

Fazemos a mesma coisa com o embrulho. Você pode ver um exemplo aqui: https://github.com/dominionenterprises/tol-api-php/blob/master/tests/provisioning/set-env.sh

Seria útil ter um script de ponto de entrada que executa um loop em todos os links e espera até que estejam funcionando antes de iniciar o comando transmitido a ele.

Isso deve ser integrado ao próprio Docker, mas a solução ainda está longe. Um contêiner não deve ser considerado iniciado até que o link que ele expõe seja aberto.

@bfirsh é mais do que eu imaginava, mas seria excelente.

Um contêiner não deve ser considerado iniciado até que o link que ele expõe seja aberto.

Acho que é exatamente disso que as pessoas precisam.

Por enquanto, usarei uma variação em https://github.com/aanand/docker-wait

Sim, eu estaria interessado em algo assim - pretendia postar sobre isso antes.

O menor padrão de impacto que posso imaginar que consertaria esse caso de uso para nós seria o seguinte:

Adicione "wait" como uma nova chave em fig.yml, com semântica de valor semelhante ao link. Docker trataria isso como um pré-requisito e esperaria até que este contêiner fosse encerrado antes de continuar.

Portanto, meu arquivo docker seria semelhante a:

db:
  image: tutum/mysql:5.6

initdb:
  build: /path/to/db
  link:
    - db:db
  command: /usr/local/bin/init_db

app:
  link:
    - db:db
  wait:
    - initdb

Ao executar o aplicativo, ele iniciará todos os contêineres de link e, em seguida, executará o contêiner de espera e só avançará para o contêiner de aplicativo real depois que o contêiner de espera (initdb) for encerrado. O initdb executaria um script que espera que o banco de dados esteja disponível, então executa qualquer inicialização / migração / qualquer coisa, então sai.

Esse é o meu pensamento, de qualquer maneira.

(revisado, veja abaixo)

1 aqui também. Não é muito atraente ter que fazer isso nos próprios comandos.

+1 também. Acabei de encontrar esse problema. Ótima ferramenta aliás, torna minha vida muito mais fácil!

+1 seria ótimo ter isso.

+1 também. Recentemente encontrei o mesmo conjunto de problemas

+1 também. qualquer declaração de dockerguys?

Estou escrevendo scripts de wrapper como pontos de entrada para sincronizar no momento, não tenho certeza se ter um mecanismo na fig é aconselhável se você tiver outros alvos para seus contêineres que executam a orquestração de uma maneira diferente. Parece muito específico de aplicação para mim, como tal a responsabilidade dos containers fazendo o trabalho.

Depois de alguma reflexão e experimentação, meio que concordo com isso.

Como tal, um aplicativo que estou construindo tem basicamente um
função waitfor (host, porta) que me permite esperar pelos serviços do aplicativo
depende de (detectado através do ambiente ou explicitamente
configuração através das opções CLI).

Felicidades
James

James Mills / prologic

E: [email protected]
W: prologic.shortcircuit.net.au

Na sexta-feira, 22 de agosto de 2014 às 18:34, Mark Stuart [email protected]
escrevi:

Estou escrevendo scripts de wrapper como pontos de entrada para sincronizar no momento,
não tenho certeza se ter um mecanismo na fig é aconselhável se você tiver outros alvos para
seus contêineres que executam a orquestração de maneira diferente. Parece muito
aplicação específica para mim, como tal, a responsabilidade dos recipientes
fazendo o trabalho.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/docker/fig/issues/374#issuecomment -53036154.

Sim, algum "depender" básico é necessário aqui ...
então se você tem 20 contêineres, você simplesmente não quer rodar fig up e tudo começa com a ordem correta ...
No entanto, também tem alguma opção de tempo limite ou outros mecanismos de detecção de falha

Outro +1 aqui. Tenho o Postgres demorando mais do que o Django para iniciar, então o banco de dados não está disponível para o comando de migração sem hackeamento.

@ahknight interessante, por que a migração está sendo executada durante run ?

Você não deseja realmente executar a migração durante a fase build ? Dessa forma, você pode inicializar imagens novas com muito mais rapidez.

Infelizmente, há um script de inicialização maior para o aplicativo em questão. Por enquanto, estamos fazendo um trabalho não-banco de dados primeiro, usando nc -w 1 em um loop para esperar pelo banco de dados e, em seguida, fazendo ações do banco de dados. Funciona, mas me faz sentir suja (er).

Tive muito sucesso fazendo este trabalho durante a fase fig build . Eu tenho um exemplo disso com um projeto django (ainda um trabalho em andamento): https://github.com/dnephin/readthedocs.org/blob/fig-demo/dockerfiles/database/Dockerfile#L21

Não há necessidade de pesquisar para inicialização. Embora eu tenha feito algo semelhante com o mysql, onde tive que pesquisar para inicialização porque o script mysqld init ainda não estava fazendo isso. Este script de inicialização do postgres parece ser muito melhor.

Aqui está o que eu estava pensando:

Usando a ideia de docker / docker # 7445, poderíamos implementar este atributo "wait_for_helth_check" na fig?
Então seria um figo, não um problema do Docker?

existe alguma maneira de fazer fig verificar o status do tcp no contêiner vinculado, então eu acho que esse é o caminho a percorrer. =)

@dnephin, você pode explicar um pouco mais o que está fazendo no Dockerfiles para ajudar nisso?
A fase de construção não é capaz de influenciar o tempo de execução?

@docteurklein I can. Corrigi o link acima (https://github.com/dnephin/readthedocs.org/blob/fig-demo/dockerfiles/database/Dockerfile#L21)

A ideia é que você faça todas as operações de "configuração" mais lentas durante a construção, para que não precise esperar nada durante a inicialização do contêiner. No caso de um banco de dados ou índice de pesquisa, você:

  1. inicie o serviço
  2. criar os usuários, bancos de dados, tabelas e dados de fixação
  3. desligue o serviço

tudo como uma única etapa de construção. Mais tarde, quando você fig up o contêiner de banco de dados, ele está pronto para funcionar basicamente imediatamente, e você também pode aproveitar as vantagens do cache de compilação do docker para essas operações mais lentas.

bom! obrigado :)

@dnephin legal, não tinha pensado nisso.

+1 Isso é definitivamente necessário.
Um hack feio de atraso de tempo seria suficiente na maioria dos casos, mas uma solução _real_ seria bem-vinda.

Você poderia dar um exemplo de por que / quando é necessário?

No caso de uso que tenho, tenho um servidor Elasticsearch e um servidor de aplicativos que está se conectando ao Elasticsearch. Elasticsearch leva alguns segundos para girar, então não posso simplesmente fazer um fig up -d porque o servidor de aplicativos irá falhar imediatamente ao se conectar ao servidor Elasticsearch.

Digamos que um contêiner inicie o MySQL e o outro inicie um aplicativo que precisa do MySQL e descubra que o outro aplicativo inicia mais rápido. Temos fig up falhas transitórias por causa disso.

O guindaste tem uma maneira de contornar isso, permitindo que você crie grupos que podem ser iniciados individualmente. Então você pode iniciar o grupo MySQL, esperar 5 segundos e então iniciar as outras coisas que dependem dele.
Funciona em pequena escala, mas não é uma solução real.

@oskarhane não tem certeza se esse "esperar 5 segundos" ajuda, em alguns casos pode precisar esperar mais (ou simplesmente não ter certeza de que não vai passar dos 5 segundos) ... não é muito seguro conte com o tempo de espera.
Além disso, você teria que fazer isso manualmente esperando e carregando o outro grupo, e isso é meio coxo, a fig deve fazer isso por você = /

@oskarhane , @dacort , @ddossot : Tenha em mente que, no mundo real, as coisas travam e reiniciam, as conexões de rede vêm e vão, etc. Independentemente de o Fig apresentar uma conveniência para esperar por um soquete TCP, seus contêineres devem ser resiliente a falhas de conexão. Dessa forma, eles funcionarão corretamente em qualquer lugar.

Você está certo, mas até que consertemos todos os aplicativos pré-existentes para fazer coisas como a recuperação normal da ausência de seus recursos críticos (como DB) no início (o que é uma Grande Coisa ™, mas infelizmente raramente suportado por estruturas), devemos usar fig start para iniciar o contêiner individual em uma determinada ordem, com atrasos, em vez de fig up .

Posso ver um script de shell chegando para controlar fig para controlar a janela de encaixe: wink:

Eu estou ok com isso não sendo integrado ao fig, mas alguns conselhos sobre as melhores práticas para aguardar a prontidão seriam bons

Vi em algum código vinculado a um comentário anterior que isso foi feito:

while ! exec 6<>/dev/tcp/${MONGO_1_PORT_27017_TCP_ADDR}/${MONGO_1_PORT_27017_TCP_PORT}; do
    echo "$(date) - still trying to connect to mongo at ${TESTING_MONGO_URL}"
    sleep 1
done

No meu caso, não existe um caminho /dev/tcp , talvez seja uma distro Linux diferente (?) - Estou no Ubuntu

Em vez disso, encontrei este método que parece funcionar bem:

until nc -z postgres 5432; do
    echo "$(date) - waiting for postgres..."
    sleep 1
done

Isso parece funcionar, mas não sei o suficiente sobre essas coisas para saber se é robusto ... alguém sabe se há uma possível condição de corrida entre a porta mostrando até nc e o servidor postgres _really_ capaz de aceitar comandos?

Eu ficaria mais feliz se fosse possível inverter a verificação - em vez de pesquisar nos contêineres dependentes, é possível enviar um sinal do contêiner de destino (isto é, servidor postgres) para todos os dependentes?

Talvez seja uma ideia boba, alguém tem alguma ideia?

Os links unilaterais , portanto, a pesquisa no contêiner inferior é atualmente a única maneira de fazê-lo.

alguém sabe se existe uma possível condição de corrida entre a porta mostrando até nc e o servidor postgres realmente capaz de aceitar comandos?

Não há como saber no caso geral - pode ser verdadeiro para o postgres, pode ser falso para outros serviços - o que é outro argumento para não fazê-lo na Fig.

@aanand tentei usar sua abordagem de imagem docker / wait, mas não tenho certeza do que está acontecendo. Então, basicamente, eu tenho esse contêiner "Orientdb" ao qual muitos outros contêineres de aplicativos NodeJS estão vinculados. Este contêiner orientdb leva algum tempo para começar a escutar na porta TCP e isso faz com que os outros contêineres recebam o erro "Conexão recusada".

Eu esperava que vinculando o container de espera ao Orientdb eu não veria esse erro. Mas, infelizmente, ainda estou recebendo aleatoriamente. Aqui está minha configuração (Docker versão 1.4.1, fig 1.0.1 em uma caixa Ubuntu 14.04):

orientdb:
    build: ./Docker/orientdb
    ports:
        -   "2424:2424"
        -   "2480:2480"
wait:
    build: ./Docker/wait
    links:
        - orientdb:orientdb
....
core:
    build:  ./Docker/core
    ports:
        -   "3000:3000"
    links:
        -   orientdb:orientdb
        -   nsqd:nsqd

Qualquer ajuda é apreciada. Obrigado.

@mindnuts, a imagem wait é mais uma demonstração; não é adequado para uso em fig.yml . Você deve usar a mesma técnica (votação repetida) em seu contêiner core para aguardar o início do contêiner orientdb antes de iniciar o processo principal.

+1 apenas comecei a encontrar isso enquanto estou puxando imagens personalizadas em vez de criá-las no fig.yml. O aplicativo Node está falhando porque o mongodb ainda não está pronto ...

Acabei de passar horas depurando por que o MySQL estava acessível ao iniciar o WordPress manualmente com o Docker e por que estava offline ao iniciar com o Fig. Só agora percebi que o Fig sempre reinicia o contêiner do MySQL sempre que eu inicio o aplicativo, então o entrypoint.sh do WordPress morre ainda não sendo capaz de se conectar ao MySQL.

Eu adicionei meu próprio entrypoint.sh substituído que espera 5 segundos antes de executar o entrypoint.sh real. Mas claramente este é um caso de uso que precisa de uma solução geral, se for para ser fácil iniciar uma combinação de contêiner MySQL + WordPress com Docker / Fig

então, o entrypoint.sh do WordPress morre ainda não sendo capaz de se conectar ao MySQL.

Acho que esse é um problema com o contêiner do WordPress.

Embora eu fosse inicialmente um fã dessa ideia, depois de ler https://github.com/docker/docker/issues/7445#issuecomment -56391294, acho que esse recurso seria a abordagem errada e, na verdade, incentiva práticas inadequadas.

Parece haver dois casos que esse problema visa abordar:

Um serviço de dependência precisa estar disponível para executar alguma inicialização.

Qualquer inicialização de contêiner deve realmente ser feita durante build . Dessa forma fica em cache, e o trabalho não precisa ser repetido por todos os usuários da imagem.

Um serviço de dependência precisa estar disponível para que uma conexão possa ser aberta

O aplicativo deve realmente ser resiliente a falhas de conexão e tentar a conexão novamente.

Suponho que a raiz do problema é que não existem regras básicas sobre de quem é a responsabilidade de esperar que os serviços estejam prontos. Mas mesmo se houvesse, acho um pouco irreal esperar que os desenvolvedores adicionem novas tentativas de conexão de banco de dados a cada script de inicialização. Esses scripts são frequentemente necessários para preparar volumes de dados vazios que acabaram de ser montados (por exemplo, criar o banco de dados).

O problema seria realmente muito menos intrusivo se Fig nem sempre reiniciasse os containers vinculados (ou seja, o servidor de banco de dados) ao reiniciar o container do aplicativo. Eu realmente não sei por que isso acontece.

O problema seria realmente muito menos intrusivo se Fig nem sempre reiniciasse os containers vinculados (ou seja, o servidor de banco de dados) ao reiniciar o container do aplicativo. Eu realmente não sei por que isso acontece.

Na verdade, ele não apenas _reinicia_ contêineres, ele _destrói e os recria_, porque é a maneira mais simples de garantir que as alterações em fig.yml sejam realizadas. Devemos eventualmente implementar uma solução mais inteligente que possa comparar a "configuração atual" com a "configuração desejada" e apenas recriar o que mudou.

Voltando ao problema original, realmente não acho que seja irreal esperar que os contêineres tenham lógica de nova tentativa de conexão - é fundamental para projetar um sistema distribuído que funcione. Se scripts diferentes precisarem compartilhá-lo, ele deve ser fatorado em um executável (ou módulo específico do idioma, se você não estiver usando o shell), de modo que cada script possa invocar waitfor db no início.

@kennu e --no-recreate ? / cc @aanand

@aanand eu quis dizer o comentário do irrealismo do ponto de vista do Docker Hub já está cheio de imagens publicadas que provavelmente não tratam de tentativas de conexão em seus scripts de inicialização, e que seria uma

Pessoalmente, prefiro manter os contêineres / imagens simples e deixar o sistema subjacente se preocupar com a resolução das dependências. Na verdade, a política de reinicialização do Docker já pode resolver tudo (se o contêiner do aplicativo falhar ao se conectar ao banco de dados, ele será reiniciado e tentará novamente até que o banco de dados esteja disponível).

Mas confiar na política de reinicialização significa que ela deve ser habilitada por padrão, caso contrário, as pessoas passarão horas depurando o problema (como eu acabei de fazer). Por exemplo, o padrão do Kubernetes é RestartPolicyAlways para pods.

algum progresso nisso? Gostaria de repetir que não é razoável esperar que todas as imagens do docker sejam alteradas e toda a comunidade implemente práticas de repetição de conexão. O Fig é uma ferramenta de orquestração do Docker e o problema está na ordem em que as coisas são feitas, então a mudança precisa ser feita no Fig, não no Docker ou na comunidade.

esperar que todas as imagens do docker mudem e toda a comunidade implementar práticas de repetição de conexão não é razoável

Não é que um aplicativo precise tentar novamente por causa do docker ou fig. Os aplicativos devem ser resilientes a conexões perdidas porque a rede não é confiável . Qualquer aplicativo já deve ser criado dessa forma.

Eu pessoalmente não tive que implementar novas tentativas em nenhum dos meus contêineres e também não precisei de nenhum atraso ou espera na inicialização. Eu acredito que a maioria dos casos desse problema se enquadram nessas duas categorias (meu uso de "repetir" provavelmente não é bom aqui, eu quis dizer mais que ele iria restabelecer uma conexão se a conexão fosse fechada, não necessariamente sondar por algum período tentando várias vezes).

Se você se certificar de que todas as inicializações acontecem durante a fase de "construção" e que as conexões são restabelecidas na próxima solicitação, não será necessário tentar novamente (ou esperar que outros contêineres sejam iniciados). Se as conexões forem abertas lentamente (quando a primeira solicitação é feita), em vez de ansiosamente (durante a inicialização), suspeito que você não precisará tentar novamente.

o problema está na ordem [fig] faz as coisas

Não vejo nenhuma menção disso nesta discussão até agora. O Fig ordena a inicialização com base nos links especificados na configuração, portanto, ele sempre deve iniciar os containers na ordem certa. Você pode fornecer um caso de teste em que o pedido está incorreto?

Eu tenho que concordar com @dnephin aqui. Claro, seria conveniente se o compose / fig pudesse fazer alguma mágica e verificar a disponibilidade dos serviços, entretanto, qual seria o comportamento esperado se um serviço não responder? Isso _realmente_ depende dos requisitos de seu aplicativo / pilha. Em alguns casos, a pilha inteira deve ser destruída e substituída por uma nova; em outros casos, uma pilha de failover deve ser usada. Muitos outros cenários podem ser pensados.

O Compose / Fig não pode tomar essas decisões e os serviços de monitoramento devem ser de responsabilidade dos aplicativos em execução no contêiner.

Eu gostaria de sugerir que @dnephin apenas teve sorte. Se você bifurcar dois processos em paralelo, um dos quais se conectará a uma porta que o outro ouvirá, você estará essencialmente introduzindo uma condição de corrida; uma loteria para ver qual processo inicializa mais rápido.

Também gostaria de repetir o exemplo de inicialização do WordPress: ele executa um script de shell de inicialização que cria um novo banco de dados se o contêiner do MySQL ainda não o tiver (isso não pode ser feito durante a construção da imagem Docker, uma vez que depende do volume de dados montado externamente). Esse script se torna significativamente mais complexo se tiver que distinguir erros genéricos de banco de dados de erros de "banco de dados ainda não está pronto" e implementar alguma lógica de nova tentativa dentro do script de shell. Eu considero altamente provável que o autor da imagem nunca teste o script de inicialização contra a condição de corrida mencionada.

Ainda assim, a política de reinicialização integrada do Docker fornece uma solução alternativa para isso, se você estiver pronto para aceitar que os contêineres falhem esporadicamente ao iniciar e imprimam erros regularmente nos logs. (E se você se lembrar de ligá-lo.)

Pessoalmente, eu faria Things Just Work, tornando Fig autodetectar quais portas de contêiner são expostas a um contêiner vinculado, ping-los antes de iniciar o contêiner vinculado (com um tempo limite lógico) e, finalmente, fornecer uma definição de configuração para substituir / desativar essa funcionalidade.

isso não pode ser feito durante a construção da imagem Docker, uma vez que depende do volume de dados montado externamente

Verdadeiro. Uma abordagem aqui é iniciar apenas o contêiner de banco de dados uma vez (se necessário, com um ponto de entrada / comando diferente), para inicializar o banco de dados ou usar um contêiner somente de dados para o banco de dados, criado a partir da mesma imagem que o próprio contêiner de banco de dados.

Esse script torna-se significativamente mais complexo se tiver que distinguir erros de banco de dados genéricos de erros de "banco de dados ainda não está pronto"

Compose / Fig terá o mesmo problema lá; Como verificar se o MySQL está ativo e _aceitando_ conexões? (e PostgreSQL, e (_insira seu serviço aqui_)). Além disso, _onde_ o "ping" deve ser executado? Dentro do container que você está iniciando, do host?

Pelo que eu posso dizer, a imagem oficial do WordPress inclui uma verificação para ver se o MySQL está aceitando conexões no docker-entrypoint.sh

@thaJeztah "Adicionar alguma lógica de nova tentativa simples em PHP para erros de conexão do MySQL" de autoria de tianon 2 dias atrás - Legal. :-) Quem sabe, talvez isso se torne uma abordagem padrão afinal, mas ainda tenho minhas dúvidas, especialmente sobre esse tipo de implementação de nova tentativa ter sido testado por todos os autores de imagens.

Sobre o ping da porta - não posso dizer de improviso qual seria a implementação ideal. Eu acho que talvez a simples verificação de conexão de um contêiner vinculado temporário e tente novamente enquanto obtém ECONNREFUSED. O que quer que resolva 80% (ou possivelmente 99%) dos problemas, para que os usuários não tenham que resolvê-los sozinhos o tempo todo.

@kennu Ah! Obrigado, não sabia que foi adicionado recentemente, apenas verifiquei o script agora por causa desta discussão.

Para ser claro, eu entendo os problemas que você está tendo, mas não tenho certeza se Compose / Fig seria capaz de resolvê-los de uma maneira limpa que funcione para todos (e de forma confiável). Eu entendo que muitas imagens no registro não possuem "salvaguardas" para lidar com esses problemas, mas duvido que seja responsabilidade do Compose / Fig consertar isso.

Tendo dito o acima; Acho que seria uma boa coisa documentar isso na seção de práticas recomendadas do

As pessoas devem estar cientes disso e alguns exemplos devem ser adicionados para ilustrar como lidar com a "interrupção" do serviço. Incluindo um link para o artigo WikiPedia que @dnephin mencionou (e possivelmente outras fontes) para referência.

Encontrei o mesmo problema e gostei da ideia de @kennu

Personally, I would make Things Just Work, by making Fig autodetect which container ports are exposed to a linked container, ping them before starting the linked container (with a sane timeout), and ultimately provide a configuration setting to override/disable this functionality.

Eu acho que isso resolveria muitos casos de uso típicos, como para mim quando dependia do contêiner mongodb oficial.

Eu concordo com @soupdiver. Também estou tendo problemas em conjunto com um contêiner mongo e, embora ele esteja funcionando com um script start.sh, o script não é muito dinâmico e adiciona outro arquivo que preciso manter em meu repo (gostaria de ter apenas um Dockerfile e docker-compose.yml em meu node repo). Seria bom se houvesse uma maneira de simplesmente fazer funcionar, mas acho que algo simples como um cronômetro de espera não vai resolver na maioria dos casos.

O ping IMO não é suficiente, porque a conexão de rede básica pode estar disponível, mas o serviço em si ainda não está pronto.
Este é o caso da imagem MySQL, por exemplo, usar curl ou telnet para a verificação de conexão nas portas expostas seria mais seguro, embora eu não saiba se seria o suficiente. Mas a maioria dos contêineres não tem essas ferramentas instaladas por padrão.

Docker ou fig podem lidar com essas verificações?

Docker ou fig podem lidar com essas verificações?

Resumindo: _não_. Por várias razões;

  • Executar um "ping" de dentro de um contêiner significaria executar um segundo processo. O Fig / Compose não pode iniciar automaticamente esse processo, e não acho que você gostaria que o Fig / Compose modificasse seu contêiner _instalando_ software (como curl ou telnet) nele.
  • (Como mencionei em um comentário anterior), cada serviço requer uma maneira diferente de verificar se está aceitando conexões / pronto para uso. Alguns serviços podem precisar de credenciais ou certificados para _estabelecer_ uma conexão. O Fig / Compose não pode inventar automaticamente como fazer isso.

e não acho que você gostaria que o Fig / Compose modificasse seu contêiner instalando um software (como curl ou telnet) nele.

Não, claro que não.

O Fig / Compose não pode inventar automaticamente como fazer isso.

Não invente. Eu estava pensando mais sobre uma instrução para fig ou docker, como verificá-lo, por exemplo.

web:
    image: nginx
    link: db
db:
   is_available: "curl DB_TCP_ADDR:DB_TCP_PORT"

O comando telnet seria executado no docker-host, não no contêiner.
Mas só estou pensando alto, sei que essa não é a solução perfeita. Mas a maneira atual de usar scripts de verificação personalizados para os contêineres pode ser melhorada.

O comando telnet seria executado no docker-host, não no contêiner.

Então curl ou <name a tool that's needed> teria que ser instalado no host. Isso pode até ter grandes problemas de segurança (por exemplo, alguém quer ser engraçado e usa is_available: "rm -rf /" ). Além disso, poder acessar o banco de dados do _host_ não é garantia de que também esteja acessível de dentro do contêiner.

Mas eu só estou pensando alto, ...

Eu sei, e agradeço. Pense apenas que não há uma maneira confiável de automatizar isso ou que atenderia à maioria dos casos de uso. Em muitos casos, você acabaria com algo complexo (tome, por exemplo, o exemplo curl ; por quanto tempo ele deve tentar se conectar? Tentar novamente?). É melhor mover essa complexidade dentro do contêiner, o que também seria útil se o contêiner fosse iniciado com Docker, não com Fig / Compose.

@thaJeztah eu concordo totalmente com você. E é muito provável que não haja solução 100%.

Vou repetir uma sugestão que fiz anteriormente: Seria suficiente para mim se eu pudesse afirmar no fig.yml “esperar que esse container saia antes de executar este outro container”.

Isso me permitiria criar um contêiner que sabe como esperar por todas as suas dependências - verificar portas, inicializar bancos de dados, o que for - e exigiria do fig know o mínimo possível.

Eu o veria configurado como algo como:

“” "
aplicativo:
links:
- db: db
pré-requisitos:
- runthisfirst

runthisfirst:
links:
- db: db
“” ”

runthisfirst tem um link que significa que o banco de dados é inicializado para que possa verificar o acesso. O aplicativo só será executado depois que runthisfirst for encerrado (pontos de bônus se runthisfirst tiver que sair com sucesso)

Isso é viável como uma resposta?

KJL

Em 10 de fevereiro de 2015, às 05:28, Tobias Munk [email protected] escreveu:

@thaJeztah https://github.com/thaJeztah Concordo totalmente com você. E é muito provável que não haja solução 100%.

-
Responda a este e-mail diretamente ou visualize-o no GitHub https://github.com/docker/fig/issues/374#issuecomment -73561930.

Acabei de tentar migrar meus inicializadores de script de shell e encontrei esse problema. Seria bom até mesmo adicionar uma chave simples de suspensão / espera que apenas durma por aquele número de segundos antes de iniciar o próximo contêiner.

db:
  image: tutum/mysql:5.6
  sleep: 10
app:
  link:
    - db:db

Eu realmente não gosto disso por vários motivos.

a) Acho que é o lugar errado para isso
b) Quanto tempo você dorme?
c) E se o tempo limite não for longo o suficiente?

Além das questões óbvias, eu realmente não acho
infraestrutura deve se preocupar com o que o aplicativo
é e vice-versa. IHMO, o aplicativo deve ser escrito para ser
mais tolerante e / ou mais inteligente sobre seus próprios requisitos.

Dito isso, aplicativos existentes e aplicativos legados
vai precisar de algo - mas provavelmente deve demorar mais
as linhas de:

a docker-compose.yml :

db:
  image: tutum/mysql:5.6
app:
  wait: db
  link:
    - db:db

Onde wait espera que os serviços "expostos" em db se tornem disponíveis.

O problema é como você determina isso?

Nos casos mais simples, você espera até que possa abrir com sucesso
uma conexão tcp ou udp aos serviços expostos.

Isso pode ser um exagero para esse problema, mas o que seria uma boa solução seria se o docker fornecesse um sistema de gatilho de evento onde você pudesse iniciar um gatilho de um contêiner que resultasse em algum tipo de retorno de chamada em outro contêiner. No caso de esperar a importação de dados para um banco de dados MySQL antes de iniciar outro serviço, apenas monitorar se a porta estava disponível não é suficiente.

Ter um script de ponto de entrada definido um alerta para o Docker de dentro do contêiner (definir uma variável de ambiente predefinida, por exemplo) que acionou um evento em outro contêiner (talvez definindo a mesma variável de ambiente sincronizada) habilitaria scripts em ambos os lados para saber quando certo as tarefas estão concluídas.

Claro que poderíamos configurar nosso próprio servidor de soquete ou outros meios, mas isso é tedioso para resolver um problema de orquestração de contêiner.

@aanand eu _quase_ tenho algo funcionando usando sua abordagem de espera como ponto de partida. No entanto, há algo mais acontecendo entre a execução docker-compose e a execução docker, onde a primeira parece travar enquanto a última funciona perfeitamente.

exemplo docker-compose.yml:

db:
  image: postgres
  ports:
    - "5432"
es:
  image: dockerfile/elasticsearch
  ports:
    - "9200"
wait:
  image: n3llyb0y/wait
  environment:
    PORTS: "5432 9200"
  links:
    - es
    - db

então usando ...

docker-compose run wait

no entanto, isso não é para ser. Os serviços vinculados são iniciados e parece que estamos prestes a esperar apenas que ele bloqueie (pelo menos dentro do meu ambiente virtualbox. Chego ao loop nc e obtemos um único ponto então ... nada).

No entanto, com os serviços vinculados em execução, posso usar este método (que é essencialmente o que venho fazendo para nossas compilações de CI)

docker run -e PORTS="5432 9200" --links service_db_1:wait1 --links service_es_1:wait2 n3llyb0y/wait

Parece que docker-compose run deve funcionar da mesma maneira. A diferença é que ao usar docker-compose run com o sinalizador de desanexação -d você não obtém nenhum benefício de espera como fundos do contêiner de espera e eu acho (neste momento) que não usar o sinalizador causa a espera para engasgar com os outros serviços não em segundo plano. Vou dar uma olhada mais de perto

Depois de um pouco de tentativa e erro, parece que a abordagem acima funciona! É só que a base do busybox não tem um utilitário netcat que funciona muito bem. Minha versão modificada do utilitário @aanand wait funciona contra docker-compose 1.1.0 ao usar docker-compose run <util label> vez de docker-compose up . Exemplo de uso no link.

Não tenho certeza se ele pode lidar com situações de encadeamento conforme a pergunta original. Provavelmente não.

Diz-me o que pensas.

Este é um assunto muito interessante. Acho que seria muito interessante ter uma maneira que um contêiner espere até que outro esteja pronto. Mas, como todo mundo diz, o que significa pronto? No meu caso tenho um container para MySQL, outro que gerencia seus backups e também se encarrega de importar um banco de dados inicial e depois os containers para cada aplicativo que precisar do banco de dados. É óbvio que não basta esperar que as portas sejam expostas. Primeiro, o contêiner mysql deve ser iniciado e então o resto deve esperar até que o serviço mysql esteja pronto para uso, não antes. Para conseguir isso, precisei implementar um script simples a ser executado na reinicialização que usa a funcionalidade docker exec . Basicamente, o pseudo-código seria como:

run mysql
waitUntil "docker exec -t mysql mysql -u root -prootpass database -e \"show tables\""
run mysql-backup
waitUntil "docker exec -t mysql mysql -u root -prootpass database -e \"describe my_table\""
run web1
waitUntil "dexec web1 curl localhost:9000 | grep '<h1>Home</h1>'"
run web2
waitUntil "dexec web2 curl localhost:9000 | grep '<h1>Home</h1>'"
run nginx

Onde waitUntil function tem um loop com um timeout que avalia o comando docker exec … e verifica se o código de saída é 0.

Com isso, garanto que cada container aguarda até que suas dependências estejam prontas para uso.

Portanto, acho que pode ser uma opção de integração com o utilitário de composição. Talvez algo assim, onde wait_until declara uma lista de outras dependências (contêineres) e espera por cada uma até que respondam ok ao comando correspondente (ou talvez com um padrão opcional ou regex para verificar se o resultado corresponde a algo que você espera, embora usar o comando grep possa ser suficiente).

mysql:
  image: mysql
  ...
mysql-backup:
  links:
   - mysql
  wait_until:
   - mysql: mysql -u root -prootpass database -e "show tables"
  ...
web1:
  links:
   - mysql
  wait_until:
   - mysql: mysql -u root -prootpass database -e "describe my_table"
  ...
web2:
  links:
   - mysql
  wait_until:
   - mysql: mysql -u root -prootpass database -e "describe my_table"
  ...
nginx:
  links:
   - web1
   - web2
  wait_until:
   - web1: curl localhost:9000 | grep '<h1>Home</h1>'
   - web2: curl localhost:9000 | grep '<h1>Home</h1>'
  ...

O que não é um prato simples para o porto como este?
http://docs.azk.io/en/azkfilejs/wait.html#

@robsonpeixoto : Esperar pela porta não é suficiente para muitos casos de uso. Por exemplo, digamos que você esteja semeando um banco de dados com dados de criação e não queira que o servidor da web inicie e se conecte a ele até que a operação de dados seja concluída. A porta ficará aberta o tempo todo para que não bloqueie a inicialização do servidor web.

1 Estou tendo o mesmo problema ao usar o Docker para testar meus aplicativos Rails que dependem do MySQL

1 Eu também tenho esse problema. Eu gosto da ideia @adrianhurt , onde você realmente fornece a condição a ser avaliada para determinar se a espera foi concluída. Dessa forma, você ainda tem um yml declarativo agradável e não precisa ter uma definição arbitrária de "pronto".

+1

Estou com esta guia aberta há algum tempo: http://crosbymichael.com/docker-events.html ... parece relevante

+1

+1 para tempo limite simples

+1 para uma condição pronta

+1

Estou resolvendo isso de forma muito confiável no nível do aplicativo há algum tempo, como foi recomendado neste tópico.

Só para dar uma ideia de como isso pode ser implementado para MySQL + PHP, aqui está meu código.

De igorw / retry :)

Como a rede é confiável, as coisas sempre devem funcionar. Estou certo? Para os casos em que não o fizerem, há uma nova tentativa.

+1

@ schmunk42 Bom material - gosto que seja um bom exemplo de estabelecer a conexão e realizar uma operação de configuração de banco de dados idempotente.

Pode ser bom criar um (s) exemplo (s) básico (s) para inclusão na documentação, para diferentes casos, por exemplo, NodeJS, Ruby, PHP.

+1, pelo menos deve fornecer algumas opções para adicionar algum atraso antes que o contêiner seja iniciado com sucesso.

+1

Como resolver problemas ao tentar conectar serviços que não são seu código.
Por exemplo, se você tiver o serviço Service e o banco de dados InfluxDB . Services requer InfluxDB e InfluxDB tem uma inicialização lenta.

Como o docker-compose pode esperar InfluxDB estar pronto?

Se o código é meu, consigo resolver colocando uma nova tentativa. Mas para o terceiro aplicativo, não consigo alterar o código.

@robsonpeixoto existem alguns exemplos neste tíquete com o netcat ou formas semelhantes. Você pode dar uma olhada no meu exemplo do MySQL em outro tíquete: https://github.com/docker/docker/issues/7445#issuecomment -101523662

Essa é a razão pela qual acho que cada contêiner deve ter a capacidade opcional de indicar sua própria prontidão. Para um banco de dados, por exemplo, quero esperar até que o serviço esteja completamente pronto, não quando o processo for criado. Eu resolvo isso com verificações personalizadas com docker exec e verificando se pode resolver uma consulta simples, por exemplo.

Algum sinalizador opcional para docker run para indicar um comando de verificação interno seria ótimo para vinculá-lo posteriormente a partir de outro contêiner usando um sinalizador especial para o link.

Algo como:

$ sudo docker run -d --name db training/postgres --readiness-check /bin/sh -c "is_ready.sh"
$ sudo docker run -d -P --name web --link db:db --wait-for-readiness db training/webapp python app.py

Onde is_ready.sh é um simples teste booleano que se encarrega da decisão de quando o container é considerado pronto.

+1

@ schmunk42 bela citação!

+1

+1

+1

+1

+1

+1

+1

+1

+1

Na verdade eu mudei de ideia sobre isso, então -1

Faz mais sentido para seu contêiner verificar se o serviço de terceiros está disponível, e isso é feito facilmente com um pequeno script de wrapper bash que usa nc por exemplo.

Depender de um atraso é tentador, mas é uma solução ruim porque:

  • Seu contêiner _sempre_ aguardará X segundos antes de estar pronto.
  • X segundos ainda podem não ser suficientes em alguns casos (por exemplo, E / S pesada ou CPU no host), então seu contêiner ainda não é à prova de falhas.
  • Nenhuma estratégia de falha.

Contar com a escrita de um script bash wrapper é melhor porque:

  • Seu contêiner estará pronto o mais rápido possível.
  • Você pode implementar qualquer estratégia de falha, por exemplo, tentar 10 vezes e depois falhar, tentar sempre, etc. Você pode até mesmo implementar o atraso dormindo antes de tentar!

Lendo este tópico, vejo que ninguém menciona segredos. Estou tentando usar um contêiner somente de dados que solicita segredos depois de executado. O problema que tenho: se meus segredos demoram muito para transmitir / descriptografar, meu contêiner dependente falha porque os dados que ele espera não estão lá. Eu realmente não posso usar o método de "colocar tudo no contêiner antes de executá-lo" porque eles são segredos.

Eu sei que há alguma ambigüidade em torno dos contêineres somente de dados na composição devido ao contexto do código de retorno, mas existe uma maneira melhor de fazer isso?

Mudando de idéia da mesma forma, -1. A abordagem de @dnephin é totalmente correta. Se o seu _aplicativo_ depende de um _serviço_, o próprio aplicativo deve ser capaz de lidar com a indisponibilidade desse serviço normalmente (por exemplo, restabelecer uma conexão). Não deve ser um script bash wrapper ou alguma lógica no Compose ou Docker, é responsabilidade do próprio aplicativo. Qualquer coisa que não esteja no nível do aplicativo também funcionará apenas na inicialização; se esse serviço cair, um script wrapper ou algo assim não será executado.

Agora, se pudéssemos fazer com que os desenvolvedores de aplicativos / bibliotecas / estruturas percebessem e apoiassem essa responsabilidade, isso seria fantástico.

Difícil de fazer, considerando as abordagens que você tomaria, envolvia fazer o sideload de outros daemons, o que não é recomendado. Para o meu exemplo, onde tenho um aplicativo rails tentando se conectar a um banco de dados MySQL enquanto outro aplicativo rails está migrando e semeando o banco de dados na inicialização inicial, para que eu faça o aplicativo rails saber que não deve tentar usar o banco de dados, eu tem que modificar a biblioteca ActiveRecord (não vai acontecer) ou executar um script que verifica continuamente se o banco de dados foi migrado e propagado. Mas como posso saber com certeza sem saber quais dados devem estar lá e / ou ter algum script que é executado no sistema que está semeando o banco de dados para informar o resto para se conectar a ele.

Talvez eu esteja perdendo a solução óbvia, mas sua resposta de "os desenvolvedores devem ser capazes de lidar com isso em seu próprio código" falha quando você está usando bibliotecas de prateleira e quando você "não deveria" transferir daemons para dentro um container.

@mattwallington Não tenho certeza de como o Compose forneceria uma solução para essa situação ...
Isso também é muito específico, o que tornaria ainda mais difícil para o Compose inventar. Eu leria algumas das dicas de

Acho que você perdeu o ponto da minha última linha, muitas bibliotecas prontas para uso não funcionam _precisamente_ porque não foram construídas de forma resiliente. Não existe uma solução mágica que pode resolver tudo isso que o Compose pode simplesmente implementar.

Entendido. Minha sugestão anterior, que funcionaria para muitos desses casos de uso, é ter uma variável de ambiente compartilhada entre os contêineres. Um lado pode bloquear a pesquisa da variável e o outro pode executar a ação e definir a variável.

+1 @mattwallington ideia de variável de ambiente compartilhado entre contêineres

Obrigado. É simples (no nível do produto. Não tenho ideia do que seria necessário do lado do desenvolvimento, pois não olhei para o código), mas resolveria muitos desses problemas e provavelmente muitos outros, pois não é específico para esse assunto.

Mas como posso saber com certeza sem saber quais dados devem estar lá e / ou ter algum script que é executado no sistema que está semeando o banco de dados para informar o resto para se conectar a ele.

@mattwallington : verifique o número de migração na tabela de esquema. Se o número estiver correto, você sabe que a migração foi executada.

Não deve ser um script bash wrapper ou alguma lógica no Compose ou Docker, é responsabilidade do próprio aplicativo. Qualquer coisa que não esteja no nível do aplicativo também funcionará apenas na inicialização; se esse serviço cair, um script wrapper ou algo assim não será executado.

@ agilgur5 : sim, concordo que seria feito pelo aplicativo, mas um script bash é uma solução simples para lidar com os aplicativos que não estão codificados dessa forma, por exemplo, reiniciando o aplicativo quando o serviço não está disponível.

Argumentos podem ser feitos o dia todo sobre o que deve ou poderia ser feito no nível do aplicativo, mas em vez de esperar que cada aplicativo no mercado lide com isso e se torne incrível na auto-recuperação (improvável), por que somos tão contra a adição de alguns recursos que podem resolver este problema para aplicativos executados no docker, independentemente de como os aplicativos de terceiros são escritos ou o que eles DEVEM fazer, mas não farão. É sobre isso que temos controle. Vamos resolver o problema em vez de decidir quem deve resolvê-lo, já que não temos controle sobre isso.

Eu concordo com @mattwallington. Você pode exigir esse esforço extra para a autorrecuperação no nível do aplicativo de cada desenvolvedor de cada imagem de contêiner, mas uma grande porcentagem deles provavelmente são muito ignorantes ou ocupados demais para implementar e testar com cuidado. O resultado final será que alguns contêineres sabem como se auto-recuperar, enquanto muitos não. E, como usuário, você não terá ferramentas para gerenciar as que não têm.

Uma ideia que me veio à mente: em vez de resolver o problema atrasando a inicialização do contêiner, o Compose poderia tentar recuperar o contêiner com falha.

Algo como recover: auto reiniciaria o contêiner com falha 5 vezes em 2, 4, 8, 16 e 32 segundos e então desistiria completamente.

Alguém _pensou_ na noção de um contêiner de dependência de contêiner?

Por exemplo:

`` `#! yml
db:
imagem: mysql

Esperar por:
links:
- db
volumes:
- /var/lib/docker.sock:/docker.sock
- $ {PWD} /docker-compose.yml:/docker-compose.yml
comando: docker-compose up -d app

aplicativo:
imagem: myuser / myapp
links:
- db
`` `

A ideia básica aqui é que você _solva_ o problema para contêineres que não têm mecanismos de autorrecuperação criando um serviço reutilizável dedicado que pode ser publicado no Docker Hub que todos podem então injetar em sua composição.

Eu _estaria_ até mesmo disposto a prototipar tal serviço / contêiner / imagem e deixar que outros explorassem isso para ver como fica ...

@prologic O problema com uma dependência é: como ter certeza de que o serviço com o qual deseja falar está realmente

Seu contêiner db pode responder a um ping mas está fazendo uma limpeza pré-lançamento / inicializando o banco de dados antes de realmente estar disponível para mysql / psql comandos.

Esse teste pode ser definido de forma configurável e / ou fornecido em um script para um serviço do tipo waitfor reutilizável?

IMHO, este é um problema muito comum e cada um tem seus próprios requisitos específicos. Conforme comentado anteriormente nesta edição, acho que o docker poderia (e deveria) fornecer uma maneira para um contêiner especificar um comando simples para verificar sua própria prontidão. Obviamente, nós, como desenvolvedores, devemos indicar especificamente como verificar a disponibilidade de cada contêiner.

Algum sinalizador opcional para docker run para indicar um comando de verificação interno seria ótimo para vinculá-lo posteriormente de outro contêiner usando um sinalizador especial para o link.

Algo como:

$ sudo docker run -d --name db training/postgres --readiness-check /bin/sh -c "is_ready.sh"
$ sudo docker run -d -P --name web --link db:db --wait-for-readiness db training/webapp python 

Onde is_ready.sh é um teste booleano simples que se encarrega de decidir quando o contêiner é considerado pronto.

Também pode ser um comando para um contêiner verificar manualmente sua prontidão.

Onde is_ready.sh é um teste booleano simples que se encarrega de decidir quando o contêiner é considerado pronto.

o que significa que cada desenvolvedor deve preparar suas imagens / contêineres para incluir _algo_ que pode ser usado para verificar se o contêiner está pronto.

O que nos traz de volta à estaca zero; os desenvolvedores são os responsáveis ​​por tornar seus contêineres resilientes ao tempo de interrupção / inicialização do serviço, porque eles são os únicos que podem dizer "o que" isso significa para sua situação?

Ou estou negligenciando algo aqui?

Concordo. A responsabilidade é do desenvolvedor / contêiner / serviço

Na quinta-feira, 30 de julho de 2015, Sebastiaan van Stijn [email protected]
escrevi:

Onde is_ready.sh é um teste booleano simples que é responsável pelo
decisão de quando o contêiner é considerado pronto.

o que significa que cada desenvolvedor deve preparar suas imagens / recipientes para
inclui _algo_ que pode ser usado para verificar se o contêiner é
pronto.

O que nos traz de volta à estaca zero; desenvolvedores são os únicos responsáveis ​​por
tornando seus contêineres resilientes ao tempo de interrupção / inicialização do serviço, porque
eles são os únicos que podem dizer "o que" isso significa para sua situação?

Ou estou negligenciando algo aqui?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/docker/compose/issues/374#issuecomment -126278215.

James Mills / prologic

E: [email protected]
W: prologic.shortcircuit.net.au

Sim, claro. Para mim, o único que realmente sabe quando um container está pronto é o próprio container. O Docker não pode saber nada sobre o conteúdo de um contêiner. É uma caixa preta. A única coisa que ele poderia fazer é perguntar ao container (com uma ação personalizada especificada quando você deseja executar, como propus, ou qualquer outra forma comum de testá-lo). E obviamente o desenvolvedor é o único que sabe o que ele precisa e o conteúdo daquela caixa preta.

Sim, está certo!

Na quinta-feira, 30 de julho de 2015, adrianhurt [email protected] escreveu:

Sim, claro. Para mim, o único que realmente sabe quando um contêiner é
pronto está o próprio contêiner. Docker não pode saber nada sobre o conteúdo de
um container. É uma caixa preta. A única coisa que poderia fazer é perguntar ao
recipiente (com uma ação personalizada especificada quando você deseja executar, como eu
proposto ou qualquer outra forma comum de testá-lo). E obviamente o desenvolvedor
é o único que sabe o que precisa e o conteúdo daquela caixa preta.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/docker/compose/issues/374#issuecomment -126285056.

James Mills / prologic

E: [email protected]
W: prologic.shortcircuit.net.au

OK - para o bem de todo o software em desenvolvimento ou legado que não consegue lidar com falhas de rede, vamos supor que queremos resolver esse problema afinal. Não estou dizendo que sim, só quero ter uma ideia da sintaxe, da semântica e da complexidade.

O conjunto mínimo de requisitos parece ser:

  • Quero que o Compose espere para iniciar um serviço até que outro esteja "pronto".
  • Quero definir "pronto" como "está aceitando conexões TCP na porta X", ou outra coisa.

Suponhamos também que as verificações de saúde não chegarão ao Docker por um tempo.

Gostaria de saber se isso poderia ser resolvido no caso geral, tornando possível _esperar a saída dos containers de outro serviço_. Você poderia então escrever seu cheque de saúde como apenas mais um serviço.

web:
  image: mywebapp
  links: ["db"]
  wait_for: ["db_wait"]

db_wait:
  image: netcat
  links: ["db"]
  command: sh -c "while ! nc -w 1 -z db 5432; do sleep 1; done"

db:
  image: postgres

Se você quiser algum tipo de verificação de saúde personalizada, defina-a no serviço "wait". Aqui, db_wait só sairá quando mytable existir no banco de dados mydb :

db_wait:
  image: postgres
  links: ["db"]
  command: sh -c "while ! psql --host db --dbname mydb -c "\d mytable"; do sleep 1; done"

Se você tiver um script de preparação de banco de dados para executar primeiro, pode torná-lo algo a esperar:

web:
  image: mywebapp
  links: ["db"]
  wait_for: ["prepare_db"]

prepare_db:
  image: prepare_db
  links: ["db"]
  command: ./prepare.sh

db:
  image: postgres

O primeiro caso (esperar até que o contêiner esteja aceitando conexões TCP) pode ser comum o suficiente para valer a pena suportá-lo pronto para uso.

web:
  image: mywebapp
  links: ["db"]
  wait_for_tcp: ["db:5432"]

db:
  image: postgres

Há uma implicação oculta em tudo isso: docker-compose up -d teria que ser bloqueado enquanto a verificação de integridade ou serviço de preparação do intermediário estivesse em execução, para que pudesse iniciar o (s) serviço (s) ao consumidor assim que terminar.

Sim. No entanto, em minha opinião, o próprio docker deve fornecer uma maneira de determinar quando um contêiner está pronto e, em seguida, o compe pode gerenciá-lo. Poderíamos então levantar um novo problema diretamente no docker. Para mim, pode ser algo como:

web:
  image: mywebapp
  links: ["db"]
  wait_for: ["db"]

db:
  image: postgres
  ready_when: sh -c "while ! psql --host db --dbname mydb -c "\d mytable"; do sleep 1; done"

Então, não há necessidade de criar novos serviços como uma solução alternativa

Acordado. Dê flexibilidade ao desenvolvedor. Além disso, pausar o contêiner pode não ser o que o desenvolvedor precisa que o contêiner faça enquanto espera. Talvez haja algum de seu próprio init que precise acontecer, mas espere que o banco de dados esteja pronto para as conexões. Portanto, se for uma variável env compartilhada simples, ela permite que o desenvolvedor a use conforme necessário.

Como uma variável de ambiente compartilhada seria atualizada? Pelo que eu sei, você não pode fazer alterações no conjunto de variáveis ​​de ambiente de um processo depois de iniciado.

Você pode alterá-los, por exemplo, em uma sessão bash, mas eles não serão propagados em todas as outras camadas.

Para fazer isso, a alteração deve ser confirmada e o contêiner reiniciado. Isso é bastante inútil neste cenário.

Além do mais, mesmo que a variável pudesse ser alterada, o processo no contêiner precisaria saber para pesquisá-la, o que torna isso uma solução não para o software legado para o qual esse recurso é ostensivamente.

Já que estamos falando apenas sobre como resolver este problema para contêineres legados em ambientes de desenvolvimento, acho que poderia ser resolvido como uma ferramenta que fica em cima de compor docker-compose ps -s (listar serviços em ordem de dependência, # 1077).

Com esses dois comandos, pode ser escrita uma ferramenta que faz algo assim:

  1. Execute docker-compose ps -s para obter a lista de nomes de serviços em ordem de dependência
  2. Execute docker-compose up -d --no-recreate <first service from the list>
  3. Execute o comando "healthcheck" para esse serviço até que ele fique saudável ou atinja o tempo limite. Pode ser uma solicitação HTTP ou uma chamada docker exec
  4. Repita 2 e 3 para cada serviço na lista
  5. Execute docker-compose logs (ou não execute se -d for aprovado)

Dessa forma, as configurações de "verificação de integridade" e "espera por" podem ser externas para compor, mas não acho que o desenvolvedor seja obrigado a fornecer nada além do que seria necessário se fosse implementado como parte do próprio compose.

Alguma razão para isso não funcionar?

Fico feliz por termos limitado o escopo a contêineres legados, isso é muito mais razoável: +1:

@dnephin Acho que é ótimo do ponto de vista imediato de que é flexível e que o Compose não precisa ter nenhum suporte integrado para contêineres legados, mas vejo um problema imediato semelhante ao que @mattwallington descreveu, e se os outros contêineres pudessem executar algumas coisas (por exemplo, init) antes de conectar a este serviço? O bloqueio naquele container _funciona_, mas não é o ideal (dito isto, não tenho certeza se existe uma solução ideal para um container legado). Isso resolveria pelo menos o meu problema, agora tenho que encontrar aquela passagem!

+1

por poder especificar dependências em arquivos docker-compose ...

... não usando links (pois são incompatíveis com net = host). Eu teria pensado que é tarefa ou pelo menos a preocupação de uma ferramenta de gerenciamento de vários contêineres saber em que ordem as coisas devem ser iniciadas, assim como o fantoche tem sua própria árvore de dependências, mas às vezes o usuário sabe melhor e pode substituir. Lendo tudo acima, parece-me que a única dificuldade é decidir quando um contêiner está "ativo" para que o próximo contêiner na cadeia de dependências possa ser iniciado. Este poderia ser exatamente o mesmo mecanismo que o mecanismo de link (por enquanto) - qualquer coisa é melhor do que nada. Posteriormente, o esclarecimento de "preencher uma dependência" poderia ser especificado pelo arquivo docker-compose, por exemplo - esta dependência é importante que o contêiner esteja em execução

depends_on:
  container: foo
  requires: running

ou essa dependência, importa se as portas TCP dos contêineres estão escutando.

depends_on:
  container: foo
  requires: listening

Dizer que é o trabalho de alguma ferramenta externa ou script sobre docker-compose é o mesmo que dizer que docker-compose não tem nenhum interesse real ou responsabilidade pela orquestração de execução de 2 ou mais contêineres na mesma máquina. Então, qual é o seu propósito?

Eu teria pensado que é o trabalho ou pelo menos a preocupação de uma ferramenta de gerenciamento de múltiplos contêineres saber em que ordem as coisas devem começar

Não, não necessariamente. Pelas razões já expostas várias vezes neste tópico, acredito que haja apenas duas desculpas para entregar este trabalho a uma ferramenta de gerenciamento de contêineres:

  1. Você está executando imagens de contêiner de prateleira que não são resilientes à indisponibilidade dos serviços upstream dos quais eles dependem e, por motivos técnicos ou comerciais, você não pode alterá-los ou ampliá-los.
  2. Você está executando software puramente em um ambiente de desenvolvimento, não de produção, e você tem coisas melhores para gastar do que implementar resiliência.

No entanto, se você tem controle sobre seu software e está planejando implantá-lo na produção, não pode esperar contar com uma ferramenta externa que apenas inicia as coisas na ordem certa, mesmo que tenha definido cuidadosamente suas condições de preparação. É uma não solução para o problema fundamental e seu sistema irá falhar no momento em que houver um problema de rede. Na melhor das hipóteses, você terá que reiniciar automaticamente todos os seus contêineres de front-end da web sempre que isso acontecer, e não consigo ver isso sendo uma quantidade de tempo de inatividade aceitável para ninguém.

Concorde com você que um software bem escrito irá lidar com falhas de rede. Acho que ambos concordamos que nem todo software é bem escrito e os desenvolvedores nem sempre pensam em todos os casos de uso possíveis.

Talvez eu queira executar um contêiner de clientes executando uma JVM e outra ferramenta de monitoramento para anexar a ele, ambos no namespace PID do host para que um possa monitorar o outro e eu só tenho suporte e licença para executar tal ferramenta com a imagem autorizada do fornecedor . Se a ferramenta de monitoramento monitora JVMs existentes, então é importante a ordem em que eles inicializam (obviamente). Provavelmente, existem centenas de casos de uso em que a ordem é importante, alguns envolvendo redes (mysql, elasticsearch, clusters de descoberta de serviço foram mencionados), mas alguns envolvendo outras coisas que podem ser compartilhadas usando os diferentes namespaces de forma eficaz.

Então, eu definitivamente concordo com o caso de uso (1), em que, em algumas circunstâncias, você simplesmente não pode mudar um contêiner

Mas também, assim que uma ferramenta se preocupa com algo múltiplo, ela imediatamente se depara com o pedido. Se ordenar é importante e a única maneira de garantir um pedido é escrever um script bash em docker-compose para primeiro fazer algo e depois fazer outra coisa, docker-compose pode muito bem nem existir na cadeia, a não ser o fato JSON / YAML é mais bonito do que args cmdline.

IMHO Em última análise, uma ferramenta é útil ou não para um monte de casos de uso. Docker-compose é claramente útil para inicializações não ordenadas de vários contêineres no mesmo host. Se um número suficiente de pessoas e casos de uso forem sobre pedidos e uma ferramenta não os atender, as pessoas irão para outro lugar para esses casos de uso, o que é uma pena.

De qualquer forma, vejo que estou apenas revisando velhos argumentos, então vou ejetar desta discussão agora ..

Este é o mesmo argumento que é afirmado em milhares de threads em tantos tópicos de desenvolvimento diferentes "Se todo o código fosse escrito corretamente por todos, não haveria necessidade disso, então não vamos fazer isso". Isso equivale a dizer: se todas as pessoas no mundo parassem de queimar combustíveis fósseis ou usar eletricidade, poderíamos consertar os problemas que estamos causando ao nosso planeta. Você está certo. Se todos os aplicativos fizessem o que deveriam, não estaríamos aqui falando sobre isso. Mas vivemos na terra. Um lugar que tinha grandes imperfeições com uma espécie de seres que tendem a ter que aprender as coisas da maneira mais difícil. As mesmas pessoas que só fazem as empresas decolarem porque criam um "produto mínimo viável" para orar para que cheguem à próxima rodada de financiamento ou ao próximo cliente que algum dia lhes permita construir a versão que desejam .

O mundo não é e nunca será perfeito e, portanto, só podemos fazer o que temos em nosso próprio controle. E, neste caso, a única coisa que tenho em meu controle é tentar convencer todos vocês (também conhecido como as pessoas que desenvolvem a ferramenta que eu absolutamente amo e usaria como um louco se tivesse apenas esse recurso) a construí-la uma maneira de lidar com o software existente no mundo em que vivemos. Não aquele em que gostaríamos de viver.

Em 31 de julho de 2015, às 03h42, Aanand Prasad [email protected] escreveu:

Eu teria pensado que é o trabalho ou pelo menos a preocupação de uma ferramenta de gerenciamento de múltiplos contêineres saber em que ordem as coisas devem começar

Não, não necessariamente. Pelas razões já expostas várias vezes neste tópico, acredito que haja apenas duas desculpas para entregar este trabalho a uma ferramenta de gerenciamento de contêineres:

Você está executando imagens de contêiner de prateleira que não são resilientes à indisponibilidade dos serviços upstream dos quais eles dependem e, por motivos técnicos ou comerciais, você não pode alterá-los ou ampliá-los.

Você está executando software puramente em um ambiente de desenvolvimento, não de produção, e você tem coisas melhores para gastar do que implementar resiliência.

No entanto, se você tem controle sobre seu software e está planejando implantá-lo na produção, não pode esperar contar com uma ferramenta externa que apenas inicia as coisas na ordem certa, mesmo que tenha definido cuidadosamente suas condições de preparação. É uma não solução para o problema fundamental e seu sistema irá falhar no momento em que houver um problema de rede. Na melhor das hipóteses, você terá que reiniciar automaticamente todos os seus contêineres de front-end da web sempre que isso acontecer, e não consigo ver isso sendo uma quantidade de tempo de inatividade aceitável para ninguém.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

+1 @aanand. Isso não é algo que você será capaz de limitar em escopo. O recurso, se feito, precisa ser algo com que as pessoas possam contar. Eles têm codificado infraestrutura "durável" por um longo tempo e está demorando muito para converter as massas. Eles _vão_ usar isso por muito tempo.

Gostaria de reiterar que não descartamos a implementação de algo para aliviar o problema de dependências de tempo de inicialização entre contêineres - esbocei uma solução possível ontem, neste mesmo tópico .

Mas eu quero que estejamos na mesma página sobre a quem se destina esse recurso, quais problemas ele resolverá, quais problemas ele _não_ resolverá e em que grau ele pode ser confiável. É isso que estou tentando sentir.

Além disso, dadas as várias definições de prontidão que existem ("o contêiner foi iniciado" versus "o contêiner está aceitando conexões TCP" versus "aprovação na verificação de integridade personalizada") e a complexidade diferente de implementação de cada um deles, quero obter uma comparação ideia de quantas pessoas se beneficiariam com o suporte pronto para uso para cada uma.

Uma técnica comum para sincronizar serviços é algo como um "registro de serviço" usando etcd ou semelhante. Que tal um wait_for_service:

web:
  image: mywebapp
  links: ["db"]
  wait_for_service:
    type: etcd (or consul, or zk)    -- or use swarm type notation
    addr: http://my.etcd.com/
    path: postgres.service

db:
  image: postgres

compose não sabe se o serviço está realmente pronto, mas pode procurar os dados no registro e iniciar o contêiner dependente com base nisso. É responsabilidade dos serviços (como o postgres) publicar sua disponibilidade no registro para que, para aplicativos legados, algum tipo de script de agrupamento faça isso: inicie o aplicativo, observe se a porta fica ativa e publique no registro.

Olá, @aanand.

Eu estava discutindo isso hoje com @bfirsh nos twitters, pois é algo que encontrei.

Especificamente, ao construir um sistema distribuído a partir de muitos pequenos componentes encaixados, descobri a necessidade de ter testes de integração que ativassem todos os aplicativos de interface principal e suas dependências e, em seguida, executei vários testes neles antes de destruir tudo novamente.

Isso levou a problemas como, por exemplo, o Riak demorar um pouco para iniciar em comparação com qualquer outra coisa que o utiliza.

Eu não me oporia especialmente a vocês dizerem "os contêineres começam assíncronos, lidam com isso", mas projetar em torno de cada serviço expondo uma verificação de integridade consistente conteria pelo menos "lidar com isso" para uma implementação por serviço em vez de ter código para lidar com novas tentativas de conexão em cada aplicativo que depende do serviço.

Os serviços que definem sua própria verificação de integridade também são benéficos para fins de monitoramento.

@elliotcm Concordo em ambos os pontos.

Para testar nosso CI, construímos um pequeno utilitário que pode ser usado em um contêiner Docker para aguardar que os serviços vinculados estejam prontos. Ele encontra automaticamente todos os serviços TCP vinculados a partir de suas variáveis ​​de ambiente e tenta estabelecer conexões TCP repetidamente e simultaneamente até obter êxito ou atingir o tempo limite.

Também escrevemos uma postagem no blog descrevendo por que o construímos e como o usamos .

@meeee isso parece muito bom e útil! Você pode nos mostrar exemplos de como ele está girando; em particular em uso com um docker-compose?

Tive um caso mais complicado hoje, em que estava iniciando um contêiner mysql pela primeira vez. O contêiner se autoinicializa quando é executado pela primeira vez e reinicia o daemon do banco de dados quando é configurado. Isso fez com que minhas verificações de disponibilidade de porta fossem disparadas prematuramente e os contêineres dependentes foram iniciados, mas não conseguiram se conectar. Isso foi em um ambiente de CI, onde queremos ter tudo configurado completamente do zero.

Eu adoro escrever para suportar algum tipo de comportamento de esperar / verificar, mas existem alguns casos enganosamente complicados por aí. Estou feliz em participar de discussões.

@prologic Tudo que você precisa fazer é executar o comando waitforservices em um contêiner do Docker dependendo de outros serviços / contêineres antes de executar seu aplicativo ou testes. Ele encontra todos os serviços vinculados e executa até que possa se conectar a eles ou até que um certo tempo tenha passado (60 segundos por padrão). Basta executar todo o código que depende de outros serviços após o fechamento do binário (você pode querer verificar o status de saída).

@pugnascotia Seu servidor de banco de dados pode escutar no localhost apenas durante o bootstrap - você terá que expor algum tipo de indicador se o contêiner está pronto de qualquer maneira. Não usamos MySQL, mas waitforservices funciona perfeitamente com a imagem oficial do postgres .

@aanand Postgres é um excelente exemplo para escolher, na verdade, porque esperar a porta TCP abrir _não_ é suficiente - se você fizer isso neste tipo de cenário (docker), às vezes receberá um erro como FATAL: the database system is starting up. se o outro contêiner se conectar muito rapidamente depois que a conexão TCP for aberta. Portanto, psql parece necessário se você quiser ter certeza de que o postgres está pronto - eu uso select version() mas talvez haja uma alternativa mais leve.

Há um plug-in do docker Maven específico que tem uma implementação de espera interessante. No momento, estou usando um wrapper bash para iniciar cada serviço, o que acaba com o uso de compose.

Usando isso como uma solução alternativa (não tenho certeza se é à prova de balas):

db:
  image: postgres:9.3
  ports:
    - "5432:5432"
createdbs:
  image: postgres:9.3
  links:
    - db
  command: >
    /bin/bash -c "
      while ! psql --host=db --username=postgres; do sleep 1; done;
      psql --host=db --username=postgres -c 'CREATE DATABASE \"somedatabase\";';
    "

Tenho usado métodos semelhantes ao @olalonde. Ao utilizar aspas simples para o comando executado após /bin/bash -c , também posso utilizar variáveis ​​de ambiente que são reutilizadas de links em outros aplicativos, para que possa usar nomes de usuário e senhas sem ter que mantê-los em dois lugares. Isso funciona bem para situações em que tenho um serviço, como uma API, que precisa de um banco de dados para estar ativo e ter os dados apropriados inicializados executando uma consulta para verificar se existe uma tabela ou registro específico. Isso também significa que preciso ter algum tipo de cliente instalado no contêiner para consultar o banco de dados corretamente, mas funciona.

+1 Estou realmente interessado nesta funcionalidade

+1 para dependências. Concordo que, em princípio, a arquitetura deve ser robusta o suficiente para suportar qualquer ordem de inicialização. MAS, muitas vezes, fazer isso não é prático.

Parece que a resistência a esse recurso vem de outras pessoas que tentam ditar a arquitetura de longe; onde eles realmente não têm o direito de fazê-lo. Esse recurso permitiria trabalhar com refatores demorados; com pouco valor a longo prazo. (Refatoração para refatorar).

Sim, eu disse "contornar"; e me sinto suja. Mas, para mim, compor significa, na verdade, permitir que os outros sejam produtivos. Esta configuração simples permite isso.

Se esse recurso existisse no toolest, eu poderia resolver meu problema em um minuto e prosseguir para agregar valor real. Em vez disso, estou batendo minha cabeça contra uma parede tentando contornar os problemas de ordem de inicialização com dependências externas.

@beardface e todos os outros: Qual recurso, especificamente, permitiria que você continuasse com o desenvolvimento de seu aplicativo?

  1. A capacidade de especificar que o serviço A deve esperar para iniciar até que o serviço B seja iniciado? (que, tenha em mente, ainda não resolverá a condição de corrida quando um contêiner for iniciado, mas não estiver pronto para aceitar conexões)
  2. A capacidade de especificar que um serviço A deve esperar para iniciar até que o serviço B esteja aceitando conexões? (que, tenha em mente, ainda não resolverá a condição de corrida quando um contêiner estiver escutando, mas não tiver concluído a inicialização - por exemplo, um contêiner postgres criando um banco de dados na inicialização, para usar o exemplo de @rarkins )
  3. A capacidade de definir uma verificação de integridade no serviço B e especificar que o serviço A deve esperar para iniciar até que a verificação de integridade do serviço B seja aprovada?

@aanand Número 3 é meu voto. Ele oferece flexibilidade ao desenvolvedor para escolher a lógica de verificação de integridade, em vez de depender novamente do serviço para decidir quando é a hora. Para este caso de uso específico, mais liberdade do desenvolvedor é melhor. Impossível antecipar todos os tipos de aplicativos que serão instalados em containers.

+3

Seria bom ter algumas verificações básicas de saúde incluídas, no entanto. Algo como _http 200 ok na porta 80_ é tão comum que pode valer a pena.

Seria bom ter um número 3 de "baterias incluídas" (atrevo-me a dizer todas as anteriores?)

Isto é, capacidade integrada para "o contêiner está ativo", "arquivo está presente" e "porta está aberta" do tipo de espera e, em seguida, uma maneira de permitir que as pessoas definam suas próprias verificações de "camada de aplicativo".

3 tem meu voto

3 é mais geral 2 é mais geral 1. Todos irão preferir 3, mas 2 ou 1 serão bons o suficiente para alguns.

Vote em 3

Obviamente, a 3ª opção. Existem muitos casos apenas nesta discussão. Portanto, a 1ª e a 2ª opções seriam ótimas e muitas pessoas ficariam felizes, mas o problema permaneceria em aberto.

Vote em 3

Vote em 3

Vote em 3. Interessado em testes beta também.

Vote em 3.

Vote em 3. Interessado em testes beta também.

Vote em 3

3

Obrigado a todos. Você pode parar de votar agora - acho que a mensagem é clara.

Gostaria de receber feedback sobre o design que propus em https://github.com/docker/compose/issues/374#issuecomment -126312313 - qualquer proposta de design alternativo, junto com discussões sobre seus pontos fortes / fracos.

O método de conveniência wait_for_tcp seria útil, mas não está claro para mim como ter um contêiner separado para fazer a verificação de saúde é mais fácil do que fazê-lo no mesmo contêiner descrito por @olalonde e @mbentley acima .

Que tal fazer algo como o que o alexec está fazendo no plug-in docker-maven? Ele explicitamente projetou sua configuração para ser semelhante a docker-compse / fig e, até agora, funcionou muito bem para meus projetos.

healthChecks:
  pings:
     # check this URL for 200 OK
     - https://localhost:8446/info
     # check another URL with non-default time out, with a pattern, and non checking SSL certificates
     - url: https://localhost:8446/info
       timeout: 60000
       pattern: pattern that must be in the body of the return value
       sslVerify: false
  logPatterns:
     - pattern that must be in log file
     - pattern: another pattern with non-default timeout
       timeout: 30000

Fonte: https://github.com/alexec/docker-maven-plugin/blob/master/USAGE.md

Verificar se a conexão tcp está ativa não é suficiente para saber se o banco de dados foi iniciado. Acho melhor ter algum comando que verifique a saúde do banco de dados.

@ceagan Assim. Comandos personalizados também seriam úteis. por exemplo

healthChecks:
  custom:
    # retry this command until it returns success exit code
    - cmd: psql --host=localhost --username=postgres
      sleep: 1s

Eu também acho que checks seria melhor do que healthChecks porque não há necessidade de lembrar a convenção de caso. Também pode ser útil ter wait (quantos segundos esperar antes de iniciar as verificações de saúde), attempts (quantas vezes a saúde deve ser verificada antes de desistir), retire Parâmetros

isso vai na direção de como a estrutura da maratona resolveu. dependências básicas mais verificações de integridade . como é muito popular para iniciar containers docker, vale a pena verificar suas opções (tempos limite, intervalo, códigos de resposta etc.) para adotá-los para composição.

O mesmo aqui, opção 3.

+1

+1

+1

Vou ficar com um script de espera personalizado, no entanto, seria muito bom.

+1

+1

Opções 3-healthChecks parece bom.

+1

+1

+1

+3.
Para adicionar alguns outros pensamentos à discussão, as escolhas que @aanand propostas realmente dizem: o estado dos contêineres é responsabilidade do docker, não docker-compose. Docker-compose poderia implementar todos esses casos de uso de uma maneira limpa e elegante, se docker fornecesse informações de estado. Mas docker não. Parece seguir a visão de contêineres imediatos e sem estado, lançando tão rápido que esses tipos de problemas de sincronização não são importantes.
No meu caso, persigo a ideia de poder escolher a melhor arquitetura para meus serviços, para cada caso. Por exemplo, às vezes eu gostaria de várias instâncias do MariaDB, cada uma servindo a um único aplicativo. Outras vezes, gostaria de uma única instância MariaDB, atendendo a vários aplicativos. Não quero que Docker me diga o que é melhor ou o que devo fazer em vez disso. Docker sempre parece ter esse tipo de tentação;).
Acho que a melhor solução é convencer o Docker a permitir que os contêineres declarem metadados arbitrários sobre si mesmos e usar esse recurso para permitir que o docker-compose descubra se um contêiner é considerado "pronto" para que outros possam confiar.
Quanto à abordagem de "aplicativos db-múltiplos únicos", gostaria de uma definição como:

db:
  image: postgres:9.3
  ports:
    - "5432:5432"
app1:
  image: wordpress
  links:
    - db [WP]
app2:
  image: ghost
  links:
    - db [GST]

O Docker Compose iniciaria "db" e perguntaria sobre seus metadados (relevantes para o Docker Compose). A partir do arquivo yml, ele sabe que "app1" espera que "db" esteja "pronto para wordpress" (significando não apenas aceitar conexões, mas também com os objetos necessários).

Não tenho uma solução simples para resolver esta situação. Atualmente faço isso manualmente, em duas etapas: uma imagem postgresql-bootstrap customizada, na qual crio o banco de dados e o usuário do banco de dados para acessá-lo; e uma imagem liquibase-postgresql customizada, para gerar os objetos de banco de dados a partir dos DDLs fornecidos pelo (ou extraídos) do contêiner Wordpress. Só então posso iniciar o "app1".
Isso me obriga a separar os contêineres em grupos de "infraestrutura" e "aplicativos", se os contêineres de "infraestrutura" estiverem atendendo a aplicativos diferentes.

O Docker Compose quer ser tão sem estado quanto o próprio Docker. Não sei se isso é possível se quer ser realmente útil.

+1 para a opção 3

+1

+1 para a opção 3.

+1 para a opção 3

+1 para a opção 3

Esse problema já existe há algum tempo, qual é a solução?

@ bweston92 Acho que o status é que @aanand propôs uma solução no início deste tópico e está procurando por

quaisquer propostas de design alternativo, juntamente com discussões sobre seus pontos fortes / fracos.

Pessoalmente, acho que a solução proposta por @aanand faz muito sentido. Parece-me muito explícito, ao mesmo tempo que é flexível. Isso cobriria minhas necessidades de esperar pela abertura de uma porta TCP ou apenas esperar um determinado período de tempo.

Meu caso de uso é apenas para teste. Meus testes falharão se começarem antes de o banco de dados ser criado, então mudei seu comando para bash -c "sleep 2; python manage.py test --keepdb" , assim:

db:
    image: postgres:9.5
test:
    build: .
    command: bash -c "sleep 2; python manage.py test --keepdb"
    volumes:
        - ./test_project:/app
    links:
        - db
        - selenium
    environment:
        - EXTERNAL_TEST_SERVER=http://testserver:8000/
        - SELENIUM_HOST=http://selenium:4444/wd/hub
selenium:
    image: selenium/standalone-chrome:2.48.2
    links:
        - testserver
testserver:
    build: .
    command: bash -c "sleep 5; python manage.py testserver 8000 --static"
    volumes:
        - ./test_project:/app
    ports:
      - "8000:8000"
    links:
        - db

para que eu possa executar docker-compose run test sem iniciar o banco de dados primeiro e esperar.

É difícil dizer qual problema atualmente é o lugar "certo" para votar em uma nova funcionalidade de composição do docker que permite que dependências explícitas sejam declaradas, mas considere meu voto forte. Com a nova funcionalidade de rede do Docker 1.9 e a iminência de descontinuação dos links de contêiner em favor dela, agora não há uma ótima maneira de garantir que o contêiner A seja inicializado antes do contêiner B - porque se você usar a rede definida pelo usuário do Docker 1.9, você pode não especifica mais links de contêiner. Isso está ... quebrado.

Concordo. Existe um cronograma para obter a opção 3? Seria ótimo ter isso acelerado.

É importante notar que a ordem de dependência não corrige realmente esse problema. Esse problema também se aplica aos links. Em muitos casos, um contêiner inicia rápido o suficiente para que você não perceba o problema, mas o problema ainda está lá.

O que é necessário para resolver esse problema é uma verificação de integridade do aplicativo. Uma verificação de integridade é basicamente um loop que tenta novamente alguma operação até que: a operação seja bem-sucedida ou um tempo limite seja atingido. No caso de um serviço HTTP, pode ser feito solicitações http até obter um código 2xx. Para um banco de dados, pode ser conectar e selecionar em uma tabela.

Seja qual for o caso, é específico do aplicativo, portanto, precisa ser definido pelo desenvolvedor. Se implementássemos a opção 3 de https://github.com/docker/compose/issues/374#issuecomment -135090543, você ainda precisaria implementar esta lógica de verificação de integridade.

Já foi mencionado algumas vezes neste problema (https://github.com/docker/compose/issues/374#issuecomment-53036154, https://github.com/docker/compose/issues/374#issuecomment-71342299 ), mas para reiterar, você pode resolver esse problema hoje, tornando seu aplicativo resiliente a falhas tentando uma conexão novamente. Você precisa fazer isso de qualquer maneira para qualquer sistema de produção.

Acontece que a funcionalidade para tornar seu aplicativo resiliente a falhas é efetivamente a mesma lógica de uma verificação de integridade. Portanto, de qualquer forma, você ainda precisa implementar a mesma lógica. A única diferença seria onde você o inclui. Agora você pode incluí-lo em seu aplicativo ou em um script de ponto de entrada. Com a mudança proposta, você seria capaz de defini-la no arquivo Compose. De qualquer forma, você ainda precisa implementar uma verificação de integridade para cada serviço.

Há uma vantagem significativa em incluí-lo no arquivo Compose em vez do script do entrypoint? Isso talvez ainda esteja em debate.

A grande _desvantagem_ de colocá-lo no arquivo Compose é que ele torna up significativamente mais lento.

Com a nova rede, podemos fazer up acontecer em paralelo (como fazemos para stop, rm e scale). Cada contêiner pode ser iniciado de uma vez, fazer algumas inicializações e esperar que suas dependências estejam disponíveis para prosseguir. Isso torna o início de um ambiente muito rápido.

Se o Compose tiver que esperar a conclusão de uma verificação de integridade, a inicialização é efetivamente sequencial. A inicialização do contêiner e a inicialização do aplicativo não acontecem em paralelo e tudo é mais lento.

A maioria dos aplicativos terá verificações de saúde por trás do LB, monitorado externamente, etc. Abrir um novo não é difícil. Portanto, se a composição for compatível, é uma escolha que as pessoas podem usar. Não é obrigatório. No mundo real, as pessoas precisam lidar com uma variedade de aplicativos e essa noção de que, de repente, todos os aplicativos podem se tornar inteligentes é irreal e impraticável. E a lógica do invólucro no ponto de entrada é simplesmente feia. Acho que houve demanda suficiente na comunidade para um recurso e, como você pode ver, a opção 3 recebeu muitos votos.

Enviado do meu iPhone

Em 18 de novembro de 2015, às 11:01, Daniel Nephin [email protected] escreveu:

É importante notar que a ordem de dependência não corrige realmente esse problema. Esse problema também se aplica aos links. Em muitos casos, um contêiner inicia rápido o suficiente para que você não perceba o problema, mas o problema ainda está lá.

O que é necessário para resolver esse problema é uma verificação de integridade do aplicativo. Uma verificação de integridade é basicamente um loop que tenta novamente alguma operação até que: a operação seja bem-sucedida ou um tempo limite seja atingido. No caso de um serviço HTTP, pode ser feito solicitações http até obter um código 2xx. Para um banco de dados, pode ser conectar e selecionar em uma tabela.

Seja qual for o caso, é específico do aplicativo, portanto, precisa ser definido pelo desenvolvedor. Se implementássemos a opção 3 de # 374 (comentário), você ainda precisaria implementar esta lógica de verificação de integridade.

Já foi mencionado algumas vezes neste problema (# 374 (comentário), # 374 (comentário)), mas para reiterar, você pode resolver esse problema hoje tornando seu aplicativo resiliente a falhas tentando uma conexão novamente. Você precisa fazer isso de qualquer maneira para qualquer sistema de produção.

Acontece que a funcionalidade para tornar seu aplicativo resiliente a falhas é efetivamente a mesma lógica de uma verificação de integridade. Portanto, de qualquer forma, você ainda precisa implementar a mesma lógica. A única diferença seria onde você o inclui. Agora você pode incluí-lo em seu aplicativo ou em um script de ponto de entrada. Com a mudança proposta, você seria capaz de defini-la no arquivo Compose. De qualquer forma, você ainda precisa implementar uma verificação de integridade para cada serviço.

Há uma vantagem significativa em incluí-lo no arquivo Compose em vez do script do entrypoint? Isso talvez ainda esteja em debate.

A grande desvantagem de colocá-lo no arquivo Compose é que ele fica significativamente mais lento.

Com a nova rede, podemos fazer tudo acontecer em paralelo (como fazemos para stop, rm e scale). Cada contêiner pode ser iniciado de uma vez, fazer algumas inicializações e esperar que suas dependências estejam disponíveis para prosseguir. Isso torna o início de um ambiente muito rápido.

Se o Compose tiver que esperar a conclusão de uma verificação de integridade, a inicialização é efetivamente sequencial. A inicialização do contêiner e a inicialização do aplicativo não acontecem em paralelo e tudo é mais lento.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

@dnephin O resultado final é que você tem os mesmos scripts de wrapper para cada serviço. Meu ponto é que há algumas coisas que são tão comuns (como HTTP 200 em 80 e 443 ou TCP em 5432) que é uma boa ideia enviá-las com o Compose.

Claro, seria legal resolver tudo isso no nível do aplicativo, mas na realidade você só terá controle sobre seu próprio aplicativo e não todas as outras partes móveis como banco de dados, cache ou fila de massagem.

Concordo com @mbdas e @jayfk , e só acrescentarei: se a resistência a isso é que mesmo com especificações de dependência e ordenação resultante de inicialização do contêiner, haverá falhas, então o uso de links de contêiner e volumes - de para a ordem de inicialização do contêiner de controle nunca deveria ter acontecido - tudo o que estamos pedindo é que, agora que o novo modelo de rede significa que os links estão sendo preteridos (e que o novo modelo de rede literalmente não pode coexistir com os links), a mesma inicialização - funcionalidade de pedido que os links permitidos nos sejam devolvidos de alguma forma. Claro, qualquer caso de falha que possa ter acontecido com o pedido de contêiner baseado em link ainda pode acontecer com o novo modelo de rede e dependências de contêiner, mas todos nós aprendemos a conviver com isso.

@delfuego : você pode explicar como os links estão sendo descontinuados e, especialmente, pelo que foram substituídos? link para alguns documentos / exemplos é suficiente

@Silex https://docs.docker.com/compose/networking. é isso que você quer dizer?

@ h17liner : sim, é interessante! obrigado

Embora eu concorde com @dnephin que

Isso já foi mencionado algumas vezes neste problema, mas para repetir, você pode resolver esse problema hoje tornando seu aplicativo resiliente a falhas tentando uma conexão novamente. Você precisa fazer isso de qualquer maneira para qualquer sistema de produção.

Não vejo isso como algo parecido com a execução de testes. Se estou apenas testando se um modelo é salvo corretamente no aplicativo Django, não tenho certeza de quanto faz sentido adicionar resiliência à conexão do banco de dados.

@delfuego Acho que originalmente você estava no lugar certo (# 686) para esse problema. Este problema não é sobre pedido, é sobre atraso artificial na inicialização (quando um pedido já existe). Embora essas coisas estejam relacionadas, são questões distintas.

Não concordo com links não suportados em redes de ponte criadas pelo usuário e documentados como obsoletos em geral, não há ordenação. Portanto, a opção 3 cuida tanto do pedido quanto de quando iniciar a emissão.

Enviado do meu iPhone

Em 19 de novembro de 2015, às 8h14, Daniel Nephin [email protected] escreveu:

@delfuego Acho que originalmente você estava no lugar certo (# 686) para esse problema. Este problema não é sobre pedido, é sobre atraso artificial na inicialização (quando um pedido já existe). Embora essas coisas estejam relacionadas, são questões distintas.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

Eu gostaria de propor a opção 4 (alguns podem dizer que é uma variação da 3)
O contêiner não está pronto até que todos os comandos de inicialização sejam concluídos com 0 código de saída. Deve ser possível definir esses comandos de inicialização no arquivo yml para cada contêiner. Esses comandos são executados como você faria com o "docker exec" em relação ao contêiner em execução. Pense nos métodos setUp () e tearDown () em testes de unidade clássicos. Sim, poderíamos ter comandos de "desligamento" também.
Obviamente, o próximo contêiner na hierarquia não será lançado até que todos os contêineres dos quais ele depende estejam prontos.
PS Obrigado pelo ótimo DockerCon.Eu 2015

Uma diretiva HEALTHCHECK é muito mais flexível (ou seja, pode ser usada em qualquer ponto posterior) e útil. A configuração deve ser feita dentro do script CMD ou (melhor ainda) ENTRYPOINT , desmontagem manipulando os sinais do processo.

Acho que o cerne do problema aqui é que as pessoas querem um único comando docker-compose up para abrir uma pilha e tudo funciona magicamente.

Com base em todos os comentários, há claramente muitas soluções para diferentes casos de uso, mas nenhum "tamanho único".

Você pode executar tarefas de "inicialização" com bastante facilidade, executando vários comandos docker-compose - e acho que essa abordagem é a mais genérica e flexível.

Por exemplo, eu executo um playbook Ansible em um contêiner de "agente" com uma única tarefa que aguarda o contêiner de meu banco de dados (MySQL) ser executado na porta 3306. Este contêiner de "agente" está vinculado ao meu contêiner "db" de forma automática inicia quando o seguinte é executado:

$ docker-compose run --rm agent
Creating db_1

PLAY [Probe Host] *************************************************************

TASK: [Set facts] *************************************************************
ok: [localhost]

TASK: [Message] ***************************************************************
ok: [localhost] => {
    "msg": "Probing db:3306 with delay=0s and timeout=180s"
}

TASK: [Waiting for host to respond...] ****************************************
ok: [localhost -> 127.0.0.1]

PLAY RECAP ********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

Depois disso, posso executar docker-compose up sabendo que o contêiner db está totalmente operacional.

Aqui está um arquivo docker-compose.yml simples que suporta isso:

...
...
db:
  image: mysql
  hostname: db
  expose:
    - "3306"
  environment:
    MYSQL_DATABASE: xxx
    MYSQL_USER: xxx
    MYSQL_PASSWORD: xxx
    MYSQL_ROOT_PASSWORD: xxx

agent:
  image: cloudhotspot/ansible
  links:
    - db
  volumes:
    - ../../ansible/probe:/ansible
  environment:
    PROBE_HOST: "db"
    PROBE_PORT: "3306"

O contêiner do "agente" executa um manual denominado site.yml no volume montado /ansible que é mostrado abaixo:

- name: Probe Host
  hosts: localhost
  connection: local
  gather_facts: no
  tasks: 
    - name: Set facts
      set_fact: 
        probe_host: "{{ lookup('env','PROBE_HOST') }}"
        probe_port: "{{ lookup('env','PROBE_PORT') }}"
        probe_delay: "{{ lookup('env','PROBE_DELAY') | default(0, true) }}"
        probe_timeout: "{{ lookup('env','PROBE_TIMEOUT') | default (180, true) }}"
    - name: Message
      debug: msg="Probing {{ probe_host }}:{{ probe_port }} with delay={{ probe_delay }}s and timeout={{ probe_timeout}}s"
    - name: Waiting for host to respond...
      local_action: >
        wait_for host={{ probe_host }}
        port={{ probe_port }}
        delay={{ probe_delay }}
        timeout={{ probe_timeout }}
      sudo: false

Uma solução para o único objetivo de docker-compose up pode ser introduzir um recurso de "fluxo de trabalho" para docker compose e incluir um arquivo de especificação de fluxo de trabalho opcional que permite cenários de orquestração mais complexos e controlados, especificando um ou mais comandos docker-compose como "tarefas" que devem ser executadas:

# The default workflow, specified tasks will be run before docker-compose up
# The "up" task is implicit and automatically invoked for the default workflow
# The "up" task is explicit for custom workflows as some workflows may not want docker-compose up
default:
  tasks:
    - run --rm agent
    - up

# Custom workflows that can be invoked via a new docker-compose command option
# This example:
# 1. Runs agent container that waits until database container is up on port 3306
# 2. Runs Django database migrations from app container
# 3. Runs Django collect static task from app container
# 4. Runs test container that runs acceptance tests against linked app container
# Does not execute a docker-compose up afterwards

test:
  tasks:
    - run --rm agent 
    - run --rm app manage.py migrate
    - run --rm app manage.py collectstatic --noinput
    - run --rm test

Hoje consigo fazer o que foi dito acima usando Makefiles, que fornecem uma capacidade de ordem superior para definir meus próprios fluxos de trabalho para diferentes cenários.

Seria ótimo se um recurso de "fluxo de trabalho" ou semelhante pudesse ser introduzido no docker compose, o que forneceria uma solução genérica e flexível para esse problema específico e muitos outros.

Concordando, o problema é que as pessoas esperam que docker-compose seja suficiente para implantações de produção. Pessoalmente, acho que vai demorar muito até que isso se torne viável e o Kubernetes / Helm pareça estar muito mais próximo desse objetivo.

@olalonde , com certeza adoraríamos compor para estar pronto para a produção ... mas TOMAREMOS isso para oferecer suporte a funcionalidades importantes e existentes que, devido à descontinuação dos links de contêiner, irão desaparecer a menos que sejam replicadas para o novo usuário modelo de redes criadas. (Novamente, talvez esta solicitação não esteja perfeitamente alinhada com este problema específico - ainda não está claro para mim se apenas obter o pedido de inicialização do contêiner "pertence" aqui ou no problema # 686 ...)

Com uma diretiva HEALTCHECK Docker, a funcionalidade depends_on pode aguardar a inicialização do contêiner (sem verificação de integridade) ou o script de verificação de integridade sair com êxito (código de saída 0 ). Isso é o mais flexível possível (você pode definir lógica arbitrária) e mantém a lógica de verificação de integridade onde ela pertence (dentro do contêiner que é verificado).

@delfuego mesmo para desenvolvimento e teste, essa funcionalidade seria útil. Pessoalmente, quero ser capaz de fazer docker-compose run test e fazê-lo funcionar sem abrir serviços com antecedência e esperar manualmente. Embora isso seja possível, torna um pouco mais difícil começar o projeto e adiciona mais maneiras de o teste falhar.

+1

Acho que a resolução requer um meio-termo - o compose nunca será capaz de dar conta de todas as diferentes maneiras pelas quais os aplicativos podem ser considerados disponíveis ou não. A ideia de um exame de saúde significará coisas diferentes para pessoas diferentes e pode não ser tão simples como "está certo ou não". Na produção, você pode derrubar um contêiner se ele estiver exibindo tempos de resposta incomumente longos, mesmo se estiver passando por qualquer verificação de HTTP.

Portanto, sinto que o suporte básico para respostas HTTP, portas abertas, arquivos criados ou linhas de log emitidas devem ser suficientes para o desenvolvimento. Qualquer coisa mais avançada do que isso torna-se quase imediatamente específico do aplicativo. Também gosto da ideia de incentivar os desenvolvedores a tornar as partes individuais de suas pilhas de aplicativos mais robustas.

@pugnascotia obrigado, esse é um comentário construtivo e uma abordagem razoável ("o melhor dos dois mundos"?)

As soluções atualmente discutidas não parecem realmente resolver o problema _originalmente relatado_ que é muito mais simples ... que NÃO é esperar que um serviço esteja disponível, mas esperar que um serviço SAIA.

Eu tenho um caso de uso em que tenho dois contêineres que expõem as mesmas portas. O primeiro é executado por 15 a 60 segundos e, em seguida, sai. Então o segundo serviço deve começar. Não há uma maneira (óbvia?) De fazer isso no compose hoje, pois ele detectará o conflito de porta e será encerrado; nem mesmo 'reiniciar: sempre' é uma solução.

Sim, o Compose não foi projetado para esse caso de uso. O Compose é focado em ambientes de tempo de execução, não em construir pipelines. Não acho que seja sobre isso o problema relatado original.

Houve algumas solicitações de recursos mais orientados para construção, mas não acho que façam sentido para composição. As duas funções são muito diferentes e tentar fazê-las se encaixar no mesmo formato de configuração pode causar muitas confusões e uma experiência ruim para o usuário.

@ewindisch, seu caso de uso pode ser generalizado para a execução de uma cadeia de tarefas em lote. O Compose é útil para esse caso (apesar de não ter sido projetado para isso) porque mantém dependências entre serviços - por exemplo, essas cadeias. Mas ele não lida com sequenciamento e IMHO não deveria porque o Compose está _fora_ dos contêineres e não tem idéia do que um processo _dentro_ de um contêiner vai fazer.

Esta parte da documentação do Compose aborda a questão de por que o Compose não tem esse recurso:

https://docs.docker.com/compose/faq/#how -do-i-get-compose-to-wait-for-my-database-to-be-ready-before-started-my-application

No entanto, essas páginas não mencionam o problema:

https://docs.docker.com/compose/django/
https://docs.docker.com/compose/rails/
https://docs.docker.com/compose/wordpress/

No mínimo, essas páginas devem incluir uma confirmação de que o Compose não esperará que um contêiner de banco de dados esteja pronto. Eles também podem incluir exemplos de maneiras de lidar com isso.

@ewindisch Na verdade, isso é exatamente o que eu estava propondo em https://github.com/docker/compose/issues/374#issuecomment -126312313 - com a hipótese de que resolver _tesse_ problema também dá aos usuários as ferramentas para resolver o problema de pedido de inicialização ( se não o problema da resiliência a longo prazo).

Ainda estou interessado em explorar esse espaço de solução, se alguém mais estiver.

Eu sou.

Eu também.

+1

+3

+1

+1 para implementar a solução https://github.com/docker/compose/issues/374#issuecomment -126312313.

Votando massivamente!
Atualmente, isso afeta o uso de ferramentas que são executadas em um contêiner, mas dependem de eventos docker (por exemplo, jwilder / nginx-proxy). A maneira como eu faço isso é apenas docker-compose até o listener manualmente e execute todos os outros contêineres depois (o que estraga toda a beleza do docker-compose como um único ponto de entrada).

@meetmatt , você tentou executar jwilder / nginx-proxy depois? A ordem de início não deve importar para isso, ela selecionará os contêineres existentes (em execução) quando for iniciada

+1

+1

Eu realmente gostaria de ver uma solução transparente baseada em canais. Basicamente, como libchan. Dessa forma, se eu consultar um banco de dados, a solicitação será armazenada em buffer até que o banco de dados esteja pronto.

Eu realmente não acho que a ordem de carregamento seja uma solução suficiente em sistemas distribuídos. E se, por exemplo, você precisar reiniciar seu banco de dados, mas outros serviços puderem travar como resultado? Uma solução real também lidaria com esse caso de uso.

Conte comigo, pois a previsibilidade do pipeline de execução é fundamental para o que fazemos no trabalho. +1

+1

Eu devo estar esquecendo alguma coisa.

Por que ninguém está defendendo a adição de um "esperar até" na execução do docker (o próprio motor do docker). Em todos os casos em que consigo pensar, o contêiner dependente sabe quando está "pronto", mas o docker não respeita isso.

No caso original (mysql carregando um grande conjunto de dados e ao ar livre), o contêiner mysql pode retornar ou sinalizar quando estiver pronto e o contêiner alfresco não iniciará até então.

Eu gostaria de executar uma lógica arbitrária e sinalizar para o docker quando estiver pronto, conforme decidido por mim (por exemplo, quando uma determinada mensagem no log aparecer -> sinal CONTAINER_UP).

net: "container:[name or id]" porque não ordenar o arranque dos meus contentores? Tive que descartar links porque ele será descontinuado e quero que toda a pilha use a rede net: "host" . Infelizmente, isso não é permitido com links . Existe outra maneira de alterar a ordem de inicialização dos contêineres ou tenho que compartilhar volumes inúteis entre eles?

Atualizar:

Acabei de reordenar com volumes inúteis em vez de links :

base:
  build: ./base
  net: "host"
  volumes:
    - /root/lemp_base
phpmyadmin:
  build: ./phpmyadmin
  net: "host"
  volumes_from:
    - base
  volumes:
    - /root/lemp_phpmyadmin
ffmpeg:
  build: ./ffmpeg
  net: "host"
  volumes_from:
    - phpmyadmin
  volumes:
    - /root/lemp_ffmpeg
mariadb:
  build: ./mariadb
  net: "host"
  volumes_from:
    - ffmpeg
  volumes:
    - /root/lemp_mariadb
php:
  build: ./php
  net: "host"
  volumes_from:
    - mariadb
  volumes:
    - /root/lemp_php
nginx:
  build: ./nginx
  net: "host"
  volumes_from:
    - php
  volumes:
    - /root/lemp_nginx

(Limpei outros volumes compartilhados da pilha e outras informações como container_name, portas para parecer simples.)

Se eu quiser usar com net: "container:base , recebo uma mensagem de erro no comando docker-compose build .

ERROR: Service "mariadb" is trying to use the network of "lemp_base", which is not the name of a service or container.

O que eu não gosto nessa solução é que todos os outros contêineres terão os arquivos do servidor da web na pasta /var/www de base .

EDITAR:
Por alguma razão, esta pilha exclui toda a pasta /var/www na inicialização.

Minha humilde opinião é que qualquer mecanismo que termine com Docker Compose sabendo sobre a dependência entre o contêiner vai contra a separação de preocupações. O Docker Compose é responsável por executar os contêineres A e B. Os contêineres A e B são responsáveis ​​por seu próprio serviço. Se B depende de A para funcionar corretamente, é responsabilidade de B esperar que A esteja em condições de funcionamento. Como foi dito na discussão, isso pode ser feito por meio de tempo limite, nova tentativa ou qualquer outra coisa, mas esse é o problema de B, não Docker Compose nem A. SoC é de suma importância para independência de serviço e dimensionamento adequado.

Há algum trabalho na sua ideia 3 @aanand ? Seria bom saber se há algum progresso, parecia um começo promissor que ajudaria em alguns casos de uso muito comuns, mesmo que não fosse uma solução perfeita

+1

+1

Talvez eu esteja errado, mas args: buildno: pode pedir os recipientes na docker-compose.yml versão 2?

Eu tendo a concordar que essa é uma preocupação que não pertence ao Compose. @jwilder 's excelente Dockerize apenas apoio tem que esperar para recipientes dependentes e você pode especificar o protocolo / porta que você está esperando. Eu sugeriria que isso se adequa à maioria dos casos de uso descritos aqui:

api:
  build: .
  ports:
   - "8000:80"
  expose:
  - "80"

test:
  build: test
  command: dockerize -wait http://api:80 -wait tcp://db:5432 somecommand -some arg -another arg2
  links:
    - api:api

Idealmente, usamos a API Docker Events para detectar isso automaticamente, mas isso significaria que cada contêiner também precisaria de acesso ao tempo de execução do Docker, o que provavelmente não é viável / algo que desejaríamos.

Acho que a espera deve ser feita fora do compor. No meu desenvolvimento, vou usar o híbrido do que o @mefellows sugeriu e as páginas de status

Estamos usando um ponto de entrada de script de shell que aguarda a porta aberta, com um tempo limite de 15 segundos:

#!/usr/bin/env bash

# wait for db to come up before starting tests, as shown in https://github.com/docker/compose/issues/374#issuecomment-126312313
# uses bash instead of netcat, because netcat is less likely to be installed
# strategy from http://superuser.com/a/806331/98716
set -e

echoerr() { echo "$@" 1>&2; }

echoerr wait-for-db: waiting for db:5432

timeout 15 bash <<EOT
while ! (echo > /dev/tcp/db/5432) >/dev/null 2>&1;
    do sleep 1;
done;
EOT
RESULT=$?

if [ $RESULT -eq 0 ]; then
  # sleep another second for so that we don't get a "the database system is starting up" error
  sleep 1
  echoerr wait-for-db: done
else
  echoerr wait-for-db: timeout out after 15 seconds waiting for db:5432
fi

exec "$@"

Isso deve ser resolvido no próximo (e aparentemente iminente com a atualização dos documentos) depend_on , certo?

Não. depends_on está apenas fazendo pedidos. Para realmente atrasar o início de outro contêiner, seria necessário haver alguma maneira de detectar quando um processo terminou de inicializar a si mesmo.

Ah, obrigado pelo esclarecimento. =)

Eu escrevi um utilitário de linha de comando bash puro chamado wait-for-it que pode ser incluído nas implantações do docker para ajudar a sincronizar as implantações de serviço.

Para mim não é uma boa ideia codificar uma coleção arbitrária de "verificações de disponibilidade". Existem inúmeras situações que são específicas para um tipo de implantação e você nunca pode cobrir todas elas. Apenas como exemplo, em meu aplicativo de vários contêineres, preciso esperar que uma determinada mensagem de log apareça em um determinado arquivo de log - só então o serviço de contêiner estará pronto.
Em vez disso, o que é necessário é um SPI que eu possa implementar. Se o Docker fornecer algumas implementações de exemplo para os casos de uso mais frequentes (por exemplo, conexão TCP), tudo bem. Mas deve haver uma maneira de conectar minha própria funcionalidade e fazer com que o Docker a chame.
O Docker Compose é praticamente inútil para mim como um produto inteiro, se eu não conseguir colocar meus contêineres em operação de maneira confiável. Portanto, é necessário um "SPI de prontidão de serviço de contêiner" estável e uniforme. E "pronto" não deve ser um booleano, pois possivelmente existem mais níveis de prontidão (como: "agora você pode ler" e "agora você pode escrever").

@realulim Boa

Isso é o que eu criei, no arquivo de entrada;

until netcat -z -w 2 database 5432; do sleep 1; done
# do the job here, database host on port 5432 accepts connections

@kulbida ,
Eu faço algo muito semelhante com o MySQL. "banco de dados", neste caso, é um link em um arquivo de composição.

if [[ "$APP_ENV" == "local" ]]; then
    while ! mysqladmin ping -h database --silent; do
        sleep 1
    done
    # Load in the schema or whatever else is needed here.
fi

Houve alguns comentários neste tópico que afirmam que a ordem de inicialização é apenas um subconjunto da recuperação de erro no nível do aplicativo, que seu aplicativo deve lidar de qualquer maneira. Eu gostaria de oferecer um exemplo para ilustrar onde isso pode nem sempre ser o caso. Considere se alguns serviços dependem de um banco de dados em cluster e sempre que um quorum é perdido devido a uma falha, etc., você _não_ deseja repetir automaticamente a partir do aplicativo. Esse pode ser o caso, por exemplo, se a recuperação do banco de dados exigir algumas etapas manuais e você precisar que os serviços permaneçam inativos até que essas etapas sejam executadas.

Agora, a lógica de tratamento de erros do aplicativo pode ser bem diferente da lógica de inicialização:

  • Se o banco de dados estiver inativo porque estamos apenas iniciando, espere que ele fique disponível.
  • Se o banco de dados estiver inativo porque travou, registre um erro crítico e morra.

Pode não ser o cenário mais comum, mas você vê esse padrão ocasionalmente. Nesse caso, o clustering é usado para resolver o problema "a rede não é confiável" no caso geral, o que muda algumas das expectativas em torno das quais as condições de erro devem ser repetidas no aplicativo. Travamentos de cluster podem ser raros o suficiente e reiniciá-los automaticamente pode ser arriscado o suficiente para que reiniciar os serviços manualmente seja preferível a tentar novamente no aplicativo. Suspeito que também haja outros cenários que podem desafiar as suposições sobre quando tentar novamente.

De maneira mais geral, estou afirmando que a ordem de inicialização e o tratamento de erros nem sempre são equivalentes e que é apropriado para uma estrutura fornecer recursos (opcionais) para gerenciar a ordem de inicialização. Eu me pergunto se isso pertence ao docker-engine, ao invés de compor. Ele pode ser necessário a qualquer momento em que o docker é inicializado, independentemente de o composição ser usado.

Há uma discussão começando no repositório do docker engine na proposta https://github.com/docker/docker/issues/21142 para adicionar suporte para verificação de integridade. Assim que este suporte estiver disponível, será possível para o Compose fornecer uma maneira de configurá-lo e usá-lo para uma inicialização retardada.

Que tal usar o sistema de arquivos para verificar a existência de um arquivo?

ready_on: /tmp/this_container_is_up_and_ready

Dessa forma, fica a critério do desenvolvedor do contêiner decidir quando as coisas estão ativadas, mas a composição pode esperar até que o contêiner se declare pronto. É uma convenção explícita, mas pode ser facilmente adicionada como uma camada adicional às imagens que não têm esse comportamento.

O suporte integrado para verificações de integridade será bom; enquanto isso, aqui está o hack que comecei a trabalhar em minha configuração docker-compose local:

    nginx:
        image: nginx:latest
        command: /bin/bash -c "sleep 2 && echo starting && nginx -g 'daemon off;'"
        ...

(Na produção, meu aplicativo faz proxy para alguns servidores upstream já em execução usando proxy_pass ; no desenvolvimento e teste local, eu inicio instâncias docker desses, e o nginx precisa esperar um pouco para que eles iniciem, caso contrário trava e morre. A coisa daemon off mantém o nginx em um único processo, caso contrário, o docker irá parar o contêiner assim que o processo pai gerar seu daemon filho.)

Só para somar meus dois centavos, se você estiver usando a ferramenta de compilação ANT, ela vem com suporte embutido para atrasar a execução até que um determinado soquete seja aberto.

Nosso servidor Jenkins CI gira os contêineres de projeto com Docker Compose e, em seguida, executa ANT de dentro do contêiner principal, como este:

docker-compose up -d
docker exec -it projectx-fpm-jenkins ant -f /var/www/projectX/build.xml

Esta é a parte relevante da configuração do arquivo docker-compose.yml. Observe que, como discutido acima, fazer o fpm depender do mysql não é suficiente para garantir que o serviço MySQL estará pronto quando for realmente necessário.

version: '2'
services:
  nginx:
    build: ./docker/nginx
    depends_on:
      - fpm
  fpm:
    build: ./docker/fpm
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=projectx
      - MYSQL_DATABASE=projectx

Mas você pode esperar por isso durante a tarefa ANT:

<!-- other targets... -->

<target name="setup db">
    <!-- wait until the 3306 TCP port in the "mysql" host is open -->
    <waitfor>
        <socket server="mysql" port="3306"/>
    </waitfor>

    <exec executable="php">
        <arg value="${consoledir}/console"/>
        <arg value="doctrine:database:create"/>
        <arg value="--no-interaction"/>
    </exec>
</target>

@kulbida Isso

while ! nc -w 1 -z db 5432; do sleep 0.1; done

_depends_on_ pode resolver o problema.
Da documentação do docker-compose .
Dependência expressa entre serviços, que tem dois efeitos:

  1. docker-compose up iniciará os serviços em ordem de dependência. No exemplo a seguir, db e redis serão iniciados antes de web.
  2. docker-compose up SERVICE incluirá automaticamente as dependências de SERVICE. No exemplo a seguir, docker-compose up web também criará e iniciará db e redis.

versão 2'
Serviços:
rede:
Construir: .
depende de:
- db
- redis
redis:
imagem: redis
db:
imagem: postgres

@alexch : em um teste de desempenho do cliente (microsserviço roteado via nginx +). Teste nginx dockerizado - uma queda na carga de alturas muito altas a quase zero baixa repetia-se a cada 1-2 minutos. Finalmente decidi ir com o Nginx não dockerizado rodando como uma VM (apenas por causa da enorme diferença de desempenho), talvez um problema de plugin / libNetwork do driver de rede.

@syamsathyan depends_on não parece ajudar.

@skorokithakis , @kulbida esta é uma boa solução. Infelizmente, netcat não está disponível por padrão em nenhum dos serviços que preciso para conectar ao meu banco de dados (incluindo postgres ). Você conhece algum método alternativo?

@nottrobin Acho que não, acabei de instalar na minha imagem: /

@nottrobin minha equipe está trabalhando nisso,

Para aqueles que têm o bash recente, existe uma solução sem netcat (inspirada em: http://stackoverflow.com/a/19866239/1581069):

while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/db/5432'; do sleep 0.1; done

ou versão menos detalhada:

while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/db/5432' >/dev/null 2>/dev/null; do sleep 0.1; done

@typekpb que funciona perfeitamente. Obrigado!

Agora que o suporte HEALTHCHECK foi mesclado upstream de acordo com https://github.com/docker/docker/pull/23218 - isso pode ser considerado para determinar quando um contêiner está íntegro antes de iniciar o próximo no pedido. Metade do quebra-cabeça resolvido :)

Agora que o suporte HEALTHCHECK foi mesclado upstream de acordo com docker / docker # 23218 - isso pode ser considerado para determinar quando um contêiner está íntegro antes de iniciar o próximo no pedido. Metade do quebra-cabeça resolvido :)

Parece bom. Como implementá-lo em docker-compose.yml ?

Parece bom. Como implementá-lo em docker-compose.yml?

A outra peça do quebra-cabeça será fazer com que docker-compose observe os contêineres saudáveis ​​e use algo como a sintaxe depends_on mencionada mais adiante neste problema. Exigirá patches para compor docker para fazer as coisas funcionarem.

Observe também que o recurso de verificação de integridade no Docker ainda não foi lançado, então provavelmente será necessário alinhar com um ciclo de lançamento do Docker / Docker Compose.

Eu escrevi uma biblioteca js que tem um método .waitForPort() . Assim como foi mencionado antes, isso pode não funcionar para todas as situações, mas pode funcionar bem para a maioria dos casos de uso.
Veja meu blog .

A fusão HEALTHCHECK é uma ótima notícia.

Nesse ínterim, este documento descreve o problema e algumas soluções.

@pablofmorales Não, porque depends_on apenas verifica se o contêiner está ativo .

Alguns daemons precisam de algum tempo extra para inicializarem e começarem a ouvir suas portas e endereços atribuídos, mais notavelmente o MySQL.

Ainda estou pensando que uma declaração "READY_ON" ainda é a melhor no geral. Ele deixa a decisão sobre quando algo está pronto para o próprio contêiner, independentemente da imagem, é explícito ao aceitar e a funcionalidade de recurso (dentro do contêiner) na API Docker Remote garante o mínimo de mudanças necessárias.

O comportamento de quando um contêiner está "ativo" é o único efeito que isso deve ter. Só será relatado como "ativo" quando o arquivo READY_ON existir.

Acho que isso é 90% do comportamento que todos estão discutindo. Acho que a "verificação de saúde" aqui está sendo confundida com 2 eventos diferentes, mas estou tentando comprimi-la em um. Um está "pronto" para a cadeia de eventos ao ativar a infraestrutura, o outro está "saudável" para que a infraestrutura possa ser mantida.

"pronto" é um lugar totalmente apropriado para o docker ajudar. Quanto à "saúde", é tão variado em termos de sistemas, acho que cabe ao container lidar com isso.

Para uma alternativa melhor à verificação de integridade, você pode querer olhar para algo como o containerpilot, que abrange não apenas a integridade, mas também a descoberta e o monitoramento de serviço. https://github.com/joyent/containerpilot

Sim, esta é uma distinção precisa e importante. No entanto, como os contêineres gravarão esse arquivo sem que as imagens se tornem significativamente mais complicadas? Parece-me que seria necessário um script de wrapper para cada contêiner que deseja usar isso.

Bem, você teria que lançar um script para inicializar a instância de qualquer maneira ... a última coisa que o script precisa fazer é tocar em um arquivo. Para mim, isso parece muito mais fácil do que tentar executar um exec em uma máquina remota para fazer uma verificação de integridade. Pelo menos com um arquivo de toque, ele pode ser assistido, etc. inteiramente por meio da API passivamente, sem a necessidade de entrar no contexto do contêiner.

Eu concordo, mas muitos containers não usam um script, eles apenas instalam um serviço como o Postgres ou Redis e o deixam iniciar sem assisti-lo.

No meu caso, estou usando o Kong API Gateway

Antes de executar o container kong, eu só verifico se Cassandra está trabalhando com este script

while true; do
    CHECK=`kong-database/check`
    if [[ $CHECK =~ "system.dateof" ]]; then
        break
    fi
    sleep 1;
done

o arquivo de verificação contém este

#!/bin/bash
docker cp cassandra-checker kong-database:/root/
docker exec -i kong-database cqlsh -f /root/cassandra-checker

cassandra-checker é apenas uma consulta simples

SELECT dateof(now()) FROM system.local ;

Claro, mas a alternativa é uma verificação de integridade, que requer um script que você teria que escrever de qualquer maneira, portanto, não há diferença no overhead. Também é uma opção explícita, o que significa que você está declarando que deseja esse comportamento. Quanto a algo que não executa um script, você sempre pode ter um caminho ready_on para verificar se há um arquivo pid ou um soquete unix; o que não exigiria um script.

Isso é verdade, você está certo.

Verificar a existência de um arquivo pode funcionar em muitos casos, mas forçar os contêineres a usarem um script de inicialização quando, de outra forma, não precisariam de um é um incômodo. Por que não pode haver verificação de outras condições muito simples? Especialmente útil seria esperar até que o processo estivesse escutando em uma porta tcp específica.

Essa ideia é opcional, portanto, não há forçar de nada. Na verdade, você está sendo explícito ao dizer o que se deve esperar.

A escuta da porta tcp pode não ser suficiente para dizer quando um contêiner foi inicializado, pois pode haver um monte de dados de configuração que precisam ser executados. Inferno, se você se conectar a um contêiner postgres muito rapidamente, mesmo por tcp, você receberá um erro informando que o banco de dados ainda não está pronto.

Se bem entendi, é "opt-in, ou então você não pode usar este recurso". Portanto, se eu precisar desse recurso e meu aplicativo não usar um arquivo pid, sou forçado a usar um script de inicialização.

Para o MySQL (o caso do OP), uma vez que está escutando, está pronto. Eles se esforçam muito para garantir que isso seja verdade, provavelmente em casos semelhantes a este. Minha opinião é que provavelmente há uma pequena lista de condições que podem ser enumeradas de forma que você possa "aceitar" configurando uma verificação pronta em relação a qualquer uma dessas condições. Não vejo razão para que isso seja feito de uma única maneira.

Para o mysql, uma vez que está ouvindo, não está pronto. No caso simples de um nó, ele estará pronto, mas se você tiver mais de um nó, certamente ainda não estará pronto. Eu entendo o que você quer dizer com "um e apenas um caminho", mas acho que como uma abstração de base é simplesmente perfeito. Eu vejo isso mais como um local onde você pode aplicar qualquer ferramenta que desejar. Inferno, seu script pode até mesmo se comunicar com serviços externos e fazer com que eles verifiquem o contêiner, caso em que seus serviços externos podem sinalizar seu agente de contêiner para gravar o arquivo. Flexibilidade ftw.

Se você tentar qualquer coisa nesta lista de "condições", SEMPRE haverá um caso em que não funcionará. No entanto, tocar em um arquivo sempre funcionará, já que a imagem sabe quando acredita que está pronta (oh, eu tenho que esperar em outros hosts, eu preciso fazer o download dos arquivos, eu preciso ter certeza de que $ external_service também está disponível, eu amplio corretamente, mas por algum motivo não tenho as permissões corretas para o banco de dados, por que esta imagem é somente leitura ... etc. etc.

Esses tipos de scripts já existem em todo o lugar ... diabos, já foi necessário escrever esses scripts porque não tínhamos uma funcionalidade como esta antes. Portanto, incluir um script como esse é mínimo, pois é provável que já exista um script.

Outro caso provável é que algo como chef ou ansible seja executado nesse host e, em seguida, grave o arquivo.

Se for uma questão de verificação do lado do Docker, algo como;

UPCHECK --port=7474 --interval=0.5s --response="Please log in"

Para constar, acho que a solução de arquivos tem muito mérito, mas também apresenta complexidade.
80% das vezes, verificar a resposta tcp funcionaria perfeitamente.

bem ... eu suponho:

UPCHECK --file=/tmp/container_is_ready --interval=0.5s --timeout=2m

É exatamente o mesmo.

Na verdade, estou trabalhando em uma reimplementação do docker-compose que adiciona funcionalidade para aguardar condições específicas. Ele usa libcompose (então não tenho que reconstruir a interação do docker) e adiciona um monte de comandos de configuração para isso. Confira aqui: https://github.com/dansteen/controlled-compose

Observe que o código está concluído, mas estou aguardando que alguns problemas do upstream sejam resolvidos antes que isso possa ser realmente usado.

Goss pode ser usado como um shim bastante flexível para atrasar a inicialização do contêiner. Escrevi uma postagem no blog explicando como isso pode ser feito com uma pequena alteração em sua imagem aqui:

O Kubernetes tem o conceito de contêineres de

+1

Acho que é melhor deixar o serviço que você está expondo em um contêiner decidir se está pronto ou se é capaz de expor seu serviço.

Por exemplo, para um aplicativo PHP pode depender da conexão do MySQL. Então, no ENTRYPOINT do contêiner de PHP, escrevi algo assim.

#!/bin/bash
cat << EOF > /tmp/wait_for_mysql.php
<?php
\$connected = false;
while(!\$connected) {
    try{
        \$dbh = new pdo( 
            'mysql:host=mysql:3306;dbname=db_name', 'db_user', 'db_pass',
            array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
        );
        \$connected = true;
    }
    catch(PDOException \$ex){
        error_log("Could not connect to MySQL");
        error_log(\$ex->getMessage());
        error_log("Waiting for MySQL Connection.");
        sleep(5);
    }
}
EOF
php /tmp/wait_for_mysql.php
# Rest of entry point bootstrapping

Desta forma, posso adicionar qualquer lógica para garantir que as dependências do serviço que estou expondo, ou seja, php, foram resolvidas.

Nabin Nepal Schrieb:

Acho que é melhor deixar o serviço que você está expondo em um contêiner decidir se está pronto ou se é capaz de expor seu serviço.

Você pode, é claro, codificar esse comportamento em cada contêiner que usa seu
Contêiner MySql. Mas se algo mudar em seu serviço MySql, então você está
mudando todos os contêineres dependentes, para não falar da codificação repetitiva
necessário em cada um. Isso não é SECO, não há contrato estável e, portanto,
levam a sistemas frágeis.

Do ponto de vista da habilidade de software, deve haver algum tipo de
"Container Readiness SPI", que o desenvolvedor do container pode implementar. Em
do outro lado deve haver uma "API Container Readiness", que o
serviços podem depender.

Ulrich

@realulim Concordo que qualquer alteração no contêiner do MySQL deve ser replicada ou propagada para todos os contêineres afetados ou vinculados.

No entanto, se a mudança for sobre parâmetros como DB_HOST, DB_NAME, DB_USER e DB_PASSWORD. Eles podem ser passados ​​como um ARG (argumento) e ser compartilhados por todos os contêineres relacionados. Se você estiver usando docker-compose.yml file, a mudança ocorre em um arquivo.

E concordo totalmente que ter uma API para verificar a disponibilidade do contêiner é a maneira real de resolver isso, mas ainda acredito que o serviço exposto seria um candidato melhor para declarar isso.

uma solução alternativa until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done

@ piotr-s-brainhub Dos comentários acima menciona que ter uma porta aberta não significa que o serviço está pronto.

Podemos ter condição opcional de prontidão que pode ser acionada por logs, abertura de porta ou atraso de tempo? Algo como:

ready_when:
  in_logs: `MySQL init process done`
  ports_open:
  - 3306

Acabei de perceber que esperar que os contêineres de dependência fiquem prontos pode ser facilmente implementado com ferramentas como o ansible. Alguém usou essa abordagem? Você pode substituir facilmente docker-compose por ansible / chef / puppet? Algum projeto no github demonstrando essa abordagem?

Nota: Eu entendo a importância de escrever um serviço robusto que possa ser executado mesmo quando suas dependências não estiverem disponíveis no momento. Essa não é a questão.

Resolvi isso hoje em dia com uma ferramenta que escrevi: https://github.com/betalo-sweden/await

Ele pode esperar até que uma determinada lista de recursos esteja disponível e continuar com o que você deseja, indo para o próximo comando implicitamente ou chamando-o explicitamente.

@djui , o que espera fazer enquanto espera por um determinado recurso?

@derekmahar Ele pesquisa. Tem um tempo limite padrão de 60 segundos. Sempre que não puder ver o recurso, ele tentará novamente em intervalos de 1s. Atualmente ele não faz detecção de recursos simultâneos, então é sequencial, mas isso acabou sendo bom o suficiente e pode ser corrigido.

Eu o uso no seguinte cenário:

Eu inicio uma infraestrutura docker-compose e, em seguida, executo um driver de teste de integração. O serviço do driver é iniciado somente depois que todos os componentes da infraestrutura estão disponíveis, usando o await; assim, o await eventualmente chama o comando de execução do driver.

Esta é uma maneira de fazer isso com a nova diretiva HEALTHCHECK do Docker usando make:

https://gist.github.com/mixja/1ed1314525ba4a04807303dad229f2e1

[ATUALIZAÇÃO: essência atualizada para lidar com se o contêiner sair com um código de erro, já que o Docker 1.12 relata estupidamente o status do Healthcheck no contêiner interrompido como "iniciando"]

Obrigado @mixja , boa solução.

@mixja , boa solução! Essa é exatamente a funcionalidade que eu esperaria sair da caixa. Mas agora a questão é: se você iniciar seus contêineres manualmente, por que precisa do docker-compose?

Para testes, eu uso https://github.com/avast/docker-compose-gradle-plugin e ele também usa a verificação de integridade do Docker - sem mais pausas artificiais, compilações mais rápidas.

@korya - Docker compose não é realmente uma ferramenta de orquestração - é mais uma especificação de ambiente e ferramenta de gerenciamento. Eu uso Make para fornecer orquestração de estilo procedural no Docker Compose e Docker (e outras ferramentas, conforme necessário). A combinação de Make, Docker e Docker Compose é muito poderosa e você pode obter muitos cenários diferentes com esses blocos de construção.

@mixja bem, pode ser que você esteja certo. Mas, como muitas pessoas apontaram neste tópico, uma funcionalidade de orquestração é muito necessária em ambientes de teste e, quando há docker-compose em sua caixa de ferramentas, é muito tentador exigir esse tipo de funcionalidade de docker-compose.

Na verdade, de acordo com os documentos "Compose é uma ferramenta para definir e executar aplicativos Docker multi-contêiner". Embora não diga que o Compose é uma ferramenta de orquestração, acho que da perspectiva do usuário (por exemplo, eu mesmo) é natural esperar de "uma ferramenta para definir e executar aplicativos Docker de vários contêineres" para oferecer suporte ao gerenciamento de dependência básica entre os contêineres gerenciados sai da caixa.

Não estou dizendo que a ferramenta tem que dar suporte a isso. Tudo o que estou dizendo é que é muito natural esperar isso. Caso contrário, todos terão que inventar maneiras superinteligentes de fazer isso. Na verdade, usamos um script bash fazendo algo semelhante ao que o makefile faz.

@mixja @korya Eu gostaria de melhorar minha ferramenta, aguarde, e gostaria de pedir um feedback sobre o que suas versões do Makefile fornecem que está faltando / mais conveniente / habilitando mais de await .

Parece que a versão healthcheck + make parece ser uma visão "global", nenhum contêiner único conhece o estado global (mas o makefile sim) e await é uma visão "local", cada contêiner habilitado conhece (apenas) o que ele precisa saber, semelhante a depends_on ou links . Além disso, você prefere enviar o contêiner com as ferramentas necessárias para a verificação de integridade (que às vezes é o padrão, por exemplo, mysqlshow ) e, de outra forma, deixar o Dockerfile intocado. Além disso, você parece usar docker-compose não mais principalmente para a composição, mas principalmente para a configuração flexível (por exemplo, docker-compose up -d mysql deve ser equivalente a docker run -d -e ... -v ... -p ... mysql ).

Olá @djui - provavelmente é um ponto de vista filosófico, mas acho que toda a premissa do HEALTHCHECK é promover o comportamento correto - ou seja, um contêiner pode fornecer um meio de estabelecer a integridade do contêiner, sem quaisquer dependências externas.

Isso de forma alguma diminui o valor de ter algo externo para verificar a conectividade, no entanto, eu normalmente executaria um conjunto de testes de aceitação para cobrir isso, pois você deseja verificar a conectividade e muito mais (ou seja, a funcionalidade do aplicativo). É claro que você geralmente não pode executar este nível de teste até que um ambiente completo tenha sido estabelecido e o escopo de sua ferramenta await e outras abordagens que usei no passado (manuais Ansible embrulhados em um agent container) está realmente focado em obter a configuração do ambiente orquestrada corretamente (não o objetivo final do teste de aceitação) e até agora era realmente a única abordagem disponível em um mundo Docker.

Com o Docker 1.12, agora temos um meio de introspectar o ambiente do Docker e a capacidade de usar construções bem estabelecidas (ou seja, mecanismos bash / shell) para "aguardar" um determinado estado, é claro, desde que nossos contêineres tenham definido suas próprias verificações de saúde . Vejo mais valor em aproveitar os recursos nativos da plataforma e incentivar os proprietários de contêiner a definir suas próprias verificações de integridade, em vez de confiar na abordagem externa histórica (comecei meu processo de inscrição, não é mais meu problema) que tivemos de recorrer a.

Como uma analogia relacionada, considere o AWS CloudFormation e o conceito de grupos de escalonamento automático e orquestração de atualizações contínuas. Como o CloudFormation sabe se uma nova instância está "íntegra" pronta para funcionar e podemos matar uma instância antiga e rolar em outra nova? Escrevemos uma verificação de integridade externa ou contamos com a própria instância para sinalizar a integridade? A resposta é a última, significa que o proprietário da instância pode definir quaisquer critérios de sucesso necessários para sua instância e, em seguida, sinalizar para o sistema de orquestração abrangente (ou seja, CloudFormation) que a instância está "íntegra".

Com relação aos seus comentários sobre o Docker Compose - é uma ferramenta que pode fornecer os dois aspectos que você mencionou. A parte docker-compose.yml é a especificação do ambiente de composição do estado desejado, enquanto os vários comandos docker-compose fornecem a capacidade de interagir com o ambiente de várias maneiras. Por enquanto, precisamos de ferramentas de orquestração externas porque, fundamentalmente, docker-compose não executa o gerenciamento de dependências entre serviços bem o suficiente. Como docker-compose obtém recursos como suporte para verificação de integridade nativa, o objetivo de um único comando docker-compose up será mais realista, assumindo que seremos capazes de especificar, por exemplo, um serviço deve ser marcado como saudável antes é considerado "ativo", o que significa que nossos serviços dependentes esperam efetivamente até que a dependência esteja íntegra.

@mixja Obrigado pela explicação detalhada. eu acho que

Vejo mais valor em aproveitar os recursos nativos da plataforma

é um ponto bom / principal. Apenas esperando o Docker Compose alavancar as verificações de saúde nativamente em depends_on ou uma nova chave, aguarde. Apenas me pergunto se deveria / irá dar um passo além disso e basicamente derrubar os containers vinculados se, por exemplo, --abort-on-container-exit estiver definido e uma verificação de saúde durante o tempo de execução definir o rótulo de verificação de integridade para _unhealthy_.

Possível solução temporária para aqueles que procuram delay functionallity para executar testes:

Eu tenho dois arquivos docker-compose yml. Um é para teste e outro para desenvolvimento. A diferença é apenas ter sut contêiner em docker-compose.test.yml . sut container executa pytest . Meu objetivo era teste de corrida docker-compose e se pytest comando no sut contêiner falhar, não correm o desenvolvimento docker-compose . Aqui está o que eu descobri:

# launch test docker-compose; note: I'm starting it with -p argument
docker-compose -f docker-compose.test.yml -p ci up --build -d
# simply get ID of sut container
tests_container_id=$(docker-compose -f docker-compose.test.yml -p ci ps -q sut)
# wait for sut container to finish (pytest will return 0 if all tests passed)
docker wait $tests_container_id
# get exit code of sut container
tests_status=$(docker-compose -f docker-compose.test.yml -p ci ps -q sut | xargs docker inspect -f '{{ .State.ExitCode  }}' | grep -v 0 | wc -l | tr -d ' ')
# print logs if tests didn't pass and return exit code
if [ $tests_status = "1" ] ; then
    docker-compose -f docker-compose.test.yml -p ci logs sut
    return 1
else
    return 0
fi

Agora você pode usar o código acima em qualquer função de sua escolha (a minha se chama test ) e fazer o seguinte:

test
test_result=$?
if [[ $test_result -eq 0 ]] ; then
    docker-compose -f docker-compose.yml up --build -d
fi

Funciona bem para mim, mas ainda estou ansioso para ver docker-compose apoiar esse tipo de coisa nativamente :)

+1

Talvez coisas que são consideradas fora do núcleo do docker-compose pudessem ser suportadas permitindo plug-ins? Semelhante ao pedido nº 1341, parece que há funcionalidades adicionais que alguns achariam úteis, mas não necessariamente alinham totalmente com a visão atual. Talvez o suporte de um sistema de plug-in como o proposto por # 3905 forneça uma maneira de permitir o foco de composição em um conjunto básico de recursos e, se este não for um, então aqueles que o desejam para seu caso de uso específico poderiam escrever um plug-in para lidar com o desempenho de up diferente?

Seria bom poder ter docker-compose atuando como o ponto de entrada para todos os projetos que temos localmente em torno da configuração do env docker, em vez de precisar adicionar um script colocado na frente de todos para atuar como o ponto de entrada padrão em vez de pessoas que precisam se lembrar de executar o script para os casos ímpares.

Esta é uma maneira de fazer isso com healthcheck e docker-compose 2.1+ :

version: "2.1"
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
    healthcheck:
      test: mysqladmin -uroot -ppassword ping
      interval: 2s
      timeout: 5s
      retries: 30
  web:
    image: nginx:latest # your image
    depends_on:
      db:
        condition: service_healthy

Aqui, docker-compose up iniciará o contêiner da web somente depois que o contêiner db for considerado íntegro.

Desculpe se já foi mencionado, mas não acho que uma solução completa foi postada.

Esta é uma forma de PostgreSQL.

Obrigado @Silex 👍

version: '2.1'
services:
  db:
    image: postgres:9.6.1
    healthcheck:
      test: "pg_isready -h localhost -p 5432 -q -U postgres"
      interval: 3s
      timeout: 5s
      retries: 5

@Silex infelizmente com a versão "3" e este formato:

    image: nginx:latest # your image
    depends_on:
      db:
        condition: service_healthy

Eu ganho ERROR: The Compose file './docker-compose.yml' is invalid because: depends_on contains an invalid type, it should be an array

2.1 continua a oferecer suporte e não será descontinuado. 3.x é principalmente para o modo de serviços de enxame (não local).

  From: Vlad Filippov <[email protected]>

Para: docker / compose [email protected]
Cc: mbdas [email protected] ; Mencione mençã[email protected]
Enviado: quarta-feira, 8 de março de 2017 11h45
Assunto: Re: [docker / compose] Existe uma maneira de atrasar a inicialização do contêiner para oferecer suporte a serviços dependentes com um tempo de inicialização mais longo (# 374)

@Silex infelizmente com a versão "3" e este formato: imagem: nginx: última # sua imagem
depende de:
db:
condição: service_healthy
Recebo ERROR: O arquivo Compose './docker-compose.yml' é inválido porque: services.auth.depends_on contém um tipo inválido, deveria ser uma matriz—
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub ou ignore a conversa.

2.1 continua a oferecer suporte e não será descontinuado. 3.x é principalmente para o modo de serviços de enxame (não local).

Obrigado!

@vladikoff : mais informações sobre a versão 3 em https://github.com/docker/compose/issues/4305

Basicamente, isso não terá suporte, você precisa tornar seus contêineres tolerantes a falhas em vez de depender do docker-compose.

Eu acredito que isso pode ser fechado agora.

Infelizmente, a condição não é mais

website:
    depends_on:
      - 'postgres'
    build: .
    ports:
      - '3000'
    volumes:
      - '.:/news_app'
      - 'bundle_data:/bundle'
    entrypoint: ./wait-for-postgres.sh postgres 5432

  postgres:
    image: 'postgres:9.6.2'
    ports:
      - '5432'

wait-for-postgres.sh:

#!/bin/sh

postgres_host=$1
postgres_port=$2
shift 2
cmd="$@"

# wait for the postgres docker to be running
while ! pg_isready -h $postgres_host -p $postgres_port -q -U postgres; do
  >&2 echo "Postgres is unavailable - sleeping"
  sleep 1
done

>&2 echo "Postgres is up - executing command"

# run the command
exec $cmd

O ponto de entrada personalizado @ slava-nikulin é uma prática comum, é quase a única maneira (nativo do docker) de definir e verificar todas as condições necessárias antes de iniciar seu aplicativo em um contêiner.

A verdade é que houve muito debate e acho que o suporte 2.x para o suporte condicional para integrar nativamente com verificações de saúde e solicitar a inicialização era um suporte muito necessário. O Docker não oferece suporte a pod local de contêineres nativamente e, quando o fizer, terá que oferecer suporte a algo semelhante novamente, assim como o kubernetes, por exemplo, fornece a semântica.

O Docker 3.x é uma série para trazer o suporte a enxames para a composição e, portanto, um monte de opções foi descartado mantendo a natureza distribuída em mente.

A série 2.x preserva os recursos de composição / topologia local originais.

O Docker precisa descobrir como mesclar essas 2 versões porque forçar o swarm a compor reduzindo o conjunto de recursos de composição não é uma direção bem-vinda.

Em 10 de maio de 2017, às 20:15, Slava Nikulin [email protected] escreveu:

Infelizmente, a condição não é mais compatível com a v3. Aqui está a solução alternativa que encontrei:

local na rede Internet:
depende de:
- 'postgres'
Construir: .
portas:
- '3000'
volumes:
- '.: / news_app'
- 'bundle_data: / bundle'
entrypoint: ./wait-for-postgres.sh postgres 5432

postgres:
imagem: ' postgres: 9.6.2 '
portas:
- '5432'
wait-for-postgres.sh:

! / bin / sh

postgres_host = $ 1
postgres_port = $ 2
cmd = "$ @"

espere que o docker do postgres esteja rodando

enquanto ! pg_isready -h $ postgres_host -p $ postgres_port -q -U postgres; Faz

& 2 echo "Postgres indisponível - hibernando"
dormir 1
feito

& 2 echo "Postgres está ativo - executando o comando"

execute o comando

exec $ cmd
-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub ou ignore a conversa.

Eu fui capaz de fazer algo assim
// start.sh

#!/bin/sh
set -eu

docker volume create --name=gql-sync
echo "Building docker containers"
docker-compose build
echo "Running tests inside docker container"
docker-compose up -d pubsub
docker-compose up -d mongo
docker-compose up -d botms
docker-compose up -d events
docker-compose up -d identity
docker-compose up -d importer
docker-compose run status
docker-compose run testing

exit $?

// status.sh

#!/bin/sh

set -eu pipefail

echo "Attempting to connect to bots"
until $(nc -zv botms 3000); do
    printf '.'
    sleep 5
done
echo "Attempting to connect to events"
until $(nc -zv events 3000); do
    printf '.'
    sleep 5
done
echo "Attempting to connect to identity"
until $(nc -zv identity 3000); do
    printf '.'
    sleep 5
done
echo "Attempting to connect to importer"
until $(nc -zv importer 8080); do
    printf '.'
    sleep 5
done
echo "Was able to connect to all"

exit 0

// em meu arquivo de composição do docker

  status:
    image: yikaus/alpine-bash
    volumes:
      - "./internals/scripts:/scripts"
    command: "sh /scripts/status.sh"
    depends_on:
      - "mongo"
      - "importer"
      - "events"
      - "identity"
      - "botms"

Eu tenho um problema semelhante, mas um pouco diferente. Tenho que esperar o MongoDB iniciar e inicializar um conjunto de réplicas.
Estou fazendo todo o procedimento no docker. ou seja, criação e autenticação de conjunto de réplicas. Mas eu tenho outro script python no qual preciso me conectar ao nó primário do conjunto de réplicas. Estou recebendo um erro aí.

docker-compose.txt
Dockerfile.txt
e no script python estou tentando fazer algo assim
for x in range(1, 4): client = MongoClient(host='node' + str(x), port=27017, username='admin', password='password') if client.is_primary: print('the client.address is: ' + str(client.address)) print(dbName) print(collectionName) break

Estou tendo dificuldade em fazer isso, alguém tem ideia?

@patrickml Se eu não usar o docker compose, como faço isso com o Dockerfile?
Eu preciso de 'cqlsh' para executar meu build_all.cql. No entanto, 'cqlsh' não está pronto ... tem que esperar 60 segundos para ficar pronto.

Cat Dockerfile

DA loja / datastax / dse- servidor: 5.1.8

Usuário root

RUN apt-get update
RUN apt-get install -y vim

ADICIONE db-scripts-2.1.33.2-RFT-01.tar / docker / cms /
COPIAR entrypoint.sh /entrypoint.sh

WORKDIR /docker/cms/db-scripts-2.1.33.2/
RUN cqlsh -f build_all.cql

USUÁRIO dse

=============

Etapa 8/9: EXECUTE cqlsh -f build_all.cql
---> Executando em 08c8a854ebf4
Erro de conexão: ('Não foi possível conectar a nenhum servidor', {'127.0.0.1': erro (111, "Tentou conectar-se a [('127.0.0.1', 9042)]. Último erro: Conexão recusada")})
O comando '/ bin / sh -c cqlsh -f build_all.cql' retornou um código diferente de zero: 1

Requer = var-lib-libvirt.mount var-lib-libvirt-images-ram.mount

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

maltefiala picture maltefiala  ·  3Comentários

bitver picture bitver  ·  3Comentários

29e7e280-0d1c-4bba-98fe-f7cd3ca7500a picture 29e7e280-0d1c-4bba-98fe-f7cd3ca7500a  ·  3Comentários

saulshanabrook picture saulshanabrook  ·  3Comentários

CrimsonGlory picture CrimsonGlory  ·  3Comentários