Aprovecha systemd para crear servicios robustos y escalables
systemd es un sistema de inicialización y gestor de servicios para sistemas Linux modernos. Reemplaza al tradicional System V init y proporciona un marco robusto para gestionar procesos, servicios y recursos del sistema.
# /etc/systemd/system/mi-aplicacion.service
[Unit]
Description=Mi Aplicación Web
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/mi-aplicacion
ExecStart=/opt/mi-aplicacion/start.sh
ExecStop=/opt/mi-aplicacion/stop.sh
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
#!/bin/bash
# /opt/mi-aplicacion/start.sh
set -euo pipefail
# Configuración
APP_NAME="mi-aplicacion"
APP_DIR="/opt/${APP_NAME}"
LOG_DIR="/var/log/${APP_NAME}"
PID_FILE="/run/${APP_NAME}.pid"
CONFIG_FILE="${APP_DIR}/config/app.conf"
# Logging
exec 1> >(logger -s -t "${APP_NAME}" -p user.info)
exec 2> >(logger -s -t "${APP_NAME}" -p user.error)
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2
}
# Función de limpieza
cleanup() {
log_info "Deteniendo aplicación..."
if [[ -f "$PID_FILE" ]]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
kill -TERM "$pid"
wait "$pid" 2>/dev/null || true
fi
rm -f "$PID_FILE"
fi
exit 0
}
# Manejo de señales
trap cleanup SIGTERM SIGINT
# Verificaciones previas
if [[ ! -f "$CONFIG_FILE" ]]; then
log_error "Archivo de configuración no encontrado: $CONFIG_FILE"
exit 1
fi
if [[ ! -d "$LOG_DIR" ]]; then
mkdir -p "$LOG_DIR"
chown www-data:www-data "$LOG_DIR"
fi
# Cargar configuración
source "$CONFIG_FILE"
# Verificar dependencias
command -v node >/dev/null 2>&1 || {
log_error "Node.js no está instalado"
exit 1
}
# Verificar puerto disponible
if netstat -tuln | grep -q ":${APP_PORT:-3000} "; then
log_error "Puerto ${APP_PORT:-3000} ya está en uso"
exit 1
fi
log_info "Iniciando $APP_NAME en el puerto ${APP_PORT:-3000}"
# Iniciar aplicación
cd "$APP_DIR"
exec node app.js &
# Guardar PID
echo $! > "$PID_FILE"
# Esperar indefinidamente
wait
set -euo pipefail
para manejo estricto de errores#!/bin/bash
# create-service.sh - Generador de servicios systemd
SERVICE_TEMPLATE="[Unit]
Description={{DESCRIPTION}}
After=network.target{{AFTER_TARGETS}}
Requires={{REQUIRES}}
{{ADDITIONAL_UNIT}}
[Service]
Type={{SERVICE_TYPE}}
User={{USER}}
Group={{GROUP}}
WorkingDirectory={{WORKDIR}}
ExecStart={{EXEC_START}}
ExecStop={{EXEC_STOP}}
ExecReload={{EXEC_RELOAD}}
Restart={{RESTART_POLICY}}
RestartSec={{RESTART_SEC}}
StandardOutput=journal
StandardError=journal
Environment={{ENVIRONMENT}}
{{ADDITIONAL_SERVICE}}
[Install]
WantedBy={{WANTED_BY}}"
create_service() {
local service_name="$1"
local config_file="$2"
# Cargar configuración
source "$config_file"
# Reemplazar variables
local service_content="$SERVICE_TEMPLATE"
service_content=${service_content//\{\{DESCRIPTION\}\}/${DESCRIPTION:-"Servicio personalizado"}}
service_content=${service_content//\{\{AFTER_TARGETS\}\}/${AFTER_TARGETS}}
service_content=${service_content//\{\{REQUIRES\}\}/${REQUIRES}}
service_content=${service_content//\{\{SERVICE_TYPE\}\}/${SERVICE_TYPE:-"simple"}}
service_content=${service_content//\{\{USER\}\}/${USER:-"nobody"}}
service_content=${service_content//\{\{GROUP\}\}/${GROUP:-"nobody"}}
service_content=${service_content//\{\{WORKDIR\}\}/${WORKDIR:-"/tmp"}}
service_content=${service_content//\{\{EXEC_START\}\}/${EXEC_START}}
service_content=${service_content//\{\{EXEC_STOP\}\}/${EXEC_STOP}}
service_content=${service_content//\{\{EXEC_RELOAD\}\}/${EXEC_RELOAD}}
service_content=${service_content//\{\{RESTART_POLICY\}\}/${RESTART_POLICY:-"always"}}
service_content=${service_content//\{\{RESTART_SEC\}\}/${RESTART_SEC:-"10"}}
service_content=${service_content//\{\{ENVIRONMENT\}\}/${ENVIRONMENT}}
service_content=${service_content//\{\{WANTED_BY\}\}/${WANTED_BY:-"multi-user.target"}}
service_content=${service_content//\{\{ADDITIONAL_UNIT\}\}/${ADDITIONAL_UNIT}}
service_content=${service_content//\{\{ADDITIONAL_SERVICE\}\}/${ADDITIONAL_SERVICE}}
# Escribir archivo de servicio
echo "$service_content" > "/etc/systemd/system/${service_name}.service"
# Recargar systemd y habilitar servicio
systemctl daemon-reload
systemctl enable "$service_name"
echo "Servicio $service_name creado y habilitado"
}
# Ejemplo de uso
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
if [[ $# -ne 2 ]]; then
echo "Uso: $0 "
exit 1
fi
create_service "$1" "$2"
fi
# webapp.conf
DESCRIPTION="Aplicación Web Node.js"
AFTER_TARGETS=" mysql.service redis.service"
REQUIRES="network.target"
SERVICE_TYPE="simple"
USER="webapp"
GROUP="webapp"
WORKDIR="/opt/webapp"
EXEC_START="/opt/webapp/bin/start.sh"
EXEC_STOP="/opt/webapp/bin/stop.sh"
EXEC_RELOAD="/bin/kill -USR1 \$MAINPID"
RESTART_POLICY="always"
RESTART_SEC="5"
ENVIRONMENT="NODE_ENV=production PORT=3000"
WANTED_BY="multi-user.target"
ADDITIONAL_SERVICE="LimitNOFILE=65536
TimeoutStartSec=30
KillMode=mixed"
# /etc/systemd/system/backup-db.service
[Unit]
Description=Backup de Base de Datos
Wants=network.target
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-database.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/backup-db.timer
[Unit]
Description=Ejecutar backup de BD cada 6 horas
Requires=backup-db.service
[Timer]
OnBootSec=30min
OnUnitActiveSec=6h
Persistent=true
[Install]
WantedBy=timers.target
#!/bin/bash
# /usr/local/bin/backup-database.sh
set -euo pipefail
# Configuración
BACKUP_DIR="/backup/mysql"
MYSQL_USER="backup_user"
MYSQL_PASS="backup_password"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/backup/mysql-backup.log"
# Logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
logger -t "mysql-backup" "$*"
}
# Función de limpieza
cleanup() {
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR: Backup falló con código $exit_code"
# Enviar notificación de error
/usr/local/bin/send-alert.sh "Backup MySQL falló" "El backup de MySQL falló con código $exit_code"
fi
exit $exit_code
}
trap cleanup EXIT
# Crear directorio de backup si no existe
mkdir -p "$BACKUP_DIR"
mkdir -p "$(dirname "$LOG_FILE")"
log "Iniciando backup de MySQL"
# Obtener lista de bases de datos
databases=$(mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "SHOW DATABASES;" | tail -n +2 | grep -v information_schema | grep -v performance_schema | grep -v mysql | grep -v sys)
# Backup de cada base de datos
for db in $databases; do
log "Haciendo backup de $db"
backup_file="${BACKUP_DIR}/${db}_${DATE}.sql.gz"
if mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASS" \
--single-transaction \
--routines \
--triggers \
--events \
--flush-logs \
--master-data=2 \
"$db" | gzip > "$backup_file"; then
log "Backup de $db completado: $backup_file"
# Verificar integridad del archivo
if ! gzip -t "$backup_file"; then
log "ERROR: Archivo de backup corrupto: $backup_file"
rm -f "$backup_file"
exit 1
fi
# Calcular checksum
checksum=$(sha256sum "$backup_file" | cut -d' ' -f1)
echo "$checksum $backup_file" > "${backup_file}.sha256"
log "Checksum: $checksum"
else
log "ERROR: Falló el backup de $db"
exit 1
fi
done
# Limpieza de backups antiguos
log "Limpiando backups antiguos (> ${RETENTION_DAYS} días)"
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR" -name "*.sha256" -mtime +$RETENTION_DAYS -delete
# Estadísticas del backup
backup_count=$(find "$BACKUP_DIR" -name "*_${DATE}.sql.gz" | wc -l)
total_size=$(find "$BACKUP_DIR" -name "*_${DATE}.sql.gz" -exec du -ch {} + | tail -1 | cut -f1)
log "Backup completado: $backup_count bases de datos, tamaño total: $total_size"
# Enviar notificación de éxito
/usr/local/bin/send-alert.sh "Backup MySQL exitoso" "Backup completado: $backup_count DBs, $total_size"
exit 0
# Habilitar y iniciar timer
sudo systemctl enable backup-db.timer
sudo systemctl start backup-db.timer
# Ver estado de timers
systemctl list-timers
# Ver logs del servicio
journalctl -u backup-db.service -f
# Ejecutar manualmente
sudo systemctl start backup-db.service
#!/bin/bash
# /usr/local/bin/service-monitor.sh
set -euo pipefail
SERVICES_TO_MONITOR=(
"nginx"
"mysql"
"redis"
"webapp"
"backup-db.timer"
)
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
ALERT_FILE="/var/run/service-alerts"
send_alert() {
local service="$1"
local status="$2"
local message="$3"
# Evitar spam de alertas
local alert_key="${service}_${status}"
if grep -q "$alert_key" "$ALERT_FILE" 2>/dev/null; then
return 0
fi
echo "$alert_key" >> "$ALERT_FILE"
# Enviar a Slack
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚨 Service Alert: $service is $status - $message\"}" \
"$WEBHOOK_URL" || true
# Enviar email
echo "$message" | mail -s "Service Alert: $service $status" [email protected] || true
# Log local
logger -t "service-monitor" "$service is $status: $message"
}
clear_alert() {
local service="$1"
local status="$2"
local alert_key="${service}_${status}"
if [[ -f "$ALERT_FILE" ]]; then
grep -v "$alert_key" "$ALERT_FILE" > "${ALERT_FILE}.tmp" || true
mv "${ALERT_FILE}.tmp" "$ALERT_FILE"
fi
}
check_service() {
local service="$1"
if systemctl is-active --quiet "$service"; then
# Servicio está activo
clear_alert "$service" "failed"
clear_alert "$service" "inactive"
# Verificar si ha tenido reiniciocios recientes
local restarts=$(systemctl show "$service" --property=NRestarts --value)
if [[ "$restarts" -gt 0 ]]; then
local restart_time=$(systemctl show "$service" --property=ExecMainStartTimestamp --value)
if [[ -n "$restart_time" ]]; then
local restart_epoch=$(date -d "$restart_time" +%s)
local now_epoch=$(date +%s)
local minutes_ago=$(( (now_epoch - restart_epoch) / 60 ))
if [[ $minutes_ago -lt 5 ]]; then
send_alert "$service" "restarted" "Service restarted $minutes_ago minutes ago (total restarts: $restarts)"
fi
fi
fi
elif systemctl is-failed --quiet "$service"; then
send_alert "$service" "failed" "Service has failed. Check with: journalctl -u $service"
else
send_alert "$service" "inactive" "Service is not running"
fi
}
# Crear archivo de alertas si no existe
touch "$ALERT_FILE"
# Verificar cada servicio
for service in "${SERVICES_TO_MONITOR[@]}"; do
echo "Verificando $service..."
check_service "$service"
done
# Limpiar alertas antiguas (más de 24 horas)
find "$ALERT_FILE" -mtime +1 -delete 2>/dev/null || true
echo "Monitoreo completado"
#!/bin/bash
# /usr/local/bin/service-dashboard.sh
create_html_dashboard() {
local html_file="/var/www/html/services-dashboard.html"
cat > "$html_file" << 'EOF'
Services Dashboard
Services Status Dashboard
EOF
# Generar estado de servicios
for service in nginx mysql redis webapp; do
local status="inactive"
local class="inactive"
local memory=""
local cpu=""
local uptime=""
if systemctl is-active --quiet "$service"; then
status="active"
class="active"
# Obtener métricas
memory=$(systemctl show "$service" --property=MemoryCurrent --value 2>/dev/null | numfmt --to=iec 2>/dev/null || echo "N/A")
cpu=$(systemctl show "$service" --property=CPUUsageNSec --value 2>/dev/null || echo "N/A")
uptime=$(systemctl show "$service" --property=ActiveEnterTimestamp --value 2>/dev/null || echo "N/A")
elif systemctl is-failed --quiet "$service"; then
status="failed"
class="failed"
fi
cat >> "$html_file" << EOF
$service
Status: $status
Memory: $memory
Uptime: $uptime
EOF
done
echo "" >> "$html_file"
}
# Crear dashboard
create_html_dashboard
# Generar métricas JSON para APIs
generate_metrics_json() {
local json_file="/var/www/html/services-metrics.json"
echo "{" > "$json_file"
echo " \"timestamp\": \"$(date -Iseconds)\"," >> "$json_file"
echo " \"services\": {" >> "$json_file"
local first=true
for service in nginx mysql redis webapp; do
[[ "$first" == true ]] && first=false || echo "," >> "$json_file"
local status="inactive"
local memory=0
local restarts=0
if systemctl is-active --quiet "$service"; then
status="active"
memory=$(systemctl show "$service" --property=MemoryCurrent --value 2>/dev/null || echo "0")
restarts=$(systemctl show "$service" --property=NRestarts --value 2>/dev/null || echo "0")
elif systemctl is-failed --quiet "$service"; then
status="failed"
fi
cat >> "$json_file" << EOF
"$service": {
"status": "$status",
"memory_bytes": $memory,
"restarts": $restarts
}EOF
done
echo "" >> "$json_file"
echo " }" >> "$json_file"
echo "}" >> "$json_file"
}
generate_metrics_json
echo "Dashboard actualizado en /var/www/html/services-dashboard.html"
Crea un servicio systemd para una API REST que:
Implementa un timer que:
Desarrolla un sistema que: