[Actualizado, ampliado y corregido en noviembre de 2017.]
En una entrada de hace un par de años ya hablé de LXC, los contenedores Linux.
LXC (linux containers) linux dentro de linux.
Hoy hablaremos de Docker.
Docker, como LXC, utiliza el kernel de Linux para conseguir contenedores encapsulados, es lo que se llama virtualización a nivel del sistema operativo, hasta ahí son básicamente lo mismo, pero la «filosofía» de Docker es diferente.
Mientras LXC crea un contenedor Linux que es una sistema operativo completo, que puede correr múltiples servicios y que acumula los cambios que le hagamos, los contenedores Docker son versiones mínimas de una imagen Linux creada para una tarea específica, y que (aunque pueden hacerlo) se pretende que solo corran un único servicio. De ahí que el concepto de arquitectura de microservicios tenga mucho que ver con Docker.
Mientras un contenedor LXC es lo mismo que un servidor físico, o que una máquina virtual (VMware o VirtualBox) pero es virtualización a nivel de OS, Docker crea minicontenedores «apilables» para aplicaciones o servicios individuales. Simplificando la tarea de copiar, clonar, escalar y portar servicios. Es por eso ideal para la nube.
Para comprenderlo, hay que entender dos conceptos básicos en Docker:
Es muy importante distinguir las imágenes de los contenedores.
Las imágenes.
Una imagen de Docker, es una estructura de directorios y paquetes mínima, creada para una función básica. Se trata de que sea una plantilla, que se puede modificar, pero que ha sido pensada para una uso básico y específico. Por ejemplo, una imagen Debian con un Apache. De esta forma, para tener múltiples servidores web apache, cada uno encapsulado y aislado de los demás, sólo tendremos que crear contenedores desde ella, y cada uno será un servidor web distinto, con su propia IP, al que se puede configurar de manera independiente y que a su vez de puede clonar o mover donde se quiera.
Los contenedores.
Un contenedor, es una instancia generada desde una imagen, que ya es en sí un pequeño OS funcionando, y que podemos usarlo para una función concreta (como por ejemplo correr un Apache).
Por eso en Docker se trabaja primero con imágenes (las plantillas) y luego con contenedores (los minisistemas operativos funcionando). Veamos.
Instalamos Docker:
sudo apt install docker.io
sudo pacman -S docker
sudo emerge -a docker
(yo he instalado Docker en Gentoo y hay que tener en cuenta que en Gentoo hay que recompilar el núcleo con la configuración adecuada para que funcione Docker, véase https://wiki.gentoo.org/wiki/Docker)
Docker tiene dos partes, un demonio que debe arrancarse de fondo, y el cliente Docker que usamos para trabajar con él en consola.
Por lo tanto arrancamos el demonio (casi seguro que será con Systemd)
sudo systemctl start docker
si no usas Systemd:
/etc/init.d/docker start
y el demonio de Docker ya estará funcionado de fondo.
Si no queremos tener que estar siempre poniendo sudo, podemos meter a nuestro usuario en el grupo «docker»:
sudo gpasswd -a tu-usuario docker
newgrp docker
Y ya podremos usar Docker sin sudo.
Puedo ver información de Docker mediante:
docker version
y
docker info
Y ahora podemos buscar en remoto una imagen así:
docker search debian
o docker searh arch, docker search ubuntu, docker searh busybox, docker search apache, docker search wordpress, etc, etc.
(aquí podemos ver en la web las imágenes disponibles: https://hub.docker.com/explore/ )
Vemos que nos muestra un listado de imágenes listas para usar, diferentes distros, versiones, servidores web como Apache o Nginx, bases de datos, WordPress, etc, etc.
Bajemos una imagen de Busybox (un pequeño Linux para probar)
docker pull busybox
tras esto, si todo ha ido bien, ya tenemos una imagen de Busybox en local.
Podemos ver nuestras imágenes así:
docker images
y ahora ha llegado el momento de correr nuestro primer contenedor Docker, vamos allá:
docker run -it busybox sh
vemos que aparece el prompt y estamos dentro de nuestro contenedor.
podemos hacer
ls
o
ifconfig
y trabajar dentro como en cualquier distro Linux.
Dejamos esa terminal con Busybox y abrimos una nueva terminal.
Podemos ver en esta segunda terminal que el contenedor está funcionando:
docker ps -a
Si abrimos una segunda instancia desde la imagen Busybox, tendremos otro contenedor, idéntico pero distinto, veamos, otra vez:
docker run -it busybox sh
ya estamos en el segundo contenedor, abrimos una tercera terminal y lo vemos:
docker ps -a

vemos que ahora hay dos contenedores funcionando, cada uno tiene su propia ID, los dos están creados desde la imagen «busybox», y al final vemos que Docker le asigna a cada uno un simpático nombre automáticamente.
Puedo hacer un «ifconfig» en ambos y hacer un ping de uno a otro, con lo que comprobaré que se comunican perfectamente como si fuesen dos máquinas reales y distintas en dos puntos cualquiera del planeta. 🙂
O podría conectar de uno a otro mediante SSH. ¿Alucinante no?
Puedes bajar las imágenes que quieras y crear desde ellas múltiples contenedores.
docker pull debian
docker pull ubuntu
docker pull archlinux
y luego:
docker run -it debian sh
docker run -it ubuntu bash
docker run -it archlinux /bin/bash
(en sucesivas terminales)
(obsérvese que en algunos casos arranco en contenedor con el comando al final «sh» un shell genérico, aunque normalmente, como estará instalado Bash, utilizo su propio comando con o sin ruta «bash» o «/bin/bash»
con lo cual ya tendríamos funcionando dos instancias de Busybox, un Debian, un Ubuntu y un Arch. Todo un cluster propio.
Cada instancia tiene su propia ID, su propio nombre, su propia interfaz de red y funciona de forma autónoma.
Por supuesto en Debian y en Ubuntu puedo usar APT, y en Arch Pacman, para instalar software.
Y aquí encontramos una gran diferencia entre Docker y LXC. Si en un contenedor LXC, supongamos que un Debian, hago apt-get update & atp-get upgrade, el contenedor guarda los cambios, es un contenedor persistente. Si embargo, los contenedores Docker no lo son. Esto quiere decir que si lo cerramos, no guardará los cambios, pues son contenedores no persistentes. Han sido pensado así, para la persistencia tenemos las imágenes.
Pero, lo que sí puedo hacer es crear un contenedor desde una imagen original, luego hacerle los cambios que yo quiera, y generar desde él una nueva imagen, esta ya sí, con nuestros cambios. Veamos un ejemplo.
Ya tenemos una imagen Debian que hemos bajado antes, pues primero la corremos si no lo habíamos hecho, si ya la tenemos corriendo simplemente nos vamos a la terminal que la controla:
docker run -it debian bash
Ya estamos dentro.
Hacemos:
apt-get update & apt-get upgrade
para actualizar el sistema.
Instalamos tres paquetes básicos:
apt-get install nano htop screenfetch
El editor nano, el visor de procesos de sistema htop y screenfetch para embellecer nuestra terminal.
editamos con nano el archivo de configuración de bash:
nano /root/.bashrc
y le añadimos la línea:
screenfetch
ctl+o y ctl+x para guardar y salir de nano.
Resumo, hemos actualizado nuestro contenedor Debian mediante APT y hemos instalado 3 paquetes y configurado nuestra terminal. Pero si ahora saliésemos y cerrásemos el contenedor perdería los cambios, por lo que voy a guardar una imagen nueva desde él para poder usarla como nueva plantilla (imagen) de mi Debian. Esto se hace así:
Abro otra terminal:
docker ps -a
miro la ID del contenedor que he modificado y hago:
docker commit -m "Debian actualizado y con nano, htop y screenfetch" -a essau 831e2760f30b deb
Lo explico, «docker commit» es el comando que se encarga de crear una nueva imagen desde un contenedor, «-m» es comentario para saber qué he hecho, «-a» es el autor de la modificación, y luego va la ID del contenedor y finalmente el nombre que le doy a esta nueva imagen modificada por mí, en ente caso la he llamado «deb».
Si ahora miro qué imágenes tengo:
docker images
puedo ver que además de la Debian original, tengo una nueva que se llama «deb». Si creo contenedores desde la original estarán «vírgenes», pero si creo uno desde la nueva:
docker run -it deb bash

Vemos que al arrancar un contenedor desde ella, aparece la terminal con screenfetch, tal y como habíamos modificado 🙂
Ya para terminar una lista de los comandos de Docker más importantes:
ayuda:
docker
buscar imágenes (por ejemplo debian):
docker search debian
bajar una imagen:
docker pull debian
ver qué imágenes tengo en local:
docker images
historial de una imagen:
docker history ID o nombre de la imagen
eliminar una imagen:
docker rmi «ID de la imagen»
arrancar un contenedor desde una imagen:
docker run -it debian bash
(si intento arrancar una imagen que no tengo en local primero la descargará)
mirar qué contenedores tengo, su estado e información:
docker ps -a
arrancar un contenedor:
docker start ID (o nombre)
(un contenedor que esté detenido, pero ya antes creado, o sea que esté en STATUS Exited, podemos arrancarlo con docker start)
entrar en un contenedor:
docker attach ID (o nombre)
(si no tenemos una terminal abierta en un contenedor que esté corriendo, o sea en STATUS Up, podemos entrar en él con docker attach)
detener un contenedor:
docker stop ID (o nombre)
(esto no lo elimina, sólo lo detiene, lo pone en STATUS Exited, es lo mismo que teclear el comando exit desde una terminal suya)
eliminar un contenedor:
docker rm ID (o nombre)
guardar una imagen nueva desde un contenedor modificado:
docker commit -m «comentario» -a autor ID nuevo-nombre

Apéndice:
Lo fascinante de Docker es que no sólo puedo crear contenedores en mi máquina para jugar, sino que puedo crear contenedores de la nube para tener mis propios servidores que corran mis microservicios.
Para ello, lo más fácil es usar AWS (Amazon Web Services) que te da un año gratis para trastear y aprender:
http://aws.amazon.com/es/docker/
o también en Google, que da gratis hasta cinco nodos:
https://cloud.google.com/container-engine/
Ampliar información, en inglés 😦 :
http://prakhar.me/docker-curriculum/