Deployment: systemd services e init scripts
1. Fundamentos de Inicialização e Gerenciamento de Serviços em C
1.1. Daemonização manual vs. delegada ao systemd
Tradicionalmente, aplicações C precisavam implementar sua própria daemonização usando fork(), setsid() e umask(). Com systemd, esse processo pode ser delegado ao gerenciador de serviços, simplificando o código.
Daemonização manual (abordagem clássica):
void daemonizar() {
pid_t pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); // Processo pai termina
if (setsid() < 0) exit(EXIT_FAILURE); // Nova sessão
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS);
umask(0);
chdir("/");
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
Abordagem systemd (sem daemonização manual):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
// systemd gerencia o background
// Apenas executa o serviço normalmente
while (1) {
printf("Serviço rodando...\n");
sleep(5);
}
return 0;
}
1.2. Ciclo de vida de um serviço C
O ciclo de vida inclui inicialização, processamento e shutdown controlado via sinais:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
volatile sig_atomic_t keep_running = 1;
void handle_signal(int sig) {
if (sig == SIGTERM || sig == SIGINT) {
keep_running = 0;
}
}
int main() {
signal(SIGTERM, handle_signal);
signal(SIGINT, handle_signal);
printf("Serviço iniciado (PID: %d)\n", getpid());
while (keep_running) {
// Lógica principal do serviço
sleep(1);
}
printf("Realizando shutdown graceful...\n");
// Limpeza de recursos
return 0;
}
1.3. Diferenças entre SysV init, Upstart e systemd
- SysV init: Scripts shell sequenciais, dependência manual de runlevels
- Upstart: Event-driven, arquivos
.confcom jobs - systemd: Gerenciamento paralelo, dependências declarativas, socket activation
2. Criando um Serviço systemd para Aplicações C
2.1. Estrutura de um arquivo .service
[Unit]
Description=Meu Serviço C
After=network.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/meu-servico
WorkingDirectory=/opt/meu-servico
User=servicouser
Group=servicouser
EnvironmentFile=-/etc/default/meu-servico
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
2.2. Configuração de Type
Type=simple: Processo principal executa diretamenteType=forking: Processo pai termina após fork (compatível com daemonização manual)Type=notify: Usasd_notify()para sinalizar prontidão
Exemplo com sd_notify():
#include <systemd/sd-daemon.h>
int main() {
// Inicialização do serviço
sd_notify(0, "READY=1");
while (1) {
// Processamento
sd_notify(0, "STATUS=Processando...");
sleep(5);
}
return 0;
}
2.3. Boas práticas
- Use
UsereGroupnão-root - Configure
EnvironmentFilepara configurações externas - Defina
Restart=on-failurecomRestartSecadequado - Utilize
LimitNOFILEpara limites de arquivos abertos
3. Escrevendo Scripts Init (SysV) Compatíveis com C
3.1. Estrutura de script init
#!/bin/bash
### BEGIN INIT INFO
# Provides: meu-servico
# Required-Start: $network $remote_fs
# Required-Stop: $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Meu Serviço em C
### END INIT INFO
DAEMON=/usr/local/bin/meu-servico
PIDFILE=/var/run/meu-servico.pid
start() {
echo "Iniciando serviço..."
start-stop-daemon --start --background --make-pidfile \
--pidfile $PIDFILE --exec $DAEMON
}
stop() {
echo "Parando serviço..."
start-stop-daemon --stop --pidfile $PIDFILE
rm -f $PIDFILE
}
status() {
if [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE) 2>/dev/null; then
echo "Serviço rodando (PID: $(cat $PIDFILE))"
else
echo "Serviço parado"
fi
}
case "$1" in
start) start ;;
stop) stop ;;
restart) stop; sleep 2; start ;;
status) status ;;
*) echo "Uso: $0 {start|stop|restart|status}" ;;
esac
3.2. Uso de arquivos PID e locks
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int write_pid_file(const char *path) {
FILE *fp = fopen(path, "w");
if (!fp) return -1;
fprintf(fp, "%d\n", getpid());
fclose(fp);
return 0;
}
3.3. Integração com chkconfig e runlevels
Instale o script em /etc/init.d/ e configure:
chmod +x /etc/init.d/meu-servico
chkconfig --add meu-servico
chkconfig meu-servico on
4. Gerenciamento de Sinais e Graceful Shutdown
4.1. Tratamento de sinais para finalização segura
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
volatile sig_atomic_t shutdown_flag = 0;
void signal_handler(int signo) {
if (signo == SIGTERM || signo == SIGINT) {
shutdown_flag = 1;
}
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
while (!shutdown_flag) {
// Processamento principal
pause(); // Aguarda sinais
}
// Cleanup
printf("Shutdown completo\n");
return 0;
}
4.2. SIGHUP para recarregar configuração
void reload_config(int signo) {
if (signo == SIGHUP) {
printf("Recarregando configuração...\n");
// Lógica de recarregamento
}
}
4.3. Timeouts e SIGKILL como fallback
Configure no systemd:
TimeoutStartSec=30
TimeoutStopSec=10
KillMode=control-group
5. Logging e Monitoramento do Serviço
5.1. syslog() vs. journald
#include <syslog.h>
#include <systemd/sd-journal.h>
void log_message(const char *msg) {
// syslog tradicional
syslog(LOG_INFO, "%s", msg);
// journald (systemd)
sd_journal_print(LOG_INFO, "%s", msg);
}
5.2. Redirecionamento para journal
No unit file:
StandardOutput=journal
StandardError=journal
5.3. Health checks com watchdog
#include <systemd/sd-daemon.h>
int main() {
uint64_t usec;
sd_watchdog_enabled(0, &usec);
sd_notify(0, "WATCHDOG=1");
while (1) {
// Processamento
sd_notify(0, "WATCHDOG=1"); // Reset watchdog
sleep(usec / 1000000 / 2);
}
}
6. Empacotamento e Distribuição do Serviço
6.1. Estrutura de diretórios
/usr/local/bin/meu-servico # Binário
/etc/systemd/system/meu-servico.service # Unit file
/etc/default/meu-servico # Configuração
/var/log/meu-servico/ # Logs
6.2. Scripts postinst/prerm para pacotes
postinst (Debian):
#!/bin/bash
set -e
case "$1" in
configure)
systemctl daemon-reload
systemctl enable meu-servico
systemctl start meu-servico || true
;;
esac
prerm:
#!/bin/bash
set -e
case "$1" in
remove|purge)
systemctl stop meu-servico || true
systemctl disable meu-servico || true
;;
esac
6.3. Testes de instalação
systemctl daemon-reload
systemctl enable meu-servico
systemctl start meu-servico
systemctl status meu-servico
journalctl -u meu-servico -n 50
7. Segurança e Hardening do Serviço
7.1. Diretivas de segurança no unit
[Service]
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=yes
ProtectHome=true
ProtectKernelModules=true
ProtectControlGroups=true
MemoryDenyWriteExecute=true
7.2. Redução de capacidades
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
7.3. Isolamento de namespace
PrivateNetwork=yes
PrivateUsers=yes
ProtectHostname=yes
8. Depuração e Troubleshooting de Serviços C no systemd
8.1. Análise de logs
journalctl -u meu-servico.service -f
journalctl -u meu-servico.service --since "5 min ago"
journalctl -u meu-servico.service -p err
8.2. Verificação de status e falhas
systemctl status meu-servico
systemctl list-units --failed
systemctl reset-failed meu-servico
8.3. Testes de inicialização
systemd-analyze verify /etc/systemd/system/meu-servico.service
strace -p $(pgrep meu-servico) -e trace=network
Referências
- Documentação oficial do systemd — Referência completa sobre unidades de serviço systemd
- Writing systemd Services in C — Artigo técnico de Lennart Poettering sobre boas práticas
- sd_notify() man page — Documentação da função de notificação do systemd
- SysV Init Scripts Tutorial — Tutorial prático para criação de scripts init
- Hardening systemd Services — Guia de segurança e hardening para serviços systemd
- Systemd for Developers — Documentação sobre tipos de serviço e configurações avançadas
- Linux Daemon Writing Guide — Guia clássico para criação de daemons em C no Linux