¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Introducción a Docker
Notas pertenecientes al curso Docker a fondo e Introducción a Kubernetes: aplicaciones basadas en contenedores
Prerrequisitos e instalación
Para seguir correctamente el curso, el único prerrequisito es disponer de un ordenador con cualquier sistema operativo reciente actualizado y Docker Engine (en Linux) o https://docs.docker.com/desktop/Docker Desktop instalado (en Linux, Windows o MacOS).
Con Docker Engine se obtienen los siguientes componentes:
- El cliente (el comando
docker), que usa la línea de comandos (CLI). - El componente servidor, que crea y ejecuta los contenedores.
Docker Desktop permite ejecutar la parte servidora de Docker en Windows o MacOS. Dado que esta parte servidora existe solo en Linux, Docker Desktop recurre a la virtualización para conseguirlo.
Así, cuando instalas Docker Desktop, se instala también una máquina virtual (MV) Linux en tu sistema y esa MV es la que ejecutará la parte servidora de Linux, y por lo tanto los contenedores. Al instalar Docker Desktop también obtenemos el cliente nativo (el comando docker, para Windows o MacOS), configurado de tal modo que automáticamente se conecta a dicha MV sin que nosotros tengamos que hacer nada: tú usas el comando docker desde una línea de comandos Windows o un terminal de MacOS y automáticamente se comunica con la parte servidora ejecutándose en la VM. Esa MV la configura y aprovisiona Docker Desktop automáticamente y no tienes que hacer nada con ella (ni pararla, ni configurarla, ni encenderla, ni nada de nada).
- En Windows puedes usar Hyper-V o WSL2 siendo ese último el modo recomendado por rendimiento
- En MacOS usa
xhyve - En Linux usa KVM (sí, Docker Desktop en Linux también usa una máquina virtual)
Además, Docker Desktop añade los siguientes componentes:
- Una pequeña interfaz gráfica de configuración en el caso de Docker Desktop (inexistente en Docker Engine)
- La integración de herramientas adicionales como Synk o Compose
- La posibilidad de ejecutar un Kubernetes de desarrollo
Cuando hablemos de contenedores Windows, el contenido afecta solamente a Windows, ya que es el único SO que puede ejecutarlos.
Cuando hablemos de aspectos avanzados (en concreto temas de seguridad y permisos), los contenidos harán referencia básicamente a Linux, ya que es el SO donde se pueden experimentar y ver con más facilidad (al no haber una capa intermedia que ejecute los contenedores). En Windows y Mac, algunos temas referentes a seguridad experimentan ligeras variaciones que dependen exactamente de la configuración del sistema.
Verificación de la instalación de Docker
Para verificar qué Docker está instalado en tu máquina, abre una CLI y teclea:
docker --version
Esto mostrará por consola la versión del motor de Docker
Docker en Windows
El soporte para contenedores en Windows llegó con Windows Server 2016 y Windows 10 Anniversary Edition. Eso significa que un contenedor Windows (un contenedor que tenga binarios de Windows) solo puede ser ejecutado si tienes una versión del sistema operativo igual o posterior a las indicadas. Eso implica que en Linux el escenario de los contenedores está bastante avanzado y en Windows lleva menos tiempo en funcionamiento.
Recuerda que los contenedores NO son un mecanismo de virtualización y que, al final, la aplicación que contiene el contenedor es ejecutada por el SO real, el del host. Por ejemplo, si tengo un sistema Linux, solo podremos ejecutar contenedores de Linux. Esto diferencia a los contenedores de una máquina virtual. Los contenedores debemos verlos como un mecanismo de empaquetar aplicaciones con sus dependencias.
En general, si tienes una versión reciente y actualizada de Windows (que sería lo recomendable), deberías instalar Docker Desktop integrado con el subsistema Linux de Windows (WSL 2), como veremos ahora mismo.
WSL es el subsistema Linux de Windows. Gracias a WSL es posible ejecutar nativamente aplicaciones de Linux, e incluso distribuciones completas, dentro de Windows, y sin necesidad de virtualizar o configurar un arranque dual. Es decir, gracias a WSL puedes tener a la vez Windows y Linux funcionando en tu equipo. La versión inicial de WSL, de 2016, no implementaba el Kernel completo de Linux, pero daba bastante margen para trabajar.
En 2020 Microsoft lanzó la versión 2 de la tecnología. WSL 2 está disponible en cualquier versión de Windows 10 o posterior cuyo número de build sea igual o superior a 18917.
Si tienes una versión de Windows que NO disponga de WSL 2, o no quieres activarla por algún motivo, entonces se va a instalar Hyper-V en tu máquina.
Debemos entender que cuando instalamos Docker Desktop for Windows sin WSL 2, terminamos teniendo dos sistemas Docker independientes:
- La máquina virtual
DockerDesktopVMque contiene imágenes Linux y ejecuta contenedores Linux - Nuestro ordenador, que contiene imágenes Windows y ejecuta contenedores Windows
Una vez instalado, para que funcione, tu usuario deberá pertenecer al grupo docker-users, un grupo de seguridad especialmente creado para ejecutar Docker.
| Modo Docker | Puedo gestionar contenedores Linux desde… | Puedo gestionar contenedores Windows desde… |
|---|---|---|
| WSL 2 - Contenedores Linux | Terminales Windows (PS/cmd) o terminales WSL 2 | Terminales Windows |
| WSL 2 - Contenedores Windows | Terminales WSL 2 y Terminales Windows | Terminales Windows (PS/cmd) |
| HyperV- Contenedores Linux | Terminales Windows (PS/cmd) | No puedo |
| HyperV- Contenedores Windows | No puedo | Terminales Windows (PS/cmd) |
Docker en Linux
Tras instalar Docker Engine es posible que obtengamos un error de permisos al ejecutarlo porque no podamos conectarnos al pipe /var/run/docker.sock que es el mecanismo que utiliza la CLI de Docker para comunicarse con el servidor.
Por supuesto tener que usar sudo no es lo ideal. Para evitarlo y poder usar Docker sin permisos de root, debes añadir el usuario actual al grupo de usuarios docker:
sudo usermod -aG docker $USER
Al igual que ocurre en Windows y en Mac, Docker Desktop en Linux utiliza una máquina virtual (VM) para ejecutar contenedores (concretamente utiliza KVM). Eso te puede sorprender un poco, pues Linux ya tiene soporte de contenedores en el propio sistema operativo, así que, no habría motivo alguno a priori para usar una VM (se pueden ejecutar los contenedores directamente, tal y como hace Docker Engine). Los motivos por los cuales Docker Desktop usa una VM en Linux son los siguientes:
- Para controlar el SO subyacente: al usar una VM controlada por Docker Desktop, es posible asegurar que todos los usuarios van a tener el mismo SO con la misma versión (especialmente del kernel de Linux), lo que permite a Docker garantizar que todas las funcionalidades funcionan igual para cualquier usuario. También permite a Docker garantizar, en la medida de lo posible, que las características de Docker Desktop serán lo más similar posible en los 3 entornos (Windows, MacOS y Linux).
- Por seguridad: al ejecutarse los contenedores en el entorno aislado de una VM, cualquier acción maliciosa que un contenedor pudiese realizar se verá limitada.
Si usas Linux no es necesario que te instales Docker Desktop. Si quieres hacerlo, adelante, no hay ningún problema, pero en ningún momento del curso se asume que este deba estar instalado.
Docker Desktop y Docker Engine funcionando a la vez: Para indicarle a la CLI sobre qué entorno actuar (si Docker Engine o Docker Desktop) se usa el comando docker context.
Si queremos parar Docker Engine:
systemctl stop docker docker.socket containerd
Y deshabilitarlo del inicio:
systemctl disable docker docker.socket containerd
Si quisiéramos habilitarlo de nuevo:
systemctl enable docker docker.socket containerd
Y arrancarlo:
systemctl start docker docker.socket containerd
Qué es Docker
Docker es una herramienta para crear, administrar y ejecutar contenedores. Así, que lo primero que debemos tener claro es, qué entendemos por “contenedor”.
Una comparación típica es comparar un contenedor con una máquina virtual (MV). La siguiente imagen ilustra dicha comparación:
IMAAAAAAAAAAAAGEN
Las semejanzas entre un contenedor y una MV son las siguientes:
- Ambos proveen un entorno aislado para la ejecución de programas.
- Ambos pueden moverse entre hosts de forma segura: si una MV o un contenedor se ejecuta correctamente en un host lo hará en todos los demás.
Por eso muchas veces se dice que los contenedores son “máquinas virtuales ligeras”, pero eso dista mucho de la verdad. La realidad es que existen más diferencias que semejanzas entre una MV y un contenedor:
- Una MV puede ejecutar un SO distinto del que hay en el huésped. Puedo ejecutar Linux en una MV en un host Windows o Mac y viceversa. Técnicamente, cualquier combinación entre sistema operativo host y sistema operativo que ejecuta la MV es posible (otra cosa es que haya algunas restricciones de licencia, como ocurre con MacOS). Por su parte, un contenedor solo puede ejecutar binarios de la misma arquitectura que el sistema operativo host. Es decir, si ejecutamos Docker en Linux, solo vamos a poder ejecutar binarios Linux (y de la misma arquitectura) en los contenedores. Lo mismo ocurre en Windows y en Mac, por supuesto.
- Una MV ejecuta un SO completo y dentro de ese SO ejecuta varios procesos. Por su parte, un contenedor ejecuta un solo proceso. Cuando ese proceso finaliza, el contenedor “muere” (aunque es cierto que este proceso raíz puede iniciar otros procesos).
- Efectivamente, tanto MV como contenedores ofrecen un entorno aislado para la ejecución de aplicaciones. Pero, a diferencia de las MV, en los que cada MV tiene toda la infraestructura (memoria, procesador, red, …), los contenedores comparten dicha infraestructura, que es proveída por el host. Por eso, a diferencia de una MV, no vas a establecer “la memoria de un contenedor” o “su tamaño de disco” (aunque sí puedes establecer límites de memoria o CPU).
En definitiva, y a pesar de la comparación, los contenedores no son una tecnología de virtualización. No compiten (ni lo pretenden) con las máquinas virtuales. Juegan un rol distinto.
Contenedores como mecanismo de empaquetamiento de aplicaciones
Dejemos pues de ver los contenedores como un sistema de virtualización y pasemos a verlos como una manera de empaquetar una aplicación y todas sus dependencias. Así, un contenedor es un mecanismo para distribuir una aplicación y todas sus dependencias de forma que dicha aplicación pueda funcionar en cualquier máquina, sin necesidad de que esta tenga instalada nada más que un entorno de ejecución de contenedores, es decir, Docker.
Cuando empaquetas una aplicación en un contenedor, dicho contenedor contiene la aplicación y todas sus dependencias, es decir:
- Una copia de la base del SO. Si es una aplicación para Linux, el contenedor contendrá una distro base de Linux. La llamo “base” porque no es necesario que sea una “distro” completa, solo que tenga lo mínimo necesario para poder ejecutar aplicaciones.
- Una copia de todas las bibliotecas necesarias para tu aplicación. Si tu aplicación depende de
foo.soo debar.dllesos ficheros estarán en el contenedor. - El código de tu aplicación, así como cualquier recurso adicional necesario.
Quizá te preguntes cuál es la ventaja de empaquetar aplicaciones en contenedores. Pues, básicamente, asegurar la independencia del contenedor respecto al host. Esto es muy interesante en escenarios con hosts que ejecutan varias aplicaciones.
Lo que es importante recalcar es que, al final, un contenedor es un artefacto binario, que por lo tanto podremos mover y copiar entre hosts.
Otras tecnologías de contenedores
En 2015 se creó la OCI (Open Container Initiative), un organismo participado por varias empresas (entre ellas Docker, pero también los grandes “players” como RedHat, IBM o Google) con el objetivo de definir estándares de contenedores, de forma que puedan compartirse contenedores e imágenes creados mediante distintas tecnologías.
Existen varios proyectos de contenedores que creo importante mencionar, para que veas que el mundo de los contenedores no se reduce a Docker:
- rkt: lo pongo aquí por motivos de completitud, ya que fue la primera gran alternativa a Docker. Vino de la mano de CoreOS (ahora RedHat) y llegó incluso a ser un proyecto incubatorio de la CNCF, pero su desarrollo se ha parado y por lo tanto no es una opción que debas considerar.
- podman: quizá la alternativa más sugerente a Docker. La ventaja principal con respecto a Docker es que no requiere de ningún daemon, lo que simplifica su arquitectura y le da mucha versatilidad. Por supuesto, las imágenes construidas con Docker son compatibles con podman y viceversa, ya que ambos sistemas son compatibles con OCI. Permite la ejecución no solo de contenedores, sino de otro concepto llamado pod (relacionado con Kubernetes).
- kaniko: a diferencia de podman y Docker, Kaniko no es un sistema para crear y ejecutar contenedores, sino un sistema para construir imágenes OCI. Está diseñado para ejecutarse en Kubernetes y permitir la ejecución de pipelines de CI/CD que construyan imágenes de contenedores en Kubernetes.
- buildah: se trata de otro sistema para construir imágenes OCI, al igual que Kaniko. Es de la gente de RedHat: de hecho, al construir una imagen con podman se usa buildah por debajo (de forma transparente). Pero, no es requisito usarlo junto con podman, puedes hacerlo de forma separada, y dado que no requiere daemon es una alternativa muy interesante en determinados escenarios (p. ej. Kubernetes).
Contenedores y microservicios
Las arquitecturas orientadas a microservicios son una de las tendencias más en boga actualmente. Es importante destacar que el uso de contenedores no implica estar desarrollando en una arquitectura orientada a microservicios y viceversa: se pueden implementar arquitecturas de microservicios sin el uso de contenedores.
Una arquitectura de microservicios requiere que cada microservicio pueda ser desarrollado, desplegado y actualizado de forma independiente. Los contenedores ayudan en los dos últimos puntos al ofrecer un entorno totalmente aislado e independiente en el que empaquetar, desplegar y actualizar nuestro microservicio.
Si usamos contenedores nos independizamos del host que al final ejecutará nuestros microservicios y además, dado que el contenedor contiene todo lo necesario para ejecutar mi aplicación (o microservicio), este se podrá desplegar de forma independiente.
Ahora bien, si usamos una arquitectura monolítica tradicional, también nos podemos beneficiar de los contenedores. Así que resumiendo: aunque los contenedores como mecanismo de despliegue facilitan y encajan muy bien con las necesidades de una arquitectura orientada a microservicios, se pueden usar en muchos otros escenarios. No caigas en el error de pensar que solo grandes arquitecturas distribuidas se benefician de los contenedores. Y tampoco caigas en el error de pensar que sin el uso de contenedores dichas arquitecturas no son posibles. Cualquier tipo de aplicación y de arquitectura se puede beneficiar de los contenedores.
Contenedores e imágenes
Haciendo un símil con la POO (Programación Orientada a Objetos) podríamos decir que la relación entre “imagen” y “contenedor” es la misma que hay entre “clase” y “objeto”. Una imagen nos describe los contenidos que tendrá el contenedor, así como su configuración y el proceso que se ejecutará. Mientras que un contenedor es el resultado de ejecutar una imagen.
Las imágenes no se ejecutan, son meras definiciones que contienen todo lo necesario para crear los contenedores. Así, cuando antes dijimos que “un contenedor es un artefacto binario, que por lo tanto podremos mover y copiar entre hosts”, lo que deberíamos haber dicho realmente es que “una imagen es un artefacto binario, que por lo tanto podremos mover y copiar entre hosts”.
Los contenedores tienen un ciclo de vida (básicamente pueden estar ejecutándose o bien pueden haber finalizado su ejecución), mientras que las imágenes no tienen ningún ciclo de vida asociado.
Registros de imágenes
Para poder crear contenedores, primero necesitamos la imagen correspondiente. Para ello, o bien debemos crearla, o bien la podemos descargar de un registro.
Muchas empresas proporcionan imágenes que podemos descargar y utilizar directamente, y nosotros podemos compartir nuestras propias imágenes de forma pública o privada utilizando un registro.
Las imágenes nunca se comparten mediante disco o enviando el archivo físico por correo electrónico. Siempre se comparten publicándolas a un registro y luego, descargándolas de dicho registro.
Existen muchos registros de imágenes, pero el registro por defecto es Docker Hub. Podemos usar Docker Hub para:
- Publicar nuestras propias imágenes para que puedan ser usadas por terceros.
- Descargar imágenes publicadas por otras personas, empresas u organizaciones.
Usaremos el comando pull para indicar cuál es la imagen que nos queremos descargar. Así que, abre una CLI y teclea:
docker pull hello-world
La salida de dicho comando será algo parecido a:
Using default tag: latest latest: Pulling from library/hello-world b04784fba78d: Pull complete Digest: sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f Status: Downloaded newer image for hello-world:latest
No te preocupes por esa salida por ahora, más adelante aprenderemos qué significa cada una de esas líneas.
Lo importante es que ahora nos hemos descargado la imagen hello-world y ya la tenemos en nuestro sistema. Y dicha descarga ha sido realizada desde el registro “Docker Hub” que es el que se usa por defecto (más adelante veremos cómo usar otros registros alternativos).
