Programando infraestructura en la nube

El pasado 23 de febrero participé, junto a mi compañero Alberto Molina en las IV Jornadas de Informática de la Universidad de Almería. Nos invitaron a dar una charla sobre Cloud Computing y decidimos presentar un tema que estamos trabajando en los últimos meses: la importancia y necesidad de programar la infraestructura. Por lo tanto, con el título “Programando infraestructura en la nube” abordamos el concepto de Cloud Computing, centrándonos en las dos capas que más nos interesaban: en el SaaS (Software como servicio) y en el IaaS (Infraestructura como servicio). Mientras que todo el mundo entiende que el SaaS es programable (generalmente mediante APIs), la pregunta que nos hacíamos era: ¿la IaaS se puede programar?

¿Por qué programar la infraestructura en la nube?

Podemos indicar varias razones:

  • Las nueva metodología DevOps que trata de resolver el tradicional conflicto entre desarrollo y sistemas, con objetivos y responsabilidades diferentes. ¿Cómo solucionarlo?, pues indicábamos que habría que utilizar las mismas herramientas y que se deberían seguir las mismas metodologías de trabajo, pasando de “integración continua” a “entrega continua o a despliegue continuo”. En este escenario resulta imprescindible el uso de escenarios replicables y automatización de la configuración.
  • Una de las características más importantes y novedosas de los servicios que podemos obtener en la nube es la elasticidad, está nos proporciona la posibilidad de obtener más servicios (en nuestro caso más infraestructura) en el momento que la necesitamos. Poníamos de ejemplo un escenario donde tuviéramos una demanda variable sobre nuestro servicio web, es decir al tener un pico de demanda podemos, mediante la elasticidad, realizar un escalado horizontal, añadiendo más recursos a nuestro cluster. En este escenario también es necesario la automatización en la creación y destrucción de servidores web que formarán parte de nuestro cluster.
  • Se está pasando de crear aplicaciones monolíticas a crear aplicaciones basadas en “microservicios”.  Normalmente para implementar está nueva arquitectura se utilizan contenedores. Los contenedores se suelen ejecutar en cluster (por ejemplo kubernetes o docker swarm). Pero el software que vamos a usar para orquestar nuestros contenedores utiliza una infraestructura de servidores, almacenamiento y redes. También llegamos a la conclusión que la creación y configuración de esta infraestructura hay que automatizarlas.
  • En los últimos tiempo se empieza hablar de la “Infraestructura como código”, es decir, tratar la configuración de nuestros servicios como nuestro código, y por tanto utilizar las mismas herramientas y metodologías al tratar nuestra configuración: usar metodologías ágiles, entornos de desarrollo, prueba y producción, entrega / despliegue continuo. En este caso estamos automatizando la configuración de nuestra infraestructura.
  • “Big Data”: En los nuevos sistemas de análisis de datos se necesitan una gran cantidad de recursos para los cálculos que hay que realizar y además podemos tener cargas variables e impredecibles. Por lo tanto la sería deseable que la creación y configuración de la infraestructura donde se van a realizar dichos cálculos se cree y configure de forma automática.
  • Quizás esta razón, no es tan evidente, ya que se trata de la solución cloud “Función como servicio” o “serverless” que nos posibilita la ejecución de un código con características cloud (elasticidad, escabilidad, pago por uso,…) sin tener que preocuparnos por los servidores y recursos necesarios. Evidentemente, y no por el usuario final, será necesario la gestión automática de una infraestructura para que este sistema funcione.
  • Por último, y quizás como una opción donde todavía hay que llegar, señalamos la posibilidad de desarrollar aplicaciones nativas cloud, entendiendo este tipo de aplicaciones, aquellas que pudieran autogestionar la infraestructura donde se esté ejecutando, creando de esta manera aplicaciones resilientes y infraestructura dinámica autogestionada.

Continue reading

bart-simpson-utf8

Cuando mis alumnos se enfrentan a realizar su proyecto de fin de curso creando una aplicación web en python casi siempre se encuentran con la problemática de las diferentes codificaciones con las que trabaja python. Normalmente trabajan con variables locales de tipo cadena que están codificada en utf-8, sin embargo cuando leen datos que provienen de una API web se puede dar el caso que la codificación sea unicode. En estos casos siempre les cuesta mucho trabajo tratar con los caracteres no ingleses codificados de diferente forma.

En estos días estoy desarrollando una aplicación web y me estoy encontrado con el mismo problema. Por lo tanto el objetivo de escribir esta entrada en el blog es hacer un resumen de cómo python gestiona las diferentes codificaciones y que sirva como material de apoyo para la realización de los proyectos de mis alumnos.

