sábado, 9 de junio de 2018

Caraterísticas principales de NetWorker 9.2

Hola de nuevo, hoy toca una entrada sobre backup, en concreto sobre NetWorker y los cambios introducidos en las versiones 9.x.

Acabamos de terminar la migración de nuestro servicio de backup en versión 8.4.2, a la reciente versión 9.2.2 y he aprovechado para realizar cambios en la infraestructura. El cambio más importante ha sido la virtualización del servidor de backup, con lo que he separado el nodo de almacenamiento que controla la biblioteca de cintas del propio servidor.

El proceso de actualización ha implicado un cambio de plataforma, lo que Dell/EMC denomina una migración cruzada, ya que nuestro servidor de backup era un sistema Solaris 10 y el nuevo servidor es un servidor virtual Red Hat 7. Este tipo de migración solo está soportado si lo realiza personal de Dell/EMC así que, con su ayuda, hemos hecho la migración en aproximadamente 3 días.

Durante la instalación del servidor ya encontramos varios cambios, siendo los principales que es necesario instalar el servidor de autenticación y el servidor de licencias. Por tanto, si monitorizamos los procesos del servidor, ahora tendremos que incluir el servidor flex y los procesos asociados al servidor de autenticación en nuestro sistema de monitorización.

Otros de los cambios más importantes es la propia base de datos de NetWorker la cual, basada antes en WiSS, es ahora una base de datos SQLite, lo cual introduce muchas mejoras de rendimiento en el funcionamiento del servidor.

Pero los cambios de verdad los vemos al entrar en la NMC ya que ahora, la nueva consola presenta el siguiente aspecto:

Consola de adminsitración de NetWorker 9.2.
A primera vista es muy similar a la de las versiones 8.x y practicamente tenemos las mismas secciones, salvo por la sección Protection que es donde nos vamos a encontrar el cambio fundamental en como NetWorker nos permitirá proteger nuestra información.

En la sección Protection nos encontramos con la siguiente estructura:

Sección Protection de NMC.
Hay varios apartados que nos resultarán familiares de las versiones anteriores menos el apartado Policies, dentro del cual veremos que es donde se centra el cambio en como definimos nuestra estrategia de protección de datos.

Para ver las diferencias vamos a crear un cliente de backup usando el asistente de creación de nuevo cliente, que es el método recomendado de creación. Una vez terminado veremos nuestro nuevo cliente en la consola con una marca, la cual nos indica que el cliente no forma parte de ninguna política de protección.


Lista de clientes de backup disponibles.
A continuación creamos un grupo de clientes de backup en el que vamos a incluir nuestro nuevo cliente:

Creación de un nuevo grupo de backup.
Mientras que en versiones anteriores de NetWorker teníamos muchas opciones cuando configurábamos un grupo, ahora queda claro que un grupo de backup o protección es un conjunto de clientes organizado siguiendo el citerio que queramos fijar. En versiones anteriores recuerdo que olvidaba asignar el grupo al pool de cintas que quería usar para el backup de dicho grupo y, cuando se lanzaba el backup, el servidor siempre pedía una cinta del pool Default para poder realizar el backup. Ahora ya no hay una relación directa entre un grupo de backup y el pool de cintas a utilizar, con lo que veremos un poco más adelante como establecemos dicha relación.

Por tanto hasta ahora, en lo que respecta a la configuración de los clientes no encontramos muchas diferencias, pero en cuanto a los grupos de clientes no hemos configurado el pool a utilizar, si el grupo y el autostart están habilitados, el schedule, si queremos guardar los índices, etc...

El siguiente punto es la creación de una política, a partir de la cual podremos empezar a definir cómo queremos proteger nuestros datos. La creación de una política es tan sencillo como:

Creación de una nueva política.
Como podemos ver, lo único configurable es el nombre, comentario y tipo de notificación que se generará. Así que ¿como establezco la política de protección del grupo que acabo de crear? Creando un workflow que contendrá una o más acciones que queremos realizar sobre el grupo de protección.

Desde la política que acabamos de crear crearemos un workflow del siguiente modo:

