Tutorial de Docker – Empezando con Docker (Parte 2)

, Author

Gracias al artículo anterior, ya sabes qué es Docker. ¡Ahora, ven y aprende a utilizarlo con los principales comandos e interacciones básicas! ¡Todo sobre la ballena!

Introducción

Este artículo forma parte de una serie de posts sobre Docker y su entorno:

  • Tutorial de Docker: entendiendo Docker (parte 1)
  • Tutorial de Docker: empezando con Docker (parte 2)
  • Tutorial de Docker: Comandos de Docker (parte 3)
  • Docker es un sistema para gestionar contenedores (como ya sabéis por el artículo Descubriendo Docker), y donde Docker es muy fuerte es que es capaz de facilitar el uso de las partes más oscuras.

    Sobre nuestra ballena (Moby Dock para sus amigos). Docker se basa en una API RESTFul de la que hablaré en un próximo post. Para ello, la compañía ha combinado un «docker-cli» que nos permite ejecutar fácilmente los comandos desde nuestro terminal.

    Desde un terminal, si ejecutas el comando docker, obtendrás una lista de comandos ejecutables, que son:

$ docker…Commands: attach Attach to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders from a container's filesystem to the host path create Create a new container diff Inspect changes on a container's filesystem events Get real time events from the server exec Run a command in an existing container export Stream the contents of a container as a tar archive history Show the history of an image images List images import Create a new filesystem image from the contents of a tarball info Display system-wide information inspect Return low-level information on a container kill Kill a running container load Load an image from a tar archive login Register or log in to a Docker registry server logout Log out from a Docker registry server logs Fetch the logs of a container port Lookup the public-facing port that is NAT-ed to PRIVATE_PORT pause Pause all processes within a container ps List containers pull Pull an image or a repository from a Docker registry server push Push an image or a repository to a Docker registry server restart Restart a running container rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save an image to a tar archive search Search for an image on the Docker Hub start Start a stopped container stop Stop a running container tag Tag an image into a repository top Lookup the running processes of a container unpause Unpause a paused container version Show the Docker version information wait Block until a container stops, then print its exit code…

Y ya está, con esta lista, tienes todo a mano para gestionar tus contenedores e imágenes. Fácil, ¿no?

(Para los que usen boot2docker, os recomiendo que sigáis la documentación y abráis un terminal en el que podáis chatear con vuestro Moby Docker),

Docker y sus imágenes

Hasta ahora, nunca os había hablado de imágenes. Sólo te he hablado de contenedores. Y, sin embargo, las imágenes Docker son de vital importancia. A continuación te contamos un poco más sobre estas imágenes, tan esenciales en el funcionamiento de Docker.

Una imagen es un contenedor estático. Podrías comparar una imagen con una instantánea de un contenedor en un momento dado, una especie de instantánea de uno de tus contenedores. Cuando quieres trabajar con un contenedor, necesariamente declaras un contenedor a partir de una imagen.

Además, las imágenes Docker funcionan a través de la herencia de otras imágenes. La propia imagen de Tomcat hereda de la imagen de Java. Esa misma imagen de Java que puede haber sido construida desde una Debian. Así que la herencia puede llegar muy lejos. El contenedor creado a partir de una imagen contiene el delta entre la imagen base a partir de la cual se instala el contenedor y el estado actual. Gracias a este sistema, la duplicación de datos es baja.

¡El punto técnico! Una imagen puede haber sido construida a partir de otra imagen que a su vez puede haber sido construida a partir de otra imagen. Este sistema funciona perfectamente mediante un sistema de apilamiento de contenedores. Por lo tanto, cuando construyes una imagen a partir de otra, en realidad estás almacenando todos los contenedores que te permitieron llegar desde tu imagen base hasta tu imagen final.Puedes visualizar de lo que estoy hablando ejecutando el comando «docker history .

