Quantcast
Channel: PLEDIN 3.0
Viewing all 104 articles
Browse latest View live

Curso: Infraestructura en la nube con OpenStack

$
0
0

openstack

El pasado mes de mayo y junio, he impartido, junto a mi compañero Alberto Molina (@alberto_molina), un curso de iniciación y uso de Openstack organizado por openwebinars.net, titulado: Infraestructura en la nube con OpenStack. Además de los contenidos que hemos ofrecido a los alumnos, se han realizado 10 vídeos de dos horas con clases en directo. Los contenidos previstos en el curso eran:

Infraestructura en la nube

  • Introducción
  • Plataformas de software de IaaS
  • El proyecto OpenStack
  • Componentes
  • Arquitectura lógica
  • Entorno de trabajo

OpenStack: Uso básico

  • Conceptos previos
  • Imágenes
  • Instancias
  • Volúmenes
  • Redes
  • Ejemplos de uso

OpenStack: Uso avanzado

  • Clientes de línea de comandos
  • Manejo desde la línea de comandos
  • OpenStack APIs RESTful

Puedes acceder a todos los contenidos del curso, cuya licencia es Creative Commons puedes acceder a  la página http://iesgn.github.io/ow1/.

 


OpenStack 5th Birthday – OpenStack Sevilla

$
0
0

5dayOpenStack

El grupo OpenStack Sevilla se une a los eventos de celebración del 5º aniversario del proyecto OpenStack que van a organizar los distintos grupos locales de usuarios de OpenStack con el apoyo de la OpenStack Foundation. En este caso tendrá lugar un encuentro el próximo 1 de Julio a partir de las 19:00 en las oficinas de Bitnami en Sevilla y se impartirán las siguientes charlas:

Puedes ver el reportaje fotográfico del evento:

Instalación de drupal en heroku

$
0
0

intro

Heroku es una aplicación que nos ofrece un servicio de Cloud Computing PaaS (Plataforma como servicio). Como leemos en la Wikipedia es propiedad de Salesforce.com y es una de las primeras plataformas de computación en la nube, que fue desarrollada desde junio de 2007, con el objetivo de soportar solamente el lenguaje de programación Ruby, pero posteriormente se ha extendido el soporte a Java, Node.js, Scala, Clojure y Python y PHP.

Características de heroku

La funcionalidad ofrecida por heroku esta disponible con el uso de dynos, que son una adaptación de los contenedores Linux y nos ofrecen la capacidad de computo dentro de la plataforma.

Cada dyno ejecuta distintos procesos, por ejemplo ejecuta los servidores web y los servidores de bases de datos, o cualquier otro proceso que le indiquemos en un fichero Procfile. Las características principales de los dynos son:

  • Escabilidad: Si, por ejemplo, tenemos muchas peticiones a nuestra aplicación podemos hacer un escalado horizontal, es decir, podemos crear más dynos que respondan las peticiones. La carga de peticiones se balanceará entre los dynos existentes. Además podemos hacer una escalabilidad vertical, en este caso lo que hacemos es cambiar las características hardware de nuestro dyno, por ejemplo aumentar la cantidad de RAM. Las características de escabilidad no están activadas en el plan gratuito de heroku. Además la escabilidad no es automática, hay que realizarla manualmente.
  • Redundancia: En el momento en que podemos tener varios dynos detrás de una balanceado de carga, nuestra aplicación es redundante. Es decir, si algún dyno tiene un problema, los demás responderían las peticiones.
  • Aislamiento y seguridad: Cada uno de los dynos está aislado de los demás. Esto nos ofrece seguridad frente a la ejecución de procesos en otros dynos, además también nos ofrece protección para que ningún dyno consuma todos los recursos de la máquina.
  • Sistema de archivo efímero: Cada dyno posee un sistema de archivo cuya principal característica es que es efímero. Es decir los datos de nuestra aplicación (por ejemplo ficheros subidos) no son accesibles desde otros dynos, y si reiniciamos el dyno estos datos se pierden. Es muy recomendable tener los datos de la aplicación en un sistema externo, por ejemplo un almacén de objetos, como Amanzon S3 o OpenStack Swift.
  • Direccionamiento IP: Cuando tenemos varios dynos, cada uno de ellos puede estar ejecutándose en máquinas diferentes. El acceso a nuestra aplicación siempre se hace desde un balanceador de carga (routers). Esto significa que los dynos no tienen una ip estática, y el acceso a ellos siempre se hace a la dirección IP que tiene el balanceador. Cuando se reinicia un dyno se puede ejecutar en otra máquina, y por lo tanto puede cambiar de dirección IP.
  • Interfaces de red: Cada dyno tiene una interfaz de red con un direccionamiento privado /30, en el rango 172.16.0.0/12. Por lo tanto cada dyno está conecta a una red independiente que no comparte con ningún otro dyno. Para acceder a él, como hemos indicado anteriormente, habrá que hacerlo a través de la ip pública que tiene asignada el balanceador de carga.

Despliegue de la aplicación web drupal en Heroku

En este ejemplo vamos a utilizar la capa gratuita que nos ofrece Heroku, que tiene las siguientes características:

  • Podemos crear un dyno, que puede ejecutar un máximo de dos tipos de procesos. Para más información sobre la ejecución de los procesos ver: https://devcenter.heroku.com/articles/process-model.
  • Nuestro dyno utiliza 512 Mb de RAM
  • Tras 30 minutos de inactividad el dyno se para (sleep), además debe estar parado 6 horas cada 24 horas.
  • Podemos utilizar una base de datos postgreSQL con no más de 10.000 registros
  • Para más información de los planes ofrecido por heroku puedes visitar: https://www.heroku.com/pricing#dynos-table-modal

Para ampliar la funcionalidad de nuestra aplicación podemos añadir a nuestro dyno distintos add-ons. Algunos lo podemos usar de forma gratuita y otros son de pago. El add-ons de mysql (ClearDB mysql) no lo podemos usar en el plan gratuito, por lo que vamos a usar una base de datos postgreSQL.

Siguiendo las instrucciones de instalación de la página oficial hemos instalado la última versión del CMS drupal en un servidor local, hemos utilizado como base de datos un servidor postgreSQL (Recordatorio: es necesario instalar el paquete php5-pgsql que es la libreria PHP de postgreSQL). Como hemos indicado anteriormente heroku nos ofrece una infraestructura para desplegar nuestra aplicación web cuyo sistema de archivo es efímero, por lo tanto no podemos realizar la instalación de las aplicaciones web directamente en heroku, ya que cada vez que reiniciemos nuestro dyno perderemos los ficheros creados, por ejemplo el fichero de configuración. Vamos a hacer una migración desde el entorno de pruebas (servidor local) a nuestro entorno de producción (dyno de herku).

Instalación de Heroku CLI

Como podemos ver en esta página, tenemos que ejecutar un script bash que nos bajamos con la siguiente instrucción y que realizará la instalción de los paquetes necesarios y la configuración inicial.

wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh

Ahora tenemos que iniciar sesión en heroku, utilizando el correo electrónico y la contraseña que hemos indicado durante el registro de la cuenta. La primera vez que ejecutamos la siguiente instrucción, se termina de configurar el cliente heroku:

# heroku login
heroku-cli: Installing Toolbelt v4... done.
For more information on Toolbelt v4: https://github.com/heroku/heroku-cli
heroku-cli: Adding dependencies... done
heroku-cli: Installing core plugins... done
Enter your Heroku credentials.
Email: tucorreo@electronico.com
Password (typing will be hidden):

Para ver la versión del cliente que estamos utilizando, además salen los plugins instalados:

# heroku version
heroku-toolbelt/3.42.22 (x86_64-linux-gnu) ruby/2.1.5
heroku-cli/4.27.6-e59743f (amd64-linux) go1.5.1

Para pedir información de las funciones disponibles podemos pedir la ayuda:

# heroku help

Subir nuestra clave púlica para acceder a nuestro dyno

Sguiendo la documentación oficial, es necesario subir una clave pública para permitr el acceso por SSH a nuestro dyno:

# heroku keys:add
Found existing public key: ~/.ssh/id_rsa.pub
Uploading SSH public key /Users/adam/.ssh/id_rsa.pub... done

Creación de nuestra primera aplicación

Para crear nuestra primera aplicación (dyno) ejecutamos la siguiente instrucción:

# heroku apps:create pledinjd
Creating pledinjd... done, stack is cedar-14
https://pledinjd.herokuapp.com/ | https://git.heroku.com/pledinjd.git

Se ha creado un nuevo dyno con un repositorio git, que a continuación vamos a clonar. Además se ha generado un FQHN que nos permite acceder a nuestra aplicación:

drupal1

A continuación vamos a clonar el repositorio y vamos a desplegar un fichero index.php de inicio:

# git clone https://git.heroku.com/pledinjd.git
Cloning into 'pledinjd'...
warning: You appear to have cloned an empty repository.
Checking connectivity... done.
# cd pledinjd/
pledinjd# echo "<h1>pledinjd funcionando</h1>">index.php
pledinjd# git add index.php 
pledinjd# git commit -m "El primer fichero"
pledinjd# git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 245 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> PHP app detected
remote: 
remote: ! WARNING: No 'composer.json' found.
remote: Using 'index.php' to declare PHP applications is considered legacy
remote: functionality and may lead to unexpected behavior.
remote: 
remote: -----> No runtime required in 'composer.json', defaulting to PHP 5.6.15.
remote: -----> Installing system packages...
remote: - PHP 5.6.15
remote: - Apache 2.4.16
remote: - Nginx 1.8.0
remote: -----> Installing PHP extensions...
remote: - zend-opcache (automatic; bundled)
remote: -----> Installing dependencies...
remote: Composer version 1.0.0-alpha11 2015-11-14 16:21:07
remote: -----> Preparing runtime environment...
remote: NOTICE: No Procfile, using 'web: vendor/bin/heroku-php-apache2'.
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote: 
remote: -----> Compressing... done, 72.8MB
remote: -----> Launching... done, v3
remote: https://pledinjd.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/pledinjd.git
 * [new branch] master -> master

Como se puede observar cuando se sube un nuevo fichero, se configura de forma adecuada el entorno de producción, reiniciando los servidores necesarios, y podemos acceder a nuestra nueva página:

drupal2

Migración de la base de datos

A continuación, vamos a instalar un addons en nuestro proyecto que nos proporciona una base de datos postgreSQL:

# heroku addons:create heroku-postgresql
Creating postgresql-amorphous-6708... done, (free)
Adding postgresql-amorphous-6708 to pledinjd... done
Setting DATABASE_URL and restarting pledinjd... done, v4
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Use `heroku addons:docs heroku-postgresql` to view documentation.

Para ver información de los addons instalados:

# heroku addons
Add-on                                         Plan       Price
─────────────────────────────────────────────  ─────────  ─────
heroku-postgresql (postgresql-amorphous-6708)  hobby-dev  free 
 └─ as DATABASE

Y comprobamos que nuestra base de datos se identifica con el nombre DATABASE, y podemos pedir las credenciales para conectarnos a ella (nombre de la base de datos, usuario y contraseña y servidor de la base de datos) con el siguiente comando:

# heroku pg:credentials DATABASE
Connection info string:
   "dbname=d3bo6f4g2gbilu host=ec2-54-83-202-218.compute-1.amazonaws.com port=5432 user=pwcbycuykpmhqb password=1dg2xwsRb6fcMRhHdtkfTlkahw sslmode=require"
Connection URL:
    postgres://pwcbycuykpmhqb:1dg2xwsRb6fcMRhHdtkfTlkahw@ec2-54-83-202-218.compute-1.amazonaws.com:5432/d3bo6f4g2gbilu

Nos queda hacer la migración de la base de datos, para ello vamos a seguir las indicaciones de la documentación oficial, primero hacemos la copia de seguridad de la base de datos drupal en local y posteriormente la restauramos en heroku:

pg_dump -Fc --no-acl --no-owner -h localhost -U jose drupal > drupal.dump

Para restaurar la copia de seguridad es necesario que la copia que hemos realizado este accesible desde una URL (por ejemplo lo podemos subir a un almacén de datos como Amazon S3), en este caso para agilizar el ejemplo lo he subido a nuestra aplicación:

pledinjd# git add drupal.dump 
pledinjd# git commit -m "Copia de seguridad BD"
pledinjd# git push

pledinjd# heroku pg:backups restore 'http://pledinjd.herokuapp.com/drupal.dump' DATABASE --confirm pledinjd
Use Ctrl-C at any time to stop monitoring progress; the backup
will continue restoring. Use heroku pg:backups to check progress.
Stop a running restore with heroku pg:backups cancel.

r001 ---restore---> DATABASE
Restore completed

Y podemos ver las tablas creadas:

# heroku pg:psql DATABASE
---> Connecting to DATABASE_URL
psql (9.4.5)
conexión SSL (protocolo: TLSv1.2, cifrado: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compresión: desactivado)
Digite «help» para obtener ayuda.

pledinjd::DATABASE=> \dt

Despliegue de nuestra aplicación

Vamos a copiar los ficheros desde nuestro servidor local, y vamos a modificar el fichero de configuración con las credenciales de la base de datos de heroku:

pledinjd# cp -r /var/www/pledinjd/drupal .

Y el fichero de configuración sites/default/settings.php hay que modificarlo para configurar los parámetros de acceso a la base de datos de nuestro proyecto:

$databases['default']['default'] = array (
  'database' => 'd3bo6f4g2gbilu',
  'username' => 'pwcbycuykpmhqb',
  'password' => '1dg2xwsRb6fcMRhHdtkfTlkahw',
  'prefix' => '',
  'host' => 'ec2-54-83-202-218.compute-1.amazonaws.com',
  'port' => '5432',
  'namespace' => 'Drupal\\Core\\Database\\Driver\\pgsql',
  'driver' => 'pgsql',
);

Y a continuación hacemos el despliegue:

pledinjd# git add drupal
pledinjd# git commit -m "Despliegue de drupal"
pledinjd# git push

Instalando librerias PHP necesarias

Si accedemos a nuestra aplicación nos daremos cuenta que no funciona, podemos ver los logs de error de la misma ejecutando la siguente instrucción:

pledinjd# heroku logs

Necesitamos instalar en nuestro dyno las librerías PHP, la librería gráfica gd y la mbstring. Para definir las librerías que se han de instalar en heroku, siguiendo la documentación oficial, tenemos que crear en la raíz de nuestro repositorio git, un fichero composer.json con el siguiente contenido:

pledinjd# cat composer.json 
{
 "require": {
 "ext-gd": "*",
 "ext-mbstring": "*"
 }
}

A partir de este fichero hay que crear el fichero composer.lock, utilizando la utilidad composer, que nos permite gestionar las dependencias de las librerias. Vamos a intalar la utilidad y generar el fichero pack:

pledinjd# php -r "readfile('https://getcomposer.org/installer');" | php
pledinjd# php composer.phar update
pldinjd# git add composer.lock 
pledinjd# git commit -m "Librerías"
pledinjd# git push

Y ya podemos acceder a nuestra aplicación:

drupal3

Accediendo al dyno

Podemos acceder a nuestro dyno con la siguiente instrucción:

# heroku run bash
Running bash on pledinjd... up, run.4539
~ $

Accedemos al repositorio git, aunque podemos acceder al directorio padre:

~ $ ls
Procfile  composer.json  composer.lock    drupal    drupal.dump  index.php    vendor
~ $ cd ..
/ $ ls
app  bin  dev  etc  home  lib  lib64  lost+found  proc    sbin  sys  tmp    usr  var

Como vimos anteriormente el dyno tiene una dirección en un rango /30:

$ ip addr show eth0
27054: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 1e:da:76:65:02:a9 brd ff:ff:ff:ff:ff:ff
    inet 172.18.174.218/30 brd 172.18.174.219 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::1cda:76ff:fe65:2a9/64 scope link 
       valid_lft forever preferred_lft forever

Y la puerta de enlace es la otra direción disponible en la red, que debe coincidir con la interfaz en el balanceador de carga/router:

$ ip route
default via 172.18.174.217 dev eth0 
172.18.174.216/30 dev eth0 proto kernel scope link src 172.18.174.218

Y para terminar podemos observar que el dyno esta corriendo en una máquina con 60Gb de RAM:

$ free -h
             total       used       free     shared    buffers     cached
Mem:           60G        58G       2.0G        70M       1.3G       6.1G

Conclusiones

Si queremos usar heroku para desplegar aplicaciones web tradicionales escritas en PHP, como drupal, tenemos que tener en cuenta varias cosas:

  • Tniendo en cuenta que el sistema de ficheros es efímero, tenemos que subir nuestros datos de la aplicación web en un sistema exerno, por ejmplo en un almacen de objetos como Amazon S3, apra ello es muy recomendable utilizar un plugin de drupal de Amazon S3.
  • Heroku no manda correos, por lo tanto necesitamos un servicio externo como Mandrill, por lo tanto podemos usar el plugin de drupal de Mandrill.

