lunes, 12 de julio de 2021

Docker para principiantes.


Logo de Docker con fichas de Lego


Docker para Principiantes.

   A groso modo lo que hace este programa es encapsular una aplicación en un contenedor, con todo lo que necesita para que funcione en cualquier entorno (sistema operativo, bibliotecas, interpretes, bases de datos etc). La idea es ejecutar programas sin que dependan tanto del sistema operativo, simplemente el contenedor se encarga de ejecutar la aplicación.

Como cada contenedor va a tener su propio espacio, nos permitirá utilizar varios contenedores de diferentes programas sin que interfieran entre ellos y de una manera muy eficiente. (las aplicaciones funcionan de forma aislada). Tienen un rendimiento muy similar al que obtendríamos si instaláramos directamente ese programa en nuestro sistema operativo. Lo entenderás mejor cuando veas los ejemplos.

Instalación de Docker en Ubuntu.

Docker o Docker Engine en Linux era originalmente un terminal. Una instalación que funciona comprende el Motor Docker, un daemon que ejecuta sus contenedores, y la interfaz de línea de comandos (CLI) docker para interactuar con el daemon.

Docker Desktop es una forma alternativa de utilizar Docker. Inicialmente sólo estaba disponible para Windows y Mac, pero se lanzó para Linux en mayo de 2022. Docker Desktop en Ubuntu requiere un procesador AMD64 y Ubuntu 21.10 o 22.04. Ofrece una interfaz gráfica para gestionar tus contenedores, un clúster Kubernetes integrado y soporte para extensiones de terceros. Docker Desktop también te ofrece las CLIs docker y Docker Compose.

Docker Desktop se diferencia de Docker Engine en que utiliza una máquina virtual para ejecutar tus contenedores (por lo que Docker Desktop es necesario para los usuarios de Windows y MacOS). Aunque es posible utilizar ambos flavors uno al lado del otro en Ubuntu, puedes encontrar conflictos entre las versiones de la CLI de docker. Lo mejor es elegir una y ceñirse a ella. Elige Docker Engine si te sientes cómodo en tu terminal y quieres el mejor soporte y rendimiento. La VM de Docker Desktop es ligeramente más pesada, pero es la mejor opción si quieres una interfaz de usuario integrada o piensas utilizar Kubernetes.

Te recomiendo que busques en la siguiente página web la opción más actual para instalar o bien Docker o Docker Desktop. Las instrucciones de instalación deberían encontrarse en https://docs.docker.com/get-docker/

Más o menos los pasos deberían ser los siguientes:

1.-  Actualizamos los repositorios e instalamos los paquetes necesarios para que la aplicación apt pueda usar repositorios sobre https.

$ sudo apt-get update

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

2.- Añadimos la llave GPG oficial de Docker.

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg


3.- Configuramos la aplicación para que utilice el repositorio estable de docker.

 $ echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null


4.- Instalamos Docker propiamente dicho con:

 $ sudo apt-get update
 $ sudo apt-get install docker-ce docker-ce-cli containerd.io

Si todo ha ido bien ya tenemos Docker instalado en nuestro equipo. Primeramente vamos a ver que todo este bien comprobando la versión que tenemos instalada. Para ello ejecutamos:

$ docker version
Docker version 20.10.7, build f0df350

Si no puedes ver la salida de la instrucción prueba utilizando "sudo" delante. Para no tener que hacer esto con cualquier instrucción de docker tenemos que añadirlo a nuestro grupo de usuarios. Para ello vamos a la sección de post instalación  y seguimos los tres puntos que nos indican:

1.- Creamos el grupo Docker y agregamos nuestro usuario. 

Advertencia

El grupo Docker tiene privilegios equivalentes a los del usuario root. Si quieres saber más detalles de como esto afecta a la seguridad de tu sistema echa un vistazo a Docker Daemon Attack Surface.

 $ sudo groupadd docker

Nota si al ejecutar Docker --version no te dio error seguramente te dirá ahora que ya existe el grupo Docker.

2.- Añadimos nuestro usuario al grupo de Docker.

 $ sudo usermod -aG docker $USER

3.- Ahora tenemos que cerrar la sesión y volver a abrirla para que los cambios tengan efecto. Si estás utilizando una máquina virtual puede ser necesario reiniciarla para que los cambios tengan efecto. Ya deberiamos poder utilizar cualquier instrucción de docker sin tener que teclear "sudo".


Instalación de Docker en una Raspberry Pi.

Paso 1: Poner al día y actualizar el sistema.

Este paso nos asegurará que tenemos las últimas versiones de las librerías y el software. Abrimos una ventana de terminal y tecleamos la instrucción:

$ sudo apt-get update && sudo apt-get upgrade


Paso 2: Descargamos el script necesario para instalar Docker en una Rapsberry Pi.

$ curl -fsSL https://get.docker.com -o get-docker.sh

y lo ejecutamos usando la instrucción:

$ sudo sh get-docker.sh

Esto instalará los paquetes necesarios para nuestra la distribución Raspbian de la Pi.

La salida del programa nos dirá que versión de Docker esta ejecutando nuestro sistema.

Salida por terminal de la instalación de Docker


Paso 3: Añadir un usuario no-root al grupo de Docker.

Por defecto, solo los usuarios que tienen privilegios de administración (root users) pueden ejecutar contenedores. Como en la Raspberry el usuario root viene deshabilitado por defecto una opción que tenemos es utilizar el prefijo "sudo" antes de ejecutar cualquier instrucción.

Sin embargo, también podemos añadir el usuario Pi o tu propio usuario al grupo de Docker lo que nos ahorrra el teclear "sudo" constantemente. 

La sintaxis para añadir usuarios al grupo de Docker es:

$ sudo usermod -aG docker [nombre_del_usuario]

Si usamos el usuario por defecto en la Raspberry, el usuario Pi la intrucción quedaría de la siguiente forma:

$ sudo usermod -aG docker pi

Para que los cambios tengan efecto necesitamos salir de la sesión y volver a entrar o apagar y encender la máquina, como tu veas.

Paso 4: Comprobar que versión de Docker tenemos instalada.

$ docker version

La salida por pantalla nos mostrará la versión de Docker con alguna información adicional. Para obtener información de todo el sistema (incluida la versión del kernel, el numero de contenedores e imagenes y una descripción más detallada) tecleamos:

$ docker info

Nota: cuando utilices imágenes para la pi solo te funcionarán las que sean compatibles con procesadores ARM.


Nuestro Primer "Hello World" usando una imagen de Docker.

Antes de entrar más en materia debemos explicar de forma muy muy sencilla que es una imagen de Docker. Para que te hagas una idea muy por encima, podríamos asociar una imagen de docker con un instalador de programas de windows, una especie de Iso. Esta tendrá todo lo que necesita el programa que queramos usar para funcionar (sistema operativo base, librerías, variables de entorno, archivo de configuración, el propio programa en si etc). Así, podemos encontrar imágenes de sistemas operativos, de bases de datos (Mysql), de servidores (apache o nginx por ejemplo), de entornos de programación (Python por ejemplo) y de muchísimos más programas listos para utilizar en cualquier sistema. Utilizando estas imágenes como base es como crearemos nuestros contenedores.


Esquema de funcionamiento de contenedores de Docker


Para encontrar las imágenes que necesitamos y sus instrucciones debemos ir a la página oficial de Docker Hub. Sería conveniente que te registraras ya que eso te facilitará posteriormente subir tus propias imágenes.

Vamos a entrar en materia ejecutando nuestra primera imagen, que no hace nada, salvo el típico "hello world" de iniciación, pero que nos servirá para ver que todo funciona bien y familiarizarnos con el programa.

Vamos a docker hub y en su buscador tecleamos "hello-world"

Pagina de Hello-World en Docker Hub


Aquí encontramos información sobre la imagen, para que sistemas funciona, instrucciones de instalación, variables de entorno que necesita etc. Esta en concreto para hacerla funcionar podríamos teclear tal como pone en las instrucciones:

$ docker pull hello-world

y bajarla hasta nuestro equipo y luego ejecutarla. Sin embargo vamos a hacer otra cosa. Entramos en la terminal y tecleamos:

$ docker run hello-world

ejecución de una imagen de Docker


Como ves si ejecutamos una imagen que no está descargada localmente, docker la busca en su base de datos, la descarga y posteriormente la ejecuta.

Si ahora quisiéramos saber que imágenes tenemos descargadas localmente, usaríamos la instrucción:

$ docker images

Imagen de Docker


Trabajando con imágenes y contenedores.

Ahora, ¡Hagamos algo más interesante! Vamos a ejecutar en un contenedor una imagen de un sistema operativo, en este caso Ubuntu.

Volvemos a docker hug y buscamos "ubuntu". Hacemos click en su imagen oficial.

Imagen de Ubuntu en Docker Hub

Aunque si eres un amante del terminal también puedes buscar imágenes desde el terminal con:

$ docker search [nombre_de_la_imagen]

$ docker search ubuntu

Resultado de busqueda de imagenes por terminal

Descargamos la imagen a nuestro ordenador.

$ docker pull ubuntu

Imagen Docker Hello-World

Ahora lo que nos va a permitir esto es ejecutar ubuntu, dentro de nuestro sistema operativo, pero no utilizando máquinas virtuales, si no como un proceso más con lo que esto supone en términos de rendimiento.

Lo lógico parece que para ejecutar ubuntu usemos la instrucción

 $ docker run ubuntu

y no vamos desencaminados lo que pasa es que se ejecuta el proceso y se vuelve a cerrar sin que ocurra nada. El asunto es que yo quiero ver el contenedor de manera interactiva y poder usarlo para hacer cosas. Vayamos un paso más y vamos a mostrar por pantalla "hello world" usando el contenedor de ubuntu. Para ello le diremos a docker que ejecute ubuntu y dentro de ubuntu el programa "echo"

 $ docker run ubuntu echo "hello world"

esto abrirá Ubuntu y ejecutará el programa "echo" mostrando el mensaje por pantalla y luego se cerrará.

Pero yo lo que quiero realmente es utilizar el programa bash de Ubuntu para interactuar con el sistema. Para ello, además de usar la estructura del comando anterior, en el que ejecutamos el programa "echo", tenemos que utilizar el argumento -it de interactivo para que no simplemente ejecute el programa y se cierre.

 $ docker run -it ubuntu bash

Salida por terminal del comando ls de Linux


o también se consigue lo mismo con:

 $ docker run --rm -it ubuntu:latest

En este caso el argumento --rm al usar run le dice a docker que borre el contenedor al finalizar o cerrar el mismo. Este argumento se suele usar de forma general ya que sino el contenedor o proceso nos quedaría parado a la espera de que lo volviéramos a iniciar. Lo del Tag latest es para ver como usar la etiqueta y porque es la versión que nos hemos descargado (se puede ver con $ docker images)

En la salida, el número después de la @ (máquina de linux) es el número de identificación único del contenedor en ejecución, que es diferente del id de la imagen. Para ver los contenedores que tenemos abiertos al ejecutar una imagen utilizamos la siguiente instrucción:

 $ docker ps 

imagen de un contenedor de Docker


y ¿por que pasa esto? Pues porque podemos ejecutar la imagen tantas veces como necesitemos, al mismo tiempo y por tanto cada contenedor tiene que diferenciarse del resto en ejecución a través de esa ID única. Pararé este contenedor tecleando exit o ctrl+c.

Como ves tengo un Linux funcionando en mi caso dentro de otro Linux (el sistema host en mi caso) de ahí que hablemos de contenedores. 

En resumen los contenedores son procesos que se crean a partir de imágenes.

Para ver los procesos que se encuentran parados, pero que no hemos destruido y por tanto podemos volver a iniciar utilizaremos la instrucción:

 $ docker ps -a

Muestra de contenedores de Docker


Donde nos listará estos procesos o contenedores que están parados y que podemos volver a arrancar en cualquier momento usando

 $ docker start -a [id entero] o [3 primeros caracteres del id] o [NAMES]

Si por ejemplo nos salimos sin querer del terminal donde tenemos abierta la aplicación y queremos pararla usaremos:

$ docker stop -a [id entero] o [3 primeros caracteres del id] o [NAMES]

Una vez que tenemos parados los contenedores si queremos borrarlos para que no ocupen espacio, lo haremos usando el comando rm. Por ejemplo, como al descargar la imagen "hello-world" y ejecutarla se creo el contendor cuyas 3 primeros números de id son, en mi caso, bad o cuyo nombre arbitrario es "modest_dijkstra" para borrarlo hare lo siguiente:

$ docker rm bad
o
$ docker rm modest_dijkstra

el terminal nos devolverá el id del contener y si vuelvo a usar $docker ps -a, el contenedor "hello-world" ya no está. pero que no esté el contenedor no significa que se haya borrado la imagen en la que se basa, que tenemos almacenada en nuestro ordenador. Al teclear:

$ docker images 

Muestra de imagenes de Docker

La imagen sigue ahí. Como ya no la necesito, porque su instalación fue para ver como funcionaba Dpcker procedo a borrarla de la misma forma que el contenedor pero usando el comando rmi (de remove image) y usando el nombre del repositorio o los 3 primeros caracteres del id de la imagen.

$ docker rmi hello-world

Borrado de Imagenes de Docker

No nos dejará borrar una imagen si tenemos algún contenedor parado o en activo que dependa de ella. Asi que tenemos primero que borrar los contenedores y después las imagenes. Si quisiéramos borrarla aun así, tendríamos que forzarla con el argumento -f.


Ejemplo Práctico: utilizar Nginx en un contenedor. 

Para seguir viendo más concepto de Docker vamos a instalar Nginx y ejecutarlo en un contenedor. Para el que no lo sepa Nginx es un servidor de páginas web, un servidor http. Como hemos visto lo podemos buscar yendo a la página de Docker Hub y buscándolo o bien utilizando la consola "$docker search nginx".

Como me interesa no solo descargar la imagen sino ver las instrucciones de funcionamiento voy a buscarla en Docker Hub.

Imagen de Nginx en Docker Hub


Vamos a traer la imagen a nuestro ordenador.

$ docker pull nginx

Descarga de imagen de Nginx

Aquí para que funcione, al ser un servidor, no vale solo con utilizar el comando "docker run nginx" si no que tenemos que pasarle algunos argumentos más. No obstante vamos a ejecutar este comando aunque el terminal se quede en escucha. Abrimos un terminal y tecleamos:

$ docker run nginx

Este terminal se nos quedará bloqueado, parado ejecutando el proceso. Lo que me interesa es ver otra cosa. Abrimos un nuevo terminal, sin cerrar el anterior y vemos que el proceso está activo:

$ docker ps

Salida de la ejecución de un contenedor basado en Nginx


Lo que me interesa ver es que el servidor internamente, dentro del contenedor funciona en el puerto 80. Si queremos que funciones en nuestro ordenador tenemos que acceder de alguna forma a ese puerto interno desde un puerto de nuestro ordenador. Veamos como. Lo primero cerramos la imagen que tenemos en ejecución, yendo al termina que está bloqueado y tecleando crtl + c. 

Ahora vamos a volver a ejecutarlo pero utilizando el argumento -p (de puerto) de la siguiente forma:

-p [Puerto_que_queremos_utilizar]:[Puerto interno en el contedor]


Ejecuta:

$ docker run --rm -p 3000:80 nginx

Esto lo que quiere decir es que ejecute nginx y lo que internamente, en el contenedor, funciona en el puerto 80 que en mi ordenador se muestre en el puerto 3000. Si vamos al navegador y vamos al puerto 3000 veremos la página de bienvenida de Nginx.

Página de bienvenida a Nginx
Nota: pongo también el parámetro --rm para que al cerrar el contenedor, lo borre, ya que sino cada vez que ejecutemos nginx (usemos su imagen ) se creará un contenedor nuevo que nos irá comiendo espacio sino lo borramos.

Si queremos que un contenedor no nos bloquee el terminal y funcione en segundo plano usamos el parámetro -d. También aprovechamos y cambiamos el puerto de nuestro ordenador al 4000, por ejemplo, para practicar.

$ docker run --rm -d -p 4000:80 nginx

Verás que te devuelve como respuesta el id (en versión larga del contenedor) y ya no bloqueará el terminal, aunque el contenedor se está ejecutando. Ve al puerto 4000 del localhost y verás como funciona.

Vamos a ver ahora como borrar varios contenedores que tengamos parados en vez de tener que irlos borrando 1 a 1. Para empezar ejecuta y para varios contenedores. Yo ejecutare varias veces la imagen de Ubuntu que descargamos antes.

comando docker ps -a contenedores detenidos
$ docker ps -a (para ver contenedores parados)

Para no tener que ir uno por uno, vamos a utilizar dos comandos enlazados. Para ver solamente la lista de ID sin toda la demás información añadiremos el argumento "-q"

$ docker ps -aq
Id de contenedores sin más argumentos

y esto se lo pasaremos a la opción que ya conocíamos para borrar los contenedores "docker rm". Lo haremos usando esta característica del bash de linux.

$ docker rm $(docker ps -aq)

Al ejecutar la anterior instrucción se utilizará la lista de ID como argumento para la opción de Docker de borrar contenedores y asi los borraremos todos a la vez.


Ejecución simultanea de un contenedor en varios puertos.

Si lo necesitamos podemos ejecutar varios contenedores de forma simultánea, usando diferentes puertos de nuestro ordenador para comunicarse. Por ejemplo si queremos ejecutar tres instancias de nginx en los puertos 5000, 7000 y 9000 podríamos, con lo que ya sabemos, ejecutar tres veces la imagen de esta forma:

$ docker run --rm -d -p 5000:80 nginx
cd925884fa2ec615ebbe35ea33b86ad9b64ec4cbc2260fe8bb3e7d03dd003011
$ docker run --rm -d -p 7000:80 nginx
0259a7977e0449028c8bbdcb4f3703c5365606fd038a1ccb429311c979c1126a
$ docker run --rm -d -p 9000:80 nginx
04cbaba2452e2483fcd7a8a2f97f625d5113b33c9739f624f89b97ee858f9bb8
Varias páginas de inicio de Nginx abierta en diferentes ventanas del navegador



Ahora para ver otra forma de hacerlo más sencilla, primero vamos a parar todos los contenedores. Como ya vimos antes usaremos el argumento -q.

$ docker stop $(docker ps -q)

Pero lo anterior también lo podemos hacer con una sola línea:

$ docker run --rm -d -p 5000:80 -p 7000:80 -p 9000:80 nginx

La diferencia es que ahora tenemos un solo proceso pero que se muestra en los tres puertos.

$ docker ps: procesos activos.




Más sobre trabajo con contenedores.

Al trabajar con cualquier proyecto normalmente sueles necesitar una base de datos. Vamos a bajarnos y ejecutar una que se utiliza con bastante frecuencia Mysql. Esta vez utilizaremos el parámetro --name para darle nosotros un nombre al contenedor y que no utilice un nombre aleatorio. (por defecto mysql funciona el en el puerto 3306)

$ docker run -p 3306:3306 --name Mi_base mysql

latest: Pulling from library/mysql
b4d181a07f80: Already exists 
a462b60610f5: Pull complete 
578fafb77ab8: Pull complete 
524046006037: Pull complete 
d0cbe54c8855: Pull complete 
aa18e05cc46d: Pull complete 
32ca814c833f: Pull complete 
9ecc8abdb7f5: Pull complete 
ad042b682e0f: Pull complete 
71d327c6bb78: Pull complete 
165d1d10a3fa: Pull complete 
2f40c47d0626: Pull complete 
Digest: sha256:52b8406e4c32b8cf0557f1b74517e14c5393aff5cf0384eff62d9e81f4985d4b
Status: Downloaded newer image for mysql:latest
2021-07-07 18:41:55+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.25-1debian10 started.
2021-07-07 18:41:55+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2021-07-07 18:41:55+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.25-1debian10 started.
2021-07-07 18:41:55+00:00 [ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
    You need to specify one of the following:
    - MYSQL_ROOT_PASSWORD
    - MYSQL_ALLOW_EMPTY_PASSWORD
    - MYSQL_RANDOM_ROOT_PASSWORD

No te preocupes porque si intentas ver los procesos activos, verás que no lo está. La razón es porque en algunos contenedores necesitamos pasarle VARIABLES DE ENTORNO para que funcionen. Las veremos en un momento. Lo que me interesa es que ya tenemos la imagen para su uso posterior y si miras los contenedores parados verás como este ya no tiene un nombre aleatorio, sino que se llama "mi_base" y este nombre lo podemos utilizar para parar el contenedor, borrarlo etc.

app/routes.py: Home page route

CONTAINER ID   IMAGE     COMMAND                  ....          NAMES
897baed637c1   mysql     "docker-entrypoint.s…"   ....          Mi_base
Bien, ahora para que funcione tenemos que pasarle variables de entorno. ¿Y  como sabemos que variables de entorno hay que pasar? Pues ahí tenemos que ir a la pagina de Docker Hub, buscar la imagen y ver la documentación. En ella nos dirán que variables de entorno se necesitan, además de ejemplos de como hacer funcionar el contenedor. Ya cuando la instalamos, si te fijas, nos decía que obligatoriamente había que pasarle una de tres opciones. Para pasar variables de entorno se usa el parámetro -e

Ejecución del contenedor pasandole la variable de entorno que es la contraseña del root = contraseña

$ docker run --rm -p 3306:3306 --name Mi_base -e MYSQL_ROOT_PASSWORD="contraseña" mysql


Eliminar imagenes.

Si queremos eliminar una o todas las imágenes tenemos que utilizar el comando rmi junto con el nombre de la imagen o los tres primeros caracteres del id de la imagen. Yo ahora mismo tengo las siguientes imágenes:

$ docker images

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    4f380adfc10f   2 weeks ago   133MB
mysql        latest    5c62e459e087   2 weeks ago   556MB
ubuntu       latest    9873176a8ff5   2 weeks ago   72.7MB

Si quisiera borrar la imagen de nginx lo podría hacer con:

$ docker rmi nginx
o tambien con
$ docker rmi 4f3

¿Pero que pasa si queremos borrar todas las imágenes a la vez? Pues lo hacemos de una forma parecida a como lo hacíamos con los contenedores, usando una lista de imagenes con el parámetro -q para no tener que ir borrándolos uno a uno.

$ docker rmi $(docker images -q)

Si de alguna imagen depende un contenedor, tenemos que eliminar los contenedores que dependan de esa imagen y luego borrarla. Otra opción es borrar la imagen usando el párametro -f (forzada).


¿Cómo copiar archivos desde nuestro ordenador a los contenedores?


Hasta este momento hemos ejecutado algunos contenedores pero no hemos colocado contenido dentro. Para hacer esto tenemos que conocer el concepto de "volumes en inglés o volúmenes en español".  Esto nos servirá pará copiar contenido de nuestro ordenador hacia los contenedores, pero también al revés, copiar archivos del contenedor hacia nuestro ordenador. Esta es una propiedad muy interesante como podrás comprobar.

Lo vamos a ver con un ejemplo. Voy a crear una carpeta en ~/website y dentro voy a colocar dos archivo html super simplones:

~/website/index.html

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Saludos desde un Contendor</title>
</head>
<body>
    <h1>¡Hola Mundo!</h1>
</body>
</html>

~/website/about.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>About</title>
</head>
<body>
    <h1>About</h1>
    <pre>Lorem ipsum dolor sit amet consectetur adipisicing elit
Rem, aliquid ullam. Fugiat, a commodi? Nesciunt aspernatur
officia sapiente similique modi ipsam in nisi veniam ducimus.</pre>
</body>
</html>

Ahora yo quiero colocar estos dos archivos en un servidor web. ¿Cómo lo hago? Pues como anteriormente había borrado todas las imágenes, lo primero que voy a hacer es bajarme la imagen de nginx, un servidor de páginas web como ya vimos.

$ docker pull nginx

Como los comandos ya empiezan a tener muchos argumentos vamos a ver como teclearlos de otra forma para que se vean más claros también. Teclearemos:

/home/usuario/website

~/website$ docker run \
-d \
-p 80:80 \
--name website \
-v /home/usuario/website:/usr/share/nginx/html:ro \
nginx

o como estamos dentro del directorio website también le podemos decir:

/home/usuario/website

~/website$ docker run \
-d \
-p 80:80 \
--name website \
-v $(pwd):/usr/share/nginx/html:ro \
nginx

ya que tanto el comando de linux "pwd" o "readlink -e ." nos darán la dirección absoluta de directorio website.

Voy a comentar la instrucción línea a línea. 

La primera línea (docker run) ejecuta el contenedor.

La segunda línea (-d) le decimos a Docker que lo ejecute en segundo plano para que nos deje el terminal libre.

En la tercera línea (-p 80:80) de decimos que ejecute en el puerto 80 de nuestro localhost lo que en el contenedor internamente se está ejecutando en el puerto 80.

En la cuarta (--name website) le ponemos un nombre explicito al contenedor. Lo llamamos website.

En la quinta esta lo interesante. Si vamos a Docker Hub, buscamos nginx y vemos la documentación encontraremos un apartado que pone "hosting some simple static content" donde nos da un ejemplo de donde colocar los archivos. 

Instrucciones para ejecutar una imagen de Nginx

La semántica de la instrucción viene a ser:

-v Directorio de Origen del ordenador:Directorio destino en el contenedor. 


En nuestro caso el directorio de origen es /home/usuario/website y el destino el que nos especifican en las instrucciones /usr/share/nginx/html, sin olvidar los dos puntos de en medio. Esto copiara todos los archivos del directorio que hemos especificado al directorio también especificado en el contenedor. Si te fijas al final de la ruta del directorio de destino hay otros dos puntos y el parámetro ro. (read only). Esto lo que hará será que, aunque modifiquemos los archivos dentro del contenedor, no se refleje en los archivos de nuestro ordenador. El contenedor funcionaria también aunque no pusiésemos :ro, pero lo hará de otra forma como veremos luego.

En la última línea tan solo le decimos la imagen que tiene que ejecutar.

Para ver que la cosa funciona, vamos al navegador y entramos en el localhost:80 donde veremos:

Hola Mundo
y si tecleamos la dirección, "localhost:80\about.html" veremos la segunda página que creamos.

Página about.html mostrada en un navegador

Esto resulta extremadamente útil ya que podemos modificar cualquier archivo html que este en el directorio website de nuestro ordenador y modificarlo on fire o sobre la marcha. Nos sirve para ir escribiendo código de páginas web y solo con refrescar el navegador ver como queda. Por ejemplo, en el archivo index.html voy a añadir un nuevo párrafo (línea 11 del código) que al principio no estaba. Pues solo con guardar el archivo y refrescar el navegador ya se ve la modificación, gracias a que el servidor sigue conectado en el contenedor usando esos archivos compartidos. 

Código de index.html


Vamos a parar el contenedor y eliminarlo para ver también como podemos tener los directorios tanto el de nuestro ordenador como el del contenedor sincronizados.

$ docker stop website && docker rm website

Voy a volver a ejecutarlo todo pero esta vez no le vamos a poner la opción :ro (read only).

/home/usuario/website

~/website$ docker run \
-d \
-p 80:80 \
--name website \
-v $(pwd):/usr/share/nginx/html \
nginx

Lo nuevo viene ahora. Con el contenedor en ejecución le ponemos decir que ejecute en modo interactivo el bash del sistema operativo del contenedor (los contenedores tienen de base un sistema operativo en donde se ejecuta el programa). Lo hacemos con esta instrucción:

/home/usuario/website

~/website$ docker exec -it website bash
root@ae4a5471adfa:/# cd /usr/share/nginx/html
root@ae4a5471adfa:/usr/share/nginx/html# ls
about.html  index.html

Y ya estamos dentro del sistema de archivos del contenedor. Para ver la sincronización nos vamos al directorio vinculado, utilizando el comando cd de linux, /usr/share/nginx/html y si hacemos un ls, voila, ahi están nuestros archivos.

Si ahora creo un archivo nuevo (recuerda que estamos en el contenedor):

root@ae4a5471adfa:/usr/share/nginx/html# > nuevo.html

y voy al directorio website pero de mi ordenador:

Contenido del directorio website


¡Sorpresa! Aquí tenemos también una copia idéntica del mismo archivo. Ahora podemos escribir desde nuestro host al contenedor o desde el contenedor a nuestro host automáticamente.


El archivo DockerFile.

Supongamos que un contenedor que hemos creado, queremos pasárselo a otro desarrollador. (un sitio con paginas html, javascript, imagenes etc). Si lo tuviera que reproducir tendríamos que decirle que imagen hemos utilizado, como lo hemos desplegado, pasarle los archivos etc. Para evitar todo esto docker tiene un archivo especial, el archivo Dockerfile. Este archivo nos permite especificar las instrucciones de como funciona nuestro contenedor (en que imagen esta basado, cual son las instrucciones que queremos que se ejecuten cuando la imagen sea llamada, pasarle los archivos que va a requerir etc) Así solo tendremos que pasarle a nuestro colaborador la imagen sin más.

Vamos ver como se haría para crear una imagen muy sencilla basado en Nginx, con los archivos que hemos usado antes.

Dentro del directorio website que ya tenemos con los archivos que vamos a a pasar creamos un nuevo archivo llamado Dockerfile.

Archivo Dockerfile








La sintaxis que se utiliza en el archivo es sintaxis de Docker. 

En primer lugar usamos la instrucción FROM para especificar en que imagen se basa el proyecto. En nuestro caso utilizamos una imagen nginx, pero ponemos los dos puntos para especificarle (Tag) la versión que queremos en concreto. Si vas a la página de docker hub verás que hay una versión de Nginx que utiliza "Alpine"como base. Linux Alpine es una versión de linux que se caracteriza por usar muuuy pocos recursos sin dejar a la vez de ser bastante segura. Si miras lo que ocupa verás que solamente pesa 22MB.

Una vez que descargue la imagen lo que quiero es que copie los archivos dentro del contenedor.  Entonces lo que voy a decirle al programa es que cree un directorio de trabajo. Si recuerdas ya teníamos un directorio de trabajo donde colocamos los archivos a servir (/usr/share/nginx/html) así que vamos a utilizar ese. (WORKDIR /usr/share/nginx/html). Es decir, le decimos al programa en donde se van a ejecutar las instrucciones que siguen más abajo.

Para acabar COPY [ruta de origen] [ruta de destino en el contenedor], le esta diciendo que como estamos en el directorio website que copie este directorio, que es el actual (de ahí el punto en la instrucción) al directorio actual del contenedor que es el directorio de trabajo (de ahí también el segundo punto).

Ahora vamos a generar la imagen. La siguiente instrucción tiene que ejecutarse en el directorio donde esta el archivo Dockerfile.(ya que utilizo el punto al final del comando). Puedes ampliar la información de este comando aquí.

$ docker build -t [nombre_imagen] . # -t es de tag (para poner nombre y opcionalmente una etiqueta
a la imagen, usando el formato nombre:etiqueta)

$ docker build -t miwebsite
Sending build context to Docker daemon  4.608kB
Step 1/3 : FROM nginx:stable-alpine
 ---> e1ccef1fb908
Step 2/3 : WORKDIR /usr/share/nginx/html
 ---> Running in 7e68879a96aa
Removing intermediate container 7e68879a96aa
 ---> 4a978fca5b17
Step 3/3 : COPY . .
 ---> babf7cf4b20e
Successfully built babf7cf4b20e
Successfully tagged miwebsite:latest

y si compruebas las imágenes instaladas, ahí estará la que hemos creado y ya podemos trabajar con ella como una imagen normal.


Subir una imagen a Docker Hub.


Si quieres que las imágenes que tu crees estén disponibles para otros, tenemos que enviarlas al registro de Docker desde donde serán accesibles para todo el mundo.

Para acceder al registro de Docker tenemos que ir a la siguiente dirección https://hub.docker.com y crearnos una cuenta (que es gratuita). Asegúrate de elegir un nombre de usuario que te guste ya que se utilizará en todas las imágenes que publiques.

Una vez que estés registrado, para acceder a tu cuenta desde la línea de comandos tenemos que iniciar sesión usando el siguiente comando:


$ docker login


Si has seguido las instrucciones al pie de la letra de este capítulo, seguramente tendrás una imagen llamada  miwebsite que está guardada físicamente en el ordenador (y si no tendrás una con tu propio nombre para la imagen que es la que tienes que utilizar). Para poder subir esta imagen al archivo de Docker es necesario cambiarle el nombre para que incluya nuestra cuenta de usuario. Esto se hace con el comando de Docker, tag:

$ docker tag miwebsite:latest <your-docker-registry-account>/miwebsite:latest

Ahora si lo compruebas verás que tienes una imagen nueva, pero con tu nombre de usuario delante (lo cual es obligatorio para subirla a Docker Hug). Otra forma de hacer esto es, cuando creamos originalmente la imagen (docker build) llamarla directamente usando esta sintaxis:

 [nombre_usuario_en_docker]/nombre_imagen:[etiqueta]

Ahora que la imagen ya esta preparada, la subimos con el siguiente comando:

$ docker push <nombre_usuario_en_docker>/miwebsite:[etiqueta]

Ahora la imagen esta disponible públicamente y podemos documentar como instalarla y ejecutarla desde el registro de Docker  de la misma manera que lo hace la imagen de Mariadb y otras muchas. 


Crear una imagen a partir de un contenedor.

También puede ocurrir que tengamos un contenedor creado, con su sistema operativo, sus programas instalados, sus archivos, bibliotecas etc y queramos hacer con ello directamente una imagen. (Un snapshot o copia inmediata de nuestro contenedor en una imagen).

El contenedor a utilizar puede estar parado o en funcionamiento y para crear la imagen usamos la siguiente instrucción:

$ docker commit ID_Nombre_Contenedor Nombre_Nueva_Imagen


Otra forma más avanzada de crear imágenes a partir de contenedores es la siguiente. Vamos a verlo creando un contenedor en el que haya un servidor de páginas web utilizando "Apache" en vez de Nginx un poco por encima para hacernos una idea. La finalidad del contenedor es que cuando se ejecute el inicie se ejecute Apache. 

Empezamos descargando y ejecutando una imagen de ubuntu, por ejemplo:

$ docker pull ubuntu

y la ejecutamos en modo iteractivo para poder acceder al bash.

$ docker run -it ubuntu bash

y como siempre que hacemos algo en Linux actualizamos los paquetes a sus últimas versiones.

root@50e045bf2067:/# apt-get update && apt-get upgrade

e instalamos el paquete de apache en el contenedor:

root@50e045bf2067:/# apt-get install apache2

Aunque lo tenemos instalado eso no quiere decir que el servicio se este ejecutando. Lo podemos comprobar viendo como esta el servicio:

root@50e045bf2067:/# service apache2 status
 * apache2 is not running
Con lo que tenemos que ponerlo en marcha.

root@50e045bf2067:/# service apache2  start

Ahora abrimos otro terminal para dejar el contenedor funcionando en el otro y vamos a ejecutar un commit. Pero esta vez será un poco más complejo. Vamos a tener que cambiar la finalidad del contendor, que al estar basado en una imagen de Ubuntu era la de ejecutar el shell cuando se inicia. Como nosotros no queremos eso, sino que se inicie apache tenemos que utilizar el argumento "--change" para cambiar la finalidad de la imagen:

root@50e045bf2067:/# docker commit --change='CMD ["apache2ctl", "-DFOREGROUND"]' \
-c "EXPOSE 80" \
nombre_contenedor \
nombre_nueva_imagen


La instrucción CMD nos sirve para ejecutar un comando por defecto al iniciar el contenedor y así sobrescribir el que ya tenía. Luego entre corchetes escribimos la orden a ejecutar, el primer elemento será la orden en si y los siguientes los argumentos. En este caso le indicamos que inicie el servicio de apache2 con el argumento -DFOREGROUND" que aunque no es exactamente así, pero para entendernos que lo haga en segundo plano. Con -c "EXPOSE 80" le indicamos que escuche las conexiones que se realicen en el puerto 80.

Ahora ya tenemos una nueva imagen y si la ejecutamos:

$ docker run -d -p 80:80 apache2

y vamos a nuestro local host, vemos como funciona.

Página de inicio de Apache


En el próximo capítulo veremos como instalar varios contenedores y que se comuniquen entre ellos.


Fuente: Fatz y Elaboración Propia.

No hay comentarios:

Publicar un comentario