Docker Compose aguarde o contêiner X antes de iniciar Y

326

Estou usando rabbitmq e um exemplo simples de python daqui em conjunto com o docker-compose. Meu problema é que preciso aguardar a inicialização completa do rabbitmq. Pelo que pesquisei até agora, não sei como esperar com o contêiner x (no meu assistente de caso) até que y (rabbitmq) seja iniciado.

Encontrei este blog onde ele verifica se o outro host está online. Eu também encontrei este comando docker :

esperar

Uso: docker wait CONTAINER [CONTAINER ...]

Bloqueie até que um contêiner pare e imprima seu código de saída.

Esperar a parada de um contêiner talvez não seja o que estou procurando, mas, se for, é possível usar esse comando dentro do docker-compose.yml? Até agora, minha solução é aguardar alguns segundos e verificar a porta, mas é esse o caminho para conseguir isso? Se eu não esperar, recebo um erro.

docker-compose.yml

worker:
    build: myapp/.
    volumes:
    - myapp/.:/usr/src/app:ro

    links:
    - rabbitmq
rabbitmq:
    image: rabbitmq:3-management

Olá, exemplo python (rabbit.py):

import pika
import time

import socket

pingcounter = 0
isreachable = False
while isreachable is False and pingcounter < 5:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('rabbitmq', 5672))
        isreachable = True
    except socket.error as e:
        time.sleep(2)
        pingcounter += 1
    s.close()

if isreachable:
    connection = pika.BlockingConnection(pika.ConnectionParameters(
            host="rabbitmq"))
    channel = connection.channel()

    channel.queue_declare(queue='hello')

    channel.basic_publish(exchange='',
                          routing_key='hello',
                          body='Hello World!')
    print (" [x] Sent 'Hello World!'")
    connection.close()

Dockerfile para trabalhador:

FROM python:2-onbuild
RUN ["pip", "install", "pika"]

CMD ["python","rabbit.py"]

Atualização de novembro de 2015 :

Um script de shell ou aguardar dentro do seu programa é talvez uma solução possível. Mas, depois de ver esse problema , estou procurando um comando ou recurso do docker / docker-componen.

Eles mencionam uma solução para implementar uma verificação de saúde, que pode ser a melhor opção. Uma conexão tcp aberta não significa que seu serviço está pronto ou pode permanecer pronto. Além disso, preciso alterar meu ponto de entrada no meu arquivo docker.

Portanto, espero obter uma resposta com os comandos docker-compose on board, que, esperançosamente, serão o caso se eles terminarem esse problema.

Atualização de março de 2016

Existe uma proposta para fornecer uma maneira interna de determinar se um contêiner está "vivo". Portanto, o docker-compose pode utilizá-lo no futuro próximo.

Atualização junho de 2016

Parece que a verificação de saúde será integrada ao docker na versão 1.12.0

Atualização de janeiro de 2017

Encontrei uma solução docker-compose, consulte: Docker Compose, aguarde o contêiner X antes de iniciar Y

svenhornberg
fonte
2
O uso de verificações de integridade foi preterido no docker-compose 2.3 para incentivar a tolerância a falhas nos sistemas distribuídos. Veja: docs.docker.com/compose/startup-order
Kmaid

Respostas:

284

Finalmente encontrei uma solução com um método docker-compose. Desde o formato de arquivo 2.1 do docker-compose, você pode definir verificações de saúde .

Fiz isso em um projeto de exemplo que você precisa instalar pelo menos o docker 1.12.0+. Eu também precisava estender o Dockerfile rabbitmq-management , porque o curl não está instalado na imagem oficial.

Agora, testo se a página de gerenciamento do rabbitmq-container está disponível. Se a curvatura terminar com o exitcode 0, o aplicativo de contêiner (python pika) será iniciado e publicará uma mensagem na fila hello. Agora está funcionando (saída).

docker-compose (versão 2.1):

version: '2.1'

services:
  app:
    build: app/.
    depends_on:
      rabbit:
        condition: service_healthy
    links: 
        - rabbit

  rabbit:
    build: rabbitmq/.
    ports: 
        - "15672:15672"
        - "5672:5672"
    healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:15672"]
        interval: 30s
        timeout: 10s
        retries: 5

resultado:

rabbit_1  | =INFO REPORT==== 25-Jan-2017::14:44:21 ===
rabbit_1  | closing AMQP connection <0.718.0> (172.18.0.3:36590 -> 172.18.0.2:5672)
app_1     |  [x] Sent 'Hello World!'
healthcheckcompose_app_1 exited with code 0

