docker Archives - Arkhotech

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.

¿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
[:]

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″]
 

[:]