Todas las entradas de Guillermo

Plataformas y tecnologías cloud

Hoy en día existen muchas soluciones para alojar servicios o sistemas remotos aunque una de las más conocidas es la “nube”. En esta entrada veremos los distintos tipos de arquitecturas remotas (on-cloud).

Antes de centrarnos en las soluciones y tecnologías Cloud o remotas deberíamos de explicar tres tipos de arquitecturas posibles:

  • Implementación Local (privada) — > Se basa en mantener toda la infraestructura física en una o varias localizaciones propias de una empresa u organización y que en ocasiones se puede gestionar como una “nube privada”. Emplean soluciones virtualizadas aunque se asemeja a la infraestructura de TI antigua.
  • Implementación Híbrida — > Es una solución que se basa en conectar la infraestructura local o privada de una empresa con la nube. Es una solución óptima para accesos a información rápida localmente y desde cualquier parte del mundo gracias a la nube pública. Además, se pueden realizar copias de seguridad on-cloud. Su gran desventaja es la duplicación de servicios, su sincronismo y el consumo de recursos económicos, de equipos y de personal.
  • Implementación en la nube — > Toda la infraestructura TI se encuentra en la nube que o bien se creó originalmente allí o se transfirió desde una solución local.

SaaS, PaaS y IaaS

Existen muchos conceptos difíciles de entender y asociados con “la nube”. Algunos son de difícil clasificación puesto que no se sabe bien cuando entran en una categoría o en otra.

Los servicios “on-cloud” pueden ser:

  • Software-as-a-Service (SaaS) — > Este servicio comprende los que proporcionan una funcionalidad directa sin que el que lo despliega (o desarrolla) tenga conocimiento expreso de la plataforma o infraestructura sobre la que subyace. Ejemplos de SaaS pueden ser Gmail, Dropbox, Alojamiento web, etc. Aquí el usuario se ocupa solo del empleo de la herramienta (aunque sea con permisos de administrador como en una web o blog).
  • Platform-as-a-Service (PaaS) — > En este caso los desarrolladores pueden interaccionar no solo con la funcionalidad o el servicio sino con la estructura que “soporta” ese servicio. Sin embargo el auto escalado, o las propias redes informáticas son autogestionadas por la plataforma sin que el desarrollador pueda configurarlas. Si puede, sin embargo, configurar espacio, memoria RAM, discos duros, etc. Son unos servicios óptimos para el desarrollo de aplicaciones eficientes con una gestión parcial de los recursos asociados en las plataformas. Ejemplos de esto pueden ser Google App Engine que permite desarrollar soluciones en Java o Python.
  • Infraestructure-as-a-Service (IaaS) — > En este caso el desarrollador se ocupa no solo de los servicios y su desarrollo sino de su infraestructura (máquinas virtuales, auto escalado, redes, elección de hardware, sistemas operativos, etc.): ejemplo de esto puede ser AWS, Azure, Google Cloud, vCloud (VMware), etc.

En la siguiente tabla tenemos una comparación entre las más conocidas plataformas.

Antes de seleccionar un proveedor para trabajar con el deberíamos tener en cuenta unos conceptos asociados a los servicios on-cloud y que nos permiten poder contratar un servicio con unos requerimientos especifico o firmar una SLA (Service Level Agreement) con un proveedor especifico. Los mas importantes que debemos saber son:

  • RTO — > Recovery Time Objective. Se refiere al tiempo que tardamos en recuperar esos datos al estado que estaban antes del “incidente”.
  • RPO — > Recovery Point Objective. Se refiere a la cantidad de datos que podemos recuperar. Es decir, desde cuando podemos recuperar los datos. Por ejemplo, si tenemos un backup de las últimas 4 semanas nuestro RTO será de 4 semanas.
  • IOPS — > Inputs Outputs Per Second. Se refiere al rendimiento de los discos duros o sistemas . Así IOPS nos marca la velocidad (mínima) que puede un disco duro leer datos y escribirlos .

Construir una imagen docker (build)

Para construir una imagen podemos emplear un archivo llamado «Dockerfile». Antes de continuar me gustaría establecer las diferencias entre «Dockerfile» y «Docker compose»

  • Dockerfile — > Lo empleamos para construir una imagen docker.
  • Docker compose — > Archivo empleado para «lanzar» varios contenedores con unas características determinadas. Ademas, este Docker compose puede utilizar un Dockerfile (build).

Después de aclarar este concepto (que a veces no se explica adecuadamente) hablaremos de construir (build) una imagen.

Un “dockerfile” es un archivo de texto donde se establecen una serie de comandos (similares a pseudo Linux) que hace que el cliente docker (el docker que corre en nuestra máquina) los ejecute para crear una imagen. Obviamente las imágenes se basan en otras imágenes (como ya se ha explicado) como puede ser una imagen de Python, Ubuntu (como en nuestro ejemplo) o cualquier otra. Estas imágenes “base” tienen las funcionalidades necesarias para luego “añadirle” otras en nuestra imagen “customizada” gracias a «Dockerfile». El proceso desde el «Dockerfile» hasta la ejecución de un contenedor es el de la siguiente imagen.

Vamos a crear un “dockerfile” muy simple con base una maquina Ubuntu 16.04 a la que añadiremos algunas “especias” a nuestro gusto. El archivo quedaría así:

#Mi primer Dockerfile
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y apache2
RUN echo «Bienvenido Maxtor!» > /var/www/html/index.html
EXPOSE 80

La explicación del código es casi obvia, creamos una imagen de Ubuntu 16.04 al que le forzamos a actualizarse, que luego se instale el servidor apache (“-y” significa que a todas las preguntas responda con un «yes»), que en la página web de un mensaje de bienvenida (siempre recogida en index.html) y que estará disponible (por defecto) en el puerto 80 del futuro contenedor qe lancemos de esa imagen.

Así, para construir nuestra imagen (obviamente la maquina de Ubuntu se obtiene de Docker Hub) ejecutamos el siguiente comando docker build -t ubuntutest .

Hay que tener claro que “-t” se refiere a “tag” (para poner el nombre a la imagen) y que el “.” final es necesario para decirle que busque el “Dockerfile” para crear la imagen en el propio directorio donde estamos. En las siguientes imágenes vemos como se ha creado la imagen siguiendo los pasos descritos.

Ahora lanzaremos un contenedor de nuestra imagen creada con la instrucción docker run -d –rm -p 8080:80 ubuntutest /usr/sbin/apache2ctl -D FOREGROUND. En este caso hemos lanzado el contenedor forzando a que arranque el servidor apache en background, ya que un docker necesita ejecutar en primer plano el proceso principal “Ubuntu” y en background el resto, SINO NO FUNCIONARA!.

La siguientes imágenes muestran los pasos seguidos.

Finalmente, alguien podria preguntarse por qué no hemos establecido en el «Dockerfile» que cuando se cree la imagen se lance en «background» el servicio apache. Pues bien, esto si se puede realizar añadidenod al final de nuestro archivo «Dockerfile» CMD [«apachectl», «-D», «FOREGROUND»].

La instrucción «CMD» tiene la siguiente sintaxis CMD [«executable»,»param1″,»param2″]. EN nuestro caso que ejecute apache en modo «detached» y «foreground». No obstante a lo anterior, si en un Dockerfile establecemos (por ejemplo) CMD [«apachectl», «-D», «FOREGROUND»] tendremos las siguientes características:

  • Para lanzar un contenedor de la imagen creada solo necesitaríamos la instrucción docker run -d –rm -p 8080:80 ubuntutest
  • Siempre que lancemos un contenedor usando esa imagen tendremos el servidor apache ejecutándose en segundo plano en ese contenedor e igual NO nos interesa ese comportamiento y preferiríamos levantarlo explícitamente en la sentencia «run» (en el caso de que no queramos). Esto es algo a valorar a la hora de crear la imagen.


Acceso al contenedor Docker

Si queremos acceder a un contenedor docker podemos verlo desde dos perspectivas:

  • Acceso al contenedor en modo consola.
  • Acceso a los servicios externos que nos proporciona dicho contenedor. Aquí vemos el concepto de mapeo de puertos.

Acceso al contenedor en modo CLI

Podemos acceder a un contenedor a través de la propia consola CLI desde nuestro host anfitrión. Para ello necesitamos poder distinguir el contenedor al que queremos acceder y ejecutar docker exec -it CONTAINER_ID/NAMES /bin/bash (o /bin/sh según sea soportado o no por el sistema operativo del contenedor que lanzamos). A continuación vemos un acceso a un contenedor en el que muestra el usuario a través del que hemos accedido. En este caso hemos accedido a un contenedor alpine que es una version Linux que ocupa menos de 6 MB.

Por defecto, cuando accedemos a un contenedor accedemos como «root» y si tenemos acceso a los recursos externos a ese contenedor (por que explícitamente compartamos una carpeta con el host anfitrión, por ejemplo) podríamos realizar cambios permanentes en el propio host anfitrión. La manera de evitar este tipo de «vulnerabilidad» del sistema se tratara en otro capitulo mas adelante.

Con este tipo de acceso podemos instalar y actualizar nuestro contenedor como si estuviésemos en una MV.

Acceso a servicios que proporciona un contenedor Docker

Los servicios a los que se pueden acceder y que se ejecutan en un contenedor son su principal funcionalidad. No obstante, un contenedor (por defecto) está conectado de manera interna a nuestro host anfitrión y utiliza su interface de red tanto para su trafico de salida como un acceso de entrada a él.

Para realizar esto se debe «mapear» los puertos que están abiertos por los servicios que se ejecutan en el contenedor con los que queremos que estén disponibles para el acceso externo a la IP o URL que identifica nuestro host.

Para mapear los puertos debemos de utilizar la sintaxis -p PUERTO_HOST:PUERTO_CONTENEDOR y lo podemos hacer tantas veces o servicios como deseemos.

En la siguiente imagen lanzaremos una imagen de nginx de solo 126 MB en el que se ha instalado OpenSSH a parte del servicio web que tiene en el. Mapearemos los puertos 80 y 22 del contenedor con los 8081 y 222 del host anfitrión y veremos el resultado del acceso a dichos puertos/servicios del host anfitrión.


Lanzar y detener un contenedor Docker

A la hora de lanzar un contenedor en Docker podemos encontrarnos varios escenarios:

  • Que ya dispongamos de la imagen en el host (equipo) desde el que hacemos la llamada de ejecución (lanzamiento) del contenedor y que hemos descargado previamente de Docker Hub u otro repositorio con la sentencia docker pull XXXX/XXXX. recordad que con docker images ls podemos comprobar las imágenes de las que disponemos en nuestro equipo anfitrión.
  • Que no dispongamos de la imagen que queremos utilizar con lo que «docker» comprobará que no disponemos de dicha imagen y a continuación la buscara en el repositorio Docker Hub. Obviamente deberíamos de haber pasado una imagen existente en dicho repositorio.
  • Que dispongamos de la imagen en nuestro equipo anfitrión ya que la hemos construido nosotros mismos (Dockerfile). A este respecto debemos de puntualizar que habitualmente nos basamos en imágenes base para , a continuación, construir la nuestra ad-hoc.

Vamos ha ver un ejemplo de ejecutar (o lanzar) un contenedor en los tres casos explicados.

Antes de realizar esto debemos explicar algunos parámetros en la sentencia de lanzamiento de nuestro contenedor. La lista de parámetros que podemos añadir al comando docker run es muy extensa, es por ello que aquí explicaremos los mas usuales o útiles para lanzar un contenedor.

La sintaxis de ejecución de un contenedor es: docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG…]

  • -d — > detached (se ejecuta el contenedor en background). Esta opción suele ser la mas cómoda y habitual para seguir trabajando en la consola de nuestro host. En caso de no incluirse se ejecuta en «foreground» y la salida de nuestra consola quedará unida a la ejecución del contenedor.
  • –name — > Es una de las formas en la que vamos a identificar a un contenedor (la otra es su UUID long identifier y su UUID short identifier). Si no especificamos un nombre docker le asignar.a uno aleatoriamente. Yo recomiendo siempre ponerle un nombre característico.
  • -it — > Mantiene abierta la entrada de datos al contenedor y una manera de acceder a el (consola tty). También recomiendo su uso. habitualmente el parámetro «t» va asociado al tipo de consola que quiero acceder en el contenedor (lo mas empleado es acceder a /bin/bash).
  • –rm — > Sirve para eliminar dicho contenedor una vez lo detengamos. Los contenedores persisten una vez creados para que puedan volver a ser lanzados. Teniendo la imagen, a veces, no tiene sentido que estos contenedores persistan una vez detenidos (tener en cuenta que un contenedor nuevo no puede ser lanzado con el mismo nombre de uno que ya exista aunque se encuentre este ultimo detenido). También lo considero una buena opción.
  • –cap-add=XXXX — > Por defecto los contenedores se ejecutan en modo sin privilegios de servicios de la maquina host. Para poder ejecutar dentro de un docker esos servicios (que serian independientes de lo que se ejecutan fuera del contenedor) se podrían añadir o eliminar «capabilities». Por ejemplo, no podríamos usar Netfilter (IPTables) dentro de un contenedor sino añadimos –cap-add=NET_ADMIN.
  • -p — > sirve para «mapear» y exponer puertos del host anfitrión con respecto al contenedor. Por ejemplo, -p 8080:80 significa que con la IP del host anfitrión en el puerto 8080 habremos unido ese socket (IP + Puerto) al socket del contenedor (es decir a su puerto 80).

Las anteriores son las mas comunes como hemos dicho, pero existen otras importantes como son las relacionadas con redes, mapeo de volúmenes, limitar los consumos de CPU o RAM, etc. MAs adelante se irán explicando muchas de ellas.

Como habíamos comentado vamos a lanzar el contenedor (luego detenerlos y borrarlos) en los dos primeros escenarios comentados (dejamos para otro capitulo el «Dockerfile»).

Lanzar un contenedor del que ya disponemos en nuestro host anfitrión

  1. Comprobamos las imágenes que tenemos con docker images
  2. Lo llamamos por su nombre canónico (REPOSITORY) aquí hay que añadir la etiqueta (TAG), podríamos haberlo llamado por su IMAGE ID.
Lanzamiento de un contenedor desde una imagen que ya existe

Como vemos en la imagen el comando le indica que transforme el puerto interno del contenedor 80 al externo del host 8080 y le damos capacidades de NET_ADMIN. Ademas, le hemos asignado un nombre, que arranche en modo detached y que se elimine cuando se detenga.

En las siguiente imagen vemos como podemos acceder al servicio web en el puerto 8080.

Descargar una imagen en nuestro host anfitrión y luego lanzar un contenedor de esa imagen

  1. Primeramente obtenemos la imagen con la sentencia docker pull ghost. Esta imagen NO existe en nuestro host por lo que docker se dirige a Docker Hub y busca esa imagen, como no hemos especificado su etiqueta busca la ultima (latest).
  2. A continuación, comprobamos que tenemos ya esa imagen descargada en nuestro host local.
  3. Por ultimo lanzamos nuestro contenedor, ene este caso le especificamos como acceder a el (opción -e) aunque realmente esta opción seria para establecer una URL o IP especifica ya que NO seria necesaria en nuestro ejemplo ya que hemos mapeado el puerto correctamente (-p 3001:2368).
Comprobación del servicio del contenedor después de descargar la imagen y lanzar el contenedor

Detener y eliminar un contenedor

Para detener y eliminar un contenedor docker depende de como lo hayamos arrancado. Si lo hemos arrancado con la opción –rm, una vez lo detengamos se eliminará automáticamente, sino deberíamos de eliminarlo través de su nombre canónico o mejor de su CONTAINER ID.

En la siguiente imagen vemos tanto los contenedores que están en ejecución como los ya detenidos (la opción -a me permite ver todos).

Primero detendremos el segundo contenedor que se eliminara automáticamente (recordad la opción –rm) y luego eliminaremos el primero y luego lo eliminaremos específicamente con su CONTAINER ID. LA siguiente imagen muestra todos estos pasos.

