Używanie docker-compose z CI - jak radzić sobie z kodami zakończenia i zdemonizowanymi kontenerami połączonymi?

88

W tej chwili nasi agenci Jenkinsa generują plik docker-compose.yml dla każdego z naszych projektów Rails, a następnie uruchamiają docker-compose. Docker-compose.yml ma główny kontener "sieciowy", który zawiera rbenv i wszystkie inne nasze zależności Railsowe. Jest powiązany z kontenerem DB, który zawiera testową bazę danych Postgres.

Problem pojawia się, gdy musimy faktycznie przeprowadzić testy i wygenerować kody wyjścia. Nasz serwer CI zostanie wdrożony tylko wtedy, gdy skrypt testowy zwróci exit 0, ale docker-compose zawsze zwraca 0, nawet jeśli jedno z poleceń kontenera zawiedzie.

Innym problemem jest to, że kontener bazy danych działa w nieskończoność, nawet po zakończeniu testów w kontenerze internetowym, więc docker-compose upnigdy nie zwraca.

Czy istnieje sposób, w jaki możemy użyć docker-compose do tego procesu? Musielibyśmy mieć możliwość uruchomienia kontenerów, ale kończą pracę po zakończeniu kontenera internetowego i zwracają jego kod zakończenia. W tej chwili utknęliśmy ręcznie przy użyciu dockera, aby uruchomić kontener DB i uruchomić kontener sieciowy z opcją --link.

Logan Serman
źródło

Odpowiedzi:

76

Od wersji 1.12.0możesz skorzystać z --exit-code-fromopcji.

Z dokumentacji :

- kod-wyjścia-z usługi

Zwróć kod zakończenia wybranego kontenera usługi. Implikuje - abort-on-container-exit.

panjan
źródło
1
To powinien być właściwy sposób, jeśli używasz wersji docker-compose1.12.0 i nowszych. Może to też twoja sprawa. Przykładem mogą być: docker-compose up --exit-code-from test-unit. Zauważ, że to nie zadziałało, dopóki nie dodałem set -ena początku mojego skryptu.
Adrian Antunez
--exit-code-fromjednak nie działa -d. Wyświetli te błędy: using --exit-code-from implies --abort-on-container-exiti --abort-on-container-exit and -d cannot be combined.
ericat
3
Udało mi się to uruchomić na Travis CI: travis-ci.org/coyote-team/coyote/builds/274582053 tutaj jest travis.yml: github.com/coyote-team/coyote/blob/master/.travis.yml # L12
subelsky
2
dokumentacja jest okropna. z jakimi flagami jest to zgodne? czy to tylko jedna usługa, czy możesz przekazać kilka?
worc
42

docker-compose runto prosty sposób na uzyskanie żądanych statusów zakończenia. Na przykład:

$ 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

Alternatywnie masz możliwość sprawdzenia martwych pojemników. Możesz użyć -fflagi, aby uzyskać tylko status wyjścia.

$ 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

Jeśli chodzi o kontener db, który nigdy nie powraca, jeśli używasz docker-compose up, będziesz musiał sigkill ten kontener; prawdopodobnie nie tego chcesz. Zamiast tego możesz użyć docker-compose up -ddo uruchomienia kontenerów zdemonizowanych i ręcznie zabić kontenery po zakończeniu testu. docker-compose run powinien uruchamiać połączone kontenery dla ciebie, ale słyszałem w SO gadkę o błędzie, który uniemożliwia to teraz działanie zgodnie z przeznaczeniem.

kojiro
źródło
Problem z uruchomieniem docker polega na tym, że nie daje on żadnych danych wyjściowych, gdy jest uruchamiany z opcją -T, i chcemy, aby dane wyjściowe mogły być sprawdzane w przypadku nieudanych kompilacji.
Logan Serman
1
@LoganSerman możesz sprawdzić wynik za pomocądocker-compose logs
kojiro
Czy istnieje sposób na ciągłe przesyłanie tych dzienników do STDOUT podczas wykonywania, abyśmy mogli je zobaczyć, gdy trwa kompilacja CI?
Logan Serman
Chyba nie rozumiem, dlaczego bierzesz z-T
kojiro
Niektóre z poleceń, które uruchamiamy wewnątrz kontenera w celu uruchomienia testów, mają potencjał, aby poprosić o dane wejściowe, chcemy uruchomić z -T, aby tego uniknąć. Na przykład Rbenv pyta, czy chcesz ponownie zainstalować wersję Rubiego, jeśli już istnieje.
Logan Serman
23

Opierając się na odpowiedzi Kojiro:

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

  1. pobierz identyfikatory kontenerów
  2. pobierz kod zakończenia ostatnich uruchomień dla każdego identyfikatora kontenera
  3. tylko kody stanu, które nie zaczynają się od „0”
  4. policzyć liczbę kodów stanu innych niż 0
  5. wyciąć spację

