Operativa básica **************** Introducción ============ Antes de entrar en harina, es preciso distinguir entre los conceptos de *imagen* y *contenedor*: :dfn:`Imagen` Es una entidad inmutable constituida por una serie de archivos (que constituyen un sistema de archivos) y una metainformación que describe algunos aspectos de la virtualización. :dfn:`Contenedor` Es la instanciación de una *imagen* para su uso. En el mundo de :program:`Docker` una *imagen* es a un *contenedor* lo que una *clase* a un *objeto* en el mundo de la |POO|. Así pues, una imagen es una plantilla con la que se crean uno o más contenedores, que serán aquellos dentro de los cuales correrá la aplicación que hayamos "*dockerizado*". Además, es necesario entender que :program:`Docker` dispone fundamentalmente de tres componentes: :dfn:`Demonio` Que es la base de todo, ya que es el que se encarga de gestionar y construir imágenes y contenedores. Por tanto, es él el que sostiene toda la lógica del asunto. Por ejemplo, cuando se da la orden de crear y ejecutar un contenedor a partir de una imagen es este demonio el que toma la imagen de su repositorio local y genera el contenedor. :dfn:`Cliente` Es la herramienta encargada de comunicarse con el demonio para transmitirle las órdenes que estimemos pertinentes. Puede encontrarse en la misma máquina que el demonio (y en ese caso se comunicará con éste a través del *socket* :file:`/var/run/docker.sock`) o en una máquina remota para lo cual el demonio dispone una |API| REST a la cual se puede acceder por |HTTP|\ [#]_. El cliente habitual es la órden |CLI| :program:`docker` que usaremos en esta pequeña introducción, pero existen muchos otros como, por ejemplo, `Portainer `_, que usa una interfaz *web*. :dfn:`Registro` Es el servicio donde se almacenan imágenes que puede descargar el demonio en su repositorio local a fin de utilizarlas para generar contenedores. Por lo general, es `Docker Hub`_, aunque puede establecerse otro distinto a través del uso de `docker login `_. .. image:: files/componentes.png Para disponer de :program:`Docker`, podemos **instalar** `la edición de la comunidad según las instrucciones oficiales `_ o, simplemente, los paquetes disponibles en la propia *Debian* a partir de Buster_:: # apt install docker.io # docker version Client: Version: 19.03.6 API version: 1.40 Go version: go1.13.8 Git commit: 369ce74 Built: Wed, 26 Feb 2020 11:20:11 +1100 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 19.03.6 API version: 1.40 (minimum version 1.12) Go version: go1.13.8 Git commit: 369ce74 Built: Wed Feb 26 00:20:11 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: 19.03.6 GitCommit: 7c1e88399ec0b0b077121d9d5ad97e647b11c870 runc: Version: 1.0.0~rc10+dfsg1 GitCommit: 1.0.0~rc10+dfsg1-1 docker-init: Version: 0.18.0 GitCommit: El paquete instala en nuestro máquina tanto el demonio como el cliente oficial, de manera que éste último conecta con el primero a través del *socket* ya referido, y usará como registro `Docker Hub`_. En este *socket* tiene permisos de escritura el grupo *docker*, por lo que cualquier usuario que pertenezca a este grupo podrá usar el cliente para manejar imágenes y contenedores. En consecuencia, si queremos que el usuario "pepe" sea capaz de manejar contenedores, podemos simplemento añadirlo a tal grupo (que la instalación de *Debian* crea automáticamente):: # adduser pepe docker .. note:: El demonio almacena imágenes y contenedores dentro de :file:`/var/lib/docker`. Esto supone que dentro de ese directorio acabará habiendo una gran cantidad de datos, por lo que quizás puede que nos interese montar ese directorio en un sistema de archivos independiente. .. _docker-image: Imágenes ======== Como ya se ha establecido, las :dfn:`imágenes` son las plantillas a partir de las cuales se crean los contenedores. Hay tres métodos para obtenerlas: #. Construyéndolas nosotros mismos con :ref:`docker build `. #. Generando una a partir de un contenedor con :ref:`docker commit `. #. Obteniéndo las imágenes de un `registro de imágenes `_. El registro existente más importante es `Docker Hub`_. Dejaremos el estudio de los dos primeros métodos al tratar la :ref:`construcción de imáganes ` y bajo este epígrafe nos centraremos en cómo obtener imágenes de `Docker Hub`_. Si nos registramos en el sitio tendremos la posibilidad de crear nuestros propios repositorios con imágenes creadas por nosotros mismos a través de las dos vías citadas anteriormente, pero sin necesidad de registro podemos usar los repositorios públicos que distintos usuarios y organizaciones mantienen en el sitio. Por ejemplo: .. _docker-pull: .. _docker-image-pull: .. code-block:: console $ docker image pull debian obtiene y almacena en nuestro repositorio local la imagen oficial de *Debian*: .. _docker-image-ls: .. code-block:: console $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE debian latest 971452c94376 4 weeks ago 114MB .. note:: Con el subcomando :kbd:`ls` podemos usar un patron con comodines como los que se usan en la *shell* (p.e. :kbd:`docker image ls de*`). Como vemos ya tenemos descargada la imagen de *Debian* en nuestro repositorio local, lista para ser usada en la creación de contenedores. La imagen se ha obtenido de `Docker Hub`. Ahora bien, ¿cómo sabemos que existe esta imagen en el repositorio? La respuesta es obvia: buscando previamente en el registro. Y del mismo que, por ejemplo, existe :ref:`apt search ` para buscar paquetes disponibles en *Debian*, existe :kbd:`docker search` para buscar imágenes en `Docker Hub`_: .. _docker-search: .. code-block:: console $ docker search debian aunque tenemos también la alternativa de visitar la web y hacer la búsqueda directamente en ella. Escogida cuál es la imagen que deseamos utilizar es importante tener presente qué significa la etiqueta (*TAG*) que acompaña al nombre. La etiqueta identifica distintas versiones del contenedor que pueden responder bien a distintas versiones del paquete que contienen, bien a qué es lo que realmente contienen. Por ejemplo, acabamos de instalar la imagen *debian:latest* (porque al no indicar etiqueta se sobrentiende que es la más reciente, o sea, la latest). Si investigamos la imagen avewriguaremos que esta imagen coincide con la que se hace para Buster_ que es la actual estable:: $ docker image pull debian:buster $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE debian latest 971452c94376 4 weeks ago 114MB debian buster 971452c94376 4 weeks ago 114MB Lo cual se hace evidente, porque no hemos tenido que esperar la descarga de la imagen y el identificador de la imagen es exactamente el mismo. Por supuesto también existe *debian:bullseye* o *debian:stretch*; pero también hay versiones *slim* que incluyen menos *software* y, por tanto, son más ligeras:: $ docker image pull debian:buster-slim $ docker image ls *buster* debian buster-slim 2f14a0fb67b9 4 weeks ago 69.2MB debian buster 971452c94376 4 weeks ago 114MB Como con las imágenes de *Debian*, sucede con otras muchas. Por ejemplo, |PHP| tiene sus propias imágenes, muchísimas en realidad porque varía en ellas desde la versión de |PHP| usada a cuál es la distribución base que han utilizado (Buster_, Alpine_) o qué software adicional se ha incluido dentro (p.e. el propio servidor Apache_). Esta variadad de imágenes es bastante lógica si atendemos a que una de las principales funciones de *Docker* es proporcionar un método portable para distribuir aplicaciones y servicios. Antes de continuar, es muy productivo parar un momento a analizar cómo funcionan las órdenes del cliente: + En cualquier punto podemos utilizar el argumento :kbd:`--help` para conocer qué es lo que podemos añadir a continuación. Por ejemplo, para saber qué subcomandos podemos usar para manipular imágenes:: $ docker image --help Usage: docker image COMMAND Manage images Commands: build Build an image from a Dockerfile history Show the history of an image import Import the contents from a tarball to create a filesystem image inspect Display detailed information on one or more images load Load an image from a tar archive or STDIN ls List images prune Remove unused images pull Pull an image or a repository from a registry push Push an image or a repository to a registry rm Remove one or more images save Save one or more images to a tar archive (streamed to STDOUT by default) tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE De los expuestos hemos usado ya los subcomandos :kbd:`pull` y :kbd:`ls`, e incluso con estos hay distintas opciones que no hemos llegado a usar:: $ docker image ls --help Usage: docker image ls [OPTIONS] [REPOSITORY[:TAG]] List images Aliases: ls, images, list Options: -a, --all Show all images (default hides intermediate images) --digests Show digests -f, --filter filter Filter output based on conditions provided --format string Pretty-print images using a Go template --no-trunc Don't truncate output -q, --quiet Only show numeric IDs * El primer subcomando (o sea, el subcomando inmediatamente posterior a :program:`docker`) indica sobre qué entidad se lleva a cabo la orden. En nuestro caso, estamos usando. :kbd:`image` puesto que estamos actuando sobre imágenes. Sin embargo, esta no fue la filosofía inicial de la orden, por lo que se mantiene tambien una sintaxis antigua que no seguí esta filosofía. Por ese motivo: .. table:: :class: docker-eq +--------------------+----------------------------+ | Sintaxis | Sintaxis simplificada | +====================+============================+ | docker image pull | docker pull | +--------------------+----------------------------+ | docker image push | docker push | +--------------------+----------------------------+ | docker image rm | docker rmi | +--------------------+----------------------------+ | docker image build | docker build | +--------------------+----------------------------+ El resto de entidades también tienen asociadas órdenes con sintaxis reducida. Algunos otras órdenes sobre imágenes las trataremos al examinar cómo se construyen. Ahora, sin embargo, es interesante saber cómo eliminarlas: .. _docker-image-rm: .. _docker-rmi: .. code-block:: console $ docker image rm debian:buster-slim para lo cual puede usarse el nombre (con la etiqueta) o el identificador. Es importante notar que para poder borrar una imagen no puede existir un contenedor que se haya generado con ella. Para fozar el borrado de la imagen y de todos los contenedores creados a partir de ella, puede usarse la opción :kbd:`-f`. Relacionado con las opciones de borrado está: .. _docker-image-prune: .. code-block:: console $ docker image prune que borra todas las imagenes antiguas de las que se ha descargado una versión más actualizada (son aquellas en cuya columna :kbd:`TAG` aparece la etiqueta :kbd:``) y que, además, no tienen contenedor asociado. Si lo que se quiere es borrar todas las imagenes sin contenedor asociado, aunque estén en su última versión, debe añadirse la opción :kbd:`-a`:: $ docker image prune -a Para consultar las características de la imagen podemos usar :kbd:`inspect`:: $ docker image inspect debian:buster-slim que las devuelve en formato |JSON|. Al inspeccionar puede interesarnos obtener sólo una parte de la información para lo cual puede usarse la opción :kbd:`-f`:: $ docker image inspect -f '{{json .Config.Cmd}}' debian:buster-slim ['bash'] $ docker image inspect -f '{{json .RootFS}}' debian:buster-slim {"Type":"layers","Layers":["sha256:f2cb0ecef392f2a630fa1205b874ab2e2aedf96de04d0b8838e4e728e28142da"]} .. note:: Al realizar una operación sobre una imagen (lo mismo ocurre con otras entidades) podemos referirnos a ella por su nombre o por su identificador único. Contenedores ============ Una imagen es, simplemente, una plantilla para crear contenededores que ejecutan aplicaciones. Por ello, un contenedor se crea instanciando una imagen e indicando cuál es la aplicación que deseamos ejecutar. Por ejemplo: .. _docker-run: .. _docker-container-run: .. code-block:: console $ docker run -ti debian:buster-slim bash root@fd65c6309e43:/# echo "Estoy ejecutando bash en el contenedor" Estoy ejecutando bash en el contenedor root@fd65c6309e43:/# exit .. note:: No es necesario descargar la imagen previamente con :kbd:`docker image pull`. Si la imagen que se invoca con :kbd:`docker run` (o el :kbd:`docker create` que veremos después) no existe en el repositorio local, se descargará del registro automáticamente. :kbd:`docker run` es el encargado de ello, aunque es la sintaxis simplificada de :kbd:`docker container run`. En la consola de :program:`bash` que se ejecuta dentro del contenedor, el sistema de archivos es una superposición de los archivos que proporciona la imagen y los archivos que puedan generarse durante la ejecución del contenedor\ [#]_. En la orden hay tres argumentos posiciones: * :kbd:`-ti` que en realidad son las opciones :kbd:`-t` y :kbd:`-i` y posibilta el uso interactivo, que es lo que realizamos a continuación. En contraposición :kbd:`-d` se usa cuando el contenedor levanta un servicio y queremos que se libere la línea de órdenes del anfitrión. * La imagen de la que deseamos crear un contenedor. * Qué programa queremos arrancar con el contenedor. El que digamos que :program:`bash`, posibilita que obtengamos una consola interactiva en la que podríamos haber llevado a cabo varias acciones, entre las cuales podría haberse encontrado instalar *software* adicional usando :ref:`apt `. Las imágenes, sin embargo, pueden predefinir un comando, de modo que si al crear un contenedor, no se especifica comando alguno, será el predefinido el que se ejecute. En este caso: .. _docker-image-inspect: .. code-block:: console $ docker image inspect -f '{{.Config.Cmd}}' debian:buster-slim [bash] Esa orden ya era :program:`bash`, así que podríamos habernos ahorrado la expresión de la orden. Es importante tener presente que el contenedor sólo tiene sentido como contenedor del programa que se pretende correr. Por ese motivo, el contenedor está en ejecución mientras dura nuestra sesión de :program:`bash` y al salir de ella (como hemos hecho con :ref:`exit `), el contenedor se para. Por ese motivo, :kbd:`docker ps` (o :kbd:`docker container ls`): .. _docker-ps: .. _docker-container-ls: .. code-block:: console $ docker ps no devuelve contenedor alguno, a pesar de haberse creado. Esto es debido a que, en principio, sólo se muestran los contenedores en ejecución. En cambio, si añadimos la opción :kbd:`-a`:: $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fd65c6309e43 debian:buster-slim "bash" 25 minutes ago Exited (0) 23 minutes ago vigorous_greider en donde podemos leer un resumen de las características del contenedor. También son interesantes las opciones: * :kbd:`-s`, que muestra el tamaño del contenedor. * :kbd:`-l` que se limita a mostrar el último contenedor arrancado con independencia de cuál sea su estado. * :kbd:`--filter` que permite filtrar la salida del listado utilizando distintos criterios (consúltese la página del manual :manpage:`docker-container-ls`). Obsérvese que en el listado el identificador de la máquina coincide con el nombre de *host* y que aparece en el *prompt* de la sesión interactiva que abrimos anteriormente. Además, el demonio ha asignado un nombre generado aleatoriamente al contenedor (*vigorous_greater*). A partir de ahora, podremos referirnos al contenedor tanto usando su nombre como su identificador. Estas asignaciones de nombres automáticas podemos manipularlas, pero antes de intentar hacerlo, notemos que :kbd:`docker run` no arranca sin más un contenedor, sino que lo crea y lo arranca. Si hubiéramos querido crearlo sin llegar a arrancarlo, podríamos haber usado :kbd:`docker container create` o, simplemente, :kbd:`docker create`: .. _docker-create: .. _docker-container-create: .. code-block:: console $ docker create -ti debian:buster-slim $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c2e42d8b9b94 debian:buster-slim "bash" 2 seconds ago Created trusting_babbage fd65c6309e43 debian:buster-slim "bash" 25 minutes ago Exited (0) 23 minutes ago vigorous_greider que, como vemos, se ejecuta igual, aunque hemos obviado la orden de ejecución, porque ya sabemos que será :program:`bash`. Para arrancar un contenedor previamente parado, debemos usar :kbd:`docker start`, (o :kbd:`docker container start`) que retomará las opciones con las que creamos el contenedor: .. _docker-start: .. _docker-container-start: .. code-block:: console $ docker start -i vigorous_greider root@fd65c6309e43:/# Ahora, en cambio, no acabamos la sesión de :program:`bash` y consecuentemente el contenedor seguirá en ejecución. Con el contenedor en ejecución, podemos interactuar con el. Por ejemplo: * ejecutando algún comando adicional. Por ejemplo: .. _docker-exec: .. _docker-container-exec: .. code-block:: console $ docker exec fd65c6309e43 hostname fd65c6309e43 * transfiriendo archivos: .. _docker-container-cp: .. _docker-cp: .. code-block:: console $ docker cp ~/texto.txt vigorous_greider:/tmp $ docker cp vigorous_greider:/tmp/texto.txt /tmp * parando el contenedor: .. _docker-container-stop: .. _docker-stop: .. code-block:: console $ docker stop vigorous_greider vigorous_greider que provocará la salida automática de la sesión interactiva de :program:`bash` que dejamos pendiente. * parando y reiniciando el contenedor en la imisma operación: .. _docker-container-restart: .. _docker-restart: .. code-block:: console $ docker restart -t30 vigorous_greiter La opción :kbd:`-t` pospone el número de segundos indicados la parada y reinicio. SI no la incluimos, la operación será inmediata. Tanto para un contenedor parado como en funcionamiento, podemos revisar su registro cómodamente con :manpage:`docker-container-logs` que utiliza una sintaxis semejante a la orden :ref:`tail ` (podremos usar las opciones :kbd:`-n` y :kbd:`-f`)\ [#]_: .. _docker-logs: .. _docker-container-logs: .. code-block:: console $ docker logs -n5 portainer 2021/03/27 06:10:47 server: Reverse tunnelling enabled 2021/03/27 06:10:47 server: Fingerprint d7:44:c1:c2:08:59:1a:7e:e1:3c:f0:e1:33:8b:5a:8e 2021/03/27 06:10:47 server: Listening on 0.0.0.0:8000... 2021/03/27 06:10:47 Starting Portainer 1.23.2 on :9000 2021/03/27 06:10:47 [DEBUG] [chisel, monitoring] [check_interval_seconds: 10.000000] [message: starting tunnel management process] Por otra parte, cuando se crea un contenedor podemos definir tanto el nombre del contenedor como el nombre de la máquina:: $ docker run --rm -ti --name=debiandock --hostname=debiandock debian:buster-slim root@debiandock:/# echo "Este contenedor es efímero" root@debiandock:/# exit En este caso, además, hemos incluido la opción :kbd:`--rm` que provoca que el contenedor se destruya en cuanto acabe la ejecución de la aplicaciói (:program:`bash` en este caso). Utilicemos una imagen distinta para ilustrar otras posibilidad al ejecutar un contenedor:: $ docker run --rm -d --name=nginx-test -p 8080:80 nginx:alpine 818ba206cb7158a9fe44c58649f9e47b39c11ede9a9fd9deb62174838f7c6420 En este caso, ejecutamos un contenedor oficial de :ref:`nginx ` construido sobre Alpine_ (lo cual nos asegura que su tamaño es mínimo). Como el contenedor ejecuta un servidor web utilizamos :kbd:`-d` para evitar que se quede ocupada la terminal del sistema anfitrión\ [#]_ y publicanmos el puerto **80** del contenedor en el **8080** del sistema anfitrión\ [#]_. Si en estas condiciones, hacemos, por ejemplo:: $ wget -qS --spider http://localhost:8080 HTTP/1.1 200 OK Server: nginx/1.17.9 Date: Thu, 26 Mar 2020 18:42:48 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 03 Mar 2020 17:36:53 GMT Connection: keep-alive ETag: "5e5e95b5-264" Accept-Ranges: bytes accederemos a la página que ofrezca el servidor web. Si nuestra intención es montar un sitio web con esta imagen tendremos, obviamente, que hacer algo más, pero ahora no es el momento. Podemos comprobar eso sí, el mapeo de puertos que hace este contenedor:: $ docker port nginx-test 80/tcp -> 0.0.0.0:8080 En realidad, la publicación del puerto **80** es posible porque al crear la imagen el autor expuso el puerto **80**. No así el **443** (de hecho, la imagen no tiene ningún certificado), por lo que no puede publicarse tal puerto. Una alternativa es pedirle al demonio que publique todos los puertos que mandó exponer el autor en puertos libres escogidos aleatoriamente:: $ docker run --rm -d --name=nginx-test -P nginx:alpine $ docker port nginx-test 80/tcp -> 0.0.0.0:32768 En este caso, es imperioso consultar el puerto para saber cómo conectar al servidor web. Finalmente, para eliminar un contenedor podemos simplemente usar :kbd:`docker rm` o :kbd:`docker container rm`:: $ docker rm nginx-test Ahora bien, si el contenedor se encuentra arrancada, entonces será necesario o pararlo primero o forzar su borrado:: $ docker rm -f nginx-test .. note:: Un truco para eliminar todas las imágenes y contenedores es:: $ docker container rm -fv `docker container ls -aq` $ docker image rm prune donde además de :kbd:`-f` hemos utilizado la opción :kbd:`-v` para borrar también los volúmenes asociados a contenedores. Veremos este concepto en el próximo epígrafe. .. note:: Hay un aspecto muy importante de los contenedores que no hemos tratado: su **política de ejecución**. Dado que un contenedor de :program:`Docker` se crea para la ejecución de una aplicación, hay que arrancar manualmente el contenedor para poner en ejecución la aplicación y, cuando la aplicación acaba, el contenedor se para. Esto hecho, sin embargo, puede ser que no nos interese, sobre todo si esa aplicación es un servidor. Por ejemplo, podría interesarnos que ante un falló que haga colapsar la máquina, el demonio de :program:`Docker` reinicio el servidor, o bien, que ante un reinicio del propio demonio, también se reinicie el contenedor. Trataremos esto al ver los :ref:`ejemplos finales `. Debe tenerse presente que al borrarse el contenedor, desaparecen todos los datos que pudiera contener. .. _docker-volume: Volúmenes ========= Un :dfn:`volumen` es un directorio externo montado en el contenedor. Por ello, los datos que se almacenan en un *volumen*, aunque accesibles, no forman parte del contenedor y ni desaparecen al borrarse el contenedor ni están sujetos al control de almacenamiento que lleva a cabo el demonio. Son útiles para: - Hacer la información persistente más allá de la vida del contenedor. - Servir de almacenamiento cuando se está constantemente escribiendo, ya que los contenedores están pensados para que se escriban en ellos pocos datos. Por ejemplo, un *volumen* sería muy adecuado si quisiéramos almacenar los *logs* de un servicio. - Compartir datos entre el anfitrión y los contenedores. Hay tres tipos de volúmenes :dfn:`Volumen temporal` que es un espacio de memoria |RAM| que se monta como directorio para datos no persistentes en el contenedor (véase `tmpfs `_). :dfn:`Volumen de host` (o :dfn:`bind mount`) es, simplemente, un directorio ya existente en el anfitrión que se monta sobre un contenedor para que ambos puedan compartir la información. :dfn:`Volumen de datos` (o, simplemente, :dfn:`volumen`) es un directorio creado expresamente para ser montado en los contenedores. Se gestionan mediante :manpage:`docker-volume`, como ya veremos; y, obviamente, acaban resultando también directorios del anfitrión creados dentro de :file:`/var/lib/docker`. La diferencia es que los *volúmenes de host*, aunque directorios del anfitrión, tienen existencia justificada al margen de :program:`Docker`. Para ilustrar el uso de los dos últimos podemos usar Portainer_ que es un contenedor que contiene un cliente web para el demonio de :program:`Docker`. El cliente necesita dos cosas: - Por un lado, debe comunicarse con el demonio o lo que es lo mismo, acceder al socket :file:`/var/run/docker.sock` del anfitrión. Esto podemos resolverlo con un *volumen de host*. - Por otro, la aplicación necesita manejar una base de datos y ello implica escrituras. Para hacerlas, se usa, en este caso, un *volumen de datos* creado para ese efecto. Así pues, el uso de tal aplicación puede hacerse así: .. _docker-volume-create: .. code-block:: console $ docker volume create portainer_data $ docker run -d -P --name=portainer --restart=unless-stopped \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer 5bab0fd1699832eb30310e4a5fb6ccd19b7e3171ead2df84e3426b67ef10b6cb $ docker port portainer 9000/tcp -> 0.0.0.0:32768 donde primero se crea el *volumen de datos* y después se crea y ejecuta el contenedor utilizan este volumen y haciendo que el *socket* se comparta como *volumen de host*. La aplicación escucha en el puerto **9000** del container que se ha mapeado al **32768** del anfitrión. Listo. Desde el navegador, conectándonos a *http://localhost:32768* podremos gestionar el demonio mediante una interfaz web. Hay, no obstante, una precisión que hacer respecto a cómo se declaran los volúmenes. Ambos tipos se declaran mediante la opción :kbd:`-v` y la sintaxis para establecer el volumen y el punto de montaje es la misma. La forma que tiene el demonio de distinguir un tipo de volumen de otro es la forma en la que expresamos el origen: - Si se declara una **ruta obsoluta**, se trata de un *volumen de host*. - Si se trata de un **nombre**. debe ser el nombre de un *volumen de datos* que, en caso de no existir, se creará. De lo que se deduce que podríamos habernos ahorrado la primera orden de creación. - Si sólo se expresa el punto de montaje (sin siquiera los dos puntos), se sobreentiende un *volumen de datos* al que se le asigna un nombre aleatorio. A estos volúmenes se les denomina :dfn:`volúmenes de datos anónimos`. .. note:: Para declarar un volumen temporal, debe recurrirse a la opción :kbd:`--tmpfs`. Al crear volúmenes puede ser interesante usar la opción :kbd:`--label` para añadir parejas clave-valor a los metadatos del volumen. que pueden ayudar luego como filtro al listarlos o borrarlos:: $ docker volume create --label service=http --label server=nginx htdocs $ docker volume ls --filter label=server=nginx DRIVER VOLUME NAME local htdocs Además de crear volúmenes, podemos llevar a cabo otras acciones básicas con ellos:: $ docker volume --help Usage: docker volume COMMAND Manage volumes Commands: create Create a volume inspect Display detailed information on one or more volumes ls List volumes prune Remove all unused local volumes rm Remove one or more volumes que no requieren demasiada explicación. Si es interesante, tener claro que al borrar un contenedor no se borran los volúmenes que se hayan podido crear como consecuencia de su creación. Existe, sin embargo, la opción :kbd:`-v` para que al borrarse un contenedor se borren todos los volúmenes de datos anónimos asociados a él:: $ docker rm -v contenedor_con_volumenes_anonimos .. note:: La opción :kbd:`-m` (o :kbd:`--mount`) permite definir los tres tipos de volúmen (véase :manpage:`docker-run`). .. _docker-network: Redes ===== :program:`Docker` dispone tres tipos fundamentales de controlador de red: .. _docker-network-ls: .. code-block:: console $ docker network ls NETWORK ID NAME DRIVER SCOPE 201d5e901e39 bridge bridge local 377c54f52aa3 host host local bed15cc78e24 none null local para cada uno de los cuales hay ya creado un nombre de red. El significado de cada driver es el siguiente: Tipos ----- **bridge** Crea los contenedores dentro una red interna que se conecta con el exterior a través de una interfaz *bridge* en el anfitrión. Para la red homónima *bridge* ya definida esta interfaz es *docker0*:: $ ip link show dev docker0 4: docker0: mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default link/ether 02:42:7f:40:be:40 brd ff:ff:ff:ff:ff:ff Este tipo de red (que se corresponde con el tipo "*Red NAT*" de :ref:`Virtualbox `) es la predeterminada para los contenedores que se crean, por lo que todos los ejemplos que hemos estado mostrando la usaban: .. _docker-network-inspect: .. code-block:: console $ docker network inspect -f '{{json .IPAM.Config}}' bridge [{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}] $ docker run --rm -ti alpine / # hostname -i 172.17.0.2 / # ip route show default via 172.17.0.1 dev eth0 172.17.0.0/16 dev eth0 scope link src 172.17.0.2 Si crearamos otro contenedor, estaría también en la red *bridge* y recibiría una dirección |IP| de la misma red *172.17.0.0/16*. Al estar todas estos contenedores en una red interna, la forma de hacer accesibles sus servicios es mediante la publicación de los puertos expuestos al crear el contenedor, como ya se ilustró al :ref:`tratar Portainer `. En aquella ocasión se usó la opción :kbd:`-P` que escoge un puerto cualquiera del anfitrión. Puede también usarse la opción :kbd:`-p` que permite indicar cuál será el puerto en concreto:: $ docker run --rm -d --name=nginx-test -p 8080:80 nginx:alpine En este caso, el puerto expuesto **80** se publica en el **8080** de la máquina anfitrión. Es posible crear otra red distinta con este mismo controlador de manera que todos los contenedoes asociados a esa red estarán dentro de ella y conectados entre sí, pero aislados de los contenedores asociados a la red predefinida. Lo trataremos :ref:`más adelante `. **host** Cuando se utiliza este contralador, el contenedor usa la misma red que el administrador. Por tanto, el contendor:: $ docker run --rm -ti --network host alpine compartirá las interfaces con el anfitrión, por lo que cualquier servicio será directamente accesible. Así pues, se levantamos un servidor web en el contenedor, éste ocupará directamente el puerto 80 del anfitrión. **null** Simplemente, aisla al contenedor de la red. En este caso el contenedor sólo dispondrá de la interfaz de *loopback*. Además de estos tres tipos de redes, existen otras. Una interesante en sistemas locales es la asociada al *driver* **macvlan** que permite incluir el contenedor en la misma red a la que pertenece el anfitrión, por lo que sería lo más aproximado al tipo "*Adaptador puente*" de :ref:`Virtualbox `. Ilustraremos su uso bajo el próximo epígrafe, Gestión de redes ---------------- Las redes se pueden gestionar a través de :kbd:`docker network`. Por ejemplo, podemos crear una nueva red de tipo *bridge*: .. _docker-network-create: .. code-block:: console $ docker network create -d bridge bridge1 $ docker network inspect -f '{{json .IPAM.Config}}' bridge1 [{"Subnet":"172.18.0.0/16","Gateway":"172.18.0.1"}] Gracias a ello y al uso de :kbd:`--network` al crear el contenedor, podremos incluir contenedores dentro de esta otra red llamada *bridge1* que se comporta como la predefinida:: $ docker run --rm -ti --network bridge1 alpine / # hostname -i 172.18.0.2 Al crear la red no hemos especificado cuál es la red ni qué dirección hará de puerta de enlace. Estos datos, sin embargo, pueden especificarse a través de :kbd:`--subnet` (la red), :kbd:`--ip-range` (el rango de |IP|\ s dinámicas) o :kbd:`--gateway` (la dirección de la puerta de enlace). Por ejemplo, si la red del anfitrión es *192.168.0.0/24*. la puerta de enlace *192.168.0.1* y la interfaz física *eth0*, podemos usar el *driver* **macvlan** del siguiente modo:: $ docker network create -d macvlan -o parent=eth0 --subnet 192.168.0.0/24 --ip-range=192.168.0.128/25 --gateway=192.168.0.1 redreal $ docker run --rm -ti --network redreal alpine $ docker run --rm -ti --network redreal --ip 192.168.0.25 alpine De asignar direcciones dinámicas a las interfaces de los contenedores se encarga el demonio, por lo que es conveniente escoger un rango que estimemos que no será concendido por el servidor |DHCP| de la red. .. note:: El driver **macvlan** se caracteriza por no poder comunicarse con la interfaz física a la que está asociada, por lo que en este caso el contenedor podrá comunicarse con el resto de la red, pero no con el anfitrión. Para evitar esta circunstancia, podría configurarse la dirección del anfitrión en otra interfaz *macvlan*, en vez de sobre *êth0*:: # The primary network interface allow-hotplug enp1s0 iface enp1s0 inet manual up ip link set dev $IFACE up down ip link set dev $IFACE down auto host iface host inet dhcp pre-up ip link add link enp1s0 $IFACE type macvtap mode bridge post-down ip link del dev $IFACE Es posible también conectar contenedores a dos o más redes *bridge* utilizando los subcomandos :kbd:`connect` y :kbd:`disconnect`: .. _docker-network-connect: .. _docker-network-disconnect: .. code-block:: console $ docker create -ti alpine $ docker network connect bridge1 De esta manera el contenedor tendrá una interfaz en la red *bridge* y otra en la red *bridge1*. Podríamos haber añadido la opción :kbd:`--ip` tal como se hace al crear un contenedor con :ref:`docker run `, para especificar cuál es la |IP| fija que deseamos que se asigna al contenedor. Como pueden crearse, pueden borrarse redes también:: $ docker network rm bridge1 .. note:: Las redes definidas por el usuario, no tienen exactamente el mismo comportamiento que las redes preconstruidas. Por ejemplo: + Se puede desconectar en caliente una máquina de ellas. + Tienes integrado un servidor |DNS|, que permite la comunicación entre máquinas a través de sus respectivos nombres de máquina (el proporcionado mediante :kbd:`--hostname`). .. _docker-limit: Limitaciones ============ Al crear un contenedor también pueden establecerse limitaciones en el uso de los recursos, en particular, al uso de la memoria y el procesador. .. note:: Es probable que al hacer :kbd:`docker info` obtengamos el mensaje: .. code-block:: none WARNING: Noswaplimitsupport Si ese es el mensaje, es necesario añadir dos parámetros al arranque del núcleo, para lo cual debemos editar el archivo :file:`/etc/default/grub`: .. code-block:: bash GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" Memoria ------- Hay varias opciones que limitan la memoria: .. table:: :class: docker-limit-params =================================== ================================================= Parámetro Descripción =================================== ================================================= -m, --memory=numero[bkmg] Establece un límite estricto al uso de memoria. --memory-reservation=numero[bkmg] Esteblece un límite suave al uso de memoria. --memory-swap=numero[bkmg] Establece un límite al uso de memoria swap. =================================== ================================================= Por ejemplo:: $ docker run --rm -d -m 256m --name=nginx nginx:alpine $ docker container stats --no-stream --format '{{.MemUsage}}' nginx 4.945MiB / 256MiB Procesador ---------- Existen varias opciones relativas al uso del procesador. Por ejemplo:: $ docker run --rm -d --cpus=1 --name=nginx nginx:alpine limitará el uso del contenedor a una |CPU|. Puede también especificarse qué |CPU| exactamente:: $ docker run --rm -d --cpuset-cpus=0,2 --name=nginx nginx:alpine que limitará el uso al primer y tercer procesador. Si se tienen varios contenedores simultáneamente, puede también repartirse el consumo máximo de |CPU| entre todos ellos mediante la opción :kbd:`--cpu-shares` cuyo valor predeterminado es **1024**:: $ cat /sys/fs/cgroup/cpu/docker/cpu.shares 1024 De esto modo, si arrancamos estos tres contenedores:: $ docker run --rm -d --cpuset-cpus=0 --name=nginx nginx:alpine $ docker run --rm -d --cpuset-cpus=0 --cpu-share=512 --name=php php:alpine $ docker run --rm -d --cpuset-cpus=0 --cpu-share=512 --name=mysql mysql:alpine El primer contenedor podrá consumir hasta el 50% de la |CPU| y los otros dos hasta el 25%. .. storage-opt (man docker-run) .. rubric:: Notas al pie .. [#] La configuración por defecto es que demonio y cliente se encuentran en la misma máquina y la comunicacion se hace a través de un *socket*. No queda pues el demonio escuchando en ningún puerto, para lo cual habría que hacer `configuración adicional en el demonio `_ y en el cliente. .. [#] Para ello se usar el driver OverlayFS_ en las versiones modernas de *Docker*. .. [#] La analogía casi es mejor hacerla con :ref:`journalctl ` ya que también dispone de :kbd:`--since` y :kbd:`--until`. .. [#] Esta imagen de :ref:`nginx ` escribe el registro de los accesos y los errores en la salida de errores, por lo que si nuestra intención es leerlos, nos interesará quitar la opción :kbd:`-d` para que empiecen a aparecer esos registros en la terminal ocupada. .. [#] En este caso se sobrentiende que los puertos son |TCP|. Si hubieran sido |UDP|, habría sido necesario especificarlo) por ejemplo, :kbd:`-p 53:53/udp`. .. |POO| replace:: :abbr:`POO (Programación Orientada a Objetos)` .. |API| replace:: :abbr:`API (Application Programming Interface)` .. |CLI| replace:: :abbr:`CLI (Command Line Interface)` .. |PHP| replace:: :abbr:`PHP (PHP HyperText Preprocessor)` .. |JSON| replace:: :abbr:`JSON (JavaScript Object Notation)` .. |CPU| replace:: :abbr:`CPU (Central Processing Unit)` .. |TCP| replace:: :abbr:`TCP (Transmission Control Protocol)` .. |UDP| replace:: :abbr:`UDP (User Datagram Protocol)` .. |RAM| replace:: :abbr:`RAM (Random Access Memory)` .. _Docker Hub: https://hub.docker.com/ .. _Alpine: https://alpinelinux.org/ .. _Apache: http://apache.org/ .. _OverlayFS: https://en.wikipedia.org/wiki/OverlayFS