Pense bem antes de usar o Docker-in-Docker para CI ou ambiente de teste

Pense bem antes de usar o Docker-in-Docker para CI ou ambiente de teste

Docker-in-Docker é um ambiente daemon Docker virtualizado executado dentro do próprio contêiner para construir imagens de contêiner. O principal objetivo da criação do Docker-in-Docker foi ajudar a desenvolver o próprio Docker. Muitas pessoas o usam para executar o Jenkins CI. Isso parece normal no início, mas depois surgem problemas que podem ser evitados instalando o Docker em um contêiner Jenkins CI. Este artigo explica como fazer isso. Se você estiver interessado na solução final sem detalhes, basta ler a última seção do artigo, “Resolvendo o problema”.

Pense bem antes de usar o Docker-in-Docker para CI ou ambiente de teste

Docker-in-Docker: "Bom"

Há mais de dois anos coloquei no Docker bandeira –privilegiado e escreveu primeira versão do dind. O objetivo era ajudar a equipe principal a desenvolver o Docker com mais rapidez. Antes do Docker-in-Docker, o ciclo de desenvolvimento típico era assim:

  • hack de hack;
  • construir;
  • parar um daemon Docker em execução;
  • lançando um novo daemon Docker;
  • teste;
  • repita o ciclo.

Se você quisesse fazer uma montagem bonita e reproduzível (ou seja, em um contêiner), ficaria mais complicado:

  • hack de hack;
  • certifique-se de que uma versão funcional do Docker esteja em execução;
  • construir um novo Docker com o Docker antigo;
  • pare o daemon do Docker;
  • inicie um novo daemon Docker;
  • teste;
  • pare o novo daemon do Docker;
  • repetir.

Com o advento do Docker-in-Docker, o processo ficou mais simples:

  • hack de hack;
  • montagem + lançamento em uma etapa;
  • repita o ciclo.

Não é muito melhor assim?

Pense bem antes de usar o Docker-in-Docker para CI ou ambiente de teste

Docker-in-Docker: "Ruim"

No entanto, ao contrário da crença popular, o Docker-in-Docker não é 100% composto por estrelas, pôneis e unicórnios. O que quero dizer é que existem vários problemas dos quais um desenvolvedor precisa estar ciente.

Um deles diz respeito aos LSMs (módulos de segurança do Linux), como AppArmor e SELinux: ao executar um contêiner, o “Docker interno” pode tentar aplicar perfis de segurança que irão entrar em conflito ou confundir o “Docker externo”. Este é o problema mais difícil de resolver ao tentar mesclar a implementação original do sinalizador –privileged. Minhas alterações funcionaram e todos os testes foram aprovados na minha máquina Debian e nas VMs de teste do Ubuntu, mas travaram e queimaram na máquina de Michael Crosby (ele tinha o Fedora, pelo que me lembro). Não me lembro a causa exata do problema, mas pode ter sido porque Mike é um cara esperto que trabalha com SELINUX=enforce (usei AppArmor) e minhas alterações não levaram em consideração os perfis do SELinux.

Docker-in-Docker: “Mal”

O segundo problema é com os drivers de armazenamento do Docker. Quando você executa o Docker-in-Docker, o Docker externo é executado sobre um sistema de arquivos regular (EXT4, BTRFS ou qualquer outro que você tenha) e o Docker interno é executado sobre um sistema de cópia na gravação (AUFS, BTRFS, Device Mapper , etc.). , dependendo do que está configurado para usar o Docker externo). Isso cria muitas combinações que não funcionarão. Por exemplo, você não poderá executar o AUFS sobre o AUFS.

Se você executar o BTRFS sobre o BTRFS, ele deverá funcionar primeiro, mas quando houver subvolumes aninhados, a exclusão do subvolume pai falhará. O módulo Device Mapper não possui namespace, portanto, se várias instâncias do Docker o estiverem executando na mesma máquina, todas poderão ver (e influenciar) as imagens umas nas outras e nos dispositivos de backup do contêiner. Isto é mau.

Existem soluções alternativas para resolver muitos desses problemas. Por exemplo, se você quiser usar AUFS no Docker interno, basta transformar a pasta /var/lib/docker em um volume e você ficará bem. O Docker adicionou alguns namespaces básicos aos nomes de destino do Device Mapper para que, se várias chamadas do Docker estiverem em execução na mesma máquina, elas não se sobreponham.

No entanto, tal configuração não é nada simples, como pode ser visto nestes artigos no repositório dind no GitHub.

Docker-in-Docker: fica pior

E o cache de compilação? Isso também pode ser bastante difícil. As pessoas costumam me perguntar “se estou executando o Docker-in-Docker, como posso usar imagens hospedadas no meu host em vez de colocar tudo de volta no meu Docker interno”?

Algumas pessoas empreendedoras tentaram vincular /var/lib/docker do host a um contêiner Docker-in-Docker. Às vezes, eles compartilham /var/lib/docker com vários contêineres.

Pense bem antes de usar o Docker-in-Docker para CI ou ambiente de teste
Você quer corromper seus dados? Porque é exatamente isso que danificará seus dados!

O daemon Docker foi claramente projetado para ter acesso exclusivo a /var/lib/docker. Nada mais deve "tocar, cutucar ou cutucar" qualquer arquivo Docker localizado nesta pasta.

Porque isto é assim? Porque este é o resultado de uma das lições mais difíceis aprendidas durante o desenvolvimento do dotCloud. O mecanismo de contêiner dotCloud foi executado com vários processos acessando /var/lib/dotcloud simultaneamente. Truques astutos, como substituição atômica de arquivos (em vez de edição no local), apimentar o código com bloqueios consultivos e obrigatórios e outros experimentos com sistemas seguros, como SQLite e BDB, nem sempre funcionavam. Quando estávamos redesenhando nosso mecanismo de contêiner, que eventualmente se tornou o Docker, uma das grandes decisões de design foi consolidar todas as operações de contêiner em um único daemon para acabar com toda a bobagem de simultaneidade.

Não me interpretem mal: é perfeitamente possível fazer algo bom, confiável e rápido que envolva múltiplos processos e controle paralelo moderno. Mas achamos que é mais simples e fácil escrever e manter código usando o Docker como único player.

Isso significa que se você compartilhar o diretório /var/lib/docker entre várias instâncias do Docker, terá problemas. Claro, isso pode funcionar, especialmente nos estágios iniciais dos testes. “Escute, mãe, posso rodar o Ubuntu como um docker!” Mas tente algo mais complexo, como extrair a mesma imagem de duas instâncias diferentes, e você verá o mundo queimar.

Isso significa que se o seu sistema de CI realizar compilações e recriações, toda vez que você reiniciar o contêiner Docker-in-Docker, você corre o risco de colocar uma bomba nuclear em seu cache. Isso não é nada legal!

A solução

Vamos dar um passo para trás. Você realmente precisa do Docker-in-Docker ou deseja apenas executar o Docker e construir e executar contêineres e imagens de seu sistema de CI enquanto o próprio sistema de CI está em um contêiner?

Aposto que a maioria das pessoas deseja a última opção, o que significa que desejam que um sistema de CI como o Jenkins seja capaz de executar contêineres. E a maneira mais fácil de fazer isso é simplesmente inserir um soquete Docker em seu contêiner de CI e associá-lo ao sinalizador -v.

Simplificando, quando você executa seu contêiner de CI (Jenkins ou outro), em vez de hackear algo junto com o Docker-in-Docker, inicie-o com a linha:

docker run -v /var/run/docker.sock:/var/run/docker.sock ...

Este contêiner agora terá acesso ao soquete Docker e, portanto, poderá executar contêineres. Exceto que, em vez de executar contêineres “filhos”, ele lançará contêineres “irmãos”.

Tente fazer isso usando a imagem oficial do Docker (que contém o binário do Docker):

docker run -v /var/run/docker.sock:/var/run/docker.sock 
           -ti docker

Parece e funciona como Docker-in-Docker, mas não é Docker-in-Docker: quando este contêiner cria contêineres adicionais, eles serão criados no Docker de nível superior. Você não sentirá os efeitos colaterais do aninhamento e o cache de assembly será compartilhado entre várias chamadas.

Nota: Versões anteriores deste artigo aconselhavam vincular o binário do Docker do host ao contêiner. Isso agora não é confiável, pois o mecanismo Docker não cobre mais bibliotecas estáticas ou quase estáticas.

Então, se quiser usar o Docker do Jenkins CI, você tem 2 opções:
instalar o Docker CLI usando o sistema básico de empacotamento de imagens (ou seja, se sua imagem for baseada em Debian, use pacotes .deb), usando a API Docker.

Alguns anúncios 🙂

Obrigado por ficar com a gente. Gostou dos nossos artigos? Quer ver mais conteúdos interessantes? Apoie-nos fazendo um pedido ou recomendando a amigos, nuvem VPS para desenvolvedores a partir de US$ 4.99, um análogo exclusivo de servidores básicos, que foi inventado por nós para você: Toda a verdade sobre VPS (KVM) E5-2697 v3 (6 núcleos) 10 GB DDR4 480 GB SSD 1 Gbps a partir de $ 19 ou como compartilhar um servidor? (disponível com RAID1 e RAID10, até 24 núcleos e até 40 GB DDR4).

Dell R730xd 2x mais barato no data center Equinix Tier IV em Amsterdã? Só aqui 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV a partir de US$ 199 na Holanda! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - a partir de US$ 99! Ler sobre Como construir uma empresa de infraestrutura. classe com o uso de servidores Dell R730xd E5-2650 v4 no valor de 9000 euros por um centavo?

Fonte: habr.com

Adicionar um comentário