03 - Dependências (Requires, Wants, After, Before)
video: https://youtu.be/... no frontmatter da nota.A arquitetura de orquestração do systemd fundamenta-se na teoria dos grafos. Diferente dos sistemas de inicialização iterativos tradicionais (como o SysVinit), que executam scripts em uma sequência linear predeterminada, o systemd avalia o estado desejado do sistema operativo e constrói um Grafo Acíclico Dirigido (DAG) na memória. Neste grafo, os vértices (nós) são as unidades (Unit Files), e as arestas (links) são as relações de dependência.
A inovação mais profunda — e a fonte da maior confusão na engenharia de unidades — é a Ortogonalidade Arquitetural entre Requisito e Ordenamento. O systemd separa estritamente a causalidade (o que deve existir na memória) da temporalidade (em que momento o processo deve ser invocado). Esta separação permite o paralelismo massivo: se duas unidades se requerem mutuamente mas não possuem ordem temporal, o PID 1 alocará processos para ambas no mesmo milissegundo.
1. O Eixo de Requisito (Causalidade e Existência)
As diretivas de requisito instruem o mecanismo de transação do systemd sobre quais unidades adicionais devem ser injetadas na fila de trabalhos (job queue) quando uma unidade alvo é solicitada. Nenhuma destas diretivas impõe ordem de execução temporal.
1.1. Wants= (Acoplamento Fraco / Weak Dependency)
É a diretiva de dependência padrão e recomendada pelos desenvolvedores do kernel e do systemd. Quando a Unidade A possui Wants=B, a solicitação de ativação de A forçará o systemd a adicionar um trabalho de ativação para B na transação atual.
- Tolerância a Falhas: A semântica do
Wants=é tolerante. Se B falhar durante a inicialização (retornar um código de saída diferente de zero ou sofrer um timeout), osystemdregistrará o erro de B no journal, mas o processo de inicialização de A continuará incólume. - Propósito: Projetado para maximizar a resiliência do sistema. Se o serviço web deseja o serviço de log centralizado, a falha do log não deve causar a indisponibilidade da aplicação web.
1.2. Requires= (Acoplamento Forte / Hard Dependency)
Estabelece uma dependência obrigatória de co-ativação. Se A possui Requires=B, ambas são enfileiradas.
- Mecânica de Falha Transacional: Se B falhar durante a transação de inicialização (antes de atingir o estado
active), o trabalho de inicialização de A é imediatamente cancelado pela máquina de estados. A nunca chega a executar o seuExecStart=. - Limitação Crítica (O Mito da Morte Síncrona): Um erro comum de arquitetura é assumir que
Requires=mantém os processos amarrados perpetuamente. Se A e B iniciarem com sucesso e estiverem no estadoactive, e o processo B sofrer um crash (Segmentation Fault) uma hora depois, a Unidade A continuará rodando normalmente. ORequires=só é restritivo durante o vetor de transição de estado.
1.3. Requisite= (Pré-requisito Estrito)
Uma diretiva severa e de uso altamente específico. Se A possui Requisite=B, o systemd não tentará iniciar B. A máquina de estados verifica a memória: se B não estiver já no estado active no exato milissegundo em que A for solicitada, a ativação de A falha instantaneamente com um erro de pré-requisito não satisfeito. Útil para serviços que não devem sob nenhuma hipótese invocar a inicialização de subsistemas pesados.
1.4. BindsTo= (Propagação de Morte)
A forma mais agressiva de acoplamento. Adiciona à semântica do Requires= o rastreamento contínuo do ciclo de vida.
- Se A possui
BindsTo=B, e o serviço B for encerrado manualmente pelo administrador (systemctl stop B), ou sofrer um crash inesperado, a máquina de estados injetará assincronamente um trabalho de terminação (stop job) para A. - Caso de Uso: Quase exclusivamente utilizado para amarrar serviços de espaço de usuário a componentes físicos ou lógicos do kernel (unidades
.device). Por exemplo, um túnel OpenVPN que possuiBindsTo=sys-subsystem-net-devices-eth0.device. Se o cabo for desconectado ou o driver da placa de rede realizar um reset, o túnel VPN é instantaneamente dizimado pelo PID 1.
1.5. PartOf= (Propagação de Ação Administrativa)
Uma dependência de gerenciamento de estado direcional. Se A possui PartOf=B, falhas em B não afetam A. No entanto, ações deliberadas de transição de estado ordenadas pelo administrador em B são refletidas em A. Se o administrador executar systemctl restart B ou systemctl stop B, a Unidade A também será reiniciada ou parada. É a fundação arquitetural dos agrupamentos de serviços (ex: reiniciar um alvo lógico reinicia todos os daemons associados a ele).
2. O Eixo Temporal (Ordering Dependencies)
A injeção de trabalhos na fila não resolve condições de corrida entre IPCs (Inter-Process Communication) ou sockets. Para isso, o systemd utiliza diretivas de ordenamento que atuam como bloqueios síncronos (locks temporais) no escalonamento interno dos trabalhos.
2.1. A Semântica de Bloqueio (Before= e After=)
Se a Unidade A declara After=B, isso introduz uma barreira temporal estrita no Grafo Acíclico Dirigido.
- Regra de Ouro da Ortogonalidade: Declarar
After=Bem A não instrui osystemda iniciar B. Se o administrador rodarsystemctl start Ae a unidade não possuirWants=BouRequires=B, a diretivaAfter=Bserá completamente ignorada, e A subirá instantaneamente. - Resolução Transacional: A diretiva
After=só tem efeito se, e somente se, ambas as unidades (A e B) estiverem presentes na mesma fila de transação. - Mecânica de Retenção: Se ambas estão na fila, o trabalho para A recebe o estado waiting. O PID 1 aguarda que a Unidade B transite do estado
activatingpara o estadoactive. O momento exato em que B é consideradaactivedepende crucialmente do seu modelo de execução (Type=na seção[Service]). Se B forType=notify, A continuará bloqueada até que o código C/Python de B envie o sinal nativoREADY=1via socket D-Bus para osystemd.
A diretiva Before= é simétrica e reflexiva. Declarar Before=B em A tem o exato mesmo peso estrutural que declarar After=A em B. O compilador de grafo do systemd funde essas declarações na RAM.
2.2. O Algoritmo de Quebra de Impasses (Deadlock Breaking)
Em configurações complexas de infraestrutura, é trivial que um administrador incauto crie uma dependência circular temporal: A precisa iniciar depois de B, B depois de C, e C depois de A.
Em um sistema de scripts SysVinit, isso causaria um travamento eterno no processo de boot (Infinite Loop). O systemd, operando sobre grafos matemáticos, detecta o ciclo durante a fase inicial de computação de transação.
Quando um ciclo é detectado, o systemd executa um algoritmo heurístico de quebra de impasses (Cycle Breaking):
- Ele inspeciona as arestas do ciclo.
- Ele tenta remover as dependências temporais geradas por
After=ouBefore=e as de requisito geradas porWants=. - As dependências geradas por
Requires=eBindsTo=têm prioridade de preservação. - Ao remover uma aresta fraca, o ciclo é desfeito e a transação procede. O
systemdemite um log com o nível de severidadeWARNINGno journald:Found dependency cycle, breaking.... Isso significa que o sistema iniciará, mas de forma subótima e com potencial condição de corrida, exigindo correção arquitetural.
3. Dependências Implícitas (DefaultDependencies)
Para evitar que os administradores tenham que digitar o mesmo arcabouço lógico repetidamente, o analisador de Unit Files do systemd possui a diretiva DefaultDependencies=yes ativada por padrão para todos os arquivos .service.
Esta diretiva injeta silenciosamente um bloco formidável de dependências no arquivo durante a compilação em RAM. Um serviço padrão herda imediatamente:
Requires=sysinit.targeteAfter=sysinit.target: Garante que nenhum daemon do usuário inicie antes que a raiz (/) esteja remontada em modo leitura-escrita, sistemas de arquivos vitais estejam montados, limites de recursos aplicados e sementes de criptografia RNG iniciadas.After=basic.target: Atrasa o serviço até que sockets IPC essenciais, como D-Bus, e caminhos do sistema estejam operacionais.Conflicts=shutdown.targeteBefore=shutdown.target: Define a semântica de encerramento. Quando a máquina receber a ordem de desligamento (invocação doshutdown.target), o conflito gerará automaticamente um trabalho destoppara o serviço atual, garantindo seu encerramento ordenado antes que os sistemas de arquivos sejam desmontados no caminho de volta.
Para escrever scripts de espaço muito inicial (early boot, como montadores customizados de partições cifradas), o administrador deve explicitamente declarar DefaultDependencies=no para evitar que a unidade fique aguardando alvos de alto nível que dependem da própria montagem que a unidade tenta realizar.
4. O Paradoxo do Network Target (network.target vs network-online.target)
Um dos tópicos de maior fricção na engenharia de dependências do systemd ocorre ao acoplar daemons à disponibilidade da rede. Muitos servidores web e bancos de dados requerem vinculação (bind) a endereços IP específicos (não apenas a 127.0.0.1 ou 0.0.0.0).
A configuração intuitiva After=network.target quase sempre resulta em falhas intermitentes. Na ontologia do systemd, o network.target indica apenas que o subsistema de gerenciamento de rede (ex: o serviço NetworkManager ou systemd-networkd) foi inicializado na memória. Não indica que existe tráfego fluindo ou um endereço IP alocado via DHCP. O daemon do servidor web será iniciado enquanto as placas de rede ainda negociam o link físico na Camada 2, resultando em erros fatais de Cannot assign requested address.
Para forçar a espera por conectividade de Camada 3 efetiva, o Grafo utiliza uma unidade síncrona especial: network-online.target.
Para que o serviço aguarde com sucesso a obtenção do IP de lease DHCP, o arquivo de unidade deve declarar:
Wants=network-online.target
After=network-online.target
O serviço de rede (como o NetworkManager-wait-online.service) executará um binário bloqueante que interroga o kernel continuamente até que o tráfego possa ser roteado, somente então marcando o network-online.target como alcançado, liberando a trava temporal para os daemons de camada de aplicação.