Actualmente no se recomienda el
enfoque del comando “docker commit”.
En su lugar, se recomienda construir imágenes utilizando un archivo de
definición llamado Dockerfile y el comando “docker
build”. El fichero Dockerfile utiliza una síntaxis básica de instrucciones
para la construcción de imágenes Docker. El enfoque Dockerfile proporciona un
mecanismo más repetible y transparente para crear imágenes.
Una vez tenemos un fichero
Dockerfile creado, utilizaremos el comando “docker
build” para crear una nueva imagen a partir de las instrucciones del
fichero Dockerfile, que permiten al sistema saber qué queremos que se instale
(imagen base que se utiliza, ficheros que queremos copiar a la imagen, etc..)
Nuestro primer Dockerfile
Vamos a crear un directorio y un
fichero Dockerfile inicial. Vamos a construir una imagen de Docker que contiene
un servidor web simple.
$
mkdir web
$
cd web
$
touch Dockerfile
Hemos creado un
directorio llamado web para mantener nuestro fichero Dockerfile. Ese directorio
será nuestro entorno de compilación, que es lo que Docker llama un contexto o construir
un contexto. Docker cargará y construirá el contexto, así como los archivos y
directorios contenidos en el mismo y a nuestro demonio Docker cuando se ejecute
la construcción. Esto lo proporciona el demonio Docker con acceso directo a
cualquier código, archivos u otros datos que deseemos incluir en la imagen.
Nosotros hemos creado un fichero Dockerfile vacío para
comentar. Ahora vamos a ver un ejemplo de un Dockerfile para crear una imagen
Docker, la cual actuará como un servidor web.
#
Version: 0.0.1
FROM
Ubuntu:14.04
RUN
apt-get update && apt-get –y install nginx
RUN echo ‘Hola, yo estoy en un contenedor’ >
/usr/share/nginx/html/index.html
EXPOSE 80
El fichero Dockerfile contiene una serie de instrucciones
emparejadas con argumento. Cada instrucción, por ejemplo FROM, debe estar en
mayúsculas y estar seguido por un argumento: FROM Ubuntu:14.04. Las
instrucciones en el fichero Dockerfile se procesan de arriba hacia abajo, por
lo que se deben ordenar en consecuencia.
Cada instrucción añade una nueva capa a la imagen y luego
graba la imagen.
Docker ejecuta las instrucciones siguiendo el siguiente
flujo de trabajo:
- Docker ejecuta un contenedor desde la imagen.
- Una instrucción se ejecuta y se hacen los cambios en el contenedor.
- Docker ejecuta el equivalente a “docker commit” y graba una nueva capa.
- Docker entonces ejecuta un nuevo contenedor desde esta nueva imagen.
- La próxima instrucción en el fichero es ejecutada, y procesada repetidamente hasta que todas las instrucciones han sido ejecutadas.
Esto significa que si un Dockerfile se detiene por alguna
razón (por ejemplo si una instrucción falla), nos quedaremos con una imagen que
podemos utilizar. Esto nos puede resultar muy útil para tareas de depuración.
Se puede ejecutar un contenedor desde esta imagen interactiva y luego depurar,
porque la instrucción falló usando la última imagen creada.
Dockerfile también es compatible con comentarios. Cualquier
línea que comienza con un símbolo # es considerado un comentario.
La primera instrucción de un Dockerfile debe ser FROM. La
instrucción FROM especifica una imagen existente en la que operarán las
instrucciones posteriores, esta imagen se llama la imagen base.
En nuestro Dockerfile hemos especificado la imagen de
ubuntu:14.04 como nuestra imagen base. Esta especificación construirá una
imagen sobre la base de un sistema operativo Ubuntu 14.04. Al igual que con la
ejecución de un contenedor, que siempre hay que especificar exactamente cual es
la imagen base sobre la que estamos construyendo.
A continuación, hemos especificado la instrucción
MAINTAINER, que le describe a Docker quién es el autor de la imagen y cuál es
su dirección de correo electrónico. Esto es útil para especificar un
propietario y para facilitar el contacto con el creador de una imagen.
Hemos seguido estas instrucciones con dos instrucciones RUN.
La instrucción RUN ejecuta comandos en la imagen actual. Los comandos en
nuestro ejemplo son: la actualización de los repositorios APT instalados e
instalar el paquete nginx y luego crear el archivo
/usr/share/nginx/html/index.html que contiene un texto de ejemplo. Como hemos
visto anteriormente, cada una de estas instrucciones creará una nueva capa y,
si tiene éxito, almacenará dicha capa y luego ejecutará la siguiente instrucción.
De forma predeterminada, la instrucción RUN se ejecuta
dentro un Shell usando el comando “/bin/sh
-c”. Si está ejecutando la instrucción en una plataforma sin un shell o
bien deseamos que no lo tenga, puede especificar la instrucción en formato de
ejecución:
RUN
[“apt-get”,” install “,”-y”,”nginx”]
Utilizamos este formato para especificar una matriz que
contiene el comando a ejecutar y luego cada parámetro que deseemos pasar al
comando.
A continuación, hemos especificado la instrucción EXPOSE, que
le indica a Docker que la aplicación alojada en este contenedor utilizará este
puerto específico dentro del contenedor. Eso no significa que podamos acceder
automáticamente a cualquier servicio que se está ejecutando en ese puerto (en
este caso, el puerto 80) en el contenedor. Por razones de seguridad, Docker no
abre los puertos automáticamente, pero espera que nosotros lo hagamos cuando se
ejecute el contenedor utilizando el comando “docker
run”.
Podemos especificar varias instrucciones EXPOSE para indicar
que varios puertos van a ser expuestos.
Construyendo imágenes desde nuestro fichero Dockerfile
Todas las instrucciones se ejecutarán y se grabará la
transacción y una imagen nueva será creada cuando ejecutamos el comando “docker
build”. Vamos a construir nuestra imagen ahora.
$ cd web
$ docker build -t="jalapuente/web" .
Sending
build context to Docker daemon 2.048 kB
Step
1 : FROM ubuntu:14.04
14.04:
Pulling from library/ubuntu
Digest:
sha256:28bd2edcebe82d41c3494bf6205016fe08e681452f1448acd44d55e2cda7e3c0
Status:
Downloaded newer image for ubuntu:14.04
---> e9ae3c220b23
Step
2 : MAINTAINER Javier Hernandez “jalapuente@example.com”
---> Running in 84f09e04f141
---> a711e3c2443f
Removing
intermediate container 84f09e04f141
Step
3 : RUN apt-get update && apt-get -y install nginx
---> Running in 20f581c46181
Ign
http://archive.ubuntu.com trusty InRelease
Get:1
http://archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Get:1
http://archive.ubuntu.com trusty-updates InRelease [64.4 kB]
….
Removing
intermediate container 20f581c46181
Step
4 : RUN echo ‘Hola, yo estoy en un contenedor’ >
/usr/share/nginx/html/index.html
---> Running in d947cc9a30e1
---> 9a1e7fb9d967
Removing
intermediate container d947cc9a30e1
Step
5 : EXPOSE 80
---> Running in a6416af141d9
---> 0e7818e84f46
Removing
intermediate container a6416af141d9
Successfully
built 0e7818e84f46
Hemos utilizado el comando “docker build” para construir nuestra nueva imagen. Hemos
especificado la opción -t para etiquetar la imagen resultante con un
repositorio y un nombre, aquí el repositorio jalapuente y el nombre de la
imagen web. Siempre es recomendable dar un nombre a nuestras imágenes, aunque
no sea obligatorio para hacer que sea más fácil seguirles la pista y gestionarlas.
También podemos etiquetar las imágenes durante el proceso de
construcción añadiendo un sufijo a la etiqueta, después del nombre de la imagen
con dos puntos, por ejemplo:
$ docker
build -t="jalapuente/web:v1" .
Si no especificamos ninguna etiqueta, Docker le asignará
automáticamente la etiqueta “latest”.
El punto le dice a Docker que mire en el directorio local
para buscar el fichero Dockerfile. También podemos especificar un repositorio
Git como fuente de origen para el fichero Dockerfile como podemos ver en esta instrucción:
$ docker
build -t="jalapuente/web:v1" git@github:jalapuente/docker-web
En este caso Docker asume que el fichero Dockerfile está
localizado en la raíz del repositorio Git.
También podríamos especificar la ruta donde está el fichero
utilizando el parámetro –f. Por ejemplo:
$ docker
build -t="jalapuente/web:v1" –f /opt/docker/proyecto1
El nombre de fichero Dockerfile no es necesario
especificarlo.
Revisando el proceso de construcción de docker, podemos ver
que el contexto construido se ha enviado al demonio Docker.
Sending
build context to Docker daemon 2.048 kB
En el caso de existir un archivo llamado .dockerignore en la
raíz del directorio, se interpreta como una lista separada por una nueva línea
de los patrones de exclusión. Al igual que un archivo .gitignore excluye los
archivos de la lista de ser tratados como parte del contexto de construcción y,
por lo tanto les impide ser subidos al demonio Docker.
Que sucede si una instrucción falla
Vamos a ver que sucede si una instrucción falla. Veamos un
ejemplo: supongamos que en el paso 4 escribimos el nombre del paquete requerido
equivocadamente y en su lugar escribimos ngin.
Si ejecutáramos de nuevo la construcción, podremos ver que
falla. Una vez que ha fallado podríamos depurar el fallo, para ello
utilizaríamos el ID del último paso que nos ofrece “docker build” y crearíamos una sesión para conectarnos:
$
docker run –t –i ID_DE_IMAGEN /bin/bash
ID_DE_IMAGEN:/#
Entonces podría tratar de ejecutar el comando “apt-get install –y ngin” de nuevo con
el nombre del paquete correcto o realizar cualquier otra depuración para
determinar qué salió mal. Una vez que hemos identificado el problema, podemos
salir del contenedor, actualizar mi Dockerfile con el nombre del paquete
correcto, y volver a intentar construir.
Dockerfiles y la caché de compilación
Como resultado de cada paso que se ejecuta, se consolida
como una imagen, Docker es capaz de ser inteligente sobre la construcción de
imágenes. Tratará las capas anteriores como caché. Si, en nuestro ejemplo de
depuración, no necesitamos cambiar nada en los pasos 1 a 3, entonces Docker
usaría las imágenes previamente construidas como memoria caché y punto de
partida. En realidad, se empezaría el proceso de construcción directamente
desde el paso 4. Esto puede ahorrarnos mucho tiempo en la construcción de
imágenes, en el caso de que un paso anterior no haya cambiado nada. Sin
embargo, si ha cambiado algo en los pasos 1 a 3, entonces Docker reiniciaría
desde la primera instrucción de cambio.
A veces, sin embargo, queremos asegurarnos de que no se utiliza
la memoria caché. Por ejemplo, si hubiera caché en el paso 3 de nuestro ejemplo
“apt-get update”, entonces no se actualizaría
la caché de paquetes APT. Es posible que deseemos que se ejecute para conseguir
una nueva versión de un paquete. Para omitir el caché, podemos utilizar el
parámetro “--no-cache” con el comando “docker
build”.
$ docker
build –no-cache –t=”jalapuente/web” .
El uso de la caché de construcción para plantillas
Como resultado de la caché de construcción, podemos
construir nuestros Dockerfiles en forma de plantillas simples (por ejemplo, añadir
un repositorio de paquetes o ejecutar paquetes de actualización en la parte
superior del archivo para asegurarnos de que la caché es utilizada). Lo más
habitual es tener el mismo conjunto de instrucciones en la parte superior de mi
Dockerfile, por ejemplo para Ubuntu:
FROM
Ubuntu:14.04
ENV ACTUALIZADO_FECHA 2015-12-08
RUN apt-get –qq update
Vamos a ver esta nueva Dockerfile. En primer lugar, hemos
utilizado la instrucción FROM para especificar una imagen base de ubuntu:14.04.
Después hemos añadido la instrucción MAINTAINER para proporcionar los datos de
contacto. Entonces he especificado una nueva instrucción, ENV. La instrucción
ENV establece las variables de entorno en la imagen. En este caso, he
especificado la instrucción ENV para establecer una variable de entorno llamada
ACTULIZADO_FECHA, mostrando cuando la plantilla ha sido actualizada. Por
último, hemos especificado el comando “apt-get
–qq update” en una instrucción RUN. Esto actualiza la caché de paquetes APT
cuando se ejecuta, asegurando que disponemos de los últimos paquetes
disponibles para instalar.
Con esta plantilla, cuando quiero actualizar la construcción,
puedo cambiar la fecha de mi instrucción ENV. Docker entonces restablece la
memoria caché cuando llega a esa instrucción ENV y se ejecuta cada instrucción
posterior de nuevo sin depender de la memoria caché. Esto significa que la instrucción
“RUN apt-get –qq update” se vuelve a
ejecutar y la caché de paquetes se actualiza con los últimos contenidos. Podemos
extender este ejemplo de plantilla para cualquier plataforma de destino o adaptarla
a las distintas necesidades.
Visualización de nuestra nueva imagen
Ahora vamos a comprobar nuestra nueva imagen. Podemos hacer
esto con el comando “docker images”.
$
docker images jalapuente/web
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
Jalapuente/web latest 0e7818e84f46 3 hours ago 227.5 MB
Si queremos profundizar sobre como se ha creado nuestra
nueva imagen, podemos usar el comando “docker history”.
$
docker history 0e7818e84f46
IMAGE CREATED CREATED BY SIZE COMMENT
0e7818e84f46 3 hours ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B
9a1e7fb9d967 3 hours ago /bin/sh -c echo ‘Hola, yo estoy en un
conte 38 B
56092f24c55b 3 hours ago /bin/sh -c apt-get update && apt-get
-y insta 39.54 MB
a711e3c2443f 3 hours ago /bin/sh -c #(nop) MAINTAINER Javier
Hernandez 0 B
e9ae3c220b23 4 weeks ago /bin/sh -c #(nop) CMD
["/bin/bash"] 0
B
a6785352b25c 4 weeks ago /bin/sh -c sed -i
's/^#\s*\(deb.*universe\)$/ 1.895
kB
0998bf8fb9e9 4 weeks ago /bin/sh -c echo '#!/bin/sh' >
/usr/sbin/polic 194.5 kB
0a85502c06c9 4 weeks ago /bin/sh -c #(nop) ADD
file:531ac3e55db4293b8f 187.7 MB
Podemos ver que cada capa de la imagen está dentro de
nuestra imagen jalapuente/web y las instrucciones del Dockerfile fueron creadas
en el momento de su construcción.
Lanzamiento de un contenedor de nuestra nueva imagen
Podemos ahora poner en marcha un nuevo contenedor utilizando
nuestra nueva imagen y comprobar que ha funcionado lo que hemos construido.
$ docker
run –d –p 80 --name web jalapuente/web nginx –g “daemon off;”
Hemos lanzado un nuevo contenedor llamado web usando el
comando “docker run” y el nombre de
la imagen que acabamos de crear. Hemos especificado la opción -d, que le indica
a Docker que funcione como servicio (en background). Esto nos permite ejecutar
procesos de larga ejecución, como el demonio Nginx. También hemos especificado
un comando para ejecutar cuando el contenedor arranque: nginx -g "daemon off;".
Esto abrirá Nginx en primer plano para ejecutar nuestro servidor web.
También hemos especificado una nuevo parámetro, -p. El parámetro
-p gestiona los puertos de red que Docker publica en tiempo de ejecución.
Cuando se ejecuta un contenedor, Docker tiene dos métodos de asignación de
puertos en el host Docker:
•
Docker puede asignar al azar un puerto alto del
rango 32.768-61.000 en el host Docker, que mapea al puerto 80 en el contenedor.
•
Podemos especificar un puerto concreto en el
host Docker, que se asigna al puerto 80 en el contenedor.
Para ver que puerto ha mapeado, podemos utilizar el comando “docker ps” o bien podemos utilizar el
comando “docker port”, indicando el
contenedor y puerto del que queremos conocer su mapeo, y nos devolverá el
puerto aleatorio que le ha sido asignado.
El parámetro “-p” también nos permite ser flexibles en
cuanto a como se mapea un puerto en el host. Por ejemplo, podemos especificar
que puerto del host se mapea al puerto del contenedor: ‘docker run –d –p 8080:80 web jalapuente/web nginx –g “daemon off;” ‘,
esto mapearía el puerto 80 en el contenedor al puerto 8080 en el host local.
Si ejecutamos varios contenedores, sólo un contenedor puede
mapearse a un puerto específico en el host local. Esto podría ser un límite de
la flexibilidad de Docker.
También podríamos mapearlo sólo a un interface de red
determinado, por ejemplo “-p 127.0.0.1:80:80” mapearía el puerto 80 del
contenedor de tal forma que sólamente seria posible acceder desde el host local
a través del puerto 80.
También podríamos mapear un puerto UDP añadiendo el sufijo
/udp al final del mapeo del puerto.
Docker también dispone del parámetro “-P”, el cual publica
todos los puerto que fueron definidos para exponer con la instrucción EXPOSE
del fichero Dockerfile.
No hay comentarios:
Publicar un comentario