Archives pour la catégorie ‘Monitoring’

Rsyslog : désactiver la limitation de réception

$SystemLogRateLimitInterval 0

CoreOS : monitoring d’un serveur avec Zabbix

Je viens de publier mes sources nécessaires à la création d’un container Docker avec un agent Zabbix patché par mes soins dans le but de monitorer un serveur CoreOS et ses containers Docker.

URL du dépôt : https://github.com/bhuisgen/docker-zabbix-coreos

zabbix-monitor : moniteur web d’alertes Zabbix

zabbix-monitor

Je publie les sources d’un mini-projet dénommé zabbix-monitor qui pourrait être utile à tout ceux qui monitorent comme moi leurs serveurs avec Zabbix. Comme le nom l’explicite, zabbix-monitor est un moniteur web d’alertes Zabbix.

Caractéristiques :

  • application client-side full JS avec interface responsive pour affichage optimisé sur desktop / tablette / mobile
  • support multi-serveurs possible et paramètres spécifiques par serveur
  • vue des alertes avec filtrage par sevérité
  • vue des derniers évènements détectés
  • vue d’état d’exécution des scénarios web

Dépôt : https://github.com/bhuisgen/zabbix-monitor

Zabbix : patch pour le support des URL longues avec les scénarios Web

zabbix=# ALTER TABLE httpstep ALTER COLUMN url TYPE varchar(2048);
root@zabbix:/usr/share/zabbix # diff -Naur include/schema.inc.php.orig include/schema.inc.php
--- include/schema.inc.php.orig 2014-04-08 15:57:52.336603380 +0200
+++ include/schema.inc.php 2014-04-08 15:58:05.268806754 +0200
@@ -844,7 +844,7 @@
'url' => array(
'null' => false,
'type' => DB::FIELD_TYPE_CHAR,
- 'length' => 255,
+ 'length' => 2048,
'default' => '',
),
'timeout' => array(
root@zabbix:/usr/share/zabbix # diff -Naur include/views/configuration.httpconf.popup.php.orig include/views/configuration.httpconf.popup.php
--- include/views/configuration.httpconf.popup.php.orig	2014-04-08 16:10:49.660807171 +0200
+++ include/views/configuration.httpconf.popup.php	2014-04-08 16:11:25.721372545 +0200
@@ -75,7 +75,7 @@

 	$httpPopupFormList = new CFormList('httpPopupFormList');
 	$httpPopupFormList->addRow(_('Name'), new CTextBox('name', get_request('name', ''), ZBX_TEXTBOX_STANDARD_SIZE, get_request('templated', null), 64));
-	$httpPopupFormList->addRow(_('URL'), new CTextBox('url', get_request('url', ''), ZBX_TEXTBOX_STANDARD_SIZE));
+	$httpPopupFormList->addRow(_('URL'), new CTextBox('url', get_request('url', ''), ZBX_TEXTBOX_STANDARD_SIZE, 'no', 2048));
 	$httpPopupFormList->addRow(_('Post'), new CTextArea('posts', get_request('posts', '')));
 	$httpPopupFormList->addRow(_('Variables'), new CTextArea('variables', get_request('variables', '')));
 	$httpPopupFormList->addRow(_('Timeout'), new CNumericBox('timeout', get_request('timeout', 15), 5));

Zabbix : patch pour l’authentification LDAP par TLS

root@zabbix:/usr/share/zabbix # diff -Naur include/classes/class.cldap.php.orig include/classes/class.cldap.php
--- include/classes/class.cldap.php.orig	2014-03-18 16:08:49.710346146 +0100
+++ include/classes/class.cldap.php	2014-04-08 12:23:00.904298965 +0200
@@ -40,7 +40,7 @@
 			),
 			'referrals' => 0,
 			'version' => 3,
-			'starttls' => null,
+			'starttls' => true,
 			'deref' => null
 		);

Zabbix : partitionnement des tables sous PostgreSQL

Petit résumé sur la configuration du partitionnement des tables Zabbix (version 2.2) avec une base de données PostgreSQL 9.3. Le partionnement des tables est automatique ; il peut être désactivé à tout moment. La suppression des tables est quant à elle à automatiser en cron.

