Context & Problem
When upgrading to Proxmox VE 9.2 running Ceph 19 (Squid), native RESTful API monitoring endpoints can break or become unstable depending on your Zabbix / PVE API configuration.Rather than relying on external HTTPS REST requests or heavy API polling, a lightweight, highly efficient local wrapper combined with Zabbix Agent 2, Dependent Items, and JSONPath Preprocessing provides an alternative.
Architecture Overview
To keep system overhead minimal, we do not run ceph status for every single item.- Master Item (custom.ceph.status[status]): Fetches the entire cluster status JSON in a single local CLI call every 60 seconds.
- Dependent Items: Extract global health, OSD counts, and pool capacity in-memory using JSONPath without additional execution costs.
- Low-Level Discovery (LLD): Uses custom.ceph.status[osd-df] to automatically discover OSDs and parse per-OSD utilization.
Step-by-Step Configuration
1. Create the Shell Wrapper Script
On the Proxmox node, create /usr/local/bin/zabbix_ceph_status.sh:
Code:
echo "zabbix ALL=(ALL) NOPASSWD: /usr/local/bin/zabbix_ceph_status.sh" > /etc/sudoers.d/zabbix_ceph
chmod 440 /etc/sudoers.d/zabbix_ceph
2. Grant Sudo Privileges
Allow the zabbix system user to execute the script without password prompting:
Code:
echo "zabbix ALL=(ALL) NOPASSWD: /usr/local/bin/zabbix_ceph_status.sh" > /etc/sudoers.d/zabbix_ceph
chmod 440 /etc/sudoers.d/zabbix_ceph
3. Configure Zabbix Agent 2
Note: Using a custom key prefix like custom.ceph.status[*] avoids conflicts with the native zabbix-agent2-plugin-ceph reserved keys.Create /etc/zabbix/zabbix_agent2.d/ceph_script.conf:
Code:
UserParameter=custom.ceph.status[*],sudo /usr/local/bin/zabbix_ceph_status.sh "$1"
Restart the agent:
Code:
systemctl restart zabbix-agent2
Validate local execution:
Code:
zabbix_agent2 -t "custom.ceph.status[status]"
Zabbix Frontend Setup (7.4 / 7.0 LTS)
Master Items
- Ceph: Get Cluster Status Raw
- Type: Zabbix agent
- Key: custom.ceph.status[status]
- Type of information: Text
- Update Interval: 1m
- Ceph: Get OSD DF Raw
- Type: Zabbix agent
- Key: custom.ceph.status[osd-df]
- Type of information: Text
- Update Interval: 5m
Dependent Items (Parsing JSONPath)
- Global Health:
- Type: Dependent item | Master: Ceph: Get Cluster Status Raw
- Key: ceph.health.status
- JSONPath: $.health.status
- Trigger Expression: last(/Template/ceph.health.status)<>"HEALTH_OK"
- Total OSDs:
- Key: ceph.osdmap.num_osds | JSONPath: $.osdmap.num_osds
- Up OSDs:
- Key: ceph.osdmap.num_up_osds | JSONPath: $.osdmap.num_up_osds
- Trigger Expression: last(/Template/ceph.osdmap.num_up_osds)<last(/Template/ceph.osdmap.num_osds)
- Used Capacity:
- Key: ceph.pgmap.bytes_used | JSONPath: $.pgmap.bytes_used (Units: B)
Low-Level Discovery (LLD) Rules
- Name: Ceph OSD Discovery
- Type: Dependent item | Master: Ceph: Get OSD DF Raw
- LLD Macros:
- {#OSD_ID} = $.nodes
- .id
- {#OSD_NAME} = $.nodes
- .name
- Item Prototype:
- Name: OSD {#OSD_NAME}: Utilization
- Type: Dependent item | Master: Ceph: Get OSD DF Raw
- Key: ceph.osd.utilization[{#OSD_ID}]
- JSONPath Preprocessing: $.nodes[?(@.id == {#OSD_ID})].utilization.first()
Key Results
- Minimal CPU & I/O Footprint: Reduced from N execution calls down to 1 single CLI call per cycle for all cluster metrics.
- Alerting Latency: Mean Time To Detect (MTTD) under 60 seconds for HEALTH_WARN or OSD DOWN states.
- Zero API Dependency: Independent from API tokens, expired certificates, or REST module breakage.
you can import this xml file in ZABBIX if you want :
Code:
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>7.0</version>
<template_groups>
<template_group>
<uuid>7df9672f09124172b7d02820d8697265</uuid>
<name>Templates/Storage</name>
</template_group>
</template_groups>
<templates>
<template>
<uuid>a89c23f1e9484029b32e18d61234abcd</uuid>
<template>Ceph Cluster by Custom Script</template>
<name>Ceph Cluster by Custom Script</name>
<description>Monitoring Ceph Squid sur Proxmox VE via script wrapper Zabbix Agent 2 local.</description>
<groups>
<group>
<name>Templates/Storage</name>
</group>
</groups>
<items>
<item>
<uuid>b1111111111141118111111111111111</uuid>
<name>Ceph: Get Cluster Status Raw</name>
<key>custom.ceph.status[status]</key>
<delay>1m</delay>
<history>1d</history>
<trends>0</trends>
<value_type>TEXT</value_type>
<description>Récupération brute du JSON ceph status.</description>
</item>
<item>
<uuid>b2222222222242228222222222222222</uuid>
<name>Ceph: Get OSD DF Raw</name>
<key>custom.ceph.status[osd-df]</key>
<delay>5m</delay>
<history>1d</history>
<trends>0</trends>
<value_type>TEXT</value_type>
<description>Récupération brute du JSON ceph osd df.</description>
</item>
<item>
<uuid>b3333333333343338333333333333333</uuid>
<name>Ceph: Health Status</name>
<type>DEPENDENT</type>
<key>ceph.health.status</key>
<delay>0</delay>
<history>31d</history>
<trends>0</trends>
<value_type>CHAR</value_type>
<master_item>
<key>custom.ceph.status[status]</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.health.status</parameter>
</parameters>
</step>
</preprocessing>
<triggers>
<trigger>
<uuid>c1111111111141118111111111111111</uuid>
<expression>last(/Ceph Cluster by Custom Script/ceph.health.status)<>"HEALTH_OK"</expression>
<name>Ceph cluster health is degraded ({ITEM.LASTVALUE1})</name>
<opdata>Current status: {ITEM.LASTVALUE1}</opdata>
<priority>HIGH</priority>
</trigger>
</triggers>
</item>
<item>
<uuid>b4444444444444448444444444444444</uuid>
<name>Ceph: OSDs Total</name>
<type>DEPENDENT</type>
<key>ceph.osdmap.num_osds</key>
<delay>0</delay>
<history>31d</history>
<master_item>
<key>custom.ceph.status[status]</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.osdmap.num_osds</parameter>
</parameters>
</step>
</preprocessing>
</item>
<item>
<uuid>b5555555555545558555555555555555</uuid>
<name>Ceph: OSDs Up</name>
<type>DEPENDENT</type>
<key>ceph.osdmap.num_up_osds</key>
<delay>0</delay>
<history>31d</history>
<master_item>
<key>custom.ceph.status[status]</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.osdmap.num_up_osds</parameter>
</parameters>
</step>
</preprocessing>
<triggers>
<trigger>
<uuid>c2222222222242228222222222222222</uuid>
<expression>last(/Ceph Cluster by Custom Script/ceph.osdmap.num_up_osds)<last(/Ceph Cluster by Custom Script/ceph.osdmap.num_osds)</expression>
<name>One or more Ceph OSDs are DOWN</name>
<priority>HIGH</priority>
</trigger>
</triggers>
</item>
<item>
<uuid>b6666666666646668666666666666666</uuid>
<name>Ceph: Storage Total Capacity</name>
<type>DEPENDENT</type>
<key>ceph.pgmap.bytes_total</key>
<delay>0</delay>
<history>31d</history>
<units>B</units>
<master_item>
<key>custom.ceph.status[status]</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.pgmap.bytes_total</parameter>
</parameters>
</step>
</preprocessing>
</item>
<item>
<uuid>b7777777777747778777777777777777</uuid>
<name>Ceph: Storage Used</name>
<type>DEPENDENT</type>
<key>ceph.pgmap.bytes_used</key>
<delay>0</delay>
<history>31d</history>
<units>B</units>
<master_item>
<key>custom.ceph.status[status]</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.pgmap.bytes_used</parameter>
</parameters>
</step>
</preprocessing>
</item>
</items>
<discovery_rules>
<discovery_rule>
<uuid>d1111111111141118111111111111111</uuid>
<name>Ceph OSD Discovery</name>
<type>DEPENDENT</type>
<key>ceph.osd.discovery</key>
<delay>0</delay>
<master_item>
<key>custom.ceph.status[osd-df]</key>
</master_item>
<lld_macro_paths>
<lld_macro_path>
<lld_macro>{#OSD_ID}</lld_macro>
<path>$.nodes[*].id</path>
</lld_macro_path>
<lld_macro_path>
<lld_macro>{#OSD_NAME}</lld_macro>
<path>$.nodes[*].name</path>
</lld_macro_path>
</lld_macro_paths>
<item_prototypes>
<item_prototype>
<uuid>e1111111111141118111111111111111</uuid>
<name>OSD {#OSD_NAME}: Utilization</name>
<type>DEPENDENT</type>
<key>ceph.osd.utilization[{#OSD_ID}]</key>
<delay>0</delay>
<history>31d</history>
<value_type>FLOAT</value_type>
<units>%</units>
<master_item>
<key>custom.ceph.status[osd-df]</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<parameters>
<parameter>$.nodes[?(@.id == {#OSD_ID})].utilization.first()</parameter>
</parameters>
</step>
</preprocessing>
</item_prototype>
</item_prototypes>
</discovery_rule>
</discovery_rules>
</template>
</templates>
</zabbix_export>