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)

  1. descargamos los paquetes localmente
  2. copiamos los paquetes al server remoto
  3. 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