En los comentarios a mi artículo de Docker: malos consejos , hubo muchas solicitudes para explicar por qué el Dockerfile descrito en él es tan terrible.
Resumen de la serie anterior : dos desarrolladores en una fecha límite difícil componen el Dockerfile. En el proceso, Ops Igor Ivanovich viene a ellos. El Dockerfile resultante es tan malo que la IA está al borde de un ataque cardíaco.

Ahora descubramos qué le pasa a este Dockerfile.
Entonces, ha pasado una semana.
Dev Petya se encuentra en el comedor con una taza de café con Ops Igor Ivanovich.
P: Igor Ivanovich, ¿estás muy ocupado? Me gustaría saber dónde nos equivocamos.
AI: Esto es bueno, a menudo no conoces desarrolladores que estén interesados en la operación.
Para comenzar, pongámonos de acuerdo en algunas cosas:
- Ideología de Docker: un contenedor, un proceso.
- Cuanto más pequeño sea el contenedor, mejor.
- Cuanto más caché se toma, mejor.
P: ¿Y por qué debería haber un proceso en un contenedor?
AI: Docker, cuando se inicia el contenedor, supervisa el estado del proceso con pid 1. Si el proceso termina, Docker intenta reiniciar el contenedor. Suponga que tiene varias aplicaciones ejecutándose en su contenedor o que la aplicación principal no se inicia con pid 1. Si el proceso termina, Docker no lo sabrá.
Si no hay más preguntas, muestre su Dockerfile.
Y Petya mostró:
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: Oh, solucionémoslo en orden. Comencemos con la primera línea:
FROM ubuntu:latest
Tomas la latest
etiqueta. Usar la latest
etiqueta lleva a consecuencias impredecibles. Imagine que el encargado de mantener la imagen está creando una nueva versión de la imagen con una lista diferente de software, esta imagen obtiene la última etiqueta. Y, en el mejor de los casos, su contenedor deja de acumularse y, en el peor de los casos, detecta errores que antes no existían.
Toma una imagen con un sistema operativo completo con una gran cantidad de software innecesario, que infla el contenedor. Y cuanto más software, más agujeros y vulnerabilidades.
Además, cuanto más grande es la imagen, más ocupa espacio en el host y en el registro (¿almacena las imágenes en algún lugar)?
P: Sí, por supuesto, tenemos un registro y usted lo configura.
II: Entonces, ¿de qué estoy hablando? Oh, sí, volúmenes ... La carga de la red también está creciendo. Para una sola imagen, esto es invisible, pero cuando hay un ensamblaje continuo, pruebas y despliegue, es palpable. Y si no tiene el modo de Dios en AWS, también recibirá una cuenta espacial.
Por lo tanto, debe elegir la imagen más adecuada, con la versión exacta y el software mínimo. Por ejemplo, tome: FROM ruby:2.5.5-stretch
P: Oh, ya veo. ¿Y cómo y dónde ver las imágenes disponibles? ¿Cómo entender cuál necesito?
AI: Por lo general, las imágenes se toman de un dockhub , no lo confundas con un pornhub :). Por lo general, hay varios ensamblajes para una imagen:
Alpine : las imágenes se compilan en una imagen minimalista de Linux, solo 5 MB. Su inconveniente: está construido con su propia implementación de libc, los paquetes estándar no funcionan. Encontrar e instalar el paquete correcto llevará mucho tiempo.
Scratch : imagen básica, no utilizada para construir otras imágenes. Está destinado únicamente a ejecutar datos binarios preparados. Ideal para ejecutar aplicaciones binarias, que incluyen todo lo que necesita, como las aplicaciones go.
Basado en cualquier sistema operativo como Ubuntu o Debian. Bueno, aquí, creo, no hay necesidad de explicarlo.
II: Ahora tenemos que poner todo el extra. Paquetes y limpiar los cachés. E inmediatamente puede lanzar apt-get upgrade . De lo contrario, con cada ensamblaje, a pesar de la etiqueta fija de la imagen básica, se obtendrán diferentes imágenes. La actualización de los paquetes en una imagen es tarea del responsable, se acompaña de un cambio de etiqueta.
P: Sí, intenté hacerlo, resultó así:
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: No está mal, pero también hay algo en lo que trabajar. Mira, aquí está este comando:
RUN rm -rf /usr/local/bundle/cache/*.gem \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
... no elimina datos de la imagen final, sino que solo crea una capa adicional sin estos datos. Correctamente así:
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/*
Pero eso no es todo. ¿Qué tienes ahí, Ruby? Entonces no es necesario copiar todo el proyecto al principio. Simplemente copie Gemfile y Gemfile.lock.
Con este enfoque, la instalación del paquete no se ejecutará para cada cambio en el origen, sino solo si Gemfile o Gemfile.lock ha cambiado.
Los mismos métodos funcionan para otros idiomas con el administrador de dependencias, como npm, pip, composer y otros basados en el archivo con la lista de dependencias.
Y finalmente, recuerde, ¿al principio hablé sobre la ideología de Docker "un contenedor - un proceso"? Esto significa que no se necesita un supervisor. Además, no instale systemd, por las mismas razones. De hecho, Docker mismo es un supervisor. Y cuando intenta ejecutar varios procesos en él, es como iniciar varias aplicaciones en un proceso de supervisor.
Al ensamblar, creará una sola imagen y luego ejecutará el número deseado de contenedores para que cada proceso tenga un proceso.
Pero más sobre eso más tarde.
P: Parece entenderlo. Mira lo que pasa:
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"]
¿Se redefinirán los demonios cuando comience el contenedor?
AI: Sí, eso es correcto. Por cierto, puedes usar CMD y ENTRYPOINT. Y para entender cuál es la diferencia, esta es tu tarea. Hay un buen artículo sobre este tema en Habré.
Así que vamos Descarga el archivo para instalar el nodo, pero no hay garantía de que tenga lo que necesita. Es necesario agregar validación. Por ejemplo, así:
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/*
Con la suma de verificación, puede verificar que descargó el archivo correcto.
P: Pero si el archivo cambia, el ensamblaje no funcionará.
II: Sí, y curiosamente, esto también es una ventaja. Descubrirá que el archivo ha cambiado y puede ver lo que se cambió allí. Nunca se sabe, agregaron, digamos, un script que elimina todo lo que alcanza o hace una puerta trasera.
P: Gracias Resulta que el Dockerfile final se verá así:
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, gracias por la ayuda. Es hora de que corra, necesito hacer 10 confirmaciones más por hoy.
Igor Ivanovich, deteniendo a su apresurado colega con una mirada, toma un sorbo de café fuerte. Después de pensar durante unos segundos sobre el 99.9% SLA y el código sin errores, hace una pregunta.
AI: ¿Y dónde guardas los registros?
P: Por supuesto, en production.log. Por cierto, sí, ¿cómo podemos acceder a ellos sin ssh?
II: Si los deja en los archivos, ya se ha inventado una solución para usted. El comando docker exec le permite ejecutar cualquier comando en un contenedor. Por ejemplo, puedes hacer un gato para troncos. Y utilizando el modificador -it y ejecutando bash (si está instalado en el contenedor), obtendrá acceso interactivo al contenedor.
Pero no debe almacenar registros en archivos. Como mínimo, esto conduce a un crecimiento incontrolado del contenedor, pero nadie rota los registros. Todos los registros deben ser arrojados en stdout. Allí ya puede verlos usando el comando docker logs .
P: Igor Ivanovich, pero ¿puede poner los registros en el directorio montado, en el nodo físico, como datos de usuario?
AI: Es bueno que no olvides eliminar los datos descargados en el disco del nodo. Con los registros, esto también es posible, solo recuerde configurar la rotación.
Todo lo que puedes correr.
P: Igor Ivanovich, ¿pero aconseja qué leer?
AI: Primero, lea las recomendaciones de los desarrolladores de Docker , casi nadie conoce a Docker mejor que ellos.
Y si quieres pasar por la práctica, ve intensivamente . Después de todo, una teoría sin práctica está muerta.