Docker Container
La solución de virtualización Docker ha cambiado radicalmente a lo largo de la última década cómo se crea, comparte y opera el software. Ahora, las aplicaciones individuales se virtualizan con Docker, a diferencia de las máquinas virtuales (VM) que se utilizaban anteriormente. Por tanto, un Docker Container es un contenedor de software o aplicaciones.
El concepto de contenedor de software se basa en los contenedores físicos, como los que transportan los barcos. Los contenedores como unidad estandarizada de la logística han hecho posibles las cadenas comerciales modernas. Un contenedor puede ser transportado en cualquier barco, camión o tren diseñado para ello, algo que funciona normalmente con independencia del contenido del contenedor. En el exterior, el contenedor está dotado de interfaces estandarizadas. Esto es muy similar al caso de los Docker Container.
- Domina el mercado con nuestra oferta 3x1 en dominios
- Tu dominio protegido con SSL Wildcard gratis
- 1 cuenta de correo electrónico por contrato
¿Qué es un Docker Container?
¿Qué es exactamente un Docker Container? Dejemos que nos lo explique el creador de Docker:
“Los contenedores son una unidad estandarizada de software que permiten a los desarrolladores aislar una app de su entorno” (Traducción de IONOS)
Cita original: “Containers are a standardized unit of software that allows developers to isolate their app from its environment”. – Fuente: https://www.docker.com/why-docker
Al igual que muchos contenedores físicos pueden remontarse a una única especificación, es posible crear el número de contenedores Docker que se quiera a partir de una sola imagen. De esta manera, los contenedores Docker sientan las bases para servicios escalables y los entornos de aplicación reproducibles. Podemos crear un Container desde una Image así como guardar Containers existentes en una nueva Image. Dentro del contenedor se inician, pausan y concluyen los procesos.
Un Docker Container, a diferencia de lo que ocurre con la virtualización mediante máquinas virtuales (VM), no contiene un sistema operativo propio (OS, por sus siglas en inglés). En cambio, todos los contenedores que se ejecutan en un mismo host de Docker se remiten al mismo núcleo del sistema operativo. Al utilizar Docker en un host de Linux, se utilizará el núcleo de Linux correspondiente, pero si el software de Docker no funciona con un sistema Linux, se utiliza una reproducción mínima del sistema Linux mediante hipervisor o máquina virtual.
En la ejecución, se asigna a cada contenedor una cierta cantidad de recursos del sistema, entre los que se encuentran la memoria de trabajo, los núcleos de la CPU, la memoria de gran capacidad y los dispositivos de red (virtuales). Técnicamente, se limita el acceso de un Docker Container a los recursos del sistema mediante los llamados cgroups (Control Groups). Para crear particiones de los recursos del núcleo y para limitar los procesos entre sí se utilizan los llamados namespaces del núcleo.
Los contenedores de Docker se comunican con el exterior a través de la red. Para ello, se habilitan los puertos en los que escuchan determinados servicios, que suelen ser servidores web o de bases de datos. Los propios contenedores se controlan en el respectivo host de Docker a través de la API de Docker. Los contenedores, entre otras cosas, pueden iniciarse, detenerse y eliminarse. El cliente de Docker proporciona una interfaz de línea de comandos (Command Line Interface, CLI) con los comandos correspondientes.
¿En qué se diferencian Docker Container y Docker Image?
La dualidad de los conceptos Docker Container y Docker Image suele generar confusión; cosa que no sorprende, ya que se trata del dilema de la gallina y el huevo. Un contenedor se crea a partir de una imagen, pero un contenedor también puede guardarse como imagen nueva. Veamos en detalle qué diferencias hay entre estos conceptos.
Una Docker Image es una representación inerte. La imagen solo ocupa algo de espacio en el disco duro; nada más. En cambio, el contenedor de Docker es una instancia “viva”. Un Docker Container en ejecución tiene comportamiento: el contenedor interactúa con el entorno. Además, el contenedor tiene un estado que cambia con el tiempo y que ocupa una cantidad variable en la memoria de trabajo.
Quizás conozcas los conceptos de “clase” y “objeto” de la programación orientada a objetos (OOP). La relación entre Docker Container y Docker Image es de cierta manera comparable con la relación que existe entre objeto y la clase a la que pertenece. La clase solo existe; y a partir de ella se pueden crear varios objetos de la misma clase. La clase como tal se carga a partir de un archivo de código fuente. En el universo Docker hay un patrón similar. A partir de una unidad de código fuente, el “Dockerfile”, se crea un patrón del que se originarán de nuevo varias instancias:
Código fuente | Patrón | Instancia | |
---|---|---|---|
Concepto Docker | Dockerfile | Docker Image | Docker Container |
Analogía de programación | Código fuente clase | Clase cargada | Objeto instanciado |
Llamamos a los contenedores de Docker “instancias en ejecución” de la imagen a la que pertenecen. Los términos “instancia” e “instanciar” son abstractos. Si no estás muy enterado del tema, utiliza una regla mnemotécnica. Sustituye mentalmente “instanciar” por “moldear”. Aunque no haya relación entre estas palabras, en informática existe un alto grado de correlación entre sus significados. Imagínate el principio de esta manera: igual que cuando hacemos galletas iguales con un mismo molde a partir de una masa, instanciamos muchos objetos del mismo tipo a partir de una misma plantilla. La instanciación es por tanto el momento en el que creamos un objeto a partir de un patrón.
¿Cómo se crea un contenedor de Docker?
Para entender cómo se construye un Docker Container, es útil echar un vistazo a la metodología de la “App de 12 factores”. Se trata de una recopilación de doce principios fundamentales para crear y operar software orientado a servicios. Tanto Docker como la app de doce factores nacieron en 2011. La app de doce factores ayuda a los desarrolladores a crear apps de software como servicio siguiendo estándares determinados. Entre ellos se encuentran:
- Utilizar formatos declarativos que automaticen la configuración y ahorren tiempo y costes a los desarrolladores nuevos en el proyecto
- Tener en cuenta el sistema operativo y garantizar la máxima portabilidad entre planos de ejecución
- Favorecer el despliegue en plataformas en la nube (evitar servidores y administración de sistemas)
- Desarrollo y producción unitarios para permitir un despliegue continuo ágil
- Escalabilidad sin tener que modificar las herramientas, la arquitectura o las prácticas de desarrollo
La construcción de un contenedor de Docker se orienta por estos principios. Un Docker Container reúne los componentes que cubrimos a continuación en detalle:
- Sistema operativo de contenedores y sistema de archivos de unión
- Componentes y configuración de software
- Variables de entorno y configuración de tiempo de ejecución
- Puertos y volumen
- Procesos y logs
Sistema operativo de contenedores y sistema de archivos de unión
A diferencia de las máquinas virtuales, un Docker Container no tiene un sistema operativo propio. En lugar de eso, todos los contenedores que se ejecutan en un host acceden a un núcleo de Linux compartido. En el contenedor solo se incluye una capa de ejecución mínima, en la que suele estar la implementación de la biblioteca estándar C así como un Shell de Linux para ejecutar procesos. He aquí un resumen de los componentes de la Image oficial de “Alpine Linux”:
Kernel de Linux | Biblioteca estándar C | Comandos de Unix |
---|---|---|
Del host | musl libc | BusyBox |
Una Docker Image consiste en un montón de capas, en inglés “layers”, de sistema de archivos de solo lectura. Una capa describe los cambios en el sistema de archivos a la capa inferior. Utilizando un sistema de archivos de unión especial como overlay2, las capas se superponen y se unifican en una superficie coherente. Cuando se crea un Docker Container a partir de una Image, se añade otra capa de escritura a las capas de solo lectura. Todos los cambios en el sistema de archivos se incorporan a la capa escribible mediante el procedimiento “copy on write”.
Componentes y configuración de software
A partir del sistema operativo de contenedores mínimo, se instalan otros componentes de software en un contenedor Docker. Luego, se suelen realizar otros pasos de instalación y configuración. Para la instalación se utilizan las rutas habituales:
- Mediante gestores de paquetes del sistema como apt, apk, yum, brew, etc.
- Mediante gestores de paquetes de lenguajes de programación como pip, npm, composer, gem, cargo, etc.
- Mediante la compilación en el contenedor con make, mvn, etc.
He aquí algunos ejemplos para componentes de software típicamente instalados en Docker Container:
Ámbito de aplicación | Componentes de software |
---|---|
Lenguaje de programación | PHP, Python, Ruby, Java, JavaScript |
Herramientas de desarrollo | node / npm, React, Laravel |
Sistemas de bases de datos | MySQL, Postgres, MongoDB, Redis |
Servidores web | Apache, nginx, lighttpd |
Caches y proxis | Varnish, Squid |
Sistemas de gestión de contenido | WordPress, Magento, Ruby on Rails |
Variables de entorno y configuración de tiempo de ejecución
Siguiendo la metodología de las app de doce factores, la configuración de un contenedor Docker se almacena en variables de entorno. Por configuración, nos referimos a todos los valores que cambian entre los diferentes entornos, como el sistema de desarrollo frente al de producción. Esto suele incluir nombres de host y credenciales de la base de datos.
Los valores de las variables de entorno influyen en el comportamiento del contenedor. Para que las variables de entorno estén disponibles dentro de un contenedor, se utilizan principalmente dos vías:
1. Definición en Dockerfile
Una variable de entorno se declara en un Dockerfile con la instrucción ENV. Ahí es posible asignar un valor por defecto, que se utilizará si la variable de entorno está vacía al iniciar el contenedor.
2. Pasar variable al iniciar el contenedor
Para acceder en el contenedor a una variable de entorno que no ha sido declarada en el Dockerfile, pasamos la variable al iniciar el contenedor. Esto sirve para las variables individuales a través de parámetros de la línea de comandos. Además, es posible pasar el archivo llamado Env, que define varias variables de entorno y sus valores.
Este es el patrón para pasar una variable de entorno al iniciar el contenedor:
docker run --env <env-var> <image-id></image-id></env-var>
En muchas variables de entorno merece la pena pasar un archivo Env:
docker run --env-file /path/to/.env <image-id></image-id>
Con el comando “docker inspect”, las variables de entorno de los contenedores pueden mostrar su valor, por lo que hay que tener cuidado al utilizar datos sensibles en variables de entorno.
Al iniciar el contenedor desde una imagen, se pueden transferir los parámetros de configuración, entre los que se encuentran las cantidades asignadas de los recursos del sistema, que por lo demás son ilimitados. Además, también se utilizan los parámetros de inicio para definir los puertos y volúmenes del contenedor (más información en la siguiente sección). Los parámetros de inicio pueden incluso sobrescribir los valores previamente asignados en el Dockerfile. A continuación, reunimos varios ejemplos.
Asignar un núcleo CPU y 10 megabytes de almacenamiento al Docker Container al iniciarlo:
docker run --cpus="1" --memory="10m" <image-id></image-id>
Liberar los puertos definidos en el Dockerfile al iniciar el contenedor:
docker run -P <image-id></image-id>
Mapear el puerto TCP 80 del host de Docker al puerto 80 del contenedor de Docker:
docker run -p 80:80/tcp <image-id></image-id>
Puertos y volumen
Un Docker Container contiene una aplicación aislada del mundo exterior. Para sacarle partido, debe poder interactuar con el entorno. Por eso, existen maneras de intercambiar datos entre host y container así como entre múltiples contenedores. Así, las interfaces estandarizadas permiten utilizar un contenedor en distintos entornos.
Comunicarse desde el exterior con los procesos en ejecución dentro de un container es posible mediante puertos en red liberados. Aquí se utilizan los protocolos estándar TCP y UDP. A modo de ejemplo, imaginemos un contenedor de Docker que contiene un servidor web y escucha al puerto 8080 TCP. Además, el Dockerfile de la Docker Image contiene la línea 'EXPOSE 8080/tcp'. Iniciamos el contenedor con 'docker run -P' y accedemos al servidor web desde la dirección 'http://localhost:8080'.
Los puertos sirven para la comunicación con los servicios que están ejecutándose dentro del contenedor. No obstante, en muchos casos tiene sentido utilizar un archivo compartido entre el contenedor y el sistema host para intercambiar datos. Con esta finalidad, Docker conoce distintos tipos de volumen:
- Volúmenes con nombre: recomendados
- Volúmenes anónimos: se pierden al eliminar el contenedor
- Bind mounts: históricamente relativos y no recomendados; performante
- Tmpfs mounts: están en la memoria de trabajo; solo en Linux
Hay diferencias sutiles entre tipos de volúmenes. Elegir el tipo adecuado depende significativamente del uso que se le vaya a dar. Explicarlos en mayor detalle se saldría del alcance de este artículo.
Procesos y logs
Un contenedor Docker suele encapsular una aplicación o servicio. El software que se ejecuta dentro del contenedor forma un conjunto de procesos en ejecución. Los procesos de un contenedor Docker están aislados de los procesos de otros contenedores o del sistema host. Dentro de un Docker Container, los procesos pueden iniciarse, detenerse y enumerarse. Se controlan a través de la línea de comandos o a través de la API de Docker.
Los procesos en ejecución emiten continuamente información de estado. Siguiendo la metodología de la app de doce factores, se utilizan los flujos de datos estándar STDOUT y STDERR para la salida. La salida de estos dos flujos de datos puede leerse con el comando 'docker logs'. Como alternativa, puede utilizarse el llamado controlador de registro. El controlador de registro estándar escribe los logs en formato JSON.
¿Cómo y dónde se utilizan los Docker Containers?
Actualmente, Docker se utiliza en todos los ámbitos del ciclo vital del software, entre ellos, el desarrollo, el testeo y las operaciones. Los contenedores en ejecución en un anfitrión Docker se dirigen mediante la API de Docker. El cliente Docker se hace cargo de los comandos de la línea de comandos: se utilizan herramientas especiales de orquestación para controlar las asociaciones de Docker Containers.
El patrón fundamental en la utilización de Docker Container es algo así:
- El anfitrión Docker se relaciona con la imagen Docker del Registry.
- Se crea e inicia el contenedor de Docker a partir de la imagen.
- La aplicación que contiene el contenedor se ejecuta hasta que se para o elimina el contenedor.
Hemos aquí dos ejemplos de uso de Docker Container:
Uso de Docker Container en entorno de desarrollo local
Uno de los usos más populares de Docker Container es el desarrollo de software. Normalmente son equipos de especialistas los que desarrollan un software. Para ello, se utiliza una colección de herramientas de desarrollo denominada toolchain, es decir, cadena de herramientas. Cada herramienta está disponible en una versión específica, y toda la cadena funciona únicamente si las versiones son compatibles entre sí. Además, la configuración de las herramientas debe ser correcta.
Docker se utiliza para garantizar la coherencia del entorno de desarrollo. Se crea una imagen Docker que contiene toda la cadena de herramientas correctamente configurada. Cada desarrollador del equipo extrae la imagen Docker en la máquina local e inicia un contenedor desde ella. El desarrollo se realiza entonces dentro del contenedor. Si hay un cambio en la toolchain, la imagen se actualiza de manera centralizada.
Uso de Docker Container en asociaciones orquestadas
En los centros de datos de los proveedores de hosting y proveedores PaaS (“Platform-as-a-Service”), se utilizan redes de contenedores Docker. Equilibradores de carga, servidores web, servidores de bases de datos, etc. Cada servicio se ejecuta en su propio Docker Container. Un solo contenedor puede únicamente soportar una determinada carga. Un software de orquestación supervisa el contenedor así como su estado y su ocupación. A medida que la carga aumenta, el orquestador inicia contenedores adicionales. Este enfoque permite que los servicios se amplíen rápidamente como respuesta a las condiciones cambiantes.
Ventajas e inconvenientes de la virtualización con contenedores de Docker
Las ventajas de virtualizar con Docker deben considerarse sobre todo con respecto al uso de máquinas virtuales (VM). En comparación con las máquinas virtuales, los Docker Container son mucho más ligeros. Pueden iniciarse con mayor rapidez y consumir menos recursos. Las imágenes en las que se basan los Docker Container también son órdenes de magnitud más pequeñas. Mientras que las imágenes de las máquinas virtuales suelen tener un tamaño de entre cientos de MB y varios GB, las imágenes de Docker empiezan con unos pocos MB.
Sin embargo, la virtualización de contenedores con Docker también tiene algunas desventajas. Como un contenedor no tiene un sistema operativo propio, el aislamiento de los procesos que se ejecutan en él no es del todo perfecto. Usar un gran número de contenedores conlleva una gran complejidad. Además, Docker es un sistema evolucionado, y la plataforma Docker hace demasiado, por lo que cada vez se hacen más esfuerzos para dividir los componentes individuales.