El alguna futura entrada del blog se posría realizar un despleigue de alguna aplicgación web utilizado Python o Ruby y estudiar las funcionalidades que nos ofrece Heroku.

 

Introducción a docker

$
0
0

docker

Últimamente Docker está de moda. Si haces una búsqueda por intenet verás que existen multitud de páginas hablando del tema. Podría preguntarme, qué necesidad tengo de escribir otra entrada en mi blog sobre un tema tan estudiado. Y la respuesta sería que si lo escribo lo aprendo, y además he llegado a la conclusión de que tengo que aprenderlo. Empezamos con una definición: Docker es un proyecto de software libre que permite automatizar el despliegue de aplicaciones dentro de contenedores.  ¿Automatizar el despliegue de aplicaciones?, ¡esto me interesa!: este año estoy impartiendo un módulo del CFGS de Administración de Sistemas Informáticos, que se titula: “Implantación de aplicaciones web”. Parece razonable que mis alumnos deban conocer esta nueva tecnología, que en los últimos años (realmente desde marzo de 2013 cuando el proyecto fue liberado como software libre) hemos escuchado como una nueva manera de gestionar contenedores. Docker nos permite, de una forma sencilla, crear contenedores  ligeros y portables donde ejecutar nuestras aplicaciones software sobre cualquier máquina con Docker instalado, independientemente del sistema operativo que la máquina tenga por debajo, facilitando así también los despliegues. De ahí que el lema de Docker sea: “Build, Ship and Run. Any application, Anywhere” y se haya convertido en una herramienta fundamental tanto para desarrolladores como para administradores de sistemas.

Virtualización ligera

Tenemos varios tipos de virtualización, pero la que nos interesa es la llamada virtualización en el nivel de sistema operativo o ligera. Este tipo de virtualización nos permite tener múltiples grupos de procesos aislados, que comparten el mismo sistema operativo y hardware, a los que llamamos contenedores o jaulas. El sistema operativo anfitrión virtualiza el hardware a nivel de sistema operativo, esto permite que varios sistemas operativos virtuales se ejecuten de forma aislada en un mismo servidor físico. Existen diferentes alternativas para implementar la virtualización ligera: jaulas BSD, vServers, OpenVZ, LXC y finalmente Docker.

¿Qué novedades ha aportado Docker a la gestión de contenedores?

Docker implementa una API de alto nivel para proporcionar virtualización ligera, es decir, contenedores livianos que ejecutan procesos de manera aislada. Esto lo consigue utilizando principalmente dos características del kernel linux: cgroups y namespaces, que nos proporcionan la posibilidad de utilizar el aislamiento de recursos (CPU, la memoria, el bloque E/S, red, etc.). Mediante el uso de contenedores, los recursos pueden ser aislados, los servicios restringidos, y se otorga a los procesos la capacidad de tener una visión casi completamente privada del sistema operativo con su propio identificador de espacio de proceso, la estructura del sistema de archivos, y las interfaces de red. Contenedores múltiples comparten el mismo núcleo, pero cada contenedor puede ser restringido a utilizar sólo una cantidad definida de recursos como CPU, memoria y E/S. Resumiendo, algunas características de docker:

  • Es ligero ya que no hay virtualización completa, aprovechándose mejor el hardware y únicamente necesitando el sistema de archivos mínimo para que funcionen los servicios.
  • Los contenedores son autosuficientes (aunque pueden depender de otros contenedores)  no necesitando nada más que la imagen del contenedor para que funcionen los servicios que ofrece.
  • Una imagen Docker podríamos entenderla como un Sistema Operativo con aplicaciones instaladas. A partir de una imagen se puede crear un contenedor. Las imágenes de docker son portables entre diferentes plataformas, el único requisito es que en el sistema huésped esté disponible docker.
  • Es seguro,como hemos explicado anteriormente, con namespaces y cgroups, los recursos están aislados.
  • El proyecto nos ofrece es un repositorio de imágenes al estilo Github. Este servicio se llama Registry Docker Hub y permite crear, compartir y utilizar imágenes creadas por nosotros o por terceros.

Componentes de Docker

Docker está formado fundamentalmente por tres componentes:

  • Docker Engine: Es un demonio que corre sobre cualquier distribución de Linux y que expone una API externa para la gestión de imágenes y contenedores. Con ella podemos crear imágnenes, subirlas y bajarla de un registro de docker y ejecutar y gestionar contenedores.
  • Docker Client: Es el cliente de línea de comandos (CLI) que nos permite gestionar el Docker Engine. El cliente docker se puede configurar para trabajar con con un Docker Engine local o remoto, permitiendo gestionar tanto nuestro entorno de desarrollo local, como nuestro entorno de producción.
  • Docker Registry: La finalidad de este componente es almacenar las imágenes generadas por el Docker Engine. Puede estar instalada en un servidor independiente y es un componente fundamental, ya que nos permite distribuir nuestras aplicaciones. Es un proyecto open source que puede ser instalado gratuitamente en cualquier servidor, pero, como hemos comentado, el proyecto nos ofrece Docker Hub.

Ventajas del uso de Docker

El uso de docker aporta beneficios tanto a desarrolladores, testers y administradores de sistema. En el caso de los desarrolladores el uso de docker posibilita el centrarse en la generación de código y no preocuparse de las distintas características que pueden tener los entorno de desarrollo y producción. Por otro lado al ser muy sencillo gestionar contenedores y una de sus principales características es que son muy ligeros, son muy adecuados para desplegar entorno de pruebas donde poder hacer el testing. Por último, también aporta ventajas a los administradores de sistemas, ya que el despliegue de las aplicaciones se puede hacer de manera más sencilla, sin necesidad de usar máquinas virtuales.

Conclusiones

Como decía al principio, me parece que el uso de docker y entender las ventajas que aporta en el despliegue de aplicaciones es un tema que deben conocer los alumnos del ciclo formativo de Administración de Sistemas Informáticos, ya que podemos estar ante una nueva forma de trabajar que cambie los distintos roles y perfiles profesionales que tradicionalmente hemos conocido. Por lo tanto, tenemos que ser conscientes de que la labor del administrador de sistema está cambiando en los últimos años, y que el uso de esta tecnología parece que va a ser importante en este nuevo paradigma de trabajo.

Intentaré seguir profundizando en el uso de docker y escribir algunas entradas en el blog.

Usando OpenStack desde Vagrant

$
0
0

vagrant_openstack

Vagrant nos permite automatizar la creación y gestión de máquinas virtuales. Las máquinas virtuales creadas por vagrant se pueden ejecutar con distintos gestores de máquinas virtuales (VirtualBox, VMWare, KVM,…), pero también tenemos a nuestra disposición varios proveedores donde podemos lanzar máquinas virtuales usando vagrant, por ejemplo Amazon Web Service. En este artículo, sin embargo, vamos a utilizar vagrant para crear instancias en un entorno de Cloud Computing IaaS desarrollado con OpenStack. Si no estás familiarizado con vagrant y quieres aprender a usarlo, ya hice una introducción a esta herramienta en el artículo: Gestionando máquinas virtuales con Vagrant. En otro caso, si no tienes experiencia trabajando con OpenStack, olvídate de este tutorial y empieza a leer.

Configuración de vagrant

Para utilizar la funcionalidad de gestionar instancias openstack desde vagrant, necesitamos instalar un plugin vagrant. El desarrollo de dicho plugin lo puedes encontrar en el repositorio GitHub: ggiamarchi/vagrant-openstack-provider, y se puede usar desde la versión 1.4 de vagrant. Para instalar el plugin, ejecutamos la siguiente instrucción:

$ vagrant plugin install vagrant-openstack-provider

Creación de una instancia

Una vez que tenemos que tenemos instalado el plugin, debemos crear una fichero Vagrantfile, donde definimos los parámetros necesarios para crear una instancia en openstack: por un lado los datos de credenciales para establecer la conexión  y por otro la información necesaria para crear una instancia (nombre, sabor, imagen, grupos de seguridad, redes, …).

Para indicar los datos de las credenciales en el fichero Vagranfile, he optado por cargar el fichero de credenciales de OpenStack y utilizar el nombre de las variables de entorno que creamos, para ello:

$ source openrc.sh

Y un ejemplo del fichero Vagrantfile, podría ser  el siguiente:

require 'vagrant-openstack-provider'

Vagrant.configure('2') do |config|

  config.vm.box       = 'openstack'
  config.ssh.username = 'debian'

  config.vm.provider :openstack do |os|
    os.openstack_auth_url = ENV['OS_AUTH_URL']
    os.username           = ENV['OS_USERNAME']
    os.password           = ENV['OS_PASSWORD']
    os.tenant_name        = ENV['OS_TENANT_NAME']
    os.region             = ENV['OS_REGION_NAME']
    os.server_name        = "server"
    os.flavor             = 'm1.small'
    os.image              = 'Debian Jessie 8.2'
    os.security_groups    = ['default']
    os.floating_ip_pool   = 'ext-net'
    os.networks = ['red']
  end
end

Veamos los distintos parámetros que hemos indicado:

  • config.vm.box: Por compatibilidad con las distintas versiones de vagrant, se indica el box que vamos a utilizar, aunque evidentemente no corresponde con ningún box que tengamos en el disco local.
  • config.ssh.username: El nombre de usuario que voy a usar para conectarme por ssh a la instancia. Veremos posteriormente que si creamos varias máquinas con distintas distribuciones Linux, este parámetro se puede configurar para cada una de las máquinas.
  • os.openstack_auth_url, os.username, os.password, os.tenant_name, os.region: Credenciales de acceso a OpenStack.
  • os.server_name: Nombre de la instancia.
  • os.flavor: Sabor que se va a utilizar para crear la instancia.
  • os.image: Imagen que se va a instanciar.
  • os.security_groups: Lista con los grupos de seguridad que afectan a la instancia.
  • os.floating_ip_pool: Indicamos el pool de ip flotantes. También podemos usar el parámetro floating_ip, indicando directamente una IP flotante que tiene que estar reservada en el proyecto.
  • os.networks: Lista de redes donde vamos a conectar la instancia.

Para crear la instancia, ejecutamos la siguiente instrucción:

$ vagrant up --provider=openstack
Bringing machine 'default' up with 'openstack' provider...
==> default: Finding flavor for server...
==> default: Finding image for server...
==> default: Finding network(s) for server...
==> default: Launching a server with the following settings...
==> default:  -- Tenant          : Proyecto de josedom
==> default:  -- Name            : server
==> default:  -- Flavor          : m1.small
==> default:  -- FlavorRef       : 3
==> default:  -- Image           : Debian Jessie 8.2
==> default:  -- ImageRef        : d303f65a-ff05-4bd3-a857-fd095699106e
==> default:  -- KeyPair         : vagrant-generated-v93pw19v
==> default:  -- Network         : 238f2e88-0322-43ce-895b-cadffab89dc9
==> default: Waiting for the server to be built...
==> default: Using floating IP 172.22.204.198
==> default: Waiting for SSH to become available...
ssh: connect to host 172.22.204.198 port 22: Connection refused
ssh: connect to host 172.22.204.198 port 22: Connection refused
Connection to 172.22.204.198 closed.
==> default: The server is ready!

Y podemos acceder a la instancia ejecutando la siguiente instrucción:

$ vagrant ssh

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Jan 16 16:40:03 2016 from 172.19.0.26
debian@server:~$

Y podemos comprobar que realmente se ha creado una instancia en openstack utilizando el cliente de línea de comandos:

$ nova show server
+--------------------------------------+----------------------------------------------------------+
| Property                             | Value                                                    |
+--------------------------------------+----------------------------------------------------------+
| OS-DCF:diskConfig                    | MANUAL                                                   |
| OS-EXT-AZ:availability_zone          | nova                                                     |
| OS-EXT-STS:power_state               | 1                                                        |
| OS-EXT-STS:task_state                | -                                                        |
| OS-EXT-STS:vm_state                  | active                                                   |
| OS-SRV-USG:launched_at               | 2016-01-16T16:39:42.000000                               |
| OS-SRV-USG:terminated_at             | -                                                        |
| accessIPv4                           |                                                          |
| accessIPv6                           |                                                          |
| config_drive                         |                                                          |
| created                              | 2016-01-16T16:38:41Z                                     |
| flavor                               | m1.small (3)                                             |
| hostId                               | e4f23fc1be36f4bacde067d97c4a4d1a11dd99941ca8df1a442657a7 |
| id                                   | 2654cdeb-ffe5-429e-91a3-c43d26bc566c                     |
| image                                | Debian Jessie 8.2 (d303f65a-ff05-4bd3-a857-fd095699106e) |
| key_name                             | vagrant-generated-v93pw19v                               |
| metadata                             | {}                                                       |
| name                                 | server                                                   |
| os-extended-volumes:volumes_attached | []                                                       |
| progress                             | 0                                                        |
| red network                          | 10.0.0.138, 172.22.204.198                               |
| security_groups                      | default                                                  |
| status                               | ACTIVE                                                   |
| tenant_id                            | 6154f7897cd64fbbb8da7de098e1b0b4                         |
| updated                              | 2016-01-16T16:39:08Z                                     |
| user_id                              | 99633b6e37bb4a7e9fbf387b0f10857d                         |
+--------------------------------------+----------------------------------------------------------+

Creando varias instancias

En el siguiente ejemplo vamos a crear dos instancias, veamos el fichero Vagrantfile:

require 'vagrant-openstack-provider'

Vagrant.configure('2') do |config|

  config.vm.box       = 'openstack'
  config.ssh.username = ''

  config.vm.provider :openstack do |os|
    os.openstack_auth_url = ENV['OS_AUTH_URL']
    os.username           = ENV['OS_USERNAME']
    os.password           = ENV['OS_PASSWORD']
    os.tenant_name        = ENV['OS_TENANT_NAME']
    os.region             = ENV['OS_REGION_NAME']
  end

    config.vm.define 'server-1' do |s|
     s.vm.provider :openstack do |os, override|
      override.ssh.username = 'debian'
      os.server_name        = 'server-1'
      os.image              = 'Debian Jessie 8.2'
      os.flavor             = 'm1.small'
      os.security_groups    = ['default']
      os.networks           = ['red']
      os.volumes            = ['vol1']
      os.floating_ip_pool   = 'ext-net'

    end
    end

  config.vm.define 'server-2' do |s|
    s.vm.provider :openstack do |os, override|
      override.ssh.username = 'ubuntu'
      os.server_name        = 'server-2'
      os.image              = 'Ubuntu 14.04 LTS'
      os.flavor             = 'm1.small'
      os.security_groups    = ['default']
      os.networks           = ['red2']
      os.volumes            = ['vol2']
      os.floating_ip_pool   = 'ext-net'

    end
  end
end

Como podemos observar tenemos las siguientes características:

  • Vamos a crear una instancia desde una imagen correspondiente a una distribución Debian y otra desde una Ubuntu, por lo que hay que indicar usuarios distintos para acceder por ssh a cada instancia  (override.ssh.username).
  • Las dos instancias tienen el mismo sabor y el mismo grupo de seguridad, pero podrían ser distintos.
  • Las dos instancias están conectadas a redes diferentes. Las redes deben estar creadas en nuestro proyecto.
  • Con el parámetro os.volumes, indicamos una lista de volúmenes que se van a conectar a la instancia. Cada instancia tiene un volumen conectado, estos volúmenes deben estar creado en nuestro proyecto.

Creamos las instancias:

$ vagrant up --provider=openstack
Bringing machine 'server-1' up with 'openstack' provider...
Bringing machine 'server-2' up with 'openstack' provider...
==> server-1: Finding flavor for server...
==> server-1: Finding image for server...
==> server-1: Finding network(s) for server...
==> server-1: Finding volume(s) to attach on server...
==> server-1: Launching a server with the following settings...
==> server-1:  -- Tenant          : Proyecto de josedom
==> server-1:  -- Name            : server-1
==> server-1:  -- Flavor          : m1.small
==> server-1:  -- FlavorRef       : 3
==> server-1:  -- Image           : Debian Jessie 8.2
==> server-1:  -- ImageRef        : d303f65a-ff05-4bd3-a857-fd095699106e
==> server-1:  -- KeyPair         : vagrant-generated-exrdx618
==> server-1:  -- Network         : 238f2e88-0322-43ce-895b-cadffab89dc9
==> server-1:  -- Volume attached : e34769d3-3bdc-43ed-ad31-837af2dc14ef => auto
==> server-1: Waiting for the server to be built...
==> server-1: Using floating IP 172.22.203.243
==> server-1: Waiting for SSH to become available...
==> server-1: The server is ready!
==> server-2: Finding flavor for server...
==> server-2: Finding image for server...
==> server-2: Finding network(s) for server...
==> server-2: Finding volume(s) to attach on server...
==> server-2: Launching a server with the following settings...
==> server-2:  -- Tenant          : Proyecto de josedom
==> server-2:  -- Name            : server-2
==> server-2:  -- Flavor          : m1.small
==> server-2:  -- FlavorRef       : 3
==> server-2:  -- Image           : Ubuntu 14.04 LTS
==> server-2:  -- ImageRef        : b8a89bd8-cf98-4902-a699-b2810341c59b
==> server-2:  -- KeyPair         : vagrant-generated-qhyppfck
==> server-2:  -- Network         : 5ba0f55a-9265-4d14-881c-9f291fa5b879
==> server-2:  -- Volume attached : 6e64c6b0-c26a-4a0c-b9c7-946e85ec3ab1 => auto
==> server-2: Waiting for the server to be built...
==> server-2: Using floating IP 172.22.203.58
==> server-2: Waiting for SSH to become available...
==> server-2: The server is ready!

Podemos acceder, por ejemplo, a la segunda instancia y comprobar que tiene un volumen asociado:

$ vagrant ssh server-2
Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.13.0-65-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  System information as of Sat Jan 16 17:00:51 UTC 2016

  System load:  0.4              Processes:           76
  Usage of /:   7.2% of 9.81GB   Users logged in:     0
  Memory usage: 5%               IP address for eth0: 192.168.0.4
  Swap usage:   0%

  Graph this data and manage this system at:
    https://landscape.canonical.com/

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.


Last login: Sat Jan 16 17:00:45 2016 from 172.19.0.26
ubuntu@server-2:~$ lsblk
NAME   MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda    253:0    0  10G  0 disk 
└─vda1 253:1    0  10G  0 part /
vdb    253:16   0   1G  0 disk

Y finalmente comprobamos con los clinetes nova y cinder que, efectivamente las instancias están conectadas a redes distintas y que tienen un volumen asociado:

$ nova list
+--------------------------------------+----------+---------+------------+-------------+---------------------------------+
| ID                                   | Name     | Status  | Task State | Power State | Networks                        |
+--------------------------------------+----------+---------+------------+-------------+---------------------------------+
| eac4c1e3-8be6-4061-8e59-85b3a891a1a5 | server-1 | ACTIVE  | -          | Running     | red=10.0.0.139, 172.22.203.243  |
| be1e8a5c-1498-4549-a68a-52a24b1a0fbf | server-2 | ACTIVE  | -          | Running     | red2=192.168.0.4, 172.22.203.58 |
+--------------------------------------+----------+---------+------------+-------------+---------------------------------+
$ cinder list
+--------------------------------------+--------+--------------+------+-------------+----------+--------------------------------------+
|                  ID                  | Status | Display Name | Size | Volume Type | Bootable |             Attached to              |
+--------------------------------------+--------+--------------+------+-------------+----------+--------------------------------------+
| 6e64c6b0-c26a-4a0c-b9c7-946e85ec3ab1 | in-use |     vol2     |  1   |     None    |  false   | be1e8a5c-1498-4549-a68a-52a24b1a0fbf |
| e34769d3-3bdc-43ed-ad31-837af2dc14ef | in-use |     vol1     |  1   |     None    |  false   | eac4c1e3-8be6-4061-8e59-85b3a891a1a5 |
+--------------------------------------+--------+--------------+------+-------------+----------+--------------------------------------+

Conclusiones

Aunque parece que el plagin de vagrant que estamos estudiando está en desarrollo y tiene funcionalidades limitadas, por ejemplo sólo se puede crear instancias, no se pueden crear otros recursos (volúmenes, redes, …), puede ser una forma sencilla de gestionar pequeños escenarios con openstack si estás acostumbrado a usar vagrant. En este artículo hemos visto muchas de las funcionalidades desarrolladas, pero existen más que puedes encontrar en la documentación.

Almacenamiento de objetos con Amazon Web Service S3

$
0
0

AWS-S3

En este artículo voy a hacer una introducción a Amazon Web Service S3 (Simple Storage Service), que nos ofrece un servicio de almacenamiento masivo de objetos. AWS S3 ofrece un almacén de objetos distribuido y altamente escalable. Se utiliza para almacenar grandes cantidades de datos de forma segura y económica. El almacenamiento de objetos es ideal para almacenar grandes ficheros multimedia o archivos grandes como copias de seguridad. Otra utilidad que nos ofrece es el almacenamiento de los datos estáticos de nuestra página web, que es lo que vamos a estudiar en esta entrada.

Conceptos sobre AWS S3

Para la organización de nuestros archivos, tenemos que conocer los siguientes conceptos:

  • buckets: son algo parecido a un directorio o carpeta de nuestro sistema operativo, donde colocaremos nuestros archivos. Los nombres de los buckets están compartidos entre toda la red de Amazon S3, por lo que si creamos un bucket, nadie más podrá usar ese nombre para un nuevo bucket.
  • objects: son las entidades de datos en sí, es decir, nuestros archivos. Un object almacena tanto los datos como los metadatos necesarios para S3, y pueden ocupar entre 1 byte y 5 Gigabytes.
  • keys: son una clave única dentro de un bucket que identifica a los objects de cada bucket. Un object se identifica de manera unívoca dentro de todo S3 mediante su bucket+key.
  • ACL: Podemos indicar el control de acceso a nuestro objetos, podremos dar capacidad de “Lectura”, “Escritura” o “Control Total”.

Más características de AWS S3

  • Uso de una API sencilla para la comunicación de nuestras aplicaciones con S3. Estas peticiones HTTP nos permitirán la gestión de los objetos, buckets, … En definitiva, todas las acciones necesarias para administrar nuestro S3. Toda la información en cuanto accesos y códigos de ejemplo se pueden encontrar en la documentación oficial.
  • Para la descargas de los objetos tenemos dos alternativas: si eres el propietario del objeto puedes hacer una llamada a la API para la descarga, sino, si hemos configurado el objeto con una ACL de lectura, podemos utilizar una URL para accder a él. Cada archivo en S3 posee una URL única, lo que nos facilitará mucho el poner a disposición de nuestros clientes todos los datos que almacenemos.
  • El servicio se paga por distintos conceptos: almacenamiento, transferencia, peticiones GET o PUT,… pero hay que tener en cuenta, siendo un servicio de Cloud Computing, que el pago se realiza por uso. La tarifas son baratas y las puedes consultar en la siguiente página.

Instalación y configuración del cliente de línea de comando AWS

La forma más cómoda de instalar el cliente de línea de comando en un sistema operativo GNU/Linux Debian es utilizando la utilidad pip, para ello ejecutamos los siguientes comandos:

# apt-get install python-pip
# pip install awscli

Podemos comprobar la versión que hemos instalado:

# aws --version
aws-cli/1.10.1 Python/2.7.9 Linux/3.16.0-4-amd64 botocore/1.3.23

La CLI de AWS nos permite gestionar todo lo relacionado con Amazon Web Services sin necesidad de acceder a la consola de administración web. Para poder utilizarlo tenemos que configurar el cliente para autentificarnos, especificando nuestro AWS Access Key ID y AWS Secret Access Key que hemos creado en la consola web:

aws1

Para realizar la configuración:

$ aws configure
 AWS Access Key ID [None]: AKIAIW2A7LBLHZKRQRNQ
 AWS Secret Access Key [None]: **********************************
 Default region name [None]:
 Default output format [None]:

Para trabajar con AWS S3 no hace falta indicar la región que vamos a usar, y el formato de salida tampoco lo hemos indicado.

Uso del cliente de línea de comando AWS

Para empezar vamos a comprobar si tenemos permiso para acceder a los recursos de nuestra cuenta en AWS S3, por ejemplo intentando visualizar la lista de buckets que tenemos:

$ aws s3 ls

A client error (AccessDenied) occurred when calling the ListBuckets operation: Access Denied

Como podemos comprobar tenemos que añadir una política de acceso para permitir el acceso a S3, para ello desde la consola web, nos vamos a la pestaña Permissions y añadimos una nueva política en la opción Attach Policy y escogemos: AmazonS3FullAccess:

aws2Y podemos comenzar a trabajar:

Listar, crear y eliminar buckets

Los “buckets” o “cubos” es el contenedor S3 donde se almacenarán los datos. Para crear uno, debemos elegir un nombre (tiene que ser válido a nivel de DNS y único). Para crear un nuevo buckets:

$ aws s3 mb s3://storage_pledin1
make_bucket: s3://storage_pledin1/

Para listar los buckets que tenemos creado:

$ aws s3 ls
2016-02-01 08:22:25 storage_pledin1

Y si quisiéramos borrarlo:

$ aws s3 rb s3://storage_pledin1
remove_bucket: s3://storage_pledin1/

Subir, descargar y eliminar objetos

Con la CLI de aws se incluyen los comandos cp, ls, mv, rm y sync. Todos ellos funcionan igual que en las shell de Linux. Sync es un añadido que permite sincronizar directorios completos. Vamos a ver unos ejemplos:

Copiar de local a remoto:

$ aws s3 cp BabyTux.png s3://storage_pledin1
upload: ./BabyTux.png to s3://storage_pledin1/BabyTux.png

Listar el contenido de un bucket:

$ aws s3 ls s3://storage_pledin1
2016-02-01 08:29:49     147061 BabyTux.png

Mover/renombrar un archivo remoto:

$ aws s3 mv s3://storage_pledin1/BabyTux.png s3://storage_pledin1/tux.png
move: s3://storage_pledin1/BabyTux.png to s3://storage_pledin1/tux.png

Copiar archivos remotos:

$ aws s3 cp s3://storage_pledin1/tux.png s3://storage_pledin1/tux2.png
copy: s3://storage_pledin1/tux.png to s3://storage_pledin1/tux2.png

$ aws s3 ls s3://storage_pledin1
2016-02-01 08:33:07     147061 tux.png
2016-02-01 08:34:01     147061 tux2.png

La parte de sincronización también es fácil de utilizar usando el comando rsync, indicamos el origen y el destino y sincronizará todos los archivos y directorios que contenga:

Sincronizar los ficheros de un directorio local a remoto:

$ $ aws s3 sync prueba/ s3://storage_pledin1/prueba
upload: prueba/fich2.txt to s3://storage_pledin1/prueba/fich2.txt
upload: prueba/fich1.txt to s3://storage_pledin1/prueba/fich1.txt
upload: prueba/fich3.txt to s3://storage_pledin1/prueba/fich3.txt

$ aws s3 ls s3://storage_pledin1/prueba/
2016-02-01 08:50:45          0 fich1.txt
2016-02-01 08:50:45          0 fich2.txt
2016-02-01 08:50:45          0 fich3.txt

Acceso a los objetos

Amazon S3 Access Control Lists (ACLs) nos permite manejar el acceso a los objetos y buckets de nuestro proyecto. Cuando se crea un objeto o un bucket se define una ACL que otorga control total (full control) al propietario del recurso. Podemos otorgar distintos permisos a usuarios y grupos de AWS. Para saber más sobre permisos en S3 puedes consultar el documento: Access Control List (ACL) Overview

En esta entrada nos vamos a conformar en mostrar una ACL que nos permita hacer público un objeto y de esta manera poder acceder a él. Para acceder a un recurso en S3 podemos hacerlo de dos maneras:

  • Si somos un usuario o pertenecemos a un grupo de AWS y el propietario del recurso nos ha dado permiso para acceder o modificar el recurso.
  • O, sin necesidad de estar autentificados, que el propietario del recurso haya dado permiso de lectura al recurso para todo el mundo, es decir lo haya hecho público. Esta opción es la que vamos a mostrar a continuación.

Anteriormente hemos subido una imagen (tux.png) como objeto a nuestro bucket, como hemos dicho cuando se crea el objeto, por defecto se declara como privado. Si accedemos a dicho recurso utilizando la siguiente URL:

https://s3.amazonaws.com/storage_pledin1/tux.png

Vemos como el control de acceso no nos deja acceder al recurso:
aws3Cunado copiamos el objeto al bucket podemos indicar la ACL para hacer le objeto público, de la siguiente manera:

aws s3 cp tux.png s3://storage_pledin1 --acl public-read-write

De tal manera que ahora podemos acceder al recurso:

aws4

Conclusiones

El uso del almacenamiento de objetos no es algo nuevo en la informática, pero si ha tenido una gran importancia en los últimos tiempo con el uso masivo que se hace de los entorno IaaS de Cloud Computing. En este artículo he intentado hacer una pequeña introducción a la solución de almacenamiento de objetos que nos ofrece AWS, aunque los conceptos son muy similares y totalmente transportables a otras soluciones, como puede ser OpenStack y su componente de almacenamiento de objetos Swift.

Primeros pasos con Docker

$
0
0

docker2En una entrada anterior, veíamos los fundamentos de docker, y repasábamos los principales componentes de la arquitectura de docker:

  • El cliente de Docker es la principal interfaz de usuario para docker, acepta los comandos del usuario y se comunica con el daemon de docker.
  • Imágenes de Docker (Docker Images): Las imágenes de Docker son plantillas de solo lectura, es decir, una imagen puede contener el sistema de archivo de un sistema operativo como Debian, pero esto solo nos permitirá crear los contenedores basados en esta configuración. Si hacemos cambios en el contenedor ya lanzado, al detenerlo esto no se verá reflejado en la imagen.
  • Registros de Docker (Docker Registries): Los registros de Docker guardan las imágenes, estos son repositorios públicos o privados donde podemos subir o descargar imágenes. El registro público del proyecto se llama Docker Hub y es el componente de distribución de Docker.
  • Contenedores de Docker (Docker Containers): El contenedor de docker aloja todo lo necesario para ejecutar una aplicación. Cada contenedor es creado de una imagen de docker. Cada contenedor es una plataforma aislada.

Instalación de docker

Vamos a instalar docker engine en un equipo con Debian Jessie, para ello, en primer lugar nos aseguramos de instalar el paquete que permite trabajar a la utilidad apt con htpps, y los certificados CA:

$ apt-get update
$ apt-get install apt-transport-https ca-certificates

A continuación añadimos la clave GPG:

$ apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

Y añadimos el nuevo repositorio:

$ nano /etc/apt/sources.list.d/docker.list

Indicando el repositorio para Debian Jessie:

deb https://apt.dockerproject.org/repo debian-jessie main

Y ya estamos en disposición de realizar la instalación:

$ apt-get update
$ apt-get install docker-engine
$  docker version
Client:
Version:      1.10.0
API version:  1.22
Go version:   go1.5.3
Git commit:   590d5108
Built:        Thu Feb  4 18:16:19 2016
OS/Arch:      linux/amd64

Server:
Version:      1.10.0
API version:  1.22
Go version:   go1.5.3
Git commit:   590d5108
Built:        Thu Feb  4 18:16:19 2016
OS/Arch:      linux/amd64

Ejecutando docker con un usuario sin privilegios

El demonio docker cuando se indicia siempre se ejecuta como root y espera una conexión a un socket unix y no a un puerto TCP. Por defecto el socker unix es propiedad del usuario root por lo que debemos usar el cliente docker como root. Para solucionar esto podemos añadir nuestro usuario sin privilegio al grupo docker, todos los usuarios pertenecientes a este grupo tienen permiso de lectura y escritura sobre el socket por lo que podremos conectar al docker engine desde nuestro usuario.

# usermod -a -G docker usuario

Y ya podemos usar el usuario para utilizar el cliente docker.

Acceder a docker engine desde otra máquina

En ocasiones es preferible tener instalado el cliente docker y el demonio en diferentes máquinas, para ello hay que configurar el docker engine para que escuche por un puerto TCP, para ello:

$ nano /etc/systemd/system/multi-user.target.wants/docker.service
...
 ExecStart=/usr/bin/docker daemon -H fd:// -H tcp://0.0.0.0:2376
 ...
systemctl daemon-reload
service docker restart

En la máquina cliente instalamos el cliente docker:

# apt-get install docker.io

Y podemos utilizarlo indicando la dirección ip y el puerto del docker daemon:

$ docker -H 192.168.0.100:2376 ps
 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

En esta entrada vamos a utilizar el cliente docker desde la misma máquina donde tenemos el demonio.

Nuestro primer contenedor “Hola Mundo”

Para crear nuestro primer contenedor vamos a ejecutar la siguiente instrucción:

$ docker run ubuntu /bin/echo 'Hello world'
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
8387d9ff0016: Pull complete 
3b52deaaf0ed: Pull complete 
4bd501fad6de: Pull complete 
a3ed95caeb02: Pull complete 
Digest: sha256:457b05828bdb5dcc044d93d042863fba3f2158ae249a6db5ae3934307c757c54
Status: Downloaded newer image for ubuntu:latest
Hello world

Con el comando run vamos a crear un contenedor donde vamos a ejecutar un comando, en este caso vamos a crear el contenedor a partir de una imagen ubuntu. Como todavía no hemos descargado ninguna imagen del registro docker hub, es necesario que se descargue la  imagen. Si la tenemos ya en nuestro ordenador no será necesario la descarga. Más adelante estudiaremos como funcionan las imágenes en docker. Finalmente indicamos el comando que vamos a ejecutar en el contenedor, en este caso vamos a escribir un “Hola Mundo”.

Por lo tanto, cuando se finaliza la descarga de la imagen, se crea un contenedor a partir de la imagen y se ejecuta el comando que hemos indicado, podemos ver la salida en pantalla. Una vez que se ejecuta la instrucción el contenedor se detiene (stop), podemos ver la lista de contenedores detenidos con el siguiente comando:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                          PORTS                  NAMES
b0ca02c7b956        ubuntu              "/bin/echo 'Hello wor"   About a minute ago   Exited (0) About a minute ago                          boring_jennings

Ejecutando un contenedor interactivo

En este caso usamos la opción -i para abrir una sesión interactiva, -t nos permite crear un pseoude-terminal que nos va a permitir interaccionar con el contenedor, indicamos un nombre del contenedor con la opción –name, y la imagen que vamos a utilizar para crearlo, en este caso “ubuntu”,  y por último el comando que vamos a ejecutar, en este caso /bin/bash, que lanzará una sesión bash en el contenedor:

$ docker run -i -t --name contenedor1 ubuntu /bin/bash 
root@2bfa404bace0:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@2bfa404bace0:/# exit
$

Como podemos comprobar podemos ejecutar distintos comandos dentro del contenedor, por ejemplo hemos visto el árbol de directorios. Cuando salimos de la sesión, el contenedor se detiene. De nuevo podemos ver los contenedores detenidos:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                        PORTS                  NAMES
2bfa404bace0        ubuntu              "/bin/bash"              13 minutes ago      Exited (0) 9 minutes ago                             contenedor1
b0ca02c7b956        ubuntu              "/bin/echo 'Hello wor"   21 minutes ago      Exited (0) 15 minutes ago                            boring_jennings

A continuación vamos a iniciar el contenedor y nos vamos a conectar a él:

$ docker start contendor1
contendor1
$ docker attach contendor1
root@2bfa404bace0:/#

Para obtener información del contenedor:

$ docker inspect contenedor1
[
    {
        "Id": "2bfa404bace09e244df4528e41f94523dcaa4f8ddeae992259fde0d2151eea03",
        "Created": "2016-02-08T21:14:56.157787821Z",
        "Path": "/bin/bash",
        "Args": [],
        "State": {
            "Status": "exited",
            "Running": false,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 0,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2016-02-08T21:14:56.40323815Z",
            "FinishedAt": "2016-02-08T21:18:37.272925413Z"
        },
        "Image": "sha256:3876b81b5a81678926c601cd842040a69eb0456d295cd395e7a895a416cf7d91",
        "ResolvConfPath": "/var/lib/docker/containers/2bfa404bace09e244df4528e41f94523dcaa4f8ddeae992259fde0d2151eea03/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/2bfa404bace09e244df4528e41f94523dcaa4f8ddeae992259fde0d2151eea03/hostname",
        "HostsPath": "/var/lib/docker/containers/2bfa404bace09e244df4528e41f94523dcaa4f8ddeae992259fde0d2151eea03/hosts",
        "LogPath": "/var/lib/docker/containers/2bfa404bace09e244df4528e41f94523dcaa4f8ddeae992259fde0d2151eea03/2bfa404bace09e244df4528e41f94523dcaa4f8ddeae992259fde0d2151eea03-json.log",
        "Name": "/contenedor1",
       ...

Creando un contenedor demonio

Crear un contenedor que ejecute una sola instrucción y que luego se pare no es muy útil, a continuación vamos a crear un contenedor que funcione como un demonio y este continuamente ejecutándose.

$ docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
7b6c3b1c0d650445b35a1107ac54610b65a03eda7e4b730ae33bf240982bba08

En esta ocasión hemos utilizado la opción -d del comando run, para la ejecución el comando en el contenedor se haga en segundo plano. La salida que hemos obtenido el el ID del contenedor que se está ejecutando, aunque cuando visualizamos los contenedores en ejecución sólo vemos un ID corto:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7b6c3b1c0d65        ubuntu              "/bin/sh -c 'while tr"   2 minutes ago       Up 2 minutes                            happy_euclid

Podemos ver la salida del contenedor accediendo a los logs del contenedor, indicando el id o el nombre que se ha asignado:

$ docker logs happy_euclid
hello world
hello world
hello world
hello world
...

Por último podemos parar el contenedor y borrarlo con las siguientes instrucciones:

$ docker stop happy_euclid
$ docker rm happy_euclid

Conclusiones

En esta primera entrada hemos hecho una introducción a la instalación de docker y hemos comenzado a crear contenedores. Realmente estos contenedores no nos sirven de mucho ya que ejecutan instrucciones muy básicas. En la próxima entrada vamos a trabajar com imágenes docker y vamos a profundizar en la creación de contenedores para ejecutar aplicaciones web.

La imagen de cabecera ha sido tomado de la URL: http://www.jsitech.com/generales/docker-fundamentos/

Ejecutando una aplicación web en docker

$
0
0

engine

Seguimos profundizando en el uso de contenedores con docker. En la pasada entrada, hicimos una introducción al uso de docker creando nuestros primeros contenedores, en esta entrada vamos a profundizar en la gestión de imágenes docker y en la creación de un contenedor que nos ofrezca un servicio, más concretamente que ejecute un servidor web y que nos ofrezcan una página web estática.

Trabajando con imágenes

Vamos a descargar una imagen del sistema operativo GNU/Linux Debian del registro público docker hub. Normalmente el nombre de las imágenes tienen la forma usuario/nombre:etiqueta, si no indicamos la etiqueta será latest. Por ejemplo el nombre de una imagen puede ser nuagebec/ubuntu:15.04

$ docker pull debian
 Using default tag: latest
 latest: Pulling from library/debian
 03e1855d4f31: Pull complete
 a3ed95caeb02: Pull complete
 Digest: sha256:d2ea9df44c61c1e3042c20dd42bf57a86bd48bb428e154bdd1d1003fad6810a4
 Status: Downloaded newer image for debian:latest

Como podemos ver cada imagen está formado por distintas capas, que corresponden a parte del sistema de fichero que forman la imagen. Las capas que son comunes a varias imágenes se comparten entre ellas y por lo tanto no es necesario bajarlas. Vamos a bajar otra imagen:

$ docker pull ubuntu:14.04

Y podemos ver todas las imágenes que tenemos en nuestro equipo local con:

$ docker images
 REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 debian              latest              9a02f494bef8        12 days ago         125.1 MB
 ubuntu              14.04               3876b81b5a81        2 weeks ago         187.9 MB

Para obtener información sobre una imagen, ejecutamos el siguiente comando:

docker inspect debian
 [
 {
 "Id": "sha256:9a02f494bef8d0d088ee7533aa1ba4aaa1dbf38a97192d36fa79a51279bc04de",
 "RepoTags": [
 "debian:latest"
 ],
 "RepoDigests": [],
 "Parent": "",
 "Comment": "",
 "Created": "2016-01-25T22:24:37.914712562Z",
 "Container": "c59024072143b04b79ac341c51571fc698636e01c13b49c523309c84af4b70fe",
 "ContainerConfig": {
 "Hostname": "e06f5a03fe1f",
 "Domainname": "",
 "User": "",
 "AttachStdin": false,
 "AttachStdout": false,
 "AttachStderr": false,
 "Tty": false,
 "OpenStdin": false,
 "StdinOnce": false,
 "Env": null,
 "Cmd": [
 "/bin/sh",
 "-c",
 "#(nop) CMD [\"/bin/bash\"]"
 ],
...

Para buscar imágenes en docker hub, podemos utilizar la siguiente instrucción:

$ docker search debian
 NAME                           DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
 ubuntu                         Ubuntu is a Debian-based Linux operating s...   3152      [OK]
 debian                         Debian is a Linux distribution that's comp...   1103      [OK]
 neurodebian                    NeuroDebian provides neuroscience research...   15        [OK]
 jesselang/debian-vagrant       Stock Debian Images made Vagrant-friendly ...   7                    [OK]
 armbuild/debian                ARMHF port of debian                            6                    [OK]
 mschuerig/debian-subsonic      Subsonic 5.1 on Debian/wheezy.                  4                    [OK]
 eboraas/debian                 Debian base images, for all currently-avai...   3                    [OK]
 datenbetrieb/debian            minor adaption of official upstream debian...   1                    [OK]
 maxexcloo/debian               Docker base image built on Debian with Sup...   1                    [OK]
 reinblau/debian                Debian with usefully default packages for ...   1                    [OK]
 webhippie/debian               Docker images for debian                        1                    [OK]
 eeacms/debian                  Docker image for Debian to be used with EE...   1                    [OK]
 lephare/debian                 Base debian images                              1                    [OK]
 lucasbarros/debian             Basic image based on Debian                     1                    [OK]
 servivum/debian                Debian Docker Base Image with Useful Tools      1                    [OK]
 fike/debian                    Debian Images with language locale installed.   0                    [OK]
 nimmis/debian                  This is different version of Debian with a...   0                    [OK]
 visono/debian                  Docker base image of debian 7 with tools i...   0                    [OK]
 opennsm/debian                 Lightly modified Debian images for OpenNSM      0                    [OK]
 thrift/debian                  build/docker/debian                             0                    [OK]
 maticmeznar/debian             A nice Debian template.                         0                    [OK]
 pl31/debian                    Debian base image.                              0                    [OK]
 mariorez/debian                Debian Containers for PHP Projects              0                    [OK]
 icedream/debian-jenkinsslave   Debian for Jenkins to be used as slaves.        0                    [OK]
 konstruktoid/debian            Debian base image                               0                    [OK]

Y para borrar una imagen:

docker rmi ubuntu:14.04

“Dockerizando” un servidor web apache

Como primera aproximación para crear una imagen desde la que podamos crear un contenedor que ofrezca un servicio, como, por ejemplo, un servidor web, daremos los siguientes pasos:

  • Crearemos un contenedor desde una imagen base, por ejemplo desde la imagen “debian”.
  • Accederemos a ese contenedor e instalaremos un servidor web apache2.
  • Crearemos una nueva imagen a partir de este contenedor que nos permitirá crear nuevos contenedores con el servidor web instalado.

Hay que indicar que el método que vamos a utilizar no es el que se usa habitualmente y que en la próxima entrada estudiaremos el procedimiento habitual para conseguirlo, que será generar directamente una imagen con un servidor web instalado utilizando para ello un fichero Dockerfile y el comando docker build. De todas maneras vamos a mostrar este ejemplo que nos puede ser muy útil para seguir profundizando en el funcionamiento de docker.

En primer lugar vamos a crear un contenedor desde la imagen “debian” que habíamos descargado, para ello vamos a ejecutar la siguiente instrucción:

$ docker run -i -t --name primero debian /bin/bash
root@61ee5966f2f3:/# apt-get update
root@61ee5966f2f3:/# apt-get install apache2
root@61ee5966f2f3:/#exit

Como vemos, hemos instalado en el contenedor el servidor web apache. Cuando se crea un contenedor, su sistema de fichero estará formado por la unión de las capas de la imagen origen, es decir tendrá el sistema de ficheros que tiene la imagen. Estas capas que forman el sistema de archivo del contenedor tienen dos características: son compartidas entre contenedores distintos que se crean desde la misma imagen, y son de sólo lectura, no se pueden modificar, por lo que cuando se crea un nuevo contenedor se añade una nueva capa de lectura y escritura donde se va registrando la creación de los nuevos ficheros y la modificación y borrado de los ficheros existentes.

Por lo tanto el sistema de archivo de nuestro contenedor estará formado por tres capas: las dos originales de la imagen “debian”, y una tercera donde hemos creado los ficheros al instalar el servidor web. Cuando salimos de un contenedor de una sesión interactiva el contenedor se detiene (stop). Podemos ver la lista de contenedores detenidos:

$ docker ps -a
 CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
 61ee5966f2f3        debian              "/bin/bash"         8 minutes ago       Exited (0) 3 seconds ago                       primero

Una vez que hemos hecho una modificación en nuestro contenedor, si queremos utilizar el servidor web deberíamos crear una nueva imagen a partir del contenedor, para ello:

$ docker commit -m "Añadido apache" -a "José Domingo" 61ee5966f2f3 jose/apache:v1

Con commit vamos a crear una nueva imagen en nuestro repositorio local, se indica un comentario con la opción -m, el autor con la opción -a, el identificador del contenedor y finalmente el nombre de la nueva imagen.

$ docker images
 REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 jose/apache         v1                  81aeeac1781a        9 seconds ago       193.3 MB
 debian              latest              9a02f494bef8        12 days ago         125.1 MB

Creando un nuevo contenedor con el servidor web

Ahora vamos a crear un nuevo contenedor a partir de la imagen que hemos creado anteriormente: jose/apache:v1. Este contenedor lo vamos a crear usando la opción -d que nos permite “demonizar” el contenedor, es decir que se ejecute indefinidamente, para ello:

$ docker run -d -p 8000:80 --name segundo jose/apache:v1 /usr/sbin/apache2ctl -D FOREGROUND
 85218e86d6e48f360eb1c6e49c62da5e4ffd94b00308734e84f0253e72ec647d
$ docker ps
 CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
 85218e86d6e4        jose/apache:v1      "/usr/sbin/apache2ctl"   3 seconds ago       Up 2 seconds        0.0.0.0:8000->80/tcp   segundo

Vemos que el contenedor se está ejecutando, además con la opción -p mapeamos un puerto del equipo donde tenemos instalado el docker engine, con un puerto del contenedor. Además la instrucción que se ejecuta en el contenedor es la que nos permite ejecutar apache2 en segundo plano. En este caso podemos ver que accediendo al puerto 8000 de nuestro servidor docker accederemos al puerto 80 del contenedor.

docker1

Podemos ver los procesos que se están ejecutando en nuestro contenedor:

$ docker top segundo
 UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
 root                11211               7597                0                   20:49               ?                   00:00:00            /bin/sh /usr/sbin/apache2ctl -D FOREGROUND
 root                11227               11211               0                   20:49               ?                   00:00:00            /usr/sbin/apache2 -D FOREGROUND
 www-data            11228               11227               0                   20:49               ?                   00:00:00            /usr/sbin/apache2 -D FOREGROUND
 www-data            11229               11227               0                   20:49               ?                   00:00:00            /usr/sbin/apache2 -D FOREGROUND

Para para nuestro contenedor:

$ docker stop segundo

Y lo podemos volver a ejecutar:

$ docker start segundo

O en un solo paso:

$ docker restart segundo

Y finalmente para borrar el contenedor:

$ docker stop segundo
$ docker rm segundo

Conclusiones

En esta entrada hemos aprendido a gestionar las imágenes que nos podemos descargar del registro docker hub, además hemos estudiado la posibilidad de crear nuevas imágenes a partir de contenedores, y de esta forma crear imágenes que nos permitan crear nuevos contenedores que ofrezcan un determinado servicio. En la próxima entrada vamos a introducir el mecanismo habitual que nos permite la creación de imágenes docker a partir de su definición en ficheros Dockerfile y el comando docker build.


Dockerfile: Creación de imágenes docker

$
0
0

dockerfileEn la entrada anterior, estudiamos un método para crear nuevas imágenes a partir de contenedores que anteriormente habíamos configurado. En esta entrada vamos a presentar la forma más usual de crear nuevas imágenes: usando el comando docker buid y definiendo las características que queremos que tenga la imagen en un fichero Dockerfile.

¿Cómo funciona docker build?

Un Dockerfile es un fichero de texto donde indicamos los comandos que queremos ejecutar sobre una imagen base para crear una nueva imagen. El comando docker build construye la nueva imagen leyendo las instrucciones del fichero Dockerfile y la información de un entorno, que para nosotros va a ser un directorio (aunque también podemos guardar información, por ejemplo, en un repositorio git).

La creación de la imagen es ejecutada por el docker engine, que recibe toda la información del entorno, por lo tanto es recomendable guardar el Dockerfile en un directorio vacío y añadir los ficheros necesarios para la creación de la imagen. El comando docker build ejecuta las instrucciones de un Dockerfile línea por línea y va mostrando los resultados en pantalla.

Tenemos que tener en cuenta que cada instrucción ejecutada crea una imagen intermedia, una vez finalizada la construcción de la imagen nos devuelve su id. Alguna imágenes intermedias se guardan en caché, otras se borran. Por lo tanto, si por ejemplo, en un comando ejecutamos cd /scripts/ y en otra linea le mandamos a ejecutar un script (./install.sh) no va a funcionar, ya que ha lanzado otra imagen intermedia. Teniendo esto en cuenta, la manera correcta de hacerlo sería:

cd /scripts/;./install.sh

Para terminar indicar que la creación de imágenes intermedias generadas por la ejecución de cada instrucción del Dockerfile, es un mecanismo de caché, es decir, si en algún momento falla la creación de la imagen, al corregir el Dockerfile y volver a construir la imagen, los pasos que habían funcionado anteriormente no se repiten ya que tenemos a nuestra disposición las imágenes intermedias, y el proceso continúa por la instrucción que causó el fallo.

Buenas prácticas al crear Dockerfile

Los contenedores deber ser “efímeros”

Cuando decimos “efímeros” queremos decir que la creación, parada, despliegue de los contenedores creados a partir de la imagen que vamos a generar con nuestro Dockerfile debe tener una mínima configuración.

Uso de ficheros .dockerignore

Como hemos indicado anteriormente, todos los ficheros del contexto se envían al docker engine, es recomendable usar un directorio vacío donde vamos creando los ficheros que vamos a enviar. Además, para aumentar el rendimiento, y no enviar al daemon ficheros innecesarios podemos hacer uso de un fichero .dockerignore, para excluir ficheros y directorios.

No instalar paquetes innecesarios

Para reducir la complejidad, dependencias, tiempo de creación y tamaño de la imagen resultante, se debe evitar instalar paquetes extras o innecesarios Si algún paquete es necesario durante la creación de la imagen, lo mejor es desinstalarlo durante el proceso.

Minimizar el número de capas

Debemos encontrar el balance entre la legibilidad del Dockerfile y minimizar el número de capa que utiliza.

Indicar las instrucciones a ejecutar en múltiples líneas

Cada vez que sea posible y para hacer más fácil futuros cambios, hay que organizar los argumentos de las instrucciones que contengan múltiples líneas, esto evitará la duplicación de paquetes y hará que el archivo sea más fácil de leer. Por ejemplo:

RUN apt-get update && apt-get install -y \
git \
wget \
apache2 \
php5

Instrucciones de Dockerfile

En este apartado vamos a hacer una introducción al uso de las instrucciones más usadas que podemos definir dentro de un fichero Dockerfile, para una descripción más detallada consulta la documentación oficial.

FROM

FROM indica la imagen base que va a utilizar para seguir futuras instrucciones. Buscará si la imagen se encuentra localmente, en caso de que no, la descargará de internet.

Sintaxis

FROM <imagen>
FROM <imagen>:<tag>

MAINTAINER

Esta instrucción nos permite configurar datos del autor que genera la imagen.
Sintaxis

MAINTAINER <nombre> <Correo>

RUN

Esta instrucción ejecuta cualquier comando en una capa nueva encima de una imagen y hace un commit de los resultados. Esa nueva imagen intermedia es usada para el siguiente paso en el Dockerfile. RUN tiene 2 formatos:

  • El modo shell: /bin/sh -c
RUN comando
  • Modo ejecución:
RUN ["ejecutable", "parámetro1", "parámetro2"]

El modo ejecución nos permite correr comandos en imágenes bases que no cuenten con /bin/sh , nos permite además hacer uso de otra shell si así lo deseamos, ejemplo:

RUN ["/bin/bash", "-c", "echo prueba"]

ENV

Esta instrucción configura las variables de ambiente, estos valores estarán en los ambientes de todos los comandos que sigan en el Dockerfile.

Sintaxis

ENV <key> <value>
ENV <key>=<value> ...

Estos valores persistirán al momento de lanzar un contenedor de la imagen creada y pueden ser usados dentro de cualquier fichero del entorno, por ejemplo un script ejecutable. Pueden ser sustituida pasando la opción -env en docker run. Ejemplo:

docker run -env <key>=<valor>

ADD

Esta instrucción copia los archivos o directorios de una ubicación especificada y los agrega al sistema de archivos del contenedor en la ruta especificada. Tiene dos formas:

Sintaxis

ADD <src>... <dest>
ADD ["<src>",... "<dest>"]

EXPOSE

Esta instrucción le especifica a docker que el contenedor escucha en los puertos especificados en su ejecución. EXPOSE no hace que los puertos puedan ser accedidos desde el host, para esto debemos mapear los puertos usando la opción -p en docker run.

Ejemplo:

EXPOSE 80 443

CMD y ENTRYPOINT

Estas dos instrucciones son muy parecidas, aunque se utilizan en situaciones diferentes, y además pueden ser usadas conjuntamente, en el siguiente artículo se explica muy bien su uso.

Estas dos instrucciones nos permiten especificar el comando que se va a ejecutar por defecto, sino indicamos ninguno cuando ejecutamos el docker run. Normalmente las imágenes bases (debian, ubuntu,…) están configuradas con estas instrucciones para ejecutar el comando /bin/sh o /bin/bash. Podemos comprobar el comando por defecto que se ha definido en una imagen con el siguiente comando:

$ docker inspect debian
...
 "Cmd": [
                "/bin/bash"
            ],
...

Por lo tanto no es necesario indicar el comando como argumento, cuando se inicia un contenedor:

$ docker run -i -t  debian

En el siguiente gráfico puedes ver los detalles de algunas imágenes oficiales: su tamaño, las capas que la conforman y el comando que se define por defecto:

image-layers

CMD

CMD tiene tres formatos:

  • Formato de ejecución:
CMD ["ejecutable", "parámetro1", "parámetro2"]
  • Modo shell:
CMD comando parámetro1 parámetro2
  • Formato para usar junto a la instrucción ENTRYPOINT
CMD ["parámetro1","parámetro2"]

Solo puede existir una instrucción CMD en un Dockerfile, si colocamos más de una, solo la última tendrá efecto.Se debe usar para indicar el comando por defecto que se va a ejecutar al crear el contenedor, pero permitimos que el usuario ejecute otro comando al iniciar el contenedor.

ENTRYPOINT

ENTRYPOINT tiene dos formatos:

  • Formato de ejecución:
ENTRYPOINT ["ejecutable", "parámetro1", "parámetro2"]
  • Modo shell:
ENTRYPOINT comando parámetro1 parámetro2

Esta instrucción también nos permite indicar el comando que se va ejecutar al iniciar el contenedor, pero en este caso el usuario no puede indicar otro comando al iniciar el contenedor. Si usamos esta instrucción no permitimos o no  esperamos que el usuario ejecute otro comando que el especificado. Se puede usar junto a una instrucción CMD, donde se indicará los parámetro por defecto que tendrá el comando indicado en el ENTRYPOINT. Cualquier argumento que pasemos en la línea de comandos mediante docker run serán anexados después de todos los elementos especificados mediante la instrucción ENTRYPOINT, y anulará cualquier elemento especificado con CMD.

Ejemplo:

Si tenemos un fichero Dockerfile, que tiene las siguientes instrucciones:

ENTRYPOINT [“http”, “-v ]”
CMD [“-p”, “80”]

Podemos crear un contenedor a partir de la imagen generada:

  • docker run centos:centos7: Se creará el contenedor con el servidor web escuchando en el puerto 80.
  • docker run centos:centros7 -p 8080: Se creará el contenedor con el servidor web escuchando en el puerto 8080.

Conclusiones

En esta entrada hemos estudiado los fundamentos y conceptos necesarios para crear imágenes a partir de fichero Dockerfile y el comando docker build. En la próxima entrada vamos a estudiar algunos ejemplos de ficheros Dockerfile, y la creación de contenedores a partir de las imágenes que vamos a generar.

Ejemplos de ficheros Dockerfile, creando imágenes docker

$
0
0

En la entrada: Dockerfile: Creación de imágenes docker, estudiamos el mecanismo de creación de imágenes docker, con el comando docker buid y los ficheros Dockerfile. En esta entrada vamos a estudiar algunos ejemplos de ficheros Dockerfile y cómo creamos y usamos las imágenes generadas a partir de ellos.

Tenemos dos imágenes en nuestro sistema, que son las que vamos a utilizar como imágenes base para crear nuestras imágenes:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
debian              latest              9a02f494bef8        2 weeks ago         125.1 MB
ubuntu              14.04               3876b81b5a81        3 weeks ago         187.9 MB

Creación una imagen con el servidor web Apache2

En este caso vamos a crear un directorio nuevo que va a ser el contexto donde podemos guardar los ficheros que se van a enviar al docker engine, en este caso el fichero index.html que vamos a copiar a nuestro servidor web:

$ mkdir apache
$ cd apache
~/apache$ echo "<h1>Prueba de funcionamiento contenedor docker</h1>">index.html

En ese directorio vamos a crear un fichero Dockerfile, con el siguiente contenido:

FROM debian
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

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

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

En este caso utilizamos la imagen base de debian, instalamos el servidor web apache2, para reducir el tamaño, borramos la caché de paquetes apt y la lista de paquetes descargada, creamos varias variables de entorno (en este ejemplo no se van a utilizar, pero se podrían utilizar en cualquier fichero del contexto, por ejemplo para configurar el servidor web), exponemos el puerto http TCP/80, copiamos el fichero index.html al DocumentRoot y finalmente indicamos el comando que se va a ejecutar al crear el contenedor, y además, al usar el comando ENTRYPOINT, no permitimos ejecutar ningún otro comando durante la creación.

Vamos a generar la imagen:

~/apache$ docker build -t josedom24/apache2:1.0 .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM debian
 ---> 9a02f494bef8
Step 2 : MAINTAINER José Domingo Muñoz "josedom24@gmail.com"
 ---> Running in 76f3f8fe0719
 ---> fda7bdbf761c
Removing intermediate container 76f3f8fe0719
Step 3 : RUN apt-get update && apt-get install -y apache2 && apt-get clean && rm -rf /var/lib/apt/lists/*
 ---> Running in c50b14cc967d
Get:1 http://security.debian.org jessie/updates InRelease [63.1 kB]
Get:2 http://security.debian.org jessie/updates/main amd64 Packages [256 kB]
Ign http://httpredir.debian.org jessie InRelease
...
 ---> 0dedcfe17eb9
Removing intermediate container c50b14cc967d
Step 4 : ENV APACHE_RUN_USER www-data
 ---> Running in 85a85c09f96c
 ---> dac18d113b15
Removing intermediate container 85a85c09f96c
Step 5 : ENV APACHE_RUN_GROUP www-data
 ---> Running in 9e7511d92c74
 ---> 8f5824bdc71a
Removing intermediate container 9e7511d92c74
Step 6 : ENV APACHE_LOG_DIR /var/log/apache2
 ---> Running in 1b9173a822f8
 ---> 313e04f3a33a
Removing intermediate container 1b9173a822f8
Step 7 : EXPOSE 80
 ---> Running in 001ce73f08a6
 ---> 76f798e8d481
Removing intermediate container 001ce73f08a6
Step 8 : ADD index.html /var/www/html
 ---> 5ce11ae0b1e6
Removing intermediate container c8f418d3a0f6
Step 9 : ENTRYPOINT /usr/sbin/apache2ctl -D FOREGROUND
 ---> Running in 4ba6954632a5
 ---> 9109b0f27a08
Removing intermediate container 4ba6954632a5
Successfully built 9109b0f27a08

Generamos la nueva imagen con el comando docker build con la opción -t indicamos el nombre de la nueva imagen (para indicar el nombre de la imagen es recomendable usar nuestro nombre de usuario en el registro docker hub, para posteriormente poder guardarlas en el registro), mandamos todos los ficheros del contexto (indicado con el punto). Podemos comprobar que tenemos generado la nueva imagen:

$ docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
josedom24/apache2   1.0                 9109b0f27a08        4 minutes ago       183.7 MB
debian              latest              9a02f494bef8        2 weeks ago         125.1 MB
ubuntu              14.04               3876b81b5a81        3 weeks ago         187.9 MB

A continuación podemos crear un nuevo contenedor a partir de la nueva imagen:

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

Comprobamos que el contenedor está creado:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
67013f91ba65        josedom24/apache    "/usr/sbin/apache2ctl"   4 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp   servidor_web

Y podemos acceder al servidor docker, para ver la página web:
dockerfile1

Creación una imagen con el servidor de base de datos mysql

En esta ocasión vamos a tener un contexto, un directorio con los siguientes ficheros:

~/mysql$ ls
Dockerfile  my.cnf  script.sh

El fichero de configuración de mysql, my.cnf:

[mysqld]
bind-address=0.0.0.0
console=1
general_log=1
general_log_file=/dev/stdout
log_error=/dev/stderr

Un script bash, que va a ser el que se va a ejecutar por defecto cunado se crea un contenedor, script.sh:

#!/bin/bash
set -e

chown -R mysql:mysql /var/lib/mysql
mysql_install_db --user mysql > /dev/null

MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-""}
MYSQL_DATABASE=${MYSQL_DATABASE:-""}
MYSQL_USER=${MYSQL_USER:-""}
MYSQL_PASSWORD=${MYSQL_PASSWORD:-""}

tfile=`mktemp`
if [[ ! -f "$tfile" ]]; then
    return 1
fi

cat << EOF > $tfile
USE mysql;
FLUSH PRIVILEGES;
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
UPDATE user SET password=PASSWORD("$MYSQL_ROOT_PASSWORD") WHERE user='root';
EOF

if [[ $MYSQL_DATABASE != "" ]]; then
    echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` CHARACTER SET utf8 COLLATE utf8_general_ci;" >> $tfile

    if [[ $MYSQL_USER != "" ]]; then
        echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* to '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD';" >> $tfile
    fi
fi

/usr/sbin/mysqld --bootstrap --verbose=0 < $tfile
rm -f $tfile

exec /usr/sbin/mysqld

Podemos ver que hace uso de varias variables de entorno:

MYSQL_ROOT_PASSWORD
MYSQL_DATABASE
MYSQL_USER
MYSQL_PASSWORD

Que nos permiten especificar la contraseña del root, el nombre de una base de datos a crear, el nombre y contraseña de un nuevo usuario a crear.  Estas variables de entorno se pueden indicar en el fichero Dockerfile, o con el parámetro --env en el comando docker run.

El fichero Dockerfile tendrá el siguiente contenido:

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

RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y mysql-server

ADD my.cnf /etc/mysql/conf.d/my.cnf 
ADD script.sh /usr/local/bin/script.sh
RUN chmod +x /usr/local/bin/script.sh

EXPOSE 3306

CMD ["/usr/local/bin/script.sh"]

Como vemos se instala mysql, se copia el fichero de configuración y el script en bash que es el comando que se va a ejecutar por defecto al crear los contenedores. Generamos la imagen:

~/mysql$ docker build -t josedom24/mysql:1.0 .

Y creamos un contenedor indicando la contraseña del root:

$ docker run -d -p 3306:3306 --env MYSQL_ROOT_PASSWORD=asdasd --name servidor_mysql jose/mysql:1.0
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
8635e1392523        josedom24/mysq      "/usr/local/bin/scrip"   3 seconds ago       Up 3 seconds        0.0.0.0:3306->3306/tcp   servidor_mysql

Y accedemos al servidor mysql, utilizando la ip del servidor docker:

mysql -u root -p -h 192.168.0.100

Este ejemplo se basa en la imagen mysql que podemos encontrar en docker hub: https://hub.docker.com/r/mysql/mysql-server/

Creación una imagen con con php a partir de nuestra imagen con apache2

En este último ejemplo, vamos a crear una imagen con php5 a parir de nuestra imagen con apache: josedom24/apache2:1.0, para ello en el directorio php creamos un fichero index.php:

~/php$ echo "<?php echo phpinfo();?>">index.php

Y el fichero Dockerfile, con el siguiente contenido:

FROM josedom24/apache2:1.0
MAINTAINER José Domingo Muñoz "josedom24@gmail.com"

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

EXPOSE 80
ADD ["index.php","/var/www/html/"]

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

Generamos la nueva imagen:

~/php$ docker build -y josedom24/php5:1.0 .

Creamos un nuevo contenedor, y realizamos la prueba de funcionamiento:

$ docker run -d -p 8080:80 --name servidor_php josedom24/php5:1.0

dockerfile2

Conclusiones

En esta entrada hemos visto una introducción a la creación de imágenes docker utilizando ficheros Dockerfile. Para avanzar más sobre la utilización de este mecanismo de creación de imágenes os sugiero que estudiéis los ficheros Dockerfile y los repositorios que podemos encontrar en el registro Docker Hub:

dockerfile3En la siguiente entrada vamos a hacer una introducción al uso del registro docker hub, para guardar nuestras imágenes y poderlas desplegar en nuestro entorno de producción.

Gestionando el registro Docker Hub

$
0
0

dockerhub

En artículos anteriores hemos estudiado la generación de imágenes docker utilizando ficheros Dockerfile y construyendo la nueva imagen con el comando docker buid. Las imágenes generadas por este método se crean en nuestro servidor docker. si queremos desplegar la aplicación o el servicio “dockerizado” desde nuestro entorno de prueba/desarrollo a nuestro entorno de producción, es necesario llevarnos la imagen de un entono a otro. Para transferir la imagen de un equipo a otro tenemos dos posibilidades:

  • Podríamos guardar la imagen en un fichero tar, que podemos copiar al otro equipo para restaurarlo en él.
  • Podríamos guardar la imagen en un registro docker. Podemos instalar un registro en nuestra infraestructura o utilizar docker hub, que es una aplicación web que nos proporciona la posibilidad de guardar nuestras imágenes. Una vez que la imagen esta guardada en el registro podemos descargarla desde el entorno de producción.

Para ver las distintas opciones que tenemos a nuestra disposición vamos a partir de la siguiente imagen que hemos creado:

docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
josedom24/apache2   latest              04800781aed6        17 seconds ago      183.7 MB

Exportación/importación de imágenes

Como hemos indicado anteriormente una imagen la podemos guardar en un fichero tar para exportarla a otro equipo.

Para exportar una imagen ejecutamos el siguiente comando:

$ docker save -o apache2.tar josedom24/apache2

Y se genera un fichero tar, que podemos ver:

$ ls -alh
-rw-r--r-- 1 usuario usuario 184M feb 17 21:02 apache2.tar

Este fichero lo podemos guardar en cualquier medio de almacenamiento, o enviarlo por internet a otro equipo, donde realizaríamos la importación:

$ docker load -i apache2.tar josedom24/apache2

Guardando nuestras imágenes en docker hub

Otra opción que tenemos es guardar nuestra imagen en el registro docker hub, de esta forma sería muy sencillo descargarlo en otro equipo. Es necesario tener una cuenta en docker hub, nosotros ya tenemos una cuenta con el usuario josedom24.

Para poder subir una imagen a nuestra cuenta de docker hub es necesario autentificarnos, para ello:

$ docker login
Username: josedom24
Password: 
Email: xxxxxxxx@gmail.com
WARNING: login credentials saved in /home/usuario/.docker/config.json
Login Succeeded

Y podemos subir nuestra imagen con el comando:

$ docker push josedom24/apache2
The push refers to a repository [docker.io/josedom24/apache2]
3155f6b09710: Pushed 
67331ad8a75e: Pushed 
5f70bf18a086: Pushed 
78dbfa5b7cbc: Pushed
latest: digest: sha256:bfe4d16f3e8d7f31b5f1bc0e1d989cbe6d762d1f4770fedf435685e24ee7bf8c size: 644

Podemos comprobar que la imagen se ha subido a docker hub: dockerhub2

Ya podemos buscar la nueva imagen que hemos subido y bajarla en otro servidor:

$ docker search josedom24
NAME                DESCRIPTION   STARS     OFFICIAL   AUTOMATED
josedom24/apache2                 0
$ docker pull josedom24/apache2

Generación automática de imágenes en docker hub

También podemos generar una imagen directamente en docker hub. Esta solución es mucho más cómoda, porque no es necesario generar la imagen en nuestro ordenador para posteriormente subirla al registro. Para realizar la generación automática vamos a guardar los ficheros de nuestro contexto (el fichero Dockerfile y los ficheros que vamos a guardar en la imagen) en un repositorio en GitHub. Para realizar este ejemplo vamos a utilizar el contexto que utilizamos en la entrada anterior para crear la imagen con mysql. Lo primero que vamos a hacer es crear un repositorio en github donde vamos a guardar los ficheros del contexto:

$ git clone git@github.com:josedom24/docker_mysql.git
Cloning into 'docker_mysql'...
$ cd docker_mysql

Copiamos los ficheros del contexto en nuestro repositorio:

docker_mysql$ ls
Dockerfile  my.cnf  script.sh

Y los subimos al repositorio github:

docker_mysql$ git add *
docker_mysql$ git commit -m "Contexto docker mysql"
docker_mysql$ git push

A continuación desde docker hub tenemos que crear un “Automated Build”:

dockerhub3

La primera vez que lo hacemos tenemos que conectar docker con github y permitir que docker hub pueda acceder a nuestro repositorio, elegimos que nos vamos a conectar a github y seleccionamos la primera opción (Public and Private) donde permitamos más opciones de trabajo, finalmente desde github autorizamos a la aplicación docker hub. Para conseguir todo esto tenemos que seguir los siguiente pasos:

dockerhub4dockerhub5dockerhub6dockerhub7Una vez realizado esta configuración, ya podemos crear un “Automated Build”, elegimos un repositorio github:

dockerhub8

A continuación configuro la imagen que voy a crear, como se puede observar si tengo distintas ramas en el repositorio github, se podrán crear distintas imágenes con tag distintos:

dockerhub9

Y finalmente, tenemos nuestro nuevo repositorio. Si esperamos un tiempo prudencial para permitir que se cree la imagen o hacemos un nuevo push en el repositorio github, podremos obtener la siguiente información:

dockerhub10

  • Build details: Detalles de la construcción de la nueva imagen, por cada modificación que hagamos en el repositorio github (push) se creará una nueva tarea de construcción de la imagen.

dockerhub12

  • Dockerfile: Obtenemos el contenido del fichero utilizado para construir la imagen.

dockerhub13Por último podemos comprobar que tenemos acceso a la nueva imagen y que podemos descargarla:

$ docker search josedom24
NAME                     DESCRIPTION    STARS     OFFICIAL   AUTOMATED
josedom24/docker_mysql   Docker mysql   0                    [OK]
josedom24/apache2                       0                    
$ docker pull josedom24/docker_mysql

 

Enlazando contenedores docker

$
0
0

wordpress-mysql-db-merge-180x180

En los artículos anteriores hemos estudiado como trabajar con imágenes y contenedores docker. En todos los ejemplos que hemos mostrado, los contenedores han trabajado ofreciendo uno o varios servicios, pero no se han comunicado o enlazado con ningún otro. En realidad sería muy deseable trabajar con el paradigma de “microservicio” donde cada contenedor ofrezca un servicio que funcione de forma autónoma y aislada del resto, pero que tenga cierta relación con otro contenedor (que ofrezca también un sólo servicio) para que entre todos ofrezcan una infraestructura más o menos compleja. En esta entrada vamos a mostrar un ejemplo de como podemos aislar servicios en distintos contenedores y enlazarlos para que trabajen de forma conjunta.

Instalación de wordpress en docker

Más concretamente vamos a crear un contenedor con un servidor web con wordpress instalado que lo vamos a enlazar con otro contenedor con un servidor de base de datos mysql. Para realizar el ejemplo vamos a utilizar las imágenes oficiales de wordpress y mysql que encontramos en docker hub.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
wordpress           latest              55f2580b9cc9        5 days ago          516.5 MB
mysql               latest              e13b20a4f248        5 days ago          361.2 MB
debian              latest              256adf7015ca        5 days ago          125.1 MB
ubuntu              14.04               14b59d36bae0        5 days ago          187.9 MB

Docker nos permite un mecanismo de enlace entre contenedores, posibilitando enviar información de forma segura entre ellos y pudiendo compartir información entre ellos, por ejemplo las variables de entorno. Para establecer la asociación entre contenedores es necesario usar el nombre con el que creamos el contenedor, el nombre sirve como punto de referencia para enlazarlo con otros contenedores.

Por lo tanto, lo primero que vamos a hacer es crear un contenedor desde la imagen mysql con el nombre servidor_mysql, siguiendo las instrucción del repositorio de docker hub:

$ docker run --name servidor_mysql -e MYSQL_ROOT_PASSWORD=asdasd -d mysql

En este caso sólo hemos indicado la variable de entrono MYSQL_ROOT_PASSWORD, que es obligatoria, indicando la contraseña del usuario root. Si seguimos las instrucciones del repositorio de docker hub podemos observar que podríamos haber creado más variables, por ejemplo:  MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD.

A continuación vamos a crear un nuevo contenedor, con el nombre servidor_wp, con el servidor web a partir de la imagen wordpress, enlazado con el contenedor anterior.

$ docker run --name servidor_wp -p 80:80 --link servidor_mysql:mysql -d wordpress

Para realizar la asociación entre contenedores hemos utilizado el parámetro --link, donde se indica el nombre del contenedor enlazado y un alias por el que nos podemos referir a él.

Podemos comprobar los contenedores con los que está asociado un determinado contenedor con la siguiente instrucción:

$ docker inspect -f "{{ .HostConfig.Links }}" servidor_wp
 [/servidor_mysql:/servidor_wp/mysql]

En esta situación el contenedor servidor_web puede acceder a información del contenedor servidor_mysql, para hacer esto docker construye un túnel seguro entre los contenedores y no es necesario exponer ningún puerto entre ellos (cuando hemos creado el contenedor servidor_mysql no hemos utilizado el parámetro -p), por lo tanto al servidor_mysql no se expone al exterior. Para facilitar la comunicación entre contenedores, docker utiliza las variables de entrono y modifica el fichero /etc/hosts.

Variables de entorno en contenedores asociados

Por cada asociación de contenedores, docker crea una serie de variables de entorno, en este caso, en el contenedor servidor_wp, se crearán las siguientes variables, donde se utiliza el nombre del alias indicada en el parámetro --link:

  • MYSQL_NAME: Con el nombre del contenedor servidor_mysql.
  • MYSQL_PORT_3306_TCP_ADDR: Por cada puerto que expone la imagen desde la que hemos creado el contenedor se crea una variable de entorno de este tipo. El contenido de esta variable es la dirección IP del contenedor.
  • MYSQL_PORT_3306_TCP_PORT: De la misma manera se crea una por cada puerto expuesto por la imagen, en este caso guardamos el puerto expuesto.
  • MYSQL_PORT_3306_TCP_PROTOCOL: Una vez más se crean tantas variables como puertos hayamos expuesto. En esta variable se guarda el protocolo del puerto.
  • MYSQL_PORT: En esta variable se guarda la url del contenedor, con la ip del mismo y el puerto más bajo expuesto. Por ejemplo MYSQL_PORT=tcp://172.17.0.82:3306.
  • Finalmente por cada variable de entorno definido en el contenedor enlazado, en este caso servidor_mysql, se crea una en el contenedor principal, en este caso servidor_web. Si en el contenedor_mysql hay una variable MYSQL_ROOT_PASSWORD, en el servidor web se creará la variable MYSQL_ENV_MYSQL_ROOT_PASSWORD

Podemos comprobar esto creando un contenedor (que vamos a borrar inmediatamente, opciones -rm) donde vemos las variables de entorno.

$ docker run --rm --name web2 --link servidor_mysql:mysql wordpress env
HOSTNAME=728f7e897f07
MYSQL_ENV_MYSQL_ROOT_PASSWORD=asdasd
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_PORT_3306_TCP=tcp://172.17.0.2:3306
MYSQL_ENV_MYSQL_VERSION=5.7.11-1debian8
MYSQL_NAME=/web2/mysql
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_PORT_3306_TCP_ADDR=172.17.0.2
MYSQL_PORT=tcp://172.17.0.2:3306
...

Por tanto llegamos a la conclusión que toda la información que necesitamos para instalar wordpress (dirección y puerto del servidor de base de datos, contraseña del usuario de la base de datos,…) lo tenemos a nuestra disposición en variables de entorno. El script bash que ejecutamos por defecto al crear el contenedor desde la imagen wordpress utilizará toda esta información, que tiene en variables de entorno, para crear el fichero de configuración de wordpress: wp-config.php. Además podremos crear nuevas variables a la hora de crear el contenedor como nos informa en la documentación del repositorio de docker hub:

  • -e WORDPRESS_DB_HOST=... (defaults to the IP and port of the linked mysql container)
  • -e WORDPRESS_DB_USER=... (defaults to “root”)
  • -e WORDPRESS_DB_PASSWORD=... (defaults to the value of the MYSQL_ROOT_PASSWORD environment variable from the linked mysql container)
  • -e WORDPRESS_DB_NAME=... (defaults to “wordpress”)
  • -e WORDPRESS_TABLE_PREFIX=... (defaults to “”, only set this when you need to override the default table prefix in wp-config.php)
  • -e WORDPRESS_AUTH_KEY=..., -e WORDPRESS_SECURE_AUTH_KEY=..., -e WORDPRESS_LOGGED_IN_KEY=..., -e WORDPRESS_NONCE_KEY=..., -e WORDPRESS_AUTH_SALT=..., -e WORDPRESS_SECURE_AUTH_SALT=..., -e WORDPRESS_LOGGED_IN_SALT=..., -e WORDPRESS_NONCE_SALT=... (default to unique random SHA1s)

Actualizando el fichero /etc/hosts

Otro mecanismo que se realiza para permitir la comunicación entre contenedores asociados es modificar el fichero /etc/hosts para que tengamos resolución estática entre ellos. Si volvemos a crear un contenedor interactivo que conectemos al contenedor servidor_mysql, podemos comprobarlo:

$ docker run --rm -i -t --name web3 --link servidor_mysql:mysql wordpress /bin/bash
root@ccc58b7ab132:/var/www/html# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 mysql 11615eb26fc9 servidor_mysql
172.17.0.4 ccc58b7ab132

root@ccc58b7ab132:/var/www/html# ping servidor_mysql
PING mysql (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.106 ms
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.067 ms

Comprobación de la instalación de wordpress

Como hemos visto anteriormente, al crear el contenedor servidor_wp asociado al contenedor servidor_mysql, el script bash que se está ejecutando en la creación es capaz de configurar la conexión a la base de datos con los datos de las variables de entorno que se han creado, además, al modificar su fichero /etc/hosts, es capaz de conectar al contenedor utilizando el nombre del mismo. Al exponer el puerto 80 podemos acceder con un navegador web y comprobar que el wordpress está instalado, solo es necesario la configuración del mismo, aunque no será necesario realizar la configuración de la conexión a la base de datos:

link1

ink2

link3

Conclusiones

He escrito varios artículos sobre docker, donde he intentado hacer una introducción y ofrecer una visión general de lo que nos puede ofrecer el trabajo con contenedores docker. Como decía en el primer artículo, el objetivo de estas entradas ha sido, desde el primer momento, obligarme a tener la experiencia de conocer los distintos conceptos referidos a docker sobre todo centrado en mostrarles a mis alumnos del módulo de “Implantación de aplicaciones web” del Ciclo Formativo de Grado Superior “Administración de Sistemas Informáticos y Redes” las grandes posibilidades que nos ofrece esta herramienta para la implantación de aplicaciones web. Podría seguir escribiendo más entradas sobre docker, me queda por introducir, las redes, los volúmenes, herramientas especificas como docker machine, docker compose, docker swarm, y seguro que me dejo atrás muchas más cosas, pero me voy a tomar un descanso, y más adelante veré la posibilidad de seguir escribiendo. También tendría que estudiar las posibilidades de orquestación de contenedores, que como nos ofrece docker swarm también nos ofrece Kubernetes de google.

Copias de seguridad de volúmenes en OpenStack

$
0
0

En esta entrada voy a explicar una característica muy específica que nos proporciona el componente Cinder de OpenStack, que es el encargado de gestionar el almacenamiento persistente con el concepto de volumen. La característica a la que me refiero es la posibilidad de hacer copias de seguridad del contenido de nuestro volúmenes. El estudio de esta opción la hemos llevado a cabo durante la realización del último curso sobre OpenStack que he impartido con Alberto Molina. Además si buscas información de este tema, hay muy poco en español, así que puede ser de utilidad.

El cliente cinder proporciona las herramientas necesarias para crear una copia de seguridad de un volumen. Las copias de seguridad se guardar como objetos en el contenedor de objetos swift. Por defecto se utiliza swift como almacén de copias de seguridad, aunque se puede configurar otros backend para realizar las copias de seguridad, por ejemplo una carpeta compartida por NFS.

Configurando devstack de forma adecuada

Podemos configurar nuestra instalación de OpenStack con devstack para habilitar la característica de copia de seguridad de volúmenes. En artículo anteriores he hecho una introducción al uso de devstack para realizar una instalación de OpenStack en un entorno de pruebas: Instalar Open Stack Juno con devstack.

Al crear nuestro fichero local.conf, tenemos que tener en cuenta dos cosas:

  • Habilitar el componente de swift (almacenamiento de objetos) donde vamos a realizar las copias de seguridad.
 enable_service s-proxy s-object s-container s-account
 SWIFT_REPLICAS=1
 SWIFT_HASH=password
  • Habilitar la característica de copia de seguridad de los volúmenes.
enable_service c-bak

Un ejemplo de fichero local.conf podría ser:

###IP Configuration
HOST_IP=IP_ADDRESS

#Credentials
ADMIN_PASSWORD=password
DATABASE_PASSWORD=password
RABBIT_PASSWORD=password
SERVICE_PASSWORD=password
SERVICE_TOKEN=password

####Tempest
enable_service tempest

#Swift Requirements
enable_service s-proxy s-object s-container s-account
SWIFT_REPLICAS=1
SWIFT_HASH=password

##Enable Cinder-Backup
enable_service c-bak

#Log Output
LOGFILE=/opt/stack/logs/stack.sh.log
VERBOSE=True
LOG_COLOR=False
SCREEN_LOGDIR=/opt/stack/logs

Crear una copia de seguridad

Para realizar una copia de seguridad de un volumen debe estar en estado Disponible, es decir, no debe estar asociada a ninguna instancia.

Partimos del siguiente volumen, que hemos formateado y creado un fichero desde una instancia:

$ cinder list
+--------------------------------------+-----------+--------+------+-------------+----------+-------------+
|                  ID                  |   Status  |  Name  | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+--------+------+-------------+----------+-------------+
| 917ef4cc-784d-4803-a19a-984b847b9f1e | available | disco1 |  1   | lvmdriver-1 |  false   |             |
+--------------------------------------+-----------+--------+------+-------------+----------+-------------+

Vamos a hacer una copia de seguridad:

$ cinder backup-create 917ef4cc-784d-4803-a19a-984b847b9f1e
+-----------+--------------------------------------+
|  Property |                Value                 |
+-----------+--------------------------------------+
|     id    | 77e2430d-afda-4733-bf55-6d150555b75f |
|    name   |                 None                 |
| volume_id | 917ef4cc-784d-4803-a19a-984b847b9f1e |
+-----------+--------------------------------------+

Vemos la lista de copias de seguridad con:

$ cinder backup-list
+--------------------------------------+--------------------------------------+-----------+------+------+--------------+---------------+
|                  ID                  |              Volume ID               |   Status  | Name | Size | Object Count |   Container   |
+--------------------------------------+--------------------------------------+-----------+------+------+--------------+---------------+
| 77e2430d-afda-4733-bf55-6d150555b75f | 917ef4cc-784d-4803-a19a-984b847b9f1e | available | None |  1   |      22      | volumebackups |
+--------------------------------------+--------------------------------------+-----------+------+------+--------------+---------------+

Y finalmente podemos pedir información sobre la copia de seguridad:

$ cinder backup-show 77e2430d-afda-4733-bf55-6d150555b75f
+-------------------+--------------------------------------+
|      Property     |                Value                 |
+-------------------+--------------------------------------+
| availability_zone |                 nova                 |
|     container     |            volumebackups             |
|     created_at    |      2016-01-08T16:39:47.000000      |
|    description    |                 None                 |
|    fail_reason    |                 None                 |
|         id        | 77e2430d-afda-4733-bf55-6d150555b75f |
|        name       |                 None                 |
|    object_count   |                  22                  |
|        size       |                  1                   |
|       status      |              available               |
|     volume_id     | 917ef4cc-784d-4803-a19a-984b847b9f1e |
+-------------------+--------------------------------------+

Para comprobar que la copia de seguridad se ha guardado en swifit ejecutamos la siguientes instrucciones:

$ swift list
volumebackups

$ swift list volumebackups
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00001
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00002
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00003
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00004
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00005
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00006
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00007
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00008
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00009
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00010
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00011
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00012
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00013
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00014
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00015
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00016
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00017
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00018
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00019
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00020
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f-00021
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f_metadata
volume_917ef4cc-784d-4803-a19a-984b847b9f1e/20160108163947/az_nova_backup_77e2430d-afda-4733-bf55-6d150555b75f_sha256file

Restaurar una copia de seguridad

Para restaurar una nueva copia de seguridad a un nuevo volumen, ejecutamos la siguiente instrucción:

$ cinder backup-restore  77e2430d-afda-4733-bf55-6d150555b75f

Podemos ver el proceso de restauración con la siguiente instrucción:

$ cinder list
+--------------------------------------+------------------+-----------------------------------------------------+------+-------------+----------+-------------+
|                  ID                  |      Status      |                         Name                        | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+------------------+-----------------------------------------------------+------+-------------+----------+-------------+
| 917ef4cc-784d-4803-a19a-984b847b9f1e |    available     |                        disco1                       |  1   | lvmdriver-1 |  false   |             |
| ebff83f2-cec8-429d-af8a-67e9d012ef5e | restoring-backup | restore_backup_77e2430d-afda-4733-bf55-6d150555b75f |  1   | lvmdriver-1 |  false   |             |
+--------------------------------------+------------------+-----------------------------------------------------+------+-------------+----------+-------------+	

Y finalmente vemos que se ha creado un nuevo volumen restaurado desde la copia de seguridad:

$ cinder list
+--------------------------------------+-----------+-----------------------------------------------------+------+-------------+----------+-------------+
|                  ID                  |   Status  |                         Name                        | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+-----------------------------------------------------+------+-------------+----------+-------------+
| 917ef4cc-784d-4803-a19a-984b847b9f1e | available |                        disco1                       |  1   | lvmdriver-1 |  false   |             |
| ebff83f2-cec8-429d-af8a-67e9d012ef5e | available | restore_backup_77e2430d-afda-4733-bf55-6d150555b75f |  1   | lvmdriver-1 |  false   |             |
+--------------------------------------+-----------+-----------------------------------------------------+------+-------------+----------+-------------+

Por último indicar que en la última versión de Openstack (Liberty) se ha introducido la posibilidad de hacer copias de seguridad incrementales y la posibilidad de hacer copias de seguridad aunque el volumen este asociado a una instancia.

Transferencias de volúmenes en OpenStack

$
0
0

Al igual que en la entrada anterior, sobre copias de seguridad de volúmenes en OpenStack, en esta entrada vamos a mostrar otra funcionalidad que nos ofrece OpenStack cuando trabajamos con volúmenes con su componente Cinder.

Transferencia de volúmenes

Esta operación nos permite transferir un volumen de un proyecto a otro.

Crear una transferencia

Vamos a trabajar con el usuario demo, que tiene creado un volumen.

$ source demo-openrc.sh	

$ cinder list
+--------------------------------------+-------------------+--------+------+-------------+----------+-------------+
|                  ID                  |       Status      |  Name  | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-------------------+--------+------+-------------+----------+-------------+
| 917ef4cc-784d-4803-a19a-984b847b9f1e | awaiting-transfer | disco1 |  1   | lvmdriver-1 |  false   |             |
+--------------------------------------+-------------------+--------+------+-------------+----------+-------------+

Vamos a crear una transferencia, con las siguientes instrucciones:

$ cinder transfer-create 917ef4cc-784d-4803-a19a-984b847b9f1e
+------------+--------------------------------------+
|  Property  |                Value                 |
+------------+--------------------------------------+
|  auth_key  |           408af7d4a1441587           |
| created_at |      2016-01-08T17:07:03.412265      |
|     id     | d24d5659-40e9-446c-9437-238fc8868571 |
|    name    |                 None                 |
| volume_id  | 917ef4cc-784d-4803-a19a-984b847b9f1e |
+------------+--------------------------------------+

$ cinder transfer-list
+--------------------------------------+--------------------------------------+------+
|                  ID                  |              Volume ID               | Name |
+--------------------------------------+--------------------------------------+------+
| d24d5659-40e9-446c-9437-238fc8868571 | 917ef4cc-784d-4803-a19a-984b847b9f1e | None |
+--------------------------------------+--------------------------------------+------+

Realizar una transferencia

A continuación vamos a trabajar con otro usuario y otro proyecto, a donde vamos a realizar la transferencia del volumen.

$ source admin-openrc.sh

Comprobamos que en este proyecto no hay ningún volumen:

$ cinder list
+----+--------+------+------+-------------+----------+-------------+
| ID | Status | Name | Size | Volume Type | Bootable | Attached to |
+----+--------+------+------+-------------+----------+-------------+
+----+--------+------+------+-------------+----------+-------------+

A continuación vamos a transferir el volumen a este proyecto, para ello tenemos que indicar el ID de la transferencia creada por el usuario demo indicando el parámetro auth_key.

$ cinder transfer-accept d24d5659-40e9-446c-9437-238fc8868571 408af7d4a1441587
+-----------+--------------------------------------+
|  Property |                Value                 |
+-----------+--------------------------------------+
|     id    | d24d5659-40e9-446c-9437-238fc8868571 |
|    name   |                 None                 |
| volume_id | 917ef4cc-784d-4803-a19a-984b847b9f1e |
+-----------+--------------------------------------+

Y confirmamos que hemos hecho la transferencia de forma correcta:

$ cinder list
+--------------------------------------+-----------+--------+------+-------------+----------+-------------+
|                  ID                  |   Status  |  Name  | Size | Volume Type | Bootable | Attached to |
+--------------------------------------+-----------+--------+------+-------------+----------+-------------+
| 917ef4cc-784d-4803-a19a-984b847b9f1e | available | disco1 |  1   | lvmdriver-1 |  false   |             |
+--------------------------------------+-----------+--------+------+-------------+----------+-------------+

Si volvemos a entrar con el usuario demo, podemos comprobar que ya no tenemos ninguna transferencia pendiente y que ya no es propietario del volumen:

$ source demo-openrc.sh

$ cinder transfer-list
+----+-----------+------+
| ID | Volume ID | Name |
+----+-----------+------+
+----+-----------+------+	

$ cinder list
+----+--------+------+------+-------------+----------+-------------+
| ID | Status | Name | Size | Volume Type | Bootable | Attached to |
+----+--------+------+------+-------------+----------+-------------+
+----+--------+------+------+-------------+----------+-------------+

Creando servidores docker con Docker Machine

$
0
0

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

Utilizando Docker Machine con VirtualBox

Vamos a ver distintos ejemplos de Docker Machine, utilizando distintos drivers. En primer lugar vamos a utilizar el driver de VirtualBox que nos permitirá crear una máquina virtual con Docker Engine en un ordenador donde tengamos instalado VirtualBox. Para ello ejecutamos la siguiente instrucción:

$ docker-machine create -d virtualbox nodo1
Running pre-create checks...
Creating machine...
(nodo1) Copying /home/jose/.docker/machine/cache/boot2docker.iso to /home/jose/.docker/machine/machines/nodo1/boot2docker.iso...
(nodo1) Creating VirtualBox VM...
(nodo1) Creating SSH key...
(nodo1) Starting the VM...
(nodo1) Check network to re-create if needed...
(nodo1) Found a new host-only adapter: "vboxnet0"
(nodo1) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env nodo1

Esta instrucción va  a crear una nueva máquina (nodo1) donde se va a instalar una distribución Linux llamada boot2docker con el Docker Engine instalado. Utilizando el driver de VirtualBox podemos indicar las características de la máquina que vamos a crear por medio de parámetros, por ejemplo podemos indicar las características hardware (--virtualbox-memory, --virtualbox-disk-size, …) Para más información de los parámetros que podemos usar puedes mirar la documentación del driver.

Una vez creada la máquina podemos comprobar que lo tenemos gestionados por Docker Machine, para ello:

$ docker-machine ls
NAME    ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
nodo1   -        virtualbox   Running   tcp://192.168.99.100:2376           v1.11.1

A continuación para conectarnos desde nuestro cliente docker al Docker Engine de la nueva máquina necesitamos declarar las variables de entornos adecuadas, para obtener las variables de entorno de esta máquina podemos ejecutar:

$ docker-machine env nodo1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/jose/.docker/machine/machines/nodo1"
export DOCKER_MACHINE_NAME="nodo1"

Y para ejecutar estos comandos y que se creen las variables de entorno, ejecutamos:

$ eval $(docker-machine env nodo1)

A partir de ahora, y utilizando el cliente docker, estaremos trabajando con el Docker Engine de nodo1:

$ docker run -d -p 80:5000 training/webapp python app.py
1450b7b2c785333834b43332a4b86505c0167893306ac511489b0f922560a938
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
1450b7b2c785        training/webapp     "python app.py"     8 minutes ago       Up 8 minutes        0.0.0.0:80->5000/tcp   cranky_hopper       

Y para acceder al contenedor deberíamos utilizar la ip del servidor docker, que la podemos obtener:

$ docker-machine ip nodo1
192.168.99.100

docker1Otras opciones de docker-machine que podemos utilizar son:

  • inspect: Nos devuelve información de una máquina.
  • ssh, scp: Nos permite acceder por ssh y copiar ficheros a una determinada máquina.
  • start, stop, restart, status: Podemos controlar una máquina.
  • rm: Es la opción que borra una máquina de la base de datos de Docker Machine. Con determinados drivers también elimina la máquina.
  • upgrade: Actualiza a la última versión de docker la máquina indicada.

Utilizando Docker Machine con OpenStack

En el ejemplo anterior hemos utilizado el driver VirtualBox que nos permite crear una máquina docker en un entorno local. Quizás lo más interesante es utilizar Docker Machine para crear nodos docker en máquinas remotas, para ello tenemos varios drivers según el proveedor: AWS, Microsoft Azure, Google Compute Engine,… En este ejemplo vamos a a hacer uso del driver OpenStack para crear una máquina en nuestra infraestructura OpenStack con el demonio docker instalado. Para ello vamos a cargar nuestras credenciales de OpenStack y a continuación creamos la nueva máquina:

$ source openrc.sh 
Please enter your OpenStack Password:
$ docker-machine create -d openstack \
> --openstack-flavor-id 3 \
> --openstack-image-id 030b7fe8-ed5d-46b8-81fb-62587c944936 \
> --openstack-net-name red \
> --openstack-floatingip-pool ext-net \
> --openstack-ssh-user debian \
> --openstack-sec-groups default \
> --openstack-keypair-name jdmr \
> --openstack-private-key-file ~/.ssh/id_rsa \
>  nodo2

Running pre-create checks...
Creating machine...
(nodo2) Creating machine...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with debian...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!

Al ejecutar el comando anterior se han realizado las siguientes acciones:

  • Docker Machine se ha autentificado en OpenStack utilizando las credenciales que hemos cargado.
  • Docker Machine ha creado una nueva instancia con las características indicadas. Si no se indica la clave SSH se genera una nueva que será la que se usará para acceder a la instancia.
  • Cuando la instancia es accesible por SSH, Docker Machine se conecta a la instancia, instala Docker Engine y lo configura de forma adecuada habilitando TLS).
  • Finalmente, recordar que el comando docker-machine rm no sólo elimina la referencia local de la máquina, también elimina la instancia que hemos creado.

Como podemos comprobar se ha creado una instancia desde una imagen Debian Jessie, con un sabor m1.nano, conectada a nuestra red interna, se le ha asociado una ip flotante, se ha configurado el grupo de seguridad por defecto (recuerda que debes abrir el puerto TCP 2376 para que el servidor docker sea accesible) y se han inyectado mis claves SSH. Podemos comprobar que ya tenemos dada de alta la nueva máquina:

$ docker-machine ls
NAME    ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
nodo1   -        virtualbox   Running   tcp://192.168.99.100:2376           v1.11.1
nodo2   -        openstack    Running   tcp://172.22.206.15:2376            v1.11.1   

A continuación creamos las variables de entorno para trabajar con esta máquina y comprobamos (como es evidente) que no tiene ningún contenedor creado:

$ eval $(docker-machine env nodo2)
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Puedes obtener más información del uso del driver OpenStack en la documentación oficial.

Utilizando Docker Machine con máquinas que ya tenemos funcionando

En los dos casos anteriores Docker Machine ha sido la responsable de crear las máquinas donde se va a instalar y configurar el demonio docker. En este último caso, ya tenemos una máquina (física o virtual) ya funcionando y queremos gestionarla con Docker Machine. Para conseguir esto tenemos que utilizar el driver genérico (generic) que ejecutará las siguientes tareas:

  •  Si la máquina no tiene instalado docker, lo instalará y lo configurará.
  • Actualiza todos los paquetes de la máquina.
  • Genera los certificados TLS para la comunicación segura.
  • Reiniciará el Docker Engine, por lo tanto si tuviéramos contenedores, estos serán detenidos.
  • Se cambia el nombre de la máquina para que coincida con el que le hemos dado con Docker Machine.

Vamos a gestionar una máquina que ya tenemos funcionando con la siguiente instrucción:

$ docker-machine create -d generic \
--generic-ip-address=172.22.205.103 \
--generic-ssh-user=debian \ 
nodo3

Running pre-create checks...
Creating machine...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with debian...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!

Por último comprobamos que Docker Machine gestiona el nuevo nodo:

$ docker-machine ls
NAME    ACTIVE   DRIVER      STATE     URL                         SWARM   DOCKER    ERRORS
nodo1   -        virtualbox   Running  tcp://192.168.99.100:2376           v1.11.1
nodo2   -        openstack   Running   tcp://172.22.206.15:2376            v1.11.1   
nodo3   -        generic     Running   tcp://172.22.205.103:2376           v1.11.1

Cargamos las variables de entorno del nuevo nodo y ya poedmos empezar a trabajar con él:

$ eval $(docker-machine env nodo3)
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Como siempre puede obtener más información del driver generic en la documentación oficial.

Conclusiones

Esta entrada ha sido una introducción a la herramienta Docker Machine y al uso de diferentes drivers para la creación de nodos docker. Para más información sobre esta herramienta consulta la documentación oficial.


Gestión del almacenamiento en docker

$
0
0

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": ""
            }
        ],
...

A continuación podemos comprobar en el docker engine el directorio correspondiente al volumen y que efectivamente contiene el fichero que hemos creado.

# cd /mnt/sda1/var/lib/docker/volumes/c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d
3ee15ca8f3427/_data
fichero1.txt

Con el cliente docker también podemos comprobar los volúmenes que hemos creados ejecutando:

$ docker volume ls
DRIVER              VOLUME NAME
local               c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427

Y a continuación podemos obtener información del volumen:

$ docker volume inspect c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427
[
    {
        "Name": "c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427",
        "Driver": "local",
        "Mountpoint": "/mnt/sda1/var/lib/docker/volumes/c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427/_data",
        "Labels": null
    }
]

Por último podemos comprobar que aunque borremos el contenedor, el volumen no se borra:

$ docker rm contenedor1

$ docker volume ls
DRIVER              VOLUME NAME
local               c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427

Creando volúmenes de datos

En el apartado anterior al añadir un nuevo volumen al contenedor, se ha creado un nuevo volumen pero con un nombre muy poco significativo. A la hora de gestionar una gran cantidad de volúmenes es muy importante poner nombres significativos a los volúmenes, para ello vamos a crear primero el volumen (indicando el nombre) y a continuación lo asociamos al contenedor:

$ docker volume create --name vol1
vol1
$ docker volume ls
DRIVER              VOLUME NAME
local               c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427
local               vol1
$ docker run -it --name contenedor2 -v vol1:/data ubuntu:14.04 bash

Hemos creado el contenedor2 que tendrá asociado el volumen vol1 y lo tendrá montado en el directorio /data.

Realmente no es necesario realizar la operación de crear el volumen con anterioridad. Si creamos un contenedor y asociamos un volumen con un nombre que no existe, el volumen se creará. Vemos un ejemplo:

$ docker run -it --name contenedor3 -v vol2:/data ubuntu:14.04 bash
root@8f36b1c407b9:/# exit

$ docker volume ls
DRIVER              VOLUME NAME
local               c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427
local               vol1
local               vol2

De forma similar, pero utilizando el nombre largo, podemos crear un nuevo contenedor al que asociamos nuestro primer volumen y podamos comprobar que la información guardada en el volumen es persistente:

$ docker run -it --name contenedor4 -v c7665edfb4505d6ac85fb0f3db118f6c7bb63958157ec722d6d3ee15ca8f3427:/data ubuntu:14.04 bash
root@1fbbe52788b5:/# cd data/
root@1fbbe52788b5:/data# ls
fichero.txt

Por último para borrar un volumen tenemos que asegurarnos que no está asociado a ningún contenedor:

$ docker volume rm vol1
Error response from daemon: Unable to remove volume, volume still in use: remove vol1: volume is in use - [969b3bd0ec30193b9ce2cef837b6231e3c39310caca4660ea0fcab695d99d065]

$ docker rm contenedor2
contenedor2
$ docker volume rm vol1
vol1

Montando directorios del host en un contenedor

Una aplicación particular del trabajo con volúmenes de datos, es la posibilidad de montar en el contenedor un directorio del docker engine. En este caso hay que tener en cuenta que si el directorio de montaje del contenedor ya existe, no se borra su contenido, simplemente se monta encima. Veamos un ejemplo:

$ docker run -it --name contenedor5 -v /home/docker:/informacion ubuntu:14.04 bash
root@c1cf905f170a:/# cd informacion/
root@c1cf905f170a:/informacion# ls
log.log

En este ejemplo hemos montado en el directorio /informacion del contenedor, el directorio /home/docker del Docker Engine. Y comprobamos que podemos acceder a los ficheros del hosts.

Montando ficheros del host en un contenedor

Además de poder montar un directorio, podemos montar un fichero del Docker Engine. En el siguiente ejemplo vamos a montar el fichero /etc/hosts en el contenedor.

$ docker run -it --name contenedor6 -v /etc/hosts:/etc/hosts.bak ubuntu:14.04 bash
root@ff58bc448b57:/# cat /etc/hosts.bak

Contenedores de volúmenes de datos

Otra posibilidad que tenemos para conseguir el almacenamiento persistente es la creación de un contenedor donde creamos un volumen de datos y que podemos asociar a uno o varios contenedores para guardar la información en él.

Vamos a crear un contenedor con un volumen de datos asociado:

$ docker create -it --name data_vol_container -v /shared_folder ubuntu:14.04 bash
ec45dbf110e14f6f4097d2b2dfaec7092669c18c7424913106a46342af54ce23

A continuación con el parámetro --volumes-from, creamos un contenedor asociado al contenedor anterior y que montará el volumen de datos:

$ docker run -it --name container1 --volumes-from data_vol_container ubuntu:14.04 bash
root@93aed9e94393:/# cd /shared_folder/
root@93aed9e94393:/shared_folder# echo "hola">fichero.txt
root@93aed9e94393:/shared_folder# exit

Finalmente creamos un nuevo contenedor asociado al contenedor con el volumen de datos, para comprobar que, efectivamente, se comparte el volumen:

$ docker run -it --name container2 --volumes-from data_vol_container ubuntu:14.04 bash
root@a2733d38c49a:/# cat /shared_folder/fichero.txt
hola

Conclusiones

En esta entrada se ha descrito de forma introductoria las distintas posibilidades que tenemos para conseguir que la información gestionada por nuestros contenedores sea persistentes, es decir, no sea eliminada cuando destruimos el contenedor. Para más información sobre el almacenamiento en Docker puedes consultar la documentación oficial.

Gestionando el almacenamiento docker con Dockerfile

$
0
0

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.

Codificación de caracteres en python 2.X

$
0
0

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.

Unicode

En el momento en que la informática evolucionó y los ordenadores se interconectaron, se demostró que los códigos anteriores son insuficientes, al existir en el mundo muchos idiomas con alfabetos y grafías diferentes. Por lo tanto se crea la codificación unicode que nos permite representar todos los caracteres de todos los alfabetos del mundo, en realidad permite representar más de un millón de caracteres, ya que utiliza 32 bits para su representación, pero en la realidad sólo se definen unos 110.000 caracteres.

Por lo tanto esa es una de sus limitaciones, que utiliza muchos bytes para la representación, cuando normalmente vamos a utilizar un conjunto pequeños de caracteres. De este modo, aunque existen códigos que utilizan 32 bits (utf-32) y que utilizan 16 bits (utf-16) el sistema de codificación que más se utiliza es el utf-8. Aquí tienes un enlace a la tabla de códigos unicode.

utf-8

UTF-8 es un sistema de codificación de longitud variable para Unicode. Esto significa que los caracteres pueden utilizar diferente número de bytes. Para los caracteres ASCII utiliza un único byte por carácter. De hecho, utiliza exactamente los mismos bytes que ASCII por lo que los 128 primeros caracteres son indistinguibles. Los caracteres “latinos extendidos” como la ñ o la ö utilizan dos bytes. Los caracteres chinos utilizan tres bytes, y los caracteres más “raros” utilizan cuatro.
Por lo tanto la representación de los caracteres españoles que no son ASCII:

char ANSI# Unicode UTF-8 Latin 1 nombre
¡ 161 u’\xa1’ \xc2\xa1 \xa1 inverted exclamation mark
¿ 191 u’\xbf’ \xc2\xbf \xbf inverted question mark
Á 193 u’\xc1’ \xc3\x81 \xc1 Latin capital a with acute
É 201 u’\xc9’ \xc3\x89 \xc9 Latin capital e with acute
Í 205 u’\xcd’ \xc3\x8d \xcd Latin capital i with acute
Ñ 209 u’\xd1’ \xc3\x91 \xd1 Latin capital n with tilde
Ó 191 u’\xbf’ \xc3\x93 \xbf Latin capital o with acute
Ú 218 u’\xda’ \xc3\x9a \xda Latin capital u with acute
Ü 220 u’\xdc’ \xc3\x9c \xdc Latin capital u with diaeresis
á 225 u’\xe1’ \xc3\xa1 \xe1 Latin small a with acute
é 233 u’\xe9’ \xc3\xa9 \xe9 Latin small e with acute
í 237 u’\xed’ \xc3\xad \xed Latin small i with acute
ñ 241 u’\xf1’ \xc3\xb1 \xf1 Latin small n with tilde
ó 243 u’\xf3’ \xc3\xb3 \xf3 Latin small o with acute
ú 250 u’\xfa’ \xc3\xba \xfa Latin small u with acute
ü 252 u’\xfc’ \xc3\xbc \xfc Latin small u with diaeresis

¿Cómo trabaja python 2 con los distintos sistemas de codificación?

Vamos a entrar en materia: aunque podemos trabajar con distintas codificaciones, Python hace un procesamiento interno de los caracteres codificándolo con Unicode y luego convierte la salida a otros formatos, lo más habitual a utf-8. Por lo tanto el flujo de trabajo que realiza python en el tratamiento de caracteres es el siguiente:


Es decir, la cadena se decodifica a Unicode, se hace el tratamiento interno necesario, y posterior se codifica a la codificación que necesitemos.

Veamos algunos ejemplos:

1) Cuando creamos una cadena de caracteres (type str) por defecto la codificación es utf-8.

>>> cad="josé"
>>> type (cad)
<type 'str'>

Si comprobamos lo que se ha guardado realmente:

>>> cad
'jos\xc3\xa9'

Por lo tanto si vemos que longitud tiene la cadena:

>>> print len(cad)
5

Como veíamos en la tabla anterior el carácter é se representa en utf-8 con dos caracteres: \xc3\xa9.

Si enviamos la cadena a la salida estándar, python es capaz de mostrarla de forma adecuada:

>>> print cad
josé

Y podemos, de esta manera imprimir el carácter é, de la siguiente forma:

>>> print '\xc3\xa9'
é

2) Vamos a crear una cadena codificada con Unicode:

>>> cad_uni = u'josé'
>>> type (cad_uni)
<type 'unicode'>

Hemos utilizado el carácter u delante de la cadena para indicar la codificación unicode. Además la variable creada no es de tipo str, es de tipo unicode. Vemos realmente lo que tiene guardada la cadena:

>>> cad_uni
u'jos\xe9'

En este caso, el carácter é se codifica con un sólo carácter /xe9. Podemos comprobar que en este caso la función len funciona de forma adecuada y que python también es capaz de representar de forma adecuada la cadena con print:

>>> len (cad_uni)
4
>>> print cad_uni
josé

Y por último si queremos imprimir el carácter é en unicode:

>>> print u'\xe9'
é

3) Comparación de cadenas de caracteres con distintas codificaciones:

Si tenemos dos cadenas con codificación distintas, aunque hayamos guardado el mismo valor, no son iguales:

>>> cad == cad_uni
__main__:1: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
False

Por lo tanto tenemos que convertir una de las cadenas al otro código.

  • O convertimos de unicode a utf8 (codificamos):
>>> cad==cad_uni.encode("utf8")
True
  • O convertimos de utf8 a unicode (descodificamos):
>>> cad.decode("utf8")==cad_uni
True

Otra forma de descodificar es utilizar la función unicode():

>>> unicode(cad,"utf8")==cad_uni
True

4) Podemos intentar codificar una cadena unicode a un código ascii.

En este caso si tenemos algún carácter que no esté codificado en la tabla ascii (128 caracteres), nos dará un error:

>>> cad_uni.encode("ascii")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 3: ordinal not in range(128)

Podemos usar otras dos modalidades, ignorar el error:

>>> cad_uni.encode("ascii",errors='ignore')
'jos'

O reemplazar el carácter que no corresponde al código ascii con el carácter “reemplazo” de unicode (U+FFFFD).

>>> cad_uni.encode("ascii",errors='replace')
'jos?'

5) ¿Qué métodos tiene la clase unicode?

Tiene los mismos que la clase str, más dos nuvos métodos: isdecimal() y isnumeric().

cad_uni.capitalize  cad_uni.islower     cad_uni.rpartition
cad_uni.center      cad_uni.isnumeric   cad_uni.rsplit
cad_uni.count       cad_uni.isspace     cad_uni.rstrip
cad_uni.decode      cad_uni.istitle     cad_uni.split
cad_uni.encode      cad_uni.isupper     cad_uni.splitlines
cad_uni.endswith    cad_uni.join        cad_uni.startswith
cad_uni.expandtabs  cad_uni.ljust       cad_uni.strip
cad_uni.find        cad_uni.lower       cad_uni.swapcase
cad_uni.format      cad_uni.lstrip      cad_uni.title
cad_uni.index       cad_uni.partition   cad_uni.translate
cad_uni.isalnum     cad_uni.replace     cad_uni.upper
cad_uni.isalpha     cad_uni.rfind       cad_uni.zfill
cad_uni.isdecimal   cad_uni.rindex
cad_uni.isdigit     cad_uni.rjust

Veamos que ocurre con el método isnumeric():

>>> cad.isnumeric()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'isnumeric'

>>> cad_uni.isnumeric()
False

Las cadenas de tipo str no tienen dicho método, mientras las unicode si.

6) Las funciones ord() y unichar()

Al igual que podemos utilizar la función chr() para obtener el caracter correspondiente a un código ascii.

>>> print chr(97)
a

Podemos usar la función unichar() para mostrar un carácter a partir de un código unicode. La función ord() nos devuelve el código unicode a partir de un caracter.

>>> cad_uni=u'ñ'
>>> ord(cad_uni)
241

>>> unichr(241)
u'\xf1'
>>> print unichr(241)
ñ

Conclusión

En esta entrada hemos abordado cómo trabaja python2 con las distintas codificaciones de caracteres. Lo he escrito pensando en mis alumnos del módulo de “Lenguaje de marcas”. Si hay que recordar que en python3 la clase string está codificada con Unicode por lo que el tratamiento de la codificación cambia radicalmente. Por lo tanto en una próxima entrada tratare la codificación de caracteres en python3.

IV Semana Informática. Universidad de Almería

$
0
0

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.

¿Qué vamos a programar?

Indicamos varios aspectos que podríamos programar en nuestra infraestructura:

  • Escenarios: máquinas virtuales, redes o almacenamiento
  • Configuración de sistemas o aplicaciones
  • Recursos de alto nivel: DNSaaS, LBaaS, DBaaS, …
  • Respuestas ante eventos

Aunque cómo ahora veremos existen herramientas más especializadas en la creación de escenarios y otras en la configuración automática de los sistemas o aplicaciones, hacíamos a los asistentes la siguiente pregunta: ¿no estamos hablando de lo mismo?, y llegábamos a la conclusión que en realidad, la creación de escenarios y la automatización de la configuración son cosas similares, ya que finalmente sólo se trata de automatizar la configuración de una aplicación software. Dicho con otras palabras cuando creamos una infraestructura en OpenSatck lo que realmente estamos haciendo es configurando el software “OpenStack”.

Herramientas que podemos utilizar

Aunque podríamos usar lenguajes de programación tradicionales, nos vamos a fijar en el llamado “Software de orquestación”, para la creación de escenarios y “Software de gestión de la configuración”, para la configuración automática.

Cómo software de orquestación podemos señalar:

  • Vagrant (escenarios simples)
  • Cloudformation (AWS)
  • Heat (OpenStack)
  • Terraform
  • Juju

Y ejemplos de Software de gestión de la configuración:

  • Puppet
  • Chef
  • Ansible
  • Salt (SaltStack)

Para terminar nuestra presentación realizamos una demostración donde creamos en AWS una máquina virtual donde instalamos docker, para ello utilizamos el software Terraform, para a continuación, utilizando Ansible, desplegamos una aplicación web utilizando dos contenedores: una base de datos mongoDB, y una aplicación web desarrollada en nodeJS, Let’s chat.

Aquí os dejo la presentación que hemos utilizado para nuestra charla: Infraestructura en la nube con OpenStack.

 

Entornos de desarrollo virtuales con python3

$
0
0

 

He comenzado un trabajo de colaboración con OpenWebinars escribiendo artículos para su blog. El primer artículo que he escrito explica las diferentes formas que tenemos en python3 para crear un entorno virtual de desarrollo. Puedes leer el artículo completo en el blog de OpenWebinars:

Entornos de desarrollo virtuales con python3

Viewing all 104 articles
Browse latest View live