Codificaciones de caracteres

Entendemos un carácter como el componente más pequeño que puede formar un texto. Aunque muchos caracteres son iguales en los distintos idiomas, hay caracteres específicos para cada alfabeto, que tienen grafías diferentes. Evidentemente para guardar en un ordenador cada uno de los caracteres es necesario asignar a cada uno un número que lo identifique, y dependiendo del sistema que utilicemos para asignar estos “códigos” nacen las distintas codificaciones de caracteres.

En los principios de la informática los ordenadores se diseñaron para utilizar sólo caracteres ingleses, por lo tanto se creó una codificación de caracteres, llamada ascii (American Standard Code for Information Interchange) que utiliza 7 bits para codificar los 128 caracteres necesarios en el alfabeto inglés. Por lo tanto con esta codificación, es imposible representar caracteres específicos de otros alfabetos, como por ejemplo, los caracteres acentuados.

Posteriormente se extendió esta codificación para incluir caracteres no ingleses. Al utilizar 8 bits se pueden representar 256 caracteres. De esta forma para codificar el alfabeto latino aparece la codificación ISO-8859-1 o Latín 1. Puedes ver las tablas de estos códigos en la siguiente tabla. Continue reading

En entradas anteriores: Dockerfile: Creación de imágenes docker y Ejemplos de ficheros Dockerfile, creando imágenes docker, hemos estudiado la utilización de la herramiento docker build para construir imágenes docker a partir de fichero Dockerfile.

En esta entrada vamos a utilizar la instrucción VOLUME, para crear volúmenes de datos en los contenedores que creemos a partir de la imagen que vamos a crear.

Creación de una imagen con un servidor web

Vamos a repetir el ejemplo que vimos en la entrada Ejemplos de ficheros Dockerfile, creando imágenes docker, pero en este caso, al crear nuestro contenedor se van a crear dos volúmenes de datos: en uno se va a guardar el contenido de nuestro servidor (/var/www) y en otro se va a guardar los logs del servidor (/var/log/apache2). En este caso si tengo que eliminar el contenedor, puedo crear uno nuevo y la información del servidor no se perderá.

En este caso el fichero Dockerfile quedaría:

FROM ubuntu:14.04
MAINTAINER José Domingo Muñoz "josedom24@gmail.com"

