Failover per DNS mit ISPConfig
Ich arbeite mit DNS Round Robin. Dieses hat zum Vorteil, dass der Traffic ca. zu 50% jeweils auf die IPs aufgeteilt wird.
admin@dns:~# dig cache.domain.ch
;; QUESTION SECTION:
;cache.domain.ch. IN A
;; ANSWER SECTION:
cache.domain.ch. 299 IN A 1.1.1.1
cache.domain.ch. 299 IN A 2.2.2.2
Jedoch kann man damit leider kein Failover betreiben. Sollte eine IP nicht erreichbar sein und der Client empfängt die nicht funktionierende IP, schlägt der Aufbau fehl.
Aus diesem Grund habe ich eine ISPConfig Extension entwickelt, welche in regelmässigen Abständen die Erreichbarkeit eines Services überprüft und im Fehlerfall den Record auf inactive setzt.
Hierzu nehme ich mir die TXT Records zu Hilfe. In einem TXT Record speichere ich dann die zu überprüfenden Services in einem JSON String:
admin@dns:~#dig _failover.cache.domain.ch TXT
;; QUESTION SECTION:
;_failover.cache.domain.ch. IN TXT
;; ANSWER SECTION:
_failover.cache.domain.ch. 3599 IN TXT "[{"name":"cache.domain.ch.","data":"1.1.1.1","check":"443","ttl":"300"},{"name":"cache.domain.ch.","data":"2.2.2.2","check":"443", "ttl":"300"}]"
Mit dem folgenden Script hole ich mir dann die Daten, überprüfe den Service und setze den Record dann im Fehlerfall auf deaktiviert.
<?php
/*
* ISPConfig Extensions for DNS Failover
*
* (c) Kai Tobias Burwieck <kai@burwieck.ch>
*
*/
// ISPConfig URL for REMOTE API
$soap_location = 'https://<ispconfig server>/remote/index.php';
$soap_uri = 'https://<ispconfig server>/remote/';
// ISPConfig Login
$username = '<ispconfig user>';
$password = '<ispconfig password>';
$client = new SoapClient(
null,
array(
'location' => $soap_location,
'uri' => $soap_uri,
'trace' => 1,
'exceptions' => 1
));
try {
//* Login to the remote server
if($session_id = $client->login($username,$password)) {
// get Records for failover RR
$records = $client->dns_txt_get($session_id, array('name' => '_failover.%'));
if (count($records)) {
foreach ($records as $record) {
$data = json_decode($record['data'], true);
if ($data) {
foreach ($data as $row) {
$available = check($row);
$exist = $client->dns_a_get(
$session_id,
array('name' => $row['name'], 'data' => $row['data'])
);
if ($exist) {
$rr = $exist[0];
$rr['active'] = $available ? "Y" : "N";
if($exist[0]['active'] !== $rr['active']) {
echo $rr['name'] . ' (' . $rr['data'] . ') becomes ' . ($available ? 'available' : 'unavailable') . "\n";
$rr['ttl'] = array_key_exists('ttl', $row) ? (int)$row['ttl'] : $rr['ttl'];
$client->dns_a_update($session_id, $rr['sys_userid'], $rr['id'], $rr);
}
} else {
throw new Exception('Record for ' . $row['name'] . ' (' . $row['data'] . ') not found. Please first create Records first');
}
}
} else {
throw new Exception('invalid json: ' . $record['data']);
}
}
}
}
} catch(SoapFault $e) {
echo ('SOAP Error: '.$e->getMessage());
exit(1);
} catch(Exception $e) {
echo('ERR: ' . $e->getMessage());
exit(1);
}
exit(0);
/*
* check ip
* example: array('check' => '443', 'data' => '1.2.3.4')
*
* @param array $data
*
* @return bool if given resource is available
*/
function check(array $data) {
if (array_key_exists('check', $data) && array_key_exists('data', $data)) {
$connection = @fsockopen($data['data'], $data['check'], $errno, $errstr, 1);
$available = is_resource($connection);
if ($available) {
fclose ($connection);
}
return $available;
}
throw new Exception('key check neither ip found in '.json_encode($data));
}
Sollte nun ein Service nicht erreichbar sein, gibt der DNS Server spätestens nach 5 Minuten nur noch den verfügbaren Service.
Nun nur noch den Cron hinzufügen:
* * * * * /usr/bin/php /usr/local/ispconfig/server/DNS-Failover.php 2>&1
simsalabim.
Nun extistiert ein Health Check für DNS Round Robin A Records.