Zabbix MySQL Multislave ueberwachen

Oft werden auf einem Host mehrere Datenbankinstanzen von MySQL ausgeführt. Jede Instanz ist Slave von einem anderen Master. So kann kann ein Host ein permanentes Backup von mehreren Mastern realisieren.

Nun könnte man für jede Instanz Items und Trigger in Zabbix anlegen und so die einzelnen Slave Status überwachen. Entweder Sie konfigurieren für jede Instanz einen eindeutigen Port oder den Pfad zum Socket in einem UserParameter, oder Sie übergeben diese Daten vom Server aus einen einzigen UserParameter.

Ohne viel Aufwand überwacht das unten stehende Script alle Datenbankeninstanzen eines Hosts. Es kann vom Zabbix-Agenten als UserParameter eingebunden werden.

Es sucht auf beliebigen Sockets oder Ports nach Datenbanken. Wenn die gefundene Datenbank ein MySQL Slave ist, wird der Slave Status geprüft.

Wenn alle Slaves replizieren, dann wird 0 zurückgegeben, andernfalls die Anzahl der gefundenen Fehler.

Das Script kann mit der Option "--verbose" aufgerufen werden.

#!/usr/bin/php
<?php

// Datenbank Zugangsdaten
$config['dbuser'] = 'root';
$config['dbpass'] = 'password';

// Ab welchem Wert von seconds behind master wird alarmiert
$config['threshold'] = '100';

// In welchem Ordner und allen Unterordnern soll nach Sockets gesucht werden
// Achten Sie auf die Berechtigungen, wenn dieses Script vom Zabbix-User ausgefuehrt wird
$config['basedir'] = '/srv/mysqld_multi';

// Über welche Sockets können die Datenbanken erreicht werden.
// Wenn = FALSE dann werden die Sockets automatisch in basedir gesucht.
// Man kann einen Pfad oder einen Port angeben

$config['sockets'][] = '/srv/mysqld_multi/instances/1/mysqld.sock';
$config['sockets'][] = '/srv/mysqld_multi/instances/2/mysqld.sock';
$config['sockets'][] = '/srv/mysqld_multi/instances/foo/mysqld.sock';
$config['sockets'][] = '/srv/mysqld_multi/instances/bla/mysqld.sock';
$config['sockets'][] = 3001;
$config['sockets'][] = 3002;
$config['sockets'][] = 3003;
$config['sockets'][] = 3004;
$config['sockets'][] = 3005;
$config['sockets'][] = 3006;
$config['sockets'][] = 3007;
$config['sockets'][] = 4001;


$Check = new check($config);
echo $Check->check_all();

class check
{
    private $config  = array();
    private $sockets = array(); //Liste mit Sockets zu allen laufenden Datenbanken
    private $error_count  = 0; // bleibt auf 0, wenn alle Checks keinen Fehler ergeben
    
    public function __construct($config)
    {
        $this->config = $config;
        $this->find_sockets();
    }
    
    private function slave_status($socket)
    {
        $host = 'localhost';
        if( ! function_exists('mysql_connect')) die('no mysql function in your php version.');
        if( ! is_numeric($socket) && ! file_exists($socket))
        {
            $this->message("kein zugriff auf $socket");
            return FALSE;
        }
        if(is_numeric($socket)) $host = '127.0.0.1'; // Wenn socket ein port ist, dann funktioniert localhost nicht
        
        $dblink = mysql_connect($host.':'.$socket,
                                $this->config['dbuser'],
                                $this->config['dbpass']) or die('db connect error');
        
        $dbresult = mysql_query('SHOW SLAVE STATUS',$dblink);
        
        if(mysql_affected_rows($dblink) == 0) return FALSE; // DB ist kein Slave
        
        while ($row = mysql_fetch_array($dbresult,MYSQL_ASSOC))
	{
            $return[]=$row;
	}
        return $return[0];
    }
    
    private function message($msg)
    {
        global $argv;
        if($argv[1] !== '--verbose') return FALSE;
        echo $msg."\n";
        return TRUE;
    }
    
    private function show_status($slave_status)
    {
        global $argv;
        if($argv[1] !== '--verbose') return FALSE;
        $show = array('Slave_IO_State','Master_Host','Master_Log_File',
                      'Relay_Log_File','Relay_Log_Pos','Relay_Master_Log_File',
                      'Slave_IO_Running','Slave_SQL_Running');
        foreach($slave_status as $key => $value)
        {
            if(in_array($key,$show)) echo "# ".str_pad($key,30)." ".$value."\n";
        }
        echo "\n";
        return TRUE;
    }
    
    // Finde alle mysql sockets im Basedir und mache ein array draus
    private function find_sockets()
    {
        if($this->config['sockets'] !== FALSE && is_array($this->config['sockets']))
        {
            $this->sockets = $this->config['sockets'];
        }
        else
        {
            // Auf die Rechte achten. Ggf. sudo verwenden.
            $cmd = 'sudo /usr/bin/find '.$this->config['basedir'].' -name mysqld.sock -type s';
            $shell_return = shell_exec($cmd);
            $this->sockets = preg_split("/\n/",$shell_return);
        }
    }
    
    public function check_all()
    {
        foreach($this->sockets as $socket)
        {
            $slave_status = NULL;
            $error        = FALSE;
            $this->message('Checking DB with socket or port '.$socket);
            $this->message(str_repeat('=',75));
            // hole slave status, wenn db kein slave, dann nächsten eintrag nehmen
            if(($slave_status = $this->slave_status($socket)) === FALSE)
            {
                $this->message("# No slave running\n");
                continue;
            }
            if($slave_status['Slave_IO_Running'] !== 'Yes')
            {
                $this->error_count++;
                $error = TRUE;
            }
            if($slave_status['Slave_SQL_Running'] !== 'Yes')
            {
                $this->error_count++;
                $error = TRUE;
            }
            if($slave_status['Seconds_Behind_Master'] > $this->config['threshold'])
            {
                $this->error_count++;
                $error = TRUE;
            }
            if($error)
            {
                $this->show_status($slave_status);   
            }
            else
            {
                $this->message("# No errors.\n");
            }
        }
        return $this->error_count;
    }
    
}