Agregando monitoreo de EKS en una versión antigua de Zabbix P3

por | 24 noviembre, 2023

Ultimo post de esta travesía para incluir al monitoreo una plataforma kubernetes operando en AWS con su servicio EKS

Vamos a resumir lo que hemos hecho hasta ahora, además agrupare todas las keys y macros creados con su respectivo propósito.

  • En la parte 1 Creamos un host atachado a un proxy especifico con una regla de descubrimiento llamada «Descubrimiento de nodos EKS» y utiliza la key DiscoveryNode[<nombre Cliente>]
  • Esta regla tiene un prototipo de host que usara de nombre de host {#NOMBRENODO}, como nombre visible tendrá CL|{#CLIENTE}|PS|{#NOMBRENODO} y se añadira a los grupos Cloud/aws Cloud/aws/eks y al grupo prototipo Cloud/aws/eks/{#CLIENTE}
  • A este hosts se le añade un template , el cual vimos en la parte 2 que contiene items para el monitoreo del nodo EKS, junto con una regla de descubrimiento llamada Pods EKS.
  • En este descubrimiento utilizamos las macros {#NOMBRE} {#NODO} y {#NOMBRE_CORTO}
  • Esta regla de descubrimiento añade tres prototipo de items, cpu, memoria y estado para almacenar la información de cada pod con sus respectivos triggers

Respecto a las keys que utilice durante todo este desarrollo son:

  • Descubrimiento:
    • DiscoveryNode[Cliente]
    • DiscoveryPods[nodo]
  • Items:
    • EKS_Pod_CPU[{#NOMBRE},{HOST.HOST}]
    • EKS_Pod_MEM[{#NOMBRE},{HOST.HOST}]
    • EKS_Pod_STATUS[{#NOMBRE},{HOST.HOST}]
    • EKS_Nodo_CPU[{HOST.HOST}]
    • EKS_Nodo_MEM[{HOST.HOST}]

Como esperaran, trabajaremos con userparameter para crear estas nuevas keys, les coloco el resultado final e iremos viendo uno a uno cada item según vea necesario

#Seccion discovery
UserParameter=DiscoveryNode[*],awk -v cliente="$1" -v lineas=$(cat <ArchivoNodos> | wc -l) 'BEGIN {print "{\"data\":["} NR>1 && NR!=lineas { print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}," } NR==lineas {print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}]}"}' <ArchivoNodos>
UserParameter=DiscoveryPods[*],grep $1 <ArchivoPods> | awk -v lineas=$(grep $1 <ArchivoPods> | wc -l) ' BEGIN {print "{\"data\":["} NR<lineas {print "{\"{#NOMBRE}\":\""$$2"\",\"{#NODO}\":\""$$1"\",\"{#NOMBRE_CORTO}\":\""$$7"\"}," } NR==lineas {print "{\"{#NOMBRE}\":\""$$2"\",\"{#NODO}\":\""$$1"\",\"{#NOMBRE_CORTO}\":\""$$7"\"}]}" }'
#Seccion datos de nodos
UserParameter=EKS_Nodo_CPU[*],grep $1 <ArchivoNodos> | awk '{gsub("%","",$$3)}  {print $$3}'
UserParameter=EKS_Nodo_MEM[*],grep $1 <ArchivoNodos> | awk '{gsub("%","",$$5)}  {print $$5}'
#Seccion datos de pods
UserParameter=EKS_Pod_CPU[*],grep $1 <ArchivoPodsPorNodo>$2  | awk '{gsub("m","")}  {print $$2}'
UserParameter=EKS_Pod_MEM[*],grep $1 <ArchivoPodsPorNodo>$2  | awk '{gsub("Mi","")}  {print $$3}'
UserParameter=EKS_Pod_STATUS[*],grep $1 <ArchivoPodsPorNodo>$2  | awk '{print $$4}'

Recordemos que la parte de los pods la realizo mi compañero y no tengo ganas de descubrir y describir que fue lo que realizo, yo describiré dentro de lo que se hizo en su momento como obtenemos la data de los nodos eks, no indicare la forma de conexión a aws ya que ese trabajo fue de una persona externa al área pero si les comentaré que una vez hecha la comunicación entre aws y nuestro equipo podemos utilizar kubectl directamente como si estuviera en localhost.

Una vez explicado esto pasemos a ver las keys creadas una por una

UserParameter=DiscoveryNode[*],awk -v cliente="$1" -v lineas=$(cat <ArchivoNodos> | wc -l) 'BEGIN {print "{\"data\":["} NR>1 && NR!=lineas { print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}," } NR==lineas {print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}]}"' <ArchivoNodos>

Como habrán notado, la información la obtenemos desde un archivo donde imprimimos la información, la estructura del archivo es la siguiente

NAME             CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
<Nombre nodo1>   795m         41%    3012Mi          20%
<Nombre Nodo2>   621m         32%    3079Mi          20%
<Nombre Nodo3>   1635m        84%    5305Mi          35%
<Nombre Nodo4>   640m         33%    2995Mi          19%
<Nombre Nodo5>   602m         31%    2838Mi          18%

Si han utilizado kubectl seguramente esta salida se les haga conocida, es la salida del comando

kubectl top nodes --use-protocol-buffers 

el cual ejecutamos desde un cron. Decidimos utilizar este método, aunque no sea de mi agrado, debido a la latencia en la ejecución del comando y el timeout configurado en zabbix

Continuando con la key vamos a desglosarla y ver en detalle el comando utilizado. Si han visto mis posts anteriores, no creo que sea sorpresa el que haya utilizado la herramienta awk que nos brinda muchas opciones. A continuación pondré el userparameter de una forma mas human-friendly

awk -v cliente="$1" -v lineas=$(cat <ArchivoNodos> | wc -l) 
'BEGIN {print "{\"data\":["} 
NR>1 && NR!=lineas { print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}," } 
NR==lineas {print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}]}"' 
<ArchivoNodos>

Ahora pasaremos sección por sección a explicar el funcionamiento

awk -v cliente="$1" -v lineas=$(cat <ArchivoNodos> | wc -l) 

En esta línea, definimos 2 variables que utilizaremos dentro de awk, el primero se llamara cliente, el cual tomara el valor directamente desde la key, y el segundo valor evalúa cuantas líneas contiene el archivo para saber en que línea estamos y poder ir agregando con mayor comodidad la sintaxis utilizada en el formato JSON

'BEGIN {print "{\"data\":["} 

BEGIN es una palabra reservada en awk que indica que antes de empezar a procesar nuestro texto haremos alguna acción, en este caso iniciare la cabecera del json

Luego tenemos una evaluacion NR>1 && NR!=lineas

NR>1 && NR!=lineas { print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}," } 

NR es una variable de awk. Significa Number of Records o numero de registros y nos indica en que línea estamos. Por lo tanto esta línea indica que la expresión posterior solo se aplicara si estamos en un numero de línea superior a 1 y distinta al final del archivo

¿Por qué no queremos trabajar en la primera línea o la ultima?

Pues si revisamos el archivo sabremos por que no queremos incorporar la primera línea pues contiene el nombre de la columna

NAME             CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
<Nombre nodo1>   795m         41%    3012Mi          20%

Y la ultima linea no la usamos en esta parte para facilitar que nuestra data quede bien estructurada en formato JSON, esa la tratamos de forma especial en la siguiente línea

Luego tenemos otra evaluación NR==lineas, que indicará que trabajaremos sobre la ultima línea del archivo, si se fijan tenemos una breve diferencia en el termino de los caracteres

NR==lineas {print "{\"{#NOMBRENODO}\":\""$$1"\",\"{#CLIENTE}\":\""cliente"\"}]}"' 
<ArchivoNodos>

La salida de este comando al utilizar zabbix_get o zabbix_agent será algo así

zabbix_get -k DiscoveryNode[CLIENTEA] -s 127.0.0.1
{"data":[
{"{#NOMBRENODO}":"<Nombre Nodo1>","{#CLIENTE}":"CLIENTEA"},
{"{#NOMBRENODO}":"<Nombre Nodo2>","{#CLIENTE}":"CLIENTEA"},
{"{#NOMBRENODO}":"<Nombre Nodo3>","{#CLIENTE}":"CLIENTEA"},
{"{#NOMBRENODO}":"<Nombre Nodo4>","{#CLIENTE}":"CLIENTEA"},
{"{#NOMBRENODO}":"<Nombre Nodo5>","{#CLIENTE}":"CLIENTEA"}]}

La siguiente key es DiscoveryPods[nodo], aquí como comente en el post anterior hicimos el filtro antes de formatear la información, y como he comentado, esta parte corresponde a un desarrollo realizado por mi compañero, el archivo resultante tiene el siguiente formato

<nombre nodo1> <nombre pod1> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo1> <nombre pod2> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo1> <nombre pod3> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo1> <nombre pod4> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo2> <nombre pod1> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo2> <nombre pod2> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo2> <nombre pod3> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo3> <nombre pod1> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo3> <nombre pod2> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo3> <nombre pod3> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo4> <nombre pod1> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo4> <nombre pod2> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo5> <nombre pod1> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo5> <nombre pod2> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre nodo5> <nombre pod3> <uso cpu> <uso ram> <estado pod> <namespace>

Ya conociendo la estructura del archivo procedo a mostrar el comando que utilizamos

UserParameter=DiscoveryPods[*],grep $1 <ArchivoPods> | awk -v lineas=$(grep $1 <ArchivoPods> | wc -l) ' BEGIN {print "{\"data\":["} NR<lineas {print "{\"{#NOMBRE}\":\""$$2"\",\"{#NODO}\":\""$$1"\",\"{#NOMBRE_CORTO}\":\""$$7"\"}," } NR==lineas {print "{\"{#NOMBRE}\":\""$$2"\",\"{#NODO}\":\""$$1"\",\"{#NOMBRE_CORTO}\":\""$$7"\"}]}" }'

A diferencia de la key anterior, aquí colocamos al inicio un grep, que tomará el primer argumento que enviamos con la key, que debe corresponder al nombre del nodo que definimos en el primer post

grep $1 <ArchivoPods>

Luego mediante el uso de una pipeline o tubería «|» pasamos esta salida filtrada directamente a awk

awk -v lineas=$(grep $1 <ArchivoPods> | wc -l) ' BEGIN {print "{\"data\":["} NR<lineas {print "{\"{#NOMBRE}\":\""$$2"\",\"{#NODO}\":\""$$1"\",\"{#NOMBRE_CORTO}\":\""$$7"\"}," } NR==lineas {print "{\"{#NOMBRE}\":\""$$2"\",\"{#NODO}\":\""$$1"\",\"{#NOMBRE_CORTO}\":\""$$7"\"}]}" }'

En este caso a diferencia de la key anterior no necesitamos filtrar el inicio del archivo, pero si debemos utilizar las opciones de BEGIN y filtrar la ultima línea para darle el tratamiento de fin del JSON.

Por lo demás es lo mismo que utilizamos en la key anterior por lo que veo innecesario volver a explicarlo.

Ahora pasaremos a los items de recolección de datos, empezando en este caso por los pods

#Seccion datos de pods
UserParameter=EKS_Pod_CPU[*],grep $1 <ArchivoPodsPorNodo>$2  | awk '{gsub("m","")}  {print $$2}'
UserParameter=EKS_Pod_MEM[*],grep $1 <ArchivoPodsPorNodo>$2  | awk '{gsub("Mi","")}  {print $$3}'
UserParameter=EKS_Pod_STATUS[*],grep $1 <ArchivoPodsPorNodo>$2  | awk '{print $$4}'

Aquí realizamos un cambio y separamos la salida que devuelve el desarrollo de mi compañero y la separamos según el nombre del nodo.

El nuevo archivo mantiene la misma estructura salvo que se omite el nombre del nodo inicial el cual se usa como nombre del archivo, quedando la estructura así

cat <ArchivoPodsPorNodo>/<nombre nodo1>
<nombre pod1> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre pod2> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre pod3> <uso cpu> <uso ram> <estado pod> <namespace>
<nombre pod4> <uso cpu> <uso ram> <estado pod> <namespace>

Aquí creo que no es necesario explicar mucho mas, excepto el funcionamiento de la función gsub()

En este caso lo utilizo para eliminar caracteres que me moleste, pero perfectamente se puede utilizar para cambiar una palabra por otra, en el primer argumento dentro del paréntesis colocamos cual es la palabra que queremos buscar, como segundo argumento le pasamos la palabra a reemplazar y opcionalmente podemos pasar un tercer argumento para indicar en que columna queremos buscar la palabra.

Un ejemplo de esto seria aquí donde cambiaremos «gente» por «people»

#echo "hola estimada gente!" | awk 'gsub("gente","people")'
hola estimada people!

En el caso del tercer argumento vamos a utilizar el mismo comando pero buscaremos en la columna 2 la palabra deseada

#echo "hola estimada gente!" | awk 'gsub("gente","people",$2)'

En este caso no se imprime nada ya que gsub no encuentra la palabra solicitada en la columna 2.

Para el caso de la información de los nodos mantenemos el mismo archivo y básicamente es el mismo formato que para obtener la información de los pods

#Seccion datos de nodos
UserParameter=EKS_Nodo_CPU[*],grep $1 <ArchivoNodos> | awk '{gsub("%","",$$3)}  {print $$3}'
UserParameter=EKS_Nodo_MEM[*],grep $1 <ArchivoNodos> | awk '{gsub("%","",$$5)}  {print $$5}'

Ya con esto terminamos esta mini serie incorporando eks a zabbix.

Gracias por leer hasta aquí, espero haya sido de su agrado, recuerden que muchas de las cosas que aplico en estos posts pueden perfectamente ser aplicadas a otros desarrollos según la necesidad.

Me mantengo atento a sus comentarios y dudas

fuente:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *