Hasta ahora todo lo que has manejado estaba quieto: archivos, permisos, texto. Esta lección cambia de objeto y se ocupa de lo que está vivo. Un proceso es un programa en ejecución, con su propia memoria, sus descriptores de archivo abiertos, su identidad y su lugar en un árbol genealógico que arranca en systemd. Entenderlos es lo que te permite responder a la pregunta que antes o después te va a hacer Marta: «¿por qué el servidor va lento?».
En la lección anterior descubriste que el 65 % de los errores de acceso.log se concentran en la franja de las 03:00, y que todo apunta a que algo compite por las conexiones de la base de datos durante la ventana nocturna. Aquí tienes las herramientas para averiguar qué es ese algo, y para actuar sobre ello sin tirar el servicio abajo.
Contenido
- Qué es un proceso
- El árbol de procesos
- Ciclo de vida: fork, exec, wait, exit
- Zombis y huérfanos
- Estados de proceso
psde verdadtopyhtop: leer la cabecerapgrepypkill- Señales
- Prioridades:
nice,reniceeionice - Control de trabajos en la sesión interactiva
lsofyfuser- Caso Tramontana: la app colgada de madrugada
- Qué es un proceso
Cada proceso tiene una ficha en el kernel con, entre otras cosas:
| Atributo | Qué es |
|---|---|
| PID | identificador único |
| PPID | PID del proceso padre |
| UID/GID efectivos | identidad con la que se comprueban los permisos |
| Estado | R, S, D, T o Z (apartado 5) |
Prioridad (PRI) y nice (NI) |
cuánta CPU le toca |
| VSZ / RSS | memoria virtual reservada / memoria física realmente usada |
| Directorio de trabajo | el cwd con el que resuelve rutas relativas |
| Descriptores abiertos | los que viste en /proc/<pid>/fd en 03-04 |
La distinción VSZ frente a RSS es la que más malentendidos causa: VSZ incluye memoria reservada pero nunca tocada y bibliotecas compartidas, así que sumar los VSZ de todos los procesos da un número muy superior a la RAM instalada sin que eso signifique nada. El número que importa es RSS.
- El árbol de procesos
Todo proceso desciende de otro. La raíz es systemd, con PID 1, el primer proceso que arranca el kernel.
operador@srv-tramontana:~$ pstree -p | head -8
systemd(1)─┬─cron(742)
├─dbus-daemon(701)
├─ejecutable(1284)─┬─{ejecutable}(1285)
│ ├─{ejecutable}(1286)
│ └─{ejecutable}(1287)
├─sshd(889)───sshd(1502)───sshd(1508)───bash(1509)───pstree(1620)
└─systemd-journald(412)Se lee mucho. ejecutable(1284) es la aplicación de Tramontana con tres hilos (entre llaves, no son procesos independientes). La cadena de sshd muestra tu propia conexión: el demonio principal, el proceso de la conexión, el de la sesión ya autenticada, tu bash y el pstree que acabas de lanzar como hijo suyo. Ahí está la herencia del entorno de 03-01, hecha diagrama.
pstree -ps 1284 muestra los ancestros de un PID concreto y devuelve systemd(1)───ejecutable(1284): la app cuelga directamente de systemd, no de una sesión de usuario. Eso significa que sobrevive a que cierres el SSH, y es la diferencia entre un servicio y un programa lanzado a mano.
- Ciclo de vida: fork, exec, wait, exit
flowchart LR
P["Padre (bash)"] -->|"fork()"| C["Hijo: copia del padre<br/>PID nuevo, mismo código"]
C -->|"exec()"| N["El hijo se reemplaza<br/>por el nuevo programa"]
N -->|"exit(código)"| Z["Zombi: solo queda<br/>el código de salida"]
P -->|"wait()"| Z
Z --> F["Liberado de la tabla<br/>de procesos"]
Cuando escribes ls en Bash ocurre esto: Bash hace fork(), que crea un hijo idéntico a él; el hijo llama a exec(), que sustituye su contenido por el binario de ls conservando el PID y los descriptores abiertos —ahí es donde encajan las redirecciones que Bash preparó antes—; ls termina llamando a exit(0); y Bash, que estaba en wait(), recoge ese código de salida, que es lo que luego lees en $?.
Esa separación entre fork y exec es la que hace posible la redirección tal como la estudiaste: hay un momento, entre las dos llamadas, en que el hijo ya existe y todavía no es el programa nuevo, y es ahí donde el shell reconfigura los descriptores.
- Zombis y huérfanos
Son dos situaciones opuestas y conviene no confundirlas.
Un zombi (estado Z) es un proceso que ya ha terminado pero cuyo padre no ha llamado a wait(). No consume CPU ni memoria: solo ocupa una entrada en la tabla de procesos con su código de salida, esperando a que alguien lo recoja.
A un zombi no se le puede matar. Ya está muerto; kill -9 sobre él no hace nada, porque las señales las reciben procesos vivos. La única forma de eliminarlo es que su padre lo recoja o que el padre muera. Un puñado de zombis es irrelevante; miles indican un padre mal programado, y la acción correcta es reiniciar al padre.
Para buscarlos: ps -eo stat,ppid,pid,comm | awk '$1 ~ /^Z/'. En srv-tramontana no devuelve nada.
Un huérfano es lo contrario: un proceso vivo cuyo padre ha muerto. No es un problema. El kernel lo reasigna a systemd (PPID pasa a 1), que sí llama a wait() correctamente. Es el mecanismo en el que se apoyan nohup y los demonios.
- Estados de proceso
| Estado | Nombre | Significa |
|---|---|---|
R |
Running / runnable | ejecutándose o listo para hacerlo |
S |
Interruptible sleep | esperando algo (E/S, red, un temporizador); la mayoría |
D |
Uninterruptible sleep | esperando E/S de disco, no acepta señales |
T |
Stopped | detenido por Ctrl+Z o SIGSTOP |
Z |
Zombie | terminado, pendiente de recoger |
Modificadores que verás pegados: s líder de sesión, l multihilo, + en primer plano, < prioridad alta, N prioridad baja.
Por qué un proceso en D no se puede matar. Está bloqueado dentro del kernel esperando que se complete una operación de disco o de red, y el kernel no le entrega señales hasta que esa operación termine. kill -9 se queda encolado: la señal se entregará cuando el proceso vuelva a estado S o R. Si un proceso lleva minutos en D, el problema no es el proceso: es el almacenamiento —un disco fallando, un montaje NFS caído— y ahí es donde hay que mirar.
ps de verdad
ps de verdadps tiene dos sintaxis históricas que conviven: BSD (sin guion) y UNIX (con guion). Las dos combinaciones que se usan:
operador@srv-tramontana:~$ ps aux | head -3
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.4 168404 12856 ? Ss 08:31 0:03 /sbin/init
svc-tram+ 1284 2.1 12.4 982340 483920 ? Ssl 08:32 1:47 /opt/tramontana/app/ejecutable| Columna | Significa |
|---|---|
USER |
dueño efectivo (truncado a 8 caracteres: svc-tram+) |
%CPU |
porcentaje de CPU promediado desde que arrancó, no instantáneo |
%MEM |
porcentaje de RAM física |
VSZ / RSS |
memoria virtual / residente, en KiB |
TTY |
terminal asociado; ? significa que no tiene, típico de un servicio |
STAT |
estado más modificadores |
START / TIME |
hora de arranque / CPU total consumida |
Ojo con %CPU: es una media desde el arranque del proceso. Un proceso que devoró la CPU hace ocho horas y ahora duerme puede seguir mostrando un porcentaje alto. Para saber qué consume ahora, usa top.
ps -ef da la vista UNIX, con PPID explícito, que es lo que quieres para seguir relaciones padre-hijo. Y ps -o construye la salida a medida:
operador@srv-tramontana:~$ ps -eo pid,ppid,user,ni,stat,rss,etime,cmd --sort=-rss | head -4
PID PPID USER NI STAT RSS ELAPSED CMD
1284 1 svc-tram 0 Ssl 483920 04:12:33 /opt/tramontana/app/ejecutable
889 1 root 0 Ss 12404 04:13:05 sshd: /usr/sbin/sshd -D
412 1 root 0 Ss 11208 04:13:11 systemd-journald--sort=-rss ordena por memoria residente descendente (el - invierte). etime da el tiempo transcurrido desde el arranque, mucho más útil que la hora absoluta cuando estás diagnosticando.
Filtros: -u operador por usuario, -C ejecutable por nombre de comando, -p 1284 por PID, --forest para ver la jerarquía en la propia salida de ps.
top y htop: leer la cabecera
top y htop: leer la cabeceratop - 12:44:18 up 4:13, 2 users, load average: 3.42, 1.87, 0.94 Tareas: 132 total, 2 ejecutar, 130 hibernar, 0 parar, 0 zombie %Cpu(s): 12,3 us, 4,1 sy, 0,0 ni, 18,2 id, 64,8 wa, 0,3 hi, 0,3 si, 0,0 st MiB Mem : 3844,0 total, 198,4 free, 2914,2 used, 731,4 buff/cache MiB Swap: 2048,0 total, 1620,0 free, 428,0 used. 612,8 avail Mem
El load average
Los tres números son la media de procesos en estado R o D durante 1, 5 y 15 minutos. Dos consecuencias que casi nadie tiene claras:
- No es un porcentaje. Hay que compararlo con el número de núcleos: en la VM de
srv-tramontana, con 2 vCPU, un load de 2,00 es plena ocupación y 3,42 significa que hay más trabajo del que la máquina puede atender. - Incluye los procesos en
D, es decir, los que esperan disco. Un load de 3,42 con la CPU casi ociosa no indica falta de CPU: indica que hay procesos atascados esperando E/S.
Y la comparación entre los tres números da la tendencia: 3.42, 1.87, 0.94 es una curva ascendente. El problema está empeorando ahora mismo, no remitiendo.
El desglose de CPU
| Sigla | Significa |
|---|---|
us |
tiempo en código de usuario |
sy |
tiempo en el kernel |
ni |
procesos con nice modificado |
id |
ocioso |
wa |
esperando E/S: la CPU está libre pero no puede avanzar |
hi / si |
interrupciones hardware / software |
st |
steal: CPU que el hipervisor dio a otra VM |
Ese 64,8 wa es el dato de la cabecera de arriba. La CPU no está saturada: está esperando al disco o a la base de datos. Perseguir un proceso que consume CPU sería buscar en el sitio equivocado. Y st alto en una máquina virtual significa que el problema no está en tu VM sino en el anfitrión, algo que conviene saber antes de optimizar código.
Sobre memoria: buff/cache no es memoria perdida, es caché de disco que el kernel libera en cuanto alguien la necesita. La cifra que importa es avail Mem. Ver poca memoria libre en Linux es normal y deseable.
Teclas útiles en top: M ordena por memoria, P por CPU, 1 desglosa por núcleo, k envía una señal, u filtra por usuario, c muestra la línea de comandos completa. htop hace lo mismo con colores, ratón y árbol con F5; se instala aparte pero merece la pena.
pgrep y pkill
pgrep y pkilloperador@srv-tramontana:~$ pgrep -a ejecutable
1284 /opt/tramontana/app/ejecutable --config /etc/tramontana/app.conf-a muestra la línea de comandos, -c cuenta, -u filtra por usuario, -f casa contra la línea completa y no solo el nombre, -x exige coincidencia exacta, -n/-o el más nuevo / el más antiguo.
Por qué son mejores que ps | grep | awk | kill: esa tubería tiene dos defectos. El primero es que el propio grep aparece en la lista y puedes acabar matando un PID que ya no existe o, peor, uno reciclado. El segundo es que una coincidencia parcial se lleva por delante procesos que no querías. pgrep consulta directamente /proc y no se incluye a sí mismo.
Antes de pkill, ejecuta siempre el pgrep equivalente. Es la misma convención que ls antes de rm:
operador@srv-tramontana:~$ pgrep -a -f 'ejecutable --config'
1284 /opt/tramontana/app/ejecutable --config /etc/tramontana/app.conf
operador@srv-tramontana:~$ pkill -f 'ejecutable --config'
- Señales
Una señal es una notificación asíncrona que el kernel entrega a un proceso.
| Nº | Nombre | Efecto por defecto | ¿Se puede capturar? |
|---|---|---|---|
| 1 | SIGHUP | terminar; por convención, recargar configuración | sí |
| 2 | SIGINT | interrumpir (Ctrl+C) |
sí |
| 3 | SIGQUIT | terminar y volcar core (Ctrl+\) |
sí |
| 9 | SIGKILL | matar de inmediato | no |
| 15 | SIGTERM | terminar ordenadamente (por defecto) | sí |
| 18/19 | SIGCONT / SIGSTOP | reanudar / detener | CONT sí, STOP no |
| 10/12 | SIGUSR1 / SIGUSR2 | libres, definidas por la aplicación | sí |
kill -l lista las 64 señales del sistema. kill 1284 envía SIGTERM. Para otra: kill -TERM 1284, kill -15 1284 o kill -s SIGTERM 1284, todas equivalentes. killall nombre actúa por nombre en lugar de por PID —con el riesgo evidente de alcanzar más de lo que pretendías.
La regla profesional: SIGTERM, esperar, y solo entonces SIGKILL
SIGTERM es una petición: el proceso la recibe, ejecuta su rutina de cierre y termina cuando ha acabado. SIGKILL es una ejecución sumaria: el kernel destruye el proceso sin avisarle. El proceso no se entera y por tanto no puede hacer nada.
Qué se pierde con -9:
- Los datos en búferes que aún no se habían escrito a disco.
- Las transacciones abiertas en la base de datos, que quedan sin confirmar y bloqueando filas hasta que expiren.
- Las peticiones HTTP en curso, que se cortan en seco: el cliente ve un error.
- Los ficheros temporales y los ficheros de bloqueo, que quedan huérfanos y pueden impedir el siguiente arranque.
- La oportunidad de que el proceso escriba en el log por qué se cerró.
El procedimiento correcto:
operador@srv-tramontana:~$ kill -TERM 1284
operador@srv-tramontana:~$ sleep 10; pgrep -c ejecutable
0Diez segundos es un margen razonable para una aplicación web; si el proceso ya no está, hemos terminado bien. Solo si sigue vivo tras ese plazo se recurre a kill -9, y entonces se documenta como incidente, porque significa que la rutina de cierre de la aplicación no funciona y eso es un fallo que hay que corregir.
SIGHUP merece una mención: por convención, muchos demonios lo interpretan como «relee tu configuración sin reiniciar». Cuando funciona, es la forma de aplicar un cambio en app.conf sin cortar ni una petición.
- Prioridades:
nice, renice e ionice
nice, renice e ioniceEl valor nice va de -20 (máxima prioridad) a 19 (mínima). El nombre viene de «ser amable»: cuanto más alto, más cede el proceso ante los demás. Un usuario normal solo puede subirlo —bajar su propia prioridad—; reducirlo exige root.
operador@srv-tramontana:~$ nice -n 15 tar -czf /srv/tramontana/backups/envios/datos.tar.gz /home/operador/datos
operador@srv-tramontana:~$ sudo renice -n 5 -p 1284
1284 (id de proceso) prioridad antigua 0, prioridad nueva 5
operador@srv-tramontana:~$ ionice -c 3 tar -czf /srv/tramontana/backups/envios/datos.tar.gz /home/operador/datosnice lanza un comando con prioridad modificada; renice cambia la de un proceso ya en marcha, también por usuario (-u) o por grupo. Y para la E/S, que en nuestro caso es el recurso escaso, está ionice. Su clase 3 es idle: la copia solo lee del disco cuando nadie más lo necesita. Con un wa del 64,8 %, ionice resuelve más que nice: bajar la prioridad de CPU de un proceso que no usa CPU no sirve de nada. Diagnosticar antes de actuar también significa elegir la palanca correcta.
- Control de trabajos en la sesión interactiva
| Acción | Cómo |
|---|---|
| Lanzar en segundo plano | comando & |
| Listar los trabajos de la sesión | jobs -l |
| Traer al primer plano | fg %1 |
| Continuar en segundo plano | bg %1 |
| Suspender el actual | Ctrl+Z (envía SIGSTOP) |
| Desligar de la sesión | disown -h %1 |
| Inmune al cierre de sesión | nohup comando & |
operador@srv-tramontana:~$ tar -czf /tmp/copia.tar.gz /opt/tramontana/releases/3.1.0 &
[1] 2041
operador@srv-tramontana:~$ jobs -l
[1]+ 2041 Ejecutando tar -czf /tmp/copia.tar.gz /opt/tramontana/releases/3.1.0 &Al cerrar la sesión, el shell envía SIGHUP a sus trabajos. nohup los inmuniza y redirige la salida a nohup.out; disown -h consigue lo mismo con un trabajo ya lanzado.
Y ahora la advertencia importante: nada de esto sirve para un servicio de verdad. Un nohup ./ejecutable & no se reinicia si el proceso muere, no arranca al encender el servidor, no tiene control de recursos, no gestiona sus logs y no puede pararse de forma ordenada por nadie que no seas tú. El control de trabajos es para tareas largas de tu sesión —una copia, una compilación—, no para producción. Los servicios se gestionan con systemd, y eso es la lección 05-05.
lsof y fuser
lsof y fuserlsof («list open files») responde a la pregunta «¿quién tiene esto abierto?». Como en Linux casi todo es un archivo, sirve también para sockets y puertos.
Caso «no puedo desmontar».
operador@srv-tramontana:~$ sudo umount /srv/tramontana/backups
umount: /srv/tramontana/backups: destino ocupado.
operador@srv-tramontana:~$ sudo lsof +D /srv/tramontana/backups
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 1509 opera cwd DIR 8,1 4096 262148 /srv/tramontana/backups/envios
tar 2041 opera 3w REG 8,1 10485760 262203 /srv/tramontana/backups/envios/datos.tar.gzDos culpables: un bash cuyo directorio de trabajo (cwd) está dentro —basta un cd fuera— y un tar escribiendo (3w, descriptor 3 en modo escritura). Con eso ya sabes qué esperar y a quién avisar, en lugar de forzar el desmontaje.
Caso «el puerto 8080 está ocupado».
operador@srv-tramontana:~$ sudo lsof -i :8080
COMMAND PID USER FD TYPE DEVICE NODE NAME
ejecutable 1284 svc-tramontana 7u IPv4 1284091 TCP *:http-alt (LISTEN)El PID 1284 tiene el puerto en escucha. Es la respuesta directa a «no puedo arrancar la aplicación nueva porque el puerto está en uso»: la vieja sigue viva.
fuser es más escueto y muy práctico para actuar: fuser -v /ruta lista los procesos, fuser -k /ruta los mata (con cuidado) y fuser -k -TERM 8080/tcp envía SIGTERM a quien ocupe ese puerto.
/proc/<pid>/ es la fuente de verdad de la que beben todas estas herramientas: cmdline la orden exacta, environ el entorno con el que arrancó, cwd y exe como enlaces simbólicos, fd/ los descriptores, status un resumen legible, limits los límites de recursos. Cuando una herramienta te dé un dato dudoso, ve al fichero.
- Caso Tramontana: la app colgada de madrugada
03:05. La aplicación no responde. Marta te escribe. Procedimiento, con la conclusión de 03-05 como hipótesis de partida.
Paso 1: ¿está viva y en qué estado?
operador@srv-tramontana:~$ ps -o pid,stat,ni,rss,etime,pcpu -p $(pgrep -x ejecutable)
PID STAT NI RSS ELAPSED %CPU
1284 Dsl 0 483920 04:12:33 2.1Estado D: no está colgada en un bucle, está bloqueada esperando E/S. Con solo 2,1 % de CPU, no es un problema de cálculo. Un kill -9 aquí ni siquiera surtiría efecto inmediato.
Paso 2: ¿qué dice la carga? El load average: 3.42, 1.87, 0.94 con 64,8 wa de la cabecera del apartado 7 confirma lo mismo desde otro ángulo: cola creciente y espera de E/S, no falta de CPU.
Paso 3: ¿quién compite?
operador@srv-tramontana:~$ ps -eo pid,user,stat,pcpu,etime,cmd --sort=-pcpu | head -4
PID USER STAT %CPU ELAPSED CMD
2210 operador D 38.4 05:12 tar -czf /srv/tramontana/backups/envios/nocturna.tar.gz /opt/tramontana
1284 svc-tram Dsl 2.1 04:12:33 /opt/tramontana/app/ejecutableAhí está. Una copia de seguridad nocturna lleva cinco minutos leyendo /opt/tramontana entero —incluidos los cuatro releases con sus ejecutable de 48 MB— y satura el disco de la VM. La aplicación, que necesita el disco para atender consultas, se queda esperando.
Paso 4: aliviar sin matar nada. La copia es legítima; lo que está mal es su prioridad.
operador@srv-tramontana:~$ sudo ionice -c 3 -p 2210
operador@srv-tramontana:~$ sudo renice -n 19 -p 2210
2210 (id de proceso) prioridad antigua 0, prioridad nueva 19Ambos cambios se aplican al proceso en marcha, sin interrumpirlo. Dos minutos después el wa baja y la aplicación vuelve a responder.
Paso 5: si hubiera hecho falta reiniciar la app, el procedimiento habría sido kill -TERM $(pgrep -x ejecutable), esperar a que las peticiones en curso terminen, verificar con pgrep que ya no está y solo entonces arrancarla de nuevo. Nunca kill -9 de entrada: cortaría las reservas que estuvieran confirmándose en ese momento, y una reserva a medias es un problema con cliente delante.
Informe para Marta. La caída no fue un fallo de la aplicación sino una competencia por el disco: la copia de seguridad nocturna se ejecutaba con la misma prioridad que el servicio y lo dejaba sin acceso al almacenamiento. La medida aplicada —darle a la copia la prioridad de E/S más baja— protege frente a que vuelva a bloquear el servicio, y no protege frente a un aumento general de la carga: si crece el número de reservas, harán falta más recursos o separar la base de datos. Además, la copia incluye los cuatro releases enteros cuando bastaría con el activo, lo que la hace innecesariamente pesada. Pendiente: ajustar el alcance de la copia y su horario, que es exactamente lo que veremos en la lección siguiente.
Errores Comunes y Consejos
- Usar
kill -9como primera opción. Es la última. SIGTERM, esperar, verificar. - Intentar matar un zombi. Ya está muerto. Actúa sobre el padre.
- Insistir con un proceso en
D. No recibirá la señal hasta que la E/S termine. Investiga el almacenamiento. - Leer
%CPUdepscomo valor instantáneo. Es una media desde el arranque. Usatop. - Alarmarse por poca memoria libre.
buff/cachees caché reutilizable. Miraavail Mem. - Comparar el load average con 100. Compáralo con el número de núcleos, y recuerda que incluye la espera de E/S.
ps | grep | killsobre un nombre corto. El propiogrepsale en la lista y una coincidencia parcial mata de más. Usapgrep -ay luegopkill.- Dejar un servicio con
nohup &. No sobrevive a un reinicio ni se recupera solo. - Consejo: ante «el servidor va lento», mira siempre primero el
wadetop. Si es alto, el cuello de botella es el disco y la mitad de las hipótesis se descartan de golpe. - Consejo:
ps -eoacepta cualquier combinación de columnas; guárdate un alias con las tuyas en~/.bashrc, como aprendiste en 03-01.
Ejercicios
Ejercicio 1. Averigua con qué línea de comandos exacta, con qué directorio de trabajo y con qué variables de entorno arrancó el proceso de Tramontana, sin usar ps. Explica qué te dice cada dato.
Ejercicio 2. Lanza una tarea larga en segundo plano, suspéndela, reanúdala en segundo plano, bájale la prioridad de CPU y de E/S, y desligala de tu sesión para que sobreviva a un cierre de SSH. Muestra la comprobación de cada paso.
Ejercicio 3. Simula el diagnóstico de «el puerto 8080 no acepta la nueva versión»: identifica qué proceso lo ocupa, de quién es, desde cuándo, y detén ese proceso de forma ordenada verificando que ha terminado antes de dar el paso por bueno.
Soluciones
Solución 1.
operador@srv-tramontana:~$ PID=$(pgrep -x ejecutable)
operador@srv-tramontana:~$ tr '\0' ' ' < /proc/$PID/cmdline; echo
/opt/tramontana/app/ejecutable --config /etc/tramontana/app.conf
operador@srv-tramontana:~$ sudo ls -l /proc/$PID/cwd /proc/$PID/exe
lrwxrwxrwx 1 svc-tramontana tramontana 0 ago 18 12:52 /proc/1284/cwd -> /opt/tramontana/app
lrwxrwxrwx 1 svc-tramontana tramontana 0 ago 18 12:52 /proc/1284/exe -> /opt/tramontana/releases/3.2.1/ejecutable
operador@srv-tramontana:~$ sudo tr '\0' '\n' < /proc/$PID/environ | grep -E 'PATH|LANG'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8cmdline guarda los argumentos separados por bytes nulos, de ahí el tr. Confirma que la app lee /etc/tramontana/app.conf, así que un cambio ahí le afecta. El cwd es /opt/tramontana/app, el enlace simbólico: significa que sus rutas relativas se resuelven a través de él.
El dato revelador es exe, que apunta a /opt/tramontana/releases/3.2.1/ejecutable, la ruta ya resuelta. Es la prueba de que el despliegue atómico funciona como esperábamos: el kernel fijó el binario real al arrancar, de modo que cambiar el enlace app a otro release no afecta al proceso en marcha. Sabe qué versión está sirviendo de verdad, no cuál dice el enlace ahora mismo. Y environ muestra un PATH mínimo y LANG=C.UTF-8: el servicio no hereda tu entorno interactivo, cosa que ya sabías desde 03-01 y que volverá a aparecer en la lección siguiente.
Solución 2.
operador@srv-tramontana:~$ tar -czf /tmp/rel.tar.gz /opt/tramontana/releases &
[1] 2311
operador@srv-tramontana:~$ fg %1
tar -czf /tmp/rel.tar.gz /opt/tramontana/releases
^Z
[1]+ Detenido tar -czf /tmp/rel.tar.gz /opt/tramontana/releases
operador@srv-tramontana:~$ ps -o pid,stat -p 2311
PID STAT
2311 T
operador@srv-tramontana:~$ bg %1
[1]+ tar -czf /tmp/rel.tar.gz /opt/tramontana/releases &
operador@srv-tramontana:~$ renice -n 15 -p 2311 && ionice -c 3 -p 2311
2311 (id de proceso) prioridad antigua 0, prioridad nueva 15
operador@srv-tramontana:~$ disown -h %1
operador@srv-tramontana:~$ ps -o pid,ni,stat -p 2311
PID NI STAT
2311 15 SNCada comprobación aporta algo: STAT en T confirma que Ctrl+Z lo detuvo de verdad (SIGSTOP), y tras bg y los ajustes, SN indica que duerme con prioridad baja (N) y NI es 15. renice a 15 no ha necesitado sudo porque subir el propio nice está permitido a cualquier usuario; bajarlo requeriría privilegios. disown -h no lo saca de la lista de trabajos, pero marca que no debe recibir SIGHUP al cerrar la sesión. Si supiéramos de antemano que la tarea es larga, lo limpio habría sido nohup ... & desde el principio.
Solución 3.
operador@srv-tramontana:~$ sudo lsof -i :8080 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE NODE NAME
ejecutable 1284 svc-tramontana 7u IPv4 1284091 TCP *:http-alt (LISTEN)
operador@srv-tramontana:~$ ps -o pid,user,etime,cmd -p 1284
PID USER ELAPSED CMD
1284 svc-tram 04:31:12 /opt/tramontana/app/ejecutable --config /etc/tramontana/app.confYa tenemos las tres respuestas: lo ocupa ejecutable, pertenece a svc-tramontana y lleva cuatro horas y media en marcha. -sTCP:LISTEN filtra los sockets en escucha y descarta las conexiones establecidas, que serían ruido.
operador@srv-tramontana:~$ sudo kill -TERM 1284
operador@srv-tramontana:~$ sleep 10; pgrep -x ejecutable || echo 'terminado correctamente'
terminado correctamente
operador@srv-tramontana:~$ sudo lsof -i :8080
operador@srv-tramontana:~$ echo "puerto libre: $?"
puerto libre: 1Dos verificaciones distintas y ambas necesarias. pgrep confirma que el proceso ya no existe; lsof sin salida confirma que el puerto está libre, que no es exactamente lo mismo: un socket puede quedar unos segundos en TIME_WAIT tras cerrarse el proceso, e intentar arrancar la versión nueva en ese hueco fallaría por un motivo distinto y desconcertante. Comprobar el recurso que de verdad necesitas, y no solo el proceso, es lo que evita ese diagnóstico erróneo. El estado TIME_WAIT y el resto de estados TCP los verás en 03-08.
Conclusión
Has dejado de mirar archivos para mirar lo que se está ejecutando, y con eso has resuelto una incidencia real de principio a fin.
- Sabes qué guarda el kernel de cada proceso —PID, PPID, UID efectivo, estado, prioridad, VSZ y RSS— y que el número de memoria que importa es RSS.
- Recorres el árbol de procesos con
pstreedesdesystemd(PID 1) y entiendes el ciclo fork / exec / wait / exit, que es donde encaja la redirección de 03-04. - Distingues zombi de huérfano: al zombi no se le puede matar y se actúa sobre el padre; el huérfano lo adopta
systemdy no es problema. - Conoces los estados R, S, D, T, Z y por qué un proceso en
Dno responde a ninguna señal, ni siquiera a-9. - Lees
ps auxyps -efcolumna a columna, construyes vistas conps -eo ... --sort=, y sabes que el%CPUdepses una media desde el arranque. - Interpretas la cabecera de
top: el load average frente al número de núcleos y su tendencia, el desgloseus/sy/ni/id/wa/hi/si/st—conwacomo la señal de que el cuello de botella es el disco— y quebuff/cacheno es memoria perdida. - Usas
pgrep/pkillen vez deps | grep | kill, comprobando siempre antes de matar, y aplicas la regla profesional de las señales: SIGTERM, esperar, verificar y solo entonces SIGKILL, sabiendo qué se pierde con-9. - Ajustas prioridades con
nice,reniceeionice, eligiendo la palanca según dónde esté el cuello de botella. - Controlas trabajos con
&,jobs,fg,bg,Ctrl+Z,disownynohup, y tienes claro que nada de eso vale para un servicio de producción. - Respondes con
lsofyfusera «¿quién tiene esto abierto?» y «¿quién ocupa este puerto?», y acudes a/proc/<pid>/cuando quieres la verdad sin intermediarios.
El diagnóstico ha dejado una tarea pendiente muy concreta: la copia nocturna se ejecuta a la hora equivocada, con la prioridad equivocada y sobre más datos de los necesarios. Arreglarla es el tema de la siguiente lección. Programación de Tareas con Cron te enseñará la sintaxis de los cinco campos hasta sus casos más retorcidos, los tres fallos clásicos que causan el 90 % de las incidencias —el PATH mínimo, el correo de salida que nadie lee y la zona horaria—, cómo depurar una tarea que «funciona a mano y en cron no», y cómo evitar con flock que dos ejecuciones se solapen, que es precisamente lo que puede haber pasado esta madrugada.
Curso de Linux: De Principiante a Administrador de Sistemas
Módulo 1: Introducción a Linux
- ¿Qué es Linux?
- Historia de Linux
- Distribuciones de Linux
- Instalando Linux
- Primer Contacto con el Sistema
- Estructura del Sistema de Archivos de Linux
Módulo 2: Comandos Básicos de Linux
- Introducción a la Línea de Comandos
- Obtener Ayuda y Documentación del Sistema
- Navegando el Sistema de Archivos
- Operaciones con Archivos y Directorios
- Visualización y Edición de Archivos
- Enlaces Duros y Simbólicos
- Permisos y Propiedad de Archivos
Módulo 3: Habilidades Avanzadas en la Línea de Comandos
- El Entorno del Shell: Variables, Alias e Historial
- Uso de Comodines y Expresiones Regulares
- Búsqueda de Archivos y Contenido: find, locate y grep
- Tuberías y Redirección
- Procesamiento de Texto: cut, sort, uniq, sed y awk
- Gestión de Procesos
- Programación de Tareas con Cron
- Comandos de Redes
Módulo 4: Scripting en Shell
- Introducción al Scripting en Shell
- Variables y Tipos de Datos
- Entrada, Salida y Argumentos de un Script
- Estructuras de Control
- Funciones y Librerías
- Depuración y Manejo de Errores
- Scripts de Producción: Buenas Prácticas
Módulo 5: Administración del Sistema
- Gestión de Usuarios y Grupos
- sudo y Permisos Especiales
- Gestión de Paquetes
- Gestión de Discos
- systemd y la Gestión de Servicios
- Registros del Sistema: journald y syslog
- Monitoreo del Sistema y Optimización del Rendimiento
- Respaldo y Restauración
Módulo 6: Redes y Seguridad
- Configuración de Redes
- SSH y Acceso Remoto
- Firewall y Seguridad Perimetral
- Sistemas de Detección de Intrusos
- Gestión de Secretos y Certificados TLS
- Asegurando Sistemas Linux
Módulo 7: Temas Avanzados
- El Proceso de Arranque y la Recuperación del Sistema
- Diagnóstico Avanzado: strace, perf y eBPF
- Optimización del Kernel de Linux
- Virtualización con Linux
- Contenedores de Linux y Docker
- Automatización con Ansible
- Alta Disponibilidad y Balanceo de Carga
