miércoles, 6 de junio de 2018

Conceptos avanzados de imágenes Docker - Parte I

Hola de nuevo, en el post anterior creamos nuestra primera imagen básica a partir de la cual queremos construir imágenes más complejas para nuestros servicios. En este post extenderemos nuestra imagen básica y exploraremos algunas de las opciones disponibles y relacionadas con la publicación de imágenes.

Para empezar recordemos que habíamos creado nuestra imagen básica y ahora, lo interesante es tenerla disponible para poder utilizarla en nuestra infraestructura. Para esto podemos crear nuestro propio registro interno de imágenes o usar una cuenta en Docker Hub, con lo que nuestra imagen estará disponible siempre desde cualquier punto con un acceso a internet.

Podéis crear una cuenta directamente en Docker Hub de forma gratuita con solo acceder aquí, con lo que tendréis disponible vuestro propio repositorio de imágenes. Una vez creada la cuenta, nuestro repositorio estará vacio y disponible como podemos ver en la siguiente imagen:

Nuestro repositorio en Docker Hub.

Una vez creada nuestra cuenta y desde la línea de comandos, podemos subir nuestra imagen a Docker Hub del siguiente modo:

Subiendo una imagen base a Docker Hub.
En resumen el proceso consiste en los siguientes pasos:
  1. Iniciamos sesión de Docker Hub con nuestro dockerid. El comando docker login nos pedirá nuestro dockerid y la contraseña.
  2. Una vez iniciada la sesión creamos una nueva etiqueta o tag usando el comando docker tag. Al hacerlo debemos especificar nuestro dockerid en el nombre de la nueva etiqueta.
  3. Usamos el comando docker push el cual, usando el dockerid incluido en el tag de la imagen, la subirá a nuestro repositorio.

Una vez subida, nuestro respositorio en Docker Hub nos mostrará la nueva imagen ya disponible:

Nuestra imagen disponible en Docker Hub.

Si el repositorio es público, cualquiera podrá utilizar nuestra imagen para generar nuevos contenedores o crear nuevas imágenes a partir de ella.

Ahora que tenemos nuestra imagen base disponible, vamos a crear una nueva imagen a partir de esta. Supongamos que nuestro servicio necesita un servidor de directorio, como por ejemplo un servidor OpenLDAP, para proporcionar servicios de configuración o de validación de usuarios. En este caso, lo primero que debemos hacer, si ninguna de las imágenes públicas disponibles nos convencen o por motivos de seguridad queremos asegurarnos de que la imagen contiene exactamente lo que necesitamos, es instalar el servidor OpenLDAP con las opciones que necesitemos en nuestro contexto de construcción, por ejemplo:

Contexto de construcción para imagen LDAP.
Básicamente lo único que hacemos es compilar las fuentes de OpenLDAP con las opciones y módulos que necesitamos, e instalarlas en una ruta dentro de nuestro entorno para construir la imagen correspondiente. Una vez instalado, es necesario cambiar las rutas que aparecen en los ficheros de configuración del servidor OpenLDAP, ya que serán rutas absolutas al contexto de construcción que estemos usando, aunque ya veremos más adelante como personalizarlo en el momento de creación del contenedor.

Ya solo necesitamos crear el dockerfile y ejecutar el comando docker build. La parte importante ahora es que nuestro nuevo dockerfile indicará que debe construirse a partir de nuestra imagen base quedando del siguiente modo:

Dockerfile para imagen ldap.
Para poder probar nuestra imagen y comprobar que todo es correcto, vamos a seguir utilizando como entrypoint la bash incluida en la imagen base, por tanto lanzamos la construcción de la imagen con el comando siguiente:

Construcción de la imagen ldap.
A continuación creamos un contenedor interactivo y efímero con esta nueva imagen para explorar su contenido. Cuando un contenedor es efímero no es necesario darle un nombre ya que, cuando lo paremos se borrará automáticamente, lo que es perfecto para probar de forma rápida nuestras imágenes. Si queremos crear contenedores efímeros usaremos la opción --rm con el subcomando run, como podemos ver en la siguiente imagen:

Contenedor creado a partir la imagen ldap.
Como podemos ver, nuestra imagen contiene tanto nuestro directorio ldap así como los directorios de nuestra imagen base. Esto nos permite comprobar como la construcción de imágenes superpone las capas que forman cada una de ellas, viendo una sistema de archivos completo a partir de los que aporta cada una de las imágenes.

Ahora, si intentamos ejecutar el proceso slapd de nuestro servidor OpenLDAP, ¿funcionará o nos faltarán bibliotecas necesarias para su correcto funcionamiento?

Error en la ejecución del poceso slapd de la imagen ldap.
Es evidente que, en el proceso de compilación, se ha incluido una dependencia con una biblioteca del sistema operativo que no está incluida en nuestra imagen base. En este punto bien podemos cambiar nuestra imagen base, incluyendo la biblioteca que falta, o cambiar solo la nueva imagen. En este punto, en mi opinión, creo que siempre es mejor idea cambiar la nueva imagen ya que si por ejemplo, fuese necesario cambiar una biblioteca de nuestra imagen base y esta la hubiesemos utilizado para construir muchas imágenes poesteriores, implicaría cambiar todas las imágenes que dependen de la imagen base. De este modo solo es necesario cambiar la imagen afectada que, en este ejemplo, es nuestra imagen ldap. Así que, añadiendo unas cuentas cosas más a nuestra nueva imagen la dejaremos del siguiente modo:

Nuevo contexto de construcción para la imagen ldap.
De esta manera incluimos las bibliotecas que faltan en la imagen y además, para asegurarnos que estén disponibles, añadimos en el directorio etc de la imagen la configuración necesaria para el enlazador de bibliotecas dinámicas en tiempo de ejecución. En nuestro caso la configuración del ld es muy sencilla y basta con hacer lo siguiente:

Configuración para el ld de nuestra imagen.
Con esta configuración le estamos diciendo al ld que puede buscar bibliotecas en la ruta /ldap/lib, que es donde los contenedores construidos a partir de nuestra imagen base tendrán la instalación de OpenLDAP. Lo único que nos queda es generar el fichero de caché que ld consulta cuando es necesario buscar bibliotecas dinámicas, para esto usaremos el comando ldconfig indicándole que su directorio root es el de nuestro contexto de construcción de la imagen ldap:

Generación de la caché para ld en nuestro contexto de construcción.
Con todo esto hecho, si ahora construimos de nuevo la imgen ldap y comprobamos un contenedor generado a partir de la misma, tenemos lo siguiente:

Construcción de la imagen ldap corregida.
Nuevo contenedor generado a partir de imagen ldap corregida.
Ahora que no recibimos ningún error de dependencias, si arrancamos el servidor tenemos lo siguiente:

Servidor slapd de OpenLDAP corriendo en nuestro contenedos efímero de pruebas.
Como podemos comprobar, el contenedor permite la ejecución del servidor slapd de OpenLDAP con lo que solo nos queda modificar nuestro dockerfile para cambiar el ENTRYPOINT y añadir una opción fundamental, la publicación del puerto 389 al exterior. Por tanto ahora, el dockerfile de esta imagen queda del siguiente modo:

Dockerfile definitivo para nuestra nueva imagen ldap.

Construcción de la imagen ldap.
Con lo que, tras la modificación y construcción de la imagen ldap definitiva, podremos grenerar contenedores que ejecuten un servidor de directorio basado en OpenLDAP.

Sobre esta imagen caben muchas mejoras, como añadir soporte TLS para la encriptación de las comunicaciones y la personalización de las opciones de configuración del servidor de directorio en el momento de generar el contenedor mediante el uso de variables de entorno.

En las próximas entradas veremos como hacer estos cambios, la importancia y diferencias entre usar ENTRYPOINT o CMD en nuestros dockerfiles y como usar un proceso init dentro de nuestros contenedores.