Verwenden von Docker-Compose mit CI - Wie gehe ich mit Exit-Codes und daemonisierten verknüpften Containern um?

87

Im Moment generieren unsere Jenkins-Agenten für jedes unserer Rails-Projekte eine docker-compose.yml und führen dann docker-compose up aus. Die Datei docker-compose.yml verfügt über einen Hauptcontainer "Web", in dem sich rbenv und alle anderen Rails-Abhängigkeiten befinden. Es ist mit einem DB-Container verknüpft, der die Test-Postgres-DB enthält.

Das Problem tritt auf, wenn wir die Tests tatsächlich ausführen und Exit-Codes generieren müssen. Unser CI-Server wird nur bereitgestellt, wenn das Testskript Exit 0 zurückgibt, Docker-Compose jedoch immer 0 zurückgibt, selbst wenn einer der Containerbefehle fehlschlägt.

Das andere Problem ist, dass der DB-Container unbegrenzt ausgeführt wird, auch nachdem der Webcontainer die Tests ausgeführt hat, und daher docker-compose upniemals zurückkehrt.

Gibt es eine Möglichkeit, Docker-Compose für diesen Prozess zu verwenden? Wir müssten in der Lage sein, die Container auszuführen, aber nach Abschluss des Webcontainers beenden und den Exit-Code zurückgeben. Im Moment stecken wir manuell mit Docker fest, um den DB-Container hochzufahren und den Webcontainer mit der Option --link auszuführen.

Logan Serman
quelle

Antworten:

76

Seit der Version 1.12.0können Sie die --exit-code-fromOption verwenden.

Aus der Dokumentation :

--exit-code-from SERVICE

Geben Sie den Exit-Code des ausgewählten Service-Containers zurück. Impliziert --abort-on-container-exit.

Panjan
quelle
1
docker-composeDies sollte der richtige Weg sein, wenn Sie 1.12.0 und höher verwenden. Vielleicht ist es auch dein Fall. Ein Beispiel könnte sein : docker-compose up --exit-code-from test-unit. Beachten Sie, dass es bei mir nicht funktioniert hat, bis ich set -eam Anfang meines Skripts ein hinzugefügt habe.
Adrian Antunez
--exit-code-fromfunktioniert aber nicht mit -d. Es wird diese Fehler werfen: using --exit-code-from implies --abort-on-container-exitund --abort-on-container-exit and -d cannot be combined.
ericat
1
Die Dokumentation ist grausam. Mit welchen Flags ist das kompatibel? ist es nur ein Dienst oder können Sie mehrere übergeben?
Worc
41

docker-compose runist der einfache Weg, um die gewünschten Exit-Status zu erhalten. Beispielsweise:

$ 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

Alternativ haben Sie die Möglichkeit, die toten Container zu inspizieren . Sie können das -fFlag verwenden, um nur den Exit-Status abzurufen.

$ 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

Was den docker-compose upDatenbankcontainer betrifft, der niemals zurückgegeben wird, müssen Sie diesen Container sigkill , wenn Sie ihn verwenden . das ist wahrscheinlich nicht was du willst. Stattdessen können Sie docker-compose up -dIhre Container dämonisiert ausführen und die Container nach Abschluss Ihres Tests manuell beenden. docker-compose run sollte verknüpfte Container für Sie ausführen, aber ich habe auf SO ein Geschwätz über einen Fehler gehört, der verhindert, dass dies jetzt wie beabsichtigt funktioniert.

Kojiro
quelle
Das Problem bei der Docker-Ausführung besteht darin, dass beim Ausführen mit -T keine Ausgabe ausgegeben wird. Wir möchten die Ausgabe, damit wir fehlgeschlagene Builds überprüfen können.
Logan Serman
1
@LoganSerman können Sie die Ausgabe mit docker-compose logs
überprüfen
Gibt es eine Möglichkeit, diese Protokolle während des Laufs ständig an STDOUT weiterzuleiten, damit wir sie sehen können, während der CI-Build ausgeführt wird?
Logan Serman
Ich glaube, ich verstehe nicht, warum du mit-T
kojiro
Einige der Befehle, die wir im Container ausführen, um Tests auszuführen, können nach Eingaben fragen. Wir möchten sie mit -T ausführen, um dies zu vermeiden. Rbenv fragt beispielsweise, ob Sie eine Ruby-Version neu installieren möchten, falls diese bereits vorhanden ist.
Logan Serman
23

Aufbauend auf Kojiros Antwort:

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

  1. Container-IDs abrufen
  2. Abrufcode für letzte Läufe für jede Container-ID abrufen
  3. nur Statuscodes, die nicht mit '0' beginnen
  4. Anzahl der Nicht-0-Statuscodes zählen
  5. Leerraum ausschneiden

Gibt zurück, wie viele Exit-Codes ungleich 0 zurückgegeben wurden. Wäre 0, wenn alles mit Code 0 beendet würde.

