Nos comentários do meu artigo houve muitos pedidos para explicar por que o Dockerfile descrito é tão ruim.
Resumo do episódio anterior: dois desenvolvedores enfrentando um prazo apertado criam um Dockerfile. Durante o processo, Ops Igor Ivanovich entra. O Dockerfile final é tão ruim que o Igor fica à beira de um infarto.

Agora vamos entender o que há de errado com esse Dockerfile.
Então, passou uma semana.
Dev Petya se encontra na cantina para um café com Ops Igor Ivanovich.
P: Igor Ivanovich, você está muito ocupado? Gostaria de entender onde erramos.
II: Isso é bom, não é comum encontrar desenvolvedores que se importam com a operação.
Para começar, vamos estabelecer algumas coisas:
- A ideologia do Docker: um contêiner — um processo.
- Quanto menor o contêiner, melhor.
- Quanto mais se pegar do cache, melhor.
P: E por que deve haver um processo único em um contêiner?
II: O Docker, ao iniciar um contêiner, monitora o estado do processo com pid 1. Se o processo morrer, o Docker tenta reiniciar o contêiner. Suponha que você tenha vários aplicativos em um contêiner ou que o aplicativo principal não esteja rodando com pid 1. Se o processo morrer, o Docker não ficará ciente disso.
Se não há mais perguntas, mostre seu Dockerfile.
E Petya mostrou:
FROM ubuntu:latest
# Copiando o código fonte
COPY .\/\/app
WORKDIR /app
# Atualizando a lista de pacotes
RUN apt-get update
# Atualizando pacotes
RUN apt-get upgrade
# Instalando pacotes necessários
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor
# Instalando bundler
RUN gem install bundler
# Instalando nodejs utilizado para a construção do estático
RUN curl -sL https:\/\/deb.nodesource.com\/setup_9.x | sudo bash -
RUN apt-get install -y nodejs
# Instalando dependências
RUN bundle install --without development test --path vendor\/bundle
# Limpando os caches
RUN rm -rf \/usr\/local\/bundle\/cache\/*.gem
RUN apt-get clean
RUN rm -rf \/var\/lib\/apt\/lists\/* \/tmp\/* \/var\/tmp\/*
RUN rake assets:precompile
# Executando o script ao iniciar o contêiner, que iniciará todo o resto.
CMD ["\/app\/init.sh"]II: Ah, vamos resolver isso passo a passo. Comecemos pela primeira linha:
FROM ubuntu:latestVocê está usando a tag latest. O uso da tag latest pode levar a consequências imprevisíveis. Imagine que o mantenedor da imagem cria uma nova versão da imagem com uma lista de software diferente, essa imagem recebe a tag latest. E seu contêiner, na melhor das hipóteses, deixa de construir, e na pior, você pega bugs que antes não existiam.
Você está usando uma imagem com um sistema operacional completo e muitos softwares desnecessários, o que aumenta o tamanho do contêiner. E quanto mais softwares, mais brechas e vulnerabilidades.
Além disso, quanto maior a imagem, mais espaço ela ocupa no host e no registro (você está guardando as imagens em algum lugar?).
P: Sim, claro, temos um registro, você mesmo o configurou.
IA: Então, sobre o que eu estava falando?.. Ah, sim, volumes… A carga na rede também aumenta. Para uma imagem única isso é imperceptível, mas quando há uma construção contínua, testes e deploys, isso se torna notável. E se você não tem o modo Deus no AWS, ainda receberá uma conta astronômica.
Por isso, é necessário escolher a imagem mais adequada, com a versão exata e o mínimo de softwares. Por exemplo, pegue: FROM ruby:2.5.5-stretch
P: Oh, entendi. E como e onde posso ver as imagens disponíveis? Como saber qual eu preciso?
IA: Normalmente, as imagens são obtidas do , não confunda com porn hub :). Para uma imagem geralmente existem várias compilações:
A Microsoft anunciou a expansão da funcionalidade do subsistema WSL2.: imagens construídas sobre uma base Linux minimalista, apenas 5 MB. Sua desvantagem: foi construída com uma implementação própria da libc, os pacotes padrão não funcionam. Buscar e instalar o pacote necessário levará um bom tempo.
Scratch: imagem base, não usada para compilar outras imagens. Destina-se exclusivamente à execução de binários e dados preparados. É ideal para executar aplicações binárias que incluem tudo o que é necessário, como aplicações em go.
Baseada em algum sistema operacional, como Ubuntu ou Debian. Bem, aqui não precisa explicar muito.
IA: Agora precisamos instalar todos os pacotes adicionais e limpar os caches. E logo podemos descartar apt-get upgrade. Se não, a cada construção, apesar do tag fixo da imagem base, serão geradas diferentes imagens. A atualização de pacotes na imagem é uma tarefa do mantenedor, que vem acompanhada de uma mudança na tag.
P: Sim, eu tentei fazer isso, consegui assim:
WORKDIR /app
COPY ./ /app
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*IA: Não está ruim, mas há coisas a serem melhoradas aqui. Veja, este comando:
RUN rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* … não remove os dados da imagem final, mas apenas cria uma camada adicional sem esses dados. Está correto assim:
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Mas isso ainda não é tudo. O que você tem aí, Ruby? Então não é necessário copiar todo o projeto no início. Basta copiar o Gemfile e o Gemfile.lock.
Com essa abordagem, o bundle install não será executado a cada alteração nos arquivos de origem, mas apenas se o Gemfile ou o Gemfile.lock mudarem.
Os mesmos métodos funcionam para outras linguagens com gerenciadores de dependências, como npm, pip, composer e outros baseados em um arquivo de lista de dependências.
E, por fim, você se lembra de que no início eu falei sobre a ideologia do Docker "um contêiner - um processo"? Isso significa que o supervisor não é necessário. Também não é preciso instalar o systemd, pelos mesmos motivos. Na prática, o Docker é o próprio supervisor. E quando você tenta executar vários processos nele, é como executar vários aplicativos dentro de um único processo do supervisor.
Ao construir, você criará uma única imagem e depois iniciará o número necessário de contêineres, para que em cada um funcione um processo.
Mas falaremos sobre isso mais tarde.
P: Parece que entendi. Veja o que acontece:
FROM ruby:2.5.5-stretch
WORKDIR /app
COPY Gemfile* /app
RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY . /app
RUN rake assets:precompile
CMD ["bundle", "exec", "passenger", "start"]E o lançamento dos serviços definidos será feito ao iniciar o contêiner?
IA: Sim, isso mesmo. Aliás, você pode usar tanto CMD quanto ENTRYPOINT. Entender a diferença entre eles é sua tarefa de casa. Há um bom artigo sobre isso no Habr. .
Então, vamos continuar. Você está baixando o arquivo para instalar o node, mas não há garantia de que ele terá o que você precisa. Precisamos adicionar validação. Por exemplo, assim:
EXECUTE curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x
&& echo "958c9a95c4974c918dca773edf6d18b1d1a41434 setup_9.x" | sha1sum -c -
&& bash setup_9.x
&& rm -rf setup_9.x
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Com a soma de verificação, você poderá verificar se baixou o arquivo correto.
P: Mas se o arquivo mudar, a construção não passará.
IA: Sim, e estranhamente, isso também é um ponto positivo. Você saberá que o arquivo mudou e poderá verificar o que foi alterado. Vai saber se, por exemplo, adicionaram um script que apaga tudo que alcança ou cria uma porta dos fundos.
P: Obrigado. Então, o Dockerfile final ficará assim:
FROM ruby:2.5.5-stretch
WORKDIR /app
COPY Gemfile* /app
RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x
&& echo "958c9a95c4974c918dca773edf6d18b1d1a41434 setup_9.x" | sha1sum -c -
&& bash setup_9.x
&& rm -rf setup_9.x
&& apt-get -y install libpq-dev imagemagick gsfonts nodejs
&& gem install bundler
&& bundle install --without development test --path vendor/bundle
&& rm -rf /usr/local/bundle/cache/*.gem
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY . /app
RUN rake assets:precompile
CMD ["bundle", "exec", "passenger", "start"]P: Igor Ivanovich, obrigado pela ajuda. Já preciso ir, tenho que fazer mais 10 commits hoje.
Igor Ivanovich, parando o colega apressado com um olhar, toma um gole do seu café forte. Pensando por alguns segundos sobre SLA de 99,9% e código sem bugs, ele faz uma pergunta.
IA: Onde vocês armazenam os logs?
P: Claro, no production.log. A propósito, como vamos acessá-los sem ssh?
IA: Se vocês mantiverem os logs em arquivos, já existe uma solução para vocês. O comando docker exec permite executar qualquer comando no contêiner. Por exemplo, vocês podem usar cat para os logs. E, usando a chave -it e iniciando o bash (se ele estiver instalado no contêiner), vocês terão acesso interativo ao contêiner.
Mas não é bom armazenar logs em arquivos. No mínimo, isso leva ao crescimento descontrolado do contêiner, pois ninguém rotaciona os logs. Todos os logs devem ser enviados para stdout. Você pode visualizá-los lá usando o comando docker logs.
P: Igor Ivanovich, e se tirássemos os logs para um diretório montado, na nodiva física, como os dados dos usuários?
IA: É bom que você não tenha se esquecido de tirar os dados gravados no disco da nodiva. Com os logs, também dá para fazer isso, só não se esqueça de configurar a rotação.
Pronto, você pode ir.
P: Igor Ivanovich, você pode me recomendar algo para ler?
II: Para começar, leia , é improvável que alguém conheça o Docker melhor do que eles.
E se você quiser ganhar experiência prática, vá para o . Afinal, teoria sem prática é morta.
Fonte: habr.com
