Docker Componi attendere il contenitore X prima di avviare Y


326

Sto usando rabbitmq e un semplice esempio di python da qui insieme a docker-compose. Il mio problema è che devo aspettare che rabbitmq si avvii completamente. Da quello che ho cercato finora, non so come aspettare con il contenitore x (nel mio caso) fino all'avvio di y (rabbitmq).

Ho trovato questo post in cui controlla se l'altro host è online. Ho anche trovato questo comando docker :

aspettare

Utilizzo: docker wait CONTAINER [CONTAINER ...]

Blocca fino all'arresto di un contenitore, quindi stampa il suo codice di uscita.

Aspettare che un container si fermi non è forse quello che sto cercando, ma se lo è, è possibile usare quel comando all'interno di docker-compose.yml? La mia soluzione finora è aspettare alcuni secondi e controllare la porta, ma è questo il modo per raggiungere questo obiettivo? Se non aspetto, ricevo un errore.

finestra mobile-compose.yml

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

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

campione ciao pitone (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 per lavoratore:

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

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

Aggiornamento novembre 2015 :

Uno script di shell o in attesa all'interno del programma è forse una possibile soluzione. Ma dopo aver visto questo problema sto cercando un comando o una funzione di docker / docker-compose.

Citano una soluzione per l'implementazione di un controllo sanitario, che potrebbe essere l'opzione migliore. Una connessione tcp aperta non significa che il servizio è pronto o potrebbe rimanere pronto. Inoltre, devo modificare il mio punto di accesso nel mio file docker.

Quindi spero in una risposta con i comandi di bordo di composizione docker, che si spera sia il caso se finissero questo problema.

Aggiornamento marzo 2016

Esiste una proposta per fornire un modo integrato per determinare se un contenitore è "vivo". Quindi la docker-compose può forse farne uso in un prossimo futuro.

Aggiornamento giugno 2016

Sembra che il controllo della salute sarà integrato nella finestra mobile nella versione 1.12.0

Aggiornamento gennaio 2017

Ho trovato una soluzione di composizione docker, consultare: Composizione Docker attendere il contenitore X prima di avviare Y


2
L'uso di healthchecks in è stato deprecato in docker-compose 2.3 per incoraggiare i sistemi distribuiti a tollerare i guasti. Vedi: docs.docker.com/compose/startup-order
Kmaid l'

Risposte:


284

Finalmente ho trovato una soluzione con un metodo comporre docker. Dal formato di file 2.1 composto da docker è possibile definire controlli di integrità .

L'ho fatto in un progetto di esempio, è necessario installare almeno la finestra mobile 1.12.0+. Avevo anche bisogno di estendere il Dockerfile di gestione di rabbitmq , perché il ricciolo non è installato sull'immagine ufficiale.

Ora collaudo se la pagina di gestione del conigliomq-container è disponibile. Se l'arricciatura termina con exitcode 0, l'app contenitore (python pika) verrà avviata e pubblicherà un messaggio in coda. Ora funziona (output).

docker-compose (versione 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

produzione:

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

Dockerfile (rabbitmq + curl):

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

La versione 3 non supporta più il modulo di condizione di depend_on . Quindi mi sono trasferito da depend_on per riavviare in caso di errore. Ora il mio contenitore di app si riavvierà 2-3 volte fino a quando non funziona, ma è ancora una funzionalità di composizione docker senza sovrascrivere il punto di accesso.

docker-compose (versione 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

6
@svenhornberg pingutilizza ICMP quindi non supporta le porte TCP. Forse ncper testare una porta TCP. Probabilmente è meglio usare psql -h localhost -p 5432e interrogare qualcosa.
Matt,

36
"dipende da" è stato rimosso nella versione 3 docs.docker.com/compose/compose-file/#dependson
nha,

48
@nha Sembra che la conditionforma di depends_onsia stata rimossa, ma depends_onè ancora presente in v3
akivajgordon

14
Come può healthchecks ancora essere utilizzato per ordine di controllo all'avvio se depends_oncon conditionè stato rimosso?
Franz,

43
Difficile crederci, un tale dolore ancora
npr

71

Nativamente ciò non è ancora possibile. Vedi anche questa richiesta di funzionalità .

Finora è necessario farlo nei contenitori CMDper attendere fino a quando tutti i servizi richiesti non sono presenti.

Nelle Dockerfiles CMDsi può consultare il proprio script di avvio che avvolge l'avvio del servizio di contenitore. Prima di avviarlo, attendi uno dipendente come:

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

Probabilmente devi installare netcat anche nel tuo Dockerfile. Non so cosa sia preinstallato sull'immagine Python.

Esistono alcuni strumenti che forniscono una logica di attesa facile da usare, per semplici controlli della porta tcp:

Per attese più complesse:


Potresti spiegare cosa intendi per CMD? Questo significa che il mio programma deve farlo, come ho fatto con un controllo delle porte? O vuoi dire un CMD specifico da eg linux per questo?
Svenhornberg,

grazie per la spiegazione, ho votato a favore della tua risposta, ma penso che la prossima richiesta di funzionalità sarebbe la risposta giusta alla mia domanda, quindi la lascio senza risposta finora.
Svenhornberg,

44

Utilizzando restart: unless-stoppedo restart: alwayspuò risolvere questo problema.

Se l'operatore si containerarresta quando rabbitMQ non è pronto, verrà riavviato fino a quando non lo è.


3
Mi piace questa soluzione per questo caso, ma non funziona per i contenitori che non escono quando uno dei sottoprocessi che esegue fallisce. Ad esempio, un contenitore Tomcat continuerebbe a funzionare anche se un servlet Java in esecuzione non riuscisse a connettersi a un server di database. Concesso, i contenitori Docker rendono i contenitori servlet come Tomcat per lo più inutili.
Derek Mahar,

@DerekMahar, se si dispone di un'applicazione Web basata su Java che serve solo chiamate REST, cosa si utilizza al posto di Jetty / Tomcat?
JoeG,

2
@JoeG, intendevo Tomcat il contenitore servlet che può ospitare molte applicazioni, non Tomcat incorporato. Docker rende il primo per lo più inutile, rendendo il secondo più popolare per i microservizi, ad esempio.
Derek Mahar,

35

Molto recentemente hanno aggiunto la depends_onfunzione .

Modificare:

A partire da comporre la versione 2.1+ è possibile utilizzare depends_oninsieme healthcheckper ottenere questo:

Dai documenti :

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"

Prima della versione 2.1

È ancora possibile utilizzare depends_on, ma influisce solo sull'ordine in cui vengono avviati i servizi, non se sono pronti prima dell'avvio del servizio dipendente.

Sembra richiedere almeno la versione 1.6.0.

L'uso sarebbe simile a questo:

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

Dai documenti:

Esprimi la dipendenza tra i servizi, che ha due effetti:

  • docker-compose up avvierà i servizi in ordine di dipendenza. Nel seguente esempio, db e redis verranno avviati prima del web.
  • Il servizio di composizione docker includerà automaticamente le dipendenze di SERVICE. Nel seguente esempio, la finestra mobile composta da Web creerà e avvierà anche db e redis.

Nota: come ho capito, anche se questo imposta l'ordine in cui vengono caricati i contenitori. Non garantisce che il servizio all'interno del contenitore sia stato effettivamente caricato.

Ad esempio, il contenitore di Postgres potrebbe essere attivo. Ma il servizio Postgres stesso potrebbe ancora essere inizializzato all'interno del contenitore.


10
dnephin ha scritto: depend_on sta solo ordinando. Per ritardare effettivamente l'avvio di un altro contenitore, dovrebbe esserci un modo per rilevare quando un processo ha terminato l'inizializzazione.
Svenhornberg,

15
"La versione 3 non supporta più la forma condizione di depends_on." docs.docker.com/compose/compose-file/#dependson
akauppi

depends_onnon aspetta che il contenitore sia nello readystato (qualunque cosa ciò possa significare nel tuo caso). Aspetta solo fino a quando il contenitore è nello stato 'in esecuzione'.
htyagi,

19

puoi anche semplicemente aggiungerlo all'opzione di comando es.

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

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

per aspettare su una porta puoi anche usare qualcosa del genere

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

per aumentare il tempo di attesa puoi hackerare un po 'di più:

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"

13

restart: on-failure ha fatto il trucco per me ... vedi sotto

---
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"

12

Per iniziare a ordinare il contenitore utilizzare

depends_on:

Per l'attesa del precedente contenitore avviare utilizzare lo script

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

Questo articolo ti aiuterà https://docs.docker.com/compose/startup-order/


5
@svenhornberg nel commento, si collega, non ci sono spiegazioni sulla funzione wait-for-it.sh.
Uscito il

7

È inoltre possibile risolvere questo problema impostando un endpoint che attende che il servizio sia attivo utilizzando netcat (utilizzando lo script docker-wait ). Mi piace questo approccio poiché hai ancora una commandsezione pulita nella tua docker-compose.ymle non hai bisogno di aggiungere un codice specifico docker alla tua applicazione:

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

Quindi il tuo 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

Questo è oggigiorno documentato nella documentazione ufficiale della finestra mobile .

PS: è necessario installare netcatnell'istanza della finestra mobile se questa non è disponibile. Per fare ciò aggiungi questo al tuo Dockerfile:

RUN apt-get update && apt-get install netcat-openbsd -y 

4

Esiste un'utilità pronta per l'uso chiamata " docker-wait " che può essere utilizzata per l'attesa.


1
Grazie, ma è solo uno script di shell, quindi è come rispondere a h3nrik o aspettare in Python. Non è una caratteristica del docker-compose stesso. Che tu possa dare un'occhiata a github.com/docker/compose/issues/374, hanno in programma di attuare un controllo sanitario che sarebbe il modo migliore. Una connessione tcp aperta non significa che il servizio è pronto o potrebbe rimanere pronto. Inoltre, devo modificare il mio punto di accesso nel mio file docker.
svenhornberg,

3

Ho provato molti modi diversi, ma mi è piaciuta la semplicità: https://github.com/ufoscout/docker-compose-wait

L'idea che è possibile utilizzare ENV vars nel file finestra mobile composizione di presentare una lista di servizi host (con porte) che dovrebbe essere "atteso" in questo modo: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017.

Supponiamo quindi di avere il seguente file docker-compose.yml (copia / passato dal README di repository ):

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

Successivamente, affinché i servizi siano in attesa, è necessario aggiungere le seguenti due righe ai file Docker (nel file Docker dei servizi che dovrebbero attendere l'avvio di altri servizi):

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

L'esempio completo di tale Dockerfile di esempio (sempre dal repository README del progetto ):

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

Per altri dettagli sul possibile utilizzo, consultare il file README


Stavo cercando una risposta simile. Di solito ho lavorato con hub.docker.com/r/dadarek/wait-for-dependencies in quanto utilizza netcat sotto. Quello che hai fornito è basato su Rust. Non posso commentare la qualità del tuo, ma per me nessun livello aggiuntivo è un vero vantaggio.
Filip Malczak,

1
Consiglio vivamente di non farlo per motivi di sicurezza. Stai eseguendo un eseguibile arbitrario da un collegamento ipertestuale. Una soluzione migliore sarebbe quella di fare la stessa cosa con uno script statico che è stato copiato nell'immagine con COPY
Paul K,

@PaulK, ovviamente, è comprensibile che eseguire qualsiasi cosa dal collegamento ipertestuale non sia sicuro, ma è solo una demo sopra come far https://github.com/ufoscout/docker-compose-waitfunzionare la libreria :) Il modo in cui usi quella libreria non cambia una risposta che puoi usare un po 'di lib. La sicurezza è un argomento complesso e se andiamo lontano, dovremmo comunque verificare cosa sta facendo quella libreria all'interno, anche se lo COPIAMO :) Quindi meglio essere più specifici nel tuo commento come: "Consiglio vivamente di non utilizzare quella libreria dal collegamento ipertestuale ". Spero che tu sia d'accordo, grazie per un suggerimento!
Evereq

2

basandosi su questo post di blog https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

Ho configurato il mio docker-compose.ymlcome mostrato di seguito:

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: {}

Quindi faccio per run =>:

docker-compose up start_dependencies

rabbitmqil servizio verrà avviato in modalità demone, start_dependenciesterminerà il lavoro.


lol, eseguendo una query tramite la "curl", "-f", "http://localhost:15672"quale è necessario installare un managementplug-in e utilizzare healthcheck che è già obsoleto: la sua migliore risposta. Semplice esempio di lavoro con controllo tramite il ncsuo - downvote. ah, ok ...
Igor Komar il

la risposta non usa una funzione docker nativa, è irrilevante se usi curl, nc o altri strumenti. mentre! nc è lo stesso di quello già pubblicato in altre risposte.
Svenhornberg,


1
@IgorKomar, grazie amico, mi hai salvato la giornata! : 3 Ho usato quasi lo stesso meccanico per verificare che il server mysql sia pronto prima di avviare l'applicazione effettiva. ;) Sto passando un comando simile aldocker-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

1

Nella versione 3 di un file Compose Docker, è possibile utilizzare RESTART .

Per esempio:

finestra mobile-compose.yml

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

Si noti che ho usato depend_on anziché i collegamenti poiché quest'ultimo è obsoleto nella versione 3.

Anche se funziona, potrebbe non essere la soluzione ideale poiché riavvii il contenitore della finestra mobile ad ogni errore.

Dai un'occhiata anche a RESTART_POLICY . ti consente di ottimizzare la politica di riavvio.

Quando si utilizza Compose in produzione , in realtà è consigliabile utilizzare il criterio di riavvio:

Specificare una politica di riavvio come il riavvio: sempre per evitare tempi di inattività


0

Una delle soluzioni alternative è quella di utilizzare una soluzione di orchestrazione dei container come Kubernetes. Kubernetes supporta i contenitori init che vengono eseguiti fino al completamento prima che altri container possano avviarsi. È possibile trovare un esempio qui con il contenitore Linux di SQL Server 2017 in cui il contenitore API utilizza il contenitore init per inizializzare un database

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


0

Ecco l'esempio in cui il maincontenitore attende workerquando inizia a rispondere ai ping:

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

Tuttavia, il modo corretto è usare healthcheck(> = 2.1).


0

Non consigliato per distribuzioni serie, ma qui è essenzialmente un comando "wait x seconds".

Con docker-composela versione 3.4di start_periodistruzioni è stato aggiuntohealthcheck . Ciò significa che possiamo fare quanto segue:

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

Quello che succede qui è che healthcheckviene invocato dopo 5 secondi. Questo chiama lo status.shscript, che restituisce sempre "Nessun problema". Abbiamo appena fatto zmq_clientattendere il contenitore 5 secondi prima di iniziare!

Nota: è importante che tu abbia version: "3.4". Se .4non è presente, docker-compose si lamenta.


1
Come ingenua soluzione "wait 5s", questa è abbastanza ingegnosa. Vorrei votare, ma non lo farò perché questo non funziona davvero con configurazioni simili a prode e temo che qualcuno guarderebbe il numero di voti invece di leggere attentamente. Tuttavia, volevo dire "amico, è intelligente";)
Filip Malczak,

PS. Per soluzioni più complicate, vedi la risposta di Evereq
Filip Malczak,

Questo è non è quello che start_periodfa. Tale configurazione indica un periodo di tolleranza in cui i controlli di integrità non riusciti non vengono conteggiati come tentativi. Se riesce presto, è considerato sano. Dopo il periodo di inizio, un errore verrà conteggiato come nuovo tentativo. Vedi docs.docker.com/engine/reference/builder/#healthcheck
Capi Etheriel,

-4

Ho solo 2 file di composizione e ne inizio uno prima e un secondo dopo. La mia sceneggiatura è simile a questa:

#!/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

Questa non è considerata una buona pratica. Quindi non è possibile fornire la soluzione composta da più conatiner da un file di composizione.
juergi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.