Asi pues:

  • Detener un contenedor — > docker container stop CONTAINER_ID
  • Eliminar un contenedor — > docker rm CONTAINER_ID
  • Ver contenedores en ejecución — > docker ps
  • Ver TODOS los contenedores — > docker ps -a

Usuario docker, ver, parar y eliminar elementos de Dockers

Como hemos visto en el capítulo anterior es necesario emplear el comando «sudo» para ejecutar los comandos docker. Esto puede ser bastante engorroso la seguridad queda en entredicho si se usan las capacidades de un usuario con muchos privilegios solo para le gestión de Dockers.

Para solucionar lo anterior es util crear un usuario especifico que no tenga privilegios de root pero sin embargo si avanzados para comandos docker. Para realizar esto hay que crear un usuario especifico e introducirlo al grupo «docker». Una forma de hacerlo es la siguiente:

  • Añadimos un usuario con el comando: sudo adduser dockeruser.
  • Añadimos nuestro usuario (dockeruser) al grupo docker con: sudo usermod -aG docker dockeruser.

La ejecución de ambos pasos se puede ver en al imagen siguiente.

  • Cambiamos a nuestro usuario con «privilegios docker» con: su dockeruser.
  • Comprobamos que estamos en el grupo docker con: id -nG.

Comprobamos que con este usuario NO necesitamos anteponer sudo a la ejecución de comandos docker como se puede ver en la siguiente imagen.

Comandos útiles para manejar y gestionar elementos en dockers

Existen multitud de comandos que podemos consultar con docker COMMAND –help. No obstante, a continuación mostraré los comandos básicos de consulta mas necesarios.

  • Mostrar las imagenes docker — > docker image ls o docker images
  • Mostrar los contenedores docker en ejecución — > docker container ls o docker ps
  • Mostrar todos los contenedores docker — > docker ls -a
  • Arrancar un contenedor — > docker container start num_contenedor o nombre_contenedor
  • Detener un contenedor — > docker container stop num_contenedor o nombre_contenedor
  • Eliminar un contenedor (debe estar parado antes) –> docker rm num_contenedor o nombre_contenedor
  • Eliminar una imagen (no debe de haber un contenedor asociado a dicha imagen) — > docker rmi num_imagen o nombre_imagen

A continuación podemos ver algunos comandos utilizados.


Instalación de Docker CE

Antes de instalar Dockers nos vienen a la cabeza varias preguntas:

  • ¿Porque Dockers y no otros?
  • ¿Que sistema operativo (SO) del host anfitrión es mejor utilizar?
  • Que versiones existen de Dockers?

Realmente la parición de Docker (2013) surgió como necesidad de ejecutar contenedores Linux (LXC) y ya en 2014 apareció su mayor competidor CoreOS rkt.

Aunque la priemras versiones de Docker adolecian de problemas de seguridad sovnetados por CoreOS: Además, CoreOS era interoperable que varias plataformas como RedHat, CentOS, etc.

Hoy en día Docker a tomado una gran ventaja y es totalmente inoperable, ha mejorado sus ejecuciones como «root» dentro del contenedor (de estos e tratará en otra entrada) y, sobre todo, ha sabido expandirse por los grandes mercado con lo que su cuota de uso, aceptación y oferta de imágenes es inmensa.

Así pues parece que la ventaja es de Docker sobre CoreOS y por ello hemos seleccionado dicho producto. No obstante, se realizará una entrada sobre CoreOS mas adelante.

En cuanto al sistema operativo a utilizar la respuesta es obvia. Uno basado en GNU/Linux debido a que la inmensa mayoría de las imágenes son solucione gratuitas y que en el caso de sistemas propietarios como Windows o iOS no están tan desarrollados. No obstante el mundo Linux es mas de «consola o CLI» que los mencionados anteriormente.

Por último nos queda elegir la versión de Docker a elegir. Actualmente docker dispone de la version EE (Enterprise Edition) que es de pago y CE (Community Edition). Dentro de esta ultima existen versiones estables y de pruebas .

La ventaja de Docker EE es que nos proporcionan una interface web de gestión (Docker Universal Control Plane -UCP-), alta disponibilidad (HA), contenido certificado, manejo privado de imágenes y soporte técnico.