RUN apt-get update && apt-get install -y apache2 && apt-get clean && rm -rf /var/lib/apt/lists/*

ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2

VOLUME /var/www /var/log/apache2
EXPOSE 80
ADD ["index.html","/var/www/html/"]

ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Además del fichero Dockerfile, tenemos el fichero index.html en nuestro contexto. con la siguiente instrucción construimos la nueva imagen:

~/apache$ docker build -t josedom24/apache2:1.0 .

Y podemos crear nuestro contenedor:

$ docker run -d -p 80:80 --name servidor_web josedom24/apache2:1.0
78033d752c8f163576e5ef1a7435613a16954f4c138cf62f4d47a635fc5eb374sss

Nuestro contenedor está ofreciendo la página web, pero la información del servidor está guardad de forma permanente en los volúmenes. Podemos comprobar que se han creado dos volúmenes:

$ docker volume ls
DRIVER              VOLUME NAME
local               8dc51c65f164b25854dac01257d3074de0a35bfd202d2d6b94de5c9e97884249
local               a611141be3434229ed22acab6a69fd591dc7ddd39c6321784c05100065ddb266

Y obteniendo información del contenedor, podemos obtener:

$ docker inspect servidor_web 
..."Mounts": [
    {
        "Name": "8dc51c65f164b25854dac01257d3074de0a35bfd202d2d6b94de5c9e97884249",
        "Source": "/mnt/sda1/var/lib/docker/volumes/8dc51c65f164b25854dac01257d3074de0a35bfd202d2d6b94de5c9e97884249/_data",
        "Destination": "/var/log/apache2",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    },
    {
        "Name": "a611141be3434229ed22acab6a69fd591dc7ddd39c6321784c05100065ddb266",
        "Source": "/mnt/sda1/var/lib/docker/volumes/a611141be3434229ed22acab6a69fd591dc7ddd39c6321784c05100065ddb266/_data",
        "Destination": "/var/www",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],
...

Si accedemos al Docker Engine podemos comprobar los ficheros que hay en cada uno de los volúmenes:

$ docker-machine ssh nodo1
docker@nodo1:~$ sudo su
root@nodo1:/home/docker# cd /mnt/sda1/var/lib/docker/volumes/8dc51c65f164b25854dac01257d3074de0a35bfd202d2d6b94de5c9e97884249/_data
root@nodo1:/mnt/sda1/var/lib/docker/volumes/8dc51c65f164b25854dac01257d3074de0a35bfd202d2d6b94de5c9e97884249/_data# ls
access.log               error.log                other_vhosts_access.log

En el primer volumen vemos los ficheros correpondiente al log del servidor, y en el segundo tenemos los fichero del document root:

cd /mnt/sda1/var/lib/docker/volumes/a611141be3434229ed22acab6a69fd591dc7ddd39c6321784c05100065ddb266/_data
root@nodo1:/mnt/sda1/var/lib/docker/volumes/a611141be3434229ed22acab6a69fd591dc7ddd39c6321784c05100065ddb266/_data# ls
html

Finalmente indicar que si borramos el contenedor, y creamos uno nuevo desde la misma imagen la información del servidor (logs y document root) no se habrá eliminado y la tendremos a nuestra disposición en el nuevo contenedor.

Cuando un contenedor es borrado, toda la información contenida en él, desaparece. Para tener almacenamiento persistente en nuestros contenedores, que no se elimine al borrar el contenedor, es necesario utilizar volúmenes de datos (data volume). Un volumen es un directorio o un fichero en el docker engine que se monta directamente en el contenedor. Podemos montar varios volúmenes en un contenedor y en varios contenedores podemos montar un mismo volumen.

Tenemos dos alternativas para gestionar el almacenamiento en docker:

  • Usando volúmenes de datos
  • Usando contenedores de volúmenes de datos

Volúmenes de datos

Los volúmenes de datos tienen las siguientes características:

  • Son utilizados para guardar e intercambiar información de forma independientemente a la vida de un contenedor.
  • Nos permiten guardar e intercambiar información entre contenedores.
  • Cuando borramos el contenedor, no se elimina el volumen asociado.
  • Los volúmenes de datos son directorios del host montados en un directorio del contenedor, aunque también se pueden montar ficheros.
  • En el caso de montar en un directorio ya existente de un contenedor un volumen de datos , su contenido no será eliminado.

Añadiendo volúmenes de datos

Vamos a empezar creando una contenedor al que le vamos a asociar un volumen:

$ docker run -it --name contenedor1 -v /volumen ubuntu:14.04 bash

Como podemos comprobar con la opción -v hemos creado un nuevo volumen que se ha montado en el directorio /volumen del contenedor. Vamos a crear un fichero en ese directorio:

root@d50f89458659:/# cd /volumen/
root@d50f89458659:/volumen# touch fichero.txt
root@d50f89458659:/volumen# exit

Podemos comprobar los puntos de montajes que tiene nuestro contnedor con la siguiente instrucción:

$ docker inspect contenedor1
...
"Mounts": [
            {
                "Name": "c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427",
                "Source": "/mnt/sda1/var/lib/docker/volumes/c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427/_data",
                "Destination": "/volumen",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
...

Continue reading

machine

Docker Machine es una herramienta que nos ayuda a crear, configurar y manejar máquinas (virtuales o físicas) con Docker Engine. Con Docker Machine podemos iniciar, parar o reiniciar los nodos docker, actualizar el cliente o el demonio docker y configurar el cliente docker para acceder a los distintos Docker Engine. El propósito principal del uso de esta herramienta es la de crear máquinas con Docker Engine en sistemas remotos y centralizar su gestión.

Docker Machine utiliza distintos drivers que nos permiten crear y configurar Docker Engine en distintos entornos y proveedores, por ejemplo virtualbox, AWS, VMWare, OpenStack, …

Las tareas fundamentales que realiza Docker Machine, son las siguientes:

  • Crea una máquina en el entorno que hayamos indicado (virtualbox, openstack,…) donde va a instalar y configurar Docker Engine.
  • Genera los certificados TLS para la comunicación segura.

También podemos utilizar un driver genérico (generic) que nos permite manejar máquinas que ya están creadas (físicas o virtuales) y configurarlas por SSH.

Instalación de Docker Machine

Para instalar la última versión (0.7.0) de esta herramienta ejecutamos:

$ curl -L https://github.com/docker/machine/releases/download/v0.7.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine && \
chmod +x /usr/local/bin/docker-machine

Y comprobamos la instalación:

$ docker-machine -version
docker-machine version 0.7.0, build a650a40

Continue reading