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 .conf com 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 diretamente
  • Type=forking: Processo pai termina após fork (compatível com daemonização manual)
  • Type=notify: Usa sd_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 User e Group não-root
  • Configure EnvironmentFile para configurações externas
  • Defina Restart=on-failure com RestartSec adequado
  • Utilize LimitNOFILE para 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