1.10.1. Docker Engine API
La comunicación entre un cliente Docker y el servicio Docker daemon se realiza a través de una API HTTP conocida como Docker Engine API.
Esta API implementa todas las operaciones que un usuario puede realizar desde el cliente oficial de Docker CLI; por ejemplo, cuando un usuario ejecuta el comando docker ps
desde la línea de comandos, el cliente Docker CLI está haciendo una petición GET al endpoint /containers/json
de la API. El servicio Docker daemon ejecuta la petición del cliente y le devuelve una respuesta en formato JSON.
La API suele cambiar cada vez que se libera una nueva versión de Docker Engine. Para que los clientes que tienen una versión antigua de la API puedan seguir manteniendo compatibilidad con las nuevas versiones de Docker Engine, se incluye un prefijo en la URL de los endpoints de la API indicando la versión que quieren utilizar para comunicarse; por ejemplo, una llamada al endpoint /v1.41/containers/json
utilizaría la versión 1.41 de la API, mientras que una llamada a /v1.40/containers/json
usaría la versión 1.40.
Puede consultar la especificación completa de la API en la web oficial de Docker. En el momento de escribir este libro, la última versión disponible es la v1.41:
En la actualidad, existe un SDK oficial para Go y otro para Python, que permite a los desarrolladores crear aplicaciones que interactúan con la API de Docker Engine. También existe una gran variedad de librerías no oficiales que han sido desarrolladas por la comunidad para otros lenguajes de programación. A continuación, se muestra un ejemplo de cómo se puede hacer uso de la API de Docker Engine con la utilidad curl
.
Ejemplo
En este ejemplo, vamos a utilizar la utilidad curl
como cliente para realizar una llamada al endpoint http://localhost/v1.41/containers/json
de la API de Docker Engine, para obtener un listado de todos los contenedores que están en ejecución:
1. En este ejemplo, utilizamos un socket UNIX porque el cliente y el servicio Docker daemon se encuentran en la misma máquina. Como este ejemplo se ha realizado en una máquina Linux, el socket que utiliza el servicio Docker daemon está ubicado en /var/run/docker.sock
.
2. Indicamos el endpoint de la API de Docker Engine al que queremos hacer la llamada.
Si no existe ningún contenedor en ejecución, obtendremos una respuesta vacía:
Pero, si existen contenedores en ejecución, obtendremos una respuesta similar a la que se muestra a continuación. Tenga en cuenta que se han omitido algunos valores de la respuesta para simplificarla y mejorar su legibilidad:
Si desea profundizar más sobre el empleo de los SDKs de Docker, puede consultar la web oficial, donde encontrará diferentes ejemplos de uso:
1.10.2. Docker daemon
El servicio Docker daemon es el encargado de crear y gestionar todos los objetos con los que trabaja Docker, como las imágenes, los contenedores, las redes y los volúmenes. Este servicio se ejecuta en un proceso llamado dockerd
.
El cliente Docker se comunica con el servicio Docker daemon a través de una API HTTP y el servicio Docker daemon se comunica con el container runtime. En las primeras versiones, el Docker daemon también incluía el container runtime, pero, actualmente, son dos componentes independientes. El container runtime de Docker está formado por containerd y runc, de los que hablaremos más adelante.
El servicio Docker daemon expone una API HTTP para comunicarse con los clientes Docker y puede utilizar tres tipos de sockets para interaccionar con ellos: unix
, tcp
y fd
.
En una instalación habitual, el cliente Docker y el Docker daemon suelen estar en la misma máquina. Cuando trabajamos con contenedores Linux, se utiliza un socket de tipo UNIX que estará en la ruta /var/run/docker.sock
. Para poder hacer uso de este socket, es necesario tener permisos de root
o pertenecer al grupo de usuarios del sistema docker
. Cuando trabajamos con contenedores Windows, se utiliza un named pipe, que estará en la ruta \\.\pipe\docker_engine
.
Si vamos a trabajar en un entorno donde el cliente y el Docker daemon se ejecutan en diferentes máquinas, entonces necesitaremos utilizar un socket TCP. En este caso, la comunicación se realiza por defecto sobre un canal no seguro en el puerto 2375. Esta configuración puede ser adecuada para un entorno de desarrollo, pero nunca se debe utilizar en un entorno de producción. En un entorno de producción, se debe usar una conexión cifrada con TLS y se suele emplear el puerto 2376.
1.11. Container runtime
El container runtime es el software encargado de ejecutar los contenedores. Docker Engine utiliza dos tipos de container runtime:
1.11.1. containerd
Es un container runtime de alto nivel, que se ejecuta como un proceso daemon. Este componente se encarga de administrar el ciclo de vida de un contenedor dentro de un host. Realiza tareas como descargar las imágenes de los registros de los contenedores, almacenarlas en el host, supervisar la ejecución de los contenedores o gestionar el almacenamiento y las redes.
Este componente está diseñado para ser embebido dentro de otros sistemas más complejos. Aunque puede ser utilizado desde la línea de comandos con el cliente ctr
, que se incluye en la instalación por defecto.
Se trata de un proyecto open source, que fue creado por Docker, Inc. en 2016, junto a Google e IBM. En 2017, fue donado a la Cloud Native Computing Foundation (CNCF) y, en 2019, se convirtió en un proyecto graduado de la CNCF. Esto indica que el proyecto se encuentra en un nivel de madurez adecuado para ser utilizado por las empresas más conservadoras a la hora de adoptar nuevas tecnologías.
El componente containerd también implementa la interfaz CRI (Container Runtime Inferface) de Kubernetes. Esto quiere decir que este container runtime puede ser utilizado en un cluster de Kubernetes para crear y ejecutar contenedores a partir de imágenes Docker, que son imágenes compatibles con la especificación OCI.
1.11.2. runc
Es un container runtime de bajo nivel, encargado de interaccionar con el kernel del host de la máquina anfitriona donde se ejecutan los contenedores. Utiliza el componente libcontainer para interaccionar con el sistema operativo del host.
Se trata de una implementación de código