1. Code
  2. Cloud & Hosting
  3. Web Servers

Docker Desde las Bases: Comprendiendo Imágenes

Scroll to top
This post is part of a series called Docker from the Ground Up.
Docker From the Ground Up: Building Images

Spanish (Español) translation by Javier Salesi (you can also view the original English article)

Los contenedores Dockers están imponiéndose como la mejor práctica para desplegar y gestionar sistemas distribuídos nativos de la nube. Los contenedores son instancias de imágenes Docker. Resulta que hay mucho que saber y entender sobre las imágenes.

En éste tutorial de dos partes, cubriré las imágenes Docker en profundidad. En ésta parte, comenzaré con los principios básicos, y luego pasaré a la consideración del diseño y la inspección de los elementos internos de la imagen. En la segunda parte, cubriré la construcción de tus propias imágenes, resolución de problemas y trabajar con respositorios de imágenes.

Cuando termines los tutoriales, tendrás una sólida comprensión de lo que son exactamente las imágenes Docker y cómo utilizarlas de manera eficaz en tus propias aplicaciones y sistemas.

Comprendiendo las Capas

Docker gestiona imágenes usando un controlador de almacenamiento back-end. Hay varios controladores soportados como AUFS, BTRFS, y overlays. Las imágenes están hechas de capas ordenadas. Puedes pensar en una capa como un conjunto de cambios en el sistema de archivos. Cuando tomas todas las capas y las apilas, obtienes una nueva imagen que contiene todos los cambios acumulados.

La parte ordenada es importante. Si agregas un archivo en una capa y lo eliminas en otra capa, mejor lo harías en el orden correcto. Docker mantiene un seguimiento de cada capa. Una imagen puede componerse de docenas de capas (el límite actual es 127). Cada capa es muy ligera. El beneficio de las capas es que las imágenes pueden compartir capas.

Si tienes muchas imágenes basadas en capas similares, como Sistema Operativo base o paquetes comunes, entonces todas éstas capas comunes será almacenadas solo una vez, y el overhead por imagen serán las capas originales de esa imagen.

Copy on Write (Copiar en Escritura)

Cuando un nuevo contenedor es creado desde una imagen, todas las capas de la imagen son únicamente de lectura y una delgada capa lectura-escritura es agregada arriba. Todos los cambios efectuados al contenedor específico son almacenados en esa capa.

Ahora, no importa que el contenedor no pueda modificar los archivos desde su capa de imagen. Definitivamente puede. Pero creará una copia en su capa superior, y desde ese punto en adelante, cualquiera que trate de accesar al archivo obtendrá la copia de la capa superior. Cuando los archivos o directorios son eliminados de capas inferiores, pasan a estar ocultos. Las capas originales de la imagen son identificadas por un hash criptográfico basado en el contenido. La capa lectura-escritura del contenedor es identificada por un UUID (Identificador Único Universal).

Ésto permie una estrategia copia-en-escritura para ambas imágenes y contenedores. Docker reutiliza los mismos elementos tanto como sea posible. Sólamente cuando un elemento es modificado Docker creará una nueva copia.

Consideraciones de Diseño para Imágenes Docker

La organización original en capas y la estrategia copy-on-write promueve algunas de las mejores prácticas para crear y conformar imágenes Docker.

Imágenes Minimalistas: Menos Es Más

Las imágenes Docker obtienen enormes beneficios desde el punto de vista de estabilidad, seguridad y tiempo de carga mientras más pequeñas son. Puedes crear imágenes realmente diminutas para fines de producción. Si necesitas resolver problemas, siempre puedes instalar herramientas en un contenedor.

Si escribes tus datos, registro y todo lo demás únicamente para volúmenes montados entonces puedes usar tu arsenal entero de herramientas de depuración y de resolución de problemas en el host. Veremos pronto cómo controlar muy cuidadosamente qué archivos van en tu imagen Docker.

Combinar Capas

Las capas son magníficas, pero hay un límite, y existe overhead asociado con las capas. Demasiadas capas podrían dañar el acceso al sistema de archivos dentro del contenedor (porque cada capa podría haber añadido o eliminado un archivo o directorio), y vuelve desordenado tu propio sistema de archivos.

Por ejemplo, si instalas una serie de paquetes, puedes tener una capa para cada paquete, al instalar cada paquete en un comando RUN separado en tu Dockerfile:

1
RUN apt-get update
2
3
RUN apt-get -y install package_1
4
5
RUN apt-get -y install package_2
6
7
RUN apt-get -y install package_3

O puedes combinarlas en una capa con un solo comando RUN.

1
RUN apt-get update && \

2
3
    apt-get -y install package_1 && \

4
5
    apt-get -y install package_2 && \

6
7
    apt-get -y install package_3    

Eligiendo una Imagen Base

