Introducción a Docker
Máximo Martinez Soria
Devops
- En local funciona.
- En staging funciona.
- ¿Qué versión tenés?
- ¿Instalaste el servicio x?
Estas son algunas de las preguntas y comentarios que solemos hacer dentro del equipo cuando cada uno arma su entorno por separado, y como resultado, todos terminamos con entornos distintos. Estoy seguro que si tenés algo de experiencia desarrollando te topaste con esta situación alguna vez.
Mantener la paridad de entornos, tanto en la computadora de cada uno de los developers, como en staging, production, etc, es super importante para no tener este tipo de problemas, ahorrar tiempo y evitar estrés.
Virtualización
Una de las soluciones que tenemos hoy en día para resolver estos problemas, es la creación de maquinas virtuales. Lo cual, en palabras simples, consiste en “”crear”” una computadora adentro de otra.
Un poco más en profundidad, la virtualización es posible gracias a Hypervisor. Un software que le permite, a una maquina host, compartir sus recursos de forma virtual con maquinas guest.
Las máquinas virtuales tienen varias desventajas, entre ellas, las más problemáticas son la velocidad y el peso.
Al ser literalmente nuevas computadoras, no es que estamos simplemente corriendo nuestro software, sino que estamos arrancando toda una computadora y después corriendo nuestro software. Con lo cual, tenemos que esperar que el sistema operativo arranque y que terminen de correr todos los procesos necesarios para que la máquina este disponible. Y, sobre eso, correr nuestro software.
Por otro lado, estamos duplicando muchísimas cosas. Probablemente el sistema operativo sea lo más pesado, pero no es lo único que se duplica. También estamos duplicando los interpretes y/o compiladores de los lenguajes que necesitamos y el resto de servicios que corren sobre la máquina y que necesitamos para ejecutar un proyecto. Cosas como Python, PHP, Node, Redis, PostgreSQL, y un largo etc.
Esto hace que sea común encontrarse con máquinas virtuales que consideramos livianas y que pesan 2gb.
Esto también hace que, como developers, necesitemos mucho más espacio en nuestras computadoras para poder guardar todas las máquinas virtuales de todos los proyectos en los que trabajamos.
Contenerización
Hace muchos años, cuando los contenedores no existían, los puertos sufrían la falta de estandarización cada vez que un barco con mercancía llegaba. Cada producto, cada parte, cada repuesto, era diferente. De diferente tamaño, peso y volumen. Esto hacía terriblemente dificil la carga y descarga de los mismos.
Hasta que a alguien se le ocurrió que podíamos crear unas estructuras de metal con forma de prisma rectangular y basar toda la logística alrededor de ellos. Crearíamos barcos especialmente diseñados para transportarlos. Carritos especialmente diseñados para moverlos. Y todos tendrían el mismo tamaño, peso y volumen.
A esas estructuras de metal se las llamó containers y solucionaron muchos problemas en sector de la logística.
En software, los containers siguen la misma idea. En vez de tener muchas formas de construir, distribuir y ejecutar software, tenemos una sola forma, estandarizada y diseñada para correr en cualquier lado.
Los contenedores tienen bastantes ventajas frente a las máquinas virtuales. Entre ellas podemos destacar:
- Livianos: pueden utilizar archivos que fueron descargados para el uso de otro contenedor. Así, si tenemos 3 proyectos corriendo sobre Ubuntu 16, tendremos que instalar este sistema operativo solamente una vez.
- Rápidos: pueden utilizar archivos que ya existen en la máquina host. Esto nos evita tener que esperar a que una máquina encienda.
- Portables: están diseñados para correr en cualquier lado.
- Seguros: están completamente aislados. El código que se ejecuta adentro de un contenedor, no sabe que está dentro de uno y no tiene acceso a nada de lo que está afuera. (a menos que explícitamente se lo permitamos)
Qué es Docker
Docker es una plataforma que brinda herramientas para construir, distribuir y ejecutar software. Nos permite ejecutar aplicaciones en un entorno aislado, los contenedores, que poseen todo lo necesario para que la aplicación corra.
Docker images
Dentro de Docker, los containers son instancias ejecutables de una imagen. Y una imagen, es un template con las instrucciones para crear un container.
Para entenderlo mejor, veamos un ejemplo…
Cómo hacemos para usar una instancia de Ubuntu?
Tenemos que crear un contenedor que tenga a Ubuntu adentro.
¿Y cómo creamos ese contenedor?
Bueno resulta que existe un hub, una especie de App Store, de imágenes, en el cual se encuentra una imagen de Ubuntu que podemos usar para crear un contenedor.
Las imágenes pueden partir de otras imágenes. Por ejemplo, si tomamos la imagen de PHP, podemos ver que parte de la imagen de Debian.
Docker networks
Como dijimos anteriormente, los contenedores son entornos aislados. Tan es así, que ni siquiera saben que existe algo afuera del contenedor. Pero qué pasa si tenemos varios servicios, cada uno corriendo en su propio contenedor, que necesitan comunicarse entre sí?
Este es el problema que resuelven las networks.
En palabras simples, nos ayudan a exponer ciertos puertos de un contenedor para que puedan ser accedidos desde el exterior.
Docker storage
Docker tiene dos mecanismos para guardar archivos en la maquina host y así evitar que se eliminen cuando el contenedor detiene su ejecución.
Hay muchas razones por las cuales esto es necesario, pero creo que estos 2 ejemplos nos van a ayudar a entender mejor el porqué de esta funcionalidad:
- Los archivos de código de nuestra app van a vivir en un repositorio de git que va a ser clonado en nuestra computadora. A medida que vamos trabajando, no queremos tener que reiniciar el contenedor cada vez que hacemos un cambio para poder ejecutarlo.
- Cuando usamos bases de datos, estas van a estar vacías cada vez que iniciamos el contenedor. Esto sería una perdida de tiempo. Cada vez que corremos el proyecto, tendríamos que volver a cargar los datos.
Para resolver estas y muchas otras situaciones similares, Docker nos provee los siguientes mecanismos:
Bind Mounts
Consiste en hacerle un bind a un directorio de nuestra computadora con un directorio adentro del contenedor. Este bind, significa que ambos directorios están espejados. Cualquier cambio que se haga en uno, se vera reflejado en el otro.
Esto es bastante práctico, pero rompe un poco con el tema de que los contenedores deberían ser entornos aislados. Y de hecho, son un poco inseguros. Cuando usamos una imagen de Docker Hub, que no fue creada por nosotros, no sabemos el 100% de las cosas que va a hacer. Y darle acceso de escritura a un directorio de nuestra computadora, no es una muy buena idea. Por otro lado, esto nos obliga a tener estos directorios creados en nuestra computadora, lo cual puede ser un poco desordenado.
Volumes
Los volúmenes, son muy parecidos a los bind mounts, pero mucho más seguros. Esto es así porque son manejados enteramente por Docker y nadie, ni siquiera nosotros desde nuestra computadora, podemos acceder a ellos. Para hacerlo tendríamos que entrar al contenedor y tener los permisos necesarios.
Docker compose
Con todas las herramientas que vimos hasta ahora, ya estamos listos para usar Docker en nuestras aplicaciones. Un problema que nos vamos a encontrar, es que es muy tedioso levantar cada una de estas cosas por separado. Si tenemos nuestro código, una base de datos, una network y un storage, tendríamos que correr 4 comandos cada vez que necesitemos usar nuestra aplicación para poder tener todos los servicios necesarios levantados.
Docker compose, va a tomar un archivo yml
, donde va a estar especificado todo lo que se necesita, y va a levantar cada uno de los servicios. Para nosotros esto es transparente. Lo único que tenemos que hacer es escribir ese archivo y correr docker-compose up
.
Conclusión
Docker es muy complejo y tiene muchas funcionalidades. Incluso los temas básicos que tocamos en este post tienen conceptos mucho más profundos y complejos. Si te interesa seguir aprendiendo sobre Docker, te recomiendo que leas nuestra guía práctica sobre como crear un entorno de desarrollo para Laravel usando Docker, donde repasamos todos los conceptos explicados en este post, pero de una forma práctica mientras creamos un entorno de desarrollo para Laravel.
Y si sos como yo y te gusta entender el porqué de absolutamente todo, te recomiendo leer la documentación oficial de Docker, donde tienen muchas guías interesantes acerca del funcionamiento de la herramienta. Espero tus comentarios contando que cosas interesantes encontraste sobre Docker.