Crear contenedores Docker en LXD

Docker es un sistema de virtualización que a diferencia de LXC «en el que se baso en sus comienzos» está especializado en virtualizar aplicaciones en lugar de sistemas operativos.

Por su lado LXC es un sistema de virtualización de propósito general que se encarga del nivel más bajo de virtualización: crear espacios separados para cada contenedor, aplicar las políticas de seguridad y proporcionar herramientas para parar, arrancar y copiar contenedores.

La ventaja de docker es la facilidad que tiene para distribuir aplicaciones y la ventaja de LXC es la facilidad para distribuir contenedores. No son excluyentes y si ejecutas contenedores docker dentro de LXC tendrás las ventajas de ambos.

Imagina levantar una aplicación de varios contenedores con docker-compose y a su vez tener las ventajas de un sistema de virtualización clásico: poder hacer snapshots, copias, migraciones a otro host, arrancar y parar la aplicación en su conjunto.

Instalar LXD

Para realizar la instalación vas a partir de un host con Ubuntu server 64-bit con el paquete LXD instalado.

Para ubuntu 16.4 y 18.4 server los paquetes LXC y LXD están instalados de forma predeterminada.

$ uname -a
Linux superadmin.es 4.4.0-97-generic 

$ cat /etc/lsb-release  | grep RELEASE
DISTRIB_RELEASE=16.04

$ lxd --version
2.0.10

Instala los paquetes linux-image-extra y carga algunos módulos del kernel que son necesarios.

$ apt-get install linux-image-extra-`uname -r` 
$ modprobe aufs 
$ modprobe br_netfilter
$ modprobe nf_nat
$ modprobe overlay

Puedes ver que entre los modulos cargados está AUFS que sirve para aprovechar las características de union fs o sistemas de capas de Docker.

Si no cargas el modulo AUFS, más adelante, cuando instales Docker no lo detectará y cargará el driver VFS. VFS es el driver más compatible y tiene un rendimiento aceptable. El pero es que, al contrario que aufs o btrfs, VFS no es un sistema de tipo Union FS. No compartir capas supone que todas las capas en cada contenedor serán únicas, aumentando considerablemente el uso de espacio en disco. En discos pequeños, principalmente SSD, es un problema si vas a crear muchos contenedores.

Nota No es posible cargar módulos del kernel dentro de un contenedor LXC. Tienes que hacerlo siempre desde el host anfitrión.

Ahora ya puedes crear el primer contenedor LXC.

Crear el contenedor LXC

Crear un contenedor con LXC es fácil. Por ejemplo, para crear un contenedor Ubuntu con el nombre “prueba” ejecuta:

$ lxc launch ubuntu: prueba -p default

-p default carga el profile default que da capacidades de red al contenedor para que pueda comunicarse con el exterior.

Y para que un contenedor LXC pueda arrancar otros contenedores en su interior tienes que configurar la propiedad security.nesting que permite anidar contenedores (nested containers).

$ lxc config set prueba security.nesting true

En lugar de configurar cada uno de los contenedores de la aplicación como en la línea anterior, es mejor crear un perfil ya preconfigurado con todos los requisitos de docker y que puedas reutilizar.

Para crear un perfil llamado “docker”:

$ lxc profile create docker
$ lxc profile set docker security.nesting "true"
$ lxc profile set docker linux.kernel_modules "overlay, nf_nat, br_netfilter"

$ lxc profile show docker
config:
  linux.kernel_modules: overlay, nf_nat, br_netfilter
  security.nesting: "true"

Y ahora crea el mismo contenedor “prueba” usando el profile default y docker:

$ lxc launch ubuntu: prueba -p default -p docker

# y si queremos arrancar el contenedor como privileged:
$ lxc launch ubuntu: prueba -c security.privileged=true -p default -p docker

# Listo el nuevo container
$ lxc list
+---------+---------+---------------------+
| prueba  | RUNNING | 10.71.134.69 (eth0) |
+---------+---------+---------------------+

Al finalizar, tendrás un contenedor con el nombre “prueba” que corre una imagen de Ubuntu y que esta preparado para instalar Docker.

Instalar Docker en el contenedor LXC

Para instalar docker en el contenedor “prueba” ejecuta en la consola:

# instalar docker

$ lxc exec prueba -- bash -c "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -"
OK
$ lxc exec prueba -- apt-key fingerprint 0EBFCD88
...

$ lxc exec prueba -- add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
$ lxc exec prueba -- apt-get update
...

$ lxc exec prueba -- apt-get install -y docker-ce
...

lxc exec sirve para ejecutar comandos de forma remota en el contenedor.

Ahora, comprueba que docker esta instalado y que ha cargado AUFS como driver para el storage:

# comprobar la instalación

$ lxc exec prueba -- docker version
Client:
 Version:           18.06.0-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        0ffa825
 Built:             Wed Jul 18 19:11:02 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.0-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       0ffa825
  Built:            Wed Jul 18 19:09:05 2018
  OS/Arch:          linux/amd64
  Experimental:     false


$ lxc exec prueba -- docker info | grep ^Storage\ Driver
Storage Driver: aufs

Cómo paso extra, configura unos nameservers apropiados en el resolv.conf:

# configurar ns personalizados

$ lxc exec prueba -- resolvconf --disable-updates
$ lxc exec prueba -- echo "nameserver 8.8.8.8" > /etc/resolv.conf

Por último, arranca un contenedor docker de prueba y comprueba que funciona:

# arrancar un docker de prueba 

$ lxc exec prueba -- docker run -d ubuntu:latest sleep 999999
acb699f3ecb8228c15c6fb8a067031ba396139d19e3b501bbd5b97c357bda80d

$ lxc exec prueba -- docker ps -q
acb699f3ecb8

Que arranca un contenedor de docker llamado “prueba” con un proceso en sleep que sirve para mantenerlo en ejecución.

Orquestar con docker-compose

Una forma de orquestar Docker es usando el API de Docker que, por defecto, no esta configurado.

Para habilitarlo modifica el script de arranque del systemd del siguiente modo:

$ lxc exec prueba -- sed -i  \
  's#^ExecStart.*#ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock# \
  /lib/systemd/system/docker.service
$ lxc exec prueba -- systemctl daemon-reload
$ lxc exec prueba -- systemctl restart docker

Una vez habilitado ya puedes usar el API de docker configurando la variable de entorno $DOCKER_HOST de modo que apunte a la dirección IP de la red del contenedor LXD. Por ejemplo:

# Obtengo la IP del contenedor
$ IP=$(lxc list prueba --format json | jq -r '.[]|.state.network.eth0.addresses[]|select(.family=="inet")|.address')
$ echo $IP
10.71.134.69

# o bien:
$ lxc list prueba -c4
+--------------------------------+
|              IPV4              |
+--------------------------------+
| 172.17.0.1 (docker0)           |
| 10.71.134.69 (eth0)            |
+--------------------------------+


$ export DOCKER_HOST=tcp://${IP}:2376
$ export COMPOSE_HTTP_TIMEOUT=300

Por último, arranca la aplicación con docker run o con docker-compose.

$ docker-compose -f up

que levantará todos los contenedores de la aplicación docker dentro del contenedor LXC.

Como puedes ver LXD es una gran alternativa a VirtualBox o Kubernetes. Especialmente frente a VirtualBox que tiene un rendimiento pobre tanto en red como a la hora de acceder a espacios de almacenamiento compartido. Sin olvidar el mayor tamaño de un disco de Virtualbox comparado con el de un contenedor LXC.

César Maeso


Creative Commons License

Esta obra está bajo una licencia de Creative
Commons Reconocimiento-NoComercial-CompartirIgual
4.0 Internacional
.