Nos comentários do meu artigo do Docker: conselhos ruins , havia muitos pedidos para explicar por que o arquivo Docker descrito nele é tão terrível.
Resumo da série anterior : dois desenvolvedores em um prazo final formam o Dockerfile. No processo, Ops Igor Ivanovich chega até eles. O Dockerfile resultante é tão ruim que a IA está à beira de um ataque cardíaco.

Agora vamos descobrir o que há de errado com este Dockerfile.
Então, uma semana se passou.
Dev Petya se encontra na sala de jantar com uma xícara de café com Ops Igor Ivanovich.
P: Igor Ivanovich, você está muito ocupado? Eu gostaria de descobrir onde erramos.
AI: Isso é bom, muitas vezes você não encontra desenvolvedores interessados em operação.
Para começar, vamos concordar com algumas coisas:
- Ideologia do Docker: um contêiner - um processo.
- Quanto menor o contêiner, melhor.
- Quanto mais cache for usado, melhor.
P: E por que deveria haver um processo em um contêiner?
AI: O Docker, quando o contêiner é iniciado, monitora o status do processo com o pid 1. Se o processo morrer, o Docker tenta reiniciar o contêiner. Suponha que você tenha vários aplicativos em execução no contêiner ou que o aplicativo principal não seja iniciado com o pid 1. Se o processo morrer, o Docker não o saberá.
Se não houver mais perguntas, mostre seu Dockerfile.
E Petya mostrou:
FROM ubuntu:latest # COPY ./ /app WORKDIR /app # RUN apt-get update # RUN apt-get upgrade # RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor # bundler RUN gem install bundler # nodejs RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash - RUN apt-get install -y nodejs # RUN bundle install --without development test --path vendor/bundle # 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 # , , . CMD ["/app/init.sh"]
AI: Ah, vamos resolver em ordem. Vamos começar com a primeira linha:
FROM ubuntu:latest
Você pega a tag latest
. O uso da tag latest
gera consequências imprevisíveis. Imagine que o mantenedor da imagem está construindo uma nova versão da imagem com uma lista diferente de software; essa imagem recebe a tag mais recente. E, na melhor das hipóteses, seu contêiner para de coletar e, na pior das hipóteses, você pega bugs que não existiam antes.
Você tira uma imagem com um sistema operacional completo com muito software desnecessário, que infla o contêiner. E quanto mais software, mais buracos e vulnerabilidades.
Além disso, quanto maior a imagem, mais ela ocupa espaço no host e no registro (você armazena as imagens em algum lugar)?
P: Sim, é claro, temos um registro e você o configura.
II: Então, do que estou falando? ... Ah, sim, volumes ... A carga da rede também está crescendo. Para uma única imagem, isso é invisível, mas quando há montagem, testes e implantação contínuos, é palpável. E se você não possui o modo de Deus na AWS, também receberá uma conta de espaço.
Portanto, você precisa escolher a imagem mais adequada, com a versão exata e o software mínimo. Por exemplo, pegue: FROM ruby:2.5.5-stretch
P: Ah, entendo. E como e onde ver as imagens disponíveis? Como entender qual eu preciso?
AI: Geralmente, as imagens são tiradas de um dockhub , não confunda com um pornhub :). Geralmente, existem várias montagens para uma imagem:
Alpino : as imagens são compiladas em uma imagem minimalista do Linux, apenas 5 MB. Seu ponto negativo: ele é construído com sua própria implementação da libc, os pacotes padrão não funcionam. Encontrar e instalar o pacote certo levará muito tempo.
Arranhão : imagem básica, não usada para criar outras imagens. Destina-se apenas a executar dados binários preparados. Ideal para executar aplicativos binários, que incluem tudo o que você precisa, como aplicativos go.
Baseado em qualquer sistema operacional, como Ubuntu ou Debian. Bem, aqui, eu acho, não há necessidade de explicar.
II: Agora precisamos colocar todo o extra. Empacota e limpa os caches. E imediatamente você pode lançar o apt-get upgrade . Caso contrário, a cada montagem, apesar da marca fixa da imagem básica, serão obtidas imagens diferentes. A atualização de pacotes em uma imagem é tarefa do mantenedor, acompanhada por uma alteração de tag.
P: Sim, eu tentei fazer isso, ficou 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/*
II: Nada mal, mas também há algo para trabalhar. Olha, aqui está este comando:
RUN rm -rf /usr/local/bundle/cache/*.gem \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
... não exclui dados da imagem final, mas apenas cria uma camada adicional sem esses dados. Corretamente:
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 não é tudo. O que você tem aí, Ruby? Então não é necessário copiar o projeto inteiro no início. Basta copiar o Gemfile e o Gemfile.lock.
Com essa abordagem, a instalação do pacote configurável não será executada para todas as alterações na fonte, mas apenas se o Gemfile ou o Gemfile.lock tiver sido alterado.
Os mesmos métodos funcionam para outros idiomas com o gerenciador de dependências, como npm, pip, compositer e outros, com base no arquivo com a lista de dependências.
E, finalmente, lembre-se, no começo eu falei sobre a ideologia do Docker "um contêiner - um processo"? Isso significa que um supervisor não é necessário. Além disso, não instale o systemd pelos mesmos motivos. De fato, o próprio Docker é um supervisor. E quando você tenta executar vários processos nele, é como iniciar vários aplicativos em um processo de supervisor.
Ao montar, você cria uma única imagem e, em seguida, executa o número desejado de contêineres para que cada processo tenha um processo.
Mas mais sobre isso mais tarde.
P: Parece entender. 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"]
Os daemons serão redefinidos quando o contêiner iniciar?
AI: Sim, está certo. A propósito, você pode usar o CMD e o ENTRYPOINT. E para entender qual é a diferença, esta é sua lição de casa. Há um bom artigo sobre esse tópico em Habré.
Então vamos lá. Você faz o download do arquivo para instalar o nó, mas não há garantia de que ele terá o que você precisa. É necessário adicionar validação. Por exemplo, assim:
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/*
Usando a soma de verificação, você pode verificar se baixou o arquivo correto.
P: Mas se o arquivo for alterado, a montagem não funcionará.
II: Sim, e por incrível que pareça, isso também é uma vantagem. Você descobrirá que o arquivo foi alterado e poderá ver o que foi alterado lá. Você nunca sabe, eles acrescentaram, digamos, um script que remove tudo o que alcança ou faz uma porta dos fundos.
P: Obrigado. Acontece que 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. É hora de eu correr, preciso fazer mais 10 confirmações por hoje.
Igor Ivanovich, parando o colega apressado com um olhar, toma um gole de café forte. Depois de pensar por alguns segundos no SLA de 99,9% e no código sem erros, ele faz uma pergunta.
AI: E onde você mantém os registros?
P: Claro, em production.log. A propósito, sim, como podemos acessá-los sem o ssh?
II: Se você os deixar nos arquivos, uma solução já foi inventada para você. O comando docker exec permite executar qualquer comando em um contêiner. Por exemplo, você pode criar gato para logs. E usando a opção -it e executando o bash (se estiver instalado no contêiner), você terá acesso interativo ao contêiner.
Mas você não deve armazenar logs em arquivos. No mínimo, isso leva a um crescimento descontrolado do contêiner, mas ninguém gira os logs. Todos os logs precisam ser lançados no stdout. Lá você já pode vê-los usando o comando docker logs .
P: Igor Ivanovich, mas pode colocar os logs no diretório montado, no nó físico, como dados do usuário?
AI: É bom que você não tenha esquecido de remover os dados baixados no disco do nó. Com logs, isso também é possível, lembre-se de configurar a rotação.
Tudo o que você pode executar.
P: Igor Ivanovich, mas aconselha o que ler?
AI: Primeiro, leia as recomendações dos desenvolvedores do Docker , quase ninguém conhece o Docker melhor do que eles.
E se você quiser passar pela prática, siga intensamente . Afinal, uma teoria sem prática está morta.