0% encontró este documento útil (0 votos)
4 vistas83 páginas

Introducción a Docker y su Seguridad

Docker es una tecnología que permite crear entornos aislados y consistentes para el desarrollo y despliegue de aplicaciones, facilitando la gestión de su complejidad. Utiliza características del Kernel de Linux para ejecutar contenedores, que son más eficientes que las máquinas virtuales al compartir recursos del sistema operativo anfitrión. Este documento también cubre la instalación de Docker en Ubuntu y proporciona ejemplos de comandos básicos para interactuar con contenedores.

Cargado por

yosoyfit.net
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
4 vistas83 páginas

Introducción a Docker y su Seguridad

Docker es una tecnología que permite crear entornos aislados y consistentes para el desarrollo y despliegue de aplicaciones, facilitando la gestión de su complejidad. Utiliza características del Kernel de Linux para ejecutar contenedores, que son más eficientes que las máquinas virtuales al compartir recursos del sistema operativo anfitrión. Este documento también cubre la instalación de Docker en Ubuntu y proporciona ejemplos de comandos básicos para interactuar con contenedores.

Cargado por

yosoyfit.net
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales

[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales


[UDI1545JY] Docker

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

en un despliegue de la aplicación rápido, escalable y seguro”.

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

el contenido y los accesos de los diferentes clientes.


su

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

introducción a Docker con sus diferentes componentes y su aplicabilidad.

[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

imágenes es la funcionalidad de volver atrás o hacer modificaciones además de ofrecer mejor

.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

(equipo que tiene instalado Docker).


eu
A veces se compara de forma errónea Docker con máquinas virtuales. Aunque comparten algunas

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

que necesita una máquina virtual al compartirlos con el host.


ce

[Link]
2 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

Figura 1. Docker vs Máquina Virtual

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

3. Primeros pasos con 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,

abrimos una consola en la terminal.

.c
La secuencia para instalar Docker en Ubuntu es la siguiente:

$sudoaptupdate
pe
eu
$sudoaptinstallapt-transport-httpsca-certificatescurlsoftware-properties-common
c

$ curl -fsSL [Link] | gpg --dearmor | sudo tee


a.

/etc/apt/[Link].d/[Link] > /dev/null


m

$ echo “deb [arch=amd64] [Link] focal stable” | sudo tee

/etc/apt/[Link].d/[Link]
su

$ sudo apt update $ sudo apt install docker-ce


ce

En este punto habremos notado que tenemos que ejecutar docker con sudo delante para que

funcione. Veríamos un mensaje similar al siguiente:

docker:[Link]?. See ‘docker run -

-help’.

Para eliminar esta restricción y poder ejecutarlo sólo con el comando docker:

sudousermod-aGdocker${USER} su ${USER}

Ya deberíamos poder ejecutar docker sin sudo.

[Link]
4 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

3.1. Comandos Docker básicos

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

de pantalla similar a la siguiente:


ce

$ docker container run ubuntu echo “Hola Mundo”

Unable to find image ‘ubuntu:latest’ locally latest: Pulling from library/ubuntu ed02c6ade914: Pull

complete

Digest: sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7 ac

Status: Downloaded newer image for ubuntu:latest “Hola Mundo”

Podemos separar la salida en dos partes. La primera es la siguiente:

[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

Status: Downloaded newer image for ubuntu:latest

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

sha256 el cual es el identificador único de esa imagen de Ubuntu.

.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

necesario entender ahora su funcionamiento, vamos a centrarnos en la estructura de estos.


c
a.

$ 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

Mundo” deberíamos de tener al menos uno:

docker container ls

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

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.

Ahora ya podemos ver el contenedor y su información:


m

CONTAINER ID: d4634506c5c9. Estos son los primeros bytes del sha256 identificativo de este
su

contenedor. Este valor reconoce de forma única al mismo.

IMAGE: ubuntu. Es la imagen desde la cual se ha lanzado el contenedor


ce

COMMAND: “echo “Hola Mundo””. Comando que se ha ejecutado dentro del contenedor

CREATED: 13 minutes ago. Cuándo se creó el contenedor

STATUS: Exited (0) 13 minutes ago. Estado, en este caso vemos que hemos salido del mismo y

por lo tanto no está en ejecución.

PORTS: vació, aquí veríamos si está usando algún puerto específico

NAMES: beautiful_euler. Este es el nombre aleatorio que le asigna Docker al contenedor si no

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

funcionamiento, pero es interesante en este punto ver la potencia y la facilidad de ejecutar

aplicaciones en Docker. En concreto vamos a levantar una web con nginx con un solo comando:

$ docker container run --name mi_nginx -d -p 8080:80 nginx

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.

Pero esta vez está en ejecución.

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

a la máquina sino todo lo contrario, la ejecute y haga un “detach” (desconectar). Finalmente


ce

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

abrimos el navegador que tengamos y vamos a la página localhost:8080:

[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

base de datos redis:


m

docker container run -d redis


su

Obtenemos la siguiente salida:


ce

Figura 7. Ejecución del contenedor con redis

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

“redis”, veremos lo siguiente:

$ ps -ef | grep redis

Figura 7. Ejecución del contenedor con redis

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

conozcamos de procesos en Linux (un kill por ejemplo).

.c
3.2. Elementos de Docker
pe
El siguiente esquema muestra los componentes principales de Docker:
c eu
a.
m
su

Figura 8. Componentes principales de Docker


ce

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

requeridos desde la máquina Host

Í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.

Contenedor Docker (Docker container). Es el entorno de ejecución Docker.

[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

creación de imágenes. Lo veremos en profundidad más adelante.

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

imaginemos que necesitamos encontrar una imagen base con Ubuntu:


su

$ docker search Ubuntu


ce

[Link]
11 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

Figura 9. Diferentes versiones de Ubuntu que podemos encontrar en el Docker Registry

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

buscar imágenes preparadas con versiones concretas de un software o lenguaje de programación.

Por ejemplo, si por cualquier motivo necesitamos una versión concreta de Python, Apache,

Wordpress, etc podemos buscar también en el Registro Docker:

om
$ docker search python3.6

$ docker search apache

.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

$ docker pull ubuntu Using default tag: latest


su

latest: Pulling from library/ubuntu d19f32bd9e41: Pull complete


ce

Digest: sha256:34fea4f31bf187bc915536831fd0afc9d214755bf700b5cdb1336c82516d154e Status:

Downloaded newer image for ubuntu:latest

[Link]/library/ubuntu:latest

Antes vimos cómo listar los contenedores usando el comando “ls”, pues con las imágenes podemos

hacer exactamente lo mismo. Para mostrar las imágenes descargadas:

docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE

[Link]
12 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

ubuntu latest df5de72bdb3b 3 days ago 77.8MB

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 /

kernel son compartidos con el sistema operativo del host.

om
Las columnas que podemos observar tienen la siguiente información:

REPOSITORY. Es el nombre del repositorio o de la imagen que hemos descargado

.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

valor único identificativo.


pe
CREATED. Fecha de la creación de la imagen
eu
SIZE. Tamaño de la imagen
c
a.
m
su
ce

[Link]
13 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

4. Creación de una imagen desde un contenedor

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

Figura 10. Ejemplo de salida de cowsay usando la forma de dragón


ce

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

$ docker run -it --name cowsay-dragon --hostname cowsay-dragon-container debian bash

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

conectarnos al contenedor e interactuar con la consola tty. El parámetro “— name” ya lo conocemos

(asignar un nombre al contenedor). Ahora vemos el parámetro “—hostname” el cual asigna un

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

root@cowsay-dragon-container:/# apt update


a.

Get:1 [Link] bullseye InRelease [116 kB]


m

Get:2 [Link] bullseye-security InRelease [48.4 kB] Get:3


su

[Link] bullseye-updates InRelease [44.1 kB]

Get:4 [Link] bullseye/main amd64 Packages [8182 kB]


ce

Get:5 [Link] bullseye-security/main amd64 Packages [170 kB]

Get:6 [Link] bullseye-updates/main amd64 Packages [2592 B] Fetched 8563

kB in 2s (5363 kB/s)

Reading package lists... Done Building dependency tree... Done Reading state information... Done All

packages are up to date. root@cowsay-dragon-container:/#

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

root@cowsay-dragon-container:/# apt install -y cowsay fortune Reading package lists... Done

Building dependency tree... Done Reading state information... Done

Note, selecting ‘fortune-mod’ instead of ‘fortune’ The following additional packages will be installed:

fortunes-min libgdbm-compat4 libgdbm6 libperl5.32 librecode0 libtext-charwidth-perl netbase perl

perl-modules-5.32

om
Suggested packages:

filters cowsay-off fortunes x11-utils bsdmainutils gdbm-l10n sensible-utils perl-doc libtermreadline-

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-

charwidth-perl netbase perl


c

perl-modules-5.32
a.

0 upgraded, 11 newly installed, to remove and not upgraded. Need to get 8032 kB of archives.
m


su

Vamos a probar la aplicación desde dentro del mismo contenedor:


ce

root@cowsay-dragon-container:/# /usr/games/fortune | /usr/games/cowsay -f dragon

[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

concatena el texto con la salida de la imagen en ASCII del dragón


eu
En este punto tenemos un contenedor que tiene todo lo que necesitamos para ejecutar nuestro
c

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

$ docker commit cowsay-dragon pruebas/cowsay-dragon-imagen


ce

sha256:577b5fcab720faa29764b27d8012ed0ef4c326ba8c00ad4b8db2c07f78cc69b5

El comando utilizado es “commit”, el cual crea una imagen partiendo de un contenedor. En nuestro

caso el contenedor se llama “cowsay-dragon” y el nombre de la imagen será “pruebas/cowsay-

dragon-image”. Hemos puesto este nombre para ver cómo es posible usar caracteres como “/” en el

nombre de la imagen, lo que facilita su organización e identificación posterior. Si ejecutamos el

comando para visualizar las imágenes veremos:

$ docker image ls

[Link]
17 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

Figura 12. Podemos ver la nueva imagen creada “pruebas/cowsay-dragon-imagen”

Ahora es el momento de probarla. Para ello ejecutaremos:

$ docker run --name test-dragon pruebas/cowsay-dragon-imagen /usr/games/cowsay -f dragon “¡Hola

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

nuestra imagen recién creada Docker


m

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

Dockerfile los cuales veremos a continuación.

IMPORTANTE: Docker levanta contenedores con nombres aleatorios si no especificamos el –name.

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

el anterior con el comando: docker container rm nombrecontenedor

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

5. Creando imágenes automatizadas con Dockerfile

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

centra en las imágenes.


a.

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.

La primera de ella se pasa como parámetro la shell del sistema:

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

Ejemplo: RUN [“/bin/sh/,”-c”,”echo hola mundo”]

Ejemplo: RUN [“Powershell”, “Get-Services”, “*”]

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:

Ejemplo: RUN [“c:\\windows\\system32\\[Link]”]

.c
La segunda forma es más directa, ya que sólo tenemos que escribir el comando Linux a ejecutar

directamente sin especificar la shell. Por ejemplo: RUN apt-get update

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.

Ejemplo: CMD echo “Probando dockerfile” | wc –

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

en todo momento qué necesitamos ejecutar en qué entorno, imagen o contenedor.

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

Algunas consideraciones a tener en cuenta:


a.

Cada fichero Dockerfile debe definir al menos un CMD o un ENTRYPOINT


m

Si tenemos la necesidad de usar un contenedor como si fuera un fichero ejecutable lo definimos

con ENTRYPOINT
su

ENTRYPOINT se utilizará para definir el último programa o servicio que queremos se ejecute

en el contenedor. Si necesitamos pasos previos debemos usar el comando CMD.


ce

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

máxima cantidad de información de la misma. Esto en el caso de tener un entorno de producción es

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

también puede ser un problema para la seguridad de nuestra imagen.


a.

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

remotas y la funcionalidad de descomprimir de ADD, ambos funcionan exactamente iguales. Pero


ce

COPY tiene una funcionalidad especial que nos permite copiar ficheros entre imágenes temporales

(esta funcionalidad la veremos más adelante).

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

importante recalcar que el volumen se creará cuando se ejecute el contenedor.

[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

ubicados en la carpeta /var/lib/docker/volumes. Si hacemos una copia de seguridad de esta ubicació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

necesarios de root por último los del usuario).


a.

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

RUN, CMD, COPY, ENTRYPOINT o ADD.


ce

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

durante el proceso de creación con docker build:

docker image build –build-arg VAR_NAME = VALUE

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

Dockefile siempre debemos crearlas antes de usarlas:

FROM wordpress

ARG user=usuario # si no pasamos el valor durante el build tendrá el valor “usuario” ARG versión #

en este caso si no lo pasamos no tendrá un valor asignado ONBUILD

Se utiliza en un proceso llamado “multifase” que veremos más adelante, el cual nos permite

combinar diferentes imágenes base.

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):

Signal Name Number Description


pe
eu
SIGHUP 1 Hangup (POSIX)
c

SIGINT 2 Terminal interrupt (ANSI)


a.

SIGQUIT 3 Terminal quit (POSIX)


m

SIGILL 4 Illegal instruction (ANSI)


su

SIGTRAP 5 Trace trap (POSIX)


ce

SIGKILL 9 Kill(can’t be caught or ignored) (POSIX)

SIGUSR1 10 User defined signal 1 (POSIX)

SIGTERM 15 Termination (ANSI)

SIGSTOP 19 Stop executing(can’t be caught or ignored) (POSIX)

SIGTSTP 20 Terminal stop signal (POSIX

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. Podemos utilizar los siguientes formatos con este comando:

HEALTHCHECK NONE

HEALTHCHECK [OPTIONS] CMD command

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

contenedor como las siguientes:

.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

segundos (por defecto) la comprobación ha fallado.


eu
--start-period es el tiempo que el contenedor necesita para arrancar en case que éste

necesite algún tiempo para levantar algún servicio.


c

--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

SHELL [“executable”, “parameters”]

Si fuera necesario podemos añadir varios SHELL dentro del fichero DOCKERFILE.

5.2. El fichero .dockerignore

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

Linux cuando manejamos ficheros desde la CLI=.

Un posible fichero .dockerignore podría ser el siguiente:

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

más adelante o que puedes consultar en este enlace: [Link]


a.

[Link]/engine/reference/builder/
m

5.3. Creando nuestra imagen desde un fichero Dockerfile


su

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

RUN apt-get update && apt-get install -y cowsay fortune

Lo guardamos con el nombre Dockerfile dentro de nuestro directorio de trabajo. Vamos a crear

ahora una imagen partiendo de esta configuración:

[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:

$ docker build -t pruebas/cowsay-dragon-dockerfile .

.c
Al final veremos una salida similar a esta:

pe
c eu
a.
m
su
ce

Figura 15. Resultado con éxito de la creación de la imagen con build

Vamos a ejecutar ahora un contenedor como ya hemos hecho antes pero ahora con esta nueva

imagen llamad “pruebas/cowsay-dragon-dockerfile”. Por cierto, como se puede comprobar en el

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

$ docker run --name test-dragon-dockerfile pruebas/cowsay-dragon-dockerfile /usr/games/ cowsay -f

dragon “¡Hola Mundo!”

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

Volvemos a crear nuestra imagen:


su

$ docker build -t pruebas/cowsay-dragon-dockerfile . Sending build context to Docker daemon

2.048kB Step 1/3 : FROM debian


ce

---> 07d9246c53a6

Step 2/3 : RUN apt-get update && apt-get install -y cowsay fortune

---> Using cache

---> 550b5bde6cf6

Step 3/3 : ENTRYPOINT [“/usr/games/cowsay -f dragon”]

[Link]
29 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

---> Running in 0e582afe4ad4

Removing intermediate container 0e582afe4ad4

---> e359f407d2d4

Successfully built e359f407d2d4

Successfully tagged pruebas/cowsay-dragon-dockerfile:latest

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

contenedor, debemos eliminarlo para poder reutilizarlo.

.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:

docker run --name test-dragon-dockerfile pruebas/cowsay-dragon-dockerfile “¡Hola Mundo!”


c
a.
m
su
ce

Figura 16. Salida ejecutando un contenedor con la nueva imagen pero esta vez con menos

parámetros gracias a ENTRYPOINT

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

programa en Bash que llamaremos [Link]:

[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

/usr/games/fortune | /usr/games/cowsay -f dragon else

/usr/games/cowsay -f dragon “$@”

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

dragón. Es importante aplicar la flag de ejecución con chmod al fichero:

.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

COPY [Link] /programa WORKDIR /programa ENTRYPOINT [“./[Link]”]


m

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

Volvemos a crear nuestra imagen:

$ docker build -t pruebas/cowsay-dragon-dockerfile . Sending build context to Docker daemon

3.072kB Step 1/6 : FROM debian

---> 07d9246c53a6

Step 2/6 : RUN apt-get update && apt-get install -y cowsay fortune

---> Using cache

[Link]
31 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

---> 550b5bde6cf6

Step 3/6 : RUN mkdir /programa

---> Running in ffa5d24940e0

Removing intermediate container ffa5d24940e0

---> dbbf15b48a15

om
Step 4/6 : COPY [Link] /programa

---> 3456a0050742

.c
Step 5/6 : WORKDIR /programa

---> Running in 429fbe698c8f pe


eu
Removing intermediate container 429fbe698c8f

---> ad43e4c56677
c
a.

Step 6/6 : ENTRYPOINT [“./[Link]”]


m

---> Running in cb3dee0fc06b

Removing intermediate container cb3dee0fc06b


su

---> c823634ffeb9
ce

Successfully built c823634ffeb9

Successfully tagged pruebas/cowsay-dragon-dockerfile:latest

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”:

$ docker run pruebas/cowsay-dragon-dockerfile

[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

con el texto de “fortune” sin tener que introducir nada más:

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

$ docker run pruebas/cowsay-dragon-dockerfile “Hola, ahora si tengo algo de decir”


a.

Obtenemos:
m
su
ce

Figura 18. Salida con parámetros, introduciendo una frase

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

aplicación que vamos a ejecutar (en nuestro caso, el script [Link]).

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,

directamente se reutiliza (una especie de caché). Veamos la siguiente 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

agrupándolo todo nuevo con el comando “commit”.

Por ejemplo, si queremos comprobar las capas que tiene la imagen NGINX:

$ docker image history 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

imagen aumente de tamaño al estar añadiendo ficheros por diferentes vías.


su

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

diferentes capas creadas dentro de la imagen base de Debian:

[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

Successfully tagged pruebas/cowsay-dragon-dockerfile:latest


c
a.

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

acción, vamos a etiquetar la imagen:

[Link]
37 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

Figura 21. Etiquetado de una imagen

Como vemos, la etiqueta actual es “latest”, vamos a asignarle una versión, por ejemplo la “v1.0”:

$ docker tag pruebas/cowsay-dragon-dockerfile:latest pruebas/cowsay-dragondockerfile:v1.0

Si volvemos a listar las imágenes veremos que ahora en el apartado “TAG” tenemos nuestra versión

(Docker siempre crea una copia de la anterior):

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

cuenta con un usuario y contraseña:

[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

desde la línea de comandos:


eu
$ docker login -u user
c

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

es importante borrar aquellas imágenes que no estemos utilizando más.

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

escribimos el siguiente comando:

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”:

$ docker image prune -a --filter “until=15m”


c

En este enlace puedes encontrar más información del comando prune y filter.
a.
m

6.4. Buenas prácticas con imágenes Docker


su

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

comandos básicos necesarios.

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

principal (Tensorflow en este caso de ejemplo).

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

Por ejemplo, en vez de escribir estos comandos en el Dockerfile:


a.

RUN apt-get update


m

RUN apt-get install -y curl


su

RUN apt-get install -y mysql-client

Es más eficiente escribir esto:


ce

RUN apt-get update && apt-get install -y curl mysql-client

Eliminar los ficheros que no necesitemos. Si por ejemplo usamos ficheros TAR o TGZ, una vez

extraído su contenido es aconsejable borrarlo. Si dejamos el fichero original estaremos

añadiendo más tamaño a la imagen final.

Multifase. Se explica más adelante en este mismo capítulo.

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

motivo es interesante escribir primero en el Dockerfile las instrucciones que no requieran

.c
cambios posteriores, para que de esta forma no se invaliden las capas superiores y la creación

de la imagen sea más rápida.


pe
Supongamos que tenemos la siguiente aplicación en Python que utiliza Flask y hemos creado el
eu
siguiente Dockerfile:
c

FROM python:3.5 WORKDIR /app COPY . .


a.

RUN pip install -r [Link] CMD [“python”, “[Link]”]


m

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

más eficiente podemos:

FROM python:3.5 WORKDIR /app

COPY [Link] .

RUN pip install -r [Link] COPY . .

CMD [“python”, “[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

fuente sí que es más normal que esté cambiando continuamente.

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

queremos ejecutar y compilada y lista.

En la documentación Docker podemos encontrar algunos ejemplos de esta técnica y una explicación

más en profundidad de su funcionamiento.

6.6. Algunos comandos útiles para utilizar con imágenes

[Link]
43 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

docker image build Crea una imagen desde un Dockerfile

docker image history Muestra el historial de la imagen o sus capas

docker image import Permite crear imágenes basadas en un filesystem

docker image inspect Muestra información detallada de la imagen

docker image load Carga una imagen desde un fichero TAR o STDIN

om
docker image ls Muestra las imágenes

docker image prune Elimina las imágenes que no estamos usando

.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

docker image save Almacena una imagen a un fichero TAR


c
a.

docker image tag Etiqueta una imagen


m

6.7. Información de las imágenes


su

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,

etc. Para obtener toda esta información podemos ejecutar el comando:

$ docker inspect pruebas/cowsay-dragon-dockerfile:v1.0

[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

arquitectura escalable en la cual se levanta lo que necesita y luego se destruye (o se ponen en

pausa), las veces que necesitemos. Así es exactamente cómo funcionan por ejemplo los servicios

serverless por ejemplo.

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

Figura 25. Ciclo de vida básico de un contenedor Docker.


ce

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

ejecuta, pero con docker run lo creamos y lo ejecutamos.

EJECUCIÓ[Link] contenedor está en ejecución

[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

PAUSA. docker pause / docker unpause. El contenedor se “congela” en el estado actual, es

distinto a la parada donde tiene que reiniciarse. Este estado se usa para retomar el acceso o la

ejecución de un contendor de una forma más rápida para procesos críticos.

DESTRUCCIÓ[Link] kill. Se elimina el contenedor, también podemos ejecutar un docker

rm para la misma función.

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

los inspeccionamos podemos ver sus diferencias:

.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

7.1. Gestionando y accediendo a contenedores

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.

La operación de parada de un contenedor es bastante trivial. Por ejemplo si tenemos un contendor

llamado “sweet_wing”, para pararlo:

[Link]
47 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

$ docker container stop sweet_wing sweet_wing

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

en disco durante la ejecución del proceso/contenedor.

Para volver a levantar el contendor:

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

de salida es diferente a cero.

unless-stopped: esta opción reinicia el contenedor excepto cuando se haya parado


ce

intencionadamente o que haya sido Docker (el demonio).

always: siempre se intentará reiniciar el contenedor, independientemente del motivo de su

parada.

La forma de ejecutar estos comandos a la hora de levantar un contendor es la siguiente (ejemplo

para mantenerlo siempre en ejecución):

$ docker run -d -it –restart always nginx

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

con él. Hay dos formas principales de acceder a un contenedor en ejecución:

docker attach. Este comando nos permitirá acceder directamente al contenedor dentro del

estado en que se encuentre.

docker exec. Con este comando podemos ejecutar los comandos que queramos dentro del

contendor sin acceder al mismo.

om
Vamos a ver los dos ejemplos, para ello vamos a levantar esta vez el contenedor nginx con los

.c
parámetros completos (puertos):

docker container run -d -p 8080:80 nginx

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

ver algunos comandos ejecutados directamente en el contenedor (recordemos que docker ps

muestra sólo contenedores en ejecución, perfecto para localizarlos):

[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

7.2. Paso de información a contenedores


c
a.

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

datos PostgreSQL donde creamos a su vez el usuario y la contraseña:

$ docker container run –rm -e POSTGRES_USER=myuser -e POSTGRES_

PASSWORD=mysecretpassword -d postgres

Unable to find image ‘postgres:latest’ locally latest: Pulling from library/postgres 3d898485473e:

Pull complete 4ab9a79a9772: Pull complete

[Link]
50 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

8116dc73cb3e: Pull complete e8ceb4515aa7: Pull complete 1d6f7eceb1d7: Pull complete

51a94e4c3770: Pull complete 5172abe7cf72: Pull complete 9577c9870fc5: Pull complete

9425594e9d43: Pull complete

b30d770e822b: Pull complete 30016c551793: Pull complete 867e39d2088e: Pull complete

886b710a0016: Pull complete

Digest sha256:b0ee049a2e347f5ec8c64ad225c7edbc88510a9e34450f23c4079a489 ce16268

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

$ docker container cp my-nginx:/etc/nginx/[Link] .


su

Este comando copia un fichero del contenedor al host, en concreto copia el fichero

mynginx:/etc/nginx/[Link] al directorio actual. Es importante notar la forma con la cual se


ce

referencia primero el contenedor, dos puntos y luego el fichero. La operación inversa es también

inmediata:

$ docker container cp [Link] my-nginx:/etc/nginx/

7.3. Información de los contenedores

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:

$ docker inspect pruebas/cowsay-dragon-dockerfile:v1.0

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

“Parent”: “sha256:0b86f7ad1825c886e8d025989eb0fee83a8ea842b22775a6b8750ae5 0b3093e1”,


su

“Comment”: “”,
ce

“Created”: “2022-09-12T13:23:04.326472308Z”,

“Container”: “e8fcf8691db0cc80fd15cd61a1c0d876796f8631c71aef5a0611f71e907ff1ba”,

“ContainerConfig”: {

“Hostname”: “e8fcf8691db0”, “Domainname”: “”,

“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

“StdinOnce”: false, “Env”: [

“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

sistema con PID 1. Si ejecutamos el comando logs a un contenedor en ejecución:

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

mostrar nuevas entradas en el log. Para evitarlo, podemos usar el parámetro


su

-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

comando podemos visualizar los procesos que están en ejecución en el contenedor:

$ docker container top vigorous_euler

[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:

$ docker container stats –all

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

Figura 31. Listado de los contenedores y sus correspondientes estadísticas


c

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

al Host. Para limitar el uso de memoria:


su

$ docker run -it -m 256m ubuntu /bin/bash


ce

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:

docker run -it --cpus=”.5” ubuntu /bin/bash

[Link]
55 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

8. Persistencia de los datos en Docker (Volúmenes)

El almacenamiento es un proceso importante dentro de cualquier tipo de arquitectura. Si por

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

importante es la compartición de esos datos. Los datos almacenados en contenedores no son

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

memoria RAM o volúmenes. Este gráfico resume perfectamente esta clasificación:

pe
c eu
a.
m

Figura 32. Posibles tipos de persistencia en Docker


su

8.1. Sistema de ficheros del host (Bind Mounts)


ce

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

carpeta serán accesibles desde el contenedor y aunque eliminemos el contenedor, si montamos de

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

type: tipo de montaje. Acepta los valores: bind, volume y tmpfs.

source, src: fichero o directorio origen del host.

destination, dst, target: destino dentro del contenedor.

readonly: no se asigna valor alguno. Montaje se hace de sólo lectura.

Vamos a verlo con un ejemplo:

$ docker run -d -it --mount type=bind,source=”$(pwd)”,target=/app ubuntu

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

Figura 33. Carpeta “app” montada a una carpeta del host

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

lectura”. Para ello simplemente:

$ docker run -d -it --mount type=bind,source=”$(pwd)”,target=/app,readonly ubuntu

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

modificar ficheros de algunas carpetas importantes del host.

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

ventaja que tenemos es la velocidad de acceso. Un ejemplo de ejecución sería:


eu
docker run -d -it --mount type=tmpfs,destination=/app nginx
c

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

ventajas que tenemos al usar volúmenes podemos destacar:

Facilitan las copias de seguridad y migrar datos.

Docker tiene comandos y API para trabajar con ellos.

Funcionan tanto en Linux como Windows.

Más fáciles y seguros a la hora de ser compartidos entre contenedores.

Existen controladores de volúmenes que permiten el almacenamiento remoto, en la nube,

encriptado de datos, etc.

[Link]
58 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

Para listar los volúmenes que tenemos, usamos el comando:

$ docker volume ls

DRIVER VOLUME NAME

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

para almacenar datos. Por ejemplo:


su

$ docker container run -d --mount=type=volume,source=nuevo_volumen,destination=/app ubuntu

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

podemos utilizar la opción de readonly como vimos en el capítulo anterior.

IMPORTANTE: Docker almacena la información dentro del host en la carpeta /var/lib/

docker/volumes. Si copiamos esa carpeta podemos mover los volúmenes de un host a otro o

simplemente usarlo de copia de seguridad.

8.4. Borrado de volúmenes

[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.

Por ejemplo, si queremos borrar el volumen anterior:

$ docker volume rune rm nuevo_volumen

Si queremos borrar todos los volúmenes que que no se están utilizando (no hay ningún contenedor

que acceda a él), podemos usar:

$ docker volume prune

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

entre otras características.


c

NAT, Network Address Translation. Será necesario para poder acceder a la IP del host, por
a.

ejemplo y de esa forma dar salida a Internet al contenedor.

IPTables. Firewall integrado de Linux.


m

Apparmor. Aplicación que permite asignar unos perfiles de seguridad según la aplicación que
su

se ejecute.

Iproute2. Esta herramienta nos permite el control y la monitorización de las redes.

Bridge-utils. Estas herramientas nos permiten controlar varias tarjetas de red y realizar
ce

operaciones sobre ellas.

Para instalar estas dos últimas herramientas:

$ sudo apt-get install iproute2

$ sudo apt-get install bridge-utils

Docker nos permite crear tres tipos de redes:

[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

contenedor estará en la misma red que el host.

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

contenedor. En el siguiente gráfico podemos ver más claramente esta configuración:

[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

componentes que antes hemos mencionado.

.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.

$ docker network create --driver=bridge --subnet=[Link]/24 --gateway=[Link] red_prueba1


m

ce084825e9fec38199f82f362c37c528a6ad29d31c441ec3872c6cfa86f275d5
su

$ docker network create --driver=bridge --subnet=[Link]/24 --gateway=[Link] red_

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

también su propio Gateway. Si levantamos un contenedor en la primera de ellas, “red_prueba1”,

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.

mencionado, con el siguiente comando:

$ docker container inspect contenedor_nginx


m

Mostramos sólo la parte de la salida que nos interesa:


su
ce

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

contenedor ya existente y en ejecución a la red que sea necesario utilizando el comando:

$ docker network connect red_prueba2 contenedor_nginx

En este caso pasaríamos el contenedor de la red_prueba1 a la red_prueba2. Para desconectarlo

simplemente usamos “disconnect”.

om
Para eliminar una red, podemos usar el comando “rm” que ya conocemos:

$ docker network rm red_prueba1

.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:

$ docker run -it --init --net red_prueba1 alpine sh


eu
/ # ifconfig
c

eth0 Link encap:Ethernet HWaddr 02:42:0A:01:01:02 inet addr:[Link] Bcast:[Link]


a.

Mask:[Link]
m

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1


su

RX packets:11 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0

carrier:0 collisions:0 txqueuelen:0


ce

RX bytes:1429 (1.3 KiB) TX bytes:0 (0.0 B)

Lo Link encap:Local Loopback

inet addr:[Link] Mask:[Link]

UP LOOPBACK RUNNING MTU:65536 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0

carrier:0 collisions:0 txqueuelen:1000

[Link]
65 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

Vemos que la dirección IP es [Link], la cual corresponde con la subred que definimos para

red_prueba1.

Ahora levantamos otro contenedor de la misma forma en otra CLI:

$ docker run -it --init --net red_prueba1 alpine sh

om
/ # ifconfig

eth0 Link encap:Ethernet HWaddr 02:42:0A:01:01:03 inet addr:[Link] Bcast:[Link]

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

RX bytes:2463 (2.4 KiB) TX bytes:0 (0.0 B)


c
a.

Lo Link encap:Local Loopback

inet addr:[Link] Mask:[Link]


m

UP LOOPBACK RUNNING MTU:65536 Metric:1


su

RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0


ce

carrier:0 collisions:0 txqueuelen:1000

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

Este vemos que tiene otra IP, la [Link]. Si ahora hacemos ping desde el contenedor con la IP

[Link] a la IP [Link], debería de respondernos:

/ # ping [Link]

PING [Link] ([Link]): 56 data bytes

[Link]
66 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

64 bytes from [Link]: seq=0 ttl=64 time=0.248 ms

64 bytes from [Link]: seq=1 ttl=64 time=0.408 ms

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

$ docker network inspect red_prueba1


a.
m
su
ce

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

9.2. Gestión de puertos de los contenedores 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

anteriores cómo se asignan estos puertos del host al contenedor:

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:

$ docker container run -d -p 8000:80 nginx


eu
$ docker container run -d -p 8001:80 nginx
c

$ docker container run -d -p 8002:80 nginx


a.

$ docker container run -d -p 8003:80 nginx


m

Si queremos ver qué puertos tiene mapeados un contenedor, podemos ejecutar el siguiente
su

comando:

$ docker container port clever_stonebraker


ce

80/tcp -> [Link]:8080

80/tcp -> :::8080

9.3. Redes Overlay y MacVLAN

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

trunking para conectar diferentes VLANs.

El esquema de este tipo de conexión sería:

[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

10. Docker Compose

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

de estos componentes en su propio contenedor, para evitar problemas de ejecución y compatibilidad.

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

permite realizar una primera aproximación a lo que se denomina orquestación de contenedores.

om
Algunas de las características más importante de esta herramienta son:

Preserva los volúmenes de datos cuando los contenedores son creados.

.c
Entornos aislados en un mismo host.

pe
Sólo se recrean los contenedores que han cambiado

Permite el uso de variables para la configuración de entornos


eu
Permite gestionar varios contenedores a la vez

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:

...

IMPORTANTE: para instalar Docker Compose: apt install docker-compose

El apartado “versión” nos indica la versión del formato Compose, ya que según la versión que

indiquemos podemos utilizar algunas características diferentes.

[Link]
71 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

El apartado “services” se centra en la configuración de los diferentes contenedores. En todo fichero

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

iremos viendo en los sucesivos ejemplos que iremos ejecutando.

El apartado “volumes” no es más que una carpeta compartida en el host, tal y como contamos en el

apartado de volúmenes y almacenamiento. Indicando su ruta definimos la ubicación de

om
almacenamiento que utilizarán los contenedores de nuestra arquitectura.

Finalmente, el apartado “redes” es similar al de “volumes” donde definiremos los diferentes

.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.

image: mariadb volumes:


m

data:/var/lib/mysql environment:
su

MYSQL_ROOT_PASSWORD=secret

MYSQL_DATABASE=wordpress
ce

MYSQL_USER=manager

MYSQL_PASSWORD=secret wordpress:

image: wordpress depends_on:

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 contenedor “wordpress” utilza la imagen wordpress y el volumen será “./target:/var/ www/html”.


a.

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 up -d compose_database_1 is up-to-date Recreating compose_wordpress_1 ... done

Podemos ver los contenedores activos con el comando:

$ 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

Figura 40. Acceso a uno de los contenedores creados en la arquitectura


su

Para parar todos los servicios:


ce

$ docker-compose stop

Si queremos borrar los servicios:

$ docker-compose stop

Para acceder a todos los logs del sistema en ejecución:

$ 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

servidores “database” para atender a otras apliaciones o a otros wordpress:


eu
docker-compose scale database=5
c

Starting compose_database_1 ... done


a.

Starting compose_database_2 ... done


m

Starting compose_database_3 ... done


su

Creating compose_database_4 ... done


ce

Creating compose_database_5 ... done

Si ahora ejecutamos de nuevo:

$ docker-compose ps

[Link]
75 / 83
[AFO028X3H] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[MOD024P3D] Fundamentos de Seguridad Cloud e Infraestructuras Industriales
[UDI1545JY] Docker

Figura 42. Ejecución de 5 contenedores con bases de datos

Si queremos reducir el número de contenedores:

$ docker-compose scale database=1

Y volveremos a tener solo un contenedor con la base de datos.

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

11. Buenas prácticas 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

procedimientos que nos pueden ser de ayuda.

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.

RUN groupadd -r migrupo && useradd --no-log-init -r -g migrupo miusuario


m

USER miusuario

Cuando levantemos un contenedor con este Dockerfile, el usuario ya no será root, en este caso sería
su

“miusuario”. Vamos a comprobarlo. Si levantamos cualquier contenedor y ejecutamos el siguiente

comando, podemos comprobar que somos “root”:


ce

En cambio, si ahora creamos un contenedor usando el fichero Dockerfile que hemos creado antes,

veremos que ya no somos “root”, en este caso el usuario será “miusuario”:

$ docker build -t test .

[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

---> Running in c09dc2e344dd

Removing intermediate container c09dc2e344dd

om
---> b5dbe1e28606

Step 3/3 : USER miusuario

.c
---> Running in cbbad46e23fe

Removing intermediate container cbbad46e23fe pe


eu
---> abf4a882215c

Successfully built abf4a882215c Successfully tagged test:latest


c
a.

Podemos comprobarlo de la siguiente forma:


m
su
ce

11.2. Evita el uso de ADD y usa COPY en el Dockerfile

El comando ADD, como ya comentamos en la sección de Dockerfile, permite descargar ficheros

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

operación de descomprimir o desempaquetar el fichero es necesario descargarlo previamente (con

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

ADD [Link] / Si ahora creamos una

imagen y ejecutamos un contenedor:

$ docker build -t test .

Sending build context to Docker daemon 3.072kB

om
Step 1/2 : FROM alpine

---> a6215f271958

.c
Step 2/2 : ADD [Link] / Downloading

[==================================================>]

10.24kB/10.24kB pe
eu
---> 5fbeb9842cf9

Successfully built 5fbeb9842cf9


c
a.

Successfully tagged test:latest

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

Podemos ver que se ha descargado el fichero llamado “[Link]”. En cambio, si tenemos ya el

fichero en local y lo pasamos al contenedor:

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

empaquetados en el fichero .tar de antes.


su

11.3. Cuidado con compartir almacenamiento del host


ce

Ya vemos en la sección de redes que es posible conectar una carpeta del host a nuestro contenedor.

Esto puede ser un problema de seguridad si el contenedor es comprometido, dando acceso al

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

que busca una cadena concreta en el fichero JSON del inspect:

[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] }}’’

11.4. Establecer una política para restablecer el contenedor en


caso de caída

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

11.5. No usar SSH en un contenedor


a.

Tenemos muchas herramientas en Docker para acceder a un contenedor. Utilizar SSH podría ser un
m

problema adicional de seguridad.


su

11.6. No permitir el uso de puertos privilegiados


ce

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

contenedores con puertos privilegiados con el siguiente comando:

$ docker container inspect $(docker container ls -aq) --format ‘{{ .Id }}: Ports={{

.[Link] }}’

11.7. Docker Bench

[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

ayuda para el usuario de Docker. Esta herramienta se llama Docker Bench:

[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.

Para edesgar la imagen y jecutarlo:

.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

--label docker_bench_security \ docker/docker-bench-security

Una vez lo ejecutemos, veremos cómo nos van apareciendo diferentes comentarios y avisos

(“warning”, “info” o “pass”) referentes a diferentes aspectos de la configuración de nuestro entorno

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

También podría gustarte