Usando docker-compose com CI - como lidar com códigos de saída e contêineres vinculados daemonizados?

88

No momento, nossos agentes Jenkins geram um docker-compose.yml para cada um de nossos projetos Rails e, em seguida, executam docker-compose up. O docker-compose.yml tem um contêiner "web" principal que contém o rbenv e todas as outras dependências do Rails. Ele está vinculado a um contêiner de banco de dados que contém o banco de dados Postgres de teste.

O problema surge quando precisamos realmente executar os testes e gerar códigos de saída. Nosso servidor de CI só será implantado se o script de teste retornar a saída 0, mas docker-compose sempre retorna 0, mesmo se um dos comandos do contêiner falhar.

O outro problema é que o contêiner de banco de dados é executado indefinidamente, mesmo depois que o contêiner da web termina de executar os testes, portanto, docker-compose upnunca retorna.

Existe uma maneira de usar docker-compose para esse processo? Precisaríamos ser capazes de executar os contêineres, mas sair depois que o contêiner da web for concluído e retornar seu código de saída. No momento, estamos travados manualmente usando o docker para ativar o contêiner DB e executar o contêiner da web com a opção --link.

Logan Serman
fonte

Respostas:

76

Desde a versão 1.12.0, você pode usar a --exit-code-fromopção.

Da documentação :

--exit-code-from SERVICE

Retorne o código de saída do container de serviço selecionado. Implica --abort-on-container-exit.

Panjan
fonte
1
Essa deve ser a maneira correta de fazer se você estiver usando docker-compose1.12.0 e superior. Talvez seja o seu caso também. Um exemplo poderia ser: docker-compose up --exit-code-from test-unit. Observe que não funcionou para mim até que eu adicionei um set -eno início do meu script.
Adrian Antunez
--exit-code-fromnão funciona -dembora. Ele gerará estes erros: using --exit-code-from implies --abort-on-container-exite --abort-on-container-exit and -d cannot be combined.
ericat
3
Consegui fazer isso funcionar no Travis CI: travis-ci.org/coyote-team/coyote/builds/274582053 aqui está o travis.yml: github.com/coyote-team/coyote/blob/master/.travis.yml # L12
subelsky
2
a documentação é atroz. com quais sinalizadores isso é compatível? é apenas um serviço ou pode passar vários?
worc
42

docker-compose runé a maneira simples de obter os status de saída desejados. Por exemplo:

$ 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

Como alternativa, você tem a opção de inspecionar os contêineres inativos. Você pode usar o -fsinalizador para obter apenas o status de saída.

$ 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

Quanto ao contêiner de banco de dados que nunca retorna, se você usar docker-compose up, será necessário assinar o contêiner; provavelmente não é isso que você quer. Em vez disso, você pode usar docker-compose up -dpara executar seus contêineres daemonizados e eliminar manualmente os contêineres quando o teste for concluído. docker-compose run deve executar contêineres vinculados para você, mas ouvi conversas no SO sobre um bug que impede que funcione como pretendido agora.

kojiro
fonte
O problema com docker run é que ele não fornece nenhuma saída quando executado com -T, e queremos a saída para que possamos inspecionar compilações com falha.
Logan Serman
1
@LoganSerman, você pode inspecionar a saída comdocker-compose logs
kojiro
Existe uma maneira de canalizar constantemente esses logs para STDOUT durante a execução para que possamos vê-los enquanto a construção do CI está em andamento?
Logan Serman
Acho que não entendo por que você está executando com-T
kojiro
Alguns dos comandos que executamos dentro do contêiner para executar testes têm o potencial de solicitar entradas, queremos executar com -T para evitar isso. Rbenv, por exemplo, pergunta se você deseja reinstalar uma versão Ruby se ela já existir.
Logan Serman
23

Com base na resposta de Kojiro:

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

  1. obter IDs de contêiner
  2. obter o código de saída das últimas execuções para cada ID de contêiner
  3. apenas códigos de status que não começam com '0'
  4. número de contagem de códigos de status diferentes de 0
  5. cortar espaço em branco

Retorna quantos códigos de saída diferentes de 0 foram retornados. Seria 0 se tudo saísse com o código 0.