Puedes ver tu «biblioteca» de imágenes en cualquier momento con el comando «docker images». Verás que, a medida que vayas avanzando en tus proyectos, tendrás más y más imágenes. Utilizables como «moldes» a medida para iniciar tus proyectos o, como mínimo, tus nuevos contenedores.

Para ver tu biblioteca de imágenes:

$ docker imagesREPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE

Aquí tienes, acabas de interactuar con Docker por primera vez. No esperes ver nada, por defecto no tienes imágenes. Lógica. Por suerte, otros ya tienen bases de imágenes listas para usar!

¿Cómo recuperar (pull) una imagen Docker?

Hay una plataforma mantenida por Docker en la que cualquiera puede empujar y tirar de imágenes. Es como el GitHub de las imágenes Docker. A partir de ahora, intentaremos obtener una imagen. Hay imágenes oficiales para todo. Esta gigantesca biblioteca compartida es el Registro de Docker Hub!

Puedes buscar una imagen de una distribución o producto ya instalado. Empecemos por encontrar una imagen Docker que podamos utilizar. Tienes dos formas de buscar una imagen de la misma: a través de la interfaz web, o a través del comando «docker search».

Yo voy a usar el comando «docker search» (porque soy un barbudo) para encontrar distribuciones de Debian, que tienen bastantes estrellas (confianza de los usuarios de la plataforma). El comando es el siguiente.

$ docker search --stars=10 debianNAME DESCRIPTION STARS OFFICIAL AUTOMATEDdebian (Semi) Official Debian base image. 248 google/debian 25 tianon/debian use "debian" instead - https://index.docke... 13

Y aquí están los resultados disponibles con mis criterios. Empecemos de forma sencilla: lo que me interesa es una Debian oficial. En efecto, hay uno, llamado debian, calificado con 248 estrellas (¡!) y marcado en el atributo OFICIAL. Adelante, Moby Dock, ¡saca esta imagen!

$ docker pull debian…

Aquí tienes, tras unos segundos de descarga, una imagen en tu host. De hecho, puedes verlo ejecutando «docker images». Un comando que enumera las imágenes presentes en tu máquina.

Hay otra forma menos útil en el día a día (pero que puede seguir utilizándose), que es importar una imagen con el comando «docker load» a partir de un archivo .tar.gz, inicialmente exportado por un Docker (el de un colega, por ejemplo).

Cómo crear una imagen:

