Tag: systemd

Entendendo o Systemd

Algum tempo atrás eu tive que fazer meu primeiro “script” de inicialização no systemd, e aproveitei para aprender mais sobre ele. Para os desavisados, o systemd é um novo PID 1, o primeiro processo levantado pelo kernel do sistema operacional. Um substituto para os veneráveis init e seus scripts de inicialização estilo SysV, ou para o mais moderno Upstart da Canonical. Mas ele não para por aí, e ele é um bicho bastante diferente dos seus antecessores.

A polêmica

O systemd faz muita coisa. Além de iniciar e supervisionar serviços, ele monta sistemas de arquivos, executa tarefas periódicas (substituindo o cron), gerencia logins e sessões, entre outras tantas coisas. O que leva a muitas discussões sobre o systemd não estar respeitando a filosofia unix de ferramentas e daemons fazerem uma coisa e fazê-la bem, sobre ele ser inchado (bloated) demais, e ser muito invasivo. Não ajudam muito as atitudes e a postura complicada dos desenvolvedores do systemd que já foi criticada por caras como o próprio Linus e o Theodore Ts’o. O resultado inclui posts apocalípticos, longas réplicas de defesa, sites anti-systemd, e até um fork do Debian motivado por ele. A página do systemd na Wikipedia traz vários links sobre a a polêmica.

E apesar de tudo isso, o systemd já se tornou o padrão no RedHat/CentOS 7, no Ubuntu 15.04, no Debian 8 (jessie) e na maioria das outras distribuições. Então é bom se entender com ele.

Primeiro script (aliás, não é um script)

É um unit configuration file, e ele que substitui o script velho de guerra que antes ficava no /etc/init.d. Services são uma das diferentes units que o systemd pode gerenciar. Um mount é outro tipo de unit, por exemplo.

O systemd procura arquivos de units em vários locais, e em geral o melhor local para criarmos um personalizado é em /etc/systemd/system/. Aqueles instalados por pacotes ficam em /lib/systemd/system/. Um man systemd.unit mostra todos os caminhos utilizados. Eis um exemplo simples do daemon do rsync:

$ cat /lib/systemd/system/rsyncd.service
[Unit]
Description=fast remote file copy program daemon
ConditionPathExists=/etc/rsyncd.conf

[Service]
ExecStart=/usr/bin/rsync --daemon --no-detach

[Install]
WantedBy=multi-user.target

Analisando algumas das diretivas:

  • O ConditionPathExists define que o serviço não será iniciado se o rsync.conf não existir. Nesse caso, o serviço não falha, ele apenas não é iniciado.
  • O ExecStart define o comando que inicia o daemon. Detalhe para a opção –no-detach, que evita que o processo vá para background. Esse tipo de opção é comumente utilizada com o systemd e outros supervisores de processos, pois dessa forma é mais fácil o supervisor saber qual o estado atual do processo.
  • O WantedBy cumpre nesse caso a função do runlevel no SysV init, mas ele é mais flexível. Targets do systemd são agrupamentos de units quaisquer. O multi-user.target especifica o conjunto de units (em sua maioria, serviços) que compõe o boot regular multi-usuário da máquina (como o velho runlevel 2 no Debian ou 3 nos RedHat).

Para manipular os serviços, usamos o utilitário systemctl. Para habilitar o serviço acima no boot, por exemplo, rodamos:

$ sudo systemctl enable rsyncd.service
ln -s '/usr/lib/systemd/system/rsyncd.service' \ 
  '/etc/systemd/system/multi-user.target.wants/rsyncd.service'

Note que ele cria um link simbólico no diretório /etc/systemd/system/multi-user.target.wants (sim, o target é na verdade um diretório de symlinks).

Iniciar e parar o serviço é meio óbvio:

$ sudo systemctl start rsyncd.service
$ sudo systemctl stop rsyncd

Note que especificar o sufixo .service  é opcional, se não houver ambiguidade. Podem existir diferentes tipos de units do systemd com o mesmo nome, por exemplo rsyncd.service e o rsyncd.socket, e dependendo do comando systemctl os dois podem ser válidos, necessitando então especificar o sufixo.  Eu acho recomendável colocar o sufixo sempre para não ter surpresas, e em geral temos o tab completion para nos ajudar.

O comando de status do systemd é bem mais útil do que o do UpStart ou do que geralmente são os status de scripts SysV:

[root@centos7 ~]# systemctl status rsyncd.service 
rsyncd.service - fast remote file copy program daemon
 Loaded: loaded (/usr/lib/systemd/system/rsyncd.service; enabled)
 Active: active (running) since Thu 2016-03-03 13:31:36 UTC; 1s ago
 Main PID: 5331 (rsync)
 CGroup: /system.slice/rsyncd.service
 └─5331 /usr/bin/rsync --daemon --no-detach

Mar 03 13:31:36 centos7.local systemd[1]: Starting fast remote file copy program daemon...
Mar 03 13:31:36 centos7.local systemd[1]: Started fast remote file copy program daemon.
Mar 03 13:31:36 centos7.local rsyncd[5331]: rsyncd version 3.0.9 starting, listening on port 873

Temos aí o estado, o PID, a linha de comando executada, e ao final um trecho recente dos logs no journal. Espera… hein? Journal?

Ahh, o Journal

Mas o que diabos é isso? Bom, lembra que systemd se mete em tudo? O journal é ele se metendo no logging do sistema. Ele coleta mensagens de log do kernel, de chamadas ao syslog, saídas de stdout e stderr, e junta tudo num lugar só. Ele pode ser usado em conjunto com um daemon de syslog, ou substituindo ele completamente (os Fedoras mais novos já usam só o journal, por exemplo).

O comando status mostra apenas as últimas linhas do journal. Para ver ele completo, temos o comando journalctl, com a opção -u para filtrar pela unit:

[root@centos7 ~]# journalctl -u rsyncd.service
-- Logs begin at Thu 2016-03-03 13:07:47 UTC, end at Thu 2016-03-03 13:31:36 UTC. --
Mar 03 13:21:58 centos7.local systemd[1]: Starting fast remote file copy program daemon...
Mar 03 13:21:58 centos7.local systemd[1]: Started fast remote file copy program daemon.
Mar 03 13:21:58 centos7.local rsyncd[5316]: rsyncd version 3.0.9 starting, listening on port 873
Mar 03 13:22:04 centos7.local systemd[1]: Stopping fast remote file copy program daemon...
Mar 03 13:22:04 centos7.local systemd[1]: rsyncd.service: main process exited, code=exited, status=20/n/a
Mar 03 13:22:04 centos7.local systemd[1]: Stopped fast remote file copy program daemon.
Mar 03 13:22:04 centos7.local systemd[1]: Unit rsyncd.service entered failed state.
Mar 03 13:31:36 centos7.local systemd[1]: Starting fast remote file copy program daemon...
Mar 03 13:31:36 centos7.local systemd[1]: Started fast remote file copy program daemon.
Mar 03 13:31:36 centos7.local rsyncd[5331]: rsyncd version 3.0.9 starting, listening on port 873

O journalctl tem zilhões de opções, permitindo vários tipos de filtros por data, severidade, serviço, etc. Por exemplo:

  • -b mostra todas mensagens do último boot.
  • -f mostra os logs continuamente, como se fosse um tail -f nos arquivos de log.
  • -p <sev> filtra por severidade. -p err, por exemplo, mostra os logs de severidade error ou maior.
  • -u <unitname> filtra pelo nome de uma unit/serviço, como visto acima.
  • –since e –until definem limites de data. Valores como yesterday, 11min ago, ou 2012-11-23 11:12:13, são todos válidos.

Sugiro este post para uma introdução mais completa ao journal.

Elaborando mais um pouco

Vamos a mais alguns exemplos de unit config files. Digamos que eu queira executar um comando arbitrário durante o boot, que não é um serviço, vai executar uma vez e só. Um exemplo disso é desabilitar o Transparent Huge Pages (THP), coisa que caras como o Redis e o MongoDB recomendam fazer:

[Unit]
Description=Disable Transparent Huge Pages

[Service]
Type=oneshot
ExecStart=/bin/sh -c "/usr/bin/echo never | tee /sys/kernel/mm/transparent_hugepage/enabled"
ExecStart=/bin/sh -c "/usr/bin/echo never | tee /sys/kernel/mm/transparent_hugepage/defrag"

[Install]
WantedBy=multi-user.target

Observe o type=oneshot (executa o comando e acabou), e que eu estou passando vários comandos usando várias diretivas ExecStart. Prático.

Agora vejamos o arquivo para um serviço redis, que eu quero que seja iniciado só depois que o THP tenha sido desabilitado. Claro que o pacote do redis possivelmente já virá com um script systemd, mas digamos que seja uma instância adicional do redis. E digamos ainda que queremos rodar essa instância como um usuário específico, que é fornecido via NIS. Segue:

[Unit]
Description=Redis persistent key-value database
# user redisabc needs NIS (ypbind.service)
After=network.target ypbind.service disable-transparent-hugepages.service

[Service]
ExecStart=/usr/bin/redis-server /etc/redis-abc.conf --daemonize no
User=redisabc
Group=redisabc

[Install]
WantedBy=multi-user.target
  • O After define algumas dependências para o serviço redis: a rede, o daemon do NIS (ypbind) e o nosso “serviço” anterior que desabilita o THP.
  • Note o –daemonize no na linha de execução, cumprindo o mesmo papel do –no-detach no exemplo do rsync.
  • User e Group … bem, já deu pra entender.

 

Indo (muito) além

Já deu para ver o básico, mas foi só uma arranhada no monstro que é o systemd. Deixo aqui mais algumas referências finais, com destaque para a série de posts “The systemd for Administrators”:

A página principal do systemd tem vários links interessantes (e de lá que eu tirei a lista acima).

Outro must read: Why systemd?

E para fechar, mais uma polêmica envolvendo o moço, dessa vez com o menina-dos-olhos-do-mundo-cloud, o Docker: System vs Docker.

Ok, agora já temos leitura para uma semana, praticamente 😉