Utilisation de docker-compose avec CI - comment gérer les codes de sortie et les conteneurs liés démonisés?

87

À l'heure actuelle, nos agents Jenkins génèrent un docker-compose.yml pour chacun de nos projets Rails, puis exécutent docker-compose up. Le docker-compose.yml a un conteneur "web" principal qui contient rbenv et toutes nos autres dépendances Rails. Il est lié à un conteneur DB contenant le test Postgres DB.

Le problème survient lorsque nous devons exécuter les tests et générer des codes de sortie. Notre serveur CI ne se déploiera que si le script de test renvoie la sortie 0, mais docker-compose renvoie toujours 0, même si l'une des commandes du conteneur échoue.

L'autre problème est que le conteneur de base de données s'exécute indéfiniment, même une fois que le conteneur Web a terminé d'exécuter les tests, il docker-compose upne revient donc jamais.

Existe-t-il un moyen d'utiliser docker-compose pour ce processus? Nous aurions besoin de pouvoir exécuter les conteneurs, mais de quitter une fois le conteneur Web terminé et de renvoyer son code de sortie. Pour le moment, nous sommes bloqués manuellement à l'aide de docker pour faire tourner le conteneur DB et exécuter le conteneur Web avec l'option --link.

Logan Serman
la source

Réponses:

76

Depuis la version 1.12.0, vous pouvez utiliser l' --exit-code-fromoption.

De la documentation :

--exit-code-from SERVICE

Renvoie le code de sortie du conteneur de services sélectionné. Implique --abort-on-container-exit.

Panjan
la source
1
Cela devrait être la bonne façon de procéder si vous utilisez la version docker-compose1.12.0 et supérieure. C'est peut-être votre cas aussi. Un exemple pourrait être: docker-compose up --exit-code-from test-unit. Notez que cela n'a pas fonctionné pour moi jusqu'à ce que j'aie ajouté un set -eau début de mon script.
Adrian Antunez
--exit-code-fromne fonctionne pas avec -dcependant. Il lancera ces erreurs: using --exit-code-from implies --abort-on-container-exitet --abort-on-container-exit and -d cannot be combined.
ericat
3
J'ai pu faire fonctionner cela sur Travis CI: travis-ci.org/coyote-team/coyote/builds/274582053 voici le travis.yml: github.com/coyote-team/coyote/blob/master/.travis.yml # L12
subelsky
2
la documentation est atroce. avec quels drapeaux est-ce compatible? s'agit-il d'un seul service ou pouvez-vous en passer plusieurs?
worc
42

docker-compose runest le moyen simple d'obtenir les statuts de sortie que vous désirez. Par exemple:

$ 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

Sinon, vous avez la possibilité d' inspecter les conteneurs morts. Vous pouvez utiliser l' -findicateur pour obtenir uniquement l'état de sortie.

$ 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

En ce qui concerne le conteneur db qui ne retourne jamais, si vous l'utilisez, docker-compose upvous devrez sigkill ce conteneur; ce n'est probablement pas ce que vous voulez. Au lieu de cela, vous pouvez utiliser docker-compose up -dpour exécuter vos conteneurs démonisés et supprimer manuellement les conteneurs lorsque votre test est terminé. docker-compose run devrait exécuter des conteneurs liés pour vous, mais j'ai entendu des discussions sur SO à propos d'un bogue empêchant cela de fonctionner comme prévu pour le moment.

Kojiro
la source
Le problème avec docker run est qu'il ne donne aucune sortie lorsqu'il est exécuté avec -T, et nous voulons la sortie afin que nous puissions inspecter les builds ayant échoué.
Logan Serman
1
@LoganSerman vous pouvez inspecter la sortie avecdocker-compose logs
kojiro
Existe-t-il un moyen de diriger constamment ces journaux vers STDOUT pendant l'exécution afin que nous puissions le voir pendant la construction du CI?
Logan Serman
Je suppose que je ne comprends pas pourquoi vous courez avec-T
kojiro
Certaines des commandes que nous exécutons à l'intérieur du conteneur pour exécuter des tests ont le potentiel de demander une entrée, nous voulons exécuter avec -T pour éviter cela. Rbenv par exemple vous demande si vous souhaitez réinstaller une version Ruby si elle existe déjà.
Logan Serman
23

S'appuyant sur la réponse de Kojiro:

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

  1. obtenir les ID de conteneur
  2. obtenir le dernier code de sortie pour chaque ID de conteneur
  3. seulement les codes d'état qui ne commencent pas par «0»
  4. compter le nombre de codes d'état non-0
  5. couper les espaces blancs

Renvoie le nombre de codes de sortie non-0 renvoyés. Serait 0 si tout sortait avec le code 0.

épuisé
la source
Vous pouvez également utiliser la sortie non silencieuse de docker-compose ps, par exemple: docker-compose ps | grep -c "Exit 1"vous donnera le décompte à partir duquel "Sortie 1" correspond à l'affichage docker-compose ps(qui fournit un joli tableau récapitulatif des résultats). Les codes de sortie sont répertoriés dans la colonne "État".
eharik
C'est vraiment génial. Dans mon cas, l'échec de la suite de tests s'exécutant dans des conteneurs ne fait pas sortir les conteneurs avec un code de 1. Je ne peux pas agréger le cas échéant avec un code de 1 car aucun d'entre eux ne le fait ... Toute idée de comment gérer cela Cas?
walkerrandophsmith
9

Si vous êtes prêt à utiliser docker-compose runpour lancer manuellement vos tests, l'ajout de l' --rmindicateur, assez curieusement, fait que Compose reflète avec précision l'état de sortie de votre commande.

Voici mon exemple:

$ 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
la source
1
Ou (docker-compose run --rm ...) || exit $?pour résiliation en cas d'erreur. Utile dans les scripts bash.
Amirreza Nasiri
8

Utilisez docker waitpour obtenir le code de sortie:

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

fooest le "nom du projet". Dans l'exemple ci-dessus, je l'ai spécifié explicitement, mais si vous ne le fournissez pas, c'est le nom du répertoire. barest le nom que vous donnez au système testé dans votre docker-compose.yml.

Notez que cela docker logs -ffait aussi la bonne chose, en sortant lorsque le conteneur s'arrête. Ainsi vous pouvez mettre

$ docker logs -f foo_bar_1

entre le docker-compose upet le docker waitpour que vous puissiez regarder vos tests s'exécuter.

Bryan Larsen
la source
8

--exit-code-from SERVICEet --abort-on-container-exitne fonctionnent pas dans les scénarios où vous devez exécuter tous les conteneurs jusqu'à la fin, mais échouent si l'un d'entre eux est sorti prématurément. Un exemple pourrait être si l'exécution de 2 combinaisons de test simultanément dans différents conteneurs.

Avec la suggestion de @ sharedhil, vous pouvez docker-composeintégrer un script qui échouera si des conteneurs échouent.

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

Ensuite, sur votre serveur CI, passez simplement docker-compose upà ./docker-compose.sh up.

Matt Cole
la source
1
ce script n'atteint jamais la section de sortie car d'autres conteneurs (tels que des bases de données, des applications Web) s'exécutent en permanence. fonctionnant en mode détaché, il sort dès que le conteneur est en place
Baldy
C'est vrai, cela ne fonctionne que si vous souhaitez exécuter tous les conteneurs jusqu'à la fin. Probablement pas particulièrement courant, mais cela m'a été utile au moment de la rédaction et j'ai pensé que je le partagerais.
Matt Cole
J'ai quand même voté pour votre réponse, car elle m'a permis d'y arriver presque! L'ajout d'une attente Docker sur chaque conteneur de test en mode détaché l'a fait fonctionner. Merci d'avoir partagé :)
Baldy
2

docker-rails vous permet de spécifier le code d'erreur du conteneur renvoyé au processus principal, afin que votre serveur CI puisse déterminer le résultat. C'est une excellente solution pour le CI et le développement de rails avec docker.

Par exemple

exit_code: web

dans votre docker-rails.ymldonnera le webcode de sortie des conteneurs à la suite de la commande docker-rails ci test. docker-rails.ymlest juste un méta wrapper autour du standard docker-compose.ymlqui vous donne le potentiel d'hériter / de réutiliser la même configuration de base pour différents environnements, c'est-à-dire développement vs test vs parallel_tests.

Kross
la source
2

Au cas où vous pourriez exécuter plus de services de composition de docker avec le même nom sur un moteur de docker et que vous ne connaissez pas le nom exact:

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

echo %? - renvoie le code de sortie du service test-chrome

Avantages:

  • attend le service exact pour quitter
  • utilise le nom du service, pas le nom du conteneur
cvakiitho
la source
2

Vous pouvez voir l'état de sortie avec:

echo $(docker-compose ps | grep "servicename" | awk '{print $4}')
RT Bathula
la source
Merci d'avoir commencé. Voici ma version de celle-ci (qui fonctionne mieux pour moi b / c je pense que le format de sortie de la commande a changé depuis que cette réponse a été écrite) -docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
DTrejo