Tu imagen base (prácticamente nadie construye imágenes desde cero) es a menudo una decisión importante. Puede contener muchas capas y agregar muchas capacidades, pero también mucho peso. La calidad de la imagen y el autor también son críticos. No quieres basar tus imágenes en alguna imagen escamosa donde no estás seguro exactamente que hay ahí y si puedes confiar en el autor.

Hay imágenes oficiales para muchas distribuciones, lenguajes de programación, bases de datos, y entornos en tiempo de ejecución. A veces las opciones son abrumantes. Toma tu tiempo y decide inteligentemente.

Inspeccionando Imágenes

Veamos algunas imágenes. Aquí está una lista de las imágenes actualmente disponibles en mi máquina:

1
REPOSITORY      TAG    IMAGE ID     CREATED      SIZE
2
python          latest 775dae9b960e 12 days ago  687 MB
3
d4w/nsenter     latest 9e4f13a0901e 4 months ago 83.8 kB
4
ubuntu-with-ssh latest 87391dca396d 4 months ago 221 MB
5
ubuntu          latest bd3d4369aebc 5 months ago 127 MB
6
hello-world     latest c54a2cc56cbb 7 months ago 1.85 kB
7
alpine          latest 4e38e38c8ce0 7 months ago 4.8 MB
8
nsqio/nsq       latest 2a82c70fe5e3 8 months ago 70.7 MB

El repositorio y la etiqueta identifican la imagen para los humanos. Si solo tratas de ejecutar o descargar usando un nombre de repositorio sin especificar la etiqueta, entonces la etiqueta "más reciente" es utilizada por defecto. La ID de la imagen es un identificador único.

Adentrémonos e inspeccionemos la imagen hello-world (hola mundo):

1
> docker inspect hello-world
2
[
3
    {
4
        "Id": "sha256:c54a2cc56cbb2f...e7e2720f70976c4b75237dc",
5
        "RepoTags": [
6
            "hello-world:latest"
7
        ],
8
        "RepoDigests": [
9
            "hello-world@sha256:0256e8a3...411de4cdcf9431a1feb60fd9"
10
        ],
11
        "Parent": "",
12
        "Comment": "",
13
        "Created": "2016-07-01T19:39:27.532838486Z",
14
        "Container": "562cadb4d17bbf30b58a...bf637f1d2d7f8afbef666",
15
        "ContainerConfig": {
16
            "Hostname": "c65bc554a4b7",
17
            "Domainname": "",
18
            "User": "",
19
            "AttachStdin": false,
20
            "AttachStdout": false,
21
            "AttachStderr": false,
22
            "Tty": false,
23
            "OpenStdin": false,
24
            "StdinOnce": false,
25
            "Env": [
26
                "PATH=/usr/bin:/usr/sbin:/usr/bin:/sbin:/bin"
27
            ],
28
            "Cmd": [
29
                "/bin/sh",
30
                "-c",
31
                "#(nop) CMD [\"/hello\"]"
32
            ],
33
            "Image": "sha256:0f9bb7da10de694...5ab0fe537ce1cd831e",
34
            "Volumes": null,
35
            "WorkingDir": "",
36
            "Entrypoint": null,
37
            "OnBuild": null,
38
            "Labels": {}
39
        },
40
        "DockerVersion": "1.10.3",
41
        "Author": "",
42
        "Config": {
43
            "Hostname": "c65bc554a4b7",
44
            "Domainname": "",
45
            "User": "",
46
            "AttachStdin": false,
47
            "AttachStdout": false,
48
            "AttachStderr": false,
49
            "Tty": false,
50
            "OpenStdin": false,
51
            "StdinOnce": false,
52
            "Env": [
53
                "PATH=/usr/sbin:/usr/bin:/sbin:/bin"
54
            ],
55
            "Cmd": [
56
                "/hello"
57
            ],
58
            "Image": "sha256:0f9bb7da10de694b...b0fe537ce1cd831e",
59
            "Volumes": null,
60
            "WorkingDir": "",
61
            "Entrypoint": null,
62
            "OnBuild": null,
63
            "Labels": {}
64
        },
65
        "Architecture": "amd64",
66
        "Os": "linux",
67
        "Size": 1848,
68
        "VirtualSize": 1848,
69
        "GraphDriver": {
70
            "Name": "aufs",
71
            "Data": null
72
        },
73
        "RootFS": {
74
            "Type": "layers",
75
            "Layers": [
76
                "sha256:a02596fdd012f22b03a...079c3e8cebceb4262d7"
77
            ]
78
        }
79
    }
80
]

Es interesante ver cuanta información es asociada con cada imagen. No explicaré cada elemento. Solo mencionaré un interesante aspecto de que las entradas "container" y "containerConfig" son para un contenedor temporal que Docker crea cuando construye la imagen. Aquí, quiero enfocarme en la últma sección de "RootFS". Puedes obtener solo ésta parte usando el soporte de plantilla Go del comando inspect:

