quarta-feira, 5 de junho de 2013

Auto ativação de contéiner com systemd

No post anterior, eu criei um contêiner com Arch Linux leve com pacstrap e executando com systemd-nspawn. Neste post, vou utilizar a ativação automática de sockets do systemd para fazer com que o contêiner seja carregado automaticamente quando uma determinada porta for acessada.

Um contêiner leve com systemd-nspawn não possui todo o isolamento de um contêiner com lxc (Linux Contéiners). O contêiner com systemd-nspawn possui isolamento de sistema de arquivos (equivalente ao chroot), isolamento de memória e isolamento de processos (processos de um contêiner não veem processos do host ou de outros contêiners). Porém, os contêiners compartilham a rede do hospedeiro. Portanto, o endereço IP e o espaço de portas é compartilhado entre todos.

A ativação automática via sockets do systemd funciona da seguinte forma:

  • Configuramos o systemd para ficar escutando uma determinada porta, através de um arquivo de unidade especial terminado em .socket.
  • Quando uma conexão é iniciada por um cliente nesta porta, o systemd ativa a unidade homônima com a extensão .service e repassa o socket para o serviço. Normalmente, esse serviço é configurado de tal forma que ele seja concluído ao fim da conexão com o cliente, de forma que não fique um serviço rodando sempre em segundo plano. Porém, isto não é obrigatório.
Por exemplo, a unidade sshd.socket pode ser ativada, de forma que o systemd vai ficar aguardando uma conexão na porta 22. Quando um cliente tentar estabelecer uma conexão, o systemd vai iniciar um sshd.service e repassar o socket a ele. No caso do sshd@.service, ele está configurado para não rodar em modo daemon e sim em modo de conexão única. Ao término da sessão ssh, a instância do servidor sshd para aquela conexão será terminada junto com a conexão. Portanto, o systemd tomou o lugar do serviço sshd que fica aguardando conexões e criando forks de si mesmo quando uma conexão é estabelecida. Mais uma funcionalidade interessante do systemd, que pode facilitar a criação de daemons a partir de serviços que preocupem-se apenas com um cliente por vez.

Este recurso também pode ser usado para carregar um contêiner com systemd-nspawn. Vamos colocar o seguinte cenário: eu tenho um contêiner criado com o objetivo de isolar um servidor JBoss. Enquanto eu não precisar do servidor JBoss, eu gostaria também de não precisar carregar o contêiner. E quando eu quisesse acessar algum serviço do JBoss, o systemd deveria carregar o contêiner, carregar o JBoss interno e repassar o socket do host ao contêiner. Este, por sua vez, passaria o socket para o serviço JBoss que foi inciado.

Vamos à implementação:

No host, é necessário criar duas unidades:

  • /etc/systemd/system/meuconteiner.socket
  • É o que define a porta em que o systemd irá escutar para ativar o contêiner.
    [Unit]
    Description=Socket for my Container
    
    [Socket]
    ListenStream=2223
    
    É possível escutar mais de uma porta no host, de forma que o systemd vai repassar os sockets correspondentes para o contêiner. Daí, o contêiner poderá repassá-los, por sua vez, aos serviços corretos internamente.
  • /etc/systemd/system/meuconteiner.service
  • É a unidade que define como o contêiner será inicializado.
    [Unit]
    Description=Inicializador do conteiner
    
    [Service]
    ExecStart=/usr/bin/systemd-nspawn -jbD /archimage --bind=/var/cache/pacman/pkg
    KillMode=process
    

O contêiner será inicializado automaticamente quando uma conexão for estabelecida na porta 2223 (utilizarei esta porta para evitar conflitar com a porta 22, em que pode estar rodando o sshd do próprio host).

Agora vamos configurar o contêiner. Primeiro, precisamos acessá-lo manualmente com o comando abaixo:

sudo systemd-nspawn -jbD archimage --bind=/var/cache/pacman/pkg

Os parãmetros foram detalhados no post anterior. Eu loguei com o usuário root, para fazer as configurações necessárias. Irei apenas criar um serviço sshd para exemplificar.

Novamente, criaremos duas unidades do systemd: uma para definir o socket a escutar e a outra para executar o serviço sshd.

  • /etc/systemd/system/sshd.socket
  • Define a porta do contêiner que o systemd interno deverá escutar.
    [Unit]
    Description=SSH Socket for Per-Connection Servers
    
    [Socket]
    ListenStream=2223
    Accept=yes
    
  • /etc/systemd/system/sshd@.service
  • Define como o serviço deverá ser iniciado.
    [Unit]
    Description=SSH Per-Connection Server for %I
    
    [Service]
    ExecStart=-/usr/bin/sshd -i
    StandardInput=socket
    

Pronto. Ao executarmos

ssh -p 2223 localhost

o systemd do host vai iniciar o contêiner, passar o socket para o systemd interno e este, por sua vez, iniciará o serviço sshd, que receberá o socket e fará a conexão.

Eu reparei que mesmo depois de fechar a conexão ssh, o contêiner não foi terminado. Precisarei investigar mais este detalhe, pois gostaria que ele fosse terminado também. Versões futuras do systemd vão resolver este problema, permitindo que contêiners ociosos sejam terminados, da mesma forma que um laptop pode ser hibernado pelo systemd quando ele está em um estado ocioso.

No próximo post, pretendo configurar uma instância do JBoss AS 7 no contêiner e pretendo associar vários sockets externos aos serviços fornecidos internamente pelo JBoss através do contêiner.

quarta-feira, 29 de maio de 2013

ArchLinux num ambiente isolado com systemd-nspawn

Introdução

Existem várias maneiras de se executar um ambiente isolado. Podemos utilizar um simples chroot ou virtualização de hardware. Porém, o chroot apenas isola o sistema de arquivos, sendo possível explorar alguma vulnerabilidade para escapar do chroot. A virtualização isola de forma bastante completa, porém costuma ter uma pequena perda de performance, além de comprometer uma quantidade fixa de memória e espaço em disco.

No Linux, tem crescido uma alternativa Linux Containers. Eles utilizam recursos do kernel chamados Namespaces. Existem vários tipos de namespaces: de sistemas de arquivos, de rede, de processos e de usuários. Eles servem para isolar os recursos entre os tipos de namespaces. Algumas vantagens são:

  • A performance não é afetada, pois não há virtualização do hardware.
  • A memória e o espaço de disco são compartilhados entre os contêiners, então não há desperdício de recursos alocados a máquinas virtuais, mas não utilizados.

Montando o ambiente


O meu ambiente de testes é uma instalação do ArchLinux, atualizada. O kernel que estou usando é o 3.9.4, oficial da distribuição. Vamos fazer uma nova instalação do Arch, embora poderíamos utilizar o Fedora ou Debian com facilidade. Porém, como minha afinidade é maior com Arch, é o que utilizarei. Porém, isso não afeta o procedimento geral.

Os comandos a seguir devem ser executados com permissão de super usuário, obviamente.

É necessário instalar o pacote com o script de instalação do Arch.

pacman -S arch-install-scripts

Escolher um diretório que vai conter a raiz do novo sistema e criar um diretório para
conter a instalação

cd <diretorio>
mkdir archimage

Executar pacstrap para instalar um Arch básico

pacstrap -cdG archimage base

Os parâmetros usados significam:

-c
utiliza o cache de pacotes do host. Isto provavelmente vai economizar muito tempo de download, se você não apagar seu cache.
-d
permite instalar em um diretório que não é um ponto de montagem. Em meus testes, eu não criei uma partição separada para /var, /tmp, etc, pois o objetivo é ter um sistema bem contido. Eventualmente, pode-se utilizar pontos de montagem vinculados para apontar para os diretórios /var, /tmp, etc do hospedeiro.
-G
não copia o keyring do host para o guest. O guest deverá ter um keyring próprio, com uma chave privada própria.

Executar o systemd-nspawn para entrar no novo sistema

systemd-nspawn -jbD archimage --bind=/var/cache/pacman/pkg

-j
faz com que o journal do hóspede seja visível a partir do hospedeiro. Isto é interessante pois pode ser que você perca o acesso ao hóspede e queira ver o que aconteceu ou está acontecendo com ele a partir do ambiente externo.
-b
executa uma sequência de boot, chamando o /bin/init como processo pid 1. Como o systemd-nspawn utiliza o recurso de Process Namespace do kernel, ele pode reiniciar a contagem de pid no sistema hóspede sem que haja conflitos com o sistema hospedeiro.
-D
utiliza o diretório especificado como raiz do sistema. Funciona de forma muito similar ao chroot, só que utiliza o sistema de Filesystem Namespaces do kernel.
--bind=
monta um diretório ou arquivo do hospedeiro dentro do hóspede. No exemplo, o cache do pacman foi montado para evitar o download desnecessário de pacotes pelo hóspede. Pode-se utilizar esta opção mais de uma vez. Também existe a opção --bind-ro para montar como somente leitura.

Neste ponto, o ambiente isolado será inicializado e poderá ser configurado como se fosse um sistema Arch convencional que acabou de ser instalado. Isto pode ser feito tomando como referência o Guia do Iniciante. Não é necessário gerar um fstab, pois o ambiente isolado roda num subdiretório do sistema hospedeiro e, por isto, a estrutura deve ser configurada antes de executar o systemd-nspawn.

Espero que tenha sido interessante. Comentários são muito bem vindos. Obrigado!

segunda-feira, 27 de maio de 2013

Começando

Este é o primeiro post deste blog. Nele eu pretendo escrever coisas que vou fazendo, principalmente para minha própria referência futura. Se servir para outras pessoas, melhor ainda. Portanto, os assuntos variarão entre Ciência da Computação (programação, principalmente), administração de sistemas (não é meu foco principal, mas é interessante experimentar coisas novas) e música.

Estou pensando em iniciar com uma série de posts que falam sobre conteiners. Provavelmente, serão 2 ou 3 artigos. O primeiro deve ser sobre o systemd-nspawn. O segundo deve ser sobre ativação automática de conteiners com o systemd. O terceiro, se houver, será sobre o uso do projeto Linux Containers, ou lxc, como é conhecido. Só farei este terceiro se eu tiver mais tempo, pois ele envolve mais preparação.

Enfim, vamos ver no que dá.