Arquivo de encaixe (rabbitmq + curl):

FROM rabbitmq:3-management
RUN apt-get update
RUN apt-get install -y curl 
EXPOSE 4369 5671 5672 25672 15671 15672

A versão 3 não suporta mais o formato de condição de depende_on . Então, mudei de depende_on para reiniciar em caso de falha. Agora, meu contêiner de aplicativos será reiniciado 2-3 vezes até que esteja funcionando, mas ainda é um recurso de composição do docker sem substituir o ponto de entrada.

docker-compose (versão 3):

version: "3"

services:

  rabbitmq: # login guest:guest
    image: rabbitmq:management
    ports:
    - "4369:4369"
    - "5671:5671"
    - "5672:5672"
    - "25672:25672"
    - "15671:15671"
    - "15672:15672"
    healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:15672"]
        interval: 30s
        timeout: 10s
        retries: 5

  app:
    build: ./app/
    environment:
      - HOSTNAMERABBIT=rabbitmq
    restart: on-failure
    depends_on:
      - rabbitmq
    links: 
        - rabbitmq
svenhornberg
fonte
6
@svenhornberg pingusa ICMP, portanto, não suporta portas TCP. Talvez ncpara testar uma porta TCP. Provavelmente é melhor usar psql -h localhost -p 5432e consultar algo.
Matt
36
"depende de" foi removido na versão 3 docs.docker.com/compose/compose-file/#dependson
nha
48
@nha Parece que a conditionforma de depends_onfoi removida, mas depends_onela ainda existe na v3 #
akivajgordon
14
Como pode Healthchecks ainda ser usado para fim de controle de inicialização se depends_oncom conditionfoi removido?
Franz
43
Difícil de acreditar essa dor tal ainda
npr
71

Nativamente, isso ainda não é possível. Veja também esta solicitação de recurso .

Até agora, você precisa fazer isso em seus contêineres CMDpara aguardar até que todos os serviços necessários estejam lá.

Nos Dockerfiles, CMDvocê pode se referir ao seu próprio script de início que encerra a inicialização do serviço de contêiner. Antes de iniciá-lo, aguarde um dependendo, como:

Dockerfile

FROM python:2-onbuild
RUN ["pip", "install", "pika"]
ADD start.sh /start.sh
CMD ["/start.sh"]

start.sh

#!/bin/bash
while ! nc -z rabbitmq 5672; do sleep 3; done
python rabbit.py

Provavelmente você também precisa instalar o netcat no seu Dockerfile. Não sei o que é pré-instalado na imagem python.

Existem algumas ferramentas disponíveis que fornecem lógica de espera fácil de usar, para verificações simples da porta tcp:

Para esperas mais complexas:

0x7d7b
fonte
Você poderia explicar o que você quer dizer com CMD? Isso significa que meu programa precisa fazer isso, como eu fiz com uma verificação de porta? Ou você quer dizer um CMD específico, por exemplo, linux para isso?
31415 sv svornornberg
obrigado por explicar, voto positivo sua resposta. Mas acho que a próxima solicitação de recurso seria a resposta certa para minha pergunta, então deixo sem resposta até o momento.
svenhornberg
44

Usando restart: unless-stoppedou restart: alwayspode resolver esse problema.

Se o worker containerparar quando o rabbitMQ não estiver pronto, ele será reiniciado até que esteja.

Toilal
fonte
3
Eu gosto dessa solução para esse caso, mas não funciona para contêineres que não saem quando um dos subprocessos que ele executa falha. Por exemplo, um contêiner do Tomcat continuaria sendo executado, mesmo que um servlet Java que ele executasse falhasse na conexão com um servidor de banco de dados. Concedido, os contêineres do Docker tornam desnecessários os contêineres de servlet como o Tomcat.
Derek Mahar
@DerekMahar, se você possui um aplicativo Web baseado em Java que atende apenas chamadas REST, o que você usa em vez do Jetty / Tomcat?
21816 JoeG
2
@ JoeG, eu quis dizer Tomcat o contêiner de servlet que pode hospedar muitos aplicativos, não Tomcat incorporado. O Docker torna o primeiro praticamente desnecessário, tornando o último mais popular para microsserviços, por exemplo.
Derek Mahar 21/12
35

Recentemente, eles adicionaram o depends_onrecurso .

Editar:

A partir do compor versão 2.1+, você pode usar depends_onem conjunto com healthcheckpara conseguir isso:

Dos documentos :

version: '2.1'
services:
  web:
    build: .
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
  redis:
    image: redis
  db:
    image: redis
    healthcheck:
      test: "exit 0"