1
> docker inspect -f '{{.RootFS}}' hello-world
2
{layers [sha256:a02596fdd012f22b03af6a...8357b079c3e8cebceb4262d7] }

Funciona, pero perdimos el formato agradable. Prefiero usar jq:

1
> docker inspect hello-world | jq .[0].RootFS
2
{
3
  "Type": "layers",
4
  "Layers": [
5
    "sha256:a02596fdd012f22b03af6a...7507558357b079c3e8cebceb4262d7"
6
  ]
7
}
8

Puedes ver que el tipo es "Layers" (Capas), y hay solo una capa.

Inspeccionemos las capas de la imagen Python:

1
> docker inspect python | jq .[0].RootFS
2
{
3
  "Type": "layers",
4
  "Layers": [
5
    "sha256:a2ae92ffcd29f7ede...e681696874932db7aee2c",
6
    "sha256:0eb22bfb707db44a8...8f04be511aba313bdc090",
7
    "sha256:30339f20ced009fc3...6b2a44b0d39098e2e2c40",
8
    "sha256:f55f65539fab084d4...52932c7d4924c9bfa6c9e",
9
    "sha256:311f330fa783aef35...e8283e06fc1975a47002d",
10
    "sha256:f250d46b2c81bf76c...365f67bdb014e98698823",
11
    "sha256:1d3d54954c0941a8f...8992c3363197536aa291a"
12
  ]
13
}

Wow. Siete capas. Pero ¿cuáles son esas capas? Podemos usar el comando history para averiguar eso:

1
IMAGE        CREATED     CREATED BY                             SIZE 
2
775dae9b960e 12 days ago /bin/sh -c #(nop)  CMD ["python3"]     0 B

3
<missing>    12 days ago /bin/sh -c cd /usr/local/bin  && { ... 48 B
4
<missing>    12 days ago /bin/sh -c set -ex  && buildDeps=' ... 66.9 MB

5
<missing>    12 days ago /bin/sh -c #(nop)  ENV PYTHON_PIP_V... 0 B

6
<missing>    12 days ago /bin/sh -c #(nop)  ENV PYTHON_VERSI...   0 B

7
<missing>    12 days ago /bin/sh -c #(nop)  ENV GPG_KEY=0D96... 0 B

8
<missing>    12 days ago /bin/sh -c apt-get update && apt-ge... 7.75 MB

9
<missing>    12 days ago /bin/sh -c #(nop)  ENV LANG=C.UTF-8    0 B

10
<missing>    12 days ago /bin/sh -c #(nop)  ENV PATH=/usr/lo... 0 B

11
<missing>    13 days ago /bin/sh -c apt-get update && apt-ge... 323 MB

12
<missing>    13 days ago /bin/sh -c apt-get update && apt-ge... 123 MB

13
<missing>    13 days ago /bin/sh -c apt-get update && apt-ge... 44.3 MB

14
<missing>    13 days ago /bin/sh -c #(nop)  CMD ["/bin/bash"... 0 B

15
<missing>    13 days ago /bin/sh -c #(nop) ADD file:89ecb642... 123 MB

OK. No te alarmes. Nada falta. Ésta es solo una terrible interfaz de usuario. Las capas solían tener una ID de la imagen antes de Docker 1.10, pero ya no. La ID de la capa superior no es realmente la ID de esa capa. Es la ID de la imagen Python. El "CREATED BY" está truncado, pero puedes ver el comando completo si pasas --no-trunc. Te ahorraré el resultado aquí debido a las limitaciones de la anchura de página que requerirá ajuste de línea extremo.

¿Cómo obtienes imágenes? Existen tres maneras:

  • Pull/Run (Descargar/Ejecutar)
  • Load (Cargar)
  • Build (Construir)

Cuando ejecutas un contenedor, especificas su imagen. Si la imagen no existe en tu sistema, está siendo descargada de un registro Docker (por defecto DockerHub). Alternativamente, puedes descargar directamente sin ejecutar el contenedor.

También puedes cargar una imagen que alguien te envió como un archivo tar. Docker lo soporta nativamente.

Finalmente, y más interesantemente, puedes construir tus propias imágenes, que es el tópico de la segunda parte.

Conclusión

Las imágenes Docker se basan en un sistema de archivos en capas que ofrece muchas ventajas y beneficios para los casos de uso para los que son diseñados los contenedores, como ser ligeros y compartir partes comunes que muchos contenedores pueden desplegar y ejecutar en la misma máquina económicamente.

Pero hay alguna documentación de referencia, y necesitas entender los principios y mecanismos para utilizar de manera eficaz las imágenes de Docker. Docker proporciona varios comandos para tener una idea de qué imágenes están disponibles ahora y cómo están estructuradas.