Uso de docker-compose con CI: ¿cómo lidiar con los códigos de salida y los contenedores vinculados demonizados?

87

En este momento, nuestros agentes de Jenkins generan un docker-compose.yml para cada uno de nuestros proyectos de Rails y luego ejecutan docker-compose. El docker-compose.yml tiene un contenedor "web" principal que tiene rbenv y todas nuestras otras dependencias de Rails dentro. Está vinculado a un contenedor de base de datos que contiene la base de datos de Postgres de prueba.

El problema surge cuando realmente necesitamos ejecutar las pruebas y generar códigos de salida. Nuestro servidor de CI solo se implementará si el script de prueba devuelve exit 0, pero docker-compose siempre devuelve 0, incluso si uno de los comandos del contenedor falla.

El otro problema es que el contenedor DB se ejecuta indefinidamente, incluso después de que el contenedor web haya terminado de ejecutar las pruebas, por lo que docker-compose upnunca regresa.

¿Hay alguna forma en que podamos usar docker-compose para este proceso? Necesitaríamos poder ejecutar los contenedores, pero salir después de que el contenedor web esté completo y devolver su código de salida. En este momento, estamos atascados manualmente al usar la ventana acoplable para activar el contenedor de base de datos y ejecutar el contenedor web con la opción --link.

Logan Serman
fuente

Respuestas:

76

Desde la versión 1.12.0, puede utilizar la --exit-code-fromopción.

De la documentación :

--exit-code-from SERVICE

Devuelve el código de salida del contenedor de servicio seleccionado. Implica --abort-on-container-exit.

panjan
fuente
1
Esa debería ser la forma correcta de hacerlo si está utilizando docker-compose1.12.0 y superior. Quizás también sea tu caso. Un ejemplo podría ser: docker-compose up --exit-code-from test-unit. Tenga en cuenta que no funcionó para mí hasta que agregué un set -eal principio de mi script.
Adrian Antunez
--exit-code-fromaunque no funciona -d. using --exit-code-from implies --abort-on-container-exit--abort-on-container-exit and -d cannot be combined.
Lanzará
3
Pude hacer que esto funcionara en Travis CI: travis-ci.org/coyote-team/coyote/builds/274582053 aquí está el travis.yml: github.com/coyote-team/coyote/blob/master/.travis.yml # L12
subelsky
2
la documentación es atroz. ¿Con qué banderas es compatible? ¿Es solo un servicio o se pueden pasar varios?
Trabajo
42

docker-compose runes la forma sencilla de obtener los estados de salida que desee. Por ejemplo:

$ 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

Alternativamente, tiene la opción de inspeccionar los contenedores muertos. Puede usar la -fbandera para obtener solo el estado de salida.

$ 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 cuanto al contenedor de la base de datos que nunca regresa, si lo usa docker-compose up, necesitará sigkill ese contenedor; probablemente eso no es lo que quieres. En su lugar, puede usar docker-compose up -dpara ejecutar sus contenedores demonizados y matar manualmente los contenedores cuando se complete la prueba. docker-compose run debería ejecutar contenedores vinculados para usted, pero he escuchado charlas en SO sobre un error que impide que funcione como se esperaba en este momento.

kojiro
fuente
El problema con la ejecución de Docker es que no da ningún resultado cuando se ejecuta con -T, y queremos el resultado para poder inspeccionar las compilaciones fallidas.
Logan Serman
1
@LoganSerman puede inspeccionar la salida condocker-compose logs
kojiro
¿Hay alguna manera de canalizar constantemente esos registros a STDOUT durante la ejecución para que podamos verlos mientras la compilación de CI está en progreso?
Logan Serman
Creo que no entiendo por qué se está ejecutando con-T
kojiro
Algunos de los comandos que ejecutamos dentro del contenedor para ejecutar pruebas tienen el potencial de solicitar información, queremos ejecutar con -T para evitar esto. Rbenv, por ejemplo, le pregunta si desea reinstalar una versión de Ruby si ya existe.
Logan Serman
23

Sobre la base de la respuesta de kojiro:

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

  1. obtener ID de contenedor
  2. obtener el código de salida de las últimas ejecuciones para cada ID de contenedor
  3. solo códigos de estado que no comiencen con '0'
  4. contar el número de códigos de estado distintos de 0
  5. recortar el espacio en blanco

