No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Curso de Docker

Curso de Docker

Guido Vilariño

Guido Vilariño

Deteniendo contenedores correctamente: SHELL vs. EXEC

27/32
Recursos

Aportes 51

Preguntas 10

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

o inicia sesión.

Comandos:
$ docker build -t loop . (construyo la imagen)
$ docker run -d --name looper loop (corro el contenedor)
$ docker stop looper (le envía la señal SIGTERM al contenedor)
$ docker ps -l (muestra el ps del último proceso)
$ docker kill looper (le envía la señal SIGKILL al contenedor)
$ docker exec looper ps -ef (veo los procesos del contenedor)

Shell: Ejecuta el proceso como hijo del shell

FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD /loop.sh

Exec: Ejecuta el comando como principal

FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD ["/loop.sh"]

27. Deteniendo contenedores correctamente: SHELL vs. EXEC
Contexto de la lección SHELL vs. EXEC
Al estar activo o recién crear un contendor, este debería estar ejecutando un proceso principal o main process para mantenerse en funcionamiento. En caso de que el main process se detenga el contendor debería dejar de funcionar.

Docker tiene una manera de manejar los procesos de los contenedores de manera estándar, cuando se manda detener el contenedor con la instrucción
docker stop <name-container>; con esta instrucción Docker manda una señal estándar de linux llamada SIGTERM al proceso; después de un tiempo el proceso deberá detenerse; en caso de no detenerse el proceso enviará otra señal llamada SIGKILL; con esta señal se garantiza que el proceso se ha terminado de manera forzada.

Existen problemas en la terminación de procesos ejecutados en los contenedores, una de las causas está asociado a ¿Cómo se debería declarar en Dockerfile el proceso a ejecutar?

digamos que tenemos estos dos archivos configurados de la siguiente manera

El primero es el dockerfile; sirve para construir nuestra imagen y el segundo es el archivo bash que se ejecutará como un main process o proceso principal del contenedor. Es importante destacar que la línea de CMD ejecuta nuestro archivo bash, a través de un shell, esta forma de expresar esta línea se conoce como shell form y esto genera que al momento de detener un contenedor que esté ejecutando un proceso por shell form necesariamente tendrá que usar un SIGKILL. Para evitar esto, el CMD debe ejecutar nuestro main process de form exec form , es decir nuestro archivo debe estar configurado de esta manera.


La idea de que el archivo esté configurado de esta manera es que de tiempo a que el contenedor procese las solicitudes restantes y pueda hacer un Great full shutdown. Otra nota importante es que los códigos de salida mayor a 128 es el resultado de una salida por un código de excepción.

comando utilizados en clases

  • $ cd ~/docker/avanzado

  • $ docker build -t loop .
    build -t <nombre imagen> <contexto de construccion>
    Construir la imagen se le coloca un tag de loop

  • $ docker run -d --name looper loop
    run -d --name <nombre contenedor> <nombre imagen>
    Crear un contenedor con un nombre basado en una imagen

  • $ docker stop looper
    stop <nombre contenedor>
    Envía la señal SIGTERM al contenedor

  • $ docker ps -l
    Muestra el último proceso ejecutado

  • $ docker kill looper
    Kill <nombre contenedor>
    Envía la señal SIGKILL al contenedor

  • $ docker exec looper ps -ef
    exec <nombre contenedor> ps -ef
    Muestra los procesos del contenedor

en windows, si estan usando Visual studio Code, y al momento de ejecutar:

docker run -d --name looper loop

si no se queda ejecutando el script, puede ser por los codigos de control, que por defecto en visual studio estan en CRLF y debe ser LF para que funcione en windows

En nuestro Dockerfile tenemos

CMD [“/loop.sh”] EXEC FORM
CMD /loop.sh SHELL FORM

La diferencia es que SHELL FORM lo ejecuta como un comando hijo del shell, EXEC FORM lo ejecuta directamente.

En Windows, cambiar en el archivo loop.sh, en el editor de código, en la parte inferior derecha la forma de hacer los saltos de línea: CRLF a LF.

The shell form prevents any CMD or RUN command line arguments from being used, but has the disadvantage that your ENTRYPOINT will be started as a subcommand of /bin/sh -c, which does not pass signals. This means that the executable will not be the container’s PID 1 - and will not receive Unix signals - so your executable will not receive a SIGTERM from docker stop <container>.

