Service management: systemctl em scripts de deploy

1. Fundamentos do systemctl para Automação

O systemctl é a principal ferramenta de gerenciamento de serviços no systemd, presente na maioria das distribuições Linux modernas. Em scripts de deploy, dominar seus comandos básicos é essencial para automação confiável.

Comandos essenciais

# Comandos básicos de controle
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl status nginx

Verificação de estado com exit codes

O verdadeiro poder do systemctl em scripts está nos comandos de verificação que retornam exit codes previsíveis:

#!/bin/bash

SERVICE="nginx"

# Verificar se o serviço está ativo
if systemctl is-active --quiet "$SERVICE"; then
    echo "$SERVICE está rodando"
else
    echo "$SERVICE não está ativo"
fi

# Verificar se está habilitado para iniciar com o sistema
if systemctl is-enabled --quiet "$SERVICE"; then
    echo "$SERVICE está habilitado"
fi

# Verificar se falhou
if systemctl is-failed --quiet "$SERVICE"; then
    echo "$SERVICE falhou - ação necessária!"
fi

2. Gerenciamento de Serviços em Deploy

Parada segura antes de atualizações

#!/bin/bash

deploy_app() {
    local SERVICE="myapp"
    local DEPLOY_DIR="/opt/myapp"
    local BACKUP_DIR="/opt/backups/myapp"

    echo "Iniciando deploy da aplicação..."

    # Parar o serviço antes de atualizar
    echo "Parando $SERVICE..."
    systemctl stop "$SERVICE"

    # Backup do diretório atual
    echo "Criando backup..."
    cp -r "$DEPLOY_DIR" "$BACKUP_DIR/$(date +%Y%m%d_%H%M%S)"

    # Atualizar arquivos (rsync, scp, etc)
    echo "Copiando novos arquivos..."
    rsync -avz --delete ./dist/ "$DEPLOY_DIR/"
}

Reinicialização condicional após deploy

#!/bin/bash

restart_if_deployed() {
    local SERVICE="myapp"
    local HEALTH_ENDPOINT="http://localhost:8080/health"

    # Verificar se arquivos foram alterados
    if [ -f /tmp/deploy_complete.flag ]; then
        echo "Deploy detectado. Reiniciando serviço..."
        systemctl restart "$SERVICE"
        rm -f /tmp/deploy_complete.flag
    else
        echo "Nenhum deploy novo detectado."
    fi
}

Habilitar/desabilitar serviços automaticamente

#!/bin/bash

configure_service_autostart() {
    local SERVICE="myapp"
    local ENABLE=${1:-true}

    if [ "$ENABLE" = true ]; then
        systemctl enable "$SERVICE"
        echo "$SERVICE habilitado para iniciar com o sistema"
    else
        systemctl disable "$SERVICE"
        echo "$SERVICE desabilitado"
    fi
}

3. Monitoramento e Validação de Saúde

Aguardando serviço ficar ativo

#!/bin/bash

wait_for_service() {
    local SERVICE="$1"
    local TIMEOUT=${2:-30}
    local INTERVAL=2
    local ELAPSED=0

    echo "Aguardando $SERVICE ficar ativo..."

    # systemctl is-active --wait (systemd 240+)
    if systemctl is-active --wait "$SERVICE" 2>/dev/null; then
        echo "$SERVICE está ativo"
        return 0
    fi
}

Polling com loops e timeouts para serviços lentos

#!/bin/bash

poll_service_health() {
    local SERVICE="$1"
    local TIMEOUT=${2:-60}
    local SLEEP_INTERVAL=${3:-5}
    local ELAPSED=0

    while [ $ELAPSED -lt $TIMEOUT ]; do
        if systemctl is-active --quiet "$SERVICE"; then
            echo "Serviço $SERVICE ativo após ${ELAPSED}s"
            return 0
        fi

        sleep "$SLEEP_INTERVAL"
        ELAPSED=$((ELAPSED + SLEEP_INTERVAL))
    done

    echo "TIMEOUT: $SERVICE não ficou ativo em ${TIMEOUT}s"
    return 1
}

Verificação de falhas e logs integrados

#!/bin/bash

check_service_failure() {
    local SERVICE="$1"
    local LOG_LINES=${2:-20}

    if systemctl is-failed --quiet "$SERVICE"; then
        echo "FALHA detectada em $SERVICE"
        echo "Últimas $LOG_LINES linhas do journal:"
        journalctl -u "$SERVICE" --no-pager -n "$LOG_LINES"
        return 1
    fi
    return 0
}

4. Tratamento de Erros e Rollback

Captura de falhas

#!/bin/bash

safe_restart() {
    local SERVICE="$1"
    local BACKUP_DIR="$2"

    if ! systemctl restart "$SERVICE"; then
        echo "ERRO: Falha ao reiniciar $SERVICE"
        return 1
    fi

    # Aguardar confirmação
    sleep 5
    if ! systemctl is-active --quiet "$SERVICE"; then
        echo "ERRO: $SERVICE não está rodando após restart"
        return 1
    fi

    return 0
}

Rollback automático

#!/bin/bash

deploy_with_rollback() {
    local SERVICE="myapp"
    local CURRENT_DIR="/opt/myapp"
    local BACKUP_DIR="/opt/backups/myapp/latest"

    # Tentar restart
    echo "Reiniciando $SERVICE..."
    if ! systemctl restart "$SERVICE"; then
        echo "Falha no restart. Iniciando rollback..."
        cp -r "$BACKUP_DIR"/* "$CURRENT_DIR/"
        systemctl restart "$SERVICE"

        if systemctl is-active --quiet "$SERVICE"; then
            echo "Rollback concluído com sucesso"
            return 0
        else
            echo "Rollback também falhou!"
            return 1
        fi
    fi

    echo "Deploy bem-sucedido"
}

Uso de trap para restaurar estado anterior

#!/bin/bash

cleanup() {
    echo "Limpando e restaurando serviços..."
    systemctl start "$SERVICE" 2>/dev/null || true
    systemctl enable "$SERVICE" 2>/dev/null || true
}

trap cleanup EXIT ERR

deploy_script() {
    local SERVICE="myapp"

    systemctl stop "$SERVICE"
    # ... operações de deploy ...
    systemctl start "$SERVICE"
}

5. Manipulação de Unidades Customizadas

Criação e instalação de arquivos .service

#!/bin/bash

install_custom_service() {
    local SERVICE_NAME="myapp"
    local SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"

    cat > "$SERVICE_FILE" << EOF
[Unit]
Description=My Application Service
After=network.target

[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

    systemctl daemon-reload
    systemctl enable "$SERVICE_NAME"
}

Validação de sintaxe

#!/bin/bash

validate_service_unit() {
    local SERVICE_FILE="$1"

    if [ ! -f "$SERVICE_FILE" ]; then
        echo "Arquivo não encontrado: $SERVICE_FILE"
        return 1
    fi

    # Validar sintaxe
    if systemd-analyze verify "$SERVICE_FILE"; then
        echo "Unidade válida"
        return 0
    else
        echo "Unidade inválida - verifique o arquivo"
        return 1
    fi
}

6. Integração com Outras Ferramentas de Deploy

Exemplo completo: deploy + restart + healthcheck

#!/bin/bash

# Script completo de deploy
set -euo pipefail

SERVICE="myapp"
DEPLOY_DIR="/opt/myapp"
BINARY_SOURCE="/tmp/builds/myapp-latest"
HEALTHCHECK_URL="http://localhost:8080/health"

echo "=== Início do Deploy ==="

# 1. Parar serviço
echo "Parando $SERVICE..."
systemctl stop "$SERVICE"

# 2. Atualizar binário
echo "Copiando novo binário..."
cp "$BINARY_SOURCE" "$DEPLOY_DIR/myapp"
chmod +x "$DEPLOY_DIR/myapp"

# 3. Recarregar daemon se necessário
systemctl daemon-reload

# 4. Iniciar serviço
echo "Iniciando $SERVICE..."
systemctl start "$SERVICE"

# 5. Healthcheck
echo "Verificando saúde..."
for i in {1..12}; do
    if systemctl is-active --quiet "$SERVICE"; then
        if curl -sf "$HEALTHCHECK_URL" > /dev/null; then
            echo "Deploy concluído com sucesso!"
            exit 0
        fi
    fi
    sleep 5
done

# 6. Rollback se falhar
echo "Falha no deploy. Realizando rollback..."
systemctl stop "$SERVICE"
cp "$DEPLOY_DIR/myapp.bak" "$DEPLOY_DIR/myapp"
systemctl start "$SERVICE"
exit 1

7. Boas Práticas e Segurança

Evitar restart sem verificação

#!/bin/bash

# EVITE:
systemctl restart nginx  # Sem verificar se há alterações

# PREFIRA:
if [ -f /etc/nginx/nginx.conf ]; then
    nginx -t && systemctl reload nginx || echo "Config inválida"
fi

Uso de systemctl --user para serviços não-root

#!/bin/bash

# Script para usuário não-root
deploy_user_service() {
    local SERVICE="myapp-user"
    local UNIT_DIR="${HOME}/.config/systemd/user"

    mkdir -p "$UNIT_DIR"

    cat > "$UNIT_DIR/${SERVICE}.service" << EOF
[Unit]
Description=User Service

[Service]
ExecStart=/usr/local/bin/myapp
Restart=on-failure

[Install]
WantedBy=default.target
EOF

    systemctl --user daemon-reload
    systemctl --user enable "$SERVICE"
    systemctl --user start "$SERVICE"
}

Logging estruturado e notificações

#!/bin/bash

log_and_notify() {
    local MESSAGE="$1"
    local LEVEL="${2:-INFO}"
    local TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

    # Log estruturado
    echo "[$TIMESTAMP] [$LEVEL] $MESSAGE" >> /var/log/deploy.log

    # Notificação (exemplo com Slack)
    if [ "$LEVEL" = "ERROR" ]; then
        curl -sf -X POST -H "Content-type: application/json" \
            --data "{\"text\":\"[$LEVEL] $MESSAGE\"}" \
            "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" 2>/dev/null || true
    fi
}

deploy_service() {
    log_and_notify "Iniciando deploy de $SERVICE" "INFO"
    systemctl restart "$SERVICE" || log_and_notify "Falha no deploy" "ERROR"
}

Referências