Contenedores Docker multi-dominio

Caso de Uso

Tenemos varias aplicaciones servidoras a la vez en un mismo entorno de desarrollo, cada una encapsulada en un contenedor, llamémosles de ahora en adelante "Contenedor A" y "Contenedor B".

Utilizando docker estas aplicaciones tienen la misma dirección IP en nuestra máquina, una forma de distinguirlas es cambiando el puerto que exponen.

/galleries/docker-multidomain/ip.thumbnail.png

Aplicaciones exponiendo la misma dirección IP utilizando diferentes puertos para diferenciar las aplicaciones

Pero esto es un poco confuso, ¿8080 sigfica que accedemos a las "aplicación A" y 8081 significa "aplicación B"?

Sería mucho más sencillo de recordar algo así:

/galleries/docker-multidomain/domain.thumbnail.png

Diferenciando aplicaciones por nombre de dominio

Obtener este valor semántico extra es más sencillo de lo que parece.

Cómo configurar un Proxy Inverso Multi-Dominio

Dije que era fácil porque no vamos a tener que hacer casi nada, ya que otro contenedor hará casi todo el trabajo por nosotros. Vamos a utilizar nginx-proxy, que generará automáticamente las configuraciones necesarias para NGINX.

Nota

Puedes descargar el ejemplo completo desde: https://github.com/carlosvin/docker-reverse-proxy-multi-domain

Así que al final no tendremos 2 contenedores, sino también tendremos un tercero que hará las veces de proxy.

/galleries/docker-multidomain/proxy.thumbnail.png

Los 2 contenedores y el proxy

Estructura del proyecto de ejemplo

  • docker-compose.yaml (Archivo con la configuración descrita en la imágen previa)
  • a (Directorio para la aplicación A)
    • Dockerfile (Archivo con la configuración del contenedor A)
  • b (Directorio para la aplicación B)
    • Dockerfile (Archivo con la configuración del contenedor B)

Ver proyecto.

Configuración de la arquitectura (docker-compose)

La parte más importante es la configuración de las relaciones entre los contenedores.

docker-reverse-proxy-multi-domain/docker-compose.yaml (Código fuente)

a:
  build: a
  environment:
    VIRTUAL_HOST: a.domain.com
  restart: always

b:
  build: b
  environment:
    VIRTUAL_HOST:  b.domain.com
  restart: always

nginx-proxy:
  image: jwilder/nginx-proxy
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - /var/run/docker.sock:/tmp/docker.sock:ro

  restart: always
  privileged: true
  • En la línea 4 y 10 es donde configuramos el nombre de dominio que queremos utilizar para cada aplicación.
  • A partir de la línea 13 configuramos el proxy (esta es la parte de copiar y pegar).
  • En la línea 2 y 8 estamos indicando a docker-compose que tiene que construir las imágenes dentro del directorio indicado. Por ejemplo, en la línea 2, estamos indicando que docker-compose tiene que construir la imágen docker utilizando ./a/Dockerfile.

Configuración de la imágen de la aplicación

A continuación vamos a comentar la configuración de la imágen del contenedor para la aplicación A.

docker-reverse-proxy-multi-domain/a/Dockerfile (Código fuente)

FROM httpd:2.4
RUN echo "<html><body><h1>A</h1>App A works!</body></html>" > /usr/local/apache2/htdocs/index.html

Línea 1: Importamos una imágen con un servidor apache.

Línea 2: Servimos un archivo que muestra "Host A" como página por defecto.

La configuración para la aplicación B, es prácticamente la misma:

docker-reverse-proxy-multi-domain/b/Dockerfile (Código fuente)

FROM httpd:2.4
RUN echo "<html><body><h1>B</h1>App B works!</body></html>" > /usr/local/apache2/htdocs/index.html

Añadiendo los nombres de dominio a tu configuración

