Monitoreo de servidores con Bash Scripts y PHP

Camilo Herrera
winkhostingla
Published in
9 min readApr 25, 2023
Photo by Chris Yang on Unsplash

Estamos de vuelta!, estoy trabajando en un servicio para monitorear nuestros servidores y quiero compartir contigo, mi querido lector, una implementación básica que puedes usar si también planeas hacerlo.

Por qué requiero una solución personalizada para monitorear mis servidores?

Me gusta tu pregunta, tu pregunta es una de esas preguntas… buenas. En mi caso, porque quiero algo especializado para nuestros servidores y queremos lanzarlo como un producto a nuestros clientes.

Tus necesidades pueden ser otras pero te recomiendo explorar esta opción, a pesar de que existen cantidades de soluciones ya disponibles y mucho más avanzadas (teniendo presente que a mayor cantidad de características, la complejidad aumenta y los requisitos técnicos y de conocimiento también).

Los componentes básicos

En esta publicación cubriremos el proceso de creación del script Bash que capturará la información de interés de nuestro servidor y la enviará a un servidor central. También exploraremos un webservice encargado de recibir los datos de telemetría y también los guardará en un directorio.

No cubriremos la presentación de información, reportes o gráficos. Lo dejaremos a tu imaginación en esta oportunidad. (Tu tienes el poder para hacerlo muchacho!).

Vamos a iniciar con el script bash:

Requisitos

Nuestros servidores usan Linux, crearemos este script usando Ubuntu 22.04 LTS. El script va a monitorear la siguiente información:

  • Load y Uptime cada 10 segundos
  • Almacenamiento cada 24 horas
  • Uso de RAM cada 30 segundos
  • Top 10 de procesos que utilizan CPU cada 5 minutos

Los comandos a utilizar para capturar esta información serán los siguientes:

Load y Uptime

El load y uptime lo consultaremos de forma sencilla con el comando “uptime” desde la consola de comandos del servidor:

> uptime

09:32:48 up 296 days, 3:01, 2 users, load average: 1.92, 1.23, 0.97

Este comando muestra el tiempo que lleva operando el servidor desde el último reinicio, cantidad de usuarios activos y la carga promedio de procesamiento de los últimos 1, 5 y 15 minutos.

Almacenamiento

El almacenamiento puede consultarse de la siguiente forma con el comando “df -h”:

> df -h

Filesystem Size Used Avail Use% Mounted on
devtmpfs 384M 0 384M 0% /dev
tmpfs 403M 0 403M 0% /dev/shm
tmpfs 403M 42M 362M 11% /run
tmpfs 403M 0 403M 0% /sys/fs/cgroup
/dev/sda 25G 12G 12G 49% /
/dev/sdb 25G 4.2M 24G 1% /home2
/dev/loop0 915M 804K 867M 1% /tmp
tmpfs 81M 0 81M 0% /run/user/0

Al ejecutarlo se muestran los dispositivos de almacenamiento detectados con su respectivo punto de montaje, capacidad total y usada en Megabytes.

Uso de RAM

El uso de RAM puede consultarse de la siguiente forma con el comando “free -m”:

> free -m

total used free shared buff/cache available
Mem: 804 533 82 50 188 102
Swap: 0 0 0

El comando mostrará el uso de memoria RAM y Swap del servidor, memoria libre y cache

Top 10 de procesos que usan CPU

Este comando es más elaborado, requiere la combinación de tres comandos básicos para ordenar, filtrar y mostrar los 10 procesos con mayor uso de CPU así:

> ps -eo pcpu,pid,user,args | sort -k 1 -r | head -10

%CPU PID USER COMMAND
0.4 75 root [kswapd0]
0.3 3623591 mysql /usr/sbin/mysqld
0.1 3831215 root lfd - sleeping
0.1 1079 root /usr/libexec/platform-python -Es /usr/sbin/tuned -l -P
0.0 9 root [mm_percpu_wq]
0.0 839 root /usr/lib/systemd/systemd-logind
0.0 838 root /usr/sbin/NetworkManager --no-daemon
0.0 831 root [ext4-rsv-conver]
0.0 830 root [jbd2/loop0-8]

Primero usaremos el comando “ps” para presentar porcentaje de cpu, id de proceso, usuario y argumentos usados. A continuación ordenamos el listado por la primera columna con el comando “sort” y finalmente filtramos para dejar únicamente los 10 primeros registros con el comando “head”.

El archivo

Vamos a empezar por crear un archivo con nombre “servermon.sh”. Puedes usar el nombre que quieras, sólo es necesario tenerlo presente durante el proceso de creación. El archivo inicial quedará así:

servermon.sh (inicial)

#!/bin/bash

La primera línea indica el intérprete a usar, normalmente este valor no debe cambiarse. Ahora vamos con la captura inicial de información.

Captura de información

Para capturar el resultado de la ejecución de los comandos que vamos a usar, enviaremos la salida de consola a un archivo de texto así:

servermon.sh (captura de información)

#!/bin/bash

# Directorio de almacenamiento temporal
DIRTMP=/tmp

# uptime y load
uptime > $DIRTMP/uptime.log

# almacenamiento
df -h > $DIRTMP/storage.log

# uso de memoria
free -m > $DIRTMP/memoryusage.log

# top 10 cpu
ps -eo pcpu,pid,user,args | sort -k 1 -r | head -10 > $DIRTMP/top10cpu.log

También creamos una variable que contiene el directorio temporal para guardar los archivos capturados, puedes usar el directorio que prefieras, en este caso usaremos /tmp para almacenar los archivos con la información y se actualizan por cada envío de datos, no es necesario guardarlos indefinidamente.

Loop de captura

El loop de captura nos permitirá consultar la información de forma periódica, para cada tipo de consulta definiremos un intervalo personalizable ya que no es necesario (o por lo menos en este caso) consultar el almacenamiento cada 3 segundos por ejemplo. Pero si nos interesa consultar el load del servidor en intervalos más cortos.

Definamos los temporizadores para recopilar información así:

servermon.sh (loop y temporizadores)

#!/bin/bash

# Directorio de almacenamiento temporal
DIRTMP=/tmp

# Intervalo en segundos para reportar estado de almacenamiento
STORAGETIMER=3600
# Intervalo en segundos para reportar estado de RAM
RAMTIMER=60
# Intervalo en segundos para reportar top 10 de procesos con uso de CPU
CPUTOPTIMER=300
# Intervalo en segundos para reportar uptime y load del servidor
UPTIMETIMER=5

# Contadores de segundos para activar reporte de información.
STORAGETICKER=0
RAMTICKER=0
CPUTOPTICKER=0
UPTIMETICKER=0

# Nuestro ciclo sin fin.
while true
do

# Actualizamos los contadores de segundos para el reporte de información
((STORAGETICKER += 1))
((RAMTICKER += 1))
((CPUTOPTICKER += 1))
((UPTIMETICKER += 1))

# Si el contador llega al límite de tiempo establecido
if [ $STORAGETICKER == $STORAGETIMER ]; then
# Reiniciamos el contador
((STORAGETICKER = 0))
# Capturamos la información
df -h > $DIRTMP/storage.log
rm -f $DIRTMP/storage.log
fi

# Check RAM.
if [ $RAMTICKER == $RAMTIMER ]; then
((RAMTICKER = 0))
free -m > $DIRTMP/memoryusage.log
rm -f $DIRTMP/memoryusage.log
fi

# Check Top 10 más CPU
if [ $CPUTOPTICKER == $CPUTOPTIMER ]; then
((CPUTOPTICKER = 0))
ps -eo pcpu,pid,user,args | sort -k 1 -r | head -10 > $DIRTMP/top10cpu.log
rm -f $DIRTMP/top10cpu.log
fi

# Uptime y load
if [ $UPTIMETICKER == $UPTIMETIMER ]; then
((UPTIMETICKER = 0))
uptime > $DIRTMP/uptime.log
rm -f $DIRTMP/uptime.log
fi

sleep 1

done

Como puedes ver, al ejecutar este script, se capturará en un archivo plano periódicamente la información de CPU, RAM, Uptime/Load y Almacenamiento.

Al final de cada ciclo se borra el archivo creado, si solo quieres mantener los archivos para consultarlos posteriormente, puedes quitar o comentar la línea de borrado y asignarle una cadena de texto variable al nombre del archivo (la fecha y hora, por ejemplo) para mantener los archivos con datos históricos. Cómo en este caso planeamos enviar la información a un servidor central, no almacenaremos localmente archivos.

Ahora vamos a crear el servicio web con PHP para recibir la información y ajustaremos el script bash cuando llegue el momento para que envíe los archivos.

Script php para recibir información de telemetría

Para cumplir este objetivo, vamos a crear un script al estilo “hack and slash” como demostración, sencillo, rápido y con la única función de recibir los archivos y guardarlos.

Requisitos

Debes tener un entorno de pruebas compatible con PHP 8.x para crear este script, ten presente que nuestro script bash también debe tener acceso dentro de la red local o Internet a la URL del script PHP que vamos a crear para enviarle los archivos.

El archivo

en nuestro caso creamos un directorio /storeinfo/ en la raíz del directorio de nuestro servidor web y al archivo lo llamaremos index.php, también crearemos un archivo /files dentro del directorio, este será usado para guardar los archivos recibidos.

El archivo index.php se verá de la siguiente forma:

index.php

<?php

//Token de seguridad para autenticar el envío de información
$_TOKEN = "f66dc5a4-f668-40d6-9b31-7d5d7c947985";

//Ruta de almacenamiento de los archivos recibidos
$_SAVEPATH = __DIR__ . "/files";


//Valida si se recibe un token en la petición POST, en caso contrario informa que no hay uno y termina
if (!isset($_POST["token"])) {
echo "Token no encontrado, necesitas un token para que tu información sea guardada";
exit;
}

//Valida si el token recibido corresponde al esperado.
if ($_POST["token"] != $_TOKEN) {
echo "El token recibido no es válido o no existe. Verifica la información";
exit;
}

//Verifica si se envió un archivo en la petición al servidor.
if (!isset($_FILES)) {
echo "No se recibió un archivo, confirma que estés enviando el contenido de un archivo de texto usando POST";
exit;
}

//Genera el nombre completo con ruta de archivo a guardar en el servidor, agrega la fecha y hora de carga.
$_SAVEPATH = $_SAVEPATH . "/" . date("Ymdhis") . "_" . basename($_FILES['data']['name']);

//Intenta mover el archivo recibido a la ruta $_SAVEPATH, si falla se muestra un mensaje y finaliza el script
//de lo contrario se notifica que el guardado fue exitoso.
if (!move_uploaded_file($_FILES['data']['tmp_name'], $_SAVEPATH)) {
echo "No fue posible mover el archivo recibido a la ruta {$_SAVEPATH}";
exit;
} else {
echo "Archivo guardado en la ruta {$_SAVEPATH}";
exit;
}

Definimos un token a ser usado para validar que quien envía la información esté autorizado y un directorio de guardado.

Los archivos recibidos serán guardados con un formato de nombre “<yyymmddhhis>_<nombre original>” en el directorio /files.

Ahora que tenemos nuestro archivo php para recibir información, vamos a ajustar el script bash para que pueda enviarle datos. Para esto vamos a agregarle el comando curl como se muestra a continuación:

Comando cURL (POST)

curl -F "data=@$DIRTMP/storage.log" -F "token=$TOKEN" http://127.0.0.1/storeinfo/index.php

La intención del comando es enviar una petición de tipo POST agregando el contenido del archivo usando data=<ruta del archivo> y el token=<token para autenticación> a la ruta http://127.0.0.1/storeinfo/index.php

El parámetro -F le indica a cURL que debe tratar cada uno de los parámetros como parte de un formulario para ser enviado usando POST.

El script bash final se verá así:

servermon.sh (final)

# Directorio de almacenamiento temporal
DIRTMP=/tmp
# Token
TOKEN=f66dc5a4-f668-40d6-9b31-7d5d7c947985
# URL
URL=http://127.0.0.1/storeinfo/index.php

# Intervalo en segundos para reportar estado de almacenamiento
STORAGETIMER=3600
# Intervalo en segundos para reportar estado de RAM
RAMTIMER=60
# Intervalo en segundos para reportar top 10 de procesos con uso de CPU
CPUTOPTIMER=300
# Intervalo en segundos para reportar uptime y load del servidor
UPTIMETIMER=5

# Contadores de segundos para activar reporte de información.
STORAGETICKER=0
RAMTICKER=0
CPUTOPTICKER=0
UPTIMETICKER=0

# Nuestro ciclo sin fin.
while true
do

# Actualizamos los contadores de segundos para el reporte de información
((STORAGETICKER += 1))
((RAMTICKER += 1))
((CPUTOPTICKER += 1))
((UPTIMETICKER += 1))

# Si el contador llega al l{imite de tiempo establecido
if [ $STORAGETICKER == $STORAGETIMER ]; then
# Reiniciamos el contado
((STORAGETICKER = 0))
# Capturamos la información
df -h > $DIRTMP/storage.log
curl -F "data=@$DIRTMP/storage.log" -F "token=$TOKEN" $URL
rm -f $DIRTMP/storage.log
fi

# Check RAM.
if [ $RAMTICKER == $RAMTIMER ]; then
((RAMTICKER = 0))
free -m > $DIRTMP/memoryusage.log
curl -F "data=@$DIRTMP/memoryusage.log" -F "token=$TOKEN" $URL
rm -f $DIRTMP/memoryusage.log
fi

# Check Top 10 más CPU
if [ $CPUTOPTICKER == $CPUTOPTIMER ]; then
((CPUTOPTICKER = 0))
ps -eo pcpu,pid,user,args | sort -k 1 -r | head -10 > $DIRTMP/top10cpu.log
curl -F "data=@$DIRTMP/top10cpu.log" -F "token=$TOKEN" $URL
rm -f $DIRTMP/top10cpu.log
fi

# Uptime y load
if [ $UPTIMETICKER == $UPTIMETIMER ]; then
((UPTIMETICKER = 0))
uptime > $DIRTMP/uptime.log
curl -F "data=@$DIRTMP/uptime.log" -F "token=$TOKEN" $URL
rm -f $DIRTMP/uptime.log
fi

sleep 1

done

Se le agregó a cada tipo de información a reportar, la línea para que sea enviado el archivo usando cURL a la dirección del servicio donde se encuentra nuestro archivo.

Y estamos listos, con esto sería suficiente para capturar la información y enviarla a un servidor central para su almacenamiento o procesamiento.

Puedes probar de la siguiente forma:

Script bash

# desde consola de comandos ingresa al directorio donde está guardado el archivo y ejecuta:
chmod +x servermon.sh
# a continuación inicia el script
./servermon.sh

Si todo sale bien, será mostrado el resultado de los envíos después de unos segundos (dependiendo del intervalo de envío que fijaste).

Ahora, en el servidor web, puedes ingresar al directorio /files y encontrarás archivos con nombres como estos:

/files
20230424043142_uptime.log
20230424043242_uptime.log

Y dentro de ellos la información reportada por el servidor para cada tipo de recurso monitoreado.

En este punto resta procesar la información dentro de los archivos y decidir qué hacer con ella en el servidor centralizado.

Gracias por llegar hasta este punto. Recuerda que en Winkhosting.co somos mucho más que hosting!

--

--