Antes da versão 2.1

Você ainda pode usar depends_on, mas isso afeta apenas a ordem em que os serviços são iniciados - e não se eles estiverem prontos antes do serviço dependente ser iniciado.

Parece exigir pelo menos a versão 1.6.0.

O uso ficaria assim:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres 

Dos documentos:

Expressar dependência entre serviços, que tem dois efeitos:

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

Nota: Pelo que entendi, embora isso defina a ordem em que os contêineres são carregados. Não garante que o serviço dentro do contêiner tenha realmente sido carregado.

Por exemplo, o contêiner do postgres pode estar ativo. Mas o próprio serviço postgres ainda pode estar inicializando no contêiner.

toast38coza
fonte
10
dnephin escreveu: depende_on está apenas ordenando. Para realmente atrasar o início de outro contêiner, haveria uma maneira de detectar quando um processo terminou de se inicializar.
svenhornberg
15
"A versão 3 não suporta mais o formulário de condição de depends_on." docs.docker.com/compose/compose-file/#dependson
akauppi
depends_onnão espera até que o contêiner esteja no readyestado (o que isso pode significar no seu caso). Ele aguarda apenas até que o contêiner esteja no estado 'running'.
Htagi #
19

você também pode adicioná-lo à opção de comando, por exemplo.

command: bash -c "sleep 5; start.sh"

https://github.com/docker/compose/issues/374#issuecomment-156546513

para esperar em uma porta, você também pode usar algo como isto

command: bash -c "while ! curl -s rabbitmq:5672 > /dev/null; do echo waiting for xxx; sleep 3; done; start.sh"

Para aumentar o tempo de espera, você pode hackear um pouco mais:

command: bash -c "for i in {1..100} ; do if ! curl -s rabbitmq:5672 > /dev/null ; then echo waiting on rabbitmq for $i seconds; sleep $i; fi; done; start.sh"
AmanicA
fonte
13

restart: on-failure fez o truque para mim .. veja abaixo

---
version: '2.1'
services:
  consumer:
    image: golang:alpine
    volumes:
      - ./:/go/src/srv-consumer
    working_dir: /go/src/srv-consumer
    environment:
      AMQP_DSN: "amqp://guest:guest@rabbitmq:5672"
    command: go run cmd/main.go
    links:
          - rabbitmq
    restart: on-failure

  rabbitmq:
    image: rabbitmq:3.7-management-alpine
    ports:
      - "15672:15672"
      - "5672:5672"
Edwin Ikechukwu Okonkwo
fonte
12

Para pedidos de início de contêiner, use

depends_on:

Para aguardar o contêiner anterior, comece a usar o script

entrypoint: ./wait-for-it.sh db:5432

Este artigo ajudará você a https://docs.docker.com/compose/startup-order/

Sair
fonte
5
@svenhornberg no comentário, você vincula, não há explicação sobre o recurso wait-for-it.sh.
encerre
7

Você também pode resolver isso definindo um terminal que aguarda a ativação do serviço usando o netcat (usando o script docker-wait ). Eu gosto dessa abordagem, pois você ainda possui uma commandseção limpa no seu docker-compose.ymle não precisa adicionar código específico do docker ao seu aplicativo:

version: '2'
services:
  db:
    image: postgres
  django:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    entrypoint: ./docker-entrypoint.sh db 5432
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

Então o seu docker-entrypoint.sh:

#!/bin/sh

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

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

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

# run the command
exec $cmd

Atualmente, isso está documentado na documentação oficial da janela de encaixe .

PS: você deve instalar netcatna sua instância do docker se isso não estiver disponível. Para fazer isso, adicione isso ao seu Dockerarquivo:

RUN apt-get update && apt-get install netcat-openbsd -y 
maerteijn
fonte
4

Existe um utilitário pronto para uso chamado " docker-wait " que pode ser usado para aguardar.

Adrian Mitev
fonte
1
Obrigado, mas é apenas um script de shell, por isso é como a resposta do h3nrik ou a espera dentro do python. Não é um recurso da própria docker-componha. Você pode dar uma olhada no github.com/docker/compose/issues/374, eles planejam implementar uma verificação de saúde que seria a melhor maneira. Uma conexão tcp aberta não significa que seu serviço está pronto ou pode permanecer pronto. Além disso, preciso alterar meu ponto de entrada no meu arquivo docker.
svenhornberg
3

Tentei de várias maneiras diferentes, mas gostei da simplicidade disso: https://github.com/ufoscout/docker-compose-wait