En Linux simplemente tenemos mapear la dirección local a los nombres de dominio que hayas elegido, en nuestro ejemplo es a.domain.com y b.domain.com.

1
2
3
4
5
#/etc/hosts
127.0.0.1             localhost.localdomain localhost
::1                 localhost6.localdomain6 localhost6
127.0.0.1   a.domain.com
127.0.0.1   b.domain.com

Simplemente he añadido las líneas 4 y 5.

¡Todo listo!

Ya solo nos queda probar el ejemplo.

docker-compose build
docker-compose up

Ya están las tres contenedores arrancados.

Ahora podemos abrir nuestro navegador y escribir a.domain.com y nos mostrará el texto App A works!. Si escribimos b.domain.com entonces veremos App B works!.

/galleries/docker-multidomain/a.screenshot.thumbnail.png

a.domain.com en el navegador

/galleries/docker-multidomain/b.screenshot.thumbnail.png

b.domain.com en el navegador

Nota

En la mayoría de distribuciones Linux necesitarás privilegios para ejecutar los comandos docker (sudo).

Comparación frameworks REST para RUST

Estoy empezando a hacer mis primeros pinitos con Rust, es un lenguage de programación que promete ser tan rápido como C/C++, pero más seguro en cuanto a la gestión de memoria ser refiere. Básicamente, no permite al programador hacer "cosas malas" con la memoria como: olvidarse de liberar la memoria que no se va a utilizar o liberar memoria que no es tuya. En estos casos, Rust simplemente no compilará.

Para aprender he pensado en hacer algún pequeño proyecto, tengo algunas ideas, pero todas pasan por ofrecer una API REST, así que me he puesto a buscar frameworks que me faciliten esta tarea y me he llevado una grata sorpresa al encontrar una muy buena comparativa con el estado de los frameworks REST para Rust, a continuación os dejo el enlace:

https://github.com/flosse/rust-web-framework-comparison

Convertir archivos en formato Windows a formato Unix

Si alguna vez has programado desde un entorno Windows para entornos Unix, seguramente habrás tenido este problema: Los archivos que instalas en tu entorno Unix tienen formato Windows.

Hay un forma bastante sencilla de convertir todos los archivos que tienes en un directorio de formato Windows a Unix.

find . -type f -print0 | xargs -0 dos2unix

Lo he sacado, como no, de http://stackoverflow.com/questions/11929461/how-can-i-run-dos2unix-on-an-entire-directory

Creando REST URLs

Las primeras veces que me puse a diseñar una API REST cometí unos cuantos errores, por supuesto. A continuación os voy a contar algunos de los errores que cometí y lo que he entendido hasta hoy sobre la construcción de URLs REST con ejemplos.

Fundamentos REST

  • Utilizamos URLs para acceder a recursos.
  • Utilizamos verbos para modificar recursos.
  • Nuestros verbos están proporcionados por el protocolo HTTP.
  • Los verbos tienen un equivalente directo con las operaciones CRUD [1].
  • Para acceder a un recurso existente necesitamos su identificador.

Verbos REST

POST
(C) Utilizado para crear nuevos recursos.
GET
(R) Utilizado para leer un recursos existentes en el sistema.
PUT
(U) Utilizado para actualizar recursos existentes.
DELETE
(D) Utilizado para borrar recursos existentes.

En una tabla quedará más claro

Verbo REST Acción CRUD Debe exisitir el recurso
POST Crear No
GET Leer
PUT Actualizar
DELETE Borrar

Acceso a Recursos

Un recurso es a lo que quieres acceder. Por ejemplo, un coche.

Para poder acceder a un coche no es suficiente con esta información, no puedes ir a un concesionario y preguntar por un coche en general, tienes que decir qué coche quieres. Así que llegas al concesionario y dices:

Hola, buenos días. Quiero información sobre el Fiat Bravo 1.9 Emotion 120CV.

De esta forma el dependiente sabe cuál es y te puede dar la información.

"Fiat Bravo 1.9 Emotion 120CV" es nuestro identificador.

Trasladando el ejemplo a las APIs REST:

GET   http://tiendadecoches.es/api/coches/fiat-bravo-19-emotion-120cv

De esta forma nuestra API nos puede proporcionar información del coche.

Esto es un ejemplo muy simplificado, pero realmente cuando accedemos a un recurso concreto solemos utilizar algo que lo identifique de forma unívoca. Una práctica común y recomendable es utilizar UUID.

GET  http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f

Pero nuestra API, al igual que una tienda, no tiene por qué ser tan estricta, podemos preguntar por los coches que tienen ciertas caracteríticas, podemos ir al concesionario y decir:

Hola buenos días, quiero un Fiat Bravo.

Y el dependiente, amablemente, te mostrará todos los Fiat Bravo que tiene. Veamos cómo podemos decir esto a nuestra API.

GET  http://tiendadecoches.es/api/coches/?marca=fiat&modelo=bravo

Nuestra API nos devolverá todos los coches que son marca Fiat y modelo Bravo.

Marca y modelo en este caso son lo que llamamos parámetros de consulta (query parameters).

Como os habréis dado cuenta durante el ejemplo, para obtener información de un recurso siempre hemos utilizado el verbo GET.

Modificar Recursos

Nuestra API también nos puede permitir modificar un recurso, al igual que para pedir información, para modificar un recurso necesitamos especificar qué recurso queremos modificar, así que necesitamos otra vez un identificador.

Antes queríamos información (leer) y utilizábamos nuestro verbo GET, ahora lo único que cambia es el verbo, queremos modificar así que utilizamos el verbo equivalente que nos proporciona el protocolo HTTP: PUT.

PUT   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f

Pero nos falta algo, tenemos que decir qué queremos cambiar del coche, por ejemplo, imaginemos que queremos cambiar la cilintrada y poner 100CV.

Tenemos que enviar la nueva cilindrada a esta URL http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f por HTTP utilizando el verbo PUT.

El protocolo HTTP nos permite enviar información en un mensaje PUT, así que solo nos falta pensar en el formato en que lo vamos a enviar.

Podemos enviarlo en JSON o XML o como queramos, solo tenemos que estar seguros de que el formato que enviamos es lo que espera recibir el servidor.

Nota

Cuando definimos una API REST tenemos que definir también el formato en que vamos a enviar los datos.

Ejemplo en JSON:

{ cilindrada: 100 }

Borrar Recursos

Continuando con el ejemplo de los coches, imaginemos que ahora somos el concesionario, y que ya no queremos vender más ese Fiat Bravo (concretamente el cce05bee-386b-11e5-a151-feff819cdc9f). Seguiremos manteniendo la URL que identifica el recurso, pero cambiamos el verbo, no queremos leer (GET), ni modificar (PUT), queremos borrar (DELETE).

DELETE   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f

En el caso del borrado, no hay que proporcionar ninguna información adicional, con el verbo (DELETE) y el recurso es suficiente.

Crear Recursos

Y nos queda último verbo, crear (POST). En este caso no hay que identificar el recurso, porque no existe todavía.

POST   http://tiendadecoches.es/api/coches/

Lo que sí que tenemos que enviar son los datos del recurso que vamos a crear.

En nuestro ejemplo, queremos crear un coche, así que ponemos toda la información necesaria para crear un coche dentro de la llamada POST de HTTP, algo muy parecido como hemos hecho en el apartado Modificar Recursos, pero en este caso mandamos toda la información necesaria, no solo la cilindrada.

Ejemplo en JSON:

{
"marca": "Fiat",
"modelo": "Bravo"
"anio": 2010
"puertas": 5,
"cilindrada": 120,
"version": "Emotion",
"climatizador": true,
"ac": false,
"fuel": "Diesel"
}

Podemos delegar en el sistema, para que cuando pidamos la creación de un recurso nuevo, nos asigne un nuevo identificador, o simplemente enviarlo con el resto de información:

{
"identificador": "cce05bee-386b-11e5-a151-feff819cdc9f"
"marca": "Fiat",
"modelo": "Bravo"
"anio": 2010
"puertas": 5,
"cilindrada": 120,
"version": "Emotion",
"climatizador": true,
"ac": false,
"fuel": "Diesel"
}

Colecciones

Algo que no quiero pasar por alto, porque al menos para mí no fue obvio, es el manejo de colecciones. Realmente ya está explicado, porque todas las acciones que hemos visto previamente sobre los coches, estaba aplicando realmente a una colección de coches.

Pero, ¿qué pasa si un recurso tiene a su vez una colección? Siguiendo con los coches, un coche puede tener una lista de aceites con los que puede funcionar, así que nuestra API debería permitir obtener, modificar, borrar o crear elementos en la lista.

Nota

Para el ejemplo asumiremos que el identificador del aceite es el atributo tipo.

Añadir un elemento a la colección

Si queremos añadir un elemento a la colección de coches lo que vamos a hacer es crear un nuevo coche, así que estamos en el caso de Crear Recursos.

Para añadir un nuevo aceite al coche cce05bee-386b-11e5-a151-feff819cdc9f, que ya existe:

POST   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/

{
"tipo": "5W30",
"otros_datos": "este es el mejor del mundo para este coche"
}

Si queremos añadir otro aceite:

POST   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/

{
"tipo": "10W30",
"otros_datos": "otras cosas sobre aceites de coche",
}

Modificar un elemento de la colección

Si queremos modificar los datos del aceite 5W30 del coche cce05bee-386b-11e5-a151-feff819cdc9f:

PUT   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/5W30/

{
"tipo": "5W30",
"otros_datos": "este ya no es el mejor del mundo para este coche"
}

Borrar un elemento de la colección

Para borrar un aceite 10W30 del coche cce05bee-386b-11e5-a151-feff819cdc9f:

DELETE   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/10W30

Leer un elemento de la colección

Para obtener la información del aceite 10W30 del coche cce05bee-386b-11e5-a151-feff819cdc9f:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/10W30

Listar elementos de la colección

Como hemos visto en Leer un elemento de la colección, podemos obtener información de cualquier elemento de la colección, pero también podemos obtener varios elementos de la colección, ordenarlos, paginarlos y aplicar cualquier tipo de acciones típicas de una colección.

Podemos obtener todos los aceites soportados por el coche cce05bee-386b-11e5-a151-feff819cdc9f, es tan simple como:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/

Pero también podemos proporcionar otras funcionalidades en nuestra API, como obtener los resultados ordenados:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?ordenar_por=tipo&orden=ascendente

Podemos pedir al API que nos devuelva los 10 primeros aceites del coche cce05bee-386b-11e5-a151-feff819cdc9f:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?numero_de_elementos=10

Cuando no queremos mostrar toda la lista completa, podemos proporcionar un sistema de paginación:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?pagina=3&numero_de_elementos=3

En la petición de arriba, estamos diciendo que nos devuelva la página 3 de los aceites del coche cce05bee-386b-11e5-a151-feff819cdc9f y que nos muestre 3 aceites por página. Si quisiéramos ir a la página siguiente:

GET   http://tiendadecoches.es/api/coches/cce05bee-386b-11e5-a151-feff819cdc9f/aceites/?pagina=4&numero_de_elementos=3

Todas estas funcionalidades, son posibles gracias a los parámetros de consulta.

Típico error

La primera vez que intenté diseñar un API REST lo que hice fué otra cosa, era una API, pero no REST.

Mi principal error fue en la construción de las URLs, incluí verbos sin tener en cuenta que los verbos ya me los proporcionaba el protocolo HTTP.

Por ejemplo, creaba URLs del tipo:

POST    http://example.com/api/coches/seat-ibiza/borrar-rueda/3

Cuando lo correcto sería

DELETE  http://example.com/api/coches/seat-ibiza/ruedas/3

Video Tutoriales

Estos tutoriales me fueron de gran ayuda y os recomiendo que los veáis enteros:

[1] Create, Read, Update, Delete

Gestión de dependencias C++: Biicode

Hace tiempo que estoy interesado la construcción, gestión de dependencias, empaquetado y despliegue de proyectos software. Para Java, Scala, Python, etc. Es bastante sencillo ya que hay herramientas como Maven, Gradle, pip, Sbt, etc. Pero para C++_, lo más parecido que he encontrado es Maven con el plugin Nar o Gradle con su plugin cpp (que está en desarrollo).

Hace tiempo que conozco Biicode, pero no había sacado nunca tiempo para probarlo, hasta hoy.

El funcionamiento general es muy sencillo.

Necesitamos instalar biicode en nuestra máquina.

Yo he creado un pequeño ejemplo utilizando el sistema de log de la librería Poco.

He ejecutado este comando para crear mi proyecto, al que he llamado bii_log. Eso sí, lo tengo que crear debajo de mi nombre de usuario en Biicode, por si posteriormente lo quiero publicar.

bii new carlovin/bii_log --hello=cpp

Se ha creado una estructura de directorios y ficheros con nuestro proyecto. Aunque los únicos ficheros que vamos a tocar son:

blocks/carlosvin/bii_log/main.cpp
blocks/carlosvin/bii_log/biicode.conf

En el fichero biicode.conf vamos a configurar nuestras dependencias, en nuestro caso la librería Poco.

# Biicode configuration file

[requirements]
    fenix/poco(develop): 0

[parent]
        carlosvin/bii_log: 0

[includes]
    Poco/*.h: fenix/poco/Foundation/include

En la sección [includes], estamos redefiniendo la ruta a nuestros ficheros de cabeceras, porque si no lo hiciéramos tendríamos que hacer algo así:

#include "fenix/poco/Foundation/include/Logger.h"

Gracias a esta línea, en nuestro código tendremos includes más claros como:

#include "Poco/Logger.h"

Así de fácil, ya podemos utilizar la librería Poco en nuestro proyecto, por ejemplo:

#include "Poco/FileChannel.h"
#include "Poco/FormattingChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/Logger.h"
#include "Poco/AutoPtr.h"

using Poco::FileChannel;
using Poco::FormattingChannel;
using Poco::PatternFormatter;
using Poco::Logger;
using Poco::AutoPtr;

int main(int argc, char** argv) {
        AutoPtr<FileChannel> pChannel(new FileChannel);
        pChannel->setProperty("path", "log/sample.log");
        pChannel->setProperty("rotation", "100 K");
        pChannel->setProperty("archive", "timestamp");

        //AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
        AutoPtr<PatternFormatter> pPF(new PatternFormatter);
        pPF->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
        AutoPtr<FormattingChannel> pFC(new FormattingChannel(pPF, pChannel));
        Logger::root().setChannel(pFC);

        Logger & logger = Logger::get("TestChannel");
        for(int i=0; i<10000; i++){
                poco_information(logger, "This is a info");
                poco_warning(logger, "This is a warning");
        }
        return 0;
}

Para compilar el proyecto simplemente hay que ejecutar:

bii cpp:build

Y para publicarlo y que cualquiera pueda utilizarlo, como nosotros hemos utilizado Poco:

bii publish

Además de lo sencillo que ha resultado todo, me ha gustado mucho que después de ejecutar "bii cpp:build", mi Eclipse con CDT indexaba perfectamente el código, así como el autocompletado y la navegación entre clases y métodos.

También he leído un artículo sobre lo bien que se integra con CLion: When CLion met biicode.

Mantenimiento de Software

Hace unos días en el trabajo tuve que rellenar un documento en el que debía indicar el tipo de mantenimiento que debía hacer y sólo tenía dos opciones: correctivo y adaptativo. Esto me extrañó, porque el cambio que yo quería hacer no terminaba de encajar en ninguno de los dos tipos.

Haciendo memoria sobre lo que había dado en mi primera asignatura de Ingeniería del Software, conseguí recordar tres tipos, pero me parecía que me faltaba un tipo.

Hoy por casualidad he encontrado mis apuntes de Ingeniería del Software y estos son los tipos de mantenimiento ordenados de más a menos porcentaje de tiempo dedicado.

Perfectivo: Actividades para mejorar o añadir nuevas funcionalidades requeridas por el usuario.
Adaptativo: Actividades para adaptar el sistema a los cambios en su entorno tecnológico (Hardware o software).
Correctivo: Corregir defectos en el hardware o software detectados por el usuario durante la explotación del sistema.
Preventivo: Actividades para facilitar el mantenimiento del futuro sistema.

Construir un proyecto C++ con Gradle

Introducción

La construcción, gestión de dependencias y distribución de mis proyectos es algo que cada vez me preocupa más, me gustaría encontrar una herramienta que unificara este proceso y fuese independiente del lenguaje, lo más parecido con lo que he trabajado ha sido SCons, Autotools, Ant, Maven y últimamente Gradle.

Llevo un tiempo haciendo algunas cosas con Gradle, pero siempre centrado en proyectos Java utilizándolo como reemplazo a Maven, porque que es más sencillo de usar y más rápido. También lo he utilizado en projectos Android y he sufrido la pareja Android Studio + Gradle en sus primeros matrimonios (porque yo he querido), actualmente está todo mucho más documentado y funciona muy bien.

Antes de nada hay que decir que la construcción de proyectos C/C++ y Objective-C con Gradle se encuentra en fase de incubación, aunque ya permite hacer algunas tareas avanzadas como:

  • Generación de múltiples artefactos dentro del mismo proyecto, esto es varias librerías o ejecutables.
  • Gestionar las dependencias entre estos artefactos (sin versiones).
  • Generar distintos “sabores” de aplicaciones, por ejemplo: podremos generar una aplicación “Community” y otra con más características habilitadas llamada “Enterprise”.
  • Permite generar binarios para distintas plataformas, esto depende de las cadenas de herramientas ( Toolchains ) que tengamos instaladas en la máquina de compilación.

Como decía todavía tiene limitaciones, aunque están trabajando en ello y si consiguen lo que tienen planeado dejaré Autotools (me arrepentiré de haber dicho esto).

Leer más…

Bases de Datos Embebidas Java: Comparación de Rendimiento

Bases de datos embebidas

Se trata de bases de datos que carecen de servidor, están inscrustadas en la propia aplicación y suelen estar almacenadas en ficheros locales. Esto último unido a que suelen tener un modo de funcionamiento en el que mantienen los datos en memoria hace que puedan tener un rendimiento muy alto.

Eso sí, este gran grado acoplamiento a la aplicación, hace que tengan peor rendimiento cuando se comparten entre varias aplicaciones debido a colisiones de acceso.

Otra ventaja es que no tenemos que encargarnos de mantener y gestionar un servidor de bases de datos.

Voy a hacer una comparativa de rendimiento entre 3 bases de datos embebidas ACID (transaccionales), las NoSQL no entran en esta comparativa que están en otra liga de rendimiento.

Leer más…

Distintas formas de serialización en Java: Comparación de rendimiento

Hace poco, he tenido que serializar/deserializar unos datos en Java, hacía mucho que no lo hacía en formato binario directamente (ultimamente he utilizado JSON o XML). Recordaba haber serializado objetos implementando el interfaz Serializable, pero había visto por Internet otra forma implementando el interfaz Externalizable, ¿cuál interfaz utilizo? Pues depende lo que quieras y necesites, como todo.

Leer más…