Tabla de Contenidos
Depurar contenedores
Notas del curso Docker a fondo e Introducción a Kubernetes: aplicaciones basadas en contenedores
En este módulo vamos a ver de qué manera podemos depurar contenedores Docker así como algunas utilidades relacionadas.
Ten presente que con el término “depurar” nos referimos a inspeccionar el contenedor y usar herramientas para solucionar problemas. Usar un depurador para depurar paso a paso los archivos de un contenedor, usar puntos de ruptura, inspeccionar variables y en general, realizar una sesión con un depurador es algo que depende más del lenguaje y del IDE utilizados, no de Docker.
En general, si tu lenguaje de programación admite ser depurado remotamente a través de un puerto, podrás depurar el contenedor. Veremos un ejemplo concreto usando Node.js, pero es extrapolable a cualquier lenguaje que acepte ser depurado remotamente.
Lo que verás en este módulo es:
- Cómo inspeccionar los logs de un contenedor
- Copiar ficheros del contenedor al host, incluso con el contenedor parado
- Crear un contenedor a partir del estado final de otro contenedor
No es una lección muy compleja, pero como siempre: ten presente todo lo que se ha ido comentando a lo largo de lecciones anteriores. Y ante cualquier duda repasa los contenidos previos.
Ver los logs de un contenedor
Examinar los logs de un contenedor es muy sencillo. Basta con usar el comando:
docker logs <id-contenedor>
para ver los logs generados por el contenedor.
En este contexto por “logs” entendemos la salida generada en stderr y stdout. Si el contenedor guarda los logs en ficheros de nada servirá el comando docker logs.
Crear contenedores "compatibles" con docker logs
Lo más sencillo es que el contenedor escriba, o bien en stdout, o bien en stderr. Por ejemplo, usaremos console.log en Node.js, o Console.WriteLine() en una aplicación .Net Core. En general, todo lo que se envíe tanto a stdout como a stderr aparecerá cuando el usuario use docker logs para ver los logs.
Pero a veces eso no es posible. Por ejemplo, cuando se haya creado un programa no interactivo y por lo tanto sin acceso a la consola. En este caso, si el contenedor es un contenedor Linux, siempre es posible crear un symlink de un fichero de log hacia stdout y escribir en el fichero. Algo parecido a lo siguiente en el Dockerfile:
RUN ln -sf /dev/stdout /var/log/nginx/access.log \ && ln -sf /dev/stderr /var/log/nginx/error.log
(Este código está sacado del Dockerfile de la imagen oficial de nginx.)
Por supuesto esto está sujeto a que el sistema operativo que ejecuta el contenedor lo permita. En general, lo ideal es usar stdout, de lo contrario, dependeríamos de encontrar alguna técnica que nos permita mandar los datos allí.
Recuerda, docker logs muestra solo el contenido de stdout y stderr.
Copiar ficheros
Copiar del contenedor al host
La peor de las pesadillas cuando usas Docker es enfrentarte a un contenedor que da un error nada más arrancar. Con docker logs puedes intentar mirar si el contenedor ha generado alguna salida útil. Pero, si el contenedor ha guardado los logs en un fichero de configuración, entonces la cosa se complica.
Se complica porque cuando el contenedor da un error, este termina. Entonces no puedes “abrir una sesión interactiva” contra él usando docker exec, ya que docker exec requiere que el contenedor esté en marcha.
En este caso la solución pasa por usar el comando docker cp que permite copiar ficheros desde el contenedor hacia el host (y viceversa). De este modo, si sabes que el contenedor guarda logs en un fichero, puedes copiar este a tu máquina para analizarlo.
No puedes copiar con docker cp:
- El contenido de volúmenes (incluyendo tmpfs)
- El contenido de algunos directorios privilegiados del contenedor
tmpfs es el nombre que recibe un sistema de almacenamiento en Linux que aparece como un sistema de archivos montado pero usa memoria volátil. Se utiliza para almacenar información temporal.
docker cp es útil, pues, para inspeccionar ciertos ficheros del contenedor. Eso sí, debes saber de antemano qué ficheros quieres copiar del contenedor al host.
Para copiar el fichero /logs/log.txt desde un contenedor al host, el comando es:
docker cp <id-contenedor>:logs/log.txt .
Copiar del host al contenedor
Es útil también poder copiar ficheros del host a un contenedor. Para ello si quieres copiar el fichero foo.txt al directorio /app de un contenedor, la sintaxis es:
docker cp foo.txt <id-contenedor>:/app/foo.txt
Esto funciona incluso si el contenedor está parado (recuerda que puedes reiniciarlo mediante docker start).
Explorar los ficheros de un contenedor
Otro truco útil es el de poder explorar todos los ficheros de un contenedor. Si el contenedor se está ejecutando es fácil: usas docker exec para crear una sesión interactiva contra él y listo.
Pero, ¿qué ocurre si el contenedor ya no está en marcha? En este caso puedes crear una nueva imagen a partir de un contenedor y crear un contenedor de esa imagen que ejecute un intérprete de comandos.
La clave aquí es un comando nuevo: docker commit. Este comando lo que hace es crear una imagen a partir de los cambios realizados en un contenedor. Es decir, existe un contenedor CA, creado a partir de la imagen A. Si este contenedor crea tres ficheros, el resultado de docker commit será una imagen IA' idéntica a la imagen A, pero con los tres ficheros adicionales creados por el contenedor.
Si docker commit se ejecuta contra un contenedor que está en marcha, entonces dicho contenedor se pausa mientras se ejecuta docker commit (para evitar corrupción de datos). Se puede usar el modificador --pause false para evitar esta pausa, pero debes saber muy bien qué estás haciendo.
La sintaxis de docker commit es:
docker commit <id-container-origen> <nombre-imagen>
Una vez la imagen esté creada, puedes crear un contenedor nuevo que ejecute un intérprete de comandos:
docker run <nombre-imagen> /bin/sh
(En este caso se ejecuta /bin/sh pero esto, por supuesto, depende del sistema operativo del contenedor y de la propia imagen de origen).
Cambiar parámetros de la imagen original
Dado que docker commit crea una imagen a partir de un contenedor, no está de más que también pueda aplicar otros cambios adicionales. Por ejemplo, cambiar variables de entorno. En este caso podemos usar el modificador --change y pasar una instrucción de Dockerfile. Esta instrucción será ejecutada sobre la nueva imagen antes de persistirla.
A veces puede interesar el comando inicial (instrucción CMD o ENTRYPOINT), exponer algún puerto (EXPOSE) o alguna otra modificación.
No todas las modificaciones (sentencias Dockerfile) están permitidas en este caso. En general solo aquellas que modifican la “definición” de la imagen (el objeto JSON que representa la imagen). Aquellas sentencias que requieren un contenedor que las ejecute (tales como RUN o COPY) no están soportadas.
Ejercicio propuesto
Antes de nada descarga, desde la tienda de Docker, la imagen dockercampusmvp/crashonstartup:stdout-logs que es la que “rompe” nada más empezar.
Una vez tengas la imagen, haz lo siguiente:
- Crea un contenedor de dicha imagen y comprueba que revienta nada más ejecutarlo
- Revisa los logs y verás que al contenedor le falta un fichero
- Crea el fichero en el host
- Usa
docker cppara copiar el fichero al contenedor - Reinicia el contenedor y verifica que se queda en ejecución
