1. Code
  2. Cloud & Hosting
  3. Hosting

Docker Desde los Cimientos: Trabajando Con Contenedores, Parte 1

Scroll to top
This post is part of a series called Docker from the Ground Up: Working with Containers.
Docker From the Ground Up: Working With Containers, Part 2

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

Esto es parte de una serie de dos partes sobre trabajar con contenedores Docker. En esta parte, nos enfocaremos en las muchas maneras y opciones para ejecutar una imagen y cómo el anfitrión puede interactuar con un contenedor Docker.

En la segunda parte cubriremos listar, iniciar, detener y reiniciar contenedores así como ejecutar comandos en contenedores en ejecución. Las imágenes Docker son las unidades de despliegue. Cuando ejecutas una imagen, instancías un contenedor Docker que ejecuta un proceso sencillo en su propio entorno asilado para el árbol de sistema de archivo, trabajo en red y procesos.

Los contenedores Docker son muy flexibles y habilitan muchos casos de uso que son demasiado pesados, complejos y/o costosos con otras tecnologías como máquinas virtuales y servidores de metal.

Antes de que comencemos, asegúrate de que Docker está instalado apropiadamente en tu entorno. Dependiendo de cómo se instaló y tu usuario, podrías necesitar ejecutarlo como sudo. Omitiré el sudo.

Ejecutando una Imagen

Lanzas un contenedor Docker ejecutando una imagen. Hay varias maneras de ejecutar un contenedor que afecta qué tan sencillo es administrar todos los contenedores. Cuando el contenedor inicia, este típicamente ejecuta el comando definido en el Dockerfile. Aquí está el Dockerfile para el contenedor hello-world:

1
FROM scratch
2
COPY hello /
3
CMD ["/hello"]

El comando simplemente ejecuta el binario "hello" que fue copiado para la raíz del contenedor cuando se construye la imagen.

Primer Plano vs. Separado

Un contenedor puede ejecutarse en primer plano en donde bloquea hasta que el proceso existe y el contenedor deja de correr. En el modo en primer plano, el contenedor imprime la salida a la consola y puede leer entrada estándar. En modo separado (cuando proporcionas la bandera -d), el control regresa inmediatamente y el contenedor.

Ejecutando un Contenedor Sin Nombre

La manera más simple de ejecutar un contenedor es: docker run <imagen id o nombre>.

Cuando ejecutas un contenedor usando este comando, Docker le asignará un nombre compuesto de dos palabras aleatorias. Por ejemplo: docker run hello-world.

Si ya tienes la imagen hello-world entonces Docker la ejecutará. Si no, la jalará del repositorio oficial DockerHub y después la ejecutará. La salida debería lucir como:

1
Hello from Docker!
2
This message shows that your installation appears to be working correctly.
3
4
To generate this message, Docker took the following steps:
5
 1. The Docker client contacted the Docker daemon.
6
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
7
 3. The Docker daemon created a new container from that image which runs the
8
    executable that produces the output you are currently reading.
9
 4. The Docker daemon streamed that output to the Docker client, which sent it
10
    to your terminal.
11
12
To try something more ambitious, you can run an Ubuntu container with:
13
 $ docker run -it ubuntu bash
14
15
Share images, automate work-flows, and more with a free Docker ID:
16
 https://cloud.docker.com/
17
18
For more examples and ideas, visit:
19
 https://docs.docker.com/engine/userguide/

El programa hello sale después de mostrar el mensaje, que termina el proceso ejecutándose dentro del contenedor y termina la ejecución del contenedor. El contenedor aún se queda en caso de que quieras conectarte a él, examinar registros, o cualquier otra cosa. Para ver el contenedor, puedes ejecutar el siguiente comando:

1
docker ps -a  --format "table {{.ID}}\t{{.Status}}\t{{.Names}}"
2
3
CONTAINER ID        STATUS                     NAMES
4
8e2e491accb5        Exited (0) 2 minutes ago   clever_liskov

