Em nossa primeira parte, falamos sobre os sistemas de init BSD-like SystemV. Nesta segunda parte, vamos falar sobre um sistema mais moderno: upstart. Se você não leu a pequena introdução sobre os sistemas init na primeira parte, recomendo dar uma olhada antes de começar: vale a pena!
Como vimos anteriormente, apesar do SystemV ser simples, ele tinha algumas limitações que não faziam mais sentido em uma era de computadores mais potentes e modernos (afinal, esse SystemV veio da década de 1980 e 1990!). É por isso que resolveram criar alternativas mais poderosas (que acabaram ficando um pouco mais complexas, mas nada que a gente não dê conta né?). Como dito no tutorial anterior, alguns das principais vantagens desses novos sistemas init:
- Poder executar serviços paralelamente;
- Lidar melhor com as dependências entre um serviço e outro (exemplo: precisa de rede para iniciar um servidor web);
- Adicionar ou remover dispositivos (pendrives, discos, entre outros) e recarregar certos serviços quando isso ocorrer;
- Não depender apenas de um arquivo com o PID para saber se o serviço está rodando bem…
Mas não é só isso, esses sistemas tem muitos e muitos recursos e e detalhes novos. Neste tutorial vamos ver o necessário para conhecê-los e nos familiarizar com as diferenças de antes para agora.
Upstart
O Upstart foi concedido para resolver as limitações do SystemV init para a distribuição Ubuntu e logo foi adotado por outras distribuições Linux. Seu princípio é ser baseado em eventos: o Upstart cria um ou vários eventos e os serviços podem ser associados à estes eventos. Ele é quem define o que fazer quando um evento começa, muda ou termina (por exemplo: iniciando e parando serviços).
Se vários serviços estão dentro de um evento e ele ocorre, o Upstart pode por exemplo, iniciar todos estes serviços paralelamente. Se um evento tem que ocorrer apenas depois de outro, quando o primeiro evento terminar, ele começa a rodar este outro dependente. Mas vamos ver isso melhor.
Como antes, quando o kernel é iniciado, ele chama o daemon do Upstart, o /sbin/init. Este daemon, quando iniciado, carrega todos os arquivos de configuração do diretório /etc/init. São todos os arquivos que tem extensão .conf. Cada um desses arquivos define um Job. Este Job significa uma tarefa ou serviço. Vale notar também que qualquer alteração nos arquivos vai ser imediatamente lida pelo Upstart, sem precisar recarregar ou algo parecido.
Exemplo simples de um arquivo de configuração de job:
- /etc/init/hostname.conf
# hostname - set system hostname # # This task is run on startup to set the system hostname from /etc/hostname, # falling back to "localhost" if that file is not readable or is empty and # no hostname has yet been set. description "set system hostname" start on startup task exec hostname -b -F /etc/hostname
Esta configuração de Job serve para, na inicialização da máquina, o sistema definir o seu nome lendo o arquivo /etc/hostname. O que podemos ver de cara no exemplo:
- Linha 7: uma descrição simples do que faz o job
- Linha 9: qual estado executar e quando executar (neste caso, iniciar na inicialização :)
- Linha 11: a configuração é uma tarefa: algo que vai ser executado e depois termina sozinho (exemplo: um shell script)
- Linha 12: O comando que será executado por esta tarefa
Uma tarefa bem simples, não? Todos os scripts que tem a linha start on startup são os primeiros a serem executados. O que acontece é que depois que o init é carregado e lê todas as configurações, ele emite o evento startup. É deste evento que começa toda a cadeia de outros eventos (por exemplo, a tarefa hostname que mostramos).
Vamos pegar o exemplo de um Ubuntu 12.10 e ver funciona essa cadeia de comandos. Primeiro descobrimos que jobs serão executados no evento startup:
# grep -l "start on startup" /etc/init/* /etc/init/hostname.conf /etc/init/mountall.conf
O hostname.conf a gente já tinha visto. Mas também temos o mountall.conf! O mountall vai montar todo o filesystem em seus lugares corretos. Dentro do arquivo, temos as seguintes linhas:
# mountall - Mount filesystems on boot # # This helper mounts filesystems in the correct order as the devices # and mountpoints become available. description "Mount filesystems on boot" start on startup stop on starting rcS expect daemon task emits virtual-filesystems emits local-filesystems emits remote-filesystems emits all-swaps emits filesystem emits mounting emits mounted # temporary, until we have progress indication # and output capture (next week :p) console output script [...] end script post-stop script [...] end script
O que temos de novo por aqui que podemos falar?
- Linha 9: o serviço deve parar quando estiver iniciando o evento rcS;
- Linha 11: o upstart vai esperar um daemon ser executado, assim ele saberá qual o seu PID (que pode ser variável devido à execuções fork);
- Linhas 14-20: Opa! As linhas com emit emitem eventos para todo o Upstart.
Então quando o evento mountall for chamado, ele chama outros eventos. Nestes outros eventos, estão outros serviços e tarefas que serão executados. E é assim que o Upstart trata as dependências de serviços e a ordenação dos mesmos: com eventos e estados de jobs.
Resumo da inicialização
Podemos resumir que a inicialização acontece então nesta ordem:
- O upstart é iniciado pelo kernel, executando o /sbin/init;
- O upstart lê todos os arquivos .conf do diretório /etc/init e define todos os jobs;
- O upstart emite o evento startup;
- Os jobs hostname e mountall são executados por causa do evento startup;
- O job mountall emite os eventos: virtual-filesystems, local-filesystems, e por aí vai;
- Devido ao evento virtual-filesystems…
- O job procps é iniciado (configura os sysctls do arquivo /etc/sysctl.conf);
- O job udev é iniciado (daemon udev que gerencia os eventos do kernel);
- Devido ao evento local-filesystems…
- O job dbus é iniciado (daemon de mensagens d-bus);
- O job networking é iniciado (interfaces de rede) apenas se os jobs udev-container ou container estiverem parados;
- O job networking emite os eventos: static-network-up e net-device-up;
- Devido ao evento static-network-up…
- O job procps pode ser iniciado (se já não foi antes);
- O job rc-sysinit é iniciado (serviços em runlevels, assim como no SystemV)…
- Emite o evento runlevel, com o runlevel padrão 2 (em sistemas Debian);
- Todos os jobs que estão inscritos no evento runlevel [2] são iniciados;
- O job rc é iniciado (compatibilidade com o SystemV;
- Devido ao evento net-device-up…
- O job mountall-net é iniciado (monta sistemas de arquivos remotos);
- O job upstart-socket-bridge é iniciado (recebe eventos em socket e retransmite pro upstart);
Cara! Como isso ficou complicado! Como eu havia dito antes, a gente ganha muito poder mas aumenta a complexidade :) Tente imaginar uma árvore, com o tronco sendo o Upstart, os vários galhos sendo os eventos e as folhas são os jobs. Se um “galho” é chamado, todas as folhas desse galho podem ser executadas. Há um sistema de dependências muito grande e complexo, mas pra começar pode imaginar desse jeito que ajuda ;)
Runlevels
Como deu pra perceber no resumo da inicialização, os jobs mais simples e que não tem muitas dependências podem obedecer o sistema de runlevels, criado pelo rc-sysinit. Veja o exemplo do job atd:
- /etc/init/atd.conf
# atd - deferred execution scheduler # # at is a standard UNIX program that runs user-specified programs at # scheduled deferred times description "deferred execution scheduler" start on runlevel [2345] stop on runlevel [!2345] expect fork respawn exec atd
- Ele vai ser iniciado quando o evento runlevel for chamado com o nível 2, 3, 4 ou 5.
- Ele vai ser parado quando o evento runlevel for chamado com o nível que não seja 2, 3, 4 ou 5.
Outras coisas legais de se falar sobre os runlevels:
- Se você mudar a variável DEFAULT_RUNLEVEL do arquivo rc-sysinit.conf, você muda o runlevel inicial do sistema. É a mesma coisa que alterar o initdefault do /etc/inittab no SystemV. Inclusive, o Upstart lê um possível /etc/inittab para procurar por essa linha também;
- O job rc-sysinit também procura na linha do kernel (/proc/cmdline) por níveis de execução (s para single, 2 para runlevel 2, e por aí vai) e o usa para o runlevel inicial;
- Um dos jobs que é chamado em todo runlevel é o rc, que é o modo de compatibilidade com o SystemV;
- O comando telinit emite um evento com o runlevel desejado, exemplo: telinit 3 emite o evento runlevel [3].
Compatibilidade com o SystemV init
O modo com que o Upstart funciona faz dele bem flexível e abstrato. Por isso, dá para você pensar e montar esquemas de gerência de serviços diversos. Um dos principais esquemas de serviços é justamente a compatibilidade com o SystemV init, que vimos na primeira parte do turorial. Imagine, se não colocassem uma compatibilidade, muita coisa poderia parar de funcionar de uma versão de distribuição para outra. Acho que ninguém ia querer isso né?
Uma das coisas que o job rc-sysinit chama através do evento runlevel é o job rc. Este job, que é o responsável pela compatibilidade SystemV, executa o script /etc/init.d/rc $RUNLEVEL, onde $RUNLEVEL é o runlevel atual. Funciona igualzinho ao SystemV. Compare com a parte 1 do tutorial.
Em outras palavras, em sistemas novos com Upstart você não precisa se preocupar se existirem arquivos de start/stop dentro do /etc/init.d. Inclusive o comando service também funcionará perfeitamente. Mas se você estiver fazendo um serviço novo para as distribuições com Upstart, é recomendável usar o Upstart.
Manipulando os serviços
Até agora aprendemos como o Upstart funciona e como definir os seus serviços. Mas e para gerenciá-los?
O comando do momento aqui é o initctl. Com ele você consegue gerenciar os serviços já existentes. Vamos aos exemplos:
Listando todos os serviços disponíveis:
initctl list
(E não apenas lista, como mostra o status de cada um)
Iniciando um serviço:
initctl start job # ou o antigo service <job> start
(O script verifica tanto o estilo SystemV quanto os jobs do Upstart no /etc/init)
Parando um serviço:
initctl stop job # ou o antigo service <job> stop
Reiniciando um serviço:
initctl restart job # ou o antigo service <job> restart
Mostrando o status de um serviço:
initctl status job # ou o antigo service <job> status
Emitindo eventos
Como tudo no Upstart são eventos, você pode querer enviar eventos para o sistema. Estes eventos que você enviar manualmente serão lidos por todos os jobs, e aqueles que estão esperando uma ação de start ou stop deste evento serão executados.
Para enviar um evento pra todo o Upstart:
initctl emit eusouumeventolegal
Um exemplo legal seria criar um evento chamado “lamp” (Linux + Apache + MySQL + PHP). Para os jobs do apache e mysql, você colocaria um start on lamp e eles serão executados quando você enviar um:
initctl emit lamp
Os estados dos jobs / serviços
Ao fazer um initctl status job, podemos ver o estado do serviço, ou seja, em que fase de execução está o job. Um job pode ter duas ações: start e stop. Cada uma dessas ações também tem seus estados e significam em que ponto do start ou do stop o job está. Veja uma lista desses estados:
- waiting: Estado inicial (esperando algo acontecer);
- starting: Está para ser iniciado;
- pre-start: Executando a seção pre-start (preparação para iniciar);
- spawned: Executando a seção exec ou script
- post-start: Executando a seção post-start (depois que iniciou)
- running: O serviço está rodando
- pre-stop: Executando a seção pre-stop (preparação para parar)
- stopping: O serviço está parando
- killed: O serviço já parou, agora o job está quase para parar
- post-stop: Executando a seção post-stop (depois que parou)
Alguns exemplos:
# initctl status tty1 tty1 start/running, process 1265
O job tty1, que fornece a console 1 (CTRL+ALT+F1) está na fase de start (iniciar) e está em execução com o PID 1265.
# initctl status mountall mountall stop/waiting
O job mountall está na fase de stop (parou) e aguardando algo acontecer/mudar. Muitos jobs estarão neste estado, e isso pode significar que o job já foi executado uma vez (como é o caso desse exemplo).
Ligando e desligando serviços na inicialização
Nos sistemas SystemV, desligar ou ligar serviços na inicialização significava mexer nos links simbólicos do diretório /etc/rcX.d (onde o X é o runlevel). No Upstart, as coisas mudam um pouco. Não há mais shell scripts de execução, apenas configurações de job do /etc/init. O que fazer para impedir que eles sejam executados?
Primeiro identifique se o job é do tipo Upstart ou está utilizando o sistema legado SystemV.
Caso o serviço já tiver sido convertido para Upstart, o comando initctl status vai ficar assim:
# initctl status rsyslog rsyslog start/running, process 579
Se for do tipo SystemV, vai ficar assim:
# initctl status nscd initctl: Unknown job: nscd
(Não existe o job no Upstart (unknown job) mas existe o script /etc/init.d/nscd)
Se o script for do tipo SystemV, utilize os mesmos procedimentos da primeira parte deste tutorial.
Se for um job Upstart, você tem duas alternativas…
Desabilitando do boot
- Desde o Upstart 1.3, crie um arquivo de override para o job da seguinte forma:
# echo "manual" >> /etc/init/job.override
O parâmetro manual vai fazer com que o serviço só seja iniciado manualmente. O arquivo override é um jeito de complementar a configuração do job sem ter que modificar o arquivo original;
- Ou se a versão for mais antiga, comente (colocar #) todas as linhas que começam com start na configuração do job (/etc/init/job.conf).
Habilitando no boot
Partindo do suposto de que o serviço já está desabilitado:
- Desde o Upstart 1.3, apague o arquivo override:
# rm -f /etc/init/job.override
Ou se o arquivo override não existir, verifique e retire a linha manual dentro da configuração do job;
- Ou se a versão for mais antiga, descomente (tirar #) todas as linhas que começam com start na configuração do job (/etc/init/job.conf).
Um serviço de exemplo
Vamos criar um serviço! Adivinha como ele vai se chamar?
- /etc/init/linuxnaveia.conf
# linuxnaveia - um exemplo de serviço devin # # Este serviço não faz nada demais, é só um exemplo para demonstrar # como o Upstart funciona ;-) # description "um exemplo de serviço devin" # quero iniciar nos runlevels mais legais start on runlevel [2345] # quero parar só no halt e reboot stop on runlevel [06] # antes de iniciar, preciso criar um diretorio pre-start script /bin/mkdir -p /tmp/linuxnaveia end script # este é o serviço que lê um log e coloca em um arquivo (inutil) :P # o script vai ser interpretado por uma shell script exec tail -f /var/log/syslog > /tmp/linuxnaveia/syslog end script # não preciso mais do meu diretório post-stop script /bin/rm -rf /tmp/linuxnaveia end script
Salvei. Pronto! Meu serviço já está instalado. Vamos ver o estado dele:
# initctl status linuxnaveia linuxnaveia stop/waiting
Agora pra não ter que reiniciar, vamos iniciar o serviço na mão:
# initctl start linuxnaveia linuxnaveia start/running, process 4803
Vamos ver o que meu serviço está criando:
# ls -lha /tmp/linuxnaveia/ total 12K drwxr-xr-x 2 root root 4.0K Apr 10 06:06 . drwxrwxrwt 3 root root 4.0K Apr 10 06:06 .. -rw-r--r-- 1 root root 1.3K Apr 10 06:06 syslog # ps aux | grep 4803 root 4803 0.0 0.0 4344 356 ? Ss 06:06 0:00 tail -f /var/log/syslog
Ele está rodando no PID 4803 (como o Upstart falou), criou um diretório /tmp/linuxnaveia e o tail está alimentando o arquivo syslog… Ótimo. Mas esse serviço é tão inútil que eu resolvo então pará-lo:
# initctl stop linuxnaveia linuxnaveia stop/waiting # ls -lha /tmp/linuxnaveia ls: cannot access /tmp/linuxnaveia: No such file or directory # ps aux | grep 4803 (nada)
Ele parou o tail e removeu o diretório. Tudo como eu disse que ele tinha que fazer. Bom menino!
Percebeu como foi mais fácil fazer o serviço, do que seria no SystemV?
- Não precisei que meu serviço gravasse o PID em algum lugar ou algo assim para saber dele ou pará-lo;
- Só me preocupei mesmo com o que o serviço faz, o resto das funcionalidades (start, stop, status, runlevel, etc) o Upstart cuidou pra mim;
- Não precisei fazer muito shell-script.
Até que é fácil, não acham? ;-)
Referências
- Página oficial do Upstart
- Upstart cookbook
- Ask Ubuntu: What’s the recommended way to enable / disable services?