Zabbix 인프라 모니터링 구축 – 05. 클러스터 수집: Quorum·HA·VM 요약 모니터링 (Cluster Collector)

2026.09.25

·

4편에서 API 토큰과 권한을 준비했다면, 이번 편부터 본격적으로 수집 스크립트를 구현합니다. 먼저 클러스터 전체를 대표하는 지표부터 시작합니다.


왜 클러스터·노드·VM을 분리하는가

Proxmox 모니터링을 처음 구현할 때 흔한 실수는 하나의 스크립트에 모든 수집 로직을 넣는 것입니다. 이 방식은 다음 문제를 낳습니다:

  • 스크립트 실행 시간이 길어져 cron 주기 설계가 어렵습니다
  • 클러스터 상태(1분마다 필요)와 디스크 SMART(10분으로 충분)를 같은 주기로 실행하게 됩니다
  • 특정 수집이 실패해도 원인을 파악하기 어렵습니다

이 시리즈에서는 수집 범위를 세 개의 스크립트로 분리합니다:

Sponsored

스크립트수집 대상권장 주기
cluster_collector.py클러스터 상태, HA, VM 요약1분
node_collector.py노드 CPU·메모리·네트워크2분
(node_collector 내 disk 모드)물리 디스크 SMART10분

이번 편은 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의미
quorumHA 쿼럼 서비스 상태
master현재 HA 마스터 노드
lrmLocal 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 클러스터는 별도의 스크립트 파일로 분리합니다. 달라지는 것은 세 가지뿐입니다:

항목DEVPRD
PROXMOX_HOSTS10.0.91.21~2310.0.92.11~13
API_TOKENDEV용 토큰PRD용 토큰
ZABBIX_HOSTproxmox-dev-clusterproxmox-prd-cluster

Zabbix에서는 이 ZABBIX_HOST에 해당하는 호스트를 각각 생성하고, 클러스터 전용 Trapper 아이템을 등록해 둡니다.


Zabbix 아이템 등록

Cluster Collector가 전송하는 모든 키는 Zabbix에 Trapper 아이템으로 등록되어 있어야 합니다.

등록할 아이템 목록:

키타입값 타입
pve.cluster.quorumTrapperNumeric (unsigned)
pve.cluster.nodes.totalTrapperNumeric (unsigned)
pve.cluster.nodes.onlineTrapperNumeric (unsigned)
pve.cluster.nodes.offlineTrapperNumeric (unsigned)
pve.cluster.versionTrapperNumeric (unsigned)
pve.cluster.ha.quorumTrapperNumeric (unsigned)
pve.cluster.ha.lrm.totalTrapperNumeric (unsigned)
pve.cluster.ha.lrm.activeTrapperNumeric (unsigned)
pve.cluster.ha.masterTrapperText
pve.cluster.ha.resourcesTrapperNumeric (unsigned)
pve.cluster.vm.totalTrapperNumeric (unsigned)
pve.cluster.vm.runningTrapperNumeric (unsigned)
pve.cluster.vm.stoppedTrapperNumeric (unsigned)
pve.cluster.storage.totalTrapperNumeric (unsigned)
pve.cluster.storage.availableTrapperNumeric (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 데이터의 타입 함정도 함께 다룹니다.


MORE POSTS

다른 글 보기

테크 랩

PMBOK 완독기 – 00. 오리엔테이션: 왜 정석을 완독하나 + 6판·7판 지도

PMBOK을 목차대로 완독하며 IT 현장에 번역하는 시리즈 시작. 6판(10지식영역)·7판(원칙·테일러링) 지도와 15편 로드맵.
2026.10.22
guniq 인사이트 썸네일 — 중소기업 OpenLDAP 통합 인증 구축 – 07. 백업·로그·운영 자동화: 장애 없는 LDAP 서버 유지하기
테크 랩

중소기업 OpenLDAP 통합 인증 구축 – 07. 백업·로그·운영 자동화: 장애 없는 LDAP 서버 유지하기

LDAP 서버는 설치 후가 더 중요합니다. 데이터 유실을 막는 백업 전략(daily/weekly/monthly), slapd 로그 관리와 logrotate 설정, 헬스체크 자동화까지 — 실제 운영 중인 스크립트를 바탕으로 LDAP 서버를 안정적으로 유지하는 방법을 정리합니다.
2026.10.21
guniq 인사이트 썸네일 — 중소기업 OpenLDAP 통합 인증 구축 – 06. Synology NAS LDAP 연동: DSM 설정과 그룹 권한 매핑
테크 랩

중소기업 OpenLDAP 통합 인증 구축 – 06. Synology NAS LDAP 연동: DSM 설정과 그룹 권한 매핑

Synology DSM의 LDAP 클라이언트를 커스텀 프로파일로 설정하는 방법을 정리합니다. groupPosixAux 필터, memberUid 매핑, LDAPS 인증서 처리까지 — 표준을 지킨 LDAP 구조가 NAS 연동을 얼마나 단순하게 만드는지 확인합니다.
2026.10.20

프로젝트 문의 환영합니다

기획부터 개발, 운영까지 함께 만들어 드립니다.

무료 3분 자가진단

우리 회사, 자체 클라우드가 답일까?

AWS vs 자체 인프라 · 11개 항목 3분 체크