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.