A idéia de que você pode usar ENV vars no arquivo janela de encaixe compor para apresentar uma lista de hosts de serviços (com portas), que deve ser "esperado" como este: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017.

Então, digamos que você tenha o seguinte arquivo docker-compose.yml (copie / cole do repo README ):

version: "3"

services:

  mongo:
    image: mongo:3.4
    hostname: mongo
    ports:
      - "27017:27017"

  postgres:
    image: "postgres:9.4"
    hostname: postgres
    ports:
      - "5432:5432"

  mysql:
    image: "mysql:5.7"
    hostname: mysql
    ports:
      - "3306:3306"

  mySuperApp:
    image: "mySuperApp:latest"
    hostname: mySuperApp
    environment:
      WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017

Em seguida, para que os serviços esperem, você deve adicionar as duas linhas a seguir aos seus Dockerfiles (no Dockerfile dos serviços que devem aguardar o início de outros serviços):

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

O exemplo completo desse exemplo Dockerfile (novamente no repositório README do projeto ):

FROM alpine

## Add your application to the docker image
ADD MySuperApp.sh /MySuperApp.sh

## Add the wait script to the image
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

## Launch the wait tool and then your application
CMD /wait && /MySuperApp.sh

Para outros detalhes sobre o possível uso, consulte o arquivo LEIA-ME

Evereq
fonte
Eu estava procurando uma resposta semelhante. Normalmente, trabalhei com hub.docker.com/r/dadarek/wait-for-dependencies, pois ele usa o netcat por baixo. O que você forneceu é baseado em Rust. Não posso comentar sobre a sua qualidade, mas para mim nenhuma camada adicional é um profissional definitivo.
Filip Malczak
1
Eu recomendo fortemente isso por razões de segurança. Você está executando um executável arbitrário a partir de um hiperlink. Uma solução melhor seria fazer a mesma coisa com um script estático que é copiado para a imagem com COPY
Paul K
@PaulK, é claro, é compreensível que executar qualquer coisa do hiperlink não seja seguro, mas é apenas uma demonstração acima de como fazer a https://github.com/ufoscout/docker-compose-waitbiblioteca funcionar :) A maneira como você usa essa biblioteca não altera a resposta de que você pode utilizar alguma lib. A segurança é um tópico complexo e, se formos longe, devemos verificar o que essa biblioteca está fazendo lá dentro, mesmo que a copiem :) É melhor ser mais específico em seu comentário, como: "Eu recomendo fortemente o uso dessa biblioteca do hiperlink ". Espero que você concorde, obrigado por uma dica!
Evereq 7/04
2

com base nesta postagem do blog https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

Eu configurei o meu docker-compose.ymlcomo mostrado abaixo:

version: "3.1"

services:
  rabbitmq:
    image: rabbitmq:3.7.2-management-alpine
    restart: always
    environment:
      RABBITMQ_HIPE_COMPILE: 1
      RABBITMQ_MANAGEMENT: 1
      RABBITMQ_VM_MEMORY_HIGH_WATERMARK: 0.2
      RABBITMQ_DEFAULT_USER: "rabbitmq"
      RABBITMQ_DEFAULT_PASS: "rabbitmq"
    ports:
      - "15672:15672"
      - "5672:5672"
    volumes:
      - data:/var/lib/rabbitmq:rw

  start_dependencies:
    image: alpine:latest
    links:
      - rabbitmq
    command: >
      /bin/sh -c "
        echo Waiting for rabbitmq service start...;
        while ! nc -z rabbitmq 5672;
        do
          sleep 1;
        done;
        echo Connected!;
      "

volumes:
  data: {}

Então eu faço para executar =>:

docker-compose up start_dependencies

rabbitmqO serviço iniciará no modo daemon, start_dependenciesfinalizará o trabalho.

Igor Komar
fonte
lol, fazendo consulta via "curl", "-f", "http://localhost:15672"para o qual você precisa instalar o managementplugin e usando o healthcheck que já foi descontinuado - sua melhor resposta. Exemplo simples de trabalho com cheque através do ncseu - voto negativo. ha, ok ...
Igor Komar 12/01
a resposta não usa um recurso nativo de janela de encaixe, é irrelevante se você usar curl, nc ou outras ferramentas. enquanto! nc é o mesmo que já postado em outras respostas.
svenhornberg
1
@IgorKomar, obrigado cara, você salvou meu dia! : 3 Eu usei quase o mesmo mecânico para verificar se o servidor mysql está pronto antes da aplicação real ser iniciada. ;) Estou passando um comando semelhante aodocker-compose run --name app-test --rm "app" bash -l -c 'echo Waiting for mysql service start... && while ! nc -z db-server 3306; do sleep 1; done && echo Connected! && /bin/bash /script/ci_tests.sh'
TooroSan 11/11
1

Na versão 3 de um arquivo Docker Compose, você pode usar RESTART .

Por exemplo:

docker-compose.yml

worker:
    build: myapp/.
    volumes:
    - myapp/.:/usr/src/app:ro
    restart: on-failure
    depends_on:
    - rabbitmq
rabbitmq:
    image: rabbitmq:3-management

Observe que eu usei o Depend_on em vez de links, pois o último está obsoleto na versão 3.

Mesmo que funcione, pode não ser a solução ideal, pois você reinicia o contêiner do docker a cada falha.

Ter um olhar para RESTART_POLICY também. permite ajustar a política de reinicialização.

Ao usar o Redigir na produção , é realmente recomendável usar a política de reinicialização:

Especificando uma política de reinicialização como restart: sempre para evitar o tempo de inatividade

Mathieu Gemard
fonte
0

Uma das soluções alternativas é usar uma solução de orquestração de contêiner como o Kubernetes. O Kubernetes tem suporte para contêineres init que são concluídos antes que outros contêineres possam iniciar. Você pode encontrar um exemplo aqui com o contêiner do SQL Server 2017 Linux em que o contêiner da API usa o contêiner init para inicializar um banco de dados

https://www.handsonarchitect.com/2018/08/understand-kubernetes-object-init.html

Nilesh Gule
fonte
0

Aqui está o exemplo em que o maincontêiner aguarda workerquando começa a responder por pings:

version: '3'
services:
  main:
    image: bash
    depends_on:
     - worker
    command: bash -c "sleep 2 && until ping -qc1 worker; do sleep 1; done &>/dev/null"
    networks:
      intra:
        ipv4_address: 172.10.0.254
  worker:
    image: bash
    hostname: test01
    command: bash -c "ip route && sleep 10"
    networks:
      intra:
        ipv4_address: 172.10.0.11
networks:
  intra:
    driver: bridge
    ipam:
      config:
      - subnet: 172.10.0.0/24

No entanto, a maneira correta é usar healthcheck(> = 2.1).

kenorb
fonte
0

Não recomendado para implantações sérias, mas aqui está essencialmente um comando "wait x seconds".

Com a docker-composeversão, 3.4uma start_periodinstrução foi adicionada ahealthcheck . Isso significa que podemos fazer o seguinte:

docker-compose.yml:

version: "3.4"
services:
  # your server docker container
  zmq_server:
    build:
      context: ./server_router_router
      dockerfile: Dockerfile

  # container that has to wait
  zmq_client:
    build:
      context: ./client_dealer/
      dockerfile: Dockerfile
    depends_on:
      - zmq_server
    healthcheck:
      test: "sh status.sh"
      start_period: 5s

status.sh:

#!/bin/sh

exit 0

O que acontece aqui é que o healthchecké invocado após 5 segundos. Isso chama o status.shscript, que sempre retorna "Sem problemas". Acabamos de fazer o zmq_clientrecipiente esperar 5 segundos antes de começar!

Nota: É importante que você tenha version: "3.4". Se .4não houver, o docker-compose reclama.

NumesSanguis
fonte
1
Como uma solução ingênua do tipo "aguarde 5s", essa é bastante engenhosa. Gostaria de votar, mas não vou, porque isso realmente não funciona com configurações semelhantes a prod e tenho medo de que alguém olhe o número de votos em vez de ler atentamente. Ainda assim, eu queria dizer "cara, isso é esperto";)
Filip Malczak
PS. Para soluções mais complicadas, ver a resposta do Evereq
Filip Malczak
Não é isso que start_periodfaz. Essa configuração significa que há um período de cortesia em que as verificações de integridade com falha não contam como novas tentativas. Se for bem-sucedido cedo, é considerado saudável. Após o período de início, uma falha será contada como uma nova tentativa. Veja docs.docker.com/engine/reference/builder/#healthcheck
Capi Etheriel
-4

Eu só tenho 2 arquivos de composição e inicio um primeiro e o segundo mais tarde. Meu script é assim:

#!/bin/bash
#before i build my docker files
#when done i start my build docker-compose
docker-compose -f docker-compose.build.yaml up
#now i start other docker-compose which needs the image of the first
docker-compose -f docker-compose.prod.yml up
Benjamin Grašič
fonte
Isso não é considerado uma boa prática. Você não pode entregar a solução que consiste em vários conatiners a partir de um arquivo de composição.
juergi