Introducción a Docker y su Seguridad
Introducción a Docker y su Seguridad
1. Introducción
“Docker es una tecnología que ha revolucionado tanto el mundo del desarrollo de aplicaciones como
la implementación de servicios. Nos permite crear entornos aislados y consistentes lo que se traduce
Hoy día es una tarea habitual desplegar aplicaciones con una gran complejidad en su arquitectura.
Estamos hablando por ejemplo de las herramientas necesarias para crearla, sus dependencias,
om
programas auxiliares, librerías, etc. Además, también es importante durante su desarrollo tener la
opción empaquetar nuestra aplicación para poder exportarla a otro departamento, gestionar errores,
pruebas para el equipo de QA, etc. Es decir, estamos hablando del proceso de creación y despliegue
.c
de una aplicación, el cual es bastante complejo debido a todos los componentes que antes se han
mencionado.
pe
La tecnología de Docker o Contenedores (ojo, Docker no es el único sistema de contenedores
eu
existente) nos puede ayudar enormemente en esta tarea. Docker nos permite unificar y empaquetar
una aplicación completa dentro de lo que se llama imágenes (que luego veremos en detalle) para
c
posteriormente, con sólo ejecutar un fichero (un Dockerfile o un Docker-compose), desplegar toda
a.
una arquitectura con todos sus componentes y totalmente funcional. Por ejemplo, Google y su
servicio de correo Gmail (además de otros servicios de Google) utilizan contenedores para gestionar
m
Docker es una tecnología que ha revolucionado tanto el mundo del desarrollo de aplicaciones como
la implementación de servicios. Nos permite crear entornos aislados y consistentes lo que se traduce
ce
en un despliegue de la aplicación rápido, escalable y seguro (aunque veremos más adelante que la
seguridad de Docker es un factor a tener muy en cuenta). En este módulo vamos a ver una
[Link]
1 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
2. ¿Qué es Docker?
Docker es un proyecto, en principio de código abierto, pero con opciones comerciales, basado en el
LXC o Linux Containers (aunque no son lo mismo). Desde el punto de vista técnico, Docker se
encarga de utilizar ciertas características específicas del Kernel de Linux como son los namespaces
(es una capa de abstracción que permite simular que un usuario tenga aislado su entorno y recursos)
y cgroups. También utiliza componentes como libcontainer, LXC y libvirt lo que le permite ejecutar
una de sus principales características: el uso de capas (layers). Estas capas las veremos más en
om
profundidad cuando hablemos de las imágenes, pero son el componente fundamental de la
funcionalidad de Docker. Entre otras, una de las capacidades más importantes de las capas en las
.c
rendimiento, menor espacio en disco, etc. Resumiendo, Docker nos permite crear entornos aislados
pe
dentro del sistema operativo desde el cual ejecutar nuestras aplicaciones sin afectar al resto del host
características, las diferencias son varias entre ambos. La principal es que Docker no requiere de un
c
sistema operativo completo para ejecutarse, lo que implica mayor velocidad y sobre todo, espacio en
a.
disco (la máquina virtual necesita del sistema operativo, funcionalidades, etc muchas de ellas las
cuales nunca llegamos a utilizar). Docker comparte sus funcionalidades con el kernel del sistema
m
operativo anfitrión (Linux) por lo que a la hora de ejecutarse no necesita crear todos los elementos
su
[Link]
2 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Las máquinas virtuales necesitan un hipervisor como podemos ver en la fiura de arriba el cual
gestionará los recursos el equipo para todas y cada una de las máquinas virtuales. Docker en cambio
utiliza el kernel compartido del host y la interacción con los recursos se realiza a través de la CLI o
una API, lo que agiliza la ejecución y facilita la construcción de aplicaciones basadas en Docker.
om
.c
pe
c eu
a.
m
su
ce
[Link]
3 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
La instalación de Docker en un host dependerá del sistema operativo que tengamos. Nosotros vamos
a centrarnos en Linux, en concreto en Ubuntu 20.04. De hecho, para realizar las prácticas y los
ejercicios que veremos a lo largo de este temario es aconsejable utilizar una máquina virtual (con
mínimo 2GB de RAM y 2 vcpu y podemos utilizar por ejemplo Virtual Box) y luego instalar Ubuntu en
ella (Docker funciona perfectamente dentro de una máquina virtual). De esta forma tendremos
control para crear por ejemplo snapshots y volver a dejar la máquina limpia si es necesario. Se
om
recomienda al alumno leer esta documentación y ejecutar los diferentes comandos docker que se
irán mostrando para una mejor comprensión de su funcionamiento. Una vez la tenamos preparada,
.c
La secuencia para instalar Docker en Ubuntu es la siguiente:
$sudoaptupdate
pe
eu
$sudoaptinstallapt-transport-httpsca-certificatescurlsoftware-properties-common
c
/etc/apt/[Link].d/[Link]
su
En este punto habremos notado que tenemos que ejecutar docker con sudo delante para que
-help’.
Para eliminar esta restricción y poder ejecutarlo sólo con el comando docker:
sudousermod-aGdocker${USER} su ${USER}
[Link]
4 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Docker utiliza comandos directos desde la consola para ejecutar todo tipo de operaciones con los
contenedores. La estructura general de los comandos y sus parámetros vamos analizarla dentro de
una sencilla sentencia de ejecución de un contendor para obtener la salida por consola de “¡Hola
Mundo!”:
om
.c
pe
c eu
a.
m
su
Si ejecutamos este comando en un entorno limpio, con docker recién instalado, veremos una salida
Unable to find image ‘ubuntu:latest’ locally latest: Pulling from library/ubuntu ed02c6ade914: Pull
complete
Digest: sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7 ac
[Link]
5 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Unable to find image ‘ubuntu:latest’ locally latest: Pulling from library/ubuntu ed02c6ade914: Pull
complete
Digest: sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7 ac
Nos indica que la imagen no se encuentra en local, es decir, descargada. Por lo tanto, Docker tiene
que descargarla desde el repositorio de Docker o Docker Hub (más adelante veremos más detalle
om
sobre este recurso). Finalmente vemos que se realiza la operación pull y nos devuelve el Digest o
.c
La segunda parte es la salida de la ejecución:
“Hola Mundo”
pe
Ya hemos ejecutado nuestro primer contenedor en Docker.
eu
Vamos a ver a continuación una serie de comandos sencillos para familiarizarnos con docker. No es
$ docker version
m
su
ce
Figura 2. Salida del comando docker versión donde podemos ver qué versión tenemos de Docker
[Link]
6 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Vamos a ver ahora qué contenedores tenemos. Si antes hemos ejecutado el ejemplo de “Hola
docker container ls
Dos consideraciones sobre esta ejecución. La primera es que hemos introducido el contexto, es
decir, qué objeto vamos a manipular. En este caso, contendores (container) con el comando ls que
om
simplemente lista los contenedores que hay en el sistema. La segunda consideración es que vemos
que no aparece ninguno. Esto es debido a que tanto el comando ls como el comando ps (también
para listar contenedores) sólo muestran en principio aquellos que están parados, detenidos, es decir,
.c
no están en ejecución. Para poder verlos necesitamos añadir el parámetro “-a”:
docker container ls -a pe
c eu
Figura 3. Salida del comando docker versión donde podemos ver qué versión tenemos de Docker
a.
CONTAINER ID: d4634506c5c9. Estos son los primeros bytes del sha256 identificativo de este
su
COMMAND: “echo “Hola Mundo””. Comando que se ha ejecutado dentro del contenedor
STATUS: Exited (0) 13 minutes ago. Estado, en este caso vemos que hemos salido del mismo y
le especificamos uno en concreto. De esa forma podemos usar este identificador en vez del
sha256. Luego veremos más adelante la importancia de poner nombres descriptivos a nuestros
contenedores.
[Link]
7 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Vamos a ejecutar nuestra primera aplicación Docker. Luego veremos más en detalle su
aplicaciones en Docker. En concreto vamos a levantar una web con nginx con un solo comando:
Podemos ver que al igual que antes, al no tener la imagen de ngnix en local, docker se encarga de
descargarla. También vemos al final de la descarga como esta vez no hay una salida por pantalla,
om
sino que esta vez vemos un sha256 completo que corresponde al contenedor que se ha ejecutado.
82ad5a7abc9b19c2f85ab21bcdbae6887ec8056ddbe2abe060efe5f6e1fdcafb
.c
pe
c eu
a.
Figura 4. Ejecución de una web con nginx desde docker y su salida final
En este comando hay varios parámetros interesantes. El primero es –name mi_nginx. De esta forma
m
le damos un nombre propio definido por nosotros al contendor para poder identificarlo más
su
fácilmente. Otro parámetro interesante es el “-d” ya que le está indicado a docker que no se conecte
tenemos el comando -p 8080:80 que nos está indicando que esta aplicación usará el puerto 80 en el
contenedor y el host lo mapeará por el 8080. Para comprobar que está funcionando, simplemente
[Link]
8 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 5. Comprobación que nginx está en ejecución y listo para ser configurado
Con una sola línea de comando ya tenemos un servicio nginx operativo en nuestro host. Ahora si
.c
comprobamos los contenedores veremos que ya aparece este en ejecución:
pe
eu
Figura 6. Listado de los contendores en ejecución. Para verlos todos, añadir “-a”
Otro punto interesante es comprender qué es un contenedor docker dentro del contexto Linux. Y la
c
respuesta es sencilla: es un proceso más. Es decir, podemos gestionar estos procesos igual que si
a.
fuera cualquier otro del sistema. Por ejemplo, vamos a levantar otra aplicación, en este caso una
Si ahora desde la línea de comandos Linux hacemos una búsqueda de procesos con el término
[Link]
9 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Podemos observar que el contenedor docker no es más que un proceso más del sistema Linux, en
concreto uno con el PID 14716, al cual le podemos aplicar cualquier tipo de operaciones que
.c
3.2. Elementos de Docker
pe
El siguiente esquema muestra los componentes principales de Docker:
c eu
a.
m
su
Cliente Docker (Docker Client). Se encarga de gestionar las operaciones realizadas sobre
contenedores.
Demonio Docker (Docker Daemon). Se encarga de gestionar los contenedores y los servicios
Índice de imagenes Docker (Docker images index) o Registro Docker (Docker Registry). es
donde se almacenan las imágenes Docker y este puede ser un repositorio público o privado.
Imágenes Docker (Docker images). Contiene las imágenes que se utilizan como base.
[Link]
10 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Dockerfiles. Conjunto de scripts que contienen los comandos necesario para automatizar la
IMPORTANTE: hay que aclarar dos términos que suelen llevar a confusión en Docker, la diferencia
entre imagen y contenedor. Un contenedor necesita de una imagen para existir, es prácticamente
como decir que un contenedor es una instancia de una imagen. La imagen es la fuente desde las cual
se crean contenedores y esta es de sólo lectura (la imagen principal). A medida que hacemos
modificaciones sobre la imagen base vamos creando capas que luego podemos agrupar para crear
om
una nueva (eso lo veremos más adelante). En resumen, la imagen es el elemento que contiene todas
las características que queramos asignar a los contenedores y estos se crean partiendo de la imagen.
.c
3.3. Más comandos Docker
pe
Antes de profundizar más en los contenedores y sus componentes, vamos a ver algunos otros
eu
comandos útiles un poco más avanzados que usaremos de manera habitual durante las prácticas con
Docker.
c
Antes hemos visto que necesitamos de una imagen base para poder crear nuestros contenedores.
a.
Estos se almacenan en el Registro Docker (que luego veremos más en profundidad) pero ahora
m
vamos a ver como podemos buscar una imagen directamente en este registro. Por ejemplo
[Link]
11 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Desde este listado podemos seleccionar aquella imagen que necesitemos en nuestro proyecto.
Prácticamente hoy día existen imágenes para casi cualquier tipo de proyecto. Podemos por ejemplo
Por ejemplo, si por cualquier motivo necesitamos una versión concreta de Python, Apache,
om
$ docker search python3.6
.c
$ docker search wordpress
pe
De esta forma nos ahorramos el tener que crear nuestra propia imagen desde cero, ya que estas
tienen los componentes necesarios para levantar un contenedor con ese software específico.
eu
Una vez que ya tenemos nuestra imagen localizada, podemos descargarla. Esto nos ahorraría tiempo
a la hora de crear nuestro contenedor. Antes hemos visto el ejemplo de Nginx, que al no tener la
c
imagen Docker tiene que descargarla. Si tenemos claro qué imagen vamos a utilizar, podemos
a.
descargarla previamente con el comando “pull”. Por ejemplo, si queremos descargar la imagen de
Ubuntu:
m
[Link]/library/ubuntu:latest
Antes vimos cómo listar los contenedores usando el comando “ls”, pues con las imágenes podemos
docker image ls
[Link]
12 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
IMPORTANTE: si nos fijamos en el tamaño de la imagen de Ubuntu vemos que ocupa muy poco,
cuando una imagen de Ubuntu suele ocupar varios GB. ¿Cuál es el motivo? Antes hemos hablado que
Docker comparte los recursos del host, por lo tanto si el host tiene un Linux, sólo necesitará aquellos
ficheros principales de la distribución Ubuntu para poder funcionar. Todos los recursos del núcleo /
om
Las columnas que podemos observar tienen la siguiente información:
.c
TAG. Es la etiqueta, la cual se asocia a la versión (esto lo veremos más adelante)
IMAGE ID. Es el código SHA256 que ya conocemos que esta vez se asigna a una imagen como
[Link]
13 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Ya hemos visto algunos comandos y cómo podemos ejecutar nuestro propio contenedor desde la
línea de comandos. Ahora vamos a ver cómo podemos crear un contenedor, configurarlo como
queramos y a partir del mismo, crear una imagen para poder ejecutarlo con todas las
funcionalidades que le hemos añadido. Como ejemplo usaremos dos programas muy sencillos:
cowsay y fortune. Cowsay es un programa de consola el cual simplemente dibuja una vaca en
formato ASCII pero nosotros vamos a dibujar algo más interesante: un dragón. Y para que ese
om
dragón tenga algo que decir, le pasaremos como parámetro la salida de otro programa: fortune. Este
programa también en línea de comandos y texto, simplemente devuelve una frase aleatoria, igual
que las galletas de la fortuna (de ahí el nombre). Si combinamos ambos, veremos un dragón con el
.c
texto de fortune.
pe
c eu
a.
m
su
Dejando de lado la broma de cowsay y fortune, estos dos ejemplos son realmente interesantes ya que
al fin y al cabo lo podemos sustituir por los programas que queramos. Tenemos un programa que
ejecuta una aplicación (cowsay) y otro que genera datos (fortune) cuya salida pasamos como
parámetro al otro. Esto es una operación muy habitual, es decir, ejecutar un programa concreto
usando parámetros.
En primer lugar, vamos a levantar un contendor utilizando como base esta vez una Debian. Ahora
veremos que no sólo levantamos el contendor, además vamos a conectarnos al mismo para poder
manipularlo:
[Link]
14 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Aquí hay varios parámetros importantes que tenemos que explicar antes de continuar. Vemos el
parámetro “-it”, el cual quiere decir “interactive” y “tty” (it) lo que significa que queremos
nombre de host al contenedor. Esto es importante a la hora de identificar dónde estamos conectados
en qué momento. Cuando tenemos muchos contenedores y estamos conectados a ellos esto será muy
om
importante. Finalmente, después de la imagen vemos que el comando que queremos ejecutar es
simplemente “bash” para que nos abra una consola de comandos. Una vez ejecutado estaremos
dentro del contenedor y si ejecutamos el comando hostname veremos el nombre que le hemos
.c
asignado:
pe
root@cowsay-dragon-container:/# hostname cowsay-dragon-container
Perfecto, pues ahora que estamos dentro del contenedor es hora de ir instalando todos los elementos
eu
que necesitamos, es decir, actualizar el sistema e instalar cowsay y fortune:
c
kB in 2s (5363 kB/s)
Reading package lists... Done Building dependency tree... Done Reading state information... Done All
El siguiente paso será instalar las aplicaciones que necesitamos (la salida está truncada por su
longitud):
[Link]
15 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Note, selecting ‘fortune-mod’ instead of ‘fortune’ The following additional packages will be installed:
perl-modules-5.32
om
Suggested packages:
gnu-perl
.c
| libterm-readline-perl-perl make libtap-harness-archive-perl The following NEW packages will be
installed: pe
eu
cowsay fortune-mod fortunes-min libgdbm-compat4 libgdbm6 libperl5.32 librecode0 libtext-
perl-modules-5.32
a.
0 upgraded, 11 newly installed, to remove and not upgraded. Need to get 8032 kB of archives.
m
…
su
[Link]
16 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
.c
pe
Figura 11. Ejecución de cowsay dragón desde dentro del contenedor donde vemos la pipe que
programa. Ahora es el momento de convertir este contenedor en una imagen, para así no tener que
a.
volver a ejecutar todos esos pasos cada vez que levantemos un contenedor. Con la nueva imagen ya
estarán todos estos programas y las actualizaciones preparadas para ser usadas por el contenedor.
m
Para crear la imagen, usamos el siguiente comando (antes salimos del contenedor con el comando
“exit”):
su
sha256:577b5fcab720faa29764b27d8012ed0ef4c326ba8c00ad4b8db2c07f78cc69b5
El comando utilizado es “commit”, el cual crea una imagen partiendo de un contenedor. En nuestro
dragon-image”. Hemos puesto este nombre para ver cómo es posible usar caracteres como “/” en el
$ docker image ls
[Link]
17 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Mundo!”
.c
pe
c eu
a.
Figura 13. Ejecución del dragón ASCII con el texto que queramos desde un contendor levantado con
Genial, ya sabemos la forma de crear nuestra propia imagen partiendo de un contenedor en el cual
su
instalamos y configuramos todo lo que necesitamos. Pero tenemos un problema a la hora de hacer
cualquier modificación ¿qué ocurre si queremos usar por ejemplo otra imagen base, por ejemplo
ce
Ubuntu? Pues que tendremos que descargarla, levantar un contenedor, volver a instalar los
programas necesarios y finalmente crear una nueva imagen partiendo de ese contenedor. La
solución a este problema se encuentra en una funcionalidad muy útil de Docker, los ficheros
Esto es muy ágil para ir probando, pero irá llenando nuestro equipo con contenedores de diferentes
nombres que realmente hacen la misma función. Para evitarlo por eso aconsejamos y recomendamos
utilizar –name, de esta forma cuando quieras levantar el contenedor, tendrás la obligación de borrar
[Link]
18 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
.c
pe
c eu
a.
m
su
ce
[Link]
19 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Para solucionar el problema que antes hemos visto referente a la creación de imágenes con
“commit” partiendo de un contenedor (ojo, esta opción puede ser útil para ciertos casos) vamos a
utilizar los ficheros Dockerfile. El fichero Dockerfile no es más que un fichero de texto donde
podemos automatizar, a base de diferentes comandos, todos los componentes necesarios para crear
una imagen. La ventaja respecto a la forma que vimos en el anterior apartado es que con el fichero
Dockerfile, podemos modificar y añadir todos los pasos que necesitemos sin tener que volver a
om
ejecutarlos para poder generar la imagen.
.c
5.1. Comandos Dockerfile
pe
Vamos a repasar algunos de los comandos más importantes del Dockerfile para luego aplicarlos a la
continuación de la construcción de nuestra imagen del cowsay dragón. Hay que tener en
eu
consideración que cada comando dentro del Dockerfile genera lo que llamamos una capa (layer) lo
que incrementa el tamaño de esta, pero luego lo veremos en profundidad en el apartado que se
c
FROM
m
Este comando nos permite definir qué imagen base es la que vamos a utilizar a la hora de generar la
nueva. Este comando es obligatorio (debe de existir un FROM dentro de un Dockerfile) y debe de ser
su
el primero en el orden de ejecución (luego veremos que existe otro que podemos colocar antes). Por
ejemplo, si queremos utilizar una imagen base de nginx escribiremos: FROM nginx
ce
RUN
Este comando nos permite ejecutar instrucciones desde la imagen, es decir, si usamos una imagen
con Linux nos permitirá ejecutar comandos Linux de todo tipo. Existen dos formas de ejecutar el
comando RUN.
Linux: /bin/sh -c
[Link]
20 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Windows: cmd /S /C
En esta forma los parámetros de RUN son interpretados como un vector JSON, por lo que no olvides
las dobles comillas y sobre todo indicar con doble barra invertida en caso de ser necesario (esto se
om
llama “escapar” en el argot). En el caso de usar el CMD de Window esto es muy importante:
.c
La segunda forma es más directa, ya que sólo tenemos que escribir el comando Linux a ejecutar
pe
Podemos perfectamente concatenar comandos RUN usando tuberías (o pipes) o con el parámetro
eu
&& para ahorrar texto y ejecución en el Dockerfile (además de minimizar el número de capas de la
imagen final). Importante, Docker sólo considera el último de los comandos, es decir, si dentro de un
pipe falla alguno del centro pero el último es correcto, Docker considerará que ha sido correcto.
c
Esto es importante a la hora de hacer una depuración de los posibles errores que encontremos en la
a.
generación de imágenes.
m
CMD
su
Podemos decir en general que el comando CMD nos permite crear valores por defecto a un
contenedor, como por ejemplo los parámetros de un ejecutable. Importante, debemos seguir las
ce
mismas recomendaciones que antes se ha mencionado en el comando RUN con las dobles comillas y
las dobles barras invertidas. Hay varias formas ejecutar comandos con CMD de forma similar a
RUNpero vamos a centrarnos en la más habitual, donde el comando se pasa como parámetro a la
shell.
Como podemos observar, tanto RUN como CMD ejecutan un comando o una aplicación. La pregunta
entonces es ¿qué diferencia hay entre ellos? Pues la principal es que los comandos ejecutados con
RUN se ejecutan durante la creación de la imagen. En cam bio, los comandos CMD se
[Link]
21 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
ejecutan durante la ejecución del contenedor. Esto es muy importante tenerlo claro para saber
ENTRYPOINT
Este comando nos permite ejecutar y utilizar servicios dentro del contenedor. Por ejemplo, si
queremos tener un servidor web utilizaremos el comando ENTRYPOINT para definir el punto donde
se ejecuta el programa principal que levanta el servicio de la web. En otras palabras, nos permite
om
definir el programa que queremos que arranque al levantar el contenedor. Su forma de ejecución es
similar a RUN y CMD, es decir, podemos especificar el ejecutable, sus parámetros y luego el
comando.
.c
Existen otras formas de utilizar este comando desde otro punto de vista más allá de ejecutar un
pe
servicio al levantar el contenedor. Estamos hablando de ejecutar varias tareas cuando se está
creando el contenedor. Por ejemplo si tenemos servidor de bases de datos, es habitual tener que
crear los usuarios, crear la base de datos o por ejemplo restablecer la contraseña. Pues con el
eu
comando ENTRYPOINT podemos ejecutar este proceso.
c
con ENTRYPOINT
su
ENTRYPOINT se utilizará para definir el último programa o servicio que queremos se ejecute
LABEL
Al igual que tenemos referenciados los contenedores asignándoles un nombre, lo mismo debemos
realizar con las imágenes. Estas deben de tener una serie de metadatos donde introduzcamos la
totalmente vital, ya que nos permitirá en todo momento identificar las imágenes que tenemos como
base de los contenedores en ejecución. El comando LABEL nos permite añadir este tipo de
información.
[Link]
22 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
EXPOSE
Si nuestro contenedor Docker necesita tener algún puerto de escucha (UDP o TCP) durante su
ejecución (runtime), el comando EXPOSE nos permite definirlo. Durante la ejecución del contenedor
tendremos que indicar los puertos que queremos utilizar pero el comando EXPOSE nos permite
definir aquellos puertos que están autorizados a ser utilizados con esta imagen. En el apartado de
redes veremos más en profundidad el control de redes y puertos. Ahora sólo necesitamos saber que
necesitamos este comando para que el contenedor pueda escuchar en el un puerto concreto.
om
ADD
Este comando nos permite copiar ficheros y directorios dentro de la imagen Docker. Tiene como
.c
característica especial que permite que sean tanto locales (los ubicados en la carpeta del host) como
pe
remotos (localizados en una ruta de red o Internet). Podemos utilizar comodines y rutas con paths
absolutos o relativos. También podemos usar el comando WORKDIR que luego veremos, el cual nos
permitirá asignar una carpeta de trabajo y así poder copiar ficheros de una forma más sencilla. Otra
eu
característica especial que tiene el comando ADD es que si copiamos un fichero comprimido (tipo
ZIP o TAR), este se descomprimirá de forma automática. Esto puede ser de gran utilidad, pero
c
COPY
m
Con este otro comando también podemos copiar ficheros y directorios desde el host (no permite
su
copiar ubicaciones remotas como ADD) a la imagen. Este comando, excepto el uso de ubicaciones
COPY tiene una funcionalidad especial que nos permite copiar ficheros entre imágenes temporales
VOLUME
Los volúmenes son un elemento fundamental en el mundo de los contenedores Docker ya que
permiten almacenar información en el disco. Luego veremos en profundidad esta funcionalidad pero
ahora sólo debemos saber que podemos definirlos dentro de la ejecución de un Dockerfile. Es
[Link]
23 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
IMPORTANTE: Docker almacena prácticamente todos sus elementos en una ubicación específica
dentro del host: /var/lib/docker. Si accedemos a esa ubicación, veremos todos los elementos que
hemos estado creando como contenedores, imágenes, etc. Por ejemplo, los volúmenes estarán
estaremos realizando también una copia completa de todos los elementos Docker que tengamos en
nuestra arquitectura.
USER
om
Con este comando podemos establecer el UID y el GID, es decir el nombre de usuario y su grupo.
Este proceso se realiza durante la creación de la imagen o algunos de los comandos CMD, RUN y
.c
ENTRYPOINT. Por lo tanto, podemos crear usuarios o grupos de usuarios dentro del contenedor
para gestionar sus accesos. Recordemos que por defecto Docker ejecuta el contenedor como root (ya
pe
veremos más adelante las consecuencias que puede traer este hecho). La ventaja que tiene USER es
que también nos permite utilizarlo durante todo el proceso de creación de la imagen. Es decir,
eu
podemos por ejemplo crear un usuario que realice algunas operaciones, luego cambiar a root para
que haga otras y finalmente volver de nuevo al root (se recomienda primero ejecutar los comandos
c
WORKDIR
m
La funcionalidad de este comando es muy sencilla a la vez que muy útil, ya que se limita a asignar la
carpeta de trabajo o dicho de otro modo, en la ubicación donde se ejecutarán los comandos como
su
ARG
Antes comentamos que existía un único comando que se puede usar antes de FROM. Vamos a ver
como ARG nos permite definir variables (una o varias dentro del fichero Dockerfile) de usuario para
poder pasarlas como parámetros durante la creación de la imagen. Para esto debemos asignarlas
Donde VAR_NAME es el nombre de la variable y VALUE su valor. Para definir la variable dentro del
[Link]
24 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
FROM wordpress
ARG user=usuario # si no pasamos el valor durante el build tendrá el valor “usuario” ARG versión #
Se utiliza en un proceso llamado “multifase” que veremos más adelante, el cual nos permite
om
STOPSIGNAL
Podemos enviar diferentes señales a un contenedor para detener su ejecución. Esta señal podemos
.c
definirla con este comando STOPSIGNAL. Podemos poner el nombre (Signal Name) o el número
(Number):
HEALTHCHECK
[Link]
25 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Para indicar en qué estado se encuentra el contenedor podemos utilizar esta instrucción
HEALTHCHECK NONE
Con la primera opción evitamos que se herede cualquier otro estado deshabilitando la opción. En la
otra forma observamos que usamos el comando CMD (cuya salida será “0” cuando funciona
om
correctamente y “1” cuando tiene errores) para ejecutar el comando que verificará la salud del
.c
--interval define la frecuencia de la comprobación la cual por defecto es de 30 segundos.
pe
--timeout indica el tiempo de espera para ejecutar la comprobación. Si tarda más de 30
--retries indica el número máximo de intentos fallidos para que el contendor se considere
a.
en modo fallo.
m
SHELL
su
Este comando nos permite seleccionar el el intéprete de comandos que queremos usar por defecto:
ce
Si fuera necesario podemos añadir varios SHELL dentro del fichero DOCKERFILE.
Cuando creamos una imagen partiendo de un fichero DOCKERFILE con el comando build que luego
veremos, se copiarán todas las carpetas y ficheros que se encuentren en la misma ubicación que el
DOCKERFILE. Seguramente cuando levantemos un contenedor con esa imagen no sea necesario que
[Link]
26 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
contenga toda esa información además de ser un posible problema de rendimiento. Para indicar qué
carpetas o ficheros no queremos que se añadan a nuestra imagen tenemos un fichero especial
llamado .dockerignore (cuidado con el punto). Dentro de este fichero de texto podemos utilizar
comodines como por ejemplo el “*” para indicar todos, “!” para excluir, etc (los que ya conocemos en
om
*.txt
*.md
.c
!README*.txt TEMP*
*TEMP
*.exe
pe
eu
El *.txt ecluye todos los ficheros .txt pero el !README*.txt indica que todos menos aquellos que
empiecen por README. Existen más formas de indicar excepciones con este fichero que veremos
c
[Link]/engine/reference/builder/
m
Volvamos a nuestro ejemplo del dragón. Antes creamos la imagen partiendo del contenedor, pero
ce
esta vez vamos a partir desde un fichero Dockerfile. Vamos a crear el siguiente utilizando cualquier
editor de texto:
FROM Debian
Lo guardamos con el nombre Dockerfile dentro de nuestro directorio de trabajo. Vamos a crear
[Link]
27 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Figura 14. Creación de Dockerfile con nano y su localización dentro de la carpeta de trabajo
om
Para crear la imagen ejecutamos:
.c
Al final veremos una salida similar a esta:
pe
c eu
a.
m
su
ce
Vamos a ejecutar ahora un contenedor como ya hemos hecho antes pero ahora con esta nueva
nombre, podemos utilizar caracteres especiales como “/”, esto nos permite crear una gran variedad
de nombres los cuales sean descriptivos y fáciles de recordar e identificar dentro de nuestro
entorno.
[Link]
28 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 16. Salida ejecutando un contenedor con la nueva imagen creada con el Dockerfile
.c
Pero aún podemos mejorar mucho más la ejecución. Por ejemplo, vamos a evitar tener que escribir
pe
la llamada al comando “cowsay”: /usr/games/cowsay -f dragon. Para ello utilizaremos el comando
ENTRYPOINT:
eu
FROM debian
RUN apt-get update && apt-get install -y cowsay fortune ENTRYPOINT [“/usr/games/cowsay”,”-
c
a.
f”,”dragon”]
Como podemos observar, los parámetros que necesitamos los ponemos por separados entre comilla.
m
---> 07d9246c53a6
Step 2/3 : RUN apt-get update && apt-get install -y cowsay fortune
---> 550b5bde6cf6
[Link]
29 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
---> e359f407d2d4
om
Y esta vez, una vez creado el ENTRYPOINT, sólo tendríamos que levantar un contenedor con el
único parámetro del texto que queremos mostrar. Pero antes, como le hemos dado un nombre al
.c
$ docker container rm test-dragon-dockerfile
pe
Ahora ya podemos volver a ejecutar el contendor con ese mismo nombre y como hemos comentado,
eu
sólo haría falta ahora poner como parámetro el texto a mostrar:
Figura 16. Salida ejecutando un contenedor con la nueva imagen pero esta vez con menos
Ahora vamos a darle una vuelta más al proyecto. Hasta ahora no hemos utilizado apenas el
programa “fortune” (que genera frases aleatorias). Para ello vamos a ver otra característica de
Docker y Dockerfile. Esta vez vamos a utilizar un script propio dentro del ENTRYPOINT que
compruebe si hay o no parámetros de entrada a nuestro programa. Para ello usaremos este sencillo
[Link]
30 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
#!/bin/bash
if [ $# -eq 0 ]; then
fi
om
Este sencillo script en Bash lo que hace es comprobar si existe o no un parámetro de entrada. En
caso de no haber ninguno, entonces llama a “fortune” para elija una frase al azar y aparezca junto al
.c
chmod +x [Link]
pe
Ya lo tenemos preparado, así que el siguiente paso será modificar de nuevo nuestro Dockerfile para
eu
que añada este fichero a la imagen y luego ejecutarlo con ENTRYPOINT:
FROM debian
c
a.
RUN apt-get update && apt-get install -y cowsay fortune RUN mkdir /programa
Ahora hemos utilizado el comando COPY para subir el fichero [Link] a la carpeta “programa”
su
que hemos creado con RUN anteriormente. Luego asignamos la carpeta “programa” como lugar de
trabajo y finalmente asignamos el ENTRYPOINT, esta vez apuntando a nuestro script “[Link]”.
ce
---> 07d9246c53a6
Step 2/6 : RUN apt-get update && apt-get install -y cowsay fortune
[Link]
31 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
---> 550b5bde6cf6
---> dbbf15b48a15
om
Step 4/6 : COPY [Link] /programa
---> 3456a0050742
.c
Step 5/6 : WORKDIR /programa
---> ad43e4c56677
c
a.
---> c823634ffeb9
ce
Ahora Podemos levantar un contenedor (no vamos asignarle un nombre ahora) y pasarle o no, algún
parámetro. Si no le pasamos parámetro veremos con aparece el dragón con una frase aleatoria de
“fortune”:
[Link]
32 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Se crea un contenedor con un nombre aleatorio el cual ofrece en la salida la imagen del dragón pero
om
.c
pe
Figura 17. Salida sin parámetros, vemos la frase aleatoria de “fortune”
eu
En cambio, si le pasamos una frase como parámetro:
c
Obtenemos:
m
su
ce
Ahora tenemos ya la visión de cómo crear una aplicación que necesite parámetros de entrada, subir
los ficheros necesarios, definir la ejecución de la aplicación y gestionar cualquier cambio desde el
[Link]
33 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
fichero Dockerfile. De esta forma podemos crear las imágenes de forma sencilla partiendo
únicamente de un fichero de texto (Dockerfile) y los ficheros necesarios que forman parte de la
En el siguiente capítulo vamos a profundizar más en las imágenes, elementos, su gestión y formato.
om
.c
pe
c eu
a.
m
su
ce
[Link]
34 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
6. Imágenes Docker
En este apartado vamos a centrarnos en el componente esencial de Docker: las imágenes. El primer
concepto o elemento de las imágenes que debemos tener muy claro su comportamiento es el de
capa. Ya hemos hablado del concepto de capa en anteriores apartados. Una capa de una imagen
podemos también definirla como una imagen intermedia, una especie de snapshot del estado en ese
momento de la imagen la cual siempre es de sólo lectura. Como cada capa tiene el estado de la
imagen, estas se pueden reutilizar cuando estemos creando contenedores partiendo de ella. Es
om
decir, si una capa realiza una función específica y más adelante en el Dockerfile realizamos la misma
acción, en vez de crear otra capa con su correspondiente aumento de tamaño de la imagen,
.c
pe
c eu
a.
m
su
ce
Figura 19. Contenedores creados a partir de una imagen y sus capas nuevas y compartidas
El bloque marcado con A nos indica las capas base que tenemos en la imagen principal. Estas capas
son inmutables y las heredamos de la imagen BASE. Cuando creamos por ejemplo el Contenedor 1,
este hereda por defecto las capas (marcadas con A, donde vemos la capa BASE, capa 1 y capa2) pero
además se van incorporando nuevas capas a medida que realizamos operaciones con el contenedor o
con el Dockerfile. Estas nuevas capas sólo están disponibles en el contenedor, a no ser que hagamos
un “commit” como ya realizamos en el ejemplo del dragón donde creamos nuestra propia imagen
partiendo del contenedor. Si realizamos el “commit”, las capas añadidas nuevas (B) pasarán a
[Link]
35 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
formar parte de la nueva imagen que creemos (nunca de la imagen principal). Resumiendo, podemos
crear imágenes nuevas partiendo de una imagen BASE realizando modificaciones en el contenedor y
Por ejemplo, si queremos comprobar las capas que tiene la imagen NGINX:
om
.c
Figura 20. Capas de la imagen base de nginx
pe
eu
Podemos ver todas las capas que tiene esta imagen base de nginx. Entre los datos de salida que
c
obtenemos podemos ver la fecha de creación (CREATED) y el comando exacto que se ejecutó en
a.
dicha capa (CREATED BY). Finalmente, y muy importante, podemos ver el tamaño de dicha capa
(SIZE). Se puede observar que hay comandos que no tienen efecto de tamaño sobre la imagen y
m
otros que sí. Por ejemplo, como antes hemos comentado, un apt update o un copy harán que la
Durante la creación de la imagen con un Dockerfile, podemos ver también el proceso de creación de
ce
las imágenes. Por ejemplo, a la izquierda podemos el proceso de creación y a la derecha las
[Link]
36 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
.c
pe
eu
Successfully built e359f407d2d4
6.1. Etiquetado
m
Etiquetar correctamente nuestras imágenes es una buena práctica que debemos de tener siempre en
su
cuenta. Como se puede observar, si no etiquetamos bien la imagen, Docker le asigna el término
“latest” (la última). Este término puede llevar a confusiones sobre todo en entornos de producción,
ce
por este motivo es importante etiquetar las imágenes con versiones específicas. Docker antes de
realizar una descarga de una imagen comprueba que esta no se encuentre en la caché. Por este
motivo, si estamos etiquetas sin versión es importante siempre forzar un “pull” de la imagen para
asegurarnos que esta es la última versión disponible de la misma. Para ver cómo se realiza esta
[Link]
37 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Como vemos, la etiqueta actual es “latest”, vamos a asignarle una versión, por ejemplo la “v1.0”:
Si volvemos a listar las imágenes veremos que ahora en el apartado “TAG” tenemos nuestra versión
om
.c
Figura 22. Imagen etiquetada con “v1.0”
pe
eu
6.2. Publicación
c
a.
Antes hemos hablado del registro Docker (Docker Hub). Este “almacén” de imágenes, tanto de
Docker como privado, es importante para poder tener un repositorio donde volcar nuestras
m
imágenes. Ya hemos visto que al crear una imagen esta se crea en el sistema local, es decir, el
ordenador host. Pero si esta imagen la tenemos que compartir con el resto del equipo o queremos
su
que alguien fuera de nuestra organización tenga acceso, será preciso publicarla. De momento vamos
a ver cómo publicar una imagen en el registro de Docker. Para ello será necesario que tengamos una
ce
[Link]
[Link]
38 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 23. Pantalla de registro a Docker Hub
.c
Una vez registrado nuestro perfil, ya tendremos un nombre de usuario y una contraseña. Para poder
pe
subir imágenes a nuestro repositorio, podemos realizarlo simplemente con el comando “push”. Pero
para que Docker sepa dónde ubicarla, primero tendremos que acceder con nuestras credenciales
Una vez accedamos, cualquier comando “push” subirá la imagen al repositorio. El nombre que le
a.
asignemos es importante. Para que Docker Hub nos permita subirla, será necesario que tenga esta
m
estructura:
su
mi_usuario/mi_imagen
Ejemplo:
ce
franramirez/imagen_docker_pruebas
6.3. Limpieza
Mantener nuestro sistema limpio y bien etiquetado es una tarea fundamental que nos ahorrará más
de un dolor de cabeza. De hecho esta tarea debería de ser periódica en nuestra arquitectura Docker
ya que las imágenes son el elemento que más espacio ocupa dentro de nuestro entorno Docker. Cada
vez que descargamos una imagen con un comando pull, build, etc esta se almacena en nuestro host,
[Link]
39 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
es decir, en el sistema de ficheros local del mismo. Si no prestamos atención, poco a poco podemos
dejar el host sin espacio en disco provocando un gran problema en la arquitectura. Por este motivo
El comando “prune” nos ayudará a mantener el sistema limpio. Este comando por defecto elimina
aquellas imágenes que no estén siendo utilizadas por un contenedor (dangling images). Si
om
$ docker image prune
Se borrarán todas aquellas imágenes Que no tengan asociadas un contenedor. Si queremos borrar
todas las imágenes, incluso aquellas que están asociadas a un contenedor podemos utilizar la flag -a:
.c
$ docker image prune -a
pe
Podemos utilizar filtros, por ejemplo si queremos eliminar aquellas imagen que se han creado hace
eu
más de 15 minutos podemos usar el comando “filter”:
En este enlace puedes encontrar más información del comando prune y filter.
a.
m
Vamos a ver a continuación una lista de recomendaciones a aplicar cuando estemos manipulando
imágenes en Docker:
ce
Usar imágenes pequeñas. Decidir una imagen base pequeña nos permitirá ahorrar espacio en
disco y tráfico de red durante la publicación y descarga de las imágenes. Una de las
distribuciones minimalistas más utilizada es Alpine, la cual lleva el mínimo kernel así como los
Usar imágenes especializadas. Por ejemplo, si estamos trabajando con Tensorflow, es mejor
buscar una imagen que ya lo tenga instalado y preparado en vez de usar una estándar y luego
instalarlo. De esta forma nuestros ficheros Dockerfile siempre serán de un tamaño más
reducido ya que sólo contendrán los añadidos necesarios y no la instalación de todo el software
[Link]
40 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Especificar la versión de la imagen base. Es decir, asignarle una etiqueta descriptiva y evitar el
“latest”. Para ver la importancia que tiene el etiquetado, supongamos que se usamos una
imagen basada en ubuntu:latest, cuya versión de python por ejemplo es la 2.9 y la aplicación
que vamos a ejecutar funciona con python 2.x. Con el tiempo, Ubuntu o la que sea cambia a
una nueva versión de su distribución y deciden que es hora de que python 3.9 se la versión de
por defecto. Una vez que se recree la imagen basado en ubuntu:latest, Docker descargará la
nueva versión y ésta potencialmente hará que la aplicación no se ejecute de forma correcta,
om
por problemas de compatibilidad ya que estamos utilizando la versión 3.9 de Python en vez de
la 2.9.
.c
Reducir el número de instrucciones en el Dockerfile. Ya hemos visto que por cada instrucción
dentro del fichero Dockerfile, Docker creará una capa en la imagen. Esta capa ocupará espacio
pe
y hará que Docker tenga más trabajo a la hora de montar el contenedor. Por este motivo es
aconsejable unir el máximo número posible de comandos dentro la misma línea de ejecución
eu
(por ejempo, usando && como ya hemos visto anteriormente).
c
Eliminar los ficheros que no necesitemos. Si por ejemplo usamos ficheros TAR o TGZ, una vez
Reutilizar imágenes. Si tenemos por ejemplo una aplicación web que funciona bien sobre
[Link]
41 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Alpine (una de las distribuciones más pequeña que existen) y además necesitamos una base de
datos que necesita otra distribución (por ejemplo, Ubuntu), lo ideal sería unificar todo en la
imagen que permite ejecutar la aplicación. Es decir, en este caso sería Ubuntu ya que permite
levantar la aplicación web así como la base de datos. Docker cuando cree la imagen compartirá
la misma imagen en vez de utilizar dos, lo que haría que el espacio de la imagen final se
incrementara.
El orden importa. Ya sabemos que Docker crea una capa por cada instrucción encontrada en el
om
fichero Dockerfile. Antes de crearla comprobará si esta existe para usar la caché. Si hay el más
mínimo cambio en la instrucción, Docker borrará esa caché y creara una nueva capa. Por este
.c
cambios posteriores, para que de esta forma no se invaliden las capas superiores y la creación
Cuando se crea la imagen Docker a partir de este Dockerfile, los ficheros que estén dentro de la
su
carpeta actual se copian en la imagen. Luego vemos que se instalan los requerimientos y ya por
último se ejecuta la aplicación. Si hay cualquier cambio en el código fuente y volvemos a crear la
imagen, Docker volverá a descargar las dependencias que necesita. Para escribirlo de una forma
ce
COPY [Link] .
Podemos ver que hemos creado un COPY con el [Link] en la tercera línea. A continuación,
[Link]
42 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
realizamos la instalación de las dependencias. Y finalmente copiamos los ficheros. La capa donde se
instalan las dependencias es la que marca la diferencia, ya que si este no se ha cambiado (se añade
otra dependencia que faltaba, por ejemplo) cuando se genere la imagen usará la caché. Aunque se
han añadido dos capas más, hemos optimizado tanto el tamaño como la velocidad de creación de la
imagen final. Las dependencias es raro que varíen durante la creación del programa pero el código
om
6.5. Multifase
Esta técnica más avanzada (por ese motivo no vamos a profundizar mucho en ella ya que está más
.c
orientada a la hora de desarrollar aplicaciones, aunque es bueno conocerla y saber que existen por
si no es útil en algún momento) nos permite evitar almacenar paquetes innecesarios en nuestra
pe
iamgen. Esto nos permite ahorrar espacio y tiempo a la hora de construir la imagen. En general, si
necesitamos usar dependencias o algunas herramientas externas especiales para nuestra aplicación,
eu
se recomienda usar Multifase.
Supongamos que tenemos una aplicación escrita en el lenguaje Rust, cuyo código fuente queremos
c
compilar durante el proceso de creación. Una de las opciones sería usar una imagen con Rust
a.
instalado, compilar el código fuente y luego desde este punto ejecutar la aplicación compilada. Esto
m
debería funcionar pero si lo analizamos bien, una vez compilada la aplicación en Rust, ya no
necesitamos el compilador ni ninguno de sus otros componentes. Una opción sería desinstalar Rust
su
pero esto complicaría bastante la imagen y el Dockerfile. La mejor opción que tenemos en este caso
es la de crear un Dockerfile multifase, en la que la primera fase usamos una imagen de Rust,
ce
compilamos la aplicación, y en la segunda fase usamos una imagen de Alpine, a la que copiamos
desde la imagen usada en la primera fase el fichero compilado a la nueva imagen. De esta forma, nos
quedará una imagen con un tamaño menor y con los ficheros esenciales de Alpine y la aplicación que
En la documentación Docker podemos encontrar algunos ejemplos de esta técnica y una explicación
[Link]
43 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
docker image load Carga una imagen desde un fichero TAR o STDIN
om
docker image ls Muestra las imágenes
.c
docker image pull Descarga (pull) una imagen o un repositorio desde un registro
pe
docker image push Sube (push) una imagen o un repositorio a un registro
eu
docker image rm Elimina una imagen
Podemos obtener la información detallada del contenedor con el comando docker image inspect.
Antes hemos visto que con “history” podemos ver las diferentes capas de ejecución creadas con
ce
Dockerfile. Con “inspect” lo que obtenemos de salida es un fichero JSON con toda la información
técnica de imagen. Es decir, su id, los comandos del Dockerfile, los ficheros subidos, los volúmenes,
[Link]
44 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
.c
pe
eu
Figura 24. Salida JSON con el comando inspect de una imagen Docker
c
a.
m
su
ce
[Link]
45 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
7. Contenedores Docker
Ya hemos trabajado con contenedores en los capítulos anteriores, pero ahora vamos a profundizar
un poco más en su funcionamiento. La gran funcionalidad que tienen los contenedores Docker es la
inmutabilidad. Gracias a estas características Docker nos permite crear cualquier tipo de
pausa), las veces que necesitemos. Así es exactamente cómo funcionan por ejemplo los servicios
om
Para entender mejor cómo funciona un contenedor, vamos a ver el ciclo de vida (resumido) de los
mismos en un gráfico:
.c
pe
c eu
a.
m
su
Este gráfico sólo muestra una parte de todos los posibles estados de un contenedor pero son los
principales. Vamos a ver los estados del gráfico anterior conectados con los comandos Docker
asociados al mismo:
CREACIÓN. docker create / docker run. Con create se crea un contenedor nuevo pero no se
[Link] stop / docker restart. Con stop el contenedor se para su ejecución y con
restart se reinicia.
[Link]
46 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
distinto a la parada donde tiene que reiniciarse. Este estado se usa para retomar el acceso o la
Es importante entender la diferencia entre estados que parecen prácticamente lo mismo. Para ver el
estado y así entender qué diferencia a cada uno ellos, podemos realizar una inspección de los
om
mismos. Por ejemplo, los comandos “start” y “run” pueden parecer lo mismo pero si los ejecutamos y
.c
pe
eu
Aparantemente hacen la misma función pero si listamos cada uno de ellos podemos ver su estado:
c
a.
m
Figura 26. Estado de un contenedor después de ejecutar los comandos run y create
su
ce
En esta sección vamos a ver cómo parar, pausar, arrancar de nuevo o acceder a un contenedor en
ejecución. También veremos cómo podemos conectar con un contenedor que ya se encuentra en
ejecución.
[Link]
47 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
También tenemos la opción “kill” y la diferencia está en la forma que Linux envía la señal de parada
al contendor. Para “stop” la señal enviada es “SIGTERM” y para kill “SIGKILL”. El problema está en
que “kill” podría afectar a los datos que estuvieran en memoria que tuvieran que ser almacenados
om
docker container start sweet_wing sweet_wing
Para pausar y volver a reanudar un contenedor es exactamente igual con el comando “pause” y
.c
“unpause”.
pe
Ya hemos visto cómo crear o ejecutar un contenedor, pero existe un parámetro muy interesante a la
hora de crearlo o ejecutarlo relacionado con el tema de la parada que acabamos de ver. Es posible
eu
que queramos (sobre todo en entornos de producción) tener siempre un contenedor arriba lo
máximo posible. Existe una política de reinicio que podemos utilizar a la hora de crearlo o ejecutarlo
ya sea porque se ha parado de forma manual o por el sistema. Dicha opción es “ --restart” y tiene los
c
siguientes parámetros:
a.
m
no: opción por defecto. El contenedor no se reinicia de forma automática bajo ningún concepto.
on-failure: el contenedor se reiniciará sólo en caso de error, es decir, si el código del proceso
su
parada.
Otro aspect interesante es poder acceder a un contenedor en ejecución, es decir, poder acceder a su
[Link]
48 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
shell. El problema es que, como nos pasaba con nginx, si lo ejecutamos con-d (detach) y luego
queremos acceder lo más probable es que estemos en la salida (stdout) y no podamos interactuar
docker attach. Este comando nos permitirá acceder directamente al contenedor dentro del
docker exec. Con este comando podemos ejecutar los comandos que queramos dentro del
om
Vamos a ver los dos ejemplos, para ello vamos a levantar esta vez el contenedor nginx con los
.c
parámetros completos (puertos):
pe
d7d8bc96c1cbeb8b6c842e6d7c0aa68d229140f9ac3d472ef0d5810f8d8e48e5
eu
Si ahora intentamos conectar con el contenedor con “attach” sólo veremos la salida por stdout que
esté enviando el contenedor. En este caso al tener levantada la web nginx, cuando accedemos
c
iremos viendo la salida, en este caso al acceder por FireFox vemos el evento en la pantalla de la
a.
terminal:
m
su
ce
Figura 27. Salida por stdout al hacer attach a un contendor en ejecución, en este caso con nginx
Entonces ¿cómo podemos interactuar con él? La solución la tenemos con el comando “exec”. Vamosa
[Link]
49 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 28. Ejecución de varios comandos con exec a un contenedor con ngix en ejecución
.c
Como podemos ver primero hacemos listado de las carpetas del contenedor con el comando “ls -a”.
El siguiente comando interactúa directamente con nginx pidiéndole un cheque de su estado con
pe
“nginx -t”. Como podemos ver, el comando exec nos puede ayudar bastante a la hora de interactuar
con un contenedor sin necesidad de conectarnos a su Shell (esto lo haríamos con “attach”).
eu
Enviar información dentro del contenedor es fundamental, ya lo hemos visto a la hora de crear la
imagen con Dockerfile donde subimos nuestro fichero .sh. Pero lo más habitual es también pasar
m
parámetros de configuración del programa que estemos ejecutando. El caso más habitual es por
ejemplo, la configuración de una base de datos, donde tenemos que crear un usuario y una
su
contraseña. La forma más habitual para pasar estos parámetros es utilizando las variables de
entorno del sistema. Podemos crear dichas variables durante la ejecución de Docker.
ce
Tenemos dos formas diferentes para hacerlo. La primera de ella sería utilizando el parámetro “-e”, “-
env” o “-envfile”. Vamos a ver un ejemplo donde levantamos un contenedor Docker con la base de
PASSWORD=mysecretpassword -d postgres
Unable to find image ‘postgres:latest’ locally latest: Pulling from library/postgres 3d898485473e:
[Link]
50 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Status: Downloaded newer image for postgres:latest
c69b0227a5ac5b8000992e31385bbc4a15af36126555a4c3cb7172b2c51ea7a4
.c
En este punto ya temenos en ejecución el contenedor, vamos a ver si se han pasado correctamente
los parámetros por variables de entorno. Para ello utilizaremos el comando “exec” esta vez para
pe
abrir una Shell (el nombre del contenedor no lo hemos asignado, así que por defecto Docker nos
asigna vigorous_euler):
c eu
a.
m
su
ce
[Link]
51 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/postgresql/14/ bin
_=/usr/bin/printenv root@c69b0227a5ac:/#
Podemos comprobar que, al conectarnos y mostrar las variables de entornos dentro del contenedor,
vemos las dos que hemos creado durante su ejecución (marcadas en amarillo). Ahora cuando
ejecutemos el script de PostgreSQL podemos añadir estas que hemos creado para crear la base de
datos con estas credenciales o lo que necesitemos añadir. Si tenemos que añadir muchas más
om
variables de entornos, no es necesario ponerlas todas con el parámetros “-e”, para ello utilizaremos
“--envifle” donde le daremos la ruta de un fichero de texto con las mismas con el formato:
# Comentario
.c
VAR_A = VALOR_A
VAR_B = VALOR_B
pe
eu
VAR_C = VALOR_C
También es interesante conocer que es posible copiar ficheros desde el contendor al host y
c
viceversa. Esta operación es bastante sencilla y nos permite por ejemplo, copiar ficheros de
a.
configuración de aplicaciones:
m
Este comando copia un fichero del contenedor al host, en concreto copia el fichero
referencia primero el contenedor, dos puntos y luego el fichero. La operación inversa es también
inmediata:
Al igual que con las imágenes, también podemos obtener un fichero JSON con toda la información
referente al contenedor, es decir, sus metadatos. Para ello vamos a utilizar el comando inspect junto
[Link]
52 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
la opción --format. Como podemos ver, el comando inspect sólo necesita como parámetro el id del
contenedor:
Al ejecutar este comando obtendremos una salida JSON con toda la información relativa al
contenedor. Aquí se reproduce sólo una parte de la salida debido a la gran cantidad de información
que contiene:
om
[
.c
“Id”: “sha256:f452322161574db5ee6fe878157a4201cf9fef23feb02608c398aa78ee034
0e8”, pe
eu
“RepoTags”: [
“pruebas/cowsay-dragon-dockerfile:latest”, “pruebas/cowsay-dragon-dockerfile:v1.0”
c
a.
],
“RepoDigests”: [],
m
“Comment”: “”,
ce
“Created”: “2022-09-12T13:23:04.326472308Z”,
“Container”: “e8fcf8691db0cc80fd15cd61a1c0d876796f8631c71aef5a0611f71e907ff1ba”,
“ContainerConfig”: {
“User”: “”, “AttachStdin”: false, “AttachStdout”: false, “AttachStderr”: false, “Tty”: false,
“OpenStdin”: false,
[Link]
53 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
“PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin”
],
El siguiente comando que debemos tener en cuenta es logs. Es decir, nos mostrará aquellos logs del
om
$ docker container logs -f vigorous_euler
.c
pe
c eu
a.
Figura 29. Salida del comando logs a un contenedor en ejecución sin el parámetro -f
m
Como podemos observar, aparecen todos los logs del sistema pero se queda esperando pendiente de
-f para que de esta forma sólo muestre los logs hasta ese momento concreto y luego detiene la
ce
ejecución.
Otro comando interesante que nos muestra información de los contenedores es top. Con este
[Link]
54 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Figura 30. Vista de los procesos en ejecución dentro del contenedor, pero desde el host
También es posible que necesitemos saber información sobre el estado del contenedor. Es decir,
consumo de CPU, memoria, procesos, límites aplicados, etc. Podemos comprobarlo utilizando el
comando stats:
Este comando nos permitirá ir viendo en tiempo real el estado y situación de todos los contenedores
om
que tenemos en nuestra arquitectura:
.c
pe
eu
IMPORTANTE: ahora que hemos visto el comando docker stats, vemos una columna con la
a.
descripción “LIMIT” (en MEM USAGE). Efectivamente, es posible limitar tanto el uso de CPU como
m
el uso de memoria de un contenedor para de esta forma, evitar que pueda colapsar de alguna forma
Esto limitará el uso de memoria hasta 256MB de RAM, no dejando en ningún momento que pueda
superar dicha cantidad. También podemos ejecutarlo para limitar el uso de CPU, en este caso al 50%
(0.5) de utilización:
[Link]
55 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
ejemplo, queremos crear nuestra página web con Wordpress y usar una base de datos para
almacenar los datos, será necesario almacenar la información de forma persistente. Recordemos que
cuando borramos un contenedor, los datos que hayamos almacenados en él también se eliminarán.
Por este motivo necesitamos una ubicación donde almacenar nuestros datos. Otro proceso
om
sencillos de compartir.
En Docker hay varias formas de crear espacios compartidos para almacenar todo tipo de
.c
información. Estos espacios se denominan volúmenes. Podemos almacenar datos en el host, en la
pe
c eu
a.
m
En estos casos, directamente montamos un fichero una carpeta del host en el contendor (igual que
cuando montamos una unidad en Linux). De esta forma los ficheros que se encuentren en dicha
nuevo la unidad veremos de nuevo la información. Para montar un fichero o carpeta del host
podemos utilizaremos el comando “mount”. Existe otra opción con -v o – volume pero nosotros nos
quedaremos con la actual de “mount”. Los valores que necesitamos introducir para ejecutar este
comando son:
[Link]
56 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
a5308cd9e6f4ca73270752b2a6899eef3dd01ae61386147e12145842a6582477
Este comando montará la carpeta actual en la cual estemos dentro del host, en un contenedor dentro
.c
de una carpeta llamada “app” en el directorio raíz (si no existe la creará).
pe
Si nos conectamos al contenedor, veremos que la carpeta /app existe y si accedemos, veremos lo que
teníamos en la carpeta desde la cual lanzamos el contenedor (en mi caso, había un Dockerfile y el
eu
fichero [Link]):
c
a.
m
su
ce
A veces queremos que el contenedor no pueda escribir en la carpeta, es decir, dejarla como “sólo
[Link]
57 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Podemos crear todos los puntos de montaje que necesitemos simplemente cambiando el nombre de
la carpeta destino y así tener varias entradas que apuntan al mismo lugar. Debemos de tener mucho
cuidado con este tipo de montajes, ya que estamos dando acceso “root” al contenedor y este podría
om
8.2. Memoria (tmpfs)
.c
Para almacenar datos de manera temporal o simplemente para acceder a ellos de una manera más
rápida, tenemos el tipo de montaje en memoria. Los datos se mantienen en la memoria del host pero
pe
hay que tener en cuenta que si se para el contendor o se reinia el host, estos datos se perderán. La
En este caso no es necesario establecer un “source” ya que será siempre la memoria (tmpfs). Todo lo
a.
que almacenemos en la carpeta /app del contenedor se almacenará en la memoria del host.
m
8.3. Volúmenes
su
Esta es la opción siempre más recomendable a la hora de almacenar datos en Docker. Entre las
ce
[Link]
58 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
$ docker volume ls
Local 79ccc92c20223e67c4b1200a086bbac280397b2cdb94328e70728c8088718a22
local 61451de009155e45bb48c93970f47214f410499c80463806947ff4e0c75f877a
om
Para crear uno nuevo:
.c
pe
c eu
a.
A partir de este punto, cualquier contenedor podría utilizar este volumen como punto de montaje
m
Ejecutaríamos un contenedor donde la carpeta /app apuntaría al nuevo volumen. En otras palabras,
ce
cada volumen es un disco virtual que podemos utilizar para almacenar información. También
docker/volumes. Si copiamos esa carpeta podemos mover los volúmenes de un host a otro o
[Link]
59 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Eliminar volúmenes es muy parecido a los comandos que ya hemos visto para eliminar contenedores.
Si queremos borrar todos los volúmenes que que no se están utilizando (no hay ningún contenedor
om
.c
pe
c eu
a.
m
su
ce
[Link]
60 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
9. Redes en Docker
Una de las características más importantes de Docker es su gran facilidad a la hora de crear nuestra
propia de red de contenedores. Por ejemplo con las redes en Docker podemos compartir recursos,
acceder a ellos desde Internet, conectarlos con otros contenedores, con el host, con otros
contenedores en hosts remotos, etc. Docker nos ofrece una gran cantidad de opciones para
configurar como queramos nuestra red, pero en este curso vamos a centrarnos en las principales.
om
Docker utiliza una colección de servicios y elementos de Linux como los drivers de red, la
configuración local de red del host, los “net namespaces” de Linux (para poder aislar y crear
instancias de los diferentes accesos configurados por y para los contenedores). A modo de
.c
información general, es importante conocer los siguientes componentes Linux asociados a la
creación de redes Docker, ya que estos nos serán de utilidad a la hora de diseñar e implementar una
red Docker: pe
eu
Open vSwitch. Switch programable virtual que permite multicapas, tunneling y automatización
NAT, Network Address Translation. Será necesario para poder acceder a la IP del host, por
a.
Apparmor. Aplicación que permite asignar unos perfiles de seguridad según la aplicación que
su
se ejecute.
Bridge-utils. Estas herramientas nos permiten controlar varias tarjetas de red y realizar
ce
[Link]
61 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Bridge:esta será la red de docker y está asociada la interfaz virtual llamada “docker0”. Aquí se
conectarán por defecto todos los contenedores. Docker crea siempre por defecto una interfaz
de red virtual de este tipo “bridge2”, la cual actúa de modo similar a un proxy. El rango de IP
sería: [Link]/24.
Host:este tipo de red se asocia directamente con la tarjeta de red “eth0” del host. Es decir, el
None: este modo es equivalente al interfaz loopback (lo), es decir, el contenedor no tendrá
om
ninguna red asociada (este modo es perfecto cuando queremos aislar totalmente un
contenedor).
.c
Para mostrar las redes que tenemos en nuestro sistema:
$ docker network ls
pe
c eu
a.
m
Hay que tener en cuenta que cuando Docker crea un contenedor, este crea dos interfaces asociadas
al mismo. La primera es una interface virtual que se crea en el host la cual tendrá una IP dentro del
su
rango de subred que antes hemos visto generada por docker0. El nombre de esta red es “eth0”. La
segunda interfaz que Docker asocia a un contendor creado es “lo” (loopback). Esta dirección sirve
ce
para comunicaciones internas del contenedor a la IP [Link] o localhost. El tráfico de estas dos
interfaces se canaliza a través de una interfaz de red llamada veth (Virtual Ethernet). Como pasaba
con las otras dos interfaces, Docker siempre crea también esta red veth cada vez que se crea un
[Link]
62 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 34. Esquema de la configuración básica de red de un contenedor Docker con todos los
.c
9.1. Creación y gestión de redes en Docker
pe
Docker nos permite crear nuestras propias redes Docker las cuales podemos personalizar, organizar
eu
y gestionar de la forma que sea más conveniente y que se adapte al proyecto que estemos
gestionando.
c
Vamos a crear dos redes tipo bridge y comentaremos cada uno de sus parámetros:
a.
ce084825e9fec38199f82f362c37c528a6ad29d31c441ec3872c6cfa86f275d5
su
prueba2
ce
8d5d2a8e3e9d992058b75dc2e258d7c2a40a81ae84aae11b4fe22cc233d886f9
$ docker network ls
[Link]
63 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Ambas redes como se puede observar en los parámetros de ejecución, tienen su propia subred y
automáticamente se le asignará una IP dentro de ese rango de subred y también utilizará el Gateway
.c
[Link]. Vamos a ver cómo realizar este proceso:
pe
$ docker run --net=red_prueba1 -itd --name=contenedor_nginx nginx
da6da716fdf9e59b8a8250bae8822d5a89131530a97f7823be3874c0a6a553d5
eu
Creamos un contenedor nginx el cual se estará ubicado dentro “red_prueba1” usando el parámetro
c
“--net”. Podemos comprobar que tiene asignada una IP dentro de ese rango además del Gateway
a.
Figura 35. Salida del comando inspect donde destacamos la configuración de red
[Link]
64 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Como se puede observar, la IP asignada ([Link]) corresponde con el rango de subred que
definimos la hora de crearla. Igual ocurre con el Gateway, [Link]. Podemos también asignar un
om
Para eliminar una red, podemos usar el comando “rm” que ya conocemos:
.c
Vamos a ver un ejemplo de conexión entre dos contenedores comprobándolo con un ping:
pe
Levantamos un contenedor con la imagen de Alpine y nos conectamos:
Mask:[Link]
m
[Link]
65 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Vemos que la dirección IP es [Link], la cual corresponde con la subred que definimos para
red_prueba1.
om
/ # ifconfig
Mask:[Link]
.c
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
pe
RX packets:18 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0
eu
carrier:0 collisions:0 txqueuelen:0
Este vemos que tiene otra IP, la [Link]. Si ahora hacemos ping desde el contenedor con la IP
/ # ping [Link]
[Link]
66 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
.c
pe
Figura 36. Ping desde un contendor a otro dentro de la misma red
eu
Podemos inspeccionar también la red y así obtener toda la configuración de la misma:
c
Figura 37. Salida truncada del comando inspect sobre una red Docker
[Link]
67 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
La gestión de los puertos es una tarea asociada directamente con el acceso desde redes. Además, de
los usos más comunes que asignamos a un contenedor suele estar la de servir todo tipo de peticiones
(API, web, etc). Para poder comunicar los servicios todos los contenedores deberían de estar en la
misma red o mapear el puerto o los puertos a puertos del host. Ya hemos visto en algunos ejemplos
om
$ docker container run -d -p 8000:80 nginx
El parámetro “-p” o “--publish” es justo el que usamos para publicar dichos puertos. Como podemos
ver en el ejemplo, 8000 es el puerto del host y 80 es el puerto del contenedor. Como podemos
.c
mapear varios puertos del host a diferentes contenedores, sólo tendríamos que ir cambiando el
pe
puerto del host (siempre y cuando este esté libre). Si queremos levantar 4 servidores nginx:
Si queremos ver qué puertos tiene mapeados un contenedor, podemos ejecutar el siguiente
su
comando:
Las redes Overlay son aquellas que están diseñadas para funcionar donde haya más de un host,
como por ejemplo pueden ser los entornos distribuidos. La utilización de estas redes requiere de la
[Link]
68 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
ejecución de herramientas adicionales de Docker como por ejemplo es Docker Swarm. Docker
Swarm se encarga de crear un clúster de contenedores, el cual podremos gestionar para poder
conectarlo a una red Overlay. Por este motivo el primer paso para poder ejecutar una red Overlay es
crear un clúster con Docker Swarm. Este sería el esquema de conexión de una red de este tipo:
om
.c
pe
Figura 38. Esquema de conexión de una red Overlay
eu
El host A será el principal y la comunicación entre ambos se realizará a través de la red Overlay,
[Link]/24. La implementación de este tipo de redes está fuera del ámbito de este curso.
c
Las redes MacVLAN permiten crear una VLAN con sus propias MAC las cuales se generan de
a.
manera aleatoria. Esta VLAN comunicará los contenedores y conectará con el host usando la interfaz
de red correspondiente.
m
Las redes MacVLAN permiten conectar contenedores sin tener que utilizar la red bridge además de
su
crear diferentes sub-interfaces a medida para interconectarlos de una manera más sencilla y óptima.
En definitiva, permiten aplicar las ventajas de las VLAN al entorno Docker, como por ejemplo utilizar
ce
[Link]
69 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 39. Esquema de conexión de una red MacVLAN
.c
pe
c eu
a.
m
su
ce
[Link]
70 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Las aplicaciones suelen estar compuestas por diferentes componentes. Por ejemplo, un Wordpress
necesita de una base de datos MySQL por ejemplo, etc. En Docker, es recomendable ejecutar cada
Aquí es donde entra en acción la herramienta Compose, la cual nos permite definir aplicaciones
multicontenedores. Los ficheros creados con Docker Compose tienen la extensión YAML y nos
om
Algunas de las características más importante de esta herramienta son:
.c
Entornos aislados en un mismo host.
pe
Sólo se recrean los contenedores que han cambiado
El formato general de un fichero Compose YAML consta de los siguientes elementos principales:
c
a.
version: “3.7”
m
services:
su
...
volumes:
ce
...
networks:
...
El apartado “versión” nos indica la versión del formato Compose, ya que según la versión que
[Link]
71 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Docker Compose debe de haber al menos una etiqueta de “service” (el resto son opcionales). Dentro
de este apartado iremos creando diferentes etiquetas con los nombres de nuestros contenedores. A
su vez, existen diferentes comandos para gestionar los contenedores dentro de este apartado que
El apartado “volumes” no es más que una carpeta compartida en el host, tal y como contamos en el
om
almacenamiento que utilizarán los contenedores de nuestra arquitectura.
.c
parámetros asociados a la red como por ejemplo los puertos expuestos.
pe
Veamos un fichero Docker Compose totalmente funcional para levantar un servidor WordPress:
version: ‘3’
eu
services:
c
database:
a.
data:/var/lib/mysql environment:
su
MYSQL_ROOT_PASSWORD=secret
MYSQL_DATABASE=wordpress
ce
MYSQL_USER=manager
MYSQL_PASSWORD=secret wordpress:
database
volumes:
[Link]
72 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
./target:/var/www/html environment:
WORDPRESS_DB_USER=manager
WORDPRESS_DB_PASSWORD=secret
WORDPRESS_DB_HOST=database ports:
8080:80
volumes:
om
data:
Podemos comprobar los diferentes parámetros y configuraciones. Vemos que que hay dos
.c
contenedores, llamados “database” y “wordpress”. El contenedor “database” contendrá la base de
pe
datos de almacenamiento. Para especificar la imagen usamos el comando “image”, en este caso
usaremos mariadb. El volumen donde estará almacenada esta base de datos es “data:/var/lib/mysql”
y los parámetros de configuración para crearla se pasarán como variables de entornos (igual que
eu
hacíamos con el Dockerfile).
c
El comando “depends_on” indica que antes de ejecutar este contenedor es necesario que el
contenedor “database” esté levantado. También utiliza variables de entorno como parámetros para
m
el acceso a la base de datos. Finalmente publica el puerto 8080 en el host para utilizar el 80 en el
contenedor.
su
El apartado volumes indica qué volúmenes serán compartidos (creados) por el resto de
ce
contenedores, en este caso “data” corresponde al volumen del contendor “database”. Este debe de
ser accesible por el contendor de WordPress para poder conectar y funcionar correctamente. Para
ejecutar nuestra arquitectura WordPress (si no están cacheada las imágenes primero se
descargarán):
$ docker-compose ps
[Link]
73 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Podemos comprobar que tenemos los dos contenedores arriba y funcionando. Si accedemos a la IP
desde el navegador:
om
.c
pe
c eu
a.
m
$ docker-compose stop
$ docker-compose stop
$ docker-compose logs
[Link]
74 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 41. Ejecución del Compose con los logs activados
.c
También es posible escalar los contenedores que tenemos en ejecución, por ejemplo, en caso de
pe
necesitar más accesos o rendimiento de una aplicación. Si por ejemplo necesitamos levantar más
$ docker-compose ps
[Link]
75 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
.c
pe
c eu
a.
m
su
ce
[Link]
76 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Con todo lo aprendido hasta ahora ya podemos empezar a crear nuestros proyectos y arquitecturas.
Pero siempre debemos tener en cuenta buenas prácticas de utilización las cuales nos permitirán
ofrecer una solución de calidad, segura y resiliente a problemas. Vamos a ver algunos
om
11.1. Ejecución de contenedores con usuario no root
Docker ejecuta los contenedores con el usuario root por defecto. Esto puede ser un problema de
.c
seguridad si por cualquier motivo, la seguridad del contenedor es comprometida. Es más, permitiría
realizar una escalada de privilegios y acceder a los recursos que tenemos en el host. Para
pe
solucionarlo, el mejor procedimiento es crear un usuario y un grupo para la ejecución del
contenedor. Esto podemos hacerlo editando el fichero Dockerfile y añadiendo los siguientes
eu
comandos:
FROM ubuntu
c
a.
USER miusuario
Cuando levantemos un contenedor con este Dockerfile, el usuario ya no será root, en este caso sería
su
En cambio, si ahora creamos un contenedor usando el fichero Dockerfile que hemos creado antes,
[Link]
77 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Sending build context to Docker daemon 2.048kB Step 1/3 : FROM ubuntu
---> a7870fd478f4
Step 2/3 : RUN groupadd -r migrupo && useradd --no-log-init -r -g migrupo miusuario
om
---> b5dbe1e28606
.c
---> Running in cbbad46e23fe
desde URLs remotas. Además, también puede desempaquetar o descomprimir el fichero, lo que
permitiría añadir ficheros no comprobados a nuestra arquitectura. Para poder realizar esta
ADD por ejemplo). Vamos a ver un pequeño ejemplo, creamos primero un Dockerfile:
[Link]
78 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
FROM alpine
om
Step 1/2 : FROM alpine
---> a6215f271958
.c
Step 2/2 : ADD [Link] / Downloading
[==================================================>]
10.24kB/10.24kB pe
eu
---> 5fbeb9842cf9
Y ejecutamos un contenedor:
m
su
ce
Figura 43. Ejecución de un Dockerfile con el comando ADD y su descarga remota de ficheros
([Link])
[Link]
79 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
FROM ubuntu
ADD ./[Link] /
Si ahora creamos un contenedor con esta imagen y vemos de nuevo el directorio principal:
om
.c
pe
c eu
a.
Figura 44. Ejecución de un Dockerfile con el comando ADD con un fichero local
m
Vemos que aparecen dos ficheros, [Link] y [Link], que eran los que estaban
Ya vemos en la sección de redes que es posible conectar una carpeta del host a nuestro contenedor.
almacenamiento del host y por lo tanto a toda nuestra arquitectura. Estamos hablando del comando:
net = host
Podemos localizar los contenedores que tienen activada esta opción de red con el siguiente comando
[Link]
80 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
$ docker container inspect $(docker container ls -aq) --format ‘{{ .Id }}:
NetworkMode={{.[Link] }}’’
Podemos definirlo de la siguiente forma (tal y como ya hemos visto en capítulos anteriores):
om
$ docker run -d --restart=on-failure:5 nginx
Podemos localizar aquellos contenedores que tengan estás políticas de reinicio aplicadas de la
.c
siguiente forma:
pe
$ docker container inspect $(docker container ls -aq) --format ‘{{ .Id }}: RestartPolicyName={{
.[Link] }} MaximumRetryCount={{
eu
.[Link] }}’
c
Tenemos muchas herramientas en Docker para acceder a un contenedor. Utilizar SSH podría ser un
m
Cualquier puerto por debajo del 1024 es un puerto privilegiado. Si hemos asigando alguno de estos a
un contenedor, estaremos exponiendo información sensible del sistema. Podemos detectar los
$ docker container inspect $(docker container ls -aq) --format ‘{{ .Id }}: Ports={{
.[Link] }}’
[Link]
81 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
Desde el punto de vista de la seguridad y la optimización, existe una herramienta que es de gran
[Link]
Para poder funcionan es necesario que tengamos la versión 1.13.0 o superior. Esta herramienta
escanea toda nuestra instalación Docker para comprobar todos aquellos problemas comunes de
configuración, así como permisos de carpetas, ficheros, etc. Podemos ejecutarla desde una imagen
om
Docker o directamente descargar el github y ejecutar un script.
.c
docker run -it --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
pe
eu
-v /usr/bin/containerd:/usr/bin/containerd:ro \
c
-v /usr/bin/runc:/usr/bin/runc:ro \
a.
-v /usr/lib/systemd:/usr/lib/systemd:ro \
m
-v /var/lib:/var/lib:ro \
su
-v /var/run/[Link]:/var/run/[Link]:ro \
ce
Una vez lo ejecutemos, veremos cómo nos van apareciendo diferentes comentarios y avisos
Docker.
[Link]
82 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker
om
Figura 45. Ejemplo de salida de Docker Bench
.c
pe
c eu
a.
m
su
ce
[Link]
83 / 83