--- title: "Scripts de Python para administrar servidores Ubuntu 24.04" description: "Crea en Ubuntu 24.04 scripts de Python para vigilar recursos con psutil, analizar logs de Nginx y comprobar URL, con entorno virtual y temporizador systemd." category: "Herramientas de Automatización" language: es source: https://cubepath.com/es/docs/herramientas-de-automatización/automatizacion-con-python-scripts-utiles updated: 2026-09-25 --- # Scripts de Python para administrar servidores Ubuntu 24.04 Python es la opción natural cuando un script de administración deja de ser una lista de comandos: procesar logs, hacer peticiones HTTP en paralelo o trabajar con datos estructurados es mucho más legible que en Bash. En este tutorial crearás en Ubuntu 24.04 tres scripts útiles: una comprobación de CPU, memoria y disco con `psutil`, un analizador de logs de Nginx y un comprobador de URL. Los instalarás en un entorno virtual y programarás uno de ellos con un temporizador de systemd. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, que incluye Python 3.12, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - Nginx instalado si quieres probar el analizador de logs con datos reales (es opcional). ## Paso 1: Crear un entorno virtual para los scripts En Ubuntu 24.04 no puedes instalar paquetes con `pip` en el Python del sistema. Aunque instales `python3-pip`, un `pip install` fuera de un entorno virtual termina con este error: ```output error: externally-managed-environment ``` Es una protección intencionada (PEP 668) para que `pip` no rompa paquetes que gestiona `apt`. La solución correcta es un entorno virtual. Instala el módulo `venv`: ```bash sudo apt update sudo apt install python3-venv ``` Crea un directorio para los scripts y un entorno virtual dentro de él: ```bash sudo mkdir -p /opt/ops-scripts sudo python3 -m venv /opt/ops-scripts/venv ``` Instala `psutil`, la biblioteca que da acceso multiplataforma a CPU, memoria, discos y procesos: ```bash sudo /opt/ops-scripts/venv/bin/pip install psutil ``` Comprueba que el entorno virtual la encuentra: ```bash /opt/ops-scripts/venv/bin/python -c "import psutil; print(psutil.__version__)" ``` ```output 7.2.2 ``` El número de versión puede ser distinto. Lo importante es que no aparezca `ModuleNotFoundError`. ## Paso 2: Comprobar CPU, memoria y disco con psutil El primer script mide el uso de CPU, memoria y cada sistema de archivos, y avisa cuando se supera un umbral. Termina con código 1 si hay algún aviso, lo que permite detectarlo desde systemd o desde cualquier otro script. Crea el archivo: ```bash sudo nano /opt/ops-scripts/check_resources.py ``` ```python #!/opt/ops-scripts/venv/bin/python """Comprueba CPU, memoria y disco y avisa si se supera un umbral.""" import argparse import logging import sys import psutil # Sistemas de archivos virtuales o de solo lectura que no interesa vigilar IGNORED_FS = {"squashfs", "overlay", "tmpfs", "devtmpfs"} def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--cpu", type=float, default=90.0, help="umbral de CPU en %%") parser.add_argument("--mem", type=float, default=90.0, help="umbral de memoria en %%") parser.add_argument("--disk", type=float, default=85.0, help="umbral de disco en %%") return parser.parse_args() def main() -> int: args = parse_args() logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") checks = [ ("CPU", psutil.cpu_percent(interval=1), args.cpu), ("Memoria", psutil.virtual_memory().percent, args.mem), ] for part in psutil.disk_partitions(): if part.fstype in IGNORED_FS: continue usage = psutil.disk_usage(part.mountpoint) checks.append((f"Disco {part.mountpoint}", usage.percent, args.disk)) problems = 0 for name, value, limit in checks: if value >= limit: logging.warning("%s al %.1f%% (umbral %.0f%%)", name, value, limit) problems += 1 else: logging.info("%s al %.1f%%", name, value) return 1 if problems else 0 if __name__ == "__main__": sys.exit(main()) ``` La primera línea (shebang) apunta al Python del entorno virtual, así que el script encuentra `psutil` sin tener que activar nada. `cpu_percent(interval=1)` mide durante un segundo; sin intervalo, la primera llamada siempre devuelve 0. Hazlo ejecutable y pruébalo: ```bash sudo chmod 755 /opt/ops-scripts/check_resources.py /opt/ops-scripts/check_resources.py ``` ```output INFO CPU al 3.0% INFO Memoria al 21.4% INFO Disco / al 23.1% INFO Disco /boot al 12.0% ``` Fuerza un aviso con un umbral bajo y comprueba el código de salida: ```bash /opt/ops-scripts/check_resources.py --mem 1; echo "código de salida: $?" ``` ```output INFO CPU al 2.5% WARNING Memoria al 21.4% (umbral 1%) INFO Disco / al 23.1% INFO Disco /boot al 12.0% código de salida: 1 ``` ## Paso 3: Analizar los logs de acceso de Nginx El segundo script lee un `access.log` de Nginx en el formato por defecto (`combined`) y resume qué códigos de estado se devuelven, qué IP hacen más peticiones y qué rutas dan más errores 404. Solo usa la biblioteca estándar y acepta también logs rotados comprimidos con gzip. Crea el archivo: ```bash sudo nano /opt/ops-scripts/nginx_summary.py ``` ```python #!/usr/bin/env python3 """Resume un access.log de Nginx en formato combined.""" import argparse import gzip import re from collections import Counter from pathlib import Path LINE_RE = re.compile( r'(?P\S+) \S+ \S+ \[[^\]]+\] ' r'"(?P[A-Z]+) (?P\S+) [^"]*" (?P\d{3}) ' ) def open_log(path: Path): if path.suffix == ".gz": return gzip.open(path, "rt", encoding="utf-8", errors="replace") return path.open(encoding="utf-8", errors="replace") def print_top(title: str, counter: Counter, top: int) -> None: print(f"\n{title}") for key, count in counter.most_common(top): print(f" {count:>8} {key}") def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("logfile", type=Path, nargs="?", default=Path("/var/log/nginx/access.log")) parser.add_argument("--top", type=int, default=10, help="filas por sección") args = parser.parse_args() statuses, ips, not_found = Counter(), Counter(), Counter() total = skipped = 0 with open_log(args.logfile) as log: for line in log: match = LINE_RE.match(line) if not match: skipped += 1 continue total += 1 statuses[match["status"]] += 1 ips[match["ip"]] += 1 if match["status"] == "404": not_found[match["path"]] += 1 print(f"Peticiones analizadas: {total} (líneas ignoradas: {skipped})") print_top("Códigos de estado", statuses, args.top) print_top("IP con más peticiones", ips, args.top) print_top("Rutas con más 404", not_found, args.top) if __name__ == "__main__": main() ``` Los logs de Nginx pertenecen al grupo `adm`, al que tu usuario ya pertenece en Ubuntu si fue el primero que se creó. Hazlo ejecutable y analiza el log actual: ```bash sudo chmod 755 /opt/ops-scripts/nginx_summary.py /opt/ops-scripts/nginx_summary.py --top 3 ``` ```output Peticiones analizadas: 18422 (líneas ignoradas: 12) Códigos de estado 15210 200 2104 404 871 301 IP con más peticiones 3120 203.0.113.24 1985 198.51.100.7 644 192.0.2.61 Rutas con más 404 412 /wp-login.php 305 /.env 118 /favicon.ico ``` Las líneas ignoradas suelen ser peticiones malformadas de escáneres, que Nginx registra con la petición `"-"`. Para analizar un log rotado, pásale la ruta: `/opt/ops-scripts/nginx_summary.py /var/log/nginx/access.log.2.gz`. ## Paso 4: Comprobar que tus URL responden El tercer script comprueba en paralelo una lista de URL y muestra el código de estado y el tiempo de respuesta de cada una. Usa `urllib` de la biblioteca estándar y un `ThreadPoolExecutor`, de modo que diez URL lentas tardan lo mismo que una. Crea el archivo: ```bash sudo nano /opt/ops-scripts/check_urls.py ``` ```python #!/usr/bin/env python3 """Comprueba que una lista de URL responde sin errores.""" import argparse import sys import time import urllib.error import urllib.request from concurrent.futures import ThreadPoolExecutor from pathlib import Path def check(url: str, timeout: float) -> tuple[str, int | None, str]: request = urllib.request.Request(url, headers={"User-Agent": "check-urls/1.0"}) start = time.monotonic() try: with urllib.request.urlopen(request, timeout=timeout) as response: status = response.status except urllib.error.HTTPError as exc: status = exc.code except (urllib.error.URLError, TimeoutError, OSError) as exc: return url, None, str(getattr(exc, "reason", exc)) elapsed_ms = (time.monotonic() - start) * 1000 return url, status, f"{elapsed_ms:.0f} ms" def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("urlfile", type=Path, help="archivo con una URL por línea") parser.add_argument("--timeout", type=float, default=10.0) args = parser.parse_args() urls = [ line.strip() for line in args.urlfile.read_text().splitlines() if line.strip() and not line.startswith("#") ] failed = 0 with ThreadPoolExecutor(max_workers=10) as pool: for url, status, detail in pool.map(lambda u: check(u, args.timeout), urls): ok = status is not None and status < 400 failed += not ok print(f"{'OK ' if ok else 'FAIL'} {status or '---'} {url} ({detail})") return 1 if failed else 0 if __name__ == "__main__": sys.exit(main()) ``` `urlopen` sigue las redirecciones, así que un 301 hacia HTTPS se muestra con el código final. Los errores HTTP (4xx y 5xx) llegan como `HTTPError` y se registran con su código; los fallos de red o DNS se muestran sin código. Crea la lista de URL. Sustituye `your_domain` por tu dominio: ```bash sudo nano /opt/ops-scripts/urls.txt ``` ```text # Una URL por línea https://your_domain/ https://your_domain/api/health https://www.cubepath.com/ ``` Hazlo ejecutable y pruébalo: ```bash sudo chmod 755 /opt/ops-scripts/check_urls.py /opt/ops-scripts/check_urls.py /opt/ops-scripts/urls.txt ``` ```output OK 200 https://your_domain/ (84 ms) FAIL 404 https://your_domain/api/health (61 ms) OK 200 https://www.cubepath.com/ (143 ms) ``` ## Paso 5: Programar la comprobación de recursos con systemd Un temporizador de systemd ejecutará `check_resources.py` cada cinco minutos. La salida irá al journal y, cuando se supere un umbral, el servicio quedará en estado `failed`. Crea la unidad de servicio: ```bash sudo nano /etc/systemd/system/check-resources.service ``` ```ini [Unit] Description=Comprobar CPU, memoria y disco [Service] Type=oneshot ExecStart=/opt/ops-scripts/check_resources.py --cpu 90 --mem 90 --disk 85 DynamicUser=yes ``` `DynamicUser=yes` ejecuta el script con un usuario temporal sin privilegios, suficiente porque solo lee métricas del sistema. Crea el temporizador: ```bash sudo nano /etc/systemd/system/check-resources.timer ``` ```ini [Unit] Description=Comprobar recursos cada 5 minutos [Timer] OnCalendar=*:0/5 [Install] WantedBy=timers.target ``` Recarga systemd, activa el temporizador y lanza una ejecución manual para comprobarlo: ```bash sudo systemctl daemon-reload sudo systemctl enable --now check-resources.timer sudo systemctl start check-resources.service journalctl -u check-resources.service -n 6 --no-pager ``` ```output sep 25 11:05:12 servidor systemd[1]: Starting check-resources.service - Comprobar CPU, memoria y disco... sep 25 11:05:13 servidor check_resources.py[6012]: INFO CPU al 2.0% sep 25 11:05:13 servidor check_resources.py[6012]: INFO Memoria al 21.6% sep 25 11:05:13 servidor check_resources.py[6012]: INFO Disco / al 23.1% sep 25 11:05:13 servidor systemd[1]: check-resources.service: Deactivated successfully. sep 25 11:05:13 servidor systemd[1]: Finished check-resources.service - Comprobar CPU, memoria y disco. ``` Para ver solo los avisos de los últimos días, filtra por prioridad: `journalctl -u check-resources.service -p warning --since "2 days ago"`. ## Solución de problemas **`error: externally-managed-environment` al usar `pip`**: estás instalando en el Python del sistema. Usa siempre el `pip` del entorno virtual (`/opt/ops-scripts/venv/bin/pip`). Si solo necesitas una biblioteca empaquetada por Ubuntu, también puedes instalarla con `apt`, por ejemplo `sudo apt install python3-psutil`. **`ModuleNotFoundError: No module named 'psutil'`**: el script se está ejecutando con `/usr/bin/python3` en lugar del Python del entorno virtual. Revisa el shebang o ejecútalo con `/opt/ops-scripts/venv/bin/python script.py`. **`PermissionError` al leer `/var/log/nginx/access.log`**: tu usuario no está en el grupo `adm`. Añádelo con `sudo usermod -aG adm $USER` y abre una sesión nueva. **El entorno virtual deja de funcionar tras actualizar Ubuntu**: los entornos virtuales dependen de la versión exacta de Python. Tras cambiar de versión mayor, recréalo con `sudo python3 -m venv --clear /opt/ops-scripts/venv` y reinstala las dependencias. ## Conclusión Has creado un entorno virtual aislado del Python del sistema, tres scripts de administración (recursos con `psutil`, análisis de logs de Nginx y comprobación de URL) y un temporizador de systemd que vigila los recursos cada cinco minutos. Como siguientes pasos puedes: - Guardar las dependencias en un `requirements.txt` y versionar `/opt/ops-scripts` en Git para desplegarlo igual en todos tus servidores. - Programar `check_urls.py` con otro temporizador y enviar los fallos a un canal de chat mediante un webhook. - Usar Fabric o Ansible cuando necesites ejecutar estas comprobaciones en varios servidores a la vez.