Pense bem antes de usar Docker-in-Docker para CI ou ambiente de testes.

Pense bem antes de usar Docker-in-Docker para CI ou ambiente de testes.

Docker-in-Docker é um ambiente virtualizado do daemon do Docker, executado dentro do próprio contêiner para a construção de imagens de contêiner. O objetivo principal de criar o Docker-in-Docker foi ajudar no desenvolvimento do próprio Docker. Muitas pessoas o utilizam para executar o Jenkins CI. A princípio, isso parece normal, mas surgem problemas que podem ser evitados ao instalar o Docker dentro do contêiner do Jenkins CI. Este artigo explica como fazer isso. Se você está interessado na solução final sem detalhes, basta ler a última seção do artigo "Solução para o problema".

Pense bem antes de usar Docker-in-Docker para CI ou ambiente de testes.

Docker-in-Docker: "Bom"

Mais de dois anos atrás, eu inseri no Docker --all --privileged e escrevi a primeira versão do dind.O objetivo era ajudar a equipe principal a desenvolver o Docker mais rapidamente. Antes do Docker-in-Docker, o ciclo típico de desenvolvimento era assim:

  • hackity hack;
  • construção (build);
  • parar o daemon do Docker em execução;
  • iniciar um novo daemon do Docker;
  • testar;
  • repetir o ciclo.

Se você quisesse fazer uma construção limpa e reproduzível (ou seja, dentro de um contêiner), ela se tornava mais complicada:

  • hackity hack;
  • certificar-se de que uma versão funcional do Docker estava em execução;
  • construir um novo Docker com um Docker antigo;
  • parar o daemon do Docker;
  • iniciar um novo daemon do Docker;
  • testar;
  • parar o novo daemon do Docker;
  • repetir.

Com a chegada do Docker-in-Docker, o processo se simplificou:

  • hackity hack;
  • construção + execução em uma única etapa;
  • repetir o ciclo.

Não é melhor assim?

Pense bem antes de usar Docker-in-Docker para CI ou ambiente de testes.

Docker-in-Docker: "Ruim"

No entanto, ao contrário da crença popular, o Docker-in-Docker não é composto 100% de estrelas, unicórnios e pôneis. Quero dizer que existem alguns problemas que o desenvolvedor precisa estar ciente.

Um deles diz respeito ao LSM (módulos de segurança do Linux), como o AppArmor e o SELinux: ao iniciar um contêiner, o "Docker interno" pode tentar aplicar perfis de segurança que entrarão em conflito ou complicarão o "Docker externo". Este é o problema mais complexo que foi necessário resolver ao tentar combinar a implementação original da opção –privileged. Minhas alterações funcionaram, e todos os testes também passariam na minha máquina Debian e nas máquinas virtuais de teste Ubuntu, mas falhariam e queimariam na máquina do Michael Crosby (se não me engano, ele tinha Fedora). Não consigo lembrar a causa exata do problema, mas é possível que tenha surgido porque o Mike é um homem sábio que trabalha com SELINUX=enforce (eu usei AppArmor), e minhas alterações não consideraram os perfis do SELinux.

Docker-in-Docker: "Maligno"

O segundo problema está relacionado aos drivers de armazenamento do Docker. Quando você executa Docker-in-Docker, o Docker externo opera sobre um sistema de arquivos normal (EXT4, BTRFS ou qualquer outro que você tenha disponível), enquanto o Docker interno opera sobre um sistema de copy-on-write (AUFS, BTRFS, Device Mapper, etc., dependendo do que o Docker externo está configurado para usar). Isso resulta em várias combinações que não funcionarão. Por exemplo, você não poderá executar AUFS sobre AUFS.

Se você executar BTRFS sobre BTRFS, inicialmente isso deve funcionar, mas assim que houver subvolumes aninhados, não será possível excluir o subvolume pai. O módulo Device Mapper não tem namespace, então, se várias instâncias do Docker o utilizarem em uma mesma máquina, todas poderão ver (e impactar) as imagens umas das outras e os dispositivos de backup dos contêineres. Isso é ruim.

Existem soluções alternativas para muitos desses problemas. Por exemplo, se você quiser usar AUFS no Docker interno, basta transformar a pasta /var/lib/docker em um volume, e tudo ficará bem. O Docker adicionou alguns namespaces básicos aos alvos do Device Mapper, portanto, se várias chamadas do Docker forem feitas em uma única máquina, elas não se "atropelarão".

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

Docker-in-Docker: fica ainda pior

E quanto ao cache de build? Isso também pode ser bastante complicado. As pessoas costumam me perguntar: “se estou executando Docker-in-Docker, como posso usar as imagens localizadas no meu host, em vez de ter que puxar tudo novamente no meu Docker interno”?

Algumas pessoas empreendedoras tentaram montar /var/lib/docker do host no contêiner Docker-in-Docker. Às vezes, eles compartilham /var/lib/docker entre vários contêineres.

Pense bem antes de usar Docker-in-Docker para CI ou ambiente de testes.
Quer corromper dados? Porque isso é exatamente o que corromperá seus dados!

O daemon do Docker foi claramente projetado para ter acesso exclusivo ao /var/lib/docker. Nada mais deve 'tocar, cutucar ou mexer' em qualquer arquivo Docker que esteja nessa pasta.

Por que isso é tão importante? Porque é o resultado de uma das lições mais difíceis aprendidas durante o desenvolvimento do dotCloud. O motor de contêineres do dotCloud funcionava com vários processos acessando /var/lib/dotcloud simultaneamente. Truques engenhosos, como substituição atômica de arquivos (em vez de edição no local), 'bloqueio' de código com recomendações e bloqueios obrigatórios e outros experimentos com sistemas seguros, como SQLite e BDB, nem sempre funcionavam. Quando reestruturamos nosso motor de contêineres, que eventualmente se tornou o Docker, uma das principais decisões de design foi centralizar todas as operações de contêiner em um único daemon para acabar com essa confusão de acesso simultâneo.

Não me entenda mal: é completamente possível criar algo bom, confiável e rápido, que inclua vários processos e gerenciamento paralelo moderno. Mas acreditamos que é mais simples e fácil escrever e manter o código usando o Docker como o único jogador.

Isso significa que se você compartilhar o diretório /var/lib/docker entre várias instâncias do Docker, você terá problemas. Claro, isso pode funcionar, especialmente nos estágios iniciais de testes. 'Ei, mãe, consigo rodar o ubuntu com docker!' Mas tente fazer algo mais complicado, como puxar a mesma imagem de duas instâncias diferentes, e você verá o mundo pegar fogo.

Isso significa que, se o seu sistema CI realiza builds e rebuilds, a cada reinício do contêiner Docker-in-Docker você corre o risco de derrubar uma bomba de núcleo no seu cache. Isso não é nada legal!

Solução do problema

Vamos dar um passo atrás. Você realmente precisa do Docker-in-Docker ou apenas quer a capacidade de executar o Docker, ou seja, construir e executar contêineres e imagens do seu sistema CI enquanto esse próprio sistema CI está em um contêiner?

Aposto que a maioria das pessoas quer a última opção, ou seja, elas querem que um sistema CI, como Jenkins, possa executar contêineres. A maneira mais simples de fazer isso é simplesmente inserir o socket do Docker no seu contêiner CI, vinculando-o com a flag -v.

Em termos simples, ao executar seu contêiner CI (Jenkins ou outro), em vez de quebrar a cabeça com Docker-in-Docker, inicie-o a partir da linha:

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

Agora este contêiner terá acesso ao socket do Docker e, portanto, poderá executar contêineres. Exceto que, em vez de executar contêineres "filhos", ele executará contêineres "parentes".

Experimente 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

Isso se parece e funciona como Docker-in-Docker, mas não é Docker-in-Docker: quando este contêiner criar contêineres adicionais, eles serão criados no Docker de nível superior. Você não experimentará os efeitos colaterais da aninhagem, e o cache de construção será compartilhado para várias chamadas.

Nota: versões anteriores deste artigo recomendavam vincular o binário do Docker do host ao contêiner. Isso agora é considerado pouco confiável, uma vez que o mecanismo Docker não se aplica mais a bibliotecas estáticas ou quase estáticas.

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

Um pouco de publicidade 🙂

Obrigado por permanecer conosco. Você gosta dos nossos artigos? Quer ver mais materiais interessantes? Apoie-nos fazendo um pedido ou recomendando para conhecidos. VPS em nuvem para desenvolvedores a partir de $4.99, uma alternativa única aos servidores entry-level, que criamos para você: Toda a verdade sobre VPS (KVM) E5-2697 v3 (6 Núcleos) 10GB DDR4 480GB SSD 1Gbps a partir de $19 ou como dividir um servidor corretamente? (disponíveis opções com RAID1 e RAID10, até 24 núcleos e até 40GB DDR4).

Dell R730xd por 2 vezes mais barato no datacenter Equinix Tier IV em Amsterdã? Somente conosco 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100TB a partir de $199 na Holanda! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — a partir de $99! Leia sobre Como construir uma infraestrutura de classe corporativa utilizando servidores Dell R730xd E5-2650 v4 com custo de 9000 euros por centavos?

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster