Volúmenes en Docker

Docker Volume

Antes de entrar en materia, debo comentar que este post se inicio por un problema que tuve alguna vez para montar un volumen NFS en un contenedor docker directamente, específicamente un volumen EFS (Elastic File System) de AWS. Debo reconocer que en un principio fue por desconocimiento, pero mas adelante me encontré con una serie de problemas por lo que opté finalmente por montar el volumen en la máquina host y luego montar el volumen en el contenedor.

Bueno, finalmente encontré una buena manera de solucionar este problema a través de los volúmenes de Docker.

Binding y Volúmenes

Debemos recordar que un contenedor es inmutable y efímero en si. Con esto quiero decir que un contenedor en si mismo no puede cambiar la imagen sobre la cual se esta ejecutando.  El contenedor mientras corre puede generar información modificar su filesystem cambiar todo lo que quiera mientras este en ejecución, pero una vez que este se termina y se remueve, toda la información escrita por el contenedor se pierde. Una vez que se inicia un nuevo contenedor con la misma imagen, nada de las modificaciones o de los datos generados se guardarán y por ende se perderán.

Binding

Para poder persistir los datos, se pueden montar directorios o archivos del filesystem de la máquina host, dentro del contenedor. Esto se llama binding. Al realizar un binding cualquier cambio que se haga dentro del contenedor, también podría verse reflejado por el archivo origen (host). Ejm.


docker run -d -v /opt/config/nginx.conf:/etc/nginx/conf.d/site.conf -p 80:80 nginx
#/opt/config/nginx.conf
# Archivo de configuración del SO host
#/etc/nginx/conf.d/site.conf
# Es el archivo que hace referencia a la máquina host, pero será usado en el container.
# En este caso será la configuración en Nginx.

Este mismo método se puede utilizar para montar directorios dentro del contenedor, de modo que los objetos o archivos generados por el contenedor puedan persistir en la máquina host y puedan ser usados en otro contenedor.

Un buen ejemplo de esto son las bases de datos. En general uno querría que los datos generados por el contenedor que la usa puedan ser persistidos y que no se pierdan en caso de tener que volver a crear el contenedor. Otra forma de persistir los datos son los volúmenes de docker

Volúmenes

Los volúmenes de docker son una forma especial de crear espacios para persistencia de archivos y la principal diferencia es que estos volúmenes pueden usar una serie de  drivers que permiten la integración de un volumen con algún tipo especifico de filesystem como por ejemplo Azure Filesystem, S3 de AWS, IPFS entre algunos. El driver por defecto es el local, que permite el uso del propio filesystem (ninguna novedad) como volumen.

Cada volumen es identificado con una etiqueta y no es necesario especificar un path en el SO host, ya que este volumen tiene una zona donde se crean todos los volúmenes dentro del filesystem del host. Una vez creado un volumen, puede ser montado dentro de un contenedor asociándolo a un directorio dentro del contenedor. Ejm.


docker run -d -v data:/var/lib/mysql -p 3306:3306 mysql

En el ejemplo anterior, se ha asociado un volumen llamado “data” en el path “/var/lib/mysql” dentro del contenedor. Esto quiere decir que todo los datos que se creen en ese path serán persistidos dentro de ese volumen.

¿ Pero, de dónde salió “data” ?

Bueno vamos a hacer un flashback al momento en que hemos definido tal volumen. Primero, debemos mencionar que utilizaremos el comando “volume” de docker para crear este volumen.

docker volume create data
data
docker volume ls
VOLUME NAME
local data

Con el comando anterior, hemos creado un volumen y el cual se llama “data”.  Con el comando “volume ls” podemos ver que se creo un volumen con el driver “local” que es el que viene por defecto.

Anteriormente hemos mencionado que el volumen se almacena localmente dentro del host en un repositorio local dentro del filesystem. La ruta en la que se guardan todos los volúmenes es “/var/lib/docker/volumes”. Para saber en que lugar exacto se ha creado el volumen se puede usar el comando “docker volume inspect {nombre_volumen}” el cual retornará un Json con la información.


docker volume inspect data
[ 
{ 
"CreatedAt": "2018-06-01T03:47:14Z",
 "Driver": "local",
 "Labels": {}, 
"Mountpoint": /var/lib/docker/volumes/data/_data", "Name": "data", 
"Options": {}, 
"Scope": "local"
 }

Dentro de la información de salida se puede notar la propiedad “Mountpoint” tiene la ruta donde localmente se guarda la información de ese volumen. Independiente del tipo de driver que se seleccione, siempre habrá una zona local donde se guardarán los datos dentro del filesystem local.

¿Qué pasa con el NFS?

Bueno, después de una gran explicación podrán entender como he llegado hasta el NFS… bueno, finalmente no hay un driver específico para NFS, pero descubrí que usando el driver local podemos montar un volumen NFS perfectamente, ya que este driver usa el sistema de archivos local como “mount” para trabajar con el sistema de archivos.

Precisamente uno de los parámetros que se necesita par montar un volumen a través del comando mount es “type”, el cual define que tipo de volumen queremos montar en nuestro SO. El driver local acepta varias opciones y entre ellas se encuentra el tipo de FS que se desea montar.  Para montar un volumen NFS debemos especificar el tipo y las opciones para montar. En nuestro caso, queremos montar un volumen de EFS de AWS, que no es mas que un volumen NFS con almacenamiento virtualmente infinito. Las opciones para montarlo son las mismas que un volumen NFS normal, mas algunas recomendaciones de AWS.

Las opciones para el driver local se deben pasar como un lista de tipo KEY=VALUE  o en una lista separadas por comas.

Nuestro caso sería:

tipo: NFS
Opciones:  rw,nfsvers=4.1,rsize=1048576,wsize=1048576,timeo=600
Host:  fs-9999999999.efs.us-east-2.amazonaws.com

las opciones del driver que usaremos son:

type: Tipo de FS
o:  Opciones para montar NFS
device: Path del volumen NFS

Finalmente el comando docker para crear un volumen NFS:

docker volume create --driver local \
--opt type=nfs \
--opt o=addr=fs-79854200.efs.us-east-2.amazonaws.com,rw,nfsvers=4.1,rsize=1048576,wsize=1048576,timeo=600 \
--opt device=:/ nfs

Con el comando anterior hemos creado un volumen de tipo NFS. En el momento de crearlo, este no monta el volumen, esto se hace efectivo cuando se ejecuta un contenedor. Una vez que se inicia con el comando run el contenedor realiza el mount del volumen NFS. Así:

docker run -d -v nfs:/mnt --name=nginx nginx

…Y Finalmente

Queda bastante claro que el manejo de volúmenes para persistencia de datos es algo que Docker ha abordado bastante bien. Sin embargo se debe dar una buena mirada en detalle en cada uno de estos drivers y así poder sacar provecho de estas funcionalidades, por que lo mas probable es que ya este resuelto.

En el caso particular que intentamos resolver en un principio los volúmenes se montaban sobre el host y luego se montaba como una carpeta (binding) el problema que derivaba de esto, es que si hay un cambio en el FS como or ejemplo el nombre de host, este se debía re configurar en la máquina host, lo que finalmente hacia mas complicado su automatización. Los contenedores no tienen por que saber cual finalmente el origen del FS y su configuración, esto se puede agregar directamente sobre los scripts que despliegan dejando el control de estas actividades a Docker.  En este caso usamos NFS por que AWS Elastic File System finalmente esta basado en el protocolo NFS Versión 4.

Finalmente lo mas recomendado en estos casos, es dejar que Docker se encargue de todas estas actividades ya que además todo esto facilita el trabajo de configuración y automatización al permitir el uso de scripts y archivos Dockerfile o Compose de manera transparente. Con esto se busca minimizar los puntos de fallas dejando que uno solo tenga el control, en este caso Docker.

 

ELK Stack

El stack ELK es una solución para la recolección, la centralización y la explotación de logs.
Estos últimos años, se ha vuelto la solución recomendada para los sistemas complejos y distribuidos que generan muchos logs.

Lleva al menos esos tres componentes:
E para ElasticSearch
L para Logstash
K para Kibana

ElasticSearch

Es una base de datos orientada a documentos (JSON) con schema opcional, tiene tres casos de uso principales :

  • Motor de búsqueda de texto
  • Análisis de logs
  • Business Intelligence

ElasticSearch es muy bueno para almacenar grandes volúmenes de documentos, indexarlos, y buscar textos en ellos, lo que le hace una herramienta muy adaptada para analizar las cantidades de archivos logs que generan las aplicaciones hoy en día.

Logstash

Es un procesador de datos, que funciona como un pipeline en tres partes :

  • Input
  • Filter
  • Output

Logstash viene con varios plugins de entrada y salida, lo que le permite colectar datos de varios fuentes y mandarlos a varias partes, los plugins de filtro, permiten tratar estos datos dándole sentido, como por ejemplo, convirtiendo una IP en una zona geográfica.

Kibana

Es un visualizador de datos, especialmente desarrollado para ElasticSearch.
Permite explorar los datos recolectados en ElasticSearch, construir varios tipos de gráficos, incluyendo mapas, y componer dashboards a partir de esos gráficos.

Elastic

Esos tres componentes son obras de la compañía Elastic (https://www.elastic.co/) y son Open Source :
– ElasticSearch (Java) https://github.com/elastic/elasticsearch
– Kibana (Javascript) https://github.com/elastic/kibana
– Logstash (Ruby) https://github.com/elastic/logstash

¿Como funcionan juntos?

La configuración de ElasticSearch y Kibana es bastante fácil, solo hay que indicarle a Kibana la URL de ElasticSearch, por ejemplo en una configuración docker-compose :

elastic:
image: docker.elastic.co/elasticsearch/elasticsearch:5.6.3
volumes:
- logs:/usr/share/elasticsearch/data
ports:
- 9200:9200
kibana:
image: docker.elastic.co/kibana/kibana:5.6.3
environment:
- ELASTICSEARCH_URL=http://elastic:9200
ports:
- 5601:5601
depends_on:
- elastic

Eso aplica también para el output de Logstash, solo hay que ocupar el plugin de salida para ElasticSearch y indicarle la URL:

output {
    elasticsearch {
        hosts => "elastic:9200"
    }
}

Lo más complicado es la recolección de los logs, ya que no hay dos aplicaciones que ocupen el mismo formato.

Modelo 1
Logstash puede colectar los logs directamente desde el filesystem, pero en este caso, se necesitará una instancia de logstash en cada nodo. Es el modelo 1, que se representa a continuación :

ELK1

Modelo 2
Un modelo más evolucionado consiste en ocupar Filebeat, es un agente liviano especialmente escrito para ese caso de uso, es capaz de comunicarse con Logstash de forma inteligente. También es de Elastic y es Open Source (escrito en Go), hace parte de una familia más grande llamada “Beats” que conlleva otros productos relacionados.
En este modelo, se puede tener una instancia única de Logstash con un agente Filebeat en cada nodo :

ELK2

Modelo 3
Finalmente, se pueden configurar los componentes en un tercer modelo, agregándole un message queue broker, lo que permite desacoplar la recolección y el tratamiento de los logs :

ELK3

De esa forma, se puede detener el ElasticSearch, por mantención por ejemplo, sin pérdida de logs, ya que se acumulan en la cola. También permite que no se colapse el sistema cuando llegan muchos logs al mismo tiempo; la cola hará un papel de amortiguador de carga, en ese caso. Como Message Broker, entre otros, se puede ocupar Redis.
Existen plugins de entrada y de salida para Logstash.

Salida:

output {
    redis {
        host => "redis"
        data_type => "list"
        key => "logstash"
    }
}

Entrada:

input {
    redis {
        host => "redis"
        data_type => "list"
        key => "logstash"
    }
}

Ejemplos de configuración de Filebeat

Este ejemplo es una configuración (filebeat.yml) que mueve los logs de un servidor HTTP Nginx a una instancia Logstash:

filebeat.prospectors:
- input_type: log
paths:
- /var/log/nginx/access.log
name: "nginx"
output.logstash:
hosts: ["logstash-collector:5044"]

Este segundo ejemplo colecta los logs de una aplicación corriendo en JBoss:

filebeat.prospectors:
- input_type: log
paths:
- /opt/jboss/jboss-eap-6.4/standalone/log/server.log
multiline:
pattern: '^[0-9]{2}/[0-9]{2}/[0-9]{4}'
negate: true
match: after
- input_type: log
paths:
- /opt/jboss/jboss-eap-6.4/standalone/log/app/*.log
multiline:
pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}'
negate: true
match: after
name: "hub"
output.logstash:
hosts: ["logstash-collector:5044"]

Acá se puede ver que Filebeat es capaz de leer desde diferentes archivos, y también, gracias al multiline pattern, de unir varias líneas del archivo en una sola entrada de log (lo que típicamente ocurre con los logs que producen las aplicaciones Java).

Ejemplos de configuración de Logstash

Este es un ejemplo de archivo de configuración en el modelo 3.
Corresponde a la primera instancia de Logstash que colecta los logs de los diferentes agentes Filebeat y los manda en Redis, sin tratamiento inmediato.

Archivo logstash.conf :

input {
    beats {
        port => 5044
    }
}

output {
    redis {
        host => "redis"
        data_type => "list"
        key => "logstash"
    }
}

Este segundo ejemplo, corresponde a la segunda instancia de Logstash, la que recupera los logs desde la cola Redis, los transforma en algo con sentido y los indexa en ElasticSearch :

input {
    redis {
        host => "redis"
        data_type => "list"
        key => "logstash"
    }
}

filter {
    if [beat][name] == "meta" {
        if [source] = ~"meta-search\.log$" {
            grok {
                match => {
                    "message" => "(\e?\[\d+m)+\[%{DATESTAMP:datetime}\] \[%{DATA:user}\] \[%{LOGLEVEL:level}] \[th:%{DATA:thread}\] \[%{GREEDYDATA:category}\]:%{INT:line} - %{GREEDYDATA:message}"
                }
                overwrite => ["message"]
            }
        } else {
            grok {
                match => {
                    "message" => "\[%{DATESTAMP:datetime}\] \[%{DATA:user}\] \[%{LOGLEVEL:level}] \[%{GREEDYDATA:category}\]:%{INT:line} - %{GREEDYDATA:message}"
                }
                overwrite => ["message"]
            }
        }
        date {
            match => ["datetime", "dd/MM/yyyy HH:mm:ss,SSS"]
            timezone => "America/Santiago"
            remove_field => ["datetime"]
        }
    } else if [beat][name] == "hub" {
        if [source] = ~"server\.log$" {
            grok {
                match => {
                    "message" => "%{DATESTAMP:datetime} %{LOGLEVEL:level} +\[%{GREEDYDATA:category}\] \(%{DATA:thread}\) %{GREEDYDATA:message}"
                }
                overwrite => ["message"]
            }
            date {
                match => ["datetime", "dd/MM/yyyy HH:mm:ss,SSS"]
                timezone => "America/Santiago"
                remove_field => ["datetime"]
            }
        } else {
            grok {
                match => {
                    "message" => "%{TIMESTAMP_ISO8601:datetime} %{LOGLEVEL:level} %{GREEDYDATA:category}:%{INT:line} - %{GREEDYDATA:message}"
                }
                overwrite => ["message"]
            }
            date {
                match => ["datetime", "yyyy-MM-dd HH:mm:ss"]
                timezone => "America/Santiago"
                remove_field => ["datetime"]
            }
        }
    } else if [beat][name] == "hazelcast" {
        grok {
            match => {
                "message" => "%{MONTH:month} %{MONTHDAY:day}, %{YEAR:year} %{TIME:time} (?<ampm>[AP]M) %{GREEDYDATA:category}\n%{LOGLEVEL:level}: \[%{GREEDYDATA:ip}\]:?(?<port>[0-9]{0,5}) \[%{GREEDYDATA:groupname}\] \[%{GREEDYDATA:version}\] ?%{GREEDYDATA:message}"
            }
            overwrite => ["message"]
        }
        mutate {
            add_field => {
                "datetime" => "%{year} %{month} %{day} %{time} %{ampm}"
            }
        }
        date {
            match => ["datetime", "yyyy MMM dd h:mm:ss a"]
            timezone => "America/Santiago"
            remove_field => ["year", "month", "day", "time", "ampm", "datetime"]
        }
    } else if [beat][name] == "elasticsearch" {
        grok {
            match => {
                "message" => "\[%{TIMESTAMP_ISO8601:datetime}\]\[%{LOGLEVEL:level} ?\]\[%{NOTSPACE:category} *\] \[%{WORD:nodename}\] %{GREEDYDATA:message}"
            }
            overwrite => ["message"]
        }
        date {
            match => ["datetime", "ISO8601"]
            timezone => "America/Santiago"
            remove_field => ["datetime"]
        }
    } else if [beat][name] == "nginx" {
        grok {
            match => {
                "message" => "\[%{HTTPDATE:time_local}\]\[%{IPORHOST:remote_addr}\]\[%{GREEDYDATA:request}\]\[%{INT:status}\]\[%{INT:body_bytes_sent}\]\[%{GREEDYDATA:http_user_agent}\]"
            }
        }
        date {
            match => ["time_local", "dd/MMM/YYYY:HH:mm:ss Z"]
            remove_field => ["time_local"]
        }
        geoip {
            source => "remote_addr"
        }
        useragent {
            source => "http_user_agent"
        }
    }
}

output {
    elasticsearch {
        hosts => "elastic:9200"
    }
}

Por supuesto, es acá donde ocurre toda la magia, por ejemplo el filtro geoip permite sacar informaciones geográficas de las IP que aparecen en los logs de Nginx, el filtro useragent sirve para formatear el http header user-agent en el tipo de navegador que ocupan los clientes.

El filtro grok, sin duda el más importante, permite separar los diferentes campos que lleva cada mensaje del log, gracias a expresiones regulares, que ocupan un formato bien particular.

Resultó muy útil ocupar este tester online: http://grokconstructor.appspot.com/do/match, que permite probar la sintaxis del pattern grok con extractos de su archivo log. Kibana en sus últimas versiones, también incluye un debugger de grok patterns.

El filtro date también es muy importante ya que permite asignar a cada entrada del log la fecha real en la cual sucedió el evento, sin eso, por defecto se le asignaría la fecha de entrada en Logstash que no es necesariamente pertinente.

En un próximo post, introduciremos el stack EFK, que es más adecuada para recolectar y centralizar los logs en un entorno basado en contenedores Docker.

Emular secuencias en MySQL

Ayer, debido a una necesidad para un proyecto en que estoy trabajando, me encontré nuevamente con la falta de sequences en MySQL. Generalmente este problema se resuelve utilizando campos con la característica autoincrement, pero aún así hay ocaciones en que eso no resuelve la problemática. Como me ocurrió a mi.

Buscando un poco en google me encontré con un enlace muy interesante en donde resuelven el problema de forma más que convincente. La probé y me funcionó a la perfección. En enlace en cuestión es :

https://www.convert-in.com/mysql-sequence.htm

La solución, muy distinta a lo acostumbrado que es tener un tabla con un único campo al que se le obtiene el Max Value y luego se incremente en “1”, consiste en (como el título de este post lo indica) emular una secuencia tal cual funciona en Oracle o en PostgreSQL.

Primero que todo, se debe crear una tabla para guardar todas nuestras sequences, de forma similar a como lo hace Oracle o Postgres (tras bambalinas).

CREATE TABLE `sequence` (
`name` varchar(100) NOT NULL,
`increment` int(11) NOT NULL DEFAULT 1,
`min_value` int(11) NOT NULL DEFAULT 1,
`max_value` bigint(20) NOT NULL DEFAULT 9223372036854775807,
`cur_value` bigint(20) DEFAULT 1,
`cycle` boolean NOT NULL DEFAULT FALSE,
PRIMARY KEY (`name`)
) ENGINE=MyISAM;

Puedes cambiar el ENGINE a InnoDB dependiendo de la versión de MySQL o MariaDB que estés utilizando.

Cada campo representa lo siguiente:

  • name: Nombre de la sequence que se está configurando.
  • increment: Valor incremental que se utilizará cada vez que use nuestra sequence.
  • min_value: Valor mínimo de la sequence.
  • max_value: Máximo valor que puede tomar la sequence.
  • cur_value: Valor inicial con que se configura la secuencia (start value ¿les suena?).
  • cycle: Permite determinar si llegado al max_value, la sequence comieza en su min_value nuevamente o no.

Luego se procede a crear nuestra primera sequence, que para este ejemplo será ejemplo_seq.

INSERT INTO sequence
( name, increment, min_value, max_value, cur_value)
VALUES
('ejemplo_seq', 1, 1, 100, 1);

Ahora toca generar la función nextval. Sí, nextval. Dijimos que emularíamos
una secuencia y esa es la forma correcta de trabajar con ellas.

Para ello se crea una function en MySQL que se encargará de consultar el valor a utilizar y de actualizar el valor. Tal cual funcionan las sequences.

DELIMITER $$
CREATE FUNCTION `nextval` (`seq_name` varchar(100))
RETURNS bigint NOT DETERMINISTIC
BEGIN
DECLARE cur_val bigint;

SELECT
cur_value INTO cur_val
FROM
sequence
WHERE
name = seq_name;

IF cur_val IS NOT NULL THEN
UPDATE
sequence
SET
cur_value = IF (
(cur_value + increment) > max_value OR (cur_value + increment) < min_value, IF ( cycle = TRUE, IF ( (cur_value + increment) > max_value,
min_value,
max_value
),
NULL
),
cur_value + increment
)
WHERE
name = seq_name;
END IF;
RETURN cur_val;
END;
$$

En este paso ya tenemos nuestra sequence creada y lista para funcionar por medio de la función nextval. Sólo queda probarla. Para esto, tal cual lo haríamos en un motor de base de datos que cuenta con esta característica, utilizaremos simplemente:

select nextval('ejemplo_seq');

Espero les sirva y lo encuentren tan útil como yo.

Saludos!

¿Máquina Virtual o Docker?

[:es]

Introducción

Ha transcurrido ya un buen tiempo, desde que empezamos a experimentar el desarrollo con Docker en nuestros proyectos, ya sea como plataforma de desarrollo o en la automatización de procesos.  Poco a poco, hemos ido comprendiendo la diferencia entre virtualización y “contenerización”, un ejemplo claro que aplica, es cuando intentamos levantar un ambiente de desarrollo con múltiples dependencias en un nuestras estaciones de trabajo, mientras que la virtualización conlleva a crear múltiples instancias (de acuerdo a necesidades), éstas generalmente exigen una cantidad importante de recursos que se traducen finalmente en un detrimento considerable en la experiencia del usuario y el desempeño de la máquina. Por su parte, la “contenerización” (docker) busca optimizar bastante bien los recursos limitados de nuestras máquinas, permitiendo que virtualmente se transformen en pequeños data center (guardando las proporciones) y se compartan recursos comunes entre servicios independientes (contenedores).

Pero ¿qué es Docker y en qué se diferencia de la virtualización?

Existen diferencias importantes: Una máquina virtual hace uso de un hypervisor para acceder al hardware físico del huésped, con el objeto de ejecutarse como si fuese una máquina física independiente; los recursos utilizados están previamente definidos en su configuración y son utilizados en su totalidad durante su ejecución, es decir, si se configuro la máquina con 4GB de RAM, ésta reservará la totalidad de memoria de la máquina huésped para su ejecución. Como consecuencia de ello, y una vea esté definida la configuración de la máquina virtual, solo queda administrar de buena manera las aplicaciones que se ejecutan dentro de ellas, y que éstas saquen el mejor provecho de los recursos asignados; si una aplicación toma menos recursos, éstos estarán disponibles en la máquina virtual pero no para la máquina huésped.

Hasta este punto, podemos deducir que las máquinas virtuales están mas orientadas a plataforma que hacia aplicación en si.

Docker por su parte, usa una forma muy diferente de interactuar con la maquina huésped.  Al igual que el software de virtualización posee una especie de hypervisor, que sirve de interfaz con el hardware subyacente, pero trabaja de forma muy distinta; lo que hace Docker es generar un wrapper, sobre el cual se ejecuta un único proceso, de ahí viene el nombre de contenedor, ya que éste “contiene” un micro ambiente de ejecución para una aplicación. Éste ambiente de ejecución, provee acceso a librerías de Sistema Operativo, a filesystem, a archivos de configuración y variables de ambiente. Cada contenedor se ejecuta sobre alguna distribución de SO Linux o Windows (ahora último), esta distribución es llamada imagen, y contiene toda la configuración necesaria para que una aplicación se ejecute tal como si la aplicación estuviera corriendo sobre una maquina virtual.

Contenedor

Un contenedor es una especie de wrapper que aísla el proceso a ejecutar de la máquina huésped. Este contenedor, encapsula una imagen de sistema operativo, posee acceso a filesystem y a recursos de hardware de la máquina huésped como si se tratara de un recurso independiente, pero que en ningún caso tiene acceso o conocimiento directo sobre el huésped.

La ventaja del contenedor es que éste solo ocupa los recursos que necesita la aplicación para ejecutarse, y a pesar de estar corriendo sobre una imagen del Sistema Operativo, no inicia otros procesos ajenos a la aplicación.

Docker provee acceso a la aplicación en ejecución, a las liberarías que éste pudiera necesitar, a montar ciertos volúmenes para realizar actividades de lectura y escritura (R/W) e incluso montar volúmenes desde el huésped para persistencia de datos.  En pocas palabras, Docker provee un ambiente de sistema operativo de solo lectura, para ejecutar un solo proceso que sin cargar nada mas que lo necesario, utiliza los recursos de Hardware que necesita dicho proceso.

¿No queda clara la diferencia?

Bueno, hasta acá probablemente sea un poco confuso, pero planteémonos el siguiente caso: Existe un programa que su ejecución toma hipotéticamente el 10% de CPU y 250M de Memoria. Por un lado, tenemos una máquina virtual con Linux Ubuntu con 4GB de Memoria y le dimos el 50% de nuestra CPU para su ejecución.  Si ejecutamos este programa sobre esta máquina virtual el proceso tomará el 10% de la CPU y 250MB de la memoria asignada para esta, pero para la maquina huésped no habrá ninguna diferencia por que ya hay 4GB y el 50% de la CPU reservado para esta maquina virtual.

Ahora bien, si ejecutamos a través de Docker, este proceso tomará 250MB del total de la memoria de la maquina huésped (quizá un poco mas por alguna que otra librería que necesite cargar para su ejecución) y el 10% de la memoria del huésped (también un poco más sumando la memoria que necesita Docker para correr).

Otras Diferencias

Como lema, Docker propone “Build, Ship and Run”, Construir, exportar ejecutar. Esto es sin duda, una de las ventajas mas considerables, ya que la habilidad de desarrollar una aplicación que se pueda exportar con todo su ambiente configurado e independiente, ahorra un montón de dolores de cabeza, sobre todo en ambientes donde conviven muchas aplicaciones, y cada una de ellas no debe afectar a las demás.

El lector seguramente podría pensar… Pero esto también se puede hacer con una maquina virtual!. Si, efectivamente se puede hacer, pero acá llegamos a otra ventaja importante, ya que el transporte de una máquina virtual es más complicado debido a su gran tamaño; Se necesita bastante tiempo y espacio para copiar una máquina en otro ambiente.  Docker, simplifica esto a nivel de archivo, es decir a partir de un archivo la máquina se construye bajando las imágenes de Sistema Operativo desde repositorios (Docker HUB) en Internet, por lo que solo basta este archivo plano para generar todo el ambiente.

Conclusión

Podemos concluir que la diferencia entre la tecnología de contenedores y la de virtualización es que la primera esta orientada a aplicación, mientras la segunda hacia la plataforma y por tanto, se puede ejecutar Docker container dentro de una maquina virtual perfectamente.

Docker provee un wrapper para hacer creer a la aplicación está sobre un ambiente específico, donde provee sistema operativo, recursos de Hardware y acceso a librerías. En cambio, la virtualización provee una ambiente completo, no solo de aplicación, sino también de los servicios que el sistema operativo tiene montados como entidades independientes.

Al momento de elegir, es importante considerar que ambas tecnologías tienen enfoques distintos, pero que también pueden ser complementarias, y que muchas veces las combinación de las dos es adecuada para los escenarios complejos a los que nos enfrentamos día a día.[:]

Monitorear contenedores con Portainer

[:es]Portainer es una aplicación web open source, que permite gestionar de forma muy fácil sus contenedores Docker.
Básicamente, permite hacer todo lo que permite el cliente docker, pero en una interfaz web bonita e intuitiva.

Instalar Portainer

docker pull portainer/portainer

Existe también una versión para Windows host (portainer/portainer:windows)

Levantar Portainer

docker run --name portainer -d \
    -p 9000:9000 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    portainer/portainer

El mapeo de puerto sirve para que sea accesible la interfaz web en el puerto 9000.
El primer mapeo de volúmen sirve para que Portainer tenga acceso al socket del Docker engine (ese mapeo no funcionará con Windows).
El segundo mapeo de volúmen sirve para guardar los datos internos de portainer.

Entrar a Portainer

Ir a http://localhost:9000 en su navegador preferido.

Al principio, Portainer le va pedir que entre una nueva contraseña:

Después le pedirá que inicie una sesión con la contraseña recién creada:

Al seguir le mostrara esta pantalla:

Seleccione “Manage the Docker instance where Portainer is running”:

Esto significa que Portainer va monitorear los contenedores que están en la misma máquina donde esta corriendo.
La otra opción permite conectarse a un Docker host distante.
Ojo: Eso no funciona si el host corre sobre Windows, en Windows el Docker engine ocupa un “named pipe” en lugar de un unix socket para exponer su API Rest y Portainer no lo esta soportando todavía.

Haga click en “Connect” y aparece el “Dashboard” de Portainer:

Vamos revisar las imágenes primero, haga click en “Images“:

Eso corresponde a lo que se puede obtener en un terminal escribiendo:

docker image ls

o

docker images

Sin embargo, tiene algunas ventajas:
– Se pueden clasificar las imágenes por tamaño o por tag, cuando en la consola siempre aparecen clasificadas por fecha.
– Una imagen con dos tags aparecerá en dos líneas en la consola, y en Portainer dos tags en una sola línea.
– Esa pantalla facilita mucho la supresión de imágenes, mediante los checkboxes y el botón “Remove”.
– La posibilidad de filtrar las imágenes por alguna parte de su tag es muy conveniente si se tienen muchas.

Haciendo click en el ID de una imagen se puede obtener más detalle:

En esta pantalla se pueden ver informaciones útiles como los puertos y los volúmenes que expone esa imagen.
También se pueden agregar y borrar los tags de la imagen.

Ahora vamos a revisar los contenedores, haciendo click en “Containers” en el menú lateral:

Aquí se pueden parar, borrar, reiniciar, pausar, matar los contenedores y levantar nuevos.
Como en la lista de imágenes, esa lista con posibilidad de filtrar y clasificar es muy conveniente.
También se puede ver el detalle de un contenedor haciendo click en su nombre:

Esa pantalla permite:
– Ver informaciones del contenedor (docker inspect)
– Guardar el contenedor en una imagen nueva (docker commit)
– Ver los logs del contenedor (docker logs)
– Ver las estadísticas del contenedor (docker stats)
– Logearse en el contenedor (docker exec)
– Desconectar el contenedor de una red (docker network disconnect)
– Y todas las operaciones común y corrientes como parar, pausar, matar o borrar el contenedor

Las pantallas “Volumes” y “Networks” permiten de la misma forma de visualizar, suprimir o agregar volúmenes y redes:

La pantalla “App Templates” facilita la creación de contenedores, propone varios modelos para configurar rápidamente Redis, MongoDB, Postgres, Nginx, ElasticSearch, entre otros.


Como ejemplo, vamos hacer click en el logo “WordPress”, ya, se muestra un pequeño formulario, pero ese formulario tiene un campo “MySql Database host” que pide seleccionar un contenedor, y no lo tengo, entonces vamos primero armar un MySql haciendo click en el logo “MySql”, acá solo me pide un nombre y la contraseña root, y eso lo puedo manejar:


Haga click en “Create” para finalizar, Portainer me redirige en la lista de contenedores y acá está mi contenedor MySql !
Puedo volver en el template WordPress y indicarle el “MySql Database host”:


Le indico también la contraseña que establecí para el MySql y hago click en “Create” para finalizar, lo que me redirige en la lista de contenedores:

Ya tengo mi WordPress corriendo, puedo acceder haciendo click en el número de puerto que está en la columna “Published Ports” donde dice 32769:80 en mi captura:

Así de fácil! 🙂

Conclusión

Portainer es una herramienta que permite manejar contenedores Docker con mucha facilidad, incluso en Windows y en máquinas distantes.
Es ideal para los que no son cómodos con la consola y permite hacer casi todo lo que se puede hacer con el docker-cli.
Les recomiendo probarlo.

Mas información en http://portainer.io/overview.html
[:en]Portainer is an open-source web application, which allows to handle easily your Docker containers.
In a nutshell, it let you do all the same than the Docker client, but with a nice and intuitive web interface.

Install Portainer

docker pull portainer/portainer

There is a Windows version too (portainer/portainer:windows)

Run Portainer

docker run --name portainer -d \
    -p 9000:9000 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    portainer/portainer

The port mapping is used to make the web interface accesible at port 9000.
The first volume mapping is used to give access Portainer on the socket for the docker engine (this won’t work on Windows).
The second volume mapping is used to keep Portainer internal data.

Access Portainer

Go to http://localhost:9000 in your favorite browser.

At start, Portainer will ask you for the admin password:

Then it will ask you this password to init the session:

Afterwards comes this screen:

Select “Manage the Docker instance where Portainer is running”:

This means that Portainer will monitor the containers of the host machine where he is running.
The other option allows connect on a remote Docker host.
Warning: This will not work on Windows host, because Windows use named pipe instead of unix socket to expose its Rest API and Portainer does not support it right now.

Click on “Connect” and the Dashboard of Portainer shows up:

We will check the images first: click on Images:

That´s what we could see in a terminal with the command:

docker image ls

or

docker images

But there are some advantages:
– You can sort images by size or tag, when in the terminal they always display sorted by date.
– An image with two tags would display two lines in the terminal, here two tags show up in the same line.
– This screen make it easy to delete images, using those checkboxes and the “Remove” button.
– Be able to filter images by part of their tag is really useful when you have a bunch.

Clicking on the ID of an image gives more details on it:

Here you can see useful data like ports and volumes that the image exposes.
You can add and delete tags for the image too.

Now we will see the containers, clicking on Containers of the lateral menu:

Here you can stop, remove, restart, pause kill container and run new ones.
As with images, this list view with filter and sort abilities is very convenient.
Y like for images you can see the details of a container clicking on its name:

On this screen you can:
– See informations of the container (docker inspect)
– Save the container into a new image (docker commit)
– See the logs of the container (docker logs)
– See the statistics of the contenedor (docker stats)
– Log in the contenedor (docker exec), but with bash or sh only.
– Disconnect the container out of a network (docker network disconnect)
– Y all commons operations like stop, pause, unpause, kill, remove the container

The screens “Volumes” and “Networks” allow to list, delete or add volumes and networks:

The “App Templates” screen make it easy to create containers, it purposes some templates to quickly config Redis, MongoDB, Postgres, Nginx, ElasticSearch, among others.


As an example, we will click on the “WordPress” logo, yeah, a little form shows up, but that form has a “MySql Database host” field asking to select a container, and I don’t have one, so we will first setup a MySql clicking on the “MySql” logo, here it justs asks for a name and the root password, that, I can handle:


Click on “Create” to finalize, Portainer redirects to the containers list and here is MySql !
Now I can go back to the WordPress template and give it the “MySql Database host” and the MySql password:


Click on “Create” to finalize, and you’ll be redirected to the containers list:

I got my WordPress running, and I can access it clicking on the port number which is in the “Published Ports” column where it says 32769:80 in my screenshot:

That was so easy ! 🙂

Conclusion

Portainer is a nice tool that allows to handle Docker containers really intuitively, on Windows included and on remote servers.
It is ideal for those who are not comfortable with the terminal command line and it allows to do almost all the things you do with the docker-cli.
I recommend you try it

More information on http://portainer.io/overview.html
[:]

Soapui mocks sobre https con apache y centOS 7 desde cero

[:es]Continuando con la serie de servicios mocks con soap-ui, nuestro próximo paso es agregar conexiones seguras https con un certificado gratis emitido por letsencrypt.

Partimos nuestro procedimiento desde un CentOS 6.8, pero a pesar que seguimos un procedimiento similar al que se va a describir, el certificado siempre quedaba inválido, indicando que “Common name” no correspondía al nombre del host (Lo cual era cierto, hablando del archivo /etc/hosts, pero finalmente el certificado que se deseaba emitir correspondía a un subdominio con un registro DNS tipo A, apuntando a la IP de la máquina), considerando ésto, y sumado a las vulnerabildiades con esa distribución al POODLE attack, migramos a CentOS 7.1.1503 (Core).

Bueno, manos a la obra. Como lo indica el post, es desde cero, así que vamos a suponer que tenemos nuestro SO recién instalado con cuenta root. Lo primero será entonces crear una cuenta de usuario con permisos de sudo.

Sistema Operativo

# Instalar sudo para agregar el usuario a los sudoers de la máquina
$ yum install sudo
# Crear el usuario y asignarle un password, así mismo, agregarlo al grupo wheel, 
# que en CentOS por defecto tiene privilegios de sudo
$ adduser username
$ passwd username
$ usermod -aG wheel username
# Instalar crontab para ejecutar la renovación programada del certificado
$sudo yum -y install cronie
# Subir el demonio de crontab:
$ sudo service crond start

De ahora en adelante, conectarse (ssh) con ese usuario, y ejecutar los comandos que requieren privilegios con sudo

Apache

Para la instalación y configuración de apache, se siguió el siguiente procedimiento:

# Instalación de apache
$ sudo yum install httpd
# Instalación de mod_proxy_html para el proxy reverso que va a ir desde apache a soapui
$ sudo yum install mod_proxy_html
# Instalación de mod_ssl que servirá para levantar apache en modo seguro (puerto 443)
$ sudo yum -y install mod_ssl
# Habilitar apache como servicio cada vez que suba
$ sudo systemctl enable httpd.service

Es importante activar mod_ssl, en caso contrario, un error común obtenido con el certbot de letsencrypt será:

Failed authorization procedure. subdomain.domain (tls-sni-01): urn:acme:error:connection :: The server could not connect to the client to verify the domain :: Failed to connect to XXX.YYY.ZZZ.OOO:443 for TLS-SNI-01 challenge

Activar el módulo proxy

# Hacer una copia de la configuración por defecto
$ cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.bk
$ sudo vi /etc/httpd/conf/httpd.conf

Buscar por la posición adecuada para agregar las directivas LoadModule y adicionar:

LoadModule proxy_html_module modules/mod_proxy_html.so

Agregar un nuevo archivo de virtual host

$ vi /etc/httpd/conf.d/vhost.conf 

Si hay un firewall activo, permitir el tráfico a apache por el 443 así:

firewall-cmd --add-service=https --permanent
firewall-cmd --reload

En este punto, se asume que la instalación de soap-ui está completa y se ha logrado subir el mockservicerunner de manera exitosa, como recomendación repasar este post y dependiendo en que puerto y contexto esté corriendo la instancia de soapui, agregar:

<VirtualHost *:80>
     ServerName subdomain.domain.com
     ProxyPass /context-path http://0.0.0.0:9090/context-path
     ProxyPassReverse /context-path http://0.0.0.0:9090/context-path
     ErrorLog /var/www/html/my-folder/logs/error.log
     CustomLog /var/www/html/my-folder/logs/access.log combined
</VirtualHost>
# Crear la carpeta de logs referida anteriormente
$ sudo mkdir -p /var/www/html/my-folder/logs    
# Reiniciar apache
$ sudo service httpd restart
# O también, así:
$ sudo systemctl restart httpd.service

Probar que la configuración tenga efecto, para ello se recomienda hacer un llamado a uno de los servicios que se tenga en los mock services, ya no usando el puerto del mock, sino el 80, donde apache haría la magia del proxy reverso enviando la petición. Un ejemplo de llamada exitosa con postman sería:

Certificado

Nuestro último paso es la instalación del certificado usando el servicio gratis de letsencrypt (Usando la configuración Apache + CentOS 7). Recordar que la emisión de este tipo de certificados se hace hasta por 3 meses y luego expirará, por lo que es necesario ir renovándolo.

Lo primero es habilitar el repositorio EPEL (Extra Packages for Enterprise Linux) que para usuarios de centOS se hace con:

$ sudo yum install epel-release

Luego instalar el Certbot de apache de letsencrypt

$ sudo yum install python-certbot-apache

Ahora, permitiendo que bot haga los cambios de forma autónoma (el otro modo es usando el modificador certonly)

$ certbot --apache

Se ejecutará un asistente que guía el proceso de creación:

  1. Los logs serán almacenados en una ruta como /var/log/letsencrypt/letsencrypt.log,
  2. Se confirma los nombres para emitir el certificado (datos extraídos de vhost.conf)
  3. Se obtiene el certificado y se hace la validación del mismo (tls-sni-01).
  4. Se genera la clave de tamaño 2048 bits, y se almacena en una ruta similar a /etc/letsencrypt/keys/0000_key-certbot.pem
  5. Se genera el CSR (Certificate Signing request) y se almacena en /etc/letsencrypt/csr/0000_csr-certbot.pem
  6. Se habilita apache para operar en el puerto 443 agregando un nuevo virtual host SSL en la carpeta
    /etc/httpd/conf.d/vhost-ssl.conf (Se agrega el sufijo -ssl al archivo original)
  7. Se consulta si HTTPS será opcional, Easy permitirá accesso HTTP como HTTPS, mientras que Secure no permitirá HTTP
  8. Felicidades! El certificado fue almacenado en /etc/letsencrypt/live/subdomain.domain.com/fullchain.pem

Revisemos el archivo generado en la configuración de apache, donde se puede validar en donde quedan finalmente los certificados, la cadena y la llave privada.

<IfModule mod_ssl.c>
<VirtualHost *:443>
     ServerName subdomain.domain.com
     ProxyPass /context-path http://0.0.0.0:9090/context-path
     ProxyPassReverse /context-path http://0.0.0.0:9090/context-path
     ErrorLog /var/www/html/sodexo/logs/error.log
     CustomLog /var/www/html/my-folder/logs/access.log combined
SSLCertificateFile /etc/letsencrypt/live/subdomain.domain.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/subdomain.domain.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/live/subdomain.domain.com/chain.pem
</VirtualHost>
</IfModule>

Siempre se recomienda validar la validez del certificado usando un servicio que provee Qualys SSL Labs, usando la siguiente URL. Si todo está ok, y el rating del certificado es trusted, y ojalá con rating A, estamos listos para que el certificado sea aceptado por browsers y otros clientes.

Reiniciar apache de nuevo y validar que los servicios respondan ahora correctamente (con postman):

$ sudo service httpd restart


Respuesta esperada: 200 OK

Por último, programar la entrada de renovación del certificado para evitar que se invalide luego de los 3 meses, para ello:

# Validar que el demonio este arriba 
$ pgrep cron 
# La salida anterior no debería ser blanca, sino el PID del demonio cron<<</span
$ sudo crontab -e

Agregar la siguiente línea, que se ejecutará todos los lunes a las 2:30 am y si el certificado está por expirar, lo renovará automáticamente.

30 2 * * 1 certbot renew >> /var/log/le-renew.log

Cuando el certificado aún tiene la suficiente vigencia, en el log sale algo como:

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/vhost.conf
-------------------------------------------------------------------------------
The following certs are not due for renewal yet:
  /etc/letsencrypt/live/subdomain.domain.com/fullchain.pem (skipped)
No renewals were attempted.

Referencias

[feather_share show=”twitter, facebook, linkedin” hide=”reddit, pinterest, tumblr, google_plus, mail” size=”24″][:]

Docker on Windows

[:es]Es importante hacer la diferencia entre Docker for Windows y Docker on Windows.

  • Docker for Windows permite usar contenedores Linux en una máquina Windows 10.
  • Docker on Windows permite usar contenedores Windows en una máquina Windows Server 2016.

En este post, voy a describir cómo se puede usar Docker on Windows

Instalar Docker

Los contenedores Windows funcionan únicamente en Windows Server 2016, para hacer pruebas se puede conseguir una versión de evaluación de 180 días en la web de Microsoft.

Antes de activar el servicio docker, se necesita descargar las últimas actualizaciones de Windows
2016, luego en PowerShell (y siendo administrador), entrar los comandos siguientes :

Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name DockerMsftProvider -Force
Install-Package -Name docker -ProviderName DockerMsftProvider -Force
Restart-Computer -Force

(Windows también tiene su package manager ahora)

Como el último comando lo deja suponer, se necesitará reiniciar la máquina.

Esto instala el servicio docker (docker daemon) y el cliente docker.

Descargar las imágenes de base

Microsoft provee dos imágenes que se puedan usar como base para un contenedor Windows:

  • microsoft/windowsservercore
  • microsoft/nanoserver

La primera lleva un servidor Windows con todas sus funcionalidades, la segunda es una versión mucho más liviana, y lógicamente con menos funcionalidades.

La versión completa ocupa casi 10 Gb, así que dura bastante la descarga.

docker pull microsoft/windowsservercore

La versión NanoServer ocupa casi 1 Gb, lo que la hace mucho más conveniente para trasladarla o para un uso en el cloud por ejemplo, así que cuando se puede se debería preferir esa.

docker pull microsoft/nanoserver

(Acá se puede ver que docker se puede levantar/bajar usando net start/stop como cualquier servicio, y que el cliente docker se puede usar como en Linux)

Construir una imagen con una máquina virtual Java

Para construir una primera imagen vamos a descargar una JDK y agregarla en una imagen de base NanoServer.

En una carpeta dedicada, se copia la JDK y se crea un archivo Dockerfile (sin extensión), con el contenido siguiente:

FROM microsoft/nanoserver

MAINTAINER Guillaume Mayer <gmayer@arkho.tech>

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]

COPY jdk1.8.0_112 c:/jdk1.8.0_112

ENV JAVA_HOME c:\\jdk1.8.0_112

RUN $newPath = ('{0};{1}\bin' -f $env:PATH, $env:JAVA_HOME); \
 Write-Host ('Updating PATH: {0}' -f $newPath); \
 setx /M PATH $newPath;

  • Esa imagen ocupa la de Microsoft NanoServer como base
  • Se define PowerShell como shell por defecto
  • Se copia la JDK en la raíz (C:)
  • Se define la variable de entorno JAVA_HOME
  • Se agrega JAVA_HOME\bin al PATH

Hay que ser cuidadoso con las rutas con anti-slash (\) porque también es el carácter de escape de los dockerfiles, por eso se deben doblar.

Para construir esta imagen, se puede llamar el comando siguiente (parado en la carpeta que lleva el Dockerfile):

docker build -t java:8-nanoserver .

La option -t permite dar un nombre (tag) a la imagen.

Se puede probar la imagen con el comando siguiente:

docker run -it --rm java:8-nanoserver

(Las opciones -it permiten simular un terminal interactivo, la opción –rm le dice a docker de borrar el contenedor cuando
no se usa mas)

(Se puede ver que la carpeta del JDK fue copiada en la raiz de C: , que la variable de entorno apunta correctamente y que java funciona)

 Construir una imagen con una aplicación

Ahora que tenemos una imagen con Java funcionando, vamos crear otra que haga algo, un hello world en Spring Boot por ejemplo.

En este caso, es una aplicación que funciona con Jetty en modo standalone, y que responde “Hello, world!” cuando se llama al URL http://localhost:8080/hello/world

src/main/java/hello/HelloController.java :

package hello;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;

@RestController
public class HelloController {

  @RequestMapping("/hello/{name}")
  String hello(@PathVariable String name) {
  return "Hello, " + name + "!";
  }
}

src/main/java/hello/Application.java :

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(applicationClass, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(applicationClass);
    }

    private static Class applicationClass = Application.class;
}

Archivo pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>hello-world</artifactId>
<version>0.1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>1.4.3.RELEASE</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

En otra carpeta dedicada, copiamos el jar y creemos otro Dockerfile con este contenido:


FROM java:8-nanoserver

MAINTAINER Guillaume Mayer <gmayer@arkho.tech>

COPY hello-world-0.1.0.jar c:/hello-world-0.1.0.jar

EXPOSE 8080

ENTRYPOINT java -jar hello-world-0.1.0.jar
  • Esa imagen ocupa la imagen con Java 8 previamente construida
  • Se copia el archivo JAR
  • Se expone el puerto 8080 (donde corre la app Spring Boot por defecto)
  • Se define el comando que se ejecutara al arrancar el contenedor (levantar la app Spring Boot)

Para construir la imagen :

docker build -t hello .

Para arrancar el contenedor :

docker run --name hello -d -p 8080:8080 hello

Acceder al contenedor

En Windows, los contenedores corren por defecto en un network de tipo “nat”.

Cada uno tiene su IP y no se puede acceder al contenedor directamente usando localhost como en Linux.

Para averiguar la dirección IP del contenedor se debe usar el comando docker inspect:

docker inspect hello

o

docker inspect -f {{.NetworkSettings.Networks.nat.IPAddress}} hello

Y se puede comprobar que la aplicación funciona :

 
[feather_share show=”twitter, facebook, linkedin” hide=”reddit, pinterest, tumblr, google_plus, mail” size=”24″]
 

[:]

Metodologías GIT – Centralized Workflow

[:es]Este es el primero de una serie de artículos que haré sobre las distintas metodologías que al momento de redactar estas líneas son ampliamente conocidas y discutidas en el mundo T.I para el trabajo con el también conocido control de versiones GIT.

Si bien ni este documento ni los siguientes pretenden ser un tutorial de GIT, de igual forma se abarcarán algunos tópicos y comandos básicos (o avanzados dependiendo de lo que se requiera mostrar)  como complemento de la metodología tratada.

 

Centralized Workflow

Se trata del workflow o estrategia de trabajo más simple que veremos en esta serie. Desde mi punto de vista, es el paso lógico de adaptación/transición si es que vienes de un sistema de control de versiones como SVN y pretendes comenzar a utilizar GIT.

Esta metodología consiste en mantener un único repositorio y una única rama sobre la cual los desarrolladores van subiendo sus cambios.

Pese a ser tan simple y tener ciertas similitudes, esta metodología aún nos permite visualizar algunas ventajas sobre SVN, por ejemplo, cada desarrollador puede trabajar y hacer commits en su copia local sin afectar el trabajo de los demás hasta el momento de hacer push sobre el branch remoto.

 

Workflow-centralized-image

 

Operación

Tal como mencioné anteriormente, esta metodología trabaja con un único branch Master, similar al trunk si hacemos la analogía con SVN. La primera gran diferencia, es que cada desarrollador puede generar cuantos commits estime necesarios en su rama local hasta llegar a una funcionalidad completa, para sólo entonces hacer un “push” al repositorio. Todo esto sin afectar el trabajo de los demás, ya que todos los cambios pequeños se hicieron localmente.

En general, los pasos para trabajar con esta metodología (o workflow) son los siguientes:

  1. Cada desarrollador realiza un clone de la única rama del proyecto, en este caso Master,  en su working copy para trabajar de forma local.
  2. Un desarrollador crea, modifica o elimina código con la finalidad de completar una historia de usuario, suponiendo que estamos trabajando con Scrum. Para esto puede generar tantos commits en su branch local como estime conveniente, como ya se dijo, sin afectar el trabajo de los demás miembros del equipo.
  3. Una vez que el desarrollador tiene código listo para liberar, hace un último commit sobre su rama local y posteriormente realiza el push a la rama Master remota, quedando de esta forma el código sincronizado y listo para que los demás miembros del team puedan actualizar su código fuente también.

 

Ejemplo

Con el fin de aterrizar un poco más esta estrategia de trabajo, veremos un ejemplo con un proyecto Libros  en el que está trabajando un equipo de tres desarrolladores: Marcelo, Javier y Jimmy.

Para tales efectos, tenemos entonces un repositorio único cuya rama principal Master será la que cada miembro del equipo debe clonar para tener una copia en su equipo.

[code]

$ git clone https://gitlab.com/company/mi-proyecto-prueba

[/code]

Una vez que tenemos el código en nuestro equipo local, cada desarrollador del equipo comienza a trabajar.

Jimmy será el primer miembro del equipo que haga un push sobre el repositorio remoto. No encontrará mayores problemas ya que dicho repositorio se encontrará exactamente igual al momento de hacer el clone. Por esto, será un push absolutamente limpio.

[code]

$git commit -m "últimas modificaciones antes de hacer push"

$git push master origin

[/code]

Cuando el segundo miembro del equipo, que esta vez será Marcelo, llegue a la fase de hacer push de SU código al repositorio, podría encontrarse con el primer problema. Jimmy en su trabajo de implementación de la funcionalidad “búsqueda de libros” ha debido agregar métodos a la clase Libro.

Marcelo por su parte ha debido crear también métodos y atributos que permiten hacer una reserva, para lo cual realizó cambios también en la clase Libro.

[code]

$git commit -m "Reserva de libros finalizada"

$git push origin master

[/code]

Si tanto Jimmy como Marcelo tuvieron la fortuna de no modificar el mismo código dentro de la clase Libro, el push de Marcelo se llevará a cabo sin mayores problemas. Pero si la suerte no nos acompaña, y esto comenzará a suceder en la medida que los proyectos vayan avanzando y aumentando su complejidad  y el número de miembros del equipo vaya en aumento, Marcelo deberá realizar el proceso de merge del código de ambos desarrolladores para poder llevar a cabo, finalmente, el push del código ya sincronizado a la rama Master.

Ahora es el turno de Javier. Debe actualizar el código que recién acaba de terminar y, deberá hacer el mismo proceso que Marcelo para poder dejar el código del proyecto totalmente sincronizado para todos los miembros de equipo.

Resolución de conflictos

En esta metodología el problema más común que se encontrarán es la constante desactualización de código en las ramas locales, más aún cuando los equipos que modifican código de forma transversal en el proyecto. Esto debido a que la única rama remota está sufriendo constantes updates con cada push que se haga sobre ella.

Será necesario entonces contar con una buena herramienta de resolución de conflictos, como el p4merge u otra que sea de su agrado.

[code]

$git commit -m "Reserva de libros finalizada"

$git push origin master
[/code]

CONFLICT (modify/delete): README.md deleted in HEAD and modified in branch-b. Version branch-b of README.md left in tree.
Automatic merge failed; fix conflicts and then commit the result.

[code]
$git merge tool

[/code]

Para resolver el conflicto, hacer el commit y hacer nuevamente el push del código al repositorio remoto.

 

Ventajas

Esta metodología es especialmente útil para equipos que están cambiando de un sistema de control de versiones como SVN a Git, ya que su línea de trabajo es muy similar. No obstante las diferencias que ya se han mencionado, la utilización de esta metodología permite comprender en la marcha de un proyecto, la filosofía de Git y los comandos utilizados para la operación diaria.

 

Desventajas

La principal desventaja de esta metodología es que, como se está trabajando con un branch local que hace tracking del branch remoto, cada uno de los commits locales que un desarrollador hace pasan a formar parte también del branch remoto, dejando una rama principal (y única) muy sucia y muy compleja de analizar su historia.

 

Recomendación Experta

Como ya he mencionado en más de una oportunidad en este artículo, esta metodología me parece un excelente primer paso para migrarse a GIT ya que se está orientada a comprender el uso básico de este control de versiones. Creo importante que el proyecto que se utilice como proceso de adaptación no corresponda a un desarrollo pequeño, sino más bien un proyecto de envergadura media, de esta forma el equipo se verá enfrentado a problemas de baja dificultad como resolución de conflictos, creación de stashes, utilización de Tags, etc, que le permitan en un siguiente proyecto abordar otras metodologías con características más avanzadas de GIT.

Como último consejo, sería conveniente sobre todo al principio del proyecto, que cada desarrollador tome líneas lo más independientes posibles dentro del desarrollo, para de estar forma comprender primero los comandos a utilizar y luego, comenzar a trabajar con situaciones más complejas como los merges y rebases.

 [:]

Ejecutando SOAP-UI mockservicerunner en modo servidor (Linux)

[:es]La ejecución de servicios mock (simulados) es una de las características mas potentes de SoapUI, hemos explorado en este post, un conjunto de tips que pueden ser útiles en la generación de los mismos. El propósito de esta publicación es compartir la forma cómo hemos automatizado la ejecución de servicios mock en modo servidor.

SoapUI cuenta con un script llamado mockservicerunner.sh el cual permite la ejecución de mock services previamente configurados en un proyecto mediante línea de comandos, lo cual se hace tremendamente útil para disponibilizar un conjunto de mocks en modo servidor, y acceder a estos mismos mediante los endpoints configurados previamente. El uso de la shell es como sigue:

usage: mockservicerunner [options] <soapui-project-file>
-a <arg> Sets the url path to listen on
-b Turns off blocking read for termination
-D <arg> Sets system property with name=value
-G <arg> Sets global property with name=value
-m <arg> Specified the name of the MockService to run
-p <arg> Sets the local port to listen on
-P <arg> Sets or overrides project property with name=value
-s <arg> Sets the soapui-settings.xml file to use
-S Saves the project after running the mockService(s)
-v <arg> Sets password for soapui-settings.xml file
-x <arg> Sets project password for decryption if project is encrypted

Generalmente nuestra configuración es más simple y solo requiere el nombre del mock service, el puerto (sobre-escribiendo la configuración por defecto de los endpoints definidos en el projecto) y el proyecto xml:

mockservicerunner.sh -p 9090 -m "REST MockServices" $PROJECT_HOME/my-project.xml"

Al ejecutar la línea de comandos, por consola se iniciaran los servicios hasta que se presione una tecla para terminar:
23:45:24,638 INFO [SoapUIMockServiceRunner] Started 1 runner
Press any key to terminate...

Sin embargo, este escenario no es muy práctico para el modo en que queremos levantar nuestro servicio, por tanto hemos creado un script para iniciar el mockservicerunner en segundo plano (con nohup), el cual permite iniciar, validar el estado actual, detener y reiniciar el servidor. Considerando que los procesos hijos que levanta SoapUI para la ejecución del runner, cambiaban de Padre (PPID) cuando intentábamos matarlos (con kill -9) fue necesario adaptar un proceso de detención dinámico basado en el nombre de los procesos hijos.

Pre-requsitos básicos (no mayor detalle):

  1. Java instalado
  2. Descargar SoapUI para linux
  3. Descargar el proyecto soapui en el servidor que ejecutará el mockservicerunner

Una vez tengas la herramienta desempaquetada, usaremos las herramientas de línea de commandos para ejectuar el modo servidor. El script que hacemos referencia, lo puedes encontrar en gist de Github acá. Su ejecución es simple:
Usage: ./soapui_mockservicerunner_script.sh { start | stop | restart | status }

Antes de ejecutar, asegurarse de completar las variables requeridas adecuadas:
[code language=”bash”]
# Variables to edit according to your environment
SOAPUI_HOME=$HOME/SoapUI-5.2.1
PROJECT_HOME=$HOME/somefolder/soap-ui
USR=myuser
[/code]

Detalles del script lo puedes encontrar en el gist, para nuestro propósito, sabemos que el servicio mock está ejecutándose en un servidor, con un puerto personalizado y siendo accedido mediante URLs que pueden ser automatizada en una ejecución de tests. Grandioso no? No olvides de probarlo con Postman.

[feather_share show=”twitter, facebook, linkedin” hide=”reddit, pinterest, tumblr, google_plus, mail” size=”24″]

[:]

Mocking REST services con SOAPUI y Postman[:en]Mocking servicios REST con SOAPUI en modo servidor

[:es]El prototipado de servicios es una potente herramienta para acelerar el desarrollo de software, más aún cuando existen dependencias de terceros o cuando simplemente las líneas de tiempo no se cruzan y la gestión de pre-requisitos se convierten en caos y búsqueda de culpables.

La premisa a postular acá, es que sin importar si los servicios (dependencias) con que me estoy integrando están desarrollados  o no, siempre debe existir una capa de “simulación controlada”, que además de servir de base para las pruebas automatizadas, permita validar condiciones de borde, reproduciéndolas fácilmente de manera controlada.

Existen un número importante de herramientas/frameworks que permiten hacer el llamado “mocking”, en nuestra experiencia, las más útiles son las menos invasivas, que al cambiar algo mínimo (como una URL o un parámetro de ambiente), ejecuten un plan simulado o un escenario real.

Al trabajar con servicios web (SOAP/REST) una de las herramientas más versátiles para las pruebas funcionales es SOAPUI, que en su versión OpenSource es suficiente para el objetivo que queremos lograr. Este POST incluye algunas de las prácticas más comunes que seguimos para su implementación, así como algunas de sus ventajas entre las que es importante destacar:

  1. Funciona tanto para un modelo simulado como para conectarse a servicios reales.
  2. El archivo de proyecto es un xml que puede ser versionado fácilmente. Además que el principio asociado a esto es que su edición sea colaborativa.
  3. Se pueden incluir conjuntos de test para un modelo automatizado.
  4. Iniciar el Mock Server puede hacerse mediante la interfaz de usuario como por línea de comandos (ver este post).
  5. El scripting dentro de los mocking es Groovy.

Algunas Prácticas (Sin importar el orden)

  1. Definir múltiples endpoints de acuerdo a los ambientes que se desean probar (show service viewer). Si tienes un nombre de contexto mucho mejor. service-endpoints
  2. Agregar los recursos que se puedan agrupar a recursos base.
    recursos
  3. Generar el Mock Service después de tener los recursos ya creados, así se crearán todos los mockings de una sola vez.
    mockservices
  4. Editar los headers de las respuestas de acuerdo al caso, en muchas ocasiones se requiere prevenir problemas con CORS (Cross-origin resource sharing) para consumir los servicios independiente al origen. Usar los siguientes con discresión (recordando que propósito principal aqui son las pruebas. No usar en servicios reales).
    headers
  5. Definir diferentes respuestas asociadas a un servicio para seleccionar la adecuada respecto a una entrada.
    response
  6. Puedes usar Scripting (Groovy) para determinar a cual respuesta se debe redireccionar. Aquí un ejemplo que recibe y retorna un json, se toman decisiones basado en el contenido para elegir una respuesta predefinida, la que también puede ser modificada dinámicamente con en el caso 3 de nuestro if.

    [code language=”groovy” highlight=”5,9,18″]
    def slurper = new groovy.json.JsonSlurper()
    def jsonReq = slurper.parseText(mockRequest.requestContent) // Entrada es un json
    log.info jsonReq.userIde
    if (jsonReq.userId == "1") {
    return "sessionNOK" // Retorna una de las respuestas definidas en el mockResponses
    }
    if (jsonReq.userId == "3") {
    sleep (8000) // Lo uso para generar timeouts de la aplicación a propósito
    return "sessionOK"
    }
    else if (jsonReq.userId == "4") { // Caso donde manipulo la salida basado en un parámetro como fecha
    use( groovy.time.TimeCategory ) { //Utilidades de fecha en groovy
    currentDate = new Date()
    after30Mins = currentDate + 5.minutes // Sumando 5m a la fecha actual para retornarlo como parámetro
    }
    dateExpiring = after30Mins.format("yyyy-MM-dd’T’HH:mm:ss’Z’", TimeZone.getTimeZone("UTC")) //Formato ISO 8601
    requestContext.responseMessage = ‘{"id": "0","desc": "OK","result":{"token":"ABCDE123456789", "dateEnd":"’+dateExpiring+’"}}’
    return "expiring" // redirige a este tipo de respuesta, que a su vez usa ${responseMessage} como salida.
    }
    else {
    return "sessionOK"
    }
    [/code]

    Expiring response:
    expiring

  7. Considerando que el archivo de proyecto es xml codificado en UTF-8 hace sentido que los datos que usemos se representen con la codificación adecuada y no tengamos conflicto cuando el servidor mock está arriba. Adicionalmente se recomienda agregar explicitamente el header Accept-Charset: utf-8. Ejemplo de mensaje json codificado utf-8 con acentos.

    [code language=”javascript”]
    { "resultado":"Buenos d\u00EDas UTF8" }
    [/code]

  8. Usar los http codes para emular problemas en las respuestas de los servicios resulta bastante útil para condiciones de borde. Ejm
    401
  9. Una vez tengas los servicios simulados, basta con iniciar el servicio desde la misma interfaz de SoapUI. Luego de esto necesitas probar que todo esto funcione bien…
    startmock

Una de las herramientas que mejor se complmenta con los servicios mock creados en SOAPUI, es Postman, plugin de chrome que también funciona como aplicacion independiente.  Te recomiendo usar Postman para validar todos los servicios creados en los pasos anteriores, Postman valida las salidas mucho mejor que SOAPUI, así que es más fácil encontrar un error (un corchete mal cerrado por ejemplo) en la respuesa de salida. Además permite crear diferentes casos de prueba, almacenar las entradas y el formato de intercambio (almacenamiento) es también un xml que puede ser versionado.

Algunos consejos con Postman

  1. Usar un archivo base json (Collection) que sea incremental, colaborativo y versionado. Este archivo debe ser importado constantemente en la herramienta y a su vez exportado (sobre-escribiendo la versión inicial) cuando haya cambios.
    postman-import-export
  2. Agrupar por carpetas los servicios similares.
  3. Parametrizar los endpoint de tal manera que las URL sean fácilmente intercambiadas entre distintos ambientes. Esta configuración, soporta http y https usando los puertos respectivos (por defecto 80 y 443 respectivamente), además se configura de esta manera buscando soportar nombres de contexto vacío (si los hubiese).
    Formato:{{protocol}}://{{server}}:{{port}}/{{context}}myServiceName
    endpoint1
  4. Los ambientes se parametrizan en la sección “manage environments” que permite almacenar configuraciones por ambiente independientes.environment
  5. Cada ambiente se almacena además como un archivo de configuración json diferente (no olvides exportarlo y versionarlo). Los valores se reemplazan dinámicamente en las variables del contexto de prueba cuando se llama el servicio (endpoint clasificado)
    env-local

Bien, esto es apenas una introducción de todo lo que se puede lograr con SoapUI como herramienta de generación de respuestas simuladas y controladas en nuestros ambientes de desarrollo. La extensión, cobertura, desarrollo de mock services y generación conjuntos de test sobre los mismos, dependerán del contexto y el objetivo que queremos lograr. Pero siempre, será buena idea lograr aislarnos de dependencias fuertes como lo son servicios web.

[feather_share show=”twitter, facebook, linkedin” hide=”reddit, pinterest, tumblr, google_plus, mail” size=”24″][:]