Utilizzo di docker-compose con CI: come gestire i codici di uscita e i container collegati daemonizzati?


87

In questo momento i nostri agenti Jenkins generano un docker-compose.yml per ciascuno dei nostri progetti Rails e quindi eseguono docker-compose up. Il docker-compose.yml ha un contenitore "web" principale che contiene rbenv e tutte le nostre altre dipendenze di Rails. È collegato a un contenitore DB che contiene il DB Postgres di prova.

Il problema nasce quando dobbiamo eseguire effettivamente i test e generare codici di uscita. Il nostro server CI verrà distribuito solo se lo script di test restituisce exit 0, ma docker-compose restituisce sempre 0, anche se uno dei comandi del contenitore non riesce.

L'altro problema è che il contenitore DB viene eseguito indefinitamente, anche dopo che il contenitore Web ha terminato di eseguire i test, quindi docker-compose upnon ritorna mai.

C'è un modo in cui possiamo usare docker-compose per questo processo? Dovremmo essere in grado di eseguire i contenitori, ma uscire dopo che il contenitore web è completo e restituire il suo codice di uscita. In questo momento siamo bloccati manualmente utilizzando docker per far girare il contenitore DB ed eseguire il contenitore web con l'opzione --link.

Risposte:


76

Dalla versione 1.12.0, puoi usare l' --exit-code-fromopzione.

Dalla documentazione :

--exit-code-from SERVICE

Restituisce il codice di uscita del contenitore di servizi selezionato. Implica --abort-on-container-exit.


1
Questo dovrebbe essere il modo giusto per farlo se stai usando docker-compose1.12.0 e versioni successive. Forse è anche il tuo caso. Un esempio potrebbe essere: docker-compose up --exit-code-from test-unit. Nota che non ha funzionato per me fino a quando non ho aggiunto un set -eall'inizio del mio script.
Adrian Antunez

--exit-code-fromnon funziona -dperò. Verranno using --exit-code-from implies --abort-on-container-exit--abort-on-container-exit and -d cannot be combined.
lanciati


2
la documentazione è atroce. con quali flag è compatibile? è solo un servizio o puoi passarlo a diversi?
worc

42

docker-compose runè il modo semplice per ottenere gli stati di uscita desiderati. Per esempio:

$ cat docker-compose.yml 
roit:
    image: busybox
    command: 'true'
naw:
    image: busybox
    command: 'false'
$ docker-compose run --rm roit; echo $?
Removing test_roit_run_1...
0
$ docker-compose run --rm naw; echo $?
Removing test_naw_run_1...
1

In alternativa, hai la possibilità di ispezionare i contenitori morti. Puoi usare il -fflag per ottenere solo lo stato di uscita.

$ docker-compose up
Creating test_naw_1...
Creating test_roit_1...
Attaching to test_roit_1
test_roit_1 exited with code 0
Gracefully stopping... (press Ctrl+C again to force)
$ docker-compose ps -q | xargs docker inspect -f '{{ .Name }} exited with status {{ .State.ExitCode }}'
/test_naw_1 exited with status 1
/test_roit_1 exited with status 0

Per quanto riguarda il contenitore db che non ritorna mai, se usi docker-compose upallora dovrai sigkill quel contenitore; probabilmente non è quello che vuoi. Invece, puoi usare docker-compose up -dper eseguire i tuoi contenitori daemonizzati e uccidere manualmente i contenitori quando il tuo test è completo. docker-compose run dovrebbe eseguire i contenitori collegati per te, ma ho sentito parlare di SO su un bug che impedisce che funzioni come previsto in questo momento.


Il problema con docker run è che non fornisce alcun output quando viene eseguito con -T, e vogliamo l'output in modo da poter ispezionare le build fallite.
Logan Serman

1
@LoganSerman puoi controllare l'output condocker-compose logs
kojiro

Esiste un modo per reindirizzare costantemente tali log a STDOUT durante l'esecuzione in modo da poterlo vedere mentre è in corso la compilazione dell'elemento della configurazione?
Logan Serman

Immagino di non capire perché stai correndo con-T
kojiro

Alcuni dei comandi che eseguiamo all'interno del contenitore per eseguire i test hanno il potenziale per chiedere input, vogliamo eseguire con -T per evitarlo. Rbenv ad esempio chiede se vuoi reinstallare una versione di Ruby se esiste già.
Logan Serman

23

Basandosi sulla risposta di kojiro:

docker-compose ps -q | xargs docker inspect -f '{{ .State.ExitCode }}' | grep -v '^0' | wc -l | tr -d ' '

  1. ottenere ID contenitore
  2. ottenere il codice di uscita delle ultime esecuzioni per ogni ID contenitore
  3. solo codici di stato che non iniziano con "0"
  4. contare il numero di codici di stato diversi da 0
  5. ritaglia lo spazio bianco

Restituisce quanti codici di uscita diversi da 0 sono stati restituiti. Sarebbe 0 se tutto uscisse con il codice 0.