Devuelve cuántos códigos de salida distintos de 0 se devolvieron. Sería 0 si todo saliera con el código 0.

Spenthil
fuente
También puede utilizar la salida no silenciosa de docker-compose ps, por ejemplo: docker-compose ps | grep -c "Exit 1"le dará el recuento donde "Salida 1" se corresponde en la pantalla desde docker-compose ps(que proporciona una tabla resumen de resultados bastante impresa). Los códigos de salida se enumeran en la columna "Estado".
eharik
Esto es realmente asombroso. En mi caso, el conjunto de pruebas fallidas que se ejecuta en contenedores no hace que los contenedores salgan con un código de 1. No puedo agregar si alguno salió con un código de 1 ya que ninguno de ellos lo hace ... Alguna idea de cómo manejar esto ¿caso?
walkerrandophsmith
9

Si está dispuesto a usar docker-compose runpara iniciar manualmente sus pruebas, agregar la --rmbandera, por extraño que parezca, hace que Compose refleje con precisión el estado de salida de su comando.

Este es mi ejemplo:

$ 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.
correo electronico
fuente
1
O (docker-compose run --rm ...) || exit $?para rescisión en caso de error. Útil en scripts bash.
Amirreza Nasiri
8

Úselo docker waitpara obtener el código de salida:

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

fooes el "nombre del proyecto". En el ejemplo anterior, lo especifiqué explícitamente, pero si no lo proporciona, es el nombre del directorio. bares el nombre que le da al sistema bajo prueba en su docker-compose.yml.

Tenga en cuenta que también docker logs -fhace lo correcto al salir cuando el contenedor se detiene. Entonces puedes poner

$ docker logs -f foo_bar_1

entre el docker-compose upy el docker waitpara que pueda ver cómo se ejecutan sus pruebas.

Bryan Larsen
fuente
8

--exit-code-from SERVICEy --abort-on-container-exitno funciona en escenarios en los que necesita ejecutar todos los contenedores hasta su finalización, pero falla si uno de ellos salió antes. Un ejemplo podría ser si se ejecutan 2 trajes de prueba simultáneamente en diferentes contenedores.

Con la sugerencia de @ spenthil, puede incluir docker-composeun script que fallará si lo hace algún contenedor.

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

Luego, en su servidor CI simplemente cambie docker-compose upa ./docker-compose.sh up.

Matt Cole
fuente
1
este script nunca llega a la sección de salida, ya que otros contenedores (como bases de datos, aplicaciones web) se ejecutan de forma permanente. funcionando en modo separado, sale tan pronto como el contenedor está arriba
Baldy
Así es, esto solo funciona si desea ejecutar todos los contenedores hasta su finalización. Probablemente no sea particularmente común, pero fue útil para mí en el momento de escribir este artículo y pensé en compartirlo.
Matt Cole
¡Voto a favor tu respuesta de todos modos, ya que me llevó la mayor parte del camino! Agregar Docker wait en cada contenedor de prueba en modo separado lo hizo funcionar. Gracias por compartir :)
Baldy
2

docker-rails le permite especificar qué código de error del contenedor se devuelve al proceso principal, para que su servidor de CI pueda determinar el resultado. Es una gran solución para CI y desarrollo para rieles con Docker.

Por ejemplo

exit_code: web

en su docker-rails.ymlgenerará el webcódigo de salida de los contenedores como resultado del comando docker-rails ci test. docker-rails.ymles solo una meta envoltura alrededor del estándar docker-compose.ymlque le brinda la posibilidad de heredar / reutilizar la misma configuración base para diferentes entornos, es decir, desarrollo frente a prueba frente a pruebas paralelas.

kross
fuente
2

En caso de que pueda ejecutar más servicios de composición de Docker con el mismo nombre en un motor de Docker y no sepa el nombre exacto:

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

echo %? - devuelve el código de salida del servicio test-chrome

Beneficios:

  • espera a que salga el servicio exacto
  • usa el nombre del servicio, no el nombre del contenedor
cvakiitho
fuente
2

Puede ver el estado de salida con:

echo $(docker-compose ps | grep "servicename" | awk '{print $4}')
RT Bathula
fuente
Gracias por empezar con esto. Aquí está mi versión de este (que funciona mejor para mí porque creo que el formato de salida del comando ha cambiado desde que se escribió esta respuesta) -docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
DTrejo