Imagen del panel de UCP

Con Docker CE podemos obtener casi todo lo anterior excepto (lógicamente) el tema del soporte técnico. Eso si, la instalación y configuración es lago mas compleja. Con el comando docker version veremos lo que tenemos en nuestro host anfitrión instalado.

Por último, Docker también ofrece otro producto llamado “Docker Machine” que nos ayuda, a través de scripts, a facilitar el proceso de tener servidores distribuidos con el producto Docker. 

Instalación de Docker CE

Despues de todas las explicaciones nateriores vamos a realizar una instalacion de Docker CE sobre Ubuntu 18.04. para ello seguiremos los siguientes pasos:

  • Muy importante actualizar nuestra lista de repositorios con: sudo apt update
  • Luego realizaremos una istalacion de paquetes relacionados con https. Aqui queria comentar que la comunicacion de nuestro Docker engine (una vez que lo tengamos instalado) con Docker Hub es a traves del puerto 443 (protocolo https) de ahí que necesitemos asegurarnos de disponer de los paquetes de https necesarios con: sudo apt install apt-transport-https ca-certificates curl software-properties-common
  • El siguiente paso es obtener la «llave» valida para el Docker Hub y la guardaremos en nuestro host anfitrión con: curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –
  • Ahora añadiremos el repositorio necesario para instalar Docker CE para nuestro sistema operativo (en este caso Ubuntu 18.04) con: sudo add-apt-repository «deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable»
  • Volvemos a actualizar nuestra lista de repositorios con: sudo apt update
  • Ahora ya podemos instalar Docker CE con: sudo apt install docker-ce
  • Para comprobar como esta la version Docker instalada podemos velo con: sudo systemctl status docker y el resultado obtenido es el de la siguiente imagen.
Salida del comando sudo systemctl status docker

Para comprobar que todo ha ido bien deberíamos descargarnos nuestra primera imagen y ejecutarla con sudo docker run hello-world. EN la siguiente imagen vemos el resultado de la ejecución de dicho comando.

Ejecución de mi primer contenedor «hello-world»


Los repositorios

Habitualmente un repositorio es un area de almacenamiento (o espacio) en «la nube» que nos facilita el desarrollo y despliegue de sistemas y aplicaciones. Lo bueno de este tipo de soluciones es que muchas de ellas son gratuitas e incluso podemos privatizarlas.

Existen multitud de repositorios pero los mas conocidos en nuestro ámbito son GitHub, Docker Hub y Bitbucket.

Desarrollo basado en repositorios

Como vemos en la imagen anterior estos repositorios son muy utilizados en el desarrollo y esto está muy relacionado con DevOps y con Dockers. Con respecto a ello aquí hablaremos de Docker Hub.

Docker Hub es un servicio/repositorio (SaaS) en el que se pueden compartir imágenes y que nos proporciona lo siguiente:

  • Repositorio — > Para subir o bajar (Push vs Pull) imágenes de contenedores. Tanto públicas como privadas, verificadas (oficiales) por docker o no.
  • Imágenes certificadas –> proporcionadas (vendidas) por vendedores externos.
  • Build — > Creación de imágenes (build) automáticamente desde Github y Bitbucket y subirlas a Docker Hub.
  • Webhooks — > Ejecuta acciones (Triggers) después de una correcta subida para integrar Docker Hub con otros servicios. Como sabemos un API Es una interfaz de servicio realtime que permite a los desarrolladores y aplicaciones de terceros tomar data de un determinado sitio en internet.  Una API es una excelente forma de comunicar datos entre aplicaciones separadas. Pero ¿qué ocurre cuando en entornos realtime es necesario notificar cambios en determinado evento o elemento en otra aplicación? En este caso, una API sería extremadamente ineficiente. En su lugar, necesitas utilizar un Webhook. Un Webhook es un mecanismo que permite ser notificado cuando un evento ha ocurrido en tu aplicación o la de un tercero. Es básicamente una solicitud POST que se envía a una URL específica. Esa URL está configurada para recibir el cuerpo de la solicitud POST y procesarla de alguna manera. Por tanto, se podría decir que un API se utiliza para hacer preguntas directas y síncronas y un Webhook se utiliza para notificar cuando se producen ciertos eventos. En lugar de preguntar constantemente si algo ha cambiado, un webhook puede activarse y notificarnos automáticamente apenas se produzca el evento.

No obstante, podemos montar nuestro propio registro Docker (particular). Habitualmente se aconseja darse de alta en Docker Hub y acceder a través de Linux con “Docker login” (una vez instalado Docker CE o Enterprise) o descargar Docker Desktop para MAC/Windows y pode acceder a Docker Hub.

Para realizar esto (se explicará en otra entrada cómo subir y como descargar una imagen en este documento) es aconsejable seguir los pasos marcados en https://docs.docker.com/docker-hub/ y en https://docs.docker.com/docker-hub/repos/

Queremos dejar claro que una imagen “privada” solo se puede descargar (Pull) teniendo el usuario y la contraseña para ello (de la cuenta) y, además, esta imagen NO aparece en la búsqueda (Docker search). Docker Hub permite gratuitamente una imagen “privada”, para tener más debe de pagarse o tener el resto de manera “publica”.


Diferencias entre imagen, contenedor e instancia

Un docker es una aplicación en una sandbox (llamada container). No es lo mismo “imagen” que “docker” (contenedor). Aunque un contenedor es solo un servicio “enjaulado” y una instancia (estrictamente hablando) podría estar compuesta de uno o varios contenedores, la verdad es que a veces se utilizan ambos conceptos como sinónimos. En este texto emplearemos contenedor e instancia como sinónimos, pero siempre tendremos en cuenta la siguiente relación:

1 instancia = 1 o más contenedores

Por otro lado, un contenedor o instancia es una ejecución de una imagen.

El docker o contenedor (como ya se ha dicho) está aislado del sistema operativo y tiene “lo justo” (aplicación + dependencias) para poder ser ejecutado con el consumo de muy pocos recursos. Sus grandes ventajas: Portabilidad, descarga anywhere (Repos), ejecución asegurada, pocos consumos de recursos, seguridad más alta, gestión y auto escalado implementado (etc.).

Existen dos tipos de imágenes:

  • Imágenes BASE — > no son heredadas de ningún “padre” y suelen ser sistemas operativos como Ubuntu, Debian o Python.
  • Imágenes Hijo — > son imágenes base a las que se les ha añadido una funcionalidad.

Aparte de esta clasificación se podrían clasificar en “imágenes oficiales” (suelen tener un nombre largo sin barras) e “imágenes de usuarios” (suelen tener una dependencia como usuario/nombre_imagen).

Por último, existen imágenes “onbuild” (como la que se usa en este ejemplo) que utilizan un archivo llamado “requirements.txt” para que se auto creen.

Existen un montón de imágenes disponibles en docker_hub, con el comando docker search podemos buscarlas.


Ventajas entre Dockers y MVs

En primer lugar quiero exponer aquí los problemas asociados a las máquinas virtuales (MVs) que quedan resueltos con los contenedores (Dockers). Las desventajas de las MVs con respecto a los contenedores (Dockers) se pueden resumir en 6 principalmente (existen muchas más):

  • «Inicialmente» la inseguridad de un servicio compromete la máquina entera y su sistema operativo (máquina física o virtual).
  • Existe un gran malgasto de recursos ya que se tienen tantos S.Os como máquinas virtuales.
  • La gestión de servicios depende de aplicaciones específicas para ello y no suelen ser triviales.
  • Las recuperaciones, backups e imágenes son muy pesados (decenas de Gb).
  • Su arranque, parada y auto escalado es muy lento y limitado (dependen en gran medida del S.O).
  • No son los modelos óptimos para servicios de altas capacidades o/y on-cloud.

Parece que la solución a todo esto es la compatibilidad de las máquinas virtuales (MVs)/maquinas físicas o en local, híbridas u on-cloud con los contenedores (dockers).

