Dumb-INIT es un simple supervisor de procesos y un sistema de inicio diseñado para ejecutarse como PID 1 dentro de entornos de contenedores mínimos (como Docker). Se despliega como un binario pequeño y vinculado estáticamente en C.
Los contenedores livianos han popularizado la idea de ejecutar un solo proceso o servicio sin sistemas de inicio normales como Systemd o Sysvinit. Sin embargo, omitir un sistema init a menudo conduce a un manejo incorrecto de procesos y señales, y puede provocar problemas como contenedores que no pueden detenerse con gracia o fugas de contenedores que deberían haber sido destruidos.
dumb-init le permite simplemente prefijar su comando con dumb-init . Actúa como PID 1 e inmediatamente genera su comando como un proceso infantil, teniendo cuidado de manejar adecuadamente y reenviar las señales a medida que se reciben.
Normalmente, cuando lanza un contenedor Docker, el proceso que está ejecutando se convierte en PID 1, dándole las peculiaridades y responsabilidades que conlleva ser el sistema init para el contenedor.
Hay dos problemas comunes que esto presenta:
En la mayoría de los casos, las señales no se manejarán correctamente.
El núcleo de Linux aplica un manejo especial de señal a los procesos que se ejecutan como PID 1.
Cuando los procesos se envían una señal en un sistema de Linux normal, el núcleo primero verificará cualquier manejador personalizado que el proceso haya registrado para esa señal, y de lo contrario retroceda al comportamiento predeterminado (por ejemplo, matar el proceso en SIGTERM ).
Sin embargo, si el proceso que recibe la señal es PID 1, el núcleo recibe un tratamiento especial por el núcleo; Si no ha registrado un controlador para la señal, el núcleo no volverá al comportamiento predeterminado, y no sucede nada. En otras palabras, si su proceso no maneja explícitamente estas señales, enviarlo a SIGTERM no tendrá ningún efecto en absoluto.
Un ejemplo común es que los trabajos de CI que realizan docker run my-container script : el envío de SIGTERM al proceso docker run generalmente matará el comando docker run , pero deje el contenedor ejecutándose en segundo plano.
Los procesos de zombis huérfanos no se cosechan correctamente.
Un proceso se convierte en un zombie cuando sale, y sigue siendo un zombie hasta que su padre llama a alguna variación del sistema wait() . Permanece en la tabla de proceso como un proceso "desaparecido". Por lo general, un proceso principal llamará wait() inmediatamente y evitará zombis de larga duración.
Si un padre sale ante su hijo, el niño está "huérfano" y se vuelve a igualar bajo PID 1. El sistema init es responsable de wait() en procesos de zombis huérfanos.
Por supuesto, la mayoría de los procesos no wait() en procesos aleatorios que se adjuntan a ellos, por lo que los contenedores a menudo terminan con docenas de zombies enraizados en el PID 1.
dumb-init dumb-init se ejecuta como PID 1, actuando como un sistema inicial simple. Lanza un solo proceso y luego los proxies recibieron señales a una sesión enraizada en ese proceso infantil.
Dado que su proceso real ya no es PID 1, cuando recibe señales de dumb-init , se aplicarán los controladores de señal predeterminados y su proceso se comportará como era de esperar. Si su proceso muere, dumb-init también morirá, teniendo cuidado de limpiar cualquier otro proceso que aún pueda permanecer.
En su modo predeterminado, dumb-init establece una sesión enraizada en el niño y envía señales a todo el grupo de proceso. Esto es útil si tiene un niño mal que se puede llevar (como un script de shell) que normalmente no indicará a sus hijos antes de morir.
Esto en realidad puede ser útil fuera de los contenedores de Docker en supervisores de procesos regulares como DaemonTools o Supervisord para supervisar los scripts de shell. Normalmente, una señal como SIGTERM recibida por un caparazón no se reenvía a subprocesos; En cambio, solo el proceso de shell muere. Con tonta inútil, puedes escribir scripts de shell con innovación en el shebang:
#!/usr/bin/dumb-init /bin/sh
my-web-server & # launch a process in the background
my-other-server # launch another process in the foreground
Por lo general, un SIGTERM enviado al caparazón mataría el caparazón pero dejaría esos procesos en funcionamiento (¡tanto el fondo como en el primer plano!). Con la entrada de tonta, sus subprocesos recibirán las mismas señales que hace su shell.
Si desea que las señales solo se envíen al niño directo, puede ejecutar con el argumento --single-child o establecer la variable de entorno DUMB_INIT_SETSID=0 cuando se ejecuta dumb-init . En este modo, la entrada tonta es completamente transparente; Incluso puede unir múltiples juntos (como dumb-init dumb-init echo 'oh, hi' ).
La INIT DUMP permite reescribir señales entrantes antes de poder proxyizarlas. Esto es útil en los casos en que tiene un supervisor de Docker (como Mesos o Kubernetes) que siempre envía una señal estándar (por ejemplo, Sigterm). Algunas aplicaciones requieren una señal de parada diferente para hacer una limpieza elegante.
Por ejemplo, para reescribir la señal Sigter (número 15) a Sigquit (número 3), solo agregue --rewrite 15:3 en la línea de comando.
Para soltar una señal por completo, puede reescribirla al número especial 0 .
Cuando se ejecuta en modo setSid, no es suficiente reenviar SIGTSTP / SIGTTIN / SIGTTOU en la mayoría de los casos, ya que si el proceso no ha agregado un controlador de señal personalizado para estas señales, entonces el núcleo no aplicará el comportamiento de manejo de señal predeterminado (que estaría suspendiendo el proceso) ya que es un miembro de un grupo de proceso orfanado. Por esta razón, establecemos reescrituras predeterminadas en SIGSTOP desde esas tres señales. Puede optar por no recibir este comportamiento reescribiendo las señales a sus valores originales, si lo desea.
Una advertencia con esta característica: para las señales de control de trabajo ( SIGTSTP , SIGTTIN , SIGTTOU ), Dumb-INIT siempre se suspenderá después de recibir la señal, incluso si la reescribe a otra cosa.
Tiene algunas opciones para usar dumb-init :
Muchas distribuciones populares de Linux (incluidas Debian (desde stretch ) y los derivados de Debian como Ubuntu (desde bionic )) ahora contienen paquetes de innovación tonta en sus repositorios oficiales.
En las distribuciones basadas en Debian, puede ejecutar apt install dumb-init para instalar Dumb-Init, al igual que instalaría cualquier otro paquete.
Nota: La mayoría de las versiones de la In-Init de DUMD no están vinculadas a estática, a diferencia de las versiones que proporcionamos (vea las otras opciones a continuación). Esto normalmente está perfectamente bien, pero significa que estas versiones de la In-Init generalmente no funcionarán cuando se copian a otras distribuciones de Linux, a diferencia de las versiones vinculadas por estática que proporcionamos.
Si tiene un servidor APT interno, cargar el .deb a su servidor es la forma recomendada de usar dumb-init . En su DockerFiles, simplemente puede apt install dumb-init y estará disponible.
Los paquetes de Debian están disponibles en la pestaña de lanzamientos de GitHub, o puede ejecutar make builddeb usted mismo.
.deb manualmente (Debian/Ubuntu) Si no tiene un servidor APT interno, puede usar dpkg -i para instalar el paquete .deb . Puede elegir cómo obtiene el .deb en su contenedor (montar un directorio o wget es algunas opciones).
Una posibilidad es con los siguientes comandos en su Dockerfile:
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_amd64.deb
RUN dpkg -i dumb-init_*.debDado que la entrada tonta se lanza como un binario vinculado a estadicalmente, generalmente puede dejarlo en sus imágenes. Aquí hay un ejemplo de hacerlo en un Dockerfile:
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64
RUN chmod +x /usr/local/bin/dumb-init Aunque dumb-init está escrita por completo en C, también proporcionamos un paquete de Python que compila e instala el binario. Se puede instalar desde PYPI usando pip . Primero querrá instalar un compilador C (en Debian/Ubuntu, apt-get install gcc es suficiente), luego solo pip install dumb-init .
A partir de 1.2.0, el paquete en PYPI está disponible como un archivo de rueda prebuilado y no necesita ser compilado en distribuciones comunes de Linux.
Una vez instalado dentro de su contenedor Docker, simplemente prefije sus comandos con dumb-init (y asegúrese de que esté utilizando la sintaxis JSON recomendada).
Dentro de un Dockerfile, es una buena práctica usar la entrada tonta como punto de entrada de su contenedor. Un "EntryPoint" es un comando parcial que se prepara para su instrucción CMD , por lo que es un excelente ajuste para la innovación tonta:
# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT [ "/usr/bin/dumb-init" , "--" ]
# or if you use --rewrite or other cli flags
# ENTRYPOINT ["dumb-init", "--rewrite", "2:3", "--"]
CMD [ "/my/script" , "--with" , "--args" ] Si declara un punto de entrada en una imagen base, cualquier imagen que descienda de ella no necesita también declarar innovación tonta. Pueden establecer un CMD como de costumbre.
Para el uso interactivo único, puede prependerlo manualmente:
$ docker run my_container dumb-init python -c 'while True: pass'
Ejecución de este mismo comando sin dumb-init resultaría en no poder detener el contenedor sin SIGKILL , pero con dumb-init , puede enviarle señales más humanas como SIGTERM .
Es importante que use la sintaxis JSON para CMD y ENTRYPOINT . De lo contrario, Docker invoca un shell para ejecutar su comando, lo que resulta en el shell como PID 1 en lugar de la innovación tonta.
A menudo, los contenedores quieren hacer un trabajo previo al inicio que no se puede hacer durante el tiempo de construcción. Por ejemplo, es posible que desee plantarse algunos archivos de configuración basados en variables de entorno.
La mejor manera de integrar eso con la innovación tonta es así:
ENTRYPOINT [ "/usr/bin/dumb-init" , "--" ]
CMD [ "bash" , "-c" , "do-some-pre-start-thing && exec my-server" ]Al usar Dumb-Init como punto de entrada, siempre tiene un sistema init adecuado en su lugar.
La parte exec del comando bash es importante porque reemplaza el proceso BASH con su servidor, de modo que el shell solo existe momentáneamente al inicio.
La construcción del binario de innovación tonta requiere un compilador de trabajo y encabezados LIBC y predeterminados a GLIBC.
$ make
La In-INIT compilada estáticamente supera los 700 kb debido a GLIBC, pero Musl ahora es una opción. En Debian/Ubuntu apt-get install musl-tools para instalar la fuente y los envoltorios, luego solo:
$ CC=musl-gcc make
Cuando se compila estáticamente con Musl, el tamaño binario es de alrededor de 20 kb.
Utilizamos las convenciones de Debian estándar para especificar dependencias de compilación (busque debian/control ). Una manera fácil de comenzar es apt-get install build-essential devscripts equivs , y luego sudo mk-build-deps -i --remove para instalar todas las dependencias de compilación faltantes automáticamente. Luego puede usar make builddeb para construir paquetes de Debian de entrada tonta.
Si prefiere una compilación de paquetes de Debian automatizado usando Docker, simplemente ejecute make builddeb-docker . Esto es más fácil, pero requiere que tenga Docker ejecutándose en su máquina.