Introdução ao LXC (LinuX Containers)

Introdução

LXC (LinuX Containers) é um tipo de virtualização em nível de sistema operacional que proporciona a execução de vários sistemas Linux de forma isolada (containers) em um único host de controle. Em outras palavras é uma alternativa para virtualização completa e leve se comparada aos hypervisors KVM, Xen e VMware.

O Kernel Linux possui o recurso Cgroups que é utilizado para limitar e isolar o uso de (CPU, memória, disco, rede, etc) e também o isolamento de namespace que basicamente separa grupos de processos de modo que eles não enxerguem os recursos de outros grupos, ou seja:

Linux Cgroups

Antes de continuar, vamos relembrar sobre os tipos de virtualização (bare metal e hosted).

O tipo bare metal, o software que proporciona a virtualização é instalado diretamente sobre o hardware:(Xen, VMware, Hyper-V), esse tipo proporciona um isolamento maior e ao mesmo tempo uma sobrecarga pois cada máquina virtual que é criada irá executar seu próprio kernel e instância do sistema operacional.

Já o tipo hosted, o software que proporciona a virtualização é executado sobre um sistema operacional:(VirtualBox).

A virtualização por containers proposta pelo LXC ocorre de forma menos isolada pois compartilha algumas partes do kernel do host fazendo com que a sobrecarga seja menor.

Testando

Durante os testes que fiz utilizei uma instância na Amazon com (Ubuntu 14.04LTS 64 bits), a instalação do LXC é bem simples no ubuntu:

ubuntu@heroku:~$ sudo apt-get install lxc

Depois de instalado vamos para a criação do nosso primeiro container, para isso usaremos o comando lxc-create:

ubuntu@heroku:~$ sudo lxc-create -t ubuntu -n dyno-01

O parâmetro -t especifica o template que usaremos para criar o container, no caso estou especificando o Ubuntu, por padrão o LXC utiliza a última versão LTS. O -n determina um nome para o nosso container.

Após finalizar a criação do container, podemos listar com lxc-ls que retorna todos os containers criados, para verificar o atual estado de execução podemos usar o comando lxc-info:

ubuntu@heroku:~$ sudo lxc-ls
dyno-01
ubuntu@heroku:~$ sudo lxc-info -n dyno-01
Name:    dyno-01
State:   STOPPED
ubuntu@heroku:~$

Agora que criamos o nosso container, podemos iniciá-lo com lxc-start:

ubuntu@heroku:~$ sudo lxc-start -d -n dyno-01
ubuntu@heroku:~$ sudo lxc-info -n dyno-01
Name:           dyno-01
State:          RUNNING
PID:            5986
IP:             10.0.3.69
CPU use:        2.54 seconds
BlkIO use:      100.00 KiB
Memory use:     7.89 MiB
Link:           vethPTIYBW
 TX bytes:      2.02 KiB
 RX bytes:      2.04 KiB
 Total bytes:   4.07 KiB
ubuntu@heroku:~$

A opção -d que utilizei foi para executar o container em segundo plano, dessa forma ele não assume o console e assim podemos nos conectar a ele com lxc-console:

ubuntu@heroku:~$ sudo lxc-console -n dyno-01
ubuntu@dyno-01:~$

Nesse momento entramos no container dyno-01 e tudo o que for executado nele será de forma isolada do sistema operacional, podemos por exemplo instalar uma versão de Ruby em dyno-01 e verificar o que acontece no host:

ubuntu@dyno-01:~$ sudo apt-get install wget
ubuntu@dyno-01:~$ wget http://apt.hellobits.com/hellobits.key
ubuntu@dyno-01:~$ sudo apt-key add hellobits.key
ubuntu@dyno-01:~$ echo 'deb http://apt.hellobits.com/ trusty main' |\
> sudo tee /etc/apt/sources.list.d/hellobits.list
ubuntu@dyno-01:~$ sudo apt-get update && sudo apt-get install ruby-2.1
ubuntu@dyno-01:~$ ruby -v
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]
ubuntu@dyno-01:~$ exit

Para sair do console do container execute Ctrl + a e tecle q, agora podemos ver o que aconteceu no host:

ubuntu@heroku:~$ ruby -v
The program 'ruby' can be found in the following packages

O ruby que instalamos existe apenas no nosso container dyno-01, nada foi afetado no nosso host.

Podemos congelar e descongelar containers criados com lxc-freeze e lxc-unfreeze:

ubuntu@heroku:~$ sudo lxc-freeze -n dyno-01
ubuntu@heroku:~$ sudo lxc-info -n dyno-01 | grep State
State:      FROZEN
ubuntu@heroku:~$ sudo lxc-unfreeze -n dyno-01
ubuntu@heroku:~$ sudo lxc-info -n dyno-01 | grep State
State:      RUNNING

É interessante notar a velocidade de execução dessas ações no container.

Outro recurso interessante é o de clonagem, onde podemos fazer uma cópia de um container já existente e atribuir um nome diferente, para isso usamos o lxc-clone:

ubuntu@heroku:~$ sudo lxc-clone -o dyno-01 -n dyno-02
Created container dyno-02 as copy of dyno-01
ubuntu@heroku:~$ sudo lxc-ls
dyno-01  dyno-02
ubuntu@heroku:~$

Como o container dyno-02 é um clone de dyno-01 a instalação do ruby feita anteriormente consta no novo container:

ubuntu@heroku:~$ sudo lxc-console -n dyno-02
ubuntu@dyno-02:~$ ruby -v
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]

Até aqui vimos como operar o LXC, veremos agora um pouco de configuração. Por padrão o LXC organiza os containers em /var/lib/lxc, cada container criado possui um diretório com seus arquivos de configuração:

ubuntu@heroku:~$ sudo ls /var/lib/lxc
dyno-01  dyno-02
ubuntu@heroku:~$ sudo ls /var/lib/lxc/dyno-01
config  fstab  rootfs
ubuntu@heroku:~$

Para o próximo exemplo, vamos configurar o container dyno-01 para que o uso de memória dele seja limitado a 512MB, para isso edite o arquivo config localizado em /var/lib/lxc/dyno-01 e adicione a seguinte instrução ao final do arquivo:

ubuntu@heroku:~$ suco echo "lxc.cgroup.memory.limit_in_bytes = 512M" >> \
> /var/lib/lxc/dyno-01/config

Em seguida reinicie o container em modo de debug para verificarmos o consumo de memória feito por dyno-01:

ubuntu@heroku:~$ sudo lxc-stop -n dyno-01
ubuntu@heroku:~$ sudo lxc-start -d -n dyno-01 -l debug -o dyno01.out
ubuntu@heroku:~$ cat dyno01.out | grep "memory.limit"

lxc-start 1403131667.309 DEBUG  lxc_cgmanager - \
cgroup 'memory.limit_in_bytes' set to '512M'
ubuntu@heroku:~$

Lembra do cgroups ? Pois é, o que fizemos foi dizer para o Kernel limitar o uso de memória feito pelo container dyno-01 a 512MB.

Agora podemos verificar quanta memória o nosso container está consumindo, isso é possível pois o Kernel armazena essas informações em runtime:

$ sudo cat /sys/fs/cgroup/memory/lxc/dyno-01/memory.usage_in_bytes
7249920
$ expr 7249920 / 1024
7080

Ou seja 7080 Kb, dos 512 Mb que configuramos estão em uso.

Conclusão

O que vimos foi a ponta do iceberg de como o Heroku faz para criar e controlar os dynos. O projeto Docker também faz uso do LXC em seu back-end.

A quantidade de opções de configuração são gigantes e merecem um estudo profundo, espero que tenha gostado desta pequena apresentação.

Happy Hacking ;)

Referências

Comentários