LXC: primeros pasos

LXC: primeros pasos

Hola a todos. Llevo un tiempo dándole vueltas a dar un salto más en la virtualización. Teóricamente con KVM conseguimos muy buen rendimiento, pero con contenedores reduciríamos al máximo la diferencia entre la máquina anfitriona y sus invitados.

Ahora todos los hispters gritarán de alegría coreando al unísono Docker! Docker! Docker!, pues guardad los fulares porque por ahora simplemente vamos a realizar una pequeña prueba y lo más natural es probar la tecnología que es más parecida a mi forma de administrar los sistemas, LXC. Para los más viejos del lugar, LXC vendría a ser como un chroot hipervitaminado. También hay que decir que Docker se basó inicialmente en LXC para su desarrollo y que su concepto de instancia es un poco diferente.

En un horizonte más lejano tenemos OpenStack, pero cualquier tecnología de virtualización que aprendamos nos servirá en ese ecosistema por lo que es tiempo bien aprovechado.

Para esta pequeña prueba vamos a crearnos un par de máquinas, una con Debian (Stretch, que es la testing actualmente) y otra con Ubuntu (*Xenial, es decir la reciente 16.04), evidentemente no utilizaremos dos máquinas físicas, bastará con que sean dos máquinas virtuales con KVM, sí, habéis oído bien, contenedores dentro de máquinas virtuales, una locura.

Crearé estas dos instancias porque quiero comparar la configuración de ambas aproximaciones para ver cuál es más cómoda de administrar. Ambas distribuciones tienen el paquete LXC en la versión 2.0 por lo que la comparación será justa. Una vez que tengamos listos estos servidores crearemos un contenedor en cada una de ellas, con la misma configuración y dentro de ellos haremos una instalación rápida de PostgreSQL para comparar su rendimiento.

En este punto supondremos que tenemos ambas distribuciones limpias y funcionando, ya que la configuración de KVM la tenemos más que superada por posts anteriores. En orden de antigüedad: 1, 2, 3, 4, 5 y 6.

La configuración de la base de LXC es similar tanto en Debian como en Ubuntu, así que estos comandos podemos ejecutarlos en ambos sistemas. Primero instalamos el paquete principal:

# apt-get install lxc

Ahora, para asegurarnos que todo funciona correctamente disponemos de una orden que se instala junto con la base:

# lxc-checkconfig
Kernel configuration not found at /proc/config.gz; searching...
Kernel configuration found at /boot/config-4.4.0-31-generic
--- Namespaces ---
Namespaces: enabled
Utsname namespace: enabled
Ipc namespace: enabled
[...]

Si la instalación ha sido exitosa todos los parámetros nos deben aparecer con un tranquilizador enabled y en un agradable verde lima. Listo, ya podemos crear nuestro primer contenedor:

# lxc-create -t download -n ubuntu-test
Setting up the GPG keyring
Downloading the image index

---
DIST	RELEASE	ARCH	VARIANT	BUILD
---
alpine	3.0	amd64	default	20160630_17:50
alpine	3.0	i386	default	20160630_17:50
[...]
ubuntu	yakkety	s390x	default	20160717_03:49
---

Distribution: ubuntu
Release: xenial
Architecture: amd64

Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs

---
You just created an Ubuntu container (release=xenial, arch=amd64, variant=default)

To enable sshd, run: apt-get install openssh-server

For security reason, container images ship without user accounts
and without a root password.

Use lxc-attach or chroot directly into the rootfs to set a root password
or create user accounts.

Como véis hemos elegido Ubuntu para nuestro contenedor y en la versión estable, la 16.04, Xenial, por suspuesto la de 64 bits. Cuando lancemos el contenedor veremos una pequeña diferencia entre los dos anfitriones, primero Debian:

# lxc-start -n ubuntu-test
# lxc-info -n ubuntu-test
Name:           ubuntu-test
State:          RUNNING
PID:            2878
CPU use:        0.51 seconds
BlkIO use:      8.00 KiB
# lxc-ls -f
NAME        STATE   AUTOSTART GROUPS IPV4 IPV6 
ubuntu-test RUNNING 0         -      -    -   

Y ahora Ubuntu:

# lxc-start -n ubuntu-test
# lxc-info -n ubuntu-test
Name:           ubuntu-test
State:          RUNNING
PID:            4459
IP:             10.0.3.162
CPU use:        0.71 seconds
BlkIO use:      8.00 KiB
Memory use:     12.52 MiB
KMem use:       0 bytes
Link:           vethYR5TMW
 TX bytes:      1.27 KiB
 RX bytes:      1.40 KiB
 Total bytes:   2.67 KiB
# lxc-ls -f
NAME        STATE   AUTOSTART GROUPS IPV4       IPV6 
ubuntu-test RUNNING 0         -      10.0.3.162 -   

La diferencia es que la red no se ha levantado automáticamente en el primer caso. Vamos a arreglar esto. Creamos el archivo /etc/default/lxc-net en nuestro anfitrión Debian con el siguiente contenido:

USE_LXC_BRIDGE="true"
LXC_BRIDGE="lxcbr0"
LXC_ADDR="10.0.3.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="10.0.3.0/24"
LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"
LXC_DHCP_MAX="253"
LXC_DHCP_CONFILE=""
LXC_DOMAIN=""

Y activamos el servicio:

# systemctl enable lxc-net
# systemctl start lxc-net
# reboot

Si ejecutamos un ifconfig vemos que ha aparecido la interfaz de red lxcbr0. Ahora debemos asignarle esta interfaz a nuestro contenedor editando el fichero /var/lib/lxc/ubuntu-test/config en el anfitrión Debian y cambiando las últimas líneas por éstas:

[...]
# Network configuration
lxc.network.type = veth
lxc.network.link = lxcbr0
lxc.network.flags = up
lxc.network.hwaddr = 00:16:3e:43:1a:7e

Tened en cuenta que la MAC de la tarjeta de red sería recomendable cambiarla si váis a crear varios contenedores. La primera vez que probé a reiniciar el contenedor con la nueva configuración no cogió una ip por lo que opté por la decisión más rápida, reiniciar por completo el anfitrión y una vez arrancado lancé la instancia:

# lxc-stop -n ubuntu-test
# lxc-start -n ubuntu-test
# lxc-info -n ubuntu-test
Name:           ubuntu-test
State:          RUNNING
PID:            2813
CPU use:        0.29 seconds
BlkIO use:      0 bytes
Link:           vethP7PUAB
 TX bytes:      850 bytes
 RX bytes:      634 bytes
 Total bytes:   1.45 KiB

Si alguien sabe porqué LXC no reparte direcciones ip en Debian cambiando el servicio lxc-net, activándolo y reiniciándolo todos le agradeceremos el comentario, de momento seguiremos con nuestra solución salomónica y reiniciaremos todo el anfitrión.

Ahora ya volvemos a tener una parte que podemos aplicar a ambos anfitriones. Lo primero que vamos a hacer es conectarnos a ellos y comprobar que tenemos acceso a internet:

# lxc-attach -n ubuntu-test
root@ubuntu-test:/# apt update
Hit:1 http://archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
[...]

Bien, todo esto está muy bien, pero necesitamos un poco de acción, PostgreSQL estaría bien. Vamos a instalar el servidor en ambos huéspedes y hacer un pequeño test. Los pasos son iguales en ambos contenedores, añadiremos las fuentes oficiales del proyecto e instalaremos el paquete:

root@ubuntu-test:/# echo "deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main" > /etc/apt/sources.list.d/pgdg.list
root@ubuntu-test:/# apt install wget
root@ubuntu-test:/# wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
root@ubuntu-test:/# apt update
root@ubuntu-test:/# apt install postgresql-9.5

Una vez instalado el servidor ya podemos realizar un test de rendimiento pero primero, para facilitar las cosas. vamos a cambiar el fichero /etc/postgresql/9.5/main/pg_hba.conf y ponemos todos los métodos de autenticación a trust:

[...]
# Database administrative login by Unix domain socket
local   all             postgres                                trust

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
host    all             all             ::1/128                 trust
# Allow replication connections from localhost, by a user with the
[...]

Ya podemos seguir con el test:

root@ubuntu-test:/# systemctl restart postgresql
root@ubuntu-test:/# su - postgres
postgres@ubuntu-test:~$ createdb performance_tests
postgres@ubuntu-test:~$ exit
logout
root@ubuntu-test:/# pgbench -i -s 10 -h localhost -U postgres performance_tests
NOTICE:  table "pgbench_history" does not exist, skipping
NOTICE:  table "pgbench_tellers" does not exist, skipping
NOTICE:  table "pgbench_accounts" does not exist, skipping
NOTICE:  table "pgbench_branches" does not exist, skipping
creating tables...
100000 of 1000000 tuples (10%) done (elapsed 0.08 s, remaining 0.70 s)
200000 of 1000000 tuples (20%) done (elapsed 0.16 s, remaining 0.63 s)
300000 of 1000000 tuples (30%) done (elapsed 0.37 s, remaining 0.86 s)
400000 of 1000000 tuples (40%) done (elapsed 0.59 s, remaining 0.88 s)
500000 of 1000000 tuples (50%) done (elapsed 0.81 s, remaining 0.81 s)
600000 of 1000000 tuples (60%) done (elapsed 1.02 s, remaining 0.68 s)
700000 of 1000000 tuples (70%) done (elapsed 1.10 s, remaining 0.47 s)
800000 of 1000000 tuples (80%) done (elapsed 1.18 s, remaining 0.29 s)
900000 of 1000000 tuples (90%) done (elapsed 1.26 s, remaining 0.14 s)
1000000 of 1000000 tuples (100%) done (elapsed 1.34 s, remaining 0.00 s)
vacuum...
set primary keys...
done.
root@ubuntu-test:/# pgbench -c 16 -j 16 -T 60 -h localhost -U postgres performance_tests
starting vacuum...end.
transaction type: TPC-B (sort of)
scaling factor: 10
query mode: simple
number of clients: 16
number of threads: 16
duration: 60 s
number of transactions actually processed: 15284
latency average: 62.811 ms
tps = 254.090037 (including connections establishing)
tps = 254.363806 (excluding connections establishing)

Podemos tunear un poco la configuración de PostgreSQL pero el tema del post no es ése, vamos a comparar el comportamiento de la base de datos en los anfitriones frente a sus contenedores para comprobar hasta qué punto se pierde rendimiento.

Para instalarlo en los anfitriones el proceso es el mismo que hemos explicado antes. Según mis pruebas, realizadas varias veces, la media de transacciones procesadas estaban en torno a las quince mil, con lo que sí, parece que realmente la penalización en los contenedores es prácticamente nula. Lo ideal sería realizar una batería de pruebas cortesía de los chicos de phoronix, pero este pequeño experimento nos ha servido para confirmar lo que se viene comentando de esta tecnología y para animarnos a seguir investigando por esta vía.

Y a partir de aquí qué?, os preguntaréis, pues yo también. De momento se me ocurre montar una prueba con Docker, sobre este mismo laboratorio de máquinas, ver si es igual de simple de configurar y realizar los tests. Luego tenemos la opción de desarrollar habilidades con alguna herramienta de orquestación de máquinas virtuales/contenedores como puede ser Ansible, Puppet o similares. A partir de ahí el cielo es el límite y una cosa que realmente quiero investigar es el sistema OverlayFS que se utiliza con LXC para permitir que varios contenedores compartan una parte del sistema de ficheros común y así reducir el mantenimiento de este tipo de infraestructuras.

También podríamos hablar de cómo tunear todos estos recursos una vez que ya tenemos los conceptos básicos claros, pero esto ya será en próximos capítulos.

Vamos a despedirnos yéndonos arriba y bailando por la convulsa inglaterra de 1984 junto con el pequeño Billy Elliot al son de The Clash y su perfecta London Calling de su disco homónimo, aunque hoy prefiero aconsejaros la impresionante recopilación hecha para la banda sonora de la película.

Dance!