Múltiples contenedores con LXC en un solo host con CentOS 7

Linux Container (LXC) como tecnología de virtualización nos permite emular en un servidor físico o una máquina virtual, múltiples sistemas GNU/Linux que estando aislados entre si, y que permiten crear entornos virtuales con su propio espacio de procesos, memoria, bloques I/O y redes. Es importante mencionar que LXC no proporciona todas las características del software estándar de virtualización como lo hacen Xen, VMware, VirtualBox, y KVM. En esta pequeña guia te mostrare como instalar LXC en CentOS 7, generar varios contenedores y redireccionar el trafico proveniente de distintos puertos en un servidor con IP publica hacia los distintos contenedores creados.

Instalacion en CentOS 7:

Para instalar LXC en un CentOS 7 x86_64 es necesario contar con los repositorios de EPEL, por lo que si no contamos con ellos, los instalamos de la siguiente manera:

yum install -y epel-release

Ahora bien, las librerías y paquetes necesarios para LXC se encuentran en el anterior repositorio instalado. Para instalarlas, hacemos lo siguiente en la línea de comandos:

# yum install -y lxc lxc-templates lxc-devel lxc-doc lxc-libs libcap-devel libcgroup busybox wget debootstrap perl bridge-utils

La ejecucion anterior, en mi caso genero la siguiente salida:

Installed:
  bridge-utils.x86_64 0:1.5-9.el7   libcap-devel.x86_64 0:2.22-8.el7   libcgroup.x86_64 0:0.41-8.el7   lxc.x86_64 0:1.0.8-1.el7   lxc-templates.x86_64 0:1.0.8-1.el7  
  wget.x86_64 0:1.14-10.el7_0.1    

Dependency Installed:
  libseccomp.x86_64 0:2.2.1-1.el7 lua-alt-getopt.noarch 0:0.7.0-4.el7 lua-filesystem.x86_64 0:1.6.2-2.el7 lua-lxc.x86_64 0:1.0.8-1.el7 lxc-libs.x86_64 0:1.0.8-1.el7

Por ultimo y algo muy importante, debemos deshabilitar SElinux en nuestro equipo, en el pasado he escrito una guia de como desactivarlo y que podrás encontrar siguiendo este enlace.

 

Nat Networking:

LXC necesita hacer uso de nat networking para acceder desde y hacia la red externa de nuestro servidor físico o máquina virtual, existen dos formas de hacerlo usando libvirt o bien usando dnsmasq, por lo que primero explicare el método usado con libvirt, que permitirá hacer forwarding de la IP publica del servidor físico o máquina virtual hacia un puerto en especifico o todo el trafico a cualquiera de las IP privadas de los contenedores Linux creados y sobretodo asignarle una dirección IP a través del servicio de DHCP.

Deshabilitar NetworkManager services

Primero, y muy importante debemos desactivar NetworkManager.service y habilitar Network.service, para ello primero editamos el siguiente archivo ubicado en /etc/sysconfig/network  y agregamos lo siguiente:

NETWORKING=yes
HOSTNAME=localhost

Luego editamos el archivo en la ruta /etc/sysconfig/network-scripts/ifcfg-eth0 donde agregamos lo siguiente:

NM_CONTROLLED=no

Por ultimo, ejecutamos lo siguiente:

# systemctl disable NetworkManager.service
# systemctl stop NetworkManager.service
# systemctl enable network.service
# systemctl start network.service

Activar IP Forwarding:

Es importante activar el IP forwarding en nuestro servidor para ello, seguimos estos pasos, editamos el siguiente archivo /etc/sysctl.conf con lo siguiente:

net.ipv4.ip_forward = 1

Posterior a la edicion anterior, ejecutamos: 

sysctl -p

Tambien podemos hacerlo de la forma: sysctl -w net.ipv4.ip_forward=1 . 

Nat Bridge con Libvirt + DHCP:

Para ello, instalamos lo siguiente:

yum install -y libvirt

Luego, comprobamos que estan habilitados los servicios de libvirt, comprobamos que esta seteada la interfaz de red que usaremos:

virsh net-info default

También podremos chequear que esta habilitada la interfaz de red creada, utilizando el comando ifconfig virbr0 y que nos regresa la siguiente información:

virbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.1  netmask 255.255.255.0  broadcast 10.0.0.255
        ether 52:54:00:d8:92:96  txqueuelen 0  (Ethernet)
        RX packets 73  bytes 4672 (4.5 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 96  bytes 318453 (310.9 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

En mi caso, el bloque seleccionado de direcciones IP es el 10.0.0.0/32 por lo que si quiero modificarla a otra de mi conveniencia, editamos el siguiente archivo: /etc/libvirt/qemu/networks/default.xml con los siguientes cambios:

<ip address='10.0.0.1' netmask='255.255.255.0'>
<dhcp>
      <range start='10.0.0.200' end='10.0.0.250'/>
</dhcp>
</ip>

Realizados los cambios reseteamos la interfaz siguiendo estos pasos, utiliamos virsh para desmontar y montar la interfaz:

1. Eliminamos la interfaz: virsh net-destroy default

2. Creamos una nueva interfaz con nuestro bloque de IP deseado: virsh net-define /etc/libvirt/qemu/networks/default.xml

3. Seteamos la interfaz para que se autonicie cuando se reinicia nuestro host: virsh net-autostart default

4. Iniciamos la interfaz creada:   virsh net-start default

5. Listamos la interfaz iniciada y compramos:  virsh net-list --all

6. Comprobamos que se han realizados los cambios: ifconfig virbr0

Volviendo a nuestra configuración con LXC, deberemos indicarle en el archivo de configuración cual es la interfaz a usar, para ello editamos el siguiente archivo /etc/lxc/default.conf con lo siguiente: 

lxc.network.type = veth
lxc.network.link = virbr0
lxc.network.name = eth0
lxc.network.mtu = 1500
lxc.network.flags = up
lxc.network.ipv4.gateway = 10.0.0.1
lxc.network.ipv4 = 0.0.0.0

Nótese que se ha seteado como link la interfaz virbr0 del servicio de libvirt. Reiniciamos el servicio de LXC con Systemd:

# systemctl restart lxc

y comprobamos que tenemos nuestra configuración correcta, por lo que ejecutamos: lxc-checkconfig

Tendremos un salida parecida a esta:

...
--- Misc ---
Veth pair device: enabled
Macvlan: enabled
Vlan: enabled
Bridges: enabled
Advanced netfilter: enabled
CONFIG_NF_NAT_IPV4: enabled
CONFIG_NF_NAT_IPV6: enabled
CONFIG_IP_NF_TARGET_MASQUERADE: enabled
CONFIG_IP6_NF_TARGET_MASQUERADE: enabled
CONFIG_NETFILTER_XT_TARGET_CHECKSUM: enabled

--- Checkpoint/Restore ---
checkpoint restore: enabled
CONFIG_FHANDLE: enabled
CONFIG_EVENTFD: enabled
CONFIG_EPOLL: enabled
CONFIG_UNIX_DIAG: enabled 
...

Nat Bridge con Dnsmasq + DHCP + IP Estaticas:

Este método lo recomiendo, hasta los momentos y a mi parecer es el mas estable. Por lo que sin mas, instalamos los paquetes necesarios:

# yum install -y dnsmasq bridge-utils

Luego de lo anterior es necesario crear dos archivos que seran los servicios encargados de asignar IP a nuestros contenedores en el directorio /etc/systemd/system/ . Creamos el primer archivo en la ruta y con nombre: /etc/systemd/system/lxc-dhcp.service

[Unit]
Requires=lxc-net.service
Requires=sys-devices-virtual-net-virbr0.device
After=sys-devices-virtual-net-virbr0.device

[Service]
ExecStart=/usr/sbin/dnsmasq \
            --strict-order \
            --bind-interfaces \
            --pid-file=/var/run/dnsmasq.pid \
            --listen-address 10.0.0.1 \
            --dhcp-range 10.0.0.150,10.0.0.200,72h \
            --dhcp-lease-max=253 \
            --dhcp-no-override \
            --except-interface=lo \
            --interface=virbr0 \
            --dhcp-leasefile=/var/run/lxc_dnsmasq.virbr0.leases \
            --dhcp-authoritative \
            --dhcp-option=6,8.8.8.8,8.8.4.4 \
            --keep-in-foreground \
            --user=nobody \
            --group=nobody \
            --dhcp-hostsfile=/etc/lxc/dnsmasq-hosts.conf

[Install]
WantedBy=default.target

Creamos el segundo archivo en la ruta y con nombre /etc/systemd/system/lxc-net.service

[Unit]
Description=Bridge interface for LXC Containers

[Service]
Type=oneshot

# Bring up bridge interface
ExecStart=/usr/sbin/brctl addbr virbr0
ExecStart=/usr/sbin/ip address add 10.0.0.1/24 dev virbr0
ExecStart=/usr/sbin/ip link set virbr0 up

RemainAfterExit=yes

# Bring bridge interface down
ExecStop=/usr/sbin/ip link set virbr0 down
ExecStop=/usr/sbin/brctl delbr virbr0

[Install]
WantedBy=default.target

Creados los archivos anteriores, habilitamos y reiniciamos los servicios para lxc-net y lxc-dhcp:

# systemctl enable lxc-net.service
# systemctl enable lxc-dhcp.service
# systemctl start lxc-net.service
# systemctl start lxc-dhcp service

Luego editamos el archivo en la ubicación /etc/lxc/default.conf con lo siguientes parámetros:

lxc.network.type = veth
lxc.network.link = virbr0
lxc.network.name = eth0
lxc.network.mtu = 1500
lxc.network.flags = up
lxc.network.ipv4.gateway = 10.0.0.1
lxc.network.ipv4 = 0.0.0.0

En la misma ubicación creamos el archivo para nuestros contenedores con dirección IP estática, el archivo tiene la ubicación y nombre /etc/lxc/dnsmasq-hosts.conf y dentro de el colocamos el nombre del contenedor que se ha creado y luego la dirección IP estática que queremos que obtenga siempre:

container_fedora,10.0.0.222
container_ubuntu,10.0.0.223

Asi por ejemplo el contenedor con nombre container_fedora se le va asignar siempre la dirección IP 10.0.0.222 . Posterior a lo anterior, reiniciamos el servicio de LXC:

# systemctl restart lxc.service

Es muy importante habilitar el servicio de lxc:

# systemctl enable lxc.service

Activar Netfilter/Iptables:

Es necesario activar algunas reglas con IPtables, por lo que crearemos un script en bash donde setearemos las rutas de la nueva interfaz virbr0 y la posibilidad de usar el protocolo DHCP , el script bash quedaría algo asi:

#!/bin/bash
iptables -I INPUT -i virbr0 -p udp --dport 67 -j ACCEPT
iptables -I INPUT -i virbr0 -p tcp --dport 67 -j ACCEPT
iptables -I INPUT -i virbr0 -p tcp --dport 53 -j ACCEPT
iptables -I INPUT -i virbr0 -p udp --dport 53 -j ACCEPT
iptables -I FORWARD -i virbr0 -j ACCEPT
iptables -I FORWARD -o virbr0 -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 ! -d 10.0.0.0/24 -j MASQUERADE
iptables -t mangle -A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill

Luego de ejecutarlo y comprobar que se han aplicado las anteriores reglas, hacemos que estos cambios sean permanentes de la siguiente forma:

# iptables-save > /etc/sysconfig/iptables
# iptables-save > /etc/sysconfig/ip6tables

O tambien podemos ejecutar:

# service iptables save

Atencion: es muy importante que la siguiente regla: iptables -t mangle -A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill este presente, sin ella, nuestros contenedores no podran obtener direcciones IP a traves del protocolo DHCP que se ha implementado usando Dnsmasq. Además, recuerden que CentOS 7 cuenta con un services llamado Firewalld y que viene por default en toda instalación, es necesario hacer un: systemctl disable firewalld && systemctl stop firewalld y luego instalar el paquete para IPtables de la siguiente forma:

# yum install iptables-services -y && systemctl enable iptables && systemctl start iptables

 

Nuestro primer Linux Container:

Luego de toda las configuraciones y modificaciones, crearemos nuestro primer contenedor para ello usare la template de Fedora 22, ejecutamos lo siguiente en la línea de comandos: 

# lxc-create -n container_fedora -t fedora

De esta forma llamamos a la template de Fedora 22 usando la -t y seteamos un nombre para el contenedor con -n.

Creado el contenedor necesitamos setear el password de root para cuando necesitemos acceder, por lo que ejecutamos en la línea de comandos:

# chroot /var/lib/lxc/container_fedora/rootfs passwd

Luego de cambiar el password, iniciaremos nuestro contenedor con la version 22 de Fedora Server, la iniciamos utilizando el comando lxc-start acompañado con dos parámetros y valores -n para el nombre y -d para enviarlo al background:

# lxc-start -n container_fedora -d

Para conectarnos a este contenedor iniciado en modo background, lo hacemos de la siguiente manera  lxc-console -n container_lxc_centos -t 0 al pasarle el parámetro -t le estamos indicando que nos conecte con la tty0. Luego veremos una salida como esta:

Connected to tty 0
Type <Ctrl+a q> to exit the console, <Ctrl+a Ctrl+a> to enter Ctrl+a itself

Deberemos presionar la tecla [ enter ] y luego introduciremos nuestro usuario root con el password previamente modificado.

Si necesitamos salir de nuestro contenedor, deberemos presionar al mismo tiempo la tecla Ctrl y la letra a , para luego soltarlas y por ultimo presionar la tecla q.

Para obtener mas información de nuestro contenedor en ejecución desde donde incluso podremos obtener la IP privada asignada al contenedor y conectarnos por SSH, para ello ejecutamos: lxc-info -n container_fedora. La salida seria algo como lo siguiente:

Name:           container_fedora
State:          RUNNING
PID:            8693
IP:             10.0.0.222
CPU use:        0.10 seconds
BlkIO use:      0 bytes
Memory use:     1.12 MiB
KMem use:       0 bytes
Link:           vethUELIE5
 TX bytes:      1.76 KiB
 RX bytes:      1.68 KiB
 Total bytes:   3.44 KiB

 

Reenvío de puertos hacia contenedores

Si se cuenta con múltiples contenedores creados que contienen distintos servicios, por ejemplo, en nuestro contenedor de nombre container_fedora esta instalado un servidor web (Nginx) y queremos que desde la interfaz con IP Publica ( eth0 ) se pueda acceder por el puerto 8080 al puerto 80 del servidor web, tendremos que setear una regla sencilla con IPtables, por lo que al anterior bash scripting agregaremos lo siguiente:

iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.222:80

Lo anterior seteado nos indica que todo el trafico proveniente del puerto 8080 sea enviado hacia la dirección IP privada 10.0.0.222 del contenedor y al puerto 80.