passouhil
fonte
Você também pode usar a saída não silenciosa de docker-compose ps, por exemplo: docker-compose ps | grep -c "Exit 1"fornecerá a contagem de onde "Saída 1" é correspondida na exibição docker-compose ps(que fornece uma tabela de resumo de resultados bem impressa). Os códigos de saída estão listados na coluna "Estado".
eharik
Isso é realmente incrível. No meu caso, a falha na suíte de testes em execução em contêineres não faz com que os contêineres saiam com um código de 1. Não posso agregar se algum saiu com um código de 1, pois nenhum deles faz ... Alguma ideia de como lidar com isso caso?
walkerrandophsmith
9

Se você deseja usar docker-compose runpara iniciar manualmente seus testes, adicionar o --rmsinalizador, por incrível que pareça, faz com que o Compose reflita com precisão o status de saída do comando.

Aqui está meu exemplo:

$ 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
fonte
1
Ou (docker-compose run --rm ...) || exit $?para rescisão em caso de erro. Útil em scripts bash.
Amirreza Nasiri
8

Use docker waitpara obter o código de saída:

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

fooé o "nome do projeto". No exemplo acima, eu especifiquei explicitamente, mas se você não fornecer, é o nome do diretório. baré o nome que você dá ao sistema em teste em docker-compose.yml.

Observe que também docker logs -ffaz a coisa certa sair quando o contêiner parar. Então você pode colocar

$ docker logs -f foo_bar_1

entre o docker-compose upe o docker waitpara que você possa assistir a execução de seus testes.

Bryan Larsen
fonte
8

--exit-code-from SERVICEe --abort-on-container-exitnão funciona em cenários em que você precisa executar todos os contêineres até a conclusão, mas falha se um deles saiu antes do tempo. Um exemplo pode ser a execução de 2 conjuntos de teste simultaneamente em recipientes diferentes.

Com a sugestão de @ gastouhil, você pode envolver docker-composeem um script que falhará se algum contêiner falhar.

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

Em seguida, em seu servidor CI, simplesmente mude docker-compose uppara ./docker-compose.sh up.

Matt Cole
fonte
1
esse script nunca atinge a seção de saída, pois outros contêineres (como bancos de dados, aplicativos da web) são executados perpetuamente. executando em modo separado, ele sai assim que o contêiner estiver
ativo
Isso mesmo, isso só funciona se você quiser executar todos os contêineres até a conclusão. Provavelmente não é muito comum, mas foi útil para mim no momento em que escrevi este artigo e pensei em compartilhá-lo.
Matt Cole
Votei positivamente em sua resposta, pois ela me levou até lá! Adicionar espera do Docker em cada contêiner de teste no modo desanexado fez com que funcionasse. Obrigado por compartilhar :)
Baldy
2

docker-rails permite que você especifique qual código de erro do contêiner é retornado ao processo principal, para que seu servidor de CI possa determinar o resultado. É uma ótima solução para CI e desenvolvimento para trilhos com docker.

Por exemplo

exit_code: web

em seu docker-rails.ymlirá produzir o webcódigo de saída dos contêineres como resultado do comando docker-rails ci test. docker-rails.ymlé apenas um meta wrapper em torno do padrão docker-compose.ymlque oferece o potencial de herdar / reutilizar a mesma configuração de base para diferentes ambientes, ou seja, desenvolvimento vs teste vs parallel_tests.

Kross
fonte
2

Caso você possa executar mais serviços docker-compose com o mesmo nome em um mecanismo docker e não saiba o nome exato:

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

echo %? - retorna o código de saída do serviço test-chrome

Benefícios:

  • espere o serviço exato sair
  • usa o nome do serviço, não o nome do contêiner
cvakiitho
fonte
2

Você pode ver o status de saída com:

echo $(docker-compose ps | grep "servicename" | awk '{print $4}')
RT Bathula
fonte
Obrigado por começar. Aqui está minha versão deste (que funciona melhor para mim b / c acho que o formato de saída do comando mudou desde que esta resposta foi escrita) -docker-compose ps | grep servicename | grep -v 'Exit 0' && echo "Automation or integration tests failed." && exit 1
DTrejo