ICINGA2 - Installation, configuration et maintenance
echo "deb https://packages.icinga.com/ubuntu icinga-${DIST} main" > /etc/apt/sources.list.d/${DIST}-icinga.list
apt-get update
AJOUT DU REGISTRE UBUNTU ET INSTALLATION DU MODULE
apt-get install icingadb
CRĂER UNE BASE DE DONNĂES
mysql -u root -p icingadb </usr/share/icingadb/schema/mysql/schema.sql
systemctl enable --now icingadb
INSTALLATION DU MODULE
Suivre ce lien pour les instructions :
apt-get update
Ref.: https://icinga.com/docs/icinga-web/latest/doc/02-Installation/02-Ubuntu/
Successfully connected to existing database "icingaweb2"...
Creating database schema...
Login "icingaweb2" already exists...
Required privileges were already granted to login "icingaweb2".
The database has been fully set up!
General configuration has been successfully written to: /etc/icingaweb2/config.ini
Authentication configuration has been successfully written to: /etc/icingaweb2/authentication.ini
Account "admin" has been successfully created.
Account "admin" has been successfully defined as initial administrator.
User Group Backend configuration has been successfully written to: /etc/icingaweb2/groups.ini
User Group "Administrators" has been successfully created.
Account "admin" has been successfully added as member to user group "Administrators".
Resource configuration has been successfully written to: /etc/icingaweb2/resources.ini
Monitoring backend configuration has been successfully written to: /etc/icingaweb2/modules/monitoring/backends.ini
Resource configuration has been successfully updated: /etc/icingaweb2/resources.ini
Command transport configuration has been successfully created: /etc/icingaweb2/modules/monitoring/commandtransports.ini
Monitoring security configuration has been successfully created: /etc/icingaweb2/modules/monitoring/config.ini
Module "doc" has been successfully enabled.
Module "monitoring" has been successfully enabled.
AJOUT DU REGISTRE UBUNTU ET INSTALLATION DU MODULE
apt update
apt -y install apt-transport-https wget gnupg
wget -O - https://packages.icinga.com/icinga.key | gpg --dearmor -o /usr/share/keyrings/icinga-archive-keyring.gpg
. /etc/os-release; if [ ! -z ${UBUNTU_CODENAME+x} ]; then DIST="${UBUNTU_CODENAME}"; else DIST="$(lsb_release -c| awk '{print $2}')"; fi; \
echo "deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/ubuntu icinga-${DIST} main" > \
/etc/apt/sources.list.d/${DIST}-icinga.list
echo "deb-src [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/ubuntu icinga-${DIST} main" >> \
/etc/apt/sources.list.d/${DIST}-icinga.list
apt update
apt install icinga-director
CRĂER UNE BASE DE DONNĂES
mysql -e "CREATE DATABASE director CHARACTER SET 'utf8';
CREATE USER director@localhost IDENTIFIED BY 'CHANGEME';
GRANT ALL ON director.* TO director@localhost;"CONFIGURER LE MODULE DANS ICINGA2
Se connecter en admin sur l'interface de Icinga Web 2
Créez une nouvelle ressource pour la base de données Icinga Director :
Configuration â Application â Ressources (configurer utf8 comme encodage) SĂ©lectionner Icinga Director du menu principal pour ĂȘtre redirigĂ© vers l'assistant de dĂ©marrage
Suivre les instructions et le tour est joué !
Ref. : https://icinga.com/docs/icinga-director/latest/doc/02-Installation/Ubuntu/
SAUVEGARDES
MISES Ă JOUR
STOCKAGE
MĂMOIRE
CHARGE CPU
VĂRIFICATION DES MISES Ă JOUR
CERTIFICATS TLS
DHCP
DNS
FAIL2BAN
SSH
Objectif
Standardiser et alléger les sondes Icinga déployées sur toutes les VM en remplaçant les scripts Python par des binaires Zig statiques, rapides au démarrage, avec une approche hybride :
-
Multicall âcoreâ : un seul binaire (
znagios
) sélectionne la sonde viaargv[0]
(symlink) ou la 1re sous-commande. -
Binaires dédiés : pour les sondes lourdes (TLS/HTTP complet, DNS, SNMP, DB) afin de ne pas enfler le core.
Pourquoi Zig ici
-
Coldâstart en ms, RSS minuscule, statique musl â pas de dĂ©pendances.
-
Pas de GC / pas de runtime â latence prĂ©visible.
-
Crossâcompile simple (x86_64/ARM, Linux musl).
-
Interop C si besoin (ABI stable pour extensions).
Note perf : en mode multicall, le noyau ne charge que les pages exĂ©cutĂ©es. Les autres sondes, bien que prĂ©sentes dans lâELF, ne sont pas rĂ©sidentes.
Architecture
/opt/icinga-checks/
znagios # binaire multicall (ReleaseSmall, statique musl)
check_load -> znagios # symlink
check_mem -> znagios # symlink
check_disk -> znagios # symlink
check_tcp -> znagios # symlink
# lourds (si requis sur lâhĂŽte)
check_http # binaire dĂ©diĂ© (TLS, redirects, SNIâŠ)
check_dns # binaire dédié
Contrat Nagios/Icinga :
-
Exit codes :
0 OK
·1 WARNING
·2 CRITICAL
·3 UNKNOWN
. -
Sortie : une ligne + perfdata aprĂšs
|
(label=value[UOM];warn;crit;min;max
).
Sondes âcoreâ (dans znagios
)
-
load
: parse/proc/loadavg
, seuils sur load1. -
mem
: parse/proc/meminfo
â% used
= Total â Free â Buffers â Cached â SReclaimable. -
disk
:statvfs()
sur un mountpoint (MiB ou %). -
tcp
: connexion TCP avec timeout ms (latence + code). -
file-age
: Ăąge dâun fichier/journal en s (use-case : fraĂźcheur CRL, dumps, exports). -
systemd-active
: statut dâune unit (viasystemctl is-active
ou DâBus plus tard).
RĂšgle dâor : communes, courtes, sans piles rĂ©seau complexes â dans le multicall. RĂ©seaux/crypto/parsing riche â binaire dĂ©diĂ©.
Exemple de squelette multicall (Zig)
Minimum viable pour
load
etmem
. Ătendre avecdisk
,tcp
, etc.
const std = @import("std");
const OK: u8 = 0; const WARN: u8 = 1; const CRIT: u8 = 2; const UNK: u8 = 3;
fn out(status: []const u8, code: u8, fmt: []const u8, args: anytype) noreturn {
const w = std.io.getStdOut().writer();
w.print("{s} - ", .{status}) catch {}; w.print(fmt, args) catch {}; w.print("\n", .{}) catch {};
std.process.exit(code);
}
fn f64Or(s: []const u8, def: f64) f64 { return std.fmt.parseFloat(f64, s) catch def; }
fn u64Or(s: []const u8, def: u64) u64 { return std.fmt.parseUnsigned(u64, s, 10) catch def; }
fn check_load(args: [][]const u8) noreturn {
var warn: f64 = 2.0; var crit: f64 = 4.0;
for (args) |a| {
if (std.mem.startsWith(u8, a, "--warn=")) warn = f64Or(a[7..], warn)
else if (std.mem.startsWith(u8, a, "--crit=")) crit = f64Or(a[7..], crit)
else if (std.mem.eql(u8, a, "--help")) out("UNKNOWN", UNK, "Usage: [--warn=2.0] [--crit=4.0]", .{});
}
var f = std.fs.cwd().openFile("/proc/loadavg", .{ .read = true }) catch { out("UNKNOWN", UNK, "cannot open /proc/loadavg", .{}); }; defer f.close();
var buf: [128]u8 = undefined; const n = f.read(&buf) catch 0; const line = buf[0..n];
var t = std.mem.tokenizeAny(u8, line, " \t\n");
const l1 = std.fmt.parseFloat(f64, t.next() orelse "0") catch 0.0;
const l5 = std.fmt.parseFloat(f64, t.next() orelse "0") catch 0.0;
const l15= std.fmt.parseFloat(f64, t.next() orelse "0") catch 0.0;
var code: u8 = OK; var txt: []const u8 = "OK";
if (l1 >= crit) { code = CRIT; txt = "CRITICAL"; } else if (l1 >= warn) { code = WARN; txt = "WARNING"; }
out(txt, code, "1m={d:.2}, 5m={d:.2}, 15m={d:.2} | load1={d:.2};{d:.2};{d:.2};0; load5={d:.2};{d:.2};{d:.2};0; load15={d:.2};{d:.2};{d:.2};0;",
.{ l1, l5, l15, l1, warn, crit, l5, warn, crit, l15, warn, crit });
}
fn check_mem(args: [][]const u8) noreturn {
var warn: u8 = 80; var crit: u8 = 90;
for (args) |a| { if (std.mem.startsWith(u8,a,"--warn=")) warn = @intCast(u8, u64Or(a[7..], warn));
else if (std.mem.startsWith(u8,a,"--crit=")) crit = @intCast(u8, u64Or(a[7..], crit));
else if (std.mem.eql(u8, a, "--help")) out("UNKNOWN", UNK, "Usage: [--warn=80] [--crit=90]", .{}); }
var f = std.fs.cwd().openFile("/proc/meminfo", .{ .read = true }) catch { out("UNKNOWN", UNK, "cannot open /proc/meminfo", .{}); }; defer f.close();
var buf: [4096]u8 = undefined; const n = f.read(&buf) catch 0; const txt = buf[0..n];
var total:u64=0; var free_:u64=0; var buffers:u64=0; var cached:u64=0; var srecl:u64=0;
var it = std.mem.tokenizeAny(u8, txt, "\n");
while (it.next()) |line| {
if (std.mem.startsWith(u8, line, "MemTotal:")) total = u64Or(std.mem.trim(u8, line[9..], " \tkBK:"), 0)
else if (std.mem.startsWith(u8, line, "MemFree:")) free_ = u64Or(std.mem.trim(u8, line[8..], " \tkBK:"), 0)
else if (std.mem.startsWith(u8, line, "Buffers:")) buffers = u64Or(std.mem.trim(u8, line[8..], " \tkBK:"), 0)
else if (std.mem.startsWith(u8, line, "Cached:")) cached = u64Or(std.mem.trim(u8, line[7..], " \tkBK:"), 0)
else if (std.mem.startsWith(u8, line, "SReclaimable:")) srecl= u64Or(std.mem.trim(u8, line[13..], " \tkBK:"), 0);
}
if (total == 0) out("UNKNOWN", UNK, "no MemTotal in /proc/meminfo", .{});
const used_kb = total - free_ - buffers - cached - srecl;
const pct = @intCast(u8, @divTrunc(used_kb * 100, total));
const used_mib: u64 = used_kb / 1024; const total_mib: u64 = total / 1024;
var code: u8 = OK; var txt: []const u8 = "OK"; if (pct >= crit) { code = CRIT; txt = "CRITICAL"; } else if (pct >= warn) { code = WARN; txt = "WARNING"; }
out(txt, code, "mem_used={d}% ({d}MiB/{d}MiB) | mem_used_pct={d}%;{d};{d};0;100 mem_used={d}MiB;;;0;{d}", .{ pct, used_mib, total_mib, pct, warn, crit, used_mib, total_mib });
}
fn dispatch(cmd: []const u8, args: [][]const u8) noreturn {
if (std.mem.eql(u8, cmd, "check_load") or std.mem.eql(u8, cmd, "load")) check_load(args);
if (std.mem.eql(u8, cmd, "check_mem") or std.mem.eql(u8, cmd, "mem")) check_mem(args);
out("UNKNOWN", UNK, "unknown command '{s}' (try: load, mem)", .{cmd});
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit();
const A = arena.allocator();
var it = std.process.argsWithAllocator(A) catch @panic("args oom"); defer it.deinit();
const argv0 = it.next() orelse "znagios"; const base = std.fs.path.basename(argv0);
var args = std.ArrayList([]const u8).init(A); defer args.deinit();
while (it.next()) |a| try args.append(a);
if (std.mem.startsWith(u8, base, "check_")) dispatch(base, args.items);
if (args.items.len == 0) out("UNKNOWN", UNK, "Usage: znagios <load|mem> [options]", .{});
dispatch(args.items[0], args.items[1..]);
}
Build & crossâcompile
Binaire statique musl, taille mini.
# Linux x86_64
zig build-exe -O ReleaseSmall -target x86_64-linux-musl -fstrip src/znagios.zig -o znagios
# ARM (Raspberry/edge)
zig build-exe -O ReleaseSmall -target aarch64-linux-musl -fstrip src/znagios.zig -o znagios-arm64
Optionnel (profilage taille) : LTO est implicite dans ReleaseSmall ; garder le code simple (pas dâexceptions, pas dâalloc implicite).
Installation & symlinks
install -d /opt/icinga-checks
install -m 0755 znagios /opt/icinga-checks/
ln -sf /opt/icinga-checks/znagios /opt/icinga-checks/check_load
ln -sf /opt/icinga-checks/znagios /opt/icinga-checks/check_mem
# idem pour check_disk, check_tcp, file-age, systemd-active
Intégration Icinga2
CheckCommand :
object CheckCommand "zig_check_load" {
command = [ "/opt/icinga-checks/check_load" ]
arguments = {
"--warn=" = "$load_warn$"
"--crit=" = "$load_crit$"
}
vars.load_warn = "2.0"
vars.load_crit = "4.0"
}
object CheckCommand "zig_check_mem" {
command = [ "/opt/icinga-checks/check_mem" ]
arguments = {
"--warn=" = "$mem_warn$"
"--crit=" = "$mem_crit$"
}
vars.mem_warn = "80"
vars.mem_crit = "90"
}
Apply :
apply Service "load" {
check_command = "zig_check_load"
assign where host.vars.os == "Linux"
}
apply Service "mem" {
check_command = "zig_check_mem"
assign where host.vars.os == "Linux"
}
Déploiement (Ansible)
- name: Deploy Zig Icinga checks
hosts: linux_vms
become: true
tasks:
- file: { path: /opt/icinga-checks, state: directory, mode: "0755" }
- copy: { src: files/znagios, dest: /opt/icinga-checks/znagios, mode: "0755" }
- file: { src: /opt/icinga-checks/znagios, dest: /opt/icinga-checks/check_load, state: link }
- file: { src: /opt/icinga-checks/znagios, dest: /opt/icinga-checks/check_mem, state: link }
- command: systemctl reload icinga2
Hardening (systemd, si un check tourne en daemon)
La plupart des checks sont oneâshot. Si un miniâdaemon est requis (ex. exporter Prometheus), durcir :
[Unit]
Description=Zig mini-agent (example)
After=network-online.target
[Service]
ExecStart=/opt/icinga-checks/znagios exporter --port=9100
User=agent
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectControlGroups=yes
MemoryMax=32M
LockPersonality=yes
CapabilityBoundingSet=
AmbientCapabilities=
IPAddressDeny=any
[Install]
WantedBy=multi-user.target
Pour une sonde ICMP (ping), préférer un binaire dédié avec
AmbientCapabilities=CAP_NET_RAW
seulement si nécessaire.
Packaging .deb
(optionnel)
fpm -s dir -t deb -n zig-icinga-core -v 0.1.0 \
--deb-no-default-config-files \
/opt/icinga-checks/znagios
# post-install pour créer les symlinks
cat > postinst.sh <<'SH'
#!/bin/sh
set -e
ln -sf /opt/icinga-checks/znagios /opt/icinga-checks/check_load
ln -sf /opt/icinga-checks/znagios /opt/icinga-checks/check_mem
exit 0
SH
chmod +x postinst.sh
fpm -s dir -t deb -n zig-icinga-core -v 0.1.0 \
--after-install postinst.sh \
/opt/icinga-checks/znagios
Mesurer & valider
-
Taille/RSS :
\time -v /opt/icinga-checks/check_load
âMaximum resident set size
. -
Perfdata : vérifier formats Nagios (UOM, ranges, warn/crit).
-
Charge : exécutions en boucle (
for i in {1..1000}; do ...
) pour valider latence/stabilité.
Migration depuis Python
-
Mapping 1âĂ â1 : crĂ©er
check_*
Zig avec la mĂȘme sĂ©mantique (args, messages, perfdata). -
DĂ©ploiement progressif : conserver lâancien script sous un autre nom (
*.py.old
) pendant une fenĂȘtre de rollback. -
Rollback : renommer symlink si besoin (Zig â Python) ; pas de redĂ©ploiement.
-
Désinstallation : supprimer les scripts Python une fois la stabilité confirmée.
Sécurité
-
ELF statique musl â moins de dĂ©pendances systĂšme â surface dâattaque rĂ©duite.
-
Principe du moindre privilĂšge : user dĂ©diĂ©, pas de network pour les checks qui nâen ont pas besoin (
IPAddressDeny=any
). -
EntrĂ©es non fiables : toujours valider/parsers stricts si entrĂ©e externe (HTTP/DNS â binaire dĂ©diĂ©, tests).
Roadmap (suggestion)
-
Ajouter
disk
,tcp
,file-age
,systemd-active
au core. -
Binaire dédié :
check_http
(TLS, SNI, redirects, cert-expiry),check_dns
. -
Option de build Ă la carte :
-Dmulti=true|false
,-Denable_http=false
, etc. (build.zig
). -
Intégration Prometheus (exporter léger) si utile.
FAQ
Q : Le multicall chargeâtâil tout en mĂ©moire ?
Non. Le noyau ne mappe que les pages exécutées. Impact mémoire comparable à un binaire par sonde.
Q : Pourquoi pas garder Python ?
Parfait pour la glue/API/ETL. Pour des checks fréquents et courts, le natif statique réduit latence, dépendances et taille.
Q : Et Rust ?
Excellente option pour des parseurs/protos complexes (sûreté mémoire). Ici, Zig offre un meilleur ratio simplicité/empreinte.
Annexes
-
Référence perfdata :
label=value[UOM];warn;crit;min;max
(UOM:s,ms,%,B,KB,MB,GB,TB,c
). -
UOM recommandées :
B
/MiB
pour mémoire/disque,ms
pour latences,%
pour ratios. -
Conventions : messages courts, verbes dâaction, labels cohĂ©rents (
load1
,mem_used_pct
,fs_used_pct
,tcp_latency_ms
).