Zwraca liczbę zwróconych kodów zakończenia innych niż 0. Byłoby 0, gdyby wszystko zakończyło się kodem 0.

spędzony
źródło
Możesz również użyć cichego wyjścia z docker-compose ps, na przykład: docker-compose ps | grep -c "Exit 1"da ci liczbę, do której dopasowano "Wyjście 1" na wyświetlaczu z docker-compose ps(co zapewnia ładnie wydrukowaną tabelę podsumowującą wyniki). Kody wyjścia są wymienione w kolumnie „Stan”.
eharik
To jest naprawdę niesamowite. W moim przypadku niepowodzenie zestawu testów działającego w kontenerach nie powoduje wyjścia kontenerów z kodem 1. Nie mogę zagregować, jeśli któryś z nich zakończył się kodem 1, ponieważ żaden z nich tego nie robi ... Każdy pomysł, jak sobie z tym poradzić walizka?
walkerrandophsmith
9

Jeśli chcesz docker-compose runręcznie rozpocząć testy, dodanie --rmflagi, co dziwne, powoduje, że komponowanie dokładnie odzwierciedla stan wyjścia polecenia.

Oto mój przykład:

$ 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.
e-mail
źródło
1
Lub (docker-compose run --rm ...) || exit $?do zakończenia w przypadku błędu. Przydatne w skryptach basha.
Amirreza Nasiri
8

Użyj, docker waitaby uzyskać kod wyjścia:

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

footo „nazwa projektu”. W powyższym przykładzie wyraźnie to określiłem, ale jeśli go nie podasz, jest to nazwa katalogu. barto nazwa nadana testowanemu systemowi w pliku docker-compose.yml.

Zauważ, że docker logs -frobi to również właściwą rzecz, wychodząc, gdy kontener się zatrzyma. Więc możesz umieścić

$ docker logs -f foo_bar_1

między docker-compose upa do docker wait, abyś mógł oglądać przebieg testów.

Bryan Larsen
źródło
8

--exit-code-from SERVICEi --abort-on-container-exitnie działa w scenariuszach, w których trzeba uruchomić wszystkie kontenery do końca, ale kończy się niepowodzeniem, jeśli jeden z nich został wcześniej zakończony. Przykładem może być jednoczesne uruchomienie 2 zestawów testowych w różnych kontenerach.

Dzięki sugestii @ Spedhil możesz zawrzeć docker-composeskrypt, który zawiedzie, jeśli zrobią to jakiekolwiek kontenery.

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

Następnie na serwerze CI po prostu zmień docker-compose upna ./docker-compose.sh up.

Matt Cole
źródło
1
ten skrypt nigdy nie dociera do sekcji wyjściowej, ponieważ inne kontenery (takie jak bazy danych, aplikacje internetowe) działają bez przerwy. działa w trybie odłączonym, wychodzi, gdy tylko kontener się podniesie
Baldy
Zgadza się, działa to tylko wtedy, gdy chcesz uruchomić wszystkie kontenery do końca. Prawdopodobnie niezbyt częste, ale przydało mi się to w momencie pisania i pomyślałem, że się nim podzielę.
Matt Cole
I tak zagłosowałem za twoją odpowiedzią, ponieważ doprowadziła mnie tam większość! Dodanie oczekiwania Dockera na każdym kontenerze testowym w trybie odłączonym spowodowało, że działało. Dzięki za udostępnienie :)
Baldy
2

docker-rails pozwala określić, który kod błędu kontenera jest zwracany do procesu głównego, dzięki czemu serwer CI może określić wynik. Jest to świetne rozwiązanie dla CI i rozwoju dla szyn z dockerem.

Na przykład

exit_code: web

w twoim docker-rails.ymlzwróci webkod zakończenia kontenera w wyniku polecenia docker-rails ci test. docker-rails.ymlto tylko meta otoka wokół standardu, docker-compose.ymlktóra daje możliwość dziedziczenia / ponownego użycia tej samej podstawowej konfiguracji dla różnych środowisk, tj. programowanie vs test vs parallel_tests.

kross
źródło
2

W przypadku, gdy możesz uruchomić więcej usług docker-compose o tej samej nazwie na jednym silniku docker, a nie znasz dokładnej nazwy:

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

echo %? - zwraca kod wyjścia z usługi test-chrome

Korzyści:

  • czekanie na zakończenie usługi
  • używa nazwy usługi, a nie nazwy kontenera
cvakiitho
źródło
2

Możesz zobaczyć status wyjścia za pomocą:

echo $(docker-compose ps | grep "servicename" | awk '{print $4}')
RT Bathula
źródło
Dzięki za rozpoczęcie tego. Oto moja wersja tego (która działa lepiej dla mnie b / c Myślę, że format wyjściowy polecenia zmienił się od czasu napisania tej odpowiedzi) -docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
DTrejo