Nuevo workflow de protección de clientes.
Ahora, dentro del workflow, definimos las acciones que queremos realizar y que se aplicarán solamente sobre el grupo LINUX_SERVERS que hemos definido antes.

Creación de una acción de backup - Tipo de backup y programación.
Creación de una acción de backup - Retención y pool.
Creación de una acción de backup - Opciones avanzadas y cambios en programación
Ahora que hemos definido la acción de backup sobre el grupo podemos resumir lo siguiente:
  • Los clientes se organizan en grupos de protección. Estos grupos los crearemos según nuestros propios criterios, pero no disponen de más información que los clientes que los forman y el nombre del grupo.
  • Las políticas de backup, también creadas según nuestros criterios y entorno, contendrán los workflows que aplicaremos a los grupos de protección anteriores. El workflow ya me permite configurar la hora de inicio, el intervalo y contiene las accioens que realizaremos sobre los grupos de protección.
  • La acción o acciones que contenidas en un workflow me permitirán definir la programación, el storage node, el pool, la retención y cualquier opción adicional necesaria.
Una vez terminada la configuración, nuestro workflow se representará graficamente del siguiente modo:
Representación gráfica de un workflow de backup.

Con esta representacion gráfica queda claro que, sobre el grupo de protección LINUX_SERVERS vamos a realizar una acción de backup y que el pool a utilizar se llama BACKUP.

Todos estos cambios ya estarán reflejados en la sección Monitoring, desde la que podremos lanzar un backup de nuestro grupo con solo ejecutar el workflow correspondiente:

Ejecución de un workflow de backup.
Una vez terminado el backup, podremos consultar los logs y resultado del mismo desde esta sección:
Consultando el resultado de un backup.
Resultado y logs de un backup.
Así que hasta aquí, para todos los que administramos NetWorker, las diferencias fundamentales se basan en la relación entre todos los elementos que nos permiten diseñar nuestra estrategia de backup.

Después de tantos años trabajando con NetWorker, desde la version 7.x, creo que el cambio le hacía falta y que el resultado es un sistema mucho más moderno tanto desde el punto de vista funcional como operativo, sin contar todos los cambios realizados en el código y todas las funcionalidades añadidas.

Como todavía me falta mucho por leer, en breve intentaré hacer otra entrada con más información sobre esta nueva versión de NetWorker.

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.

sábado, 2 de junio de 2018

Cluster ONTAP - Parte III

Hola de nuevo, tras ver un poco las diferencias hardware entre sistemas ONTAP 7-Mode y los sistemas cluster ONTAP, hoy voy a resumir las diferencias a nivel software que, como veréis son muchas.

Como ya comenté, un sistema cluster ONTAP está formado por parejas de controladoras. Esto nos lleva a una de las primeras diferencias que, desde mi punto de vista, es fundamental para entender como funciona cluster ONTAP. Mientras que en 7-Mode cada controladora de una pareja era independiente y podíamos administrarlas por separado, en cluster ONTAP tenemos un solo punto de administración para todo el cluster, tanto si está formado por dos nodos como por 10.
Este punto de administración nos permitirá gestionar todos los recursos del cluster de forma centralizada y servir el almacenamiento según nuestras necesidades. Aún podremos conectarnos a cada nodo del cluster si queremos, pero lo habitual es solo utilizar la administración única del cluster.

Este funcionamiento en cluster nos lleva a otra de las diferencias fundamentales entre 7-Mode y C-mode, las máquinas virtuales de almacenamiento.

Mientras que en 7-Mode cada controladora disponía de un servidor para cada protocolo ahora, cluster ONTAP nos permite crear instancias separadas de cada servicio o protocolo con los puertos de acceso necesarios. A cada una de estas instancias se las denomina storage virtual machine (SVM) y se caracterizarán por:

  • La dirección IP o puertos de fibra necesarios para servir datos.
  • Los protocolos de acceso usados.
  • Los volúmenes a los que tengan acceso para servir datos.
  • Sus propios usuarios y grupos.
Es decir, una SVM proporcionará ciertos servicios separados y totalmente aislados del resto de SVMs del cluster.

Más o menos y de manera muy simplificada, podemos verlo del siguiente modo:
SVMs en un sistema C-mode.
Por tanto, podremos crear SVMs cuyos volúmenes se encuentren en agregados de cualquier pareja del cluster, asignar los puertos, ya sean de red o fibra, de cualquier nodo físico del cluster y habilitar los protocolos necesarios en cada una de ellas para servicios NAS (NFS, CIFS) o de bloque (FCP, FCoE, iSCSI) así como crear los usuarios que sean necesarios en cada una de ellas.

Esta claro que con esta forma de trabajar, NetApp ha dado una orientación total a sus sistemas a proveedores de servicios, donde podremos tener tenants separados y cada cliente podrá administrar su SVM de manera totalmente independiente. Pero lo mejor de esto es que, aunque no seamos un proveedor de servicio, podremos hacer exactamente lo mismo y crear diferentes SVMs para cada uno de nuestros cliente internos y permitirles administrar totalmente su SVM y como siempre, da igual que nuestras controladoras sean la gama baja o la más alta, ya que cluster ONTAP correrá perfectamente en ellas.

Con esta entrada termino la parte de introducción a cluster ONTAP, la cual ha sido muy simple, con lo que recomiendo visitar la web de NetApp para profundizar mucho más en ella. A partir de ahora intentaré que las nuevas entradas sean un poco más prácticas.

sábado, 26 de mayo de 2018

Docker - Construcción de imágenes básicas

Hola de nuevo, hasta ahora hemos explorado muchas funcionalidades de Docker y en todos los casos, hemos trabajado con un elemento común, la imagen del software con el que queremos construir nuestro contenedor.

En un post anterior ya vimos de manera muy simple que es una imagen, pero ya llega el momento de preguntarnos ¿cómo puedo construir las mías?

Como ya vimos, el repositorio de imágenes oficial de Docker contiene muchísimas imágenes que podemos usar libremente para montar nuestros servicios, con lo que puede que en muchos casos no necesitemos construir nuestras imágenes, pero algunos motivos para querer construirlas son:
  • Por seguridad, si queremos saber que es lo que hay exactamente dentro de la imagen que estamos usando.
  • Uso de una versión de software distinta a la de las imágenes disponibles.
  • Nuestro servicio utiliza aplicaciones que son un desarrollo interno.
  • Para activar ciertas funcionalidades o módulos de la aplicación no disponibles en las imágenes publicadas.
Estos son algunos de los motivos que se me ocurren, pero estoy seguro que hay muchos más que en cada caso, harán necesario que construyamos nuestras propias imágenes.

Antes de empezar, vamos a enumerar las recomendaciones fundamentales a tener en cuenta:
  • Idealmente un contenedor debería ejecutar solamente un proceso, por tanto debemos hacer imágenes que solo contengan todo lo necesario para ejecutar dicho proceso.
  • No debemos añadir software adicional sin necesidad. Por ejemplo, si nuestro contenedor no va a conectarse con otros sistemas por ssh, no es necesario incluir dicho comando ni las librerias necesarias.
Siguiendo las recomendaciones anteriores, conseguiremos imágenes muy ligeras que contendrán solo lo necesario para nuestro servicio.

Con todo esto en mente, vamos a construir una imagen base sobre la cual añadiremos software adicional.

Voy a seguir un método de construcción de imágenes totalmente manual, en el cual voy a comenzar creando un chroot que luego usaré para construir la imagen. Este método puede ser un poco lento, pero es automatizable mediante scripts muy simples y una vez creada la imagen, será rapidamente reproducible.

Por la relación entre un chroot y los contenedores, esta forma de construcción nos permite entender mejor como funcionan ambos y aprender un poco mejor como funciona un sistema Linux. Para empezar, vamos a crear una estructura de directorios en la que vamos copiar nuestra shell preferida, junto con las bibliotecas de las que depende para su correcto funcionamiento. Para esto y durante todo el procedimiento, usaremos el comando ldd con el cual comprobaremos las bibliotecas dinámicas de las que depende un binario y que necesitaremos copiar a nuestro chroot.

Empezamos creando una estructura de directorios y copiando unos cuantos comandos, entre ellos nuestra shell preferida (la imagen siguiente está resumida):

Creación de un chroot básico.
Con esto tenemos una shell básica y solamente los comandos cd y ls. Ahora, para probar nuestro chroot y comprobar si nos falta alguna biblioteca solo necesitamos ejecutar el comando chroot especificando la ruta donde tenemos nuestro contexto de construcción:
Prueba de nuestro contexto de construcción mediante chroot.

Tenemos un entorno muy básico en el cual solo disponemos de dos comandos, el comando cd y el comando ls. Como necesitaremos más comandos, siempre teniendo en cuenta que debemos hacer una imagen base sencilla y solo con lo que vayamos a necesitar, tendremos que copiarlos a las rutas correspondientes, así como las bibliotecas necesarias para el correcto funcionamiento de los mismos. Tras añadir todos los comandos que vayamos a necesitar, el entorno de construcción que vamos a usar queda del siguiente modo:

Entorno de construcción básico definitivo.
Una vez que tenemos nuestro entorno chroot básico y hemos comprobado que los comandos que hemos copiado funcionan, podemos construir nuestra primera imagen base, a partir de la cual construiremos imágenes más complejas.

Para poder construir una imagen, necesitamos decirle a docker que es lo que vamos a añadir a la misma y cual va a ser el primer comando que se va a ejecutar dentro de la imagen cuando creemos un contenedor. Para esto, necesitamos crear un fichero de configuración dockerfile que contiene las instrucciones necesarias para el subcomando build de docker. En el caso de nuestra imagen base, el contenido de nuestro dockerfile será tan simple como:

Dockerfile para imagen base.

Como vemos, este fichero de texto contiene tres instrucciones solamentecada una de las cuales indica a docker lo siguiente:
  • FROM scratch. La directiva FROM indica cual es la imagen padre a partir de la cual vamos a construir la nuestra. En nuestro caso, al tratarse de una imagen base, no usaremos  ninguna otra imagen como origen.
  • ADD ./base_image /. Con esta directiva indicamos que el contenido de nuestro directorio base_image debe copiarse al raíz de nuestra imagen.
  • ENTRYPOINT ["/bin/bash"]. Con esta directiva le indicamos a docker cual es el comando que debe ejecutarse al arrancar un contenedor basado en esta imagen.
Os recomiendo que consultéis la referencia oficial de Docker sobre los dockerfile aqui ya que, además de explicarlo mucho mejor que yo, os darán información sobre muchas otras directivas a utilizar para construir imágenes.

Con nuestro dockerfile ya listo, solo necesitamos ejecutar el comando docker build como se mustra a continuación:

Construcción de la imagen base.
Como podemos ver el comando es muy simple y solo necesitamos indicarle la ruta y nombre de nuestro dockerfile, el tag o nombre de nuestra imagen y la ruta a nuestro contexto de construcción. Ahora, al consultar las imágenes disponibles en el repositorio local de imágenes, podemos ver nuestra imagen lista para usarse:

Nuestra nueva imagen base lista para usarse.
Ya solo tenemos que crear un contenedor con nuestra imagen y comprobar que todo funciona correctamente:

Contenedor a partir de imagen base.
Teniendo en cuenta el tipo de imagen que hemos construido y que nuestro ENTRYPOINT apunta a una shell, es importante tener en cuenta que si no creamos un contenedor interactivo este se parará inmediatamente tras ejecutar dicha shell.

Con lo expuesto hasta aquí, ya podemos construir una imagen base, a partir de la cual generaremos imágenes más complejas incluyendo más software.

En las siguientes entradas intentaré crear una imagen con un servidor OpenLDAP a partir de esta y además, veremos opciones de personalización de imágenes, así como la importancia de la directiva ENTRYPOINT y sus alternativas.

miércoles, 16 de mayo de 2018

Usando Docker de manera práctica - Parte IV

Hola de nuevo, hoy vamos con una nueva entrada dedicada a Docker y como podemos usarlo para crear servicios complejos.

Hasta ahora, en todos los ejemplos que hemos visto, hemos utilizado la potencia de Docker para crear y desplegar de forma rápida servicios simples basados en aplicaciones, como podía ser un servidor web o una base de datos. En alguno de los ejemplos hemos creado servicios un poco más complejos enlazando dos contenedores entre sí, comunicando nuestro servidor web con nuestra base de datos. Las funcionalidades que proporciona Docker lo hacen ideal precisamente para casos como este, en el que nuestro servicio está basado por múltiples aplicaciones individuales que proprocionan un servicio a nuestros clientes.

Con esto último no quiero decir que no se pueda usar en otros casos, los contenedores por sus características nos permiten diseñar múltiples arquitecturas en las que, aunque nuestro servicio sea pequeño y quizás nada complejo, nos puede ser de gran utilidad. Así que, ahora que sabemos un poco más sobre Docker, podemos pensar en usarlo en casos como los siguientes:
  • Migración de aplicaciones antiguas y no soportadas a un entorno más moderno. Con Docker podremos crear una imagen con nuestra aplicación y migrarla directamente a un nuevo host o entorno virtual.
  • Entornos de pruebas. En aquellos casos en los que necesitamos probar nuevas versiones o funcionalidades de software, pero no queremos instalarlo en nuestras máquinas, si existe la imagen correspondiente podremos crear un contenedor a partir de dicha imagen y realizar todas las pruebas que necesitemos.
  • Reducción del número de hosts o entornos virtuales. Si tenemos varios servicios ligeros desplegados en diferentes hosts o entornos virtuales, los cuales no interactuan entre sí, podemos convertirlos en contenedores con sus dependencias y unificarlos todos en un solo servidor, ya sea físico o virtual simplificando así nuestra infraestructura.
Estos ejemplos son casos sencillos pero las aplicaciones son innumerables y dependerán del entorno en el cual nos encontremos y sobre todo, de nuestras necesidades. En un post posterior intentaré desarrollar un poco más todo esto con ejemplos.

Ahora, siguiendo con la idea de servicios formados por diferentes aplicaciones que se comunican entre sí vamos a investigar Docker Compose o compose a partir de este punto, que nos permite definir un servicio a partir de todos sus componentes y lanzarlo con un solo comando.

Compose es una herramienta que, a partir de la definición de un servicio nos permite lanzarlo y gestionarlo de forma sencilla, olvidándonos así de los contenedores individuales y centrándonos en la imagen completa del servicio que queremos proporcionar así como las interacciones entre los contenedores que lo forman.

Para empezar lo primero es instalar el comando docker-compose, ya que por defecto puede no estar incluido en la instalación de Docker. Siguiendo las instrucciones de instalación dadas en https://docs.docker.com/compose/install/ instalamos compose en nuestro sistema.

Si ejecutamos compose sin argumentos, veremos que nos devuelve una ayuda muy similar a la que nos devuelve el comando docker que hemos venido utilizando hasta ahora:

Salida del comando docker-compose.
Como vemos, con compose podemos realizar muchas tareas que en apariencia son similares a las que realizamos con el comando docker estándar. La verdadera diferencia es que compose usará un fichero de definición de servicio, el cual contendrá la descripción de los contenedores que definen el servicio así como la relación existente entre ellos.

Para describir o declarar un servicio crearemos un fichero YAML en el cual estableceremos de forma muy simple los elementos que forman el servicio y la relación entre los mismos. Como creo que con un ejemplo quedará mucho más claro, vamos a repetir nuestro ejemplo de servidor web enlazado con una base de datos mariaDB del post anterior. En aquel caso nuestro servicio era un servidor web en el cual suponemos que corre un servicio que necesita consultar una base de datos externa, dada por otro contenedor que ejecuta una base de datos mariaDB. Lo que entonces hicimos  podemos describirlo con YAML del siguiente modo:

Definición en YAML de un servicio web simple.
Analizando el fichero es fácil ver como mediante YAML hemos definido cada uno de nuestros contenedores, un servidor web basado en Apache y una base de datos basada en mariaDB y cual es la relación entre ambos contenedores.

Con este fichero, con solo ejecutar el comando docker-compose especificando cual es nuestro ficheo de definición, arrancaremos el servicio formado por nuestros dos contenedores:

Lanzando un servicio con docker-compose.
Si comprobamos que contenedores hay corriendo, pero usamos docker-compose, la salida es la siguiente:

Contenedores en ejecución de un servicio.
Podemos ver de forma sencilla los contenedores que forman el servicio, su estado, los puertos que tienen publicados así como el comando ejecutado en la imagen.

Para controlar nuestro servicio usaremos diferentes subcomandos de compose, los cuales me permitirán parar el servicio, inspeccionar los logs de los contenedores, etc...

Mostrando los logs de un contenedor miembro del servicio.
Parando un contenedor miembro del servicio.
Arrancando un contenedor miembro del servicio.
Parando un servicio de forma completa.

Eliminando completamente un servicio.
Por tanto, compose nos permite controlar nuesros servicios, así como los contenedores que lo forman, de una manera muy similar a como lo hacíamos con el comando docker.

Un punto muy importante que debemos tener en cuenta al usar compose es que el comando siempre busca el fichero de definición del servicio, el cual por defecto es docker-compose.yml, con lo que si nuestro fichero de definición se llama de otra manera, tendremos que especificarlo con la opción -f. Para ahorrarnos esto en todos nuestros comandos, un enlace simbólico nos permitirá especificarlo y además tener nosmbres más descriptivos en nuestros fichero YAML. 

Debe quedar claro que un ejemplo tan sencillo como este no permite apreciar la potencia que proporciona compose. Para desarrollarlo un poco más, en el próximo post, intentaré complicar esto un poco más y estudiar más funcionalidades de compose.

martes, 1 de mayo de 2018

Usando Docker de manera práctica - Parte III

Hola de nuevo, en el último post de esta serie hablamos un poco del acceso a la red de los contenedores y de como conseguir permanencia de datos usando volúmenes.
En este post vamos a profundizar un poco más en las caraterísticas de red de los contenedores para poder comunicarlos entre ellos, además de explorar otras opciones y subcomandos para su uso con nuestros contenedores.
Para entender un poco más como funciona el subsistema de red asociado a Docker, vamos a ver que configuración de red tenemos en nuestro sistema de pruebas.

Interfaz virtual docker0.
Como vemos, hay un interfaz de red con una dirección IP asociada al demonio Docker la cual está relacionada con la red de cada uno de los contenedores que creamos, así como ya hicimos en un post anterior, si comprobamos la dirección IP de nuestros contenedores veremos algo como lo siguiente:

Interfaz de red de un contenedor.
Nuestro contenedor webserver dispone de un interfaz en la misma red que el interfaz virtual docker0 de nuestro host. Como es lógico, cualquier nuevo contenedor con acceso a la red tendrá un interfaz con un direccionamiento similar, por ejemplo:

Red usada por interfaces de red en contenedores Docker.
Y estas interfaces de red ¿como se relacionan con nuestro host?, pues comprobando de nuevo el estado de la red con dos contenedores corriendo vemos que han aparecido dos nuevas interfaces de red virtuales y lo que es más importante, que se ha creado un bridge:

Interfaces de red virtuales en el host ejecutando Docker.
Bridge creado en el host ejecutando Docker.
Sin entrar en mucho detalle sobre lo que es un bridge, está claro que el funcionamiento básico de Docekr con la red, consiste en crear un bridge en el host al cual se conectan los interfaces de red de todos nuestros contenedores. Como ya vimos, gracias a esto, los contenedores que creamos tienen acceso al exterior por defecto pero no se puede acceder a ellos desde el exterior de nuestro host.

Al tratarse de un bridge, los contenedores tendrán acceso por red entre ellos pero con ese direccionamiento la pregunta obvia es, ¿como fijamos que puedan comunicarse correctamente entre ellos si no podemos asegurar su direccionamiento?

Para esto disponemos de una opción que nos permitirá enlazar un contenedor con otro ya existente en el momento de su creación. Básicamente lo que hacemos es crear un contenedor y enlazarlo con otro que ya se encuentra corriendo en nuestro host. Como siempre, para entenderlo mejor, supongamos que vamos crear un servicio web formado por un servidor de base de datos, por ejemplo una base de datos MariaDB, un servidor web Apache y una estación de gestión de la base de datos.

Con esta descripción de nuestro servicio, lo primero que debemos pensar es que vamos a necesitar tres contenedores y que tanto nuestro servidor web como nuestra estación de gestión de la base de datos, dependerán del contenedor en el que se ejecute el motor de base de datos. Con este pequeño análisis ya hemos determinado un orden de arranque, por tanto primero creamos nuestro contenedor de base de datos:

Creamos un contenedor con una base de datos.
Si comprobamos si nuestro contenedor con el motor de base de datos está corriendo, nos daremos cuenta que no es así:

Error en el arranque del contenedor webDB.
En estos casos, para poder comprobar que ha sucedido y porque no ha arrancado correctamente, podemos usar el subcomando logs para que nos muestre el contenido de STDERR del contenedor y así poder determinar la causa del problema. En el caso de un contenedor MariaDB tenemos el siguiente mensaje:

Salida de logs de un contenedor MySQL.

Este mensaje nos indica que para poder inicializar correctamente la base de datos, es necesario que le proporcionemos la password de root a través de alguna de las variables de entorno que indica en la salida, pero ¿como podemos pasarle variables de entorno a un contenedor? Para esto solo necesitamos usar la opción -e cuando creamos nuestro contenedor. Por tanto, si ahora volvemos a crear el contenedor de base de datos y especificamos la password de root como nos indica el log, tendremos lo siguiente:

Creación de contenedor MariaDB con variables de entorno.
Ya tenemos el primer contenedor de nuestro servicio web, ahora ya podemos crear los otros dos contenedores en el orden que queramos ya que solo dependen de este. Para crear el servidor web que realizará consultas a la base de datos ejecutaremos el sigueinte comando, en el cual ya establecemos el enlace entre ambos:

Creación de un contenedor enlazado con otro.
Como podemos ver en el comando hemos especificado la opción --link webDB:DB con lo que estamos diciendo que hay un enlace con el contenedor llamado webDB y el alias del mismo lo llamamos DB. La pregunta es, ¿como se establece esta relación entre ambos contenedores? ¿donde se guarda la información de este enalce? lo que sucede al usar esta opción es que Docker modifica el fichero /etc/hosts del contenedor webserver2 que acabamos de crear y añade uan entrada como la siguiente:

Fichero /etc/hosts del contendor Apache dependiente de base de datos.
De este modo, cuando desarrollemos nuestra aplciación web, cualquier operación que realicemos a la base de datos en el host DB, se realizará sobre el contenedor con la base de datos MySQL.

Ahora creamos un contenedor que usaremos para administrar la base de datos de forma remota, así que por ejemplo podemos crear un contenedor a partir de una imagen de CentOS, instalar el cliente de mariaDB y conectarnos a nuestra base de datos:

Contenedor para administrar la base de datos.
Con el cliente de administración de base de datos instalado en este contenedor, podremos conectarnos a la base de datos corriendo en el contenedor webDB y administrar la base de datos:

Conexión del contenedor de administración con el contenedor de base de datos.
Por tanto vemos que gracias a la opción --link usada en el momento de crear los contenedores, podemos enlazar unos con otros de manera muy sencilla y solventar así el problema que puede suponer la asignación dinámica de direcciones IP a los contenedores para la creación de nuestros servicios.

Como es lógico, estas configuraciones crean relaciones de dependencia entre los contenedores que provocan que, si intentamos arrancar un contenedor que se enlaza con otro que está parado, recibiremos un error como el siguiente:

Error arrancando un contenedor enlazado a uno parado.
Es muy importante entender esta funcionalidad dentro del concepto de creación de servicios complejos, en los cuales varios contenedores relacionados entre sí nos permitirán proporcionar un servicio el cual se definirá a partir de los elementos que lo forman. Para esto usaremos otras herramientas de Docker como docker-compose, lo cual veremos más adelante.

Hasta aquí hemos ampliado un poco más el uso de la red y hemos visto como enlazar contenedores entre sí para crear servicios un poco más complejos, en las siguientes entradas ampliaremos más la idea de servicio y como crearlos.