Monitoring de l'état de la réplication ldap
Ça serait bien de monitorer l'état de la réplication ldap entre vert et chaque slave.
Ça permettrait de détecter ces problèmes avant d'avoir des remonter d'adhérent qi n'arrivent pas à se connecter en wifi/filaire pour le réplicat radius, ou d'un nom dns qui n'apparait pas pour le réplicat sur soyouz, etc…
C'est replativement simple, il y a un attribut ldap spécialement pour ça: contextCSN. Si c'est le même entre vert et un réplicat, c'est que le réplicat est à jour et que la réplication marche bien, sinon, qu'il y a un problème. La sémantique de l'attribut est disponible a cette adresse: http://www.openldap.org/faq/data/cache/1145.html. Il contient en particulier un timestamp qui permet de savoir quel est le décalage entre le master et le slave.
Voilà un exemple de plugin nagios (qui devrait marcher avec icinga2) pour faire ça. Ça s'utilise comme suit:
$ ./check_ldap.py -D 'cn=readonly,dc=crans,dc=org' -w MOTDEPASSEREADONLY -b 'dc=crans,dc=org' -s 'vert.adm.crans.org sable.adm.crans.org'
OK: ldap servers vert.adm.crans.org, sable.adm.crans.org are in sync on dc=crans,dc=org
Il faudrait voir s'il est possible de ne pas passer le mot de passe ldap sur la ligne de commande, mais plutôt dans une variable d'environnement, pour éviter que le mot de passe apparaisse dans la liste des processus.
Il y aurait deux bases à monitorer au crans: dc=crans,dc=org et cn=config.
#!/usr/bin/env python
import os
import sys
import subprocess
import tempfile
import argparse
import datetime
def get_status(binddn, password, suffix, servers):
fp, passfile = tempfile.mkstemp()
results = []
try:
os.write(fp, password)
os.close(fp)
for server in servers:
cmd = ["/usr/bin/ldapsearch", "-LLL", "-s", "base", "-y", passfile, "-D", binddn, "-b", suffix, "-H", "ldap://%s" % server, "contextCSN"]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if p.returncode > 0 or err:
print("WARNING: %s: %s" % (server, err.strip()))
return 1
csn = {}
for line in out.split('\n'):
if line.startswith('contextCSN'):
gt, count, sid, mod = line.split(':', 1)[1].strip().split('#')
gt = datetime.datetime.strptime(gt, '%Y%m%d%H%M%S.%fZ')
# this should never happend
if sid in csn:
print(csn)
raise RuntimeError("Duplicate sid")
count = int(count, 16)
# mod if always 000000 in openldap so we ignore it
csn[sid] = (gt, count)
results.append(csn)
finally:
try:
os.remove(passfile)
except OSError:
pass
max_delta_t = datetime.timedelta(0)
max_delta_count = 0
# check that all servers have the same number of contextCSN attribut
if not all(len(results[0]) == len(r) for r in results):
print("CRITICAL: ldapd servers have a different numbers of contextCSN for suffix %s" % (suffix,))
print(", ".join("%s: %s" % (s, sorted(r.keys())) for s,r in zip(servers, results)))
return 2
# check that the set of SIDs is the same an all servers
s = set(results[0].keys())
if not all(s == set(r.keys()) for r in results):
print("CRITICAL: contextCSN sids not matching betweens servers on %s" % (suffix,))
print(", ".join("%s: %s" % (s, sorted(r.keys())) for s,r in zip(servers, results)))
return 2
# for each SID, check if a server is late on anoter
for i in results[0].keys():
for r in results[1:]:
if results[0][i][0] != r[i][0]:
max_delta_t = max(max_delta_t, abs(results[0][i][0] - r[i][0]))
max_delta_count = 0
if max_delta_t == datetime.timedelta(0) and results[0][i][1] != r[i][1]:
max_delta_count = max(max_delta_count, abs(results[0][i][1] - r[i][1]))
if max_delta_t > datetime.timedelta(0):
print("CRITICAL: ldap servers %s are distant of %ss on %s" % (', '.join(servers), max_delta_t.total_seconds(), suffix))
return 2
elif max_delta_count > 0:
print("WARNING: ldap servers %s are distant of %s ops on %s" % (', '.join(servers), max_delta_count, suffix))
return 1
else:
print("OK: ldap servers %s are in sync on %s" % (', '.join(servers), suffix))
return 0
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('-D', required=True)
parser.add_argument('-w', required=True)
parser.add_argument('-b', required=True)
parser.add_argument('-s', required=True)
args = parser.parse_args()
sys.exit(get_status(args.D, args.w, args.b, args.s.split()))