Ejecutar contenedores docker

Este es uno de los capítulos del tutorial Docker. Introducción y primeros pasos. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.

En los capítulos anteriores de este tutorial viste como gestionar imágenes y como gestionar contenedores. Esto, era preciso para poder controlar lo que haces con docker, sin llenar tu equipo ni de imágenes ni contenedores que nunca utilizas, o de varios contenedores que están corriendo sin control. Pero, ha llegado ya el momento, de que empieces a lanzar contenedores como si no hubiera un mañana. Y para esto, te propongo hacerlo paso a paso, y siguiendo un ejemplo sencillo. Comenzando por lo mas sencillo para terminar por lo mas complejo. Pero básicamente, el objetivo de este capítulo del tutorial es ejecutar contenedores con docker.

Vamos allá

docker run o ejecutar contenedores

Ejecutar contenedores docker

Un pequeño consejo…

Si quieres una buena ayuda para trabajar con docker, te recomiendo que utilices Bash-it. Si no sabes lo que es, te recomiendo también la lectura del artículo sobre potenciar el poder del terminal, y activa los alias para docker.

El contenedor de entrenamiento

Mi propuesta para este capítulo, es partir de un contenedor de Nginx. Un cotenedor con el que probar esto de ejecutar contenedores docker. Se trata de partir de una imagen de Nginx sobre Alpine. De esta manera te vas a asegurar tener tanto una imagen como un contenedor de reducidas dimensiones.

Pero antes de esto aclarar que es Alpine, Nginx, y que es lo que tendremos al final de todo este proceso.

Alpine

Alpine Linux es una distribución Linux basada en musl y BusyBox, diseñada pensando en la seguridad, simplicidad y la eficiencia de recursos. En este sentido, imagina que el peso de la imagen de docker apenas supera los 5 MB. ¿Que mas se puede pedir en cuanto a ligereza?.

Nginx

Si eres un oyente o lector asiduo de este sitio, seguro que Nginx te resulta conocido. Lo cierto que en los últimos tiempos lo he utilizado para casi cualquier cosa que puedas imaginar, desde un servidor web hasta un proxy inverso.

Si no lo conoces y quieres comenzar a probar con él, te recomiendo una lectura al capítulo del tutorial sobre Raspberry, primeros pasos, en el que te explico como instalar una infraestructura LEMP con Nginx.

Cuando tengas el contenedor en funcionamiento vas a tener un sencillo servidor de páginas web que vas a poder utilizar para mostrar lo que quieras, o necesites. Y poner ese servidor en funcionamiento, será igual de sencillo que cualquier otro servicio que quieras. Tan solo se tratará de ejecutar contenedores docker.

La imagen

La imagen de Nginx con Alpine esta disponible en el repositorio oficial, con lo que tenerla en tu equipo y crear tu primer contenedor es terriblemente sencillo. Igual que para cualquier otro servicio, tan solo ejecutar contenedores. Pero, no te preocupes que se irá complicando poco a poco.

Paso a paso

Ejecutando el contenedor sin mas…

Llegó el momento. Como primer paso, ejecuta el contenedor sin mas. Para ello, simplemente tienes que ejecutar la siguiente instrucción,

$ docker run -d --name test01 nginx:alpine

Algunas observaciones,

  • Como es una imagen oficial observarás que no es el repositorio típico usuario/nombre_imagen.
  • Lo segundo que habrás notado es la etiqueta :alpine. En el repositorio de nginx, existen multitud de imágenes. Y no solo por versión de Nginx, también por los acompañamientos que lleve, por ejemplo Perl, o Alpine o Alpine y Perl, y mucho mas.
  • La opción -d es para que el contenedor se ejecute en segundo plano. De otra forma, si no incluyes esta opción, el terminal se detendrá en este punto, y no podrás interactuar con él.
  • La opción --name no es necesaria, al igual que la anterior, pero es realmente cómoda, porque te permite llamar al contenedor de tu a tu. Es mucho mas sencillo indicar el nombre del contenedor para trabajar con él que su código identificador.

Para conocer el peso de esta imagen, ejecuta,

docker images | grep nginx

Verás que apenas supera los 21 MB. Si quieres tener mas información, ejecuta docker history nginx:alpine. El resultado que verás es el siguiente,

MAGE               CREATED             CREATED BY                                      SIZE                COMMENT
d87c83ec7a66        3 weeks ago         /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
<missing>           3 weeks ago         /bin/sh -c #(nop)  STOPSIGNAL SIGTERM           0B
<missing>           3 weeks ago         /bin/sh -c #(nop)  EXPOSE 80                    0B
<missing>           3 weeks ago         /bin/sh -c set -x     && addgroup -g 101 -S …   15.6MB
<missing>           4 weeks ago         /bin/sh -c #(nop)  ENV PKG_RELEASE=1            0B
<missing>           4 weeks ago         /bin/sh -c #(nop)  ENV NJS_VERSION=0.3.5        0B
<missing>           4 weeks ago         /bin/sh -c #(nop)  ENV NGINX_VERSION=1.17.3     0B
<missing>           4 weeks ago         /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B
<missing>           5 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
<missing>           5 weeks ago         /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9…   5.58MB

Si te fijas, la última posición, aunque en realidad es la primera, se corresponde con Alpine, y supera ligeramente los 5.5 MB. Por otro lado, el peso de Nginx es de 15.6MB.

Si ahora ejecutas docker ps, te aparecerá la ejecución de tu contenedor. Sin embargo no puedes hacer nada con él. A menos, que entremos en el interior de ese contenedor y demos un vistazo. Para entrar en el interior de ese contenedor, la instrucción a ejecutar es,

docker exec -it test01 sh

Una vez en el interior del contenedor, simplemente, ejecuta ps -ef, y verás que hay muy pocos procesos. En particular,

PID   USER     TIME  COMMAND
    1 root      0:00 nginx: master process nginx -g daemon off;
    8 nginx     0:00 nginx: worker process
    9 nginx     0:00 nginx: worker process
   10 nginx     0:00 nginx: worker process
   11 nginx     0:00 nginx: worker process
   17 root      0:00 sh
   26 root      0:00 ps -ef

Sin embargo, no podemos ver Nginx en funcionamiento, con lo que, realmente, este contenedor que tenemos ahora no nos sirve para gran cosa.

Exponiendo puertos

Dado que el contenedor anterior, como has visto, no te es de gran utilidad, detenlo y bórralo. Para esto, tienes que aprovechar que le has puesto nombre, recuerda test01. Para ello, ejecuta estas dos instrucciones,

docker stop test01
docker rm test01

Para poder poder ver lo que sirve Nginx, tienes que conectar el puerto del contenedor con el puerto de tu equipo. Para eso, la instrucción que vas a ejecutar es,

docker run -d --name test01 -p 81:80 nginx:alpine

En este caso, la opción -p 81:80 indica que cuando te conectas al puerto 81 de tu equipo, estás conectandote al puerto 80 del contenedor. Así, ahora, simplemente inicia el navegador y escribe la dirección http://localhost:81.

¿porque he puesto el puerto 81? Simplemente, para que tengas claro quien es quien. Es decir, para que veas cual es el puerto del equipo y cual el del contenedor. Si quieres, para el contenedor, bórralo y ejecuta un contenedor, pero esta vez con -p 80:80. Verás que ahora tan solo tienes que excribir http://localhost.

Por supuesto, puedes exponer mas de un puerto. Por ejemplo, si quieres tener una conexión segura, puedes exponer en puerto 443. Para ello, ejecuta la siguiente instrucción,

docker stop test01
docker rm test01
docker run -d --name test01 -p 80:80 -p 443:443 nginx:alpine

Ahora ya puedes conectarte tanto al puerto 80 como al puerto 443. Simplemente tienes que escrbir en tu navegador http://localhost o https://localhost. Sin embargo, en el segundo caso te pondrá un mensaje de error indicando que la conexión segura ha fallado.

Para resolver el problema de la conexión segura es necesario contar con certificados. Para este tutorial, partiré de resolverlo con certificados autofirmados. Esto realmente no es necesario, porque podríamos hacerlo con Let’s Encrypt, pero esto, lo haremos en otra ocasión.

Así, te ofrezco dos soluciones, una que es no deseable, y a continuación el procedimiento mas adecuado

La solución no deseable

Esto que te voy a comentar a continuación es justo lo que no tienes que hacer, para resolver el problema de la conexión segura, o cualquier otro problema, utilizando el mismo procedimiento.

Para resolver este problema, vas a generar los certificados autofirmados en tu equipo, para posteriormente copiarlos al contenedor. Para hacer esto, ejecuta las siguientes instrucciones en un terminal en tu equipo, no en el contenedor,

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx-selfsigned.key -out nginx-selfsigned.crt
openssl dhparam -out dhparam.pem 2048

A continuación los copias al contenedor,

docker cp nginx-selfsigned.crt test01:/
docker cp nginx-selfsigned.key test01:/
docker cp dhparam.pem test01:

Entra en el contenedor ejecutando docker exec -it test01 sh. Una vez dentro del contenedor, crea los directorios para los certificados y copialos en esos directorios, conforme puedes ver en las siguientes líneas,

mkdir -p /etc/ssl/certs/
mkdir -p /etc/ssl/private/
mv /nginx-selfsigned.crt /etc/ssl/certs/
mv /nginx-selfsigned.key  /etc/ssl/private/
mv dhparam.pem /etc/ssl/certs/

Una vez ya tienes esto, tienes que modificar la configuración de Nginx, para esto, tienes dos opciones, o bien, la editas directamente en el contenedor, o bien, la editas en tu equipo y la copias. Voy a optar por editarla en el equipo.

Para editarla en el contenedor, necesitas instalar un editor, porque no hay ninguno por defecto. Para instalarlo, simplemente ejecuta la siguiente instrucción,

apk add nano

Ya tienes instalado nano, ahora edita la configuración de Nginx, ejecutando,

nano /etc/nginx/conf.d/default.conf

A continuación, te dejo el contenido de la configuración que yo he utilizado,

server {
    listen       80;
    listen 443 http2 ssl;
    listen [::]:443 http2 ssl;

    server_name  localhost;

    ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Ya lo tienes todo. Los siguientes pasos, son salir del contenedor, pararlo y arrancarlo. Ojo, que tienes que arrancar este contenedor,

docker stop test01
docker start test01

Si todo ha ido bien, deberías acceder a https://localhost/. Pero, te habrás dado cuenta del trabajo.

Guardando los cambios

Ahora que ya has realizado todos los cambios, no estaría nada mal poder guardarlos. Efectivamente, tienes la posibilidad de guardarlos en forma de imagen, de forma que la próxima que necesites un contenedor de Nginx con certificado no tengas que hacerlo de nuevo.

Para crear esa imagen, simplmente ejecuta, la siguiente orden,

docker commit test01 atareao/nginx:autocertificado

Ahora, lista tus imágenes, y encotrarás esta nueva imagen. Y, no estaría nada mal, que la tuvieras disponible desde un repositorio, ¿no?. Pues esto lo podrás ver en un capítulo posterior dedicado en exclusiva a Docker Hub.

Exponiendo volúmenes

Esto es una auténtica locura. Cada vez que quieras modificar la configuración de Nginx, o cada ves que quieras cambiar los certificados tendrás que modificar la imagen. Es mas, todavía voy un paso mas allá. Cada vez que quieras cambiar la web, vas a tener que cambiar la imagen. Esto no es ni productivo ni mucho menos. Es necesario buscar otra solución.

Y por supuesto que la hay. La solución es algo similar a lo que hiciste con los puertos, pero esta vez con directorios. Me explico. Se trata de engañar al contenedor, haciéndole pensar que un directorio de tu equipo es un directorio del contenedor. Así, por ejemplo, podemos decirle que el directorio de tu equipo /home/lorenzo/docker/conf es el directorio /etc/nginx/conf.d del contenedor. De esta sencilla manera, cuando busque en el directorio del contenedor la configuración de Nginx, realmente la estará buscando en tu equipo.

De esta forma, de una manera realmente sencilla, habrás solucionado el problema de modificar la configuración sin necesidad de modificar ni el contenedor ni la imagen. Ya tienes todo el problema resuelto.

Así por ejemplo, para el ejemplo en cuestión, podemos ejecutar la siguiente instrucción que nos levantará el docker con los directorios de configuración y el de la página web en nuestro local,

docker run \
-d -p  80:80 \
-p 443:443 \
-v "$(pwd)"/crt:/etc/ssl/certs \
-v "$(pwd)"/conf:/etc/nginx/conf.d \
-v "$(pwd)"/web:/var/www/html \
--name test01 atareao/nginx:autocertificado

En este caso, he realizado algunas modificaciones para el archivo de configuración de Nginx. Por supuesto en local, sin necesidad de la locura que había hecho anteriormente. La nueva configuración es la que puedes ver a continuación, y que iría dentro del archivo /etc/nginx/conf.d/default.conf


server {
    listen       80;
    listen 443 http2 ssl;
    listen [::]:443 http2 ssl;

    server_name  localhost;

    ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
    ssl_certificate_key /etc/ssl/certs/nginx-selfsigned.key;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

En el directorio crt he puesto los certificados, en el directorio conf la configuración de Nginx, y por último, en el directorio web he dejado el contenido de la web.

Simplemente espectacular

Conclusión

Desde luego que la parte en la que explico como no utilizar docker, no la tienes que repetir. Se trata de que vieras lo complicado que se puede volver hacer una imagen, y lo sencillo que es utilizar la asignación tanto de puertos como de volúmenes a la hora de ejecutar contenedores.

Fíjate que ahora, con una sola línea, tienes montado el servidor Nginx, con la ventaja de que todas las modificaciones que haces en local se verán reflejadas en el servidor. Una auténtica maravilla.

De esta forma, si eres desarrollador web, no tienes que levantar ni un XAMP, ni un LAMP ni u LEMP, ni nada de nada. Con una sola línea de terminal, es decir, con ejecutar contenedores, tienes preparado tu servidor para empezar a trabajar. Con la increíble ventaja de que esta imagen, la puedes subir a los repositorios de Docker Hub, y desde allí descargarla donde la necesites utilizar, con la garantía de que funcionará.


Más información,

Imagen de Erich Westendarp en Pixabay

Deja un comentario

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