Monitoreando remotos
Requerimiento original
A partir de un pool de remotos (switches inet 900):
Obtener 5 datos:
- Name
- Uptime
- IP
- SNR (signal to noise ratio)
- RSSI (received signal strength indicator)
Monitorear los equipos y visualizar los datos.
plus
Sustituir un WhatsupGold
Initial Requirement
retrieve data from devices
- static values
- name
- ip address
- VLAN
- dynamic values
- RSSI
- SNR
- uptime
excel - > csv -> git -> jenkins -> ansible -> nagios
nagios plugin -> mysql/influxdb
mysql -> html -> table
influxdb -> grafana -> graphics
Problemas Retos
- No. de hosts (>1k)
- Limitado ancho de banda en la red
- Visualización de los datos
- formato óptimo
- históricos
Challenges
- >1k devices
- bandwidht constraints
- low cpu processing devices
Implementation Challenges
- easy data input for end users
- group output by parameters (location or funcionality)
- simplicity, split requirements into small tasks (the unix philosophy)
- keep low network usage
- keep low cpu usage on remote device
Solución versión 1.0
Que tenemos ?
Nagios: un sistema de monitoreo
- hosts
- contacts
- alerts
- checks/plugins
Que nos da ?
monitoreo de hosts: status up/down
Qué no nos da?
- visualización de los datos
- no tiene soporte nativo para gráficos
- es un sistema de monitoreo, no de reporting
Alternativas
- clones de nagios
- icinga, centron (mismos problemas que nagios)
- similares
- zabbix, zenoss (aprender un nuevo sistema)
- new kids on the block
- sensuapp
Sensuapp
lo recomiendan como sucesor de nagios
excelente para ambientes dinámicos
instalación compleja: muchos componentes: rabbitmq, redis, server/cliente.
se puede instalar un cliente en un remoto? no
nuestra red es dinámica? no, es estática
Arquitectura
filosofia unix
¿ Qué tenemos ?
- hosts
- datos (cambian en el tiempo)
¿ Qué queremos ?
- monitorear los hosts
- visualizar los datos
Datos
Almacenamiento de datos que cambian en el tiempo:
Time Series Database
opciones:
- rrd
- InfluxDB
- OpenTSDB
- MySQL
- ....
Visualización
¿ Time Series Database ?
Grafana 2.0
buena integración con InfluxDB
Obtener Datos
consultas snmp
quien monitorea? nagios
solución: nagios check/plugin
que es un nagios plugin? un script
en que lenguaje se hace el script?
Nagios plugin
¿bash o python?
python tiene librerias para conectar a influxdb y usar snmp.
bash llama a comandos externos en ambos casos
python sería más "performante"
Python - uptime
[osvaldo@na3lmon1 ~]$ snmpget -v 2c -c ${COMMUNITY} -Oa 11.192.100.155 \
$oid_nombre $oid_NetworkName $oid_RSSI $oid_SNR $oid_uptime
SNMPv2-SMI::enterprises.4130.5.2.9.0 = STRING: "CL-1630 (CM2)."
SNMPv2-SMI::enterprises.4130.2.1.1.2.1.2.0 = STRING: "***********"
SNMPv2-SMI::enterprises.4130.2.1.1.3.2.4.0 = INTEGER: -63
SNMPv2-SMI::enterprises.4130.2.1.1.3.2.5.0 = INTEGER: 24
SNMPv2-SMI::enterprises.4130.5.2.5.0 = STRING: "40 days, 03 hours."
[osvaldo@na3lmon1 ~]$
>>> import dateparser
>>> dateparser.parse('1 hour ago')
datetime.datetime(2015, 10, 15, 12, 39, 22, 618141)
>>> dateparser.parse('40 days, 03 hours.')
datetime.datetime(2015, 9, 5, 10, 39, 22, 618141)
>>>
>>> dateparser.parse('06 hrs, 38 min')
datetime.datetime(2015, 10, 15, 7, 1, 22, 618141)
>>> dateparser.parse('32 days, 06 hours')
datetime.datetime(2015, 9, 13, 7, 39, 22, 618141)
>>>
https://pypi.python.org/pypi/dateparser
Python - uptime
(master) osvaldo@mbp:remotos-sur $ pip install dateparser
Collecting dateparser
Downloading dateparser-0.3.0-py2.py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): PyYAML in
/usr/local/lib/python2.7/site-packages (from dateparser)
Collecting python-dateutil>=2.3 (from dateparser)
Downloading python_dateutil-2.4.2-py2.py3-none-any.whl (188kB)
100% | | 192kB 546kB/s
Collecting six>=1.5 (from python-dateutil>=2.3->dateparser)
Downloading six-1.10.0-py2.py3-none-any.whl
Installing collected packages: six, python-dateutil, dateparser
Successfully installed dateparser-0.3.0 python-dateutil-2.4.2 six-1.10.0
instalamos dateparser
Python - uptime
>>> hh = dateparser.parse('06 hrs, 38 min')
>>> time.mktime(hh.timetuple())
1444903282.0
>>> int(hh.strftime('%s'))
1444903282
>>>
>>> str(datetime.datetime.fromtimestamp(1444903282))
'2015-10-15 07:01:22'
>>>
usando dateparser
Python - uptime
instalando dateparser en el server remoto (sin conexion a internet)
- descargamos los paquetes localmente
- copiamos los paquetes al server remoto
- instalamos los paquetes en el server remoto
Python - uptime
# online host
PIP_CACHE=/var/tmp/pip
mkdir ${PIP_CACHE}
pip install --download=${PIP_CACHE} dateparser
instalando dateparser en el server remoto (sin conexion a internet)
Python - uptime
pero si lo haces en una mac, hay paquetes (PyYML?!) que no te van a funcionar en un linux
$ ls /var/tmp/pip/
PyYAML-3.11-cp27-none-macosx_10_10_x86_64.whl
six-1.10.0-py2.py3-none-any.whl
dateparser-0.3.0-py2.py3-none-any.whl
python_dateutil-2.4.2-py2.py3-none-any.whl
1. descargamos los paquetes
2. copiarlos para el server remoto
3. instalar
# online host
# primero instalar pip
yum install pip
# pero el host offline no le funciona yum
# https://dl.fedoraproject.org/pub/epel/6/x86_64/repoview/python-pip.html
wget https://dl.fedoraproject.org/pub/epel/6/x86_64/python-pip-7.1.0-1.el6.noarch.rpm -P /var/tmp/pip/
rsync -av -e ssh /var/tmp/pip nagios:/var/tmp/
# offline host
yum --nogpgcheck localinstall /var/tmp/pip/python-pip-7.1.0-1.el6.noarch.rpm
# instalemos el paquete dateparser
PIP_CACHE=/var/tmp/pip
sudo pip install --no-index --find-links=file://$PIP_CACHE dateparser
hagámoslo desde en un server CentOS
Python - uptime
# virtualenvwrapper tiraba error
TypeError: __init__() got an unexpected keyword argument 'stream'
virtualenvwrapper.sh: There was a problem running the initialization hooks.
# solucion: instalar la version anterior (descargar, copiar, instalar)
pip install virtualenvwrapper==4.7.0
# dataparser da error en CentOS 6
# solucion: probar version anterior
pip install --download=${PIP_CACHE} dateparser==0.2.1
# sigue dado error
>>> import dateparser
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/osvaldo/src/python/virtualenvs/nagioscs-pip
/lib/python2.6/site-packages/dateparser/__init__.py", line 4, in <module>
from date import DateDataParser
hagámoslo desde en un server CentOS 6
Python - uptime
decido guardar el uptime como string y continuo con la parte de snmp
Python - uptime
sudo yum install net-snmp-python
# online host
sudo yum install --downloadonly --downloaddir=/tmp net-snmp-python
# me faltaba el plugin de yum, lo instalo
sudo yum install yum-plugin-downloadonly
# offline host
yum --nogpgcheck localinstall /var/tmp/pip/python-pip-7.1.0-1.el6.noarch.rpm
# instalo el paquete pero encuentro dependencias, las descargo, copio, instalo ...
yum --nogpgcheck localinstall /var/tmp/pip/lm_sensors-libs-3.1.1-17.el6.x86_64.rpm
yum --nogpgcheck localinstall /var/tmp/pip/net-snmp-libs-5.5-54.el6_7.1.x86_64.rpm
yum --nogpgcheck localinstall /var/tmp/pip/net-snmp-python-5.5-54.el6_7.1.x86_64.rpm
# imposible, tenia monton de dependencias. mas sencillo usar proxy
# instalo y configuro un proxy en un server con conexion a internet: squid
#/etc/yum.conf
proxy=http://proxy.acme.com:3128
yum -y install pysnmp
instalando paquetes
Python - snmp
# http://pysnmp.sourceforge.net/quick-start.html#fetch-snmp-variable
[root@nagios ~]# python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pysnmp.hlapi import *
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named hlapi
# claro, error ...
# en la página aclaran:
# " (you’d need at least version 4.3.0 to run code from this page)."
# la versión que se instala en CentOS es anterior
probando código
Python - snmp
[root@nagios ~]# python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pysnmp.entity.rfc3413.oneliner import cmdgen
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.6/site-packages/pysnmp/entity/rfc3413/oneliner/cmdgen.py", line 1, in <module>
from pysnmp.entity import engine, config
File "/usr/lib/python2.6/site-packages/pysnmp/entity/engine.py", line 2, in <module>
from pysnmp.proto.rfc3412 import MsgAndPduDispatcher
File "/usr/lib/python2.6/site-packages/pysnmp/proto/rfc3412.py", line 3, in <module>
from pyasn1.compat.octets import null
ImportError: No module named compat.octets
# pareciera problema de versiones ...
# http://stackoverflow.com/questions/24356398/no-module-named-compat-octets
probando código (otro ejemplo)
Python - snmp
Installation
~~~
go get github.com/soniah/gosnmp
~~~
http_proxy=http://proxy.acme.com:3128 go get github.com/soniah/gosnmp
alias go='http_proxy=127.0.0.1:8080 go'
no hace nada ....
probando con Go (la forma desesperada)
Python - Go
# http://www.emilsit.net/blog/archives/how-to-use-the-git-protocol-through-a-http-connect-proxy/
#@server
sudo yum -y install socat
sudo vi /usr/local/bin/gitproxy-socat
sudo chmod 755 /usr/local/bin/gitproxy-socat
git config --global core.gitproxy gitproxy
#@squid server
#add to /etc/squid3/squid.conf
acl SSL_ports port 9418 # git
acl Safe_ports port 9418 # git
# nada ...
http_proxy=http://proxy.acme.com:3128
export http_proxy
git config --global http.proxy $http_proxy
error again
Initialized empty Git repository in /home/osvaldo/src/go/src/github.com/soniah/gosnmp/.git/
error: Received HTTP code 302 from proxy after CONNECT while accessing https://github.com/soniah/gosnmp/info/refs
probando con Go (la forma desesperada)
Python - Go
http_proxy=http://proxy.acme.com:3128
export http_proxy
https_proxy=http://proxy.acme.com:3128
export https_proxy
git config --global http.proxy $http_proxy
git config --global https.proxy $https_proxy
export GIT_TRACE_PACKET=1
export GIT_TRACE=1
export GIT_CURL_VERBOSE=1
git config --global http.sslverify false
import "github.com/soniah/gosnmp"
# nada ...
probando con Go (la forma desesperada)
Python - Go
OUT=$(mktemp /var/tmp/nagiostmpdata.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; }
cat <<_EOF_>$OUT
networkname,host=${HOST} value="${NetworkName}"
rssi,host=${HOST} value=${RSSI}
snr,host=${HOST} value=${SNR}
_EOF_
#UPTIME,host=${HOST},name={NAME} value=${UPTIME}
curl -i -XPOST 'http://localhost:8086/write?db=mydb' --data-binary @${OUT}
it just works
Python - Bash
# yeap:
curl -i -XPOST 'http://localhost:8086/write?db=mydb'
--data-binary 'networkname,host=11.192.100.155,region=cs value="^pxC{bqYMraP3}&."'
# nop:
curl -i -XPOST 'http://localhost:8086/write?db=mydb'
-d '{"points" : [{ "measurement" : "rssi",
"fields" : { "value" : -65 }, "tags" : { "region" : "cs", "ip" : "11.192.100.155" } } ]}'
# yeap:
curl -G 'http://localhost:8086/query?pretty=true'
--data-urlencode "db=mydb" --data-urlencode "q=SELECT value FROM rssi"
# nop:
curl -X POST 'http://localhost:8086/db/mydb/series'
-d '{"name": "rssi", "columns": ["value"], "points": [[-66]]}'
probando como cargar los datos
Python - Bash
algunas formas funcionaban, otras no. al final me quede con la generación de un archivo y cargarlo desde ahi (diapositiva anterior)
se complicaba cargar el networkname en InfluxDB
InfluxDB - Datos
Solución
cargar datos númericos en InfluxDB y los de texto en MySQL
# -----------------
# System Name: PB-2015 (PB-500)
# -----------------
define host{
use generic-host-remote ; Inherit default values from a template
#host_name PB-2015 ; The name we're giving to this server
#host_name PB-2015 ; The name we're giving to this server
host_name PB-2015 ; The name we're giving to this server
#host_name PB-2015
alias PB-2015 (PB-500) ; A longer name for the server
address 10.11.12.13 ; IP address of the server
hostgroups remotes_inet900;
statusmap_image switch.gd2
icon_image switch.gif
contact_groups admins
}
hosts definition = objects.cfg
Nagios - Datos
10.11.12.13,online,PB-2015 (PB-500),PBO, -65, 21,43 days, 02 hours
Data file (csv): objects.csv
Nagios - Datos
{% for host in item %}
# -----------------
# System Name: {{ host.name[5:] }}
# -----------------
define host{
use moxa-switch ; Inherit default values from a template
host_name {{ host.name }} ; The name we're giving to this server
alias {{ host.name }} ; A longer name for the server
address {{ host.ip }} ; IP address of the server
hostgroups AP
statusmap_image switch.gd2
icon_image switch.gif
contact_groups admins
}
{% endfor %}
Template file: objects.cfg.j2
Error: The name of host 'EH-4161 (D)' contains one or more illegal characters.
host_name {{ host.name | regex_replace('^(.*)\(.*$', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }}
Error: The name of host 'MEa-4012 EC)' contains one or more illegal characters.
hay un hostname que tiene un ")" pero no tiene "(" por lo que la regex fallaba
tomo lo que esta hasta el primer espacio en blanco y descarto el resto
pero no ...
Warning: Duplicate definition found for host 'MEA'
fgrep 'MEA' data/na3lmon1/csv/remotes.csv
11.192.112.75,online,MEA 4013 (ME1),ME1, -75, 22,46 days, 01 hours
11.192.112.157,online,MEA 3069(ME),ME, -75, 21,46 days, 01 hours
11.192.117.225,online,MEA-3184 (ME),ME, -77, 24,1 days, 04 hours
11.192.112.187,online,MEA-3186 (ME),ME, -80, 22,20 days, 06 hours
algunos tienen guion, otros no ....
host_name {{ host.name | regex_replace('^([a-zA-Z0-9\-\s]+)(\(.*$)', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }} ;
ese no sirve ...
Error: The name of host 'MEa-4012 EC)' contains one or more illegal characters.
falta de estandarización en los host name
Nagios - Datos
# pruebo con un if/then/else
{% if '-' in host.name %}
host_name {{ host.name | regex_replace('^([a-zA-Z0-9\-]+)\s.*$', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }} ; The name we're giving to this server
{% else %}
host_name {{ host.name | regex_replace('^([a-zA-Z0-9\-\s]+)(\(.*$)', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }} ; The name we're giving to this server
{% endif %}
---
no sirve
Error: The name of host 'LH-3006(d)' contains one or more illegal characters.
Error: The name of host 'ME-4031(D)' contains one or more illegal characters.
falta de estandarización en los host name
Nagios - Datos
{% for host in item %}
{% if 'name' in host %}
# -----------------
# System Name: {{ host.name }}
# -----------------
define host{
use generic-host-remote ; Inherit default values from a template
#host_name {{ host.name | regex_replace('^(.*)\(.*$', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }}
#host_name {{ host.name | regex_replace('^([a-zA-Z0-9\-]+)\s.*$', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }}
{% if '-' in host.name %}
host_name {{ host.name | regex_replace('^([a-zA-Z0-9\-]+).*$', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }}
{% else %}
host_name {{ host.name | regex_replace('^([a-zA-Z0-9\-\s]+)(\(.*$)', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }}
{% endif %}
#host_name {{ host.name | regex_replace('^(.*)(\(.*$)', '\\1') | regex_replace('^(.*)\(.*$', '\\1') }}
alias {{ host.name }} ; A longer name for the server
address {{ host.ip }} ; IP address of the server
hostgroups remotes_inet900;
statusmap_image switch.gd2
icon_image switch.gif
contact_groups admins
}
{% endif %}
{% endfor %}
Template file
Nagios - Datos
http://gitlab.sinopecarg.com.ar/infra/tempura/blob/master/data/na3lmon1/templates/remotes.cfg.j2
bonus point: se desarrollo un plugin de ansible (python)
Retos
-
impacto en el ancho de banda al tener >1k hosts realizando chequeos en simultáneo.
-
distribuir los chequeos en el tiempo para minizar dicho impacto.
-
impacto de la consulta snmp en el host
Nagios - Obtener datos
Opciones
- write an script which execute 4 times snmpget to retrieve each OID
- use snmpwalk which will cause high cpu usage on remote device
- no era tan complicado: rtfm
Nagios - Obtener datos
[osvaldo@na3lmon1 ~]$ snmpget
No hostname specified.
USAGE: snmpget [OPTIONS] AGENT OID [OID]...
snmpget allows to retrieve more than one OID in one connection
Solución
-
se determino un tiempo mínimo para colectar datos: 15 mins. De esta manera los gráficos serían representativos.
-
se pueden obtener varios oid con una ejecución del comando snmpget (RTFM)
- nagios distribuye automáticamente los chequeos en base al intervalo definido y la cantidad de hosts
Nagios - bash script
# http://mywiki.wooledge.org/BashFAQ/024
OUT=$(mktemp -u /var/tmp/nagiostmpfifo.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; }
mkfifo $OUT
${SNMPGET} -v 2c -c ${COMMUNITY} -Oa ${HOST} ${OIDS} > $OUT &
snmpget_pid=$!
while read val
do
case "$val" in
*4130.5.2.9.0* )
NAME=$(echo $val | sed 's/SNMPv2-SMI::enterprises.4130.5.2.9.0 = STRING: "\(.*\)"$/\1/')
;;
*4130.2.1.1.2.1.2.0* )
NetworkName=$(echo $val | sed 's/SNMPv2-SMI::enterprises.4130.2.1.1.2.1.2.0 = STRING: "\(.*\)"$/\1/')
;;
*4130.2.1.1.3.2.4.0* )
RSSI=$(echo $val | sed 's/SNMPv2-SMI::enterprises.4130.2.1.1.3.2.4.0 = INTEGER: \(.*\)$/\1/')
;;
*4130.2.1.1.3.2.5.0* )
SNR=$(echo $val | sed 's/SNMPv2-SMI::enterprises.4130.2.1.1.3.2.5.0 = INTEGER: \(.*\)$/\1/')
;;
*4130.5.2.5.0* )
UPTIME=$(echo $val | sed 's/SNMPv2-SMI::enterprises.4130.5.2.5.0 = STRING: "\(.*\)"$/\1/')
;;
esac
done < $OUT
wait $snmpget_pid
snmpget_rc=$?
rm -f $OUT
if [[ $snmpget_rc != 0 ]]
then
exitstatus=$STATE_CRITICAL
exit $exitstatus
fi
Opciones
- angularjs
- datatables
Visualizar datos
Ventajas
-
código sencillo.
-
ejemplo de server side processing (se modificó el php).
Datatables
$(document).ready(function() {
$('#remotes').dataTable( {
"processing": true,
"serverSide": true,
"ajax": "scripts/server_processing_customized.php"
} );
} );
Performance Issues
mysql> SELECT SQL_CALC_FOUND_ROWS `u`.`name`, `u`.`ip`, `ud`.`ap`, `u`.`rssi`, `u`.`snr`, `u`.`uptime`
-> FROM `networknames` AS `ud` JOIN ( SELECT st1.* FROM status st1 LEFT JOIN status st2 ON (st1.ip = st2.ip AND st1.id < st2.id) WHERE st2.id IS NULL) as `u` ON (u.networkname=ud.name)
-> ORDER BY `u`.`name` ASC
-> LIMIT 0, 10;
+---------------+----------------+-----+------+-----+--------------------+
| name | ip | ap | rssi | snr | uptime |
+---------------+----------------+-----+------+-----+--------------------+
| AH-1111 (EH). | 11.192.109.74 | EH | -81 | 21 | 4 days, 04 hours. |
| AH-1219 (EH). | 11.192.109.51 | EH | -69 | 20 | 4 days, 04 hours. |
| AH-1410 (EH). | 11.192.109.54 | EH | -77 | 19 | 4 days, 04 hours. |
| AH-1414 (EH). | 11.192.109.105 | EH | -73 | 21 | 22 hrs, 38 min. |
| AH-3213 (EH). | 11.192.109.56 | EH | -77 | 20 | 28 days, 18 hours. |
| AH-3315 (EH). | 11.192.109.107 | EH | -76 | 19 | 44 days, 18 hours. |
| AH-3317 (EH). | 11.192.109.108 | EH | -64 | 20 | 5 days, 20 hours. |
| AH-3420 (EH). | 11.192.110.101 | EH | -73 | 20 | 77 days, 08 hours. |
| AH-4223 (EH). | 11.192.109.116 | EH | -82 | 22 | 14 days, 21 hours. |
| AH-6221(EH1). | 11.192.109.222 | EH1 | -80 | 20 | 13 days, 19 hours. |
+---------------+----------------+-----+------+-----+--------------------+
10 rows in set (35.52 sec)
35 secs es mucho tiempo
Performance Issues
SELECT p1.name
FROM status p1 LEFT JOIN status p2
ON (p1.ip = p2.ip AND p1.id < p2.id)
WHERE p2.id IS NULL limit 0,10;
10 rows in set (34.53 sec)
mysql> SELECT count(*) FROM status as t1
-> JOIN (SELECT ip, MAX(id) id FROM status GROUP BY ip) as t2
-> ON t1.id = t2.id AND t1.ip = t2.ip limit 0,10;
+----------+
| count(*) |
+----------+
| 1077 |
+----------+
1 row in set (0.17 sec)
Demo
Preguntas
Lecciones
- Usar la mejor herramienta para el trabajo (bash vs python, mysql)
- Lo que funciona no hace falta cambiarlo (sensuapp vs nagios)
- Lo que esta de moda no siempre nos queda bien (datatables vs angularjs)
- keep it simple sir (usar jquery, usar bash)
- poner limites (probar algo durante un tiempo limitado, no funciona, buscar alternativa)
- automatizar (jenkins)
- simplificar (usar templates y csv)
- errar es humano (detras de cada buena decision hay un monton de malas decisiones)
Gracias
Monitoreando con nagios
By osvaldo
Monitoreando con nagios
- 1,383