Anteriormente hemos hablado de «Inicialmente» la inseguridad de un servicio (en una MV) compromete la máquina entera y su sistema operativo. Sin embargo, existe una característica que le confiere una mayor seguridad a las MVs que a los contenedores (Dockers). En el caso de las MVs, cada una de ellas tiene una estructura completa y lo que afecta a esta se queda en ella sin afectar al resto. Esto NO ocurre con Dockers ya que existe una capa común sobre la que se ejecutan todos los contenedores y si la seguridad no se establece adecuadamente un contenedor podría afectar a la capa «Docker Engine» y, por lo tanto, afectar al resto de contenedores.

Una de las grandes apuestas por los servicios on-cloud o las estructuras hibridas es la capacidad de balanceo de carga (Load Balancer). La arquitectura de balanceo de carga permite distribuir las peticiones de servicio o procesamiento entre distintas arquitecturas, ya sean contenedores, máquinas virtuales, servicios on-cloud o servidores dedicados.

Esta característica también se manifiesta en la arquitectura de los microservicios (como se vio anteriormente) y se basa en la estrategia de distribuir la carga de trabajo en varias entidades para que la latencia de respuesta sea menor y que la caída de uno solo de estos servicios no comprometa al resto.

En la siguiente imagen podemos ver como podemos balancear la carga de demanda de los servicios de varios clientes (los cuales dirigen sus peticiones a un solo punto) entre varios sistemas.

Diagrama de un balanceador de carga (LB)

Esto es posible realizarlo con varias soluciones de servicios on-cloud o las soluciones que proporciona Docker (Swarm) o Kubernetes (entre otras).

Además de los anterior, el poder contar con servicios de LB (Load Balancer) on-cloud que nos asegura la utilización de esta característica en cualquier lugar del planeta.


¿Qué es un contenedor y para qué se utiliza?

Podríamos llamar contenedor a una Máquina Virtual (MV a partir de ahora) ligera. Obviamente esto es una definición inexacta y realmente sería más acertado decir que es un “paquete” (contenedor) donde existe un servicio y las librerías asociadas para que funcione sin un S.O subyacente.

No está claro si la necesidad hizo virtud o al revés. Es decir, si nacieron los dockers como respuesta a dos grandes necesidades (microservicios y DevOps) o al revés. El caso es que durante los últimos años existía una necesidad debido al incipiente desarrollo a través de microservicios y del desarrollo tipo DevOps.

Microservicios — > Un esquema de microservicios podríamos decir que no se basa en la arquitectura monolítica con la que se estuvo desarrollando durante la primera década del 2000. En él se establece una API (Application programming interfaz) para comunicarse con varios servicios, pero sin tener un único “paquete” en el que todo está integrado. Esto ayudo a que cada vez que se cambiase algún modulo no debía de redistribuirse todo el paquete completo. La comparación con el sistema monolito para una aplicación web se observa en la siguiente imagen.

Arquitectura monolítica vs arquitectura de microservicios

DevOps — > Es un concepto de desarrollo de operaciones en un ciclo de ejecución-desarrollo continuo, en el que la integración entre desarrolladores y los administradores de “ese producto en desarrollo” es crucial. Este modelo de desarrollo permite seguir desarrollando servicios sin parar los que ya se están ejecutando y diseñar las pruebas que debe cumplir el software incluso antes de desarrollarse este.

Gracias a Docker estas dos filosofías de desarrollo (Devops y Microservicios) alcanzaron un potencial enorme, tanto que no se entiende una PaaS o una IaaS sin el uso de dockers o contenedores.

Pero si un contenedor no dispone de su propio S.O, ¿Cómo accede el contenedor a los recursos de la maquina física o virtual en la que se ejecuta? Ahí es donde entra nuestro amigo Docker Engine.

Docker CE (Docker engine Community Edition) es la plataforma Open Source que instalamos en una máquina virtual y/o física que nos permite comunicarnos con el sistema subyacente (SO + Hardware) y sobre la que “montamos” nuestros contenedores. Una de las desventajas que nos encontramos (por ahora) es que S.O de Windows pueden correr contenedores de Windows, pero no pueden correr servicios Linux. Esto se está solucionando actualmente.

Comparativa entre los sistemas virtuales (MVs) y contenedores