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”.
Docker-in-Docker: "Bom"
Há mais de dois anos coloquei no Docker
- 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?
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
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.
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,
Dell R730xd 2x mais barato no data center Equinix Tier IV em Amsterdã? Só aqui
Fonte: habr.com