Ahora queremos aprender a trabajar de forma autónoma y construir nuestras imágenes a nuestro gusto. Hay varios métodos:

  1. el Dockerfile: ya visto en el primer artículo, permite entre otras cosas partir de una imagen inicial, lanzar acciones y construir una nueva imagen.
  2. lanzamiento de un contenedor, realización de acciones propias y commit (con el comando «docker commit») de los cambios en una nueva imagen.
  3. Hoy no detallaré el segundo método, que para mí es menos útil en un principio. A título personal, utilizo este método para historiar mis personalizaciones. Esto me permite guardar estados paso a paso de la personalización de mi contenedor, para poder empezar desde un paso concreto, por ejemplo (de ahí el uso del término snapshot utilizado anteriormente).

    Las diferencias entre un contenedor y su imagen se pueden ver gracias al comando «docker diff».

    Crear una imagen con un Dockerfile

    Este archivo tiene una mezcla de instrucciones y metadatos. Con este archivo, das la receta para que Docker construya tu imagen. El sitio web oficial describe, obviamente, el conjunto completo de instrucciones que se pueden hacer.

    Empecemos por crear un archivo llamado Dockerfile (¡sin extensión! El nombre de este archivo no es negociable) en la carpeta de nuestra elección, y digamos cuáles son las prerrogativas para crear una nueva imagen. Para nuestro ejemplo, queremos partir de una Debian Wheezy (o Debian 7), en la que añadimos Nginx.

    Línea por línea, vamos a detallar nuestro futuro Dockerfile:

    1 – Indico la distribución de partida con la línea

    FROM debian:wheezy

    2 – Nombro a la persona que ha escrito este Dockerfile, en este caso soy yo. Gracias a la línea

    MAINTAINER Baptiste Donaux <[email protected]>

    3 – Busco los paquetes disponibles e instalo Nginx.

    RUN apt-get update \ && apt-get install -y \ nginx

    4 – Copio sucesivamente las configuraciones y scripts de mi sistema anfitrión a mi imagen

    COPY nginx.conf /etc/nginx/nginx.conf
    COPY service_start.sh /home/docker/script/service_start.sh

    5 -. Aplico los permisos para ejecutar mi script

    RUN chmod 744 /home/docker/script/service_start.sh

    6 – Defino un punto de entrada: El primer script que se ejecutará cuando se inicie el contenedor

    ENTRYPOINT /home/docker/script/service_start.sh

    7 – La carpeta en la que estaré cuando ejecute un nuevo contenedor será WORKDIR

    WORKDIR /home/docker

    Tenemos todas las líneas para hacer nuestro Dockerfile. Aquí está completo:

    FROM debian:wheezyMAINTAINER Baptiste Donaux <[email protected]>RUN apt-get update \ && apt-get install -y \ nginxCOPY nginx.conf /etc/nginx/nginx.confCOPY service_start.sh /home/docker/script/service_start.shRUN chmod 744 /home/docker/script/service_start.shENTRYPOINT /home/docker/script/service_start.shWORKDIR /home/docker

    Construyendo una imagen a partir de un Dockerfile

    Ahora que tenemos un Dockerfile, queremos crear nuestra imagen. Y el comando mágico es… «docker build»!

    $ docker build .

    Cuando se ejecuta «docker build», es necesario especificar la ruta al Dockerfile (de ahí el punto al final del comando si se ejecuta el comando desde la misma ubicación).

    Durante la construcción verás que todos los pasos se ejecutan uno tras otro. La primera ejecución tarda mucho tiempo, porque ninguno de los pasos ya ha sido llamado. En una segunda construcción, ¡verás cómo los pasos se despachan a gran velocidad! (¡Probarlo, para ver!). Todo esto siempre que los pasos anteriores no hayan sido modificados en tu Dockerfile, por supuesto!

    Por supuesto, Docker historiza todas las acciones que realiza (cada declaración realizada en un Dockerfile se almacena en un contenedor intermedio). Este es también el principio que se utiliza para realizar la herencia entre imágenes. Si las instrucciones no cambian y su orden es idéntico al de la vez anterior, NO se regeneran porque Docker sabe utilizar su caché y reutilizar las acciones ya realizadas. Fortiche, la ballena!

    Explicación: ¿cómo funciona la caché de Docker?

    La caché que utiliza «docker build» no es mágica (aunque lo parezca), pero es fácil de entender.

    Arriba te hablé vagamente del comando docker-commit que te permite gestionar los estados de tus imágenes guardando los cambios de un contenedor en una nueva imagen. Por defecto, cuando se hace un docker-build, cada paso (cada declaración en su dockerfile) se almacena en un contenedor intermedio (puede ver todos los pasos de la construcción de una imagen utilizando el comando docker-history). Docker dispone de un fiddle interno para gestionar sus contenedores internos con el fin de proporcionar el mejor rendimiento al construir una imagen. Herencia, historización, recuerda 😉

    Etiquetar una imagen o sacar tus imágenes del anonimato

    Cualquier imagen o contenedor tiene un identificador único. Podríamos utilizar estos identificadores únicos, pero Docker Inc ha proporcionado una función para etiquetar nuestras imágenes así como nuestros contenedores para reutilizarlos más fácilmente.

    Aquí tienes cómo nombrar tus imágenes:

    1. Da un nombre a la construcción de la imagen con la opción –tag
    2. Usa el comando docker-tag
    3. Nombra tu imagen cuando uses docker-commit
      1. Ejemplo más concreto: creando una imagen usando el comando docker-build y su opción –tag

        $ docker build --tag="myImage"

        ¡Aquí tienes, ahora haz un docker-images y tu imagen correctamente etiquetada aparecerá en tu lista!

        ¡El punto técnico!Las imágenes pueden tener tantos nombres y tantas etiquetas como quieras. Por cierto, la imagen oficial de Debian Wheezy puede encontrarse bajo el nombre debian:7, debian:latest o incluso debian:wheezy. En este caso, hemos aplicado múltiples etiquetas sobre el mismo nombre de imagen, pero quizás quieras llamar a esta imagen con otro nombre.

        default debian c90d655b99b2debian 7.8 c90d655b99b2debian latest c90d655b99b2debian wheezy c90d655b99b2debian 7 c90d655b99b2

        Aquí ves que la imagen identificada por c90d655b99b2 también se identifica por defecto:debian.

        Gestión de contenedores

        Ahora que la gestión de imágenes ya no es un problema para ti, vamos a hablar un poco de los contenedores.

        Definiendo cómo funciona un contenedor

        Ya hemos hablado de ello más arriba, un contenedor encarna la noción de un entorno. En Docker, incluso las imágenes son contenedores, pero la diferencia entre una imagen y un contenedor se debe a los comportamientos.

        Las imágenes son estáticas; no se escribe directamente en una imagen. Declaramos un contenedor DESDE una imagen. Este contenedor vivirá y contendrá las diferencias realizadas con la imagen base. ¿Me sigues?

        Un contenedor también contiene otros elementos, almacena parámetros de ejecución, como el mapeo de puertos, carpetas a compartir.

        El punto técnico!Actualmente aplicamos parámetros a un contenedor como el mapeo de puertos, compartir carpetas… Pero no es posible cambiar sus parámetros sobre la marcha. Docker Inc. también está trabajando en la posibilidad de cambiar en caliente sus variables. Actualmente, la única forma que tenemos de cambiar la configuración es:

        1. Guardar su contenedor como una imagen (docker commit)
        2. Lanzar un contenedor desde la nueva imagen con la configuración completa que desea utilizar.

        Hagamos un ejercicio juntos.

        Declarar un contenedor

        Normalmente tenemos una imagen debian con varias etiquetas en nuestra máquina gracias a la imagen creada anteriormente. Vamos a declarar un contenedor.

        $ docker run debian:wheezy

        ¡Ahí tienes, has ejecutado un contenedor desde una imagen Docker! Con esto, no esperes hacer ninguna locura, pero es un comienzo. Si sientes que este comando no hizo nada, puedes ver con el comando «docker ps -l» que las cosas sucedieron:

        $ docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES9b4841d73b6e debian:7 "/bin/bash" 3 seconds ago Exited (0) 3 seconds ago furious_hopper

        Este retorno indica que un contenedor llamado «furious_hopper» identificado por el ID 9b4841d73b6e, ejecutó el comando /bin/bash desde una imagen debian:7 y terminó con el código de retorno 0. (¡Nótese que Docker sabe cómo nombrar sus contenedores de forma original! Aquí furious_hopper!)

        Aunque su contenedor esté apagado (estado Exited), no se borra, se almacena (esto es importante para lo siguiente). Por cierto, puedes mostrar todos los contenedores (tanto los que están en ejecución como los que están parados) utilizando el comando «docker ps -a».

        Ahora queremos interactuar con nuestro contenedor. Para ello, tendremos que utilizar algunas opciones en el comando «docker run», que son:

        $ docker run --tty --interactive debian:7

        Deberíamos estar en un bonito contenedor con un bash listo para responder. Notarás que el tiempo de lanzamiento es bastante bajo. ¿Pero qué hemos hecho? Vamos a detallar lo que significa:

        • La opción –tty nos permite adjuntar la consola a nuestra consola actual y no perder el foco. Es por esta opción que su contenedor no terminará.
        • La opción –interactive le permite hablar con su contenedor. Sin esta opción, cualquier cosa que escribas en tu consola no se pasará al bash del contenedor.
          • En nuestro caso, el comando ejecutado fue /bin/bash pero esto no es una coincidencia. Al declarar una imagen (en su Dockerfile), puedes especificar algunos metadatos como CMD que te permiten dar un comando por defecto si no se especifica ninguno.

            Si querías ejecutar un comando que no fuera /bin/bash, podías haber hecho así al declarar tu contenedor:

        $ docker run debian:7 echo Docker is fun

        Y voilá, tu contenedor se creó y ejecutó el comando que le diste («echo Docker is fun»), y luego terminó porque ningún comando ejecutado tomó el foco de la consola.

        Usando un contenedor

        Ahora que sabes cómo ejecutar comandos en un contenedor, vamos a ir un poco más allá con un ejemplo sencillo.

        Un contenedor es el equivalente a un Filesystem. Además, mi proyecto Symfony utiliza una base de datos. Como mi base de datos va a cambiar, no queremos perder nuestros datos (bueno, supongo ;)). Este sería el caso si hicieras un «docker run» cada vez que quisieras iniciar tu servicio de base de datos. ¿Por qué lo haces? Porque como se ve arriba, la ejecución de docker inicia un nuevo contenedor. ¡Una nueva! ¡No utiliza el que ya estaba creado antes! Veamos cómo evitar este problema.

        Detallemos cómo funciona «docker run»

        Al principio, era EL (¡!) comando que usaba todo el tiempo. Ahora no tanto. ¿Qué hace realmente «docker run»? Este comando ejecuta sucesivamente otros dos comandos a los que tiene acceso: «docker create» y «docker start». Vamos a detallarlos juntos.

        docker-create

        ¡»docker create» es un comando indispensable! Si haces un man, notarás que éste contiene las mismas opciones que «docker run». Con este comando, crearás un contenedor a partir de una imagen dándole parámetros de ejecución.

        • Mapeo de puertos
        • Compartición de carpetas

        Entre los datos parametrizables, podrás darle un nombre a tu contenedor. Docker siempre le dará un nombre a tu contenedor (furious_hopper, ¿recuerdas?), pero será más fácil para ti encontrar el contenedor de tu proyecto con un nombre apropiado en lugar de «condescendiente_wozniak». XD

        Aquí tienes el comando completo para crear un contenedor, ¡el que usarás a partir de ahora!

        $ docker create --tty --interactive --name="wonderful_mobidock" debian:7

        El retorno de este comando debe ser una cadena de caracteres stodgy (indicando el ID dado a tu contenedor).

        docker-start

        Nuestro contenedor ya está creado gracias al comando «docker create». Por cierto haciendo un «docker ps -a» deberías encontrarlo.

        Ahora que has creado tu contenedor y lo has configurado, puedes iniciarlo con el comando «docker start».

        $ docker start wonderful_mobidock

        Por defecto, «docker start» no te adjunta la consola, pero puedes especificarlo con la opción –attach.

        $ docker start --attach wonderful_mobido

        Aquí tienes una sencilla, ¿verdad?

        Reutilizar un contenedor

        Nuestro contenedor se lanza y se nombra. Nuestra base de datos vive en nuestro contenedor, y de un tiempo a esta parte no queremos perder nuestros datos (lo que sería una pena, admitámoslo). El comando «docker stop» permite cerrar limpiamente un contenedor (el uso de «docker kill» debe ser limitado).

        Si más tarde quieres reiniciar este maravilloso_mobydock y recuperar tus datos, ¡ejecuta «docker start»!!

        Conclusión

        Ahora eres capaz de crear una imagen y gestionar tus contenedores con sólo nombrarlos, crearlos y detenerlos. No se han detallado aquí todos los comandos y opciones, pero ahora deberías tener las mejores prácticas para empezar a usar Docker. El manual es tu amigo, y Docker está creciendo rápidamente, así que estate atento

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *