Stack: Django + DRF · Proyecto: TicketFlow · SO: Linux Estado: Publicada — impartición tras corregir la 04 Prerrequisito: Lección 04 — Estructuras de datos
Objetivos
Al terminar esta lección podrás:
- Ver, entender y operar los procesos de tu servidor (Gunicorn, Celery, Postgres) con
ps,top/htopy señales. - Gestionar permisos y usuarios con criterio de producción (principio de mínimo privilegio).
- Leer logs con
journalctl,tail -f,grep,awky encontrar el error en millones de líneas. - Configurar el entorno con variables (12-factor adelantado) sin filtrar secretos.
- Conectarte a producción por SSH con claves y crear túneles seguros para depurar.
1. Procesos: verlos, medirlos, señalarlos
Tu TicketFlow en producción son varios procesos: Gunicorn (2-4), Celery (1-4), PostgreSQL, Redis, Nginx. Comandos del oficio:
ps aux | grep gunicorn # quién soy, cuánta memoria (RSS), cuánta CPU
top # en vivo; P ordena por CPU, M por memoria
htop # top de los buenos: árboles y colores
ss -tlnp # quién escucha en qué puerto (el "netstat" moderno)
uptime # carga media: 1, 5 y 15 minutosLa carga media se lee así: en una máquina de 2 vCPU, una carga de 2.00 es "justo llena", de 4.00 es "el doble de trabajo que núcleos" (los procesos esperan). Si uptime dice 8.00 sostenido, tu API va lenta aunque ningún error aparezca.
Señales: la forma elegante de hablar con un proceso:
| Señal | Qué hace | Uso típico |
|---|---|---|
| SIGTERM (15) | apagado educado | kill <pid>; Gunicorn termina las peticiones en curso |
| SIGKILL (9) | apagado violento | último recurso; el proceso ni se entera |
| SIGHUP (1) | recarga config | Gunicorn lo usa para re-leer sin cortar el servicio |
| SIGUSR1/2 | definidas por la app | rotar logs de Gunicorn, workers de debug |
Clave:
kill -9es la rendición: el proceso no puede guardar nada (logs, transacciones en curso). Primero SIGTERM y espera; SIGKILL solo si está colgado de verdad. Reiniciar consystemctl restart gunicornmanda SIGTERM y espera correctamente — por eso los servicios de producción viven en systemd, no ennohup.
2. Permisos y usuarios: mínimo privilegio
Regla de producción: tu app no corre como root. Nunca.
sudo useradd -r -s /usr/sbin/nologin ticketflow # usuario de sistema, sin shell
sudo chown -R ticketflow:ticketflow /srv/ticketflow
# en el service de systemd: User=ticketflowPermisos en 3 grupos × 3 bits: rwx para usuario/grupo/otros. 755 = yo todo, los demás leen y ejecutan. 600 = solo yo leo/escribo (para secretos). 640 para .env: dueño lee, grupo lee, otros nada.
chmod 600 /srv/ticketflow/.env # los secretos: solo el usuario de la app
chmod 750 /srv/ticketflow # nadie del sistema puede listar tu código
ls -la # mirar quién es quién antes de tocar
umask 027 # permisos por defecto prudentesErrores clásicos (y sus consecuencias): .env con 644 (cualquier usuario del servidor lee tus claves); correr Gunicorn como root (un RCE en tu app = servidor entero comprometido); chmod -R 777 como "arreglo rápido" (arreglo que es la vulnerabilidad).
3. Logs: encontrar la aguja
Un servidor sano genera millones de líneas. Las herramientas de corte:
journalctl -u gunicorn -f # seguir el servicio en vivo
tail -n 100 error.log # lo último
grep -i "error" gunicorn-error.log # filtrar
grep -i error gunicorn-error.log | awk '{print $1}' | sort | uniq -c | sort -rn | headEl pipeline de arriba es el comando de diagnóstico: cuenta errores agrupados por la primera columna, ordenados de más a menos. Con esto sabes si tienes un error repetido 5000 veces (un bug) o 5000 errores distintos (una tormenta).
Qué debe contener una línea de log útil (lo formalizamos en la Lección 45):
2026-09-28T10:14:22Z INFO [req_id=ab12cd] POST /api/reservations/ 201 45ms user=1042Nivel, timestamp UTC, identificador de correlación, acción, resultado, duración, actor. Sin req_id, depurar un incidente multi-servicio es adivinación.
Y la regla de retención: los logs rotan (logrotate, journald con límite de tamaño) o llenan el disco — y un disco lleno es la forma #2 de caerse (la #1 es la BD creciendo sin control; lo verás en el Módulo 2).
4. Variables de entorno: la configuración que no se versiona
Las claves, contraseñas y ajustes por entorno viven en el entorno, no en el código (12-factor: Lección 27 completa):
# .env (fuera de Git: la .gitignore ya lo hace desde la Lección 00)
DEBUG=0
SECRET_KEY=...
DATABASE_URL=postgres://ticketflow:...@localhost/ticketflow
CELERY_BROKER_URL=redis://localhost:6379/0
# cargarlas en tu shell de desarrollo
set -a; source .env; set +aEn Python: os.environ["SECRET_KEY"] (y con django-environ o pydantic-settings, validadas al arrancar). Dos reglas duras:
- Un
.env.exampleversionado con todos los nombres y ningún valor real. Quien se incorpore al proyecto copia, rellena y funciona. - Los secretos no van en logs ni en tracebacks:
DEBUG=1en producción imprime tu configuración al mundo (por esoDEBUG=0es la primera comprobación de cualquier auditoría).
5. SSH: la puerta de producción
Acceso por claves, nunca por contraseña:
ssh-keygen -t ed25519 -C "tu@correo" # una clave por máquina (o por proyecto)
ssh-copy-id usuario@ticketflow.app # instalar la pública en el servidor
ssh -i ~/.ssh/ed25519 usuario@ticketflow.app~/.ssh/config para no teclear:
Host prod
HostName ticketflow.app
User deploy
IdentityFile ~/.ssh/ed25519Y los dos trucos que cambian tu vida de backend:
# Túnel: la BD de producción en tu localhost (solo lectura, con tu usuario de app)
ssh -L 5433:localhost:5432 prod
# ahora: psql -h localhost -p 5433 -U ticketflow ticketflow
# Ejecutar sin abrir shell interactiva (para scripts de despliegue)
ssh prod "systemctl status gunicorn --no-pager"Clave: el túnel es para depurar con permiso; el acceso a datos de producción se rige por RGPD y política de la empresa (Lección 56). Menos acceso del que necesitas y siempre auditable.
6. Scripting: automatiza lo que hagas tres veces
Todo lo anterior se combina en scripts pequeños y legibles. Ejemplo real: comprobar la salud completa de TicketFlow:
#!/usr/bin/env bash
# healthcheck.sh — salud de TicketFlow en 4 comprobaciones
set -euo pipefail # falla rápido, variables obligatorias, pipes estrictos
for url in "http://127.0.0.1:8000/api/health/" "http://127.0.0.1:8000/api/events/"; do
code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [[ "$code" != 200 ]]; then
echo "FALLO $url -> $code" >&2
exit 1
fi
done
systemctl is-active --quiet gunicorn celery
echo "OK: web y workers vivos"Lo mínimo de bash que evita el 90% de los sustos: set -euo pipefail (salir ante el primer error), comillas en todas las variables ("$var"), y [[ ]] en vez de [ ]. Y la regla del oficio: si lo has hecho a mano tres veces, es un script; si lo ejecuta un cron, es un job con logs y alerta (Lección 31).
Autoevaluación (respóndeme en el chat)
- ¿Por qué
systemctl restart gunicorny nokill -9a los workers? ¿Qué se pierde con SIGKILL? - Tu
/srv/ticketflow/.envtiene permisos 644 y el servidor tiene 5 usuarios. ¿Qué riesgos concretos hay y cómo los cierras? - El pipeline
grep... | awk | sort | uniq -c | sort -rn | head: explica cada eslabón y qué pregunta responde en un incidente. - ¿Por qué
DEBUG=1en producción es la primera finding de cualquier auditoría? ¿Qué se filtra exactamente? - Diseña el
deploy.shmínimo para TicketFlow: pull, instalar deps, migrar, recolectar estáticos, recargar servicios. ¿Qué orden has elegido y por qué el "migrar" va antes de recargar?
Continúa con los ejercicios. Las soluciones solo tras intentarlo.