Docker

Iniciando con Docker: Parte I

Aunque el concepto de contenedores no es nuevo en ambientes UNIX (LXC, OpenVZ), en los últimos años han tomado fuerza con el auge del movimiento DevOps que ha traído consigo la integración de nuevas herramientas en el proceso de desarrollo e implementación (deployment) de software como lo son Docker, Vagrant, Puppet, Chef, Ansible, y un largo etcétera[0]. Con este poderoso concepto (contenedores) podemos aislar la ejecución de una aplicación junto con todas sus dependencias (binarios, librerías, espacio de procesos, sistema de archivos, etc) siendo una aproximación de virtualización más liviana a las ya conocidas máquinas virtuales porque no gastan tantos recursos de máquina debibo a que la virtualización no es a nivel de hardware sino a nivel de sistema operativo, permitiendo de esta manera mejorar el rendimiento ya que es el mismo que del sistema host. A pesar de todo, el uso de contenedores no era muy amplio debido al manto de complejidad que siempre los rodeaban. Docker, intenta cambiar esto haciendo el uso de contenedores extremadamente fácil tanto para la gente de desarrollo como el personal de operaciones.

Docker es un container engine de código abierto escrito en Go que automatiza el despliegue de aplicaciones (obviamente) en contenedores. Presentado en el año 2013 sin mucha parafernalia en la PyCon en una pequeña charla de cinco minutos llamada “The Future of Linux Containers”[1] y que a día de hoy con pocos años en el mercado, tiene un fuerte crecimiento no solamente en su número de usuarios sino también en el número de herramientas que han surgido para extenderlo, encontrándonos por ejemplo con Docker Hub, Docker compose, Docker machine o Docker Swarm. Integránsose también con muchas otras herramientas del ecosistema DevOps como AWS, OpenStack, Kubernetes, Jenkins, y muchas otras.

Componentes de Docker

Docker es una aplicación que sigue la arquitectura cliente/servidor, siendo el binario docker el cliente y el servidor el docker daemon (encargado de gestionar los contenedores) que después de la instalación[2], ambos quedarán en nuestro sistema aunque también es posible configurar el cliente para que haga uso de un docker daemon que se encuentre en un host diferente haciendo uso de una RESTful API[3].

Las imágenes son servicios ya “dockerizados” desde las cuales vamos a lanzar los contenedores (instancias de dicho servicio) que necesitemos. Por ejemplo si poseemos la imagen del servicio MySQL[4] podemos lanzar (docker run) cuantas instancias de MySQL deseemos para después detenerlas (docker stop) o destruirlas (docker rm) cuando sea necesario de manera rápida y fácil. Por lo tanto, una vez tengamos creada la imagen de un servicio o aplicación, podemos garantizar la portabilidad y ejecución correcta de ésta cuando se vaya a utilizar en otro ambiente posiblemente con un sistema operativo diferente pero que tenga el docker daemon instalado.

Las imágenes las podemos obtener ya sea de registros públicos o privados (podemos implementar nuestro propio registro con Docker registry[5][6]). Docker Hub[7] es el registro público utilizado por defecto y lo interesante es que además de encontrar una variedad de imágenes aportadas por la comunidad, encontramos imágenes oficialmente soportadas por los responsables de grandes proyectos de software como: Debian[7], Fedora[8], Nginx[9], MongoDB[10], Node.js[11] entre otros.

Ejecutando nuestro primer contenedor: Hello World

En Docker Hub contamos con una imagen oficial llamada hello-world[12] que nos permitirá no solamente validar nuestra instalación de Docker sino realizar el clásico “Hello world”, vamos a ello:

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world

c04b14da8d14: Pull complete
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

....

Como podemos ver en la salida de ejecución del contenedor hello-world, cuando se lanzó se conectó con el docker daemon y al no encontrar la imagen localmente la obtuvo desde Docker Hub para luego crear un contenedor con base en dicha imagen y mostrar el mensaje “Hello from Docker!”. Ahora vamos a decir “Hello World!” pero partiendo desde la imagen de Debian:

$ docker run debian:stable echo "Hello world!"
Unable to find image 'debian:stable' locally
stable: Pulling from library/debian

6f0e36e9ed69: Pull complete
Digest: sha256:33bd29f0ea52eed0039ea2a633116d8e9ffe723002acba9a47417af13e62fb32
Status: Downloaded newer image for debian:stable
Hello world!

También se ha descargado la imagen de Debian Stable porque no se encontraba localmente, luego se ejecuta el comando pasado como argumento (echo) y el contenedor termina su ejecución. Si consultamos los contenedores ejecutándose actualmente (docker ps) no veríamos ninguno ya que los dos contenedores anteriores terminaron su ejecución, para ver todos los contenedores incluidos los que no están en ejecución usamos la opcion -a:

$ docker ps -a
CONTAINER ID       IMAGE             COMMAND                 CREATED         STATUS                    PORTS    NAMES
463d7aa14c4d       debian:stable     "echo 'Hello world!'"   3 seconds ago   Exited (0) 2 seconds ago           stoic_ride
25a77ad9fa50       hello-world       "/hello"                6 seconds ago   Exited (0) 5 seconds ago           adoring_swanson

Iniciando un contenedor de Nginx

Ahora vamos a observar cómo tener un servicio de Nginx:

  • -d Ejecución del contenedor en background
  • -p 8080:80 Unimos el puerto 8080 del host al puerto 80 del contenedor para acceder a Nginx
$ docker run -d -p 8080:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
51f5c6a04d83: Pull complete
a3ed95caeb02: Pull complete
51d229e136d0: Pull complete
bcd41daec8cc: Pull complete
Digest: sha256:0fe6413f3e30fcc5920bc8fa769280975b10b1c26721de956e1428b9e2f29d04
Status: Downloaded newer image for nginx:latest
b6e6077b64665d60daa430348fa39e7e724f347d287b1f59cfbbba748d8e931f

La filosofía de Docker es tener un servicio por contenedor y generalmente las aplicaciones necesitan más de un servicio para su correcto funcionamiento. Por ejemplo, para tener una instancia de WordPress necesitaremos mínimo un servicio de base de datos (MySQL, MariaDB), un servicio web (Apache, Nginx) y realizar un link entre estos dos contenedores, pero cómo hacer dicho link lo veremos en una próxima ocasión.