Puoi anche utilizzare l'output non silenzioso da docker-compose ps, ad esempio: docker-compose ps | grep -c "Exit 1"ti darà il conteggio da cui "Uscita 1" viene trovata nel display da docker-compose ps(che fornisce una tabella riassuntiva dei risultati piuttosto stampata). I codici di uscita sono elencati nella colonna "Stato".
eharik

Questo è davvero fantastico. Nel mio caso il fallimento della suite di test in esecuzione nei contenitori non fa uscire i contenitori con un codice di 1. Non posso aggregare se qualcuno è uscito con un codice di 1 poiché nessuno di loro lo fa .... Qualche idea su come gestirlo Astuccio?
walkerrandophsmith

9

Se sei disposto a utilizzare docker-compose runper avviare manualmente i tuoi test, l'aggiunta del --rmflag, stranamente, fa sì che Compose rifletta accuratamente lo stato di uscita del tuo comando.

Ecco il mio esempio:

$ docker-compose -v
docker-compose version 1.7.0, build 0d7bf73

$ (docker-compose run bash false) || echo 'Test failed!'  # False negative.

$ (docker-compose run --rm bash false) || echo 'Test failed!'  # True positive.
Test failed!

$ (docker-compose run --rm bash true) || echo 'Test failed!'  # True negative.

1
O (docker-compose run --rm ...) || exit $?per la risoluzione in caso di errore. Utile negli script bash.
Amirreza Nasiri

8

Utilizzare docker waitper ottenere il codice di uscita:

$ docker-compose -p foo up -d
$ ret=$(docker wait foo_bar_1)

fooè il "nome del progetto". Nell'esempio sopra, l'ho specificato esplicitamente, ma se non lo fornisci, è il nome della directory. barè il nome che dai al sistema in prova nel tuo docker-compose.yml.

Nota che docker logs -ffa anche la cosa giusta, uscire quando il contenitore si ferma. Quindi puoi mettere

$ docker logs -f foo_bar_1

tra il docker-compose upe il docker waitcosì puoi vedere i tuoi test eseguiti.


8

--exit-code-from SERVICEe --abort-on-container-exitnon funzionano in scenari in cui è necessario eseguire tutti i contenitori fino al completamento, ma fallire se uno di essi è stato chiuso in anticipo. Un esempio potrebbe essere se si eseguono 2 tute di prova contemporaneamente in contenitori diversi.

Con il suggerimento di @ spesohil, puoi inserire docker-composeuno script che fallirà se qualche contenitore lo fa.

#!/bin/bash
set -e

# Wrap docker-compose and return a non-zero exit code if any containers failed.

docker-compose "$@"

exit $(docker-compose -f docker-compose.ci.build.yml ps -q | tr -d '[:space:]' |
  xargs docker inspect -f '{{ .State.ExitCode }}' | grep -v 0 | wc -l | tr -d '[:space:]')

Quindi sul tuo server CI docker-compose uppassa semplicemente a ./docker-compose.sh up.


1
questo script non raggiunge mai la sezione di uscita poiché altri contenitori (come database, app Web) vengono eseguiti continuamente. funzionante in modalità scollegata esce non appena i container sono
attivi

Esatto, funziona solo se desideri eseguire tutti i contenitori fino al completamento. Probabilmente non particolarmente comune, ma mi è stato utile al momento della scrittura e ho pensato di condividerlo.
Matt Cole

Ho comunque votato positivamente la tua risposta perché mi ha portato quasi tutto lì! L'aggiunta di Docker wait su ogni contenitore di test in modalità scollegata ha funzionato. Grazie per la condivisione :)
Baldy

2

docker-rails consente di specificare quale codice di errore del contenitore viene restituito al processo principale, in modo che il server CI possa determinare il risultato. È un'ottima soluzione per CI e sviluppo per rotaie con docker.

Per esempio

exit_code: web

nel tuo docker-rails.ymlrestituirà il webcodice di uscita dei contenitori come risultato del comando docker-rails ci test. docker-rails.ymlè solo un meta wrapper attorno allo standard docker-compose.ymlche ti dà il potenziale per ereditare / riutilizzare la stessa configurazione di base per ambienti diversi, ad esempio sviluppo vs test vs parallel_tests.


2

Nel caso in cui potresti eseguire più servizi di composizione mobile con lo stesso nome su un motore di finestra mobile e non conosci il nome esatto:

docker-compose up -d
(exit "${$(docker-compose logs -f test-chrome)##* }")

echo %? - restituisce il codice di uscita dal servizio test-chrome

Benefici:

  • aspetta che esca il servizio
  • utilizza il nome del servizio, non il nome del contenitore

2

Puoi vedere lo stato di uscita con:

echo $(docker-compose ps | grep "servicename" | awk '{print $4}')

Grazie per aver iniziato. Ecco la mia versione di questo (che funziona meglio per me b / c Penso che il formato di output del comando sia cambiato da quando è stata scritta questa risposta) -docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
DTrejo
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.