verbraucht
quelle
Sie können auch die nicht leise Ausgabe von verwenden docker-compose ps, zum Beispiel: docker-compose ps | grep -c "Exit 1"gibt Ihnen die Anzahl an, bei der "Exit 1" in der Anzeige von übereinstimmt docker-compose ps(was eine hübsch gedruckte Übersichtstabelle der Ergebnisse liefert). Die Exit-Codes sind in der Spalte "Status" aufgeführt.
Eharik
Das ist wirklich großartig. In meinem Fall führt ein Fehler in der Testsuite, die in Containern ausgeführt wird, nicht dazu, dass die Container mit einem Code von 1 beendet werden. Ich kann nicht aggregieren, wenn sie mit einem Code von 1 beendet werden, da keiner von ihnen dies tut Fall?
Walkerrandophsmith
9

Wenn Sie bereit sind, docker-compose runIhre Tests manuell zu starten, führt das Hinzufügen des --rmFlags seltsamerweise dazu, dass Compose den Exit-Status Ihres Befehls genau wiedergibt.

Hier ist mein Beispiel:

$ 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.
esmail
quelle
1
Oder (docker-compose run --rm ...) || exit $?zur Kündigung im Fehlerfall. Nützlich in Bash-Skripten.
Amirreza Nasiri
8

Verwenden Sie docker wait, um den Exit-Code zu erhalten:

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

fooist der "Projektname". Im obigen Beispiel habe ich es explizit angegeben, aber wenn Sie es nicht angeben, ist es der Verzeichnisname. barist der Name, den Sie dem zu testenden System in Ihrer docker-compose.yml geben.

Beachten Sie, dass docker logs -fdies auch das Richtige ist, wenn der Container stoppt. So können Sie setzen

$ docker logs -f foo_bar_1

zwischen dem docker-compose upund dem, docker waitdamit Sie Ihre Tests laufen sehen können.

Bryan Larsen
quelle
8

--exit-code-from SERVICEund arbeiten --abort-on-container-exitSie nicht in Szenarien, in denen Sie alle Container bis zur Fertigstellung ausführen müssen, sondern schlagen Sie fehl, wenn einer von ihnen vorzeitig beendet wird. Ein Beispiel könnte sein, wenn 2 Testanzüge gleichzeitig in verschiedenen Containern ausgeführt werden.

Mit dem Vorschlag von @ spendhil können Sie docker-composeein Skript einbinden , das fehlschlägt, wenn Container dies tun.

#!/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:]')

Dann wechseln Sie auf Ihrem CI-Server einfach docker-compose upzu ./docker-compose.sh up.

Matt Cole
quelle
1
Dieses Skript erreicht nie den Exit-Bereich, da andere Container (wie Datenbanken, Web-Apps) ständig ausgeführt werden. läuft im losgelösten Modus und wird beendet, sobald der Container hoch ist
Baldy
Dies funktioniert nur, wenn Sie alle Container vollständig ausführen möchten . Wahrscheinlich nicht besonders häufig, aber es war zum Zeitpunkt des Schreibens für mich nützlich und ich dachte, ich würde es teilen.
Matt Cole
Ich habe deine Antwort trotzdem positiv bewertet, da sie mich fast dorthin gebracht hat! Durch Hinzufügen von Docker Wait auf jedem Testcontainer im getrennten Modus funktionierte es. Vielen Dank für das Teilen :)
Baldy
2

Mit Docker-Rails können Sie angeben, welcher Container-Fehlercode an den Hauptprozess zurückgegeben wird, damit Ihr CI-Server das Ergebnis ermitteln kann. Es ist eine großartige Lösung für CI und die Entwicklung von Schienen mit Docker.

Beispielsweise

exit_code: web

In Ihrem docker-rails.ymlTestament erhalten Sie den webContainer-Exit-Code als Ergebnis des Befehls docker-rails ci test. docker-rails.ymlist nur ein Meta-Wrapper um den Standard docker-compose.yml, der Ihnen das Potenzial gibt, dieselbe Basiskonfiguration für verschiedene Umgebungen zu erben / wiederzuverwenden, dh Entwicklung gegen Test gegen parallele Tests.

kross
quelle
2

Falls Sie möglicherweise mehr Docker-Compose-Dienste mit demselben Namen auf einer Docker-Engine ausführen und den genauen Namen nicht kennen:

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

echo %? - Gibt den Exit-Code vom Test-Chrome-Dienst zurück

Leistungen:

  • Warten Sie, bis der genaue Service beendet ist
  • Verwendet den Dienstnamen, nicht den Containernamen
cvakiitho
quelle
2

Sie können den Exit-Status sehen mit:

echo $(docker-compose ps | grep "servicename" | awk '{print $4}')
RT Bathula
quelle
Vielen Dank, dass Sie damit begonnen haben. Hier ist meine Version von dieser (die für mich besser funktioniert b / c Ich denke, das Befehlsausgabeformat hat sich geändert, seit diese Antwort geschrieben wurde) -docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
DTrejo