Comandos de la clase

Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, RUN [ “echo”, “$HOME” ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: RUN [ “sh”, “-c”, “echo $HOME” ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.

Si no se ejcuta correctamente el contenedor y en los logs dice algo como permiso denegado, en el Dockerfile se debe agregar permisos de ejecución al script loop

FROM ubuntu:trusty
COPY ["loop.sh", "/"]
RUN chmod +x /loop.sh
CMD ["/loop.sh"]
~                                                                                                                     
~                         

Un loop en el min 10:27, este ... este .... este

#Enviar señal sigterm al contenedor
docker stop <container>

#Mostar  último proceso
docker ps -l

#Enviar señal sigkill al contenedor
docker kill <container>

#Ver procesos del contenedor
docker exec <container> ps -ef 

No basta con solo usar el EXEC FORM en su dockerfile tambien su applicacion debe estar lista para hacer un graceful shutdown

Busquen en sus frameworks favoritos la mejor forma de hacer un graceful shutdown

SIGKILL

  • SIGKILL es la mejor forma de matar un proceso. Siempre matará un proceso y lo matará abruptamente, generando un error fatal. SIGKILL siempre debería funcionar. Si no funciona, el sistema operativo ha fallado.
    `

SIGTERM:

  • SIGTERM intenta matar un proceso, pero a diferencia de SIGKILL, puede bloquearse o manejarse de otra manera. Puede considerarse una forma más suave de intentar finalizar un proceso.
    Para la mayoría de los propósitos, SIGKILL será el método más rápido y efectivo para terminar el proceso.

How to kill a process in Linux

Windows user

Excelente clase, suena a pregunta tricky de una entrevista de docker!

En pocas palabras el docker stop pregunta y luego dispara si es el caso y el kill llega disparando

Clase magistral de docker, linux y linea de comandos que fue esta clase, solo queria acotar eso 😁😁.

que hace el flag -ef en el comando $ docker exec looper ps -ef ?

Estructura de los comandos

cd avanzado/loop
nano loop.sh
######loop.sh######
#!/usr/bin/env bash
trap 'exit 0' SIGTERM
while true; do :; done
###################

– Shell: Ejecuta el proceso como hijo del shell

nano Dockerfile
######Dockerfile######
FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD /loop.sh
###################
./loop.sh
docker build -t loop .

docker run -d --name looper loop

docker stop looper

docker kill looper

docker exec looper ps -ef

– Exec: Ejecuta el comando como principal

nano Dockerfile
######Dockerfile######
FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD ["/loop.sh"]
###################
docker build -t loop .
docker run -d --name looper loop
docker exec looper ps -ef

SHELL vs. EXEC

docker ps -l muestra el último contenedor creado incluyendo todos los estados.

Conclusión: Se recomienda usar EXEC

Detener usando Shell (docker stop <nombre_contenedor>): Cuando se utiliza el comando docker stop, Docker envía una señal SIGTERM al proceso principal del contenedor. Esto le da al proceso la oportunidad de realizar tareas de limpieza y apagar correctamente antes de finalizar. Sin embargo, algunos procesos o aplicaciones pueden no manejar adecuadamente la señal SIGTERM y podrían necesitar más tiempo para finalizar. Si el proceso no se detiene en un tiempo razonable (10 segundos por defecto), Docker puede enviar una señal SIGKILL, que fuerza la terminación inmediata del contenedor. Esto podría resultar en pérdida de datos o recursos no liberados.

Detener usando Exec (docker exec -it <nombre_contenedor> bash): Al usar docker exec, estás ingresando a un nuevo proceso dentro del contenedor (en este caso, un shell Bash). Al salir del shell (con exit), el proceso del shell se detiene, pero el contenedor en sí no se detiene. Esto podría ser útil si necesitas acceder al interior del contenedor para realizar tareas de mantenimiento o solución de problemas, sin detener completamente el contenedor y sus servicios.

Esta ha sido una clase bastante sustanciosa, excelente!

exec from vs. shell form

Recordando: Docker asigna al proceso principal de un contenedor el PID_1 y que de este depende la vida del contenedor. Esto por el ciclo de vida de los contenedores. Un SybProceso tendra su propio PID y el PPID que representa el proceso padre que lo llama, siendo este un proceso hijo.

En ocaciones se requiere que termine el proceso para apagar el contenedor y docker tiene medios para pedir ese cierre de forma controlada y la app termine lo que esta haciendo y no forzar el cierre.

Docker para terminar el proceso manda una señal:

  • SIGTERM: Es una señal estandar de linux que pide terminar un proceso, funciona como el Ctrl+C en la terminal. Esta señal se puede enviar mediante el comando docker stop <app>. Pero despues de un tiempo se llamara a al SIGKILL si no se detiene.
  • SIGKILL: Es una señal que garantizara que el proceso se terminara de una forma forzada. Se puede llamar con docker kill <app>.

El proceso principal /bin/sh -c /loop.sh no reenvia las señales a los hijos y por ende no termina el proceso del loop.sh. (Investigar si es asi o si es que el proceso termina pero no el principal)

Para captar la señal hay que tener la app preparada. Por ejemplo la siguiente app de bash revisara que no haya una señal SIGTERM para continuar (con un loop infinito) y si la hay terminara el proceso. Ademas que este en el proceso PID=1.

comandos

docker build -t loop .
docker run -d --name looper loop

# enviar la señal SIGTERM
docker stop looper

# revisar los procesos del contenedor
docker exec looper ps -ef

archivo loop.sh

trap 'exit 0' SIGTERM
while true: do:; done

dockerfile de loop

FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD /loop.sh #SHELL FORM: llamado por shell `/bin/sh -c /loop.sh`

Este intenta detener el proceso con SIGTERM, pero tarda mucho y pasa a usar el SIGKILL.

Esto ocurre porque el proceso main es /bin/sh -c /loop.sh y el subproceso es el ./loop.sh, terminando el subproceso y no el proceso principal.

Para analizar podemos usar el docker ps -l que mostrara el ultimo contenedor. Este mostrara que salio con el codigo 137 Exited (137). Los codigos de salida sobre 128 es el resultado de una excepcion o una señal no manejada correctamente. Entonces 137-128 = 9, entonces la señal que recibio para salir es 9 que es SIGKILL, la señal de linux que mata todo. Si mandamos en lugar del SIGTERM el SIGKILL, terminaria el proceso casi al instante.

Entonces lo que necesitamos para hacer el “Graceful Shutdown” que es un apagado sin que afecte a los usuarios, es modificar la forma de ejecucion que tenemos en el Dockerfile. Donde por ejemplo queremos que un contenedor deje de recibir peteciones de usuarios con un SIGTERM y termine las que faltan y se apague para colocar una nueva version del contenedor.

dockerfile de loop (Correcto)

FROM ubuntu:trusty
COPY ["loop.sh", "/"]
CMD ["/loop.sh"] #EXEC FORM: Se ejecuta directamente

En su lugar usamos el EXEC FORM que tendra como proceso principal el archivo /loop.sh deteniendose el proceso con solo el SIGTERM inmediatamente. El comando asociado sera "/loop.sh" si lo vemos con el docker ps. Y la salida tendra un estatus de Exited (0).

Formas de ejecutar:

  • SHELL FORM: El Shell Form ejecuta el comando como un hijo del proceso shell.

    CMD /loop.sh
    
  • EXEC FORM: Mientras que este lo ejecuta directamente, siendo este su propio proceso.

    CMD ["/loop.sh"]
    

Siempre que tengamos un código de salida mayor a 128 es por una excepción.

Siempre es recomendable usar el formato ‘EXEC’ antes que el formato ‘SHELL’, es decir:

CMD ["/loop.sh"]

Esto siempre debe de estar entre corchetes y con comillas.

Muy interezante esta clase

Si deseas borrar todas las imagenes en uso o no:
docker image rm -f $(docker image ls -aq)

Igualmente para todos los volumenes en uso, primero borras los containers y luego puedes hacer :
docker volume rm -f $(docker volume ls -q)

excelente explicación, docker es una gran herramienta para aprender y utilizar

En mi caso me dio el código de salida 127 que significa el sistema no puede encontrar la ruta especificada., alguna idea del por que me da el error e estado revisando pero no encuentro el error.

Que buena clase, todos estos detallitos valen oro

Supongo que para lograr descubrir esto tuvo que pasar por varias horas de maldecir Docker y buscar porqué sucede jajaja.

A seguir aprendiendo… Violento EXEC

Ejecucion EXEC y SHELL

Al usar cmd "/loop.sh" se ejecuta el comando en "shell form". De esta forma, Docker utiliza el shell que se encuentre configurado en el dockerfile y ejecuta el comando como un subproceso del shell. Es así que el shell queda como proceso principal y el comando como subproceso. Al usar cmd ["/loop.sh"] se ejecuta el comando en "exec form". De esta forma, Docker lo ejecuta como proceso principal.

forzado de contenedores

a lo chirimbolo XD jajajaja!

Así que… entre SHELL vs EXEC, gana EXEC? que buena pelea

😎

Excelente explicacion… Muy buena clase

  • CMD command param1 param2, ejecuta el comando en la shell.
  • CMD ["command", "param1", "param2"], la forma recomendada, ejecuta el comando en la bash del contenedor.

Estoy trabajando en linux y el contenedor no se queda encendido, se apaga inmediatamente. Cómo hago para que quede encendido?

Excelente la diferencia entre exec y shell.

Chirimbolos

“Chirimbolos” jajaja

segun la explicacion de Guido y el aporte del amigo Isaac Guido

asi deberia ser el codigo en formato exec.

no me queda claro.

Esto me recuerda cuando doy refrescar y no carga hasta los 10 minutos jaja. No pasa muy seguido.