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