sexta-feira, 3 de abril de 2015

Daemon do Squid em inits modernos

O Squid pode operar em dois modos no que diz respeito à forma do daemon: com a opção -N ou sem ela.

Com -N, não é feito o processo de daemonização. É o preferido em inits modernos. Deve ser usado sempre que possível com o Upstart ou systemd. Contudo, o modo de múltiplos processos do Squid não funciona com -N até agora (3.5) e as distribuições, portanto, não podem usar por padrão.

Sem -N, ativa a daemonização. No systemd, isso significa um serviço Type=forking. À primeira vista, não haveria problema, pois existe compatibilidade. Não fosse o comportamento totalmente bugado do Squid ao funcionar dessa maneira.

PID1
 \_ MASTER/PARENT
     \_ WORKER <--- PROCESSO PAI DE FATO (ARQUIVO PID)
         \_ FILHO 1
         \_ FILHO 2
            ...

Os problemas insolúveis são dois:

- O processo que deve ser sinalizado (worker) não é filho direto do init. Isso impede o systemd monitorá-lo eficazmente via SIGCHLD. Ele lhe avisa com "squid.service: Supervising process XXX which is not our child. We'll most likely not notice when it exits.". A útil opção Restart= não funcionará.
- Ao receber SIGTERM, o processo worker retorna imediatamente.

O systemd finaliza daemons assim:

- Executa, caso exista, o comando configurado em ExecStop=.
- Envia, caso ainda existam processos no cgroup daquele serviço após ExecStop= finalizar, SIGTERM (ou o sinal configurado em KillSignal=) para todos (KillMode=control-group — padrão), apenas ao principal (KillMode=process) ou nenhum (KillMode=none). No caso de Type=forking, o processo principal é o presente no arquivo pid[*].
- Depois de um temporizador (90s, configurável), caso ainda existam processos, envia SIGKILL (obedecendo à configuração acima). A partir da versão 209, temos KillMode=mixed, que envia SIGTERM (ou o sinal configurado em KillSignal=) apenas ao processo principal e SIGKILL para todos após o temporizador.

squid -k shutdown acha o PID do processo worker através do arquivo pid e envia SIGTERM ao mesmo. -k rotate faz o mesmo, mas usa SIGUSR1. -k reconfigure, por fim, SIGHUP. Poderíamos inferir que squid -k shutdown é desnecessário, já que podemos usar o mecanismo embutido de entrega de sinais do systemd (ajustando KillMode para process). Infelizmente, por causa do primeiro problema, ele não tem como saber quando o processo finalizou e mata imediatamente tudo que estiver no cgroup.

No arquivo de configuração do Squid existe a opção shutdown_lifetime (30 segundos se não for especificada), que faz o daemon esperar por determinado tempo até todos os clientes finalizarem suas conexões. Por causa do explicado acima, na prática, nunca é obedecida. O Squid nunca é finalizado corretamente.

As duas soluções possíveis, que demandam modificações no código do Squid:

- Fazer o modo de múltiplos processos funcionar sem daemonização (squid -N). Seria o ideal.
- Fazer o processo worker retornar ao ser sinalizado com SIGTERM apenas quando finalizar por completo. Continuaria sendo uma configuração capenga (o processo a ser monitorado não seria um filho do init), porém pelo menos funcionaria com ExecStop= presente.

No tempo das cavernas do SysV, era usada alguma gambiarra como isto:

[...]

$SQUID -k shutdown -f $SQUID_CONF &
rm -f /var/lock/subsys/$SQUID
timeout=0
while : ; do
    [ -f /var/run/squid.pid ] || break
    if [ $timeout -ge $SQUID_SHUTDOWN_TIMEOUT ]; then
        echo
        return 1
    fi
    sleep 2 && echo -n "."
    timeout=$((timeout+2))
done
echo_success
echo

[...]

Ou seja, era colocado um valor alto o suficiente em $SQUID_SHUTDOWN_TIMEOUT (Fedora e RHEL antigos usavam 60 segundos), o script enviava SIGTERM (via squid -k shutdown) e ficava esperando e torcendo para que tudo desse certo (tipo "seja o que Deus quiser"). Como é que pode um sistema profissional usar uma coisa dessas?

Haja vista o exposto aqui, recomendo que, se você não precisar do modo de múltiplos processos, substitua o squid.service localmente na sua instalação por algo similar ao distribuído a partir da versão 3.5.0.4.

[*] Se não for especificado um arquivo pid com Type=forking, o systemd tenta adivinhar o processo principal (que no Squid fica sendo o master). Entretanto, o processo master não é o principal, que deve ser sinalizado e monitorado. No Fedora, por causa disso, propositadamente é suprimido PIDFile=. Uma bagunça!

[Atualização - 04/04/2015] Adicionar KillMode=mixed.
[Atualização - 15/06/2015] Esclarecer que o sinal pode ser modificado com KillSignal=.

Nenhum comentário:

Postar um comentário