Trigger de gestion du partitionnement journalier

-- Function: trg_daily_partition()

-- DROP FUNCTION trg_daily_partition();

CREATE OR REPLACE FUNCTION trg_daily_partition()
RETURNS trigger AS
$BODY$
DECLARE
tablename text;
partname text;
startdate text;
enddate text;
query text;

BEGIN
tablename := TG_ARGV[0];
partname := tablename || '_' || TO_CHAR(TO_TIMESTAMP(NEW.clock), 'YYYYMMDD');

EXECUTE 'INSERT INTO ' || 'partitions.' || quote_ident(partname) || ' SELECT ($1).*' USING NEW;

RETURN NULL;

EXCEPTION
WHEN undefined_table THEN

startdate := EXTRACT(EPOCH FROM date_trunc('day', TO_TIMESTAMP(NEW.clock)));
enddate := EXTRACT(EPOCH FROM date_trunc('day', TO_TIMESTAMP(NEW.clock) + ('1 day')::interval));

EXECUTE 'CREATE TABLE IF NOT EXISTS ' || 'partitions.' || quote_ident(partname) || ' (CHECK ((clock >= ' || quote_literal(startdate) || ' AND clock < ' || quote_literal(enddate) || '))) INHERITS ( ' || tablename || ' )';
EXECUTE 'CREATE INDEX ' || quote_ident(partname) || '_1 on ' || 'partitions.' || quote_ident(partname) || '(itemid,clock)';

EXECUTE 'INSERT INTO ' || 'partitions.' || quote_ident(partname) || ' SELECT($1).*' USING NEW;

RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

ALTER FUNCTION trg_daily_partition()
OWNER TO zabbix;

Trigger de gestion du partitionnement mensuel

-- Function: trg_monthly_partition()

-- DROP FUNCTION trg_monthly_partition();

CREATE OR REPLACE FUNCTION trg_monthly_partition()
RETURNS trigger AS
$BODY$
DECLARE
tablename text;
partname text;
startdate text;
enddate text;
query text;

BEGIN
tablename := TG_ARGV[0];
partname := tablename || '_' || TO_CHAR(TO_TIMESTAMP(NEW.clock), 'YYYYMM');

EXECUTE 'INSERT INTO ' || 'partitions.' || quote_ident(partname) || ' SELECT ($1).*' USING NEW;

RETURN NULL;

EXCEPTION
WHEN undefined_table THEN

startdate := EXTRACT(EPOCH FROM date_trunc('month', TO_TIMESTAMP(NEW.clock)));
enddate := EXTRACT(EPOCH FROM date_trunc('month', TO_TIMESTAMP(NEW.clock) + ('1 month')::interval));

EXECUTE 'CREATE TABLE IF NOT EXISTS ' || 'partitions.' || quote_ident(partname) || ' (CHECK ((clock >= ' || quote_literal(startdate) || ' AND clock < ' || quote_literal(enddate) || '))) INHERITS ( ' || tablename || ' )';
EXECUTE 'CREATE INDEX ' || quote_ident(partname) || '_1 on ' || 'partitions.' || quote_ident(partname) || '(itemid,clock)';

EXECUTE 'INSERT INTO ' || 'partitions.' || quote_ident(partname) || ' SELECT($1).*' USING NEW;

RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

ALTER FUNCTION trg_monthly_partition()
OWNER TO zabbix;

Création du schéma

CREATE SCHEMA partitions AUTHORIZATION zabbix

Ajout des triggers sur les tables parentes

CREATE TRIGGER partition_trg BEFORE INSERT ON history FOR EACH ROW EXECUTE PROCEDURE trg_daily_partition('history');
CREATE TRIGGER partition_trg BEFORE INSERT ON history_sync FOR EACH ROW EXECUTE PROCEDURE trg_daily_partition('history_sync');
CREATE TRIGGER partition_trg BEFORE INSERT ON history_uint FOR EACH ROW EXECUTE PROCEDURE trg_daily_partition('history_uint');
CREATE TRIGGER partition_trg BEFORE INSERT ON history_str_sync FOR EACH ROW EXECUTE PROCEDURE trg_daily_partition('history_str_sync');
CREATE TRIGGER partition_trg BEFORE INSERT ON history_log FOR EACH ROW EXECUTE PROCEDURE trg_daily_partition('history_log');
CREATE TRIGGER partition_trg BEFORE INSERT ON trends FOR EACH ROW EXECUTE PROCEDURE trg_monthly_partition('trends');
CREATE TRIGGER partition_trg BEFORE INSERT ON trends_uint FOR EACH ROW EXECUTE PROCEDURE trg_monthly_partition('trends_uint');

Fonctions de suppression des partitions

-- Function: delete_daily_partitions(integer)

-- DROP FUNCTION delete_daily_partitions(integer);

CREATE OR REPLACE FUNCTION delete_daily_partitions(max integer)
RETURNS text AS
$BODY$
DECLARE
result RECORD;
tmp text;
tmstp TIMESTAMP;
maxdate DATE;
BEGIN
maxdate := date_trunc('day', NOW() - (max || ' day')::interval);

FOR result IN SELECT * FROM pg_tables WHERE schemaname = 'partitions' LOOP
tmp := SUBSTRING(result.tablename FROM '[0-9_]*$');
IF LENGTH(tmp) <> 9 THEN
CONTINUE;
END IF;

tmstp := TO_TIMESTAMP(tmp, '_YYYYMMDD');
IF tmstp <= maxdate THEN
RAISE NOTICE 'Deleting daily table %', result.tablename;
EXECUTE 'DROP TABLE ' || result.schemaname || '.' || quote_ident(result.tablename);
END IF;
END LOOP;

RETURN 1;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

ALTER FUNCTION delete_daily_partitions(integer)
OWNER TO zabbix;

 

-- Function: delete_monthly_partitions(integer)

-- DROP FUNCTION delete_monthly_partitions(integer);

CREATE OR REPLACE FUNCTION delete_monthly_partitions(max integer)
RETURNS text AS
$BODY$
DECLARE
prefix text;
result RECORD;
tmp text;
tmstp TIMESTAMP;
maxdate DATE;
BEGIN
maxdate := date_trunc('day', NOW() - (max || ' month')::interval);

FOR result IN SELECT * FROM pg_tables WHERE schemaname = 'partitions' LOOP
tmp := SUBSTRING(result.tablename FROM '[0-9_]*$');
IF LENGTH(tmp) <> 7 THEN
CONTINUE;
END IF;

tmstp := TO_TIMESTAMP(tmp, '_YYYYMM');
IF tmstp <= maxdate THEN
RAISE NOTICE 'Deleting monthly table %', result.tablename;
EXECUTE 'DROP TABLE ' || result.schemaname || '.' || quote_ident(result.tablename);
END IF;
END LOOP;

RETURN 'OK';
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

ALTER FUNCTION delete_monthly_partitions(integer)
OWNER TO zabbix;

Exemple d’utilisation :

SELECT delete_daily_partitions(31);
SELECT delete_monthly_partitions(12);

Nagios : sonde check DNS

Je vous joins ici la sonde Nagios que j’utilise pour vérifier des enregistrements DNS.

Plusieurs serveurs DNS peuvent être interrogés et leurs réponses vérifiées. Un seuil d’alerte critique/avertissement est configurable. La sonde renvoie également des performance data pour générer des graphes pour chaque serveur interrogé.

#!/usr/bin/perl -w

#
# This plugin checks DNS nameservers resolution.
#
# Boris HUISGEN <bhuisgen@hbis.fr>
#

use strict;
use warnings;

use NetAddr::IP::Util qw( inet_ntoa inet_n2dx );
use Nagios::Plugin;
use Net::DNS::Dig qw( :forceEmu ndd_gethostbyaddr ndd_gethostbyname ndd_gethostbyname2 AF_INET AF_INET6 );
use Switch;

my $np = Nagios::Plugin->new(
   shortname => 'DNS',
   version   => '0.1',
   blurb     => "This plugin checks DNS nameservers resolution.",
   usage     => "Usage: %s -Q  -T  -S <SERVER1[,SERVER2 ...]> [-R [RDATA1[,RDATA2 ...]] [-w warn] [-c crit] [--debug]",
   timeout   => 15
);

$np->add_arg(
   spec     => 'query|Q=s',
   help     => '-Q ',
   required => 1
);

$np->add_arg(
   spec     => 'type|T=s',
   help     => '-T ',
   required => 1
);

$np->add_arg(
   spec     => 'servers|S=s',
   help     => '-S ',
   required => 1
);

$np->add_arg(
   spec     => 'rdatas|R=s',
   help     => '-R ',
   required => 0,
   default => ""
);

$np->add_arg(
   spec     => 'warn|w=i',
   help     => '-warn <time>',
   required => 0,
   default => 1000
);

$np->add_arg(
   spec     => 'crit|c=i',
   help     => '-crit <time>',
   required => 0,
   default => 1500
);

$np->add_arg(
   spec     => 'debug|d',
   help     => '--debug',
   required => 0,
   default => 0
);

$np->getopts;
alarm $np->opts->timeout;

my @servers = ("host");
my @rdatas = "";

@servers = split(",", $np->opts->servers) if (defined $np->opts->servers && $np->opts->servers ne "");

@rdatas = split(",", $np->opts->rdatas) if (defined $np->opts->rdatas && $np->opts->rdatas ne "");

my $max_time = 0;
my $index = 0;

foreach (@servers)
	{
   		my $server = $_;

   		my $config = {
      				Timeout   => 10,
      				PeerAddr  => $server,
      				PeerPort  => 53,
      				Proto     => 'UDP',
      				Recursion => 1
   			     };

		my $response = Net::DNS::Dig->new($config)->for($np->opts->query, $np->opts->type)
			or $np->nagios_exit(CRITICAL, $np->opts->query . "," . $np->opts->type . " - failed to resolve on server $server");

   		$max_time = $response->{ELAPSED} if ($response->{ELAPSED} > $max_time);

   		my (@results) = $response->rdata();
   		my $found = 0;

		switch ($np->opts->type)
		{
      			case "A"
			{
         			foreach (@results)
				{
            				my ($result) = inet_ntoa($_);
            				$found++;

        				if ((scalar @rdatas > 0) && ($rdatas[0] ne "") && (grep(/^$result$/, @rdatas) eq 0))
					{
               					$found = -1;
               					last;
            				}
         			}
      			}

	      		case "AAAA"
			{
         			foreach (@results)
				{
            				my ($result) = inet_n2dx($_);
            				$found++;

            				if ((scalar @rdatas > 0) && ($rdatas[0] ne "") && (grep(/^$result$/, @rdatas) eq 0))
					{
               					$found = -1;
               					last;
            				}
         			}
      			}

      			else
			{
         			foreach (@results)
				{
            				my ($result) = $_;
            				$found++;

            				if ((scalar @rdatas > 0) && ($rdatas[0] ne "") && (grep(/^$result$/, @rdatas) eq 0))
					{               					$found = -1;               					last;
            				}
         			}
      			}
		}

		$np->nagios_exit(CRITICAL, $np->opts->query . "," . $np->opts->type . " - unexpected rdata found on server $server") if ($found eq -1);
   		$np->nagios_exit(CRITICAL, $np->opts->query . "," . $np->opts->type . " - none rdata found") if ($found eq 0);
   		$np->nagios_exit(CRITICAL, $np->opts->query . "," . $np->opts->type . " - some rdatas not found") if ($found < $#rdatas);
    		$np->add_perfdata(label => "server_$index", value => $response->{ELAPSED}, uom => "ms");
   		$index++;

   		$np->nagios_exit(CRITICAL, $np->opts->query . "," . $np->opts->type . " - slow resolution time from server $server") if ($response->{ELAPSED} >= $np->opts->crit);
   		$np->nagios_exit(WARNING, $np->opts->query . "," . $np->opts->type . " - slow resolution time from server $server") if ($response->{ELAPSED} >= $np->opts->warn);
	}

$np->nagios_exit(OK, $max_time . " ms maximum response time");

# END
$np->nagios_exit(UNKNOWN,"unexpected end of script");

Exemple d’utilisation :

root@muse:~# /usr/lib/nagios/plugins/check_dns -Q blog.hbis.fr -T A -S 173.246.97.2,217.70.184.40,217.70.182.20 -R 37.59.126.51 -d
DNS OK - 22 ms maximum response time | server_0=20ms;; server_1=20ms;; server_2=22ms;;

Munin-async : nettoyage des fichiers temporaires

root@vm-mon:~# cat /etc/cron.daily/munin-asyncd
#!/bin/sh

find /var/lib/munin-async/ -type f -name 'munin-daemon.*.86400' -daystart -mtime +7 -delete

Remarque : le fichier de configuration logrotate fourni dans le package Debian est à supprimer.

Munin : configuration en mode asynchrone

La version 2 de Munin apporte un nouveau mode de récupération des données entre le master et ses nodes : le mode asynchrone. Celui-ci vise à corriger deux situations critiques à savoir :

  • lorsque le nombre de nodes à monitorer est important, le temps de récupération des données depuis les nodes est supérieur à celui de mise à jour.
  • lorsque les nodes sont distants, les connexions peuvent finir en timeout du fait d’une ligne lente ou d’un trop grand nombre d’éléments monitorés.

Dans les deux cas, le monitoring est impossible. Toutefois, ces contraintes sont levées en activant le mode asynchrone. Un démon supplémentaire munin-asyncd est alors lancé sur chaque node, dont le rôle est de se connecter à munin-node pour récupérer et stocker temporairement les résultats au format texte. Le serveur munin se contente ensuite de récupérer ces fichiers par connexion SSH pour en générer les fichiers RRD.

Je présente ici la configuration d’un node en asynchrone.

Configuration du node

root@node:~# apt-get install munin-node munin-async munin-plugins-extra

Seule l’écoute réseau en local est nécessaire :

root@node:~# nano /etc/munin/munin-node.conf
listen 120.0.0.1
allow ^127\.0\.0\.1$

Le node est prêt pour être démarré ; munin-asyncd doit être lancé à la suite  :

root@node:~# /etc/init.d/munin-node restart && /etc/init.d/munin-async restart

Configuration du serveur

root@server:~# apt-get install munin

Les clés SSH de l’utilisateur munin doivent être générées afin que celui-ci puisse se connecter en tant que munin-async sur le node :

root@server:~# su -s /bin/bash munin
munin@server:~$ ssh-keygen -t rsa
munin@server:~$ ssh-copy-id munin-async@node

L’utilisateur munin peut alors se connecter sur le node. La première connexion SSH doit être faite manuellement afin de valider la confirmation (ne pas le faire bloque la récupéreration des données) :

munin@server:~$ ssh munin-async@node

Par sécurité, je vous invite à restreindre la connexion SSH sur le node :

  • en insérant une ligne de ce type dans le fichier /etc/security/access.conf :
    +munin-async : [IP_SRV_MUNIN]
    
  • limiter la connexion SSH de l’utilisateur munin à ne pouvoir exécuter que la commande munin-async :
    munin-async@node$ nano .ssh/authorized_keys
    
    no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,command="/usr/share/munin/munin-async --spoolfetch" ssh-rsa [PUBLIC KEY]
    

Vous pouvez relancer la connexion SSH, munin-async prend la main comme shell. Ceci validera au final la configuration réseau. Reste à déclarer ce node au niveau du master :

root@server:~# nano /etc/munin/munin-node.conf
[node]
   address ssh://munin-async@node

Patientez quelques minutes (dix minutes) pour que les graphes soient visibles.

Zabbix : monitoring de Dovecot

Monitoring de l’authentification utilisateur et du nombre d’utilisateurs connectés :

UserParameter=dovecot.auth,doveadm auth -x service=imap -x rip=127.0.0.1 test@noreply.my.domain blablablaaa|grep "auth succeeded"|wc -l
UserParameter=dovecot.who,doveadm who 2>/dev/null|wc -l
Haut de page