Articles

Docker Run: Cómo crear imágenes a partir de una aplicación

En nuestra serie de blogs anterior, vimos cómo desplegar Kubernetes y crear un clúster. También vimos cómo desplegar una aplicación en el entorno del clúster y configurar las instancias de OpenStack (incluyendo la seguridad) para ayudarle a acceder a ellas. Ahora vamos a profundizar en el desarrollo de Kubernetes viendo cómo crear imágenes Docker para que puedas desplegar tus propias aplicaciones y ponerlas a disposición de otras personas.

Cómo funcionan las imágenes Docker

Antes de aprender a crear imágenes Docker, lo primero que tenemos que entender es cómo funcionan las propias imágenes Docker.

La clave de una imagen Docker es que es un sistema de archivos en capas. En otras palabras, si empiezas con una imagen que es sólo el sistema operativo (digamos Windows) y luego añades una aplicación (digamos Nginx), terminarás con algo como esto:

Como puedes ver, la diferencia entre IMAGEN1 e IMAGEN2 es sólo la aplicación en sí, y luego IMAGEN4 tiene los cambios realizados en las capas 3 y 4. Así que para crear una imagen, básicamente estás empezando con una imagen base y definiendo los cambios en ella.

Ahora, te oigo preguntar: «¿Pero qué pasa si quiero empezar desde cero?». Bueno, vamos a definir «desde cero» por un minuto. Lo más probable es que te refieras a que quieres empezar con un sistema operativo limpio y partir de ahí. Bueno, en la mayoría de los casos hay una imagen base para eso, así que sigues empezando con una imagen base. (Si no, puedes consultar las instrucciones para crear una imagen base de Docker.)

En general, hay dos formas de crear una nueva imagen de Docker:

  • Crear una imagen de Docker a partir de un contenedor existente: En este caso, se parte de una imagen existente, se personaliza con los cambios que se desean y luego se construye una nueva imagen a partir de ella.
  • Utilizar un Dockerfile: En este caso, utilizas un archivo de instrucciones -el Dockerfile- para especificar la imagen base y los cambios que quieres hacer en ella.
    • En este artículo, vamos a ver ambos métodos. Empecemos con la creación de una nueva imagen a partir de un contenedor existente.

      Crear a partir de un contenedor existente

      Cuando se trata de Docker, empezar puede ser bastante sencillo. En este ejemplo, vamos a empezar con una imagen que incluye el servidor de aplicaciones web nginx y PHP. A eso, vamos a añadir soporte para la lectura de datos RSS utilizando un paquete de código abierto llamado SimplePie (disponible para su descarga en GitHub). A continuación, vamos a hacer una nueva imagen del contenedor alterado.

      Crear el contenedor Docker original

      Lo primero que tenemos que hacer es instanciar la imagen base original, o hacer que docker cree un contenedor a partir de una imagen.

  1. El primer paso es asegurarse de que su sistema tiene Docker instalado. Si has seguido nuestra serie anterior sobre la ejecución de Kubernetes en OpenStack, ya tienes esto controlado. Si no, puedes seguir las instrucciones aquí para simplemente desplegar Docker.
  2. A continuación, necesitarás obtener la imagen base. En el caso de este tutorial, eso es webdevops/php-nginx, que es parte del Docker Hub, por lo que para «tirar» de ella necesitarás tener un ID de Docker Hub. Si aún no tienes uno, ve a https://hub.docker.com y crea una cuenta gratuita.
  3. Ve a la línea de comandos donde tienes instalado Docker e inicia sesión en el Docker Hub:
    # docker loginInicia con tu ID de Docker para empujar y extraer imágenes del Docker Hub. Si no tienes un ID de Docker, dirígete a https://hub.docker.com para crear uno.Username: nickchasePassword:Login Succeeded
  4. Vamos a empezar con la imagen base y hacer que Docker inicie un contenedor. Instanciar webdevops/php-nginx:
    # docker run -dP webdevops/php-nginx

    El flag -dP se encarga de que el contenedor se ejecute en segundo plano, y que los puertos en los que escucha estén disponibles.

  5. Asegúrate de que el contenedor se está ejecutando:
    # docker psCONTAINER ID IMAGE COMANDO CREADO ESTADO PORTS NAMES1311034ca7dc webdevops/php-nginx "/opt/docker/bin/entr" 35 seconds ago Up 34 seconds 0.0.0.0:32822->80/tcp, 0.0.0.0:32821->443/tcp, 0.0.0.0:32820->9000/tcp small_bassi

Un par de notas aquí. En primer lugar, como no hemos especificado un nombre concreto para el contenedor, Docker le ha asignado uno. En este ejemplo, es small_bassi. En segundo lugar, observe que hay 3 puertos que están abiertos: 80, 443 y 9000, y que han sido asignados a otros puertos (en este caso 32822, 32821 y 32820, respectivamente – en tu máquina estos puertos serán diferentes). Esto hace posible que varios contenedores estén «escuchando» en el mismo puerto en la misma máquina anfitriona. Así que si intentáramos acceder a una página web alojada en este contenedor, lo haríamos accediendo a:

http://localhost:32822

Pero hasta ahora no hay ninguna página a la que acceder; vamos a arreglarlo.

Crear un archivo en el contenedor

Para poder probar este contenedor, necesitamos crear un archivo PHP de ejemplo. Lo haremos iniciando sesión en el contenedor y creando un archivo.

  1. Inicia sesión en el contenedor
    # docker exec -it small_bassi /bin/bashroot@1311034ca7dc:/#

    Usar exec con el switch -it crea una sesión interactiva para que ejecutes comandos directamente dentro del contenedor. En este caso, estamos ejecutando /bin/bash, así que podemos hacer cualquier otra cosa que necesitemos.

  2. La raíz del documento para el servidor nginx en este contenedor está en /app, así que sigue adelante y crea el archivo /app/index.php:
    vi /app/index.php
  3. Añade una simple rutina PHP al archivo y guárdala:
    <?phpfor ($i; $i < 10; $i++){ echo "Item number ".$i."\n";}?>
  4. Ahora sal del contenedor para volver a la línea de comandos principal:
    root@1311034ca7dc:/# exit
  5. Ahora vamos a probar la página. Para ello, ejecuta un simple comando curl:
    # curl http://localhost:32822/index.phpEstímulo númeroEstímulo número 1Estímulo número 2Estímulo número 3Estímulo número 4Estímulo número 5Estímulo número 6Estímulo número 7Estímulo número 8Estímulo número 9

Ahora es el momento de seguir adelante y añadir el RSS.

Hacer cambios en el contenedor

Ahora que sabemos que PHP está funcionando podemos seguir adelante y añadir soporte para RSS usando el paquete SimplePie. Para ello, simplemente lo descargaremos en el contenedor y lo instalaremos.

  1. El primer paso es volver a entrar en el contenedor:
    # docker exec -it small_bassi /bin/bashroot@1311034ca7dc:/#
  2. A continuación, sigue adelante y utiliza curl para descargar el paquete, guardándolo como un archivo zip:
    root@1311034ca7dc:/# curl https://codeload.github.com/simplepie/simplepie/zip/1.4.3> simplepie1.4.3.zip
  3. Ahora tienes que instalarlo. Para ello, descomprime el paquete, crea los directorios adecuados y copia en ellos los archivos necesarios de la siguiente manera:
    root@1311034ca7dc:/# unzip simplepie1.4.3.ziproot@1311034ca7dc:/# mkdir /app/phproot@1311034ca7dc:/# mkdir /app/cacheroot@1311034ca7dc:/# mkdir /app/php/libraryroot@1311034ca7dc:/# cp -r s*/library/* /app/php/library/.root@1311034ca7dc:/# cp s*/autoloader.php /app/php/.root@1311034ca7dc:/# chmod 777 /app/cache
  4. Ahora sólo necesitamos una página de prueba para asegurarnos de que funciona. Crea un nuevo archivo en el directorio /app:
    root@1311034ca7dc:/# vi /app/rss.php
  5. Ahora añade el contenido del archivo de ejemplo. (Este archivo está extraído de la página web de SimplePie, pero lo he recortado por brevedad, ya que no es realmente el foco de lo que estamos haciendo. Por favor, vea la versión original para comentarios, etc.)
    <?phprequire_once('php/autoloader.php');$feed = new SimplePie();$feed->set_feed_url("http://rss.cnn.com/rss/edition.rss");$feed->init();$feed->handle_content_type();?><html><head><title>Página de ejemplo de SimplePie</title></head><body><div class="header"><h1><a href="<?php echo $feed->get_permalink(); ?>><?php echo $feed->get_title(); ?></a></h1><p><?php echo $feed->get_description(); ?></p><><?php foreach ($feed->get_items() as $item): ?><div class="item"><h2><a href="<?php echo $item->get_permalink(); ?>><?php echo $item->get_title(); ?></a></h2><p><?php echo $item->get_description(); ?></p><p><small>Puesto en <?php echo $item->get_date('j F Y | g:i a'); ?></small></p></div><?php endforeach; ?></body></html>
  6. Salir del contenedor:
    root@1311034ca7dc:/# exit
  7. Comprobemos que funciona. Recuerda que tenemos que acceder al contenedor en el puerto alternativo (revisa docker ps para ver qué puertos necesitas usar):
    # curl http://localhost:32822/rss.php<html><head><title>Sample SimplePie Page</title></head><body><div class="header"><h1><a href="http://www.cnn.com/intl_index.html">CNN.com - Canal RSS - Página web de Intl - Noticias</a><CNN.com ofrece noticias e información al minuto sobre las últimas noticias, el tiempo, el entretenimiento, la política y mucho más.<p><>...
  8. Ahora, podemos convertirlo en una nueva imagen.

    Crear la nueva imagen

    Ahora vamos a ver cómo crear una imagen Docker desde el contenedor. Tenemos un contenedor que funciona y queremos convertirlo en una imagen y empujarla a Docker Hub para que los usuarios de la comunidad puedan acceder a ella. El nombre que usarás para tu contenedor normalmente tendrá tres partes:

    /:

    Por ejemplo, mi nombre de usuario de Docker Hub es nickchase, así que voy a nombrar la versión 1 de mi nuevo contenedor RSS-ificado

    nickchase/rss-php-nginx:v1
    1. Si cuando empezamos a hablar de las diferencias entre capas empezaste a pensar en los sistemas de control de versiones, estás en lo cierto. El primer paso para crear una nueva imagen es confirmar los cambios que ya hemos hecho, añadiendo un mensaje sobre los cambios y especificando el autor, como en:
      docker commit -m "Mensaje" -a "Nombre del autor" 

      Así que en mi caso, eso será:

      # docker commit -m "Added RSS" -a "Nick Chase" small_bassi nickchase/rss-php-nginx:v1sha256:148f1dbceb292b38b40ae6cb7f12f096acf95d85bb3ead40e07d6b1621ad529e
    2. A continuación queremos seguir adelante y empujar la nueva imagen al Docker Hub para poder utilizarla:
      # docker push nickchase/rss-php-nginx:v1El push se refiere a un repositorio 69671563c949: Pushed3e78222b8621: Pushed5b33e5939134: Pushed54798bfbf935: Pushedb8c21f8faea9: Pushed...v1: digest: sha256:48da56a77fe4ecff4917121365d8e0ce615ebbdfe31f48a996255f5592894e2b size: 3667
    3. Ahora si listáis las imágenes que están disponibles, deberíais verlo en la lista:
      # docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnickchase/rss-php-nginx v1 148f1dbceb29 hace 11 minutos 677 MBnginx latest abf312888d13 hace 3 días 181.5 MBwebdevops/php-nginx latest 93037e4c8998 3 days ago 675.4 MBubuntu latest e4415b714b62 2 weeks ago 128.1 MBhello-world latest c54a2cc56cbb 5 months ago 1.848 kB
    4. Ahora vamos a probarlo. Empezaremos parando el contenedor original para poder eliminar la copia local de la imagen:
      # docker stop small_bassi# docker rm small_bassi
    5. Ahora podemos eliminar la propia imagen:
      # docker rmi nickchase/rss-php-nginx:v1Untagged: nickchase/rss-php-nginx:v1Untagged: nickchase/rss-php-nginx@sha256:0a33c7a25a6d2db4b82517b039e9e21a77e5e2262206fdcac8b96f5afa64d96cDeleted: sha256:208c4fc237bb6b2d3ef8fa16a78e105d80d00d75fe0792e1dcc77aa0835455e3Deleted: sha256:d7de4d9c00136e2852c65e228944a3dea3712a4e7bcb477eb7393cd309be179b
    6. Si vuelves a ejecutar docker images, verás que ha desaparecido:
      # docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEnginx latest abf312888d13 3 days ago 181.5 MBwebdevops/php-nginx latest 93037e4c8998 3 days ago 675.4 MBubuntu latest e4415b714b62 2 weeks ago 128.1 MBhello-world latest c54a2cc56cbb 5 months ago 1.848 kB
    7. Ahora, si creas un nuevo contenedor basado en esta imagen, verás que se descarga del Docker Hub:
      # docker run -dP nickchase/rss-php-nginx:v1
    8. Por último, prueba el nuevo contenedor obteniendo el nuevo puerto…
      # docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES13a423324d80 nickchase/rss-php-nginx:v1 "/opt/docker/bin/entr" hace 6 segundos Arriba 5 segundos 0.0.0.0:32825->80/tcp, 0.0.0.0:32824->443/tcp, 0.0.0.0:32823->9000/tcp goofy_brahmagupta
    9. … y acceder al archivo rss.php.
      curl http://localhost:32825/rss.php
    10. Deberías ver la misma salida que antes.

      Usa un Dockerfile

      Crear manualmente una nueva imagen a partir de un contenedor existente te da mucho control, pero tiene un inconveniente. Si el contenedor base se actualiza, no necesariamente vas a tener los beneficios de esos cambios.

      Por ejemplo, supongamos que quiero un contenedor que siempre tome la última versión de un sistema operativo Linux como el sistema operativo Ubuntu y construya sobre él. El método anterior no nos da esa ventaja.

      En su lugar, podemos utilizar un método llamado Dockerfile, que nos permite especificar una versión concreta de una imagen base, o especificar que queremos utilizar siempre la última versión.

      Por ejemplo, digamos que queremos crear una versión del contenedor rss-php-nginx que empiece por v1 pero que sirva en el puerto 88 (en lugar del tradicional 80). Para ello, básicamente queremos realizar tres pasos:

      1. Empezar con el contenedor base deseado.
      2. Decirle a Nginx que escuche en el puerto 88 en vez de en el 80.
      3. Decirle a Docker que el contenedor escucha en el puerto 88.

      Lo haremos creando un contexto local, descargando una copia local del archivo de configuración, actualizándolo y creando un Dockerfile que incluya las instrucciones para construir el nuevo contenedor.

      Vamos a configurarlo.

      1. Crea un directorio de trabajo en el que construir tu nuevo contenedor. Lo que usted llama es completamente a usted. Yo llamé al mío k8stutorial.

      2. Desde la línea de comandos, En el contexto local, empieza por instanciar la imagen para que tengamos algo con lo que trabajar:
        # docker run -dP nickchase/rss-php-nginx:v1
      3. Ahora consigue una copia del archivo vhost.conf archivo. En este contenedor en particular, puedes encontrarlo en /opt/docker/etc/nginx/vhost.conf.
        # docker cp amazing_minksy:/opt/docker/etc/nginx/vhost.conf .

        Nota que tengo un nuevo contenedor llamado amazing_minsky para sustituir a small_bassi. En este punto deberías tener una copia de vhost.conf en tu directorio local, así que en mi caso, sería ~/k8stutorial/vhost.conf.

      4. Ahora tienes una copia local del archivo vhost.conf. Usando un editor de texto, abra el archivo y especifique que nginx debe estar escuchando en el puerto 88 en lugar del puerto 80:
        server { listen 88 default_server; listen 8000 default_server; server_name _ *.vm docker;...
      5. A continuación, queremos seguir adelante y crear el Dockerfile. Usted puede hacer esto en cualquier editor de texto. El archivo, que debe llamarse Dockerfile, debe comenzar especificando la imagen base:
        FROM nickchase/rss-php-nginx:v1
      6. Cualquier contenedor que se instancie desde esta imagen va a estar escuchando en el puerto 80, así que queremos seguir adelante y sobrescribir ese archivo de configuración de Nginx con el que hemos editado:
        FROM nickchase/rss-php-nginx:v1COPY vhost.conf /opt/docker/etc/nginx/vhost.conf
      7. Por último, tenemos que decirle a Docker que el contenedor escucha en el puerto 88:
        FROM nickchase/rss-php-nginx:v1COPY vhost.conf /opt/docker/etc/nginx/vhost.confEXPOSE 88
      8. Ahora necesitamos construir la imagen real. Para ello, utilizaremos el comando docker build:
        # docker build -t nickchase/rss-php-nginx:v2 .Sending build context to Docker daemon 2.048 kBSPaso 1 : FROM nickchase/rss-php-nginx:v1 ---> 208c4fc237bbPaso 2 : EXPOSICIÓN 88 ---> Ejecutando en 23408def6214 ---> 93a43c3df834Retirar el contenedor intermedio 23408def6214Construido con éxito 93a43c3df834

        Nota que hemos especificado el nombre de la imagen, junto con una nueva etiqueta (también puedes crear una imagen completamente nueva) y el directorio en el que encontrar el Dockerfile y cualquier archivo de apoyo.

      9. Por último, empuja la nueva imagen al hub:
        # docker push nickchase/rss-php-nginx:v2
      10. Prueba tu nueva imagen instanciándola y sacando la página de prueba. con un comando como docker run <image>. Por ejemplo:
        # docker run -dP nickchase/rss-php-nginx:v2root@kubeclient:/home/ubuntu/tutorial# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES04f4b384e8e2 nickchase/rss-php-nginx:v2 "/opt/docker/bin/entr" 8 seconds ago Up 7 seconds 0.0.0.0:32829->80/tcp, 0.0.0.0:32828->88/tcp, 0.0.0.0:32827->443/tcp, 0.0.0.0:32826->9000/tcp goofy_brahmagupta13a423324d80 nickchase/rss-php-nginx:v1 "/opt/docker/bin/entr" hace 12 minutos Up 12 minutes 0.0.0.0:32825->80/tcp, 0.0.0.0:32824->443/tcp, 0.0.0.0:32823->9000/tcp amazing_minsky

      Nota que ahora tienes un puerto mapeado para el puerto 88 al que puedes llamar:

      curl http://localhost:32828/rss.php

      Otras cosas que puedes hacer con Dockerfile

      Docker define toda una lista de cosas que puedes hacer con un Dockerfile, como:

  • .dockerignore
  • FROM
  • MAINTAINER
  • RUN
  • CMD
  • .

  • EXPOSICIÓN
  • ENV
  • COPY
  • ENTRYPOINT
  • VOLUME
  • USER
  • WORKDIR
  • ARG
  • ONBUILD
  • STOPSIGNAL
  • LABEL
  • Como puedes ver, hay bastante flexibilidad aquí. Puedes ver la documentación para más información, y wsargent ha publicado una buena hoja de trucos de Dockerfile.

    Avanzando

    Crear nuevas imágenes Docker que puedan ser utilizadas por ti o por otros desarrolladores es bastante sencillo. Tienes la opción de crearlas manualmente y confirmar los cambios, o guionizarlas usando un Dockerfile.

    En nuestro próximo tutorial, veremos cómo usar YAML para gestionar estos contenedores con Kubernetes.

Deja una respuesta

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