4편에서 API 토큰과 권한을 준비했다면, 이번 편부터 본격적으로 수집 스크립트를 구현합니다. 먼저 클러스터 전체를 대표하는 지표부터 시작합니다.
왜 클러스터·노드·VM을 분리하는가
Proxmox 모니터링을 처음 구현할 때 흔한 실수는 하나의 스크립트에 모든 수집 로직을 넣는 것입니다. 이 방식은 다음 문제를 낳습니다:
- 스크립트 실행 시간이 길어져 cron 주기 설계가 어렵습니다
- 클러스터 상태(1분마다 필요)와 디스크 SMART(10분으로 충분)를 같은 주기로 실행하게 됩니다
- 특정 수집이 실패해도 원인을 파악하기 어렵습니다
이 시리즈에서는 수집 범위를 세 개의 스크립트로 분리합니다:
| 스크립트 | 수집 대상 | 권장 주기 |
|---|---|---|
| cluster_collector.py | 클러스터 상태, HA, VM 요약 | 1분 |
| node_collector.py | 노드 CPU·메모리·네트워크 | 2분 |
| (node_collector 내 disk 모드) | 물리 디스크 SMART | 10분 |
이번 편은 cluster_collector.py를 다룹니다.
수집 대상 API 경로
| 수집 항목 | API 경로 |
|---|---|
| 클러스터 기본 상태 | /api2/json/cluster/status |
| HA 상태 | /api2/json/cluster/ha/status/current |
| HA 관리 리소스 수 | /api2/json/cluster/ha/resources |
| VM 목록 | /api2/json/cluster/resources?type=vm |
| 스토리지 목록 | /api2/json/cluster/resources?type=storage |
모두 클러스터 API이므로 어느 노드에 요청해도 동일한 응답을 받습니다.
클러스터 기본 상태 수집
/cluster/status는 클러스터 전체와 각 노드의 상태를 함께 반환합니다. 응답의 type 필드로 구분합니다:
cluster_status = api("/api2/json/cluster/status")
nodes_total = nodes_online = 0
for item in cluster_status:
if item.get("type") == "cluster":
lines.append(sl("pve.cluster.quorum", 1 if item.get("quorate") else 0))
lines.append(sl("pve.cluster.nodes.total", item.get("nodes", 0)))
lines.append(sl("pve.cluster.version", item.get("version", 0)))
elif item.get("type") == "node":
nodes_total += 1
if item.get("online"):
nodes_online += 1
lines.append(sl("pve.cluster.nodes.online", nodes_online))
lines.append(sl("pve.cluster.nodes.offline", nodes_total - nodes_online))
quorate 값은 클러스터 쿼럼 상태를 나타냅니다. 과반수 노드가 정상이면 1, 쿼럼을 잃으면 0입니다. 쿼럼을 잃은 클러스터는 VM 마이그레이션이나 HA 페일오버가 중단됩니다. 최우선 알림 대상입니다.
수집하는 Zabbix 키:
| 키 | 설명 |
|---|---|
| pve.cluster.quorum | 쿼럼 상태 (1=정상, 0=쿼럼 손실) |
| pve.cluster.nodes.total | 전체 노드 수 |
| pve.cluster.nodes.online | 온라인 노드 수 |
| pve.cluster.nodes.offline | 오프라인 노드 수 |
| pve.cluster.version | 클러스터 구성 버전 |
HA 상태 수집
/cluster/ha/status/current는 HA 구성 요소별 상태를 반환합니다. 응답의 type으로 구분합니다:
ha_status = api("/api2/json/cluster/ha/status/current")
ha_quorum = lrm_active = lrm_total = 0
ha_master = ""
for item in ha_status:
t = item.get("type")
if t == "quorum":
ha_quorum = 1 if item.get("status") == "OK" else 0
elif t == "master":
ha_master = item.get("node", "")
elif t == "lrm":
lrm_total += 1
status = item.get("status", "")
if "active" in status or "idle" in status:
lrm_active += 1
lines.append(sl("pve.cluster.ha.quorum", ha_quorum))
lines.append(sl("pve.cluster.ha.lrm.total", lrm_total))
lines.append(sl("pve.cluster.ha.lrm.active", lrm_active))
if ha_master:
lines.append(sl("pve.cluster.ha.master", ha_master))
각 구성 요소의 의미:
| type | 의미 |
|---|---|
| quorum | HA 쿼럼 서비스 상태 |
| master | 현재 HA 마스터 노드 |
| lrm | Local Resource Manager — 각 노드에서 HA 리소스를 관리하는 데몬 |
LRM(Local Resource Manager) 은 노드마다 하나씩 동작합니다. 3노드 클러스터라면 lrm_total=3이 정상입니다. lrm_active가 lrm_total보다 작으면 해당 노드의 HA 관리 기능에 문제가 생긴 것입니다.
VM 요약 수집
resources = api("/api2/json/cluster/resources?type=vm")
# 템플릿 제외
vms = [r for r in resources if r.get("template", 0) == 0]
lines += [
sl("pve.cluster.vm.total", len(vms)),
sl("pve.cluster.vm.running", sum(1 for v in vms if v.get("status") == "running")),
sl("pve.cluster.vm.stopped", sum(1 for v in vms if v.get("status") == "stopped")),
]
template 필드가 1인 항목은 VM 템플릿입니다. 실제 운영 VM만 집계하려면 제외합니다.
VM 상태가 running도 stopped도 아닌 경우(예: paused, suspended)는 두 카운트에 포함되지 않아 이상 감지에 활용할 수 있습니다.
스토리지 요약 수집
storages = api("/api2/json/cluster/resources?type=storage")
lines += [
sl("pve.cluster.storage.total", len(storages)),
sl("pve.cluster.storage.available", sum(1 for s in storages if s.get("status") == "available")),
]
available 상태가 아닌 스토리지가 있으면 트리거로 알림을 보냅니다.
Zabbix 전송 구조
수집한 모든 메트릭은 zabbix_sender로 한 번에 전송합니다:
def send(lines):
if not lines:
return
input_data = "\n".join(lines) + "\n"
proc = subprocess.run(
["docker", "exec", "-i", "zabbix-server",
"zabbix_sender", "-z", "127.0.0.1", "-p", "10051", "-i", "-"],
input=input_data, capture_output=True, text=True, timeout=30
)
sys.stderr.write(f"[INFO] sender: {proc.stdout.strip()}\n")
각 라인의 형식은 탭으로 구분된 세 필드입니다:
호스트명 키 값
예:
proxmox-prd-cluster pve.cluster.quorum 1
proxmox-prd-cluster pve.cluster.vm.running 12
DEV/PRD 분리 운영
DEV와 PRD 클러스터는 별도의 스크립트 파일로 분리합니다. 달라지는 것은 세 가지뿐입니다:
| 항목 | DEV | PRD |
|---|---|---|
| PROXMOX_HOSTS | 10.0.91.21~23 | 10.0.92.11~13 |
| API_TOKEN | DEV용 토큰 | PRD용 토큰 |
| ZABBIX_HOST | proxmox-dev-cluster | proxmox-prd-cluster |
Zabbix에서는 이 ZABBIX_HOST에 해당하는 호스트를 각각 생성하고, 클러스터 전용 Trapper 아이템을 등록해 둡니다.
Zabbix 아이템 등록
Cluster Collector가 전송하는 모든 키는 Zabbix에 Trapper 아이템으로 등록되어 있어야 합니다.
등록할 아이템 목록:
| 키 | 타입 | 값 타입 |
|---|---|---|
| pve.cluster.quorum | Trapper | Numeric (unsigned) |
| pve.cluster.nodes.total | Trapper | Numeric (unsigned) |
| pve.cluster.nodes.online | Trapper | Numeric (unsigned) |
| pve.cluster.nodes.offline | Trapper | Numeric (unsigned) |
| pve.cluster.version | Trapper | Numeric (unsigned) |
| pve.cluster.ha.quorum | Trapper | Numeric (unsigned) |
| pve.cluster.ha.lrm.total | Trapper | Numeric (unsigned) |
| pve.cluster.ha.lrm.active | Trapper | Numeric (unsigned) |
| pve.cluster.ha.master | Trapper | Text |
| pve.cluster.ha.resources | Trapper | Numeric (unsigned) |
| pve.cluster.vm.total | Trapper | Numeric (unsigned) |
| pve.cluster.vm.running | Trapper | Numeric (unsigned) |
| pve.cluster.vm.stopped | Trapper | Numeric (unsigned) |
| pve.cluster.storage.total | Trapper | Numeric (unsigned) |
| pve.cluster.storage.available | Trapper | Numeric (unsigned) |
> [스크린샷 위치: Data collection > Hosts > proxmox-prd-cluster > Items > Create item]
트리거 설계
클러스터 수준에서 알림이 필요한 조건들입니다:
# 쿼럼 손실 — Disaster
last(/proxmox-prd-cluster/pve.cluster.quorum) = 0
# HA 쿼럼 이상 — High
last(/proxmox-prd-cluster/pve.cluster.ha.quorum) = 0
# 오프라인 노드 발생 — High
last(/proxmox-prd-cluster/pve.cluster.nodes.offline) > 0
# LRM 비정상 노드 발생 — Average
last(/proxmox-prd-cluster/pve.cluster.ha.lrm.active)
< last(/proxmox-prd-cluster/pve.cluster.ha.lrm.total)
# 스토리지 비가용 — High
last(/proxmox-prd-cluster/pve.cluster.storage.available)
< last(/proxmox-prd-cluster/pve.cluster.storage.total)
crontab 등록
# Proxmox PRD Cluster Collector (1분)
* * * * * /usr/bin/python3 /data/zabbix/scripts/proxmox_cluster_collector_prod.py --collect >> /var/log/zabbix-collector.log 2>&1
1분 주기로 실행하면 클러스터 이상 발생 후 최대 1분 내에 Zabbix에서 감지할 수 있습니다.
마치며
이번 편에서 구현한 내용:
- 클러스터 상태, HA(quorum/master/LRM), VM 요약, 스토리지 현황 수집
- 템플릿 VM 제외 처리
- DEV/PRD 스크립트 분리 구조
- Zabbix Trapper 아이템 목록 및 트리거 설계
다음 편에서는 노드별 CPU, 메모리, rootfs, 네트워크 트래픽을 수집하는 Node Collector를 구현합니다. 멀티노드 병렬 처리와 netstat 데이터의 타입 함정도 함께 다룹니다.