Explicaré después cómo listar contenedores y todas las opciones relevantes. Por ahora, enfoquémonos en la sección Names. Docker generó el nombre "clever_liskov" automáticamente, y tendré que usarlo o el ID de contenedor para referir a este contenedor para cualquier propósito como reiniciarlo, quitarlo, o ejecutar un comando.

Ejecutando un Contenedor Nombrado

Usar IDs de contenedor o nombres auto-generados a veces es inconveniente. Si interactúas frecuentemente con un contenedor que recreas frecuentemente, entonces este tendrá un ID diferente y nombre auto-generado. También, el nombre será aleatorio.

Dockers te permite nombrar a tus contenedores cuando los ejecutas proporcionan un argumento "--name" de línea de comando. En casos simples, en donde solo tienes un contenedor por imagen, puedes nombrar al contenedor como tu imagen: docker run --name hello-world hello-world.

Ahora, si miramos el proceso (removí clever_liskov antes) veremos que el contenedor es nombrado hello-world:

1
docker ps -a --format "table {{.ID}}\t{{.Names}}"
2
CONTAINER ID        NAMES
3
f6fe77b3b6e8        hello-world

Hay muchos beneficios para un contenedor nombrado:

  • Tienes un nombre estable para tus contenedores que usas de manera interactiva y en scripts.
  • Puedes elegir un nombre significativo.
  • Puedes elegir un nombre corto para conveniencia cuando trabajes de manera interactiva.
  • Esto previene que tengas múltiples contenedores de la misma imagen por accidente (mientras siempre proporciones el mismo nombre).

Veamos la última opción. Si intentas ejecutar el mismo comando de nuevo con el nombre "hello-world", obtengo en mensaje claro de error:

1
docker run --name hello-world hello-world
2
docker: Error response from daemon: Conflict. The container name
3
"/hello-world" is already in use by container 
4
f6fe77b3b6e8e77ccf346c32c599e67b2982893ca39f0415472c2949cacc4a51. 
5
You have to remove (or rename) that container to be able to reuse 
6
that name.
7
See 'docker run --help'.

Ejecutando una Imagen Auto-Remover

Los contenedores permanecen por defecto. Algunas veces, no los necesitas. En vez de quitar manualmente contenedores salidos, haces que el contenedor se vaya por si mismo. El argumento de línea de comando --rm hace el truco: docker run --rm hello-world.

Ejecutando un Comando Diferente

Por defecto, Docker ejecuta el comando especificado en el Dockerfile usado para construir la imagen (o directamente el punto de entrada si no se encuentra un comando). Siempre puedes anularlo proporcionando tu propio comando al final del comando ejecutar. Ejecutemos ls -la en la imagen busybox (la imagen hello-world no tiene ls ejecutable):

1
docker run busybox ls -la
2
total 44
3
drwxr-xr-x   18 root     root          4096 Mar 18 17:06 .
4
drwxr-xr-x   18 root     root          4096 Mar 18 17:06 ..
5
-rwxr-xr-x    1 root     root             0 Mar 18 17:06 .dockerenv
6
drwxr-xr-x    2 root     root         12288 Mar  9 00:05 bin
7
drwxr-xr-x    5 root     root           340 Mar 18 17:06 dev
8
drwxr-xr-x    2 root     root          4096 Mar 18 17:06 etc
9
drwxr-xr-x    2 nobody   nogroup       4096 Mar  9 00:05 home
10
dr-xr-xr-x   85 root     root             0 Mar 18 17:06 proc
11
drwxr-xr-x    2 root     root          4096 Mar  9 00:05 root
12
dr-xr-xr-x   13 root     root             0 Mar 18 17:06 sys
13
drwxrwxrwt    2 root     root          4096 Mar  9 00:05 tmp
14
drwxr-xr-x    3 root     root          4096 Mar  9 00:05 usr
15
drwxr-xr-x    4 root     root          4096 Mar  9 00:05 var

Interactuando Con el Anfitrión

Los contenedores Docker ejecutan procesos aislados en su propio mundo pequeño. Pero frecuentemente es necesario y útil proporcional acceso al anfitrión.

Pasando Variables de Entorno a un Contenedor

Los contenedores Docker no heredan automáticamente el entorno del proceso anfitrión que los ejecutó. Necesitas proporcionar explícitamente variables de entorno al contenedor cuando lo ejecutas usando la bandera de línea de comando -e. Puedes pasar múltiples variables de entorno. Aquí está un ejemplo:

1
docker run --rm -it -e ENV_FROM_HOST="123" busybox
2
/ # env
3
HOSTNAME=8e7672bce5a7
4
SHLVL=1
5
HOME=/root
6
ENV_FROM_HOST=123
7
TERM=xterm
8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
9
PWD=/
10
/ #

La primera línea ejecuta el contenedor busybox, pasándole la variable ENV_FROM_HOST y después dentro del contenedor ejecutando env muestra que la propiedad ENV_FROM_HOST está establecida.

Puedes usar las variables de entorno anfitrión también. Esto establece un par de variables de entorno anfitrión y las usa en el comando de ejecución:

1
$ export VAR_1=1
2
$ export VAR_2=2
3
$ docker run --rm -it -e VAR_1="$VAR_1" -e VAR_2="$VAR_2" busybox

Dentro del contenedor, ahora son visibles:

1
/ # env | grep VAR
2
VAR_1=1
3
VAR_2=2

Montando Directorios Anfitrión

Una de las interacciones más útiles es montar directorios anfitrión. Esto permite muchos casos de uso interesantes:

  • Almacenamiento compartido entre contenedores corriendo sobre el mismo anfitrión.
  • Ver y editar archivos usando tu entorno anfitrión y herramientas y usando los archivos en el contenedor.
  • Persistencia a nivel de anfitrión más allá del tiempo de vida de un contenedor.

Aquí creo un archivo en el anfitrión: $ echo "Yeah, it works!" > ~/data/1.txt

Después ejecuto la imagen busybox montando el ~/data directory a /data en el contenedor y mostrando los contenidos del archivo en la pantalla:

1
$ docker run --rm -v ~/data:/data busybox cat /data/1.txt
2
Yeah, it works!

Aquí usé el comando cat /data/1.txt.

Exponiendo Puertos al Anfitrión

Si expones un puerto en tu Dockerfile usando EXPOSE, será accesible solo a otros contenedores docker. Para hacerlo accesible al anfitrión, necesitas usar el argumento de línea de comando -p. La sintaxis es -p <host port>:<exposed container port>.

Aquí está corriendo la imagen nginx, que expone el puerto 80 y usa el argumento de línea de ocmando -p para hacerlo visible en el puerto anfitrión 9000:

1
docker run --name nginx --rm -d -p 9000:80 nginx

Nota que a diferencia de comandos anteriores que realizaban algunas tareas y completaban, el contenedor nginx seguirá ejecutándose y escuchando peticiones entrantes. Verifiquemos que nginx está realmente funcionando y responde a peticiones en el puerto 9000. Prefiero el excelente cliente HTTP httpie sobre curl para acceder servidores web y servicios desde la línea de comando:

1
http HEAD localhost:9000
2
3
HTTP/1.1 200 OK
4
Accept-Ranges: bytes
5
Connection: keep-alive
6
Content-Length: 612
7
Content-Type: text/html
8
Date: Sun, 19 Mar 2017 07:35:55 GMT
9
ETag: "58a323e4-264"
10
Last-Modified: Tue, 14 Feb 2017 15:36:04 GMT
11
Server: nginx/1.11.10

Conclusión

Hay muchas formas de ejecutar una imagen Docker para crear un contenedor, y hay muchas opciones. Cada combinación soporta un uso particular. Es muy útil cuando trabajamos con contenedores Docker entender completamente los detalles y usar el mejor método para lanzar tus contenedores.

En adición, adjuntar volúmenes anfitrión y exponer puertos de publicación permite integración con el anfitrión y una plétora de escenarios de uso. En la parte dos, nos sumergiremos en administrar un montón de contenedores y sacar ventaja de todo el potencial que Docker proporciona.