모니터링 & 알림
PureCVisor는 외부 에이전트 없이 자체 메트릭 수집, 알림 엔진, Prometheus 호환 엔드포인트를 내장한다.
8.1 자체 node_exporter
섹션 제목: “8.1 자체 node_exporter”ebpf_telemetry.c가 10개 콜렉터로 ~178개 Prometheus 메트릭을 직접 수집한다 (node_* 126 + purecvisor_* 52).
별도의 node_exporter 설치가 불필요하다.
| 콜렉터 | 메트릭 수 | 수집 내용 |
|---|---|---|
| CPU | ~18 | per-core 9모드 (user/system/idle/iowait/irq/softirq/steal/nice/guest) |
| Memory | ~60+ | meminfo 전 필드 (Slab/SReclaimable/Swap/PageFault) |
| Diskstats | ~12 | IOPS, read/write bytes, IO time |
| Filesystem | ~6 | 마운트별 total/free/used |
| Netdev | ~8 | rx/tx bytes/packets, Error, Drop |
| VMstat | ~4 | pgfault, pgmajfault, pswpin, pswpout |
| Sockstat | ~4 | TCP/UDP 소켓 수 |
| Pressure (PSI) | ~6 | CPU/memory/io some/full |
| Hwmon | 가변 | 온도, 팬 속도 |
| Misc | ~8 | boot_time, entropy, filefd, conntrack, ARP, NIC meta |
추가 purecvisor_* 메트릭 (~44개):
purecvisor_cb_state/purecvisor_cb_failures(서킷 브레이커)purecvisor_vm_locks_held(VM 상태 잠금)purecvisor_tls_cert_expiry_days(인증서 만료)purecvisor_zpool_*(6개: size/alloc/free/frag/cap/health)purecvisor_worker_pool_pending(GTask 큐 깊이)purecvisor_audit_queue_depth(감사 큐)purecvisor_connpool_idle/active/max(커넥션 풀)
# Prometheus 메트릭 엔드포인트 (인증 불필요)curl -s http://127.0.0.1:8080/api/v1/metrics
# 특정 메트릭 필터curl -s http://127.0.0.1:8080/api/v1/metrics | grep purecvisor_cb_state8.2 프로세스 모니터
섹션 제목: “8.2 프로세스 모니터”process_monitor.c가 /proc/[pid]/stat+io를 20초 주기로 스캔하여 프로세스별 CPU%, 메모리, I/O를 추적한다.
| 항목 | 값 |
|---|---|
| 스캔 주기 | 20초 |
| 최대 추적 수 | 512 프로세스 |
| CPU% 계산 | delta(utime+stime) / delta(total) |
| 정렬 | CPU% 내림차순 Top N |
# CLIpcvctl monitor processes --top 10
# RESTcurl -s -H "Authorization: Bearer $TOKEN" \ http://127.0.0.1:8080/api/v1/processes | python3 -m json.tool
# RPCecho '{"jsonrpc":"2.0","method":"monitor.processes","params":{"top":10},"id":"1"}' \ | nc -U /var/run/purecvisor/daemon.sock8.3 알림 엔진
섹션 제목: “8.3 알림 엔진”WhaTap 지속 조건(sustained condition) 모델을 기반으로 한 알림 시스템.
| 파라미터 | 기본값 | daemon.conf 키 |
|---|---|---|
| 평가 주기 | 5초 | 하드코딩 |
| eval_period | 30초 | [alert] eval_period |
| dedup_window | 300초 | [alert] dedup_window |
| CPU 경고/위험 | 80% / 95% | [alert] cpu_warn / cpu_crit |
| Memory 경고/위험 | 85% / 95% | [alert] mem_warn / mem_crit |
| Disk 경고/위험 | 80% / 90% | [alert] disk_warn / disk_crit |
| 히스토리 | 1000건 링버퍼 | 하드코딩 |
알림 평가 흐름:
5초 주기 → 메트릭 샘플링 → 임계값 비교 → eval_period(30초) 연속 초과 확인 → dedup_window(300초) 내 중복 제거 → 웹훅 발송 + 히스토리 기록런타임 설정 변경과 revision
섹션 제목: “런타임 설정 변경과 revision”알림 설정은 process-local config_revision으로 낙관적 동시성을 제어한다.
alert.config.set 요청은 alert.config.get에서 읽은 양의 정수
expected_revision을 반드시 포함해야 한다.
서버는 revision 비교, 부분 patch 적용,
최종 설정 전체 검증, commit, revision 증가를 하나의 원자 구간에서 수행한다.
# CLI는 내부에서 GET→SET을 한 번씩 수행한다.pcvctl alert set --cpu_warn 82
# raw JSON-RPC를 사용할 때는 조회한 revision을 직접 전달한다.echo '{"jsonrpc":"2.0","method":"alert.config.set","params":{"expected_revision":7,"cpu_warn":82},"id":"1"}' \ | nc -U /var/run/purecvisor/daemon.sock- 성공한 SET과 유효한 reload만 revision을 정확히 1 증가시킨다.
- 오래된 revision은
-32002로 거절되며 CLI는 자동 재시도하지 않는다. - CPU/Memory/Disk/DataPool 임계값은 각각
0..100범위이며Warning < Critical이어야 한다.
eval_period는5..600초다. - 잘못된 타입, 알 수 없는 키, 임계값 쌍/범위 위반, 잘못된 URL은
-32602로 요청 전체가 거절된다.
실패 시 설정과 revision은 변하지 않는다. alert.config.reload의 source가 잘못되면 현재 런타임 설정을 보존하고daemon_config_valid=false,daemon_config_error=invalid_alert_config로 경고한다.webhook_secret원문은 GET/SET 성공 응답, 로그, 오류에 반환하지 않는다.
응답의webhook_secret_configuredboolean으로 설정 여부만 확인한다.- 시작 source가 잘못되면 자동 metric 평가가 꺼진 안전 기본값으로 시작한다.
enabled=false는 CPU/메모리/디스크/DataPool 자동 평가만 멈춘다.
보안 이벤트, 직접 운영 이벤트, 히스토리, ACK, 에스컬레이션 처리는 계속 동작한다.alert.config.set과pcvctl alert set변경은 현재 프로세스에만 적용된다.
daemon.conf를 영속 수정하지 않으며, 프로세스 재시작 시 daemon.conf 값으로 복원된다.
라이브 JSON-RPC 계약은 응답 가능한 실제 daemon에서 strict 모드로 검증한다.
daemon socket이 없거나 응답하지 않으면 SKIP이 아니라 실패한다.
sudo env PCV_R7_ALERT_LIVE=1 \ bash tests/integration/test_alert_config_live.sh이 전용 gate는 daemon.conf를 root 전용 백업으로 보존하고 모든 종료 경로에서
원본 파일과 서비스를 복구한다.
VM·스토리지·호스트 네트워크는 변경하지 않는다.
DataPool 디스크 모니터링 (v1.0)
섹션 제목: “DataPool 디스크 모니터링 (v1.0)”ZFS pool 사용량을 purecvisor_zpool_* 메트릭으로 수집하고, disk_warn/disk_crit 임계값 초과 시 알림을 발생시킨다.
8.4 비동기 웹훅
섹션 제목: “8.4 비동기 웹훅”GTask 스레드에서 비동기로 웹훅을 발송하여 메인 루프를 블로킹하지 않는다.
| 형식 | 설정값 | 페이로드 |
|---|---|---|
| Slack | webhook_format=slack | {"text": "..."} |
| Telegram | webhook_format=telegram | {"chat_id": "...", "text": "..."} |
| Generic | webhook_format=generic | PureCVisor JSON 전문 |
webhook_secret이 설정된 웹훅에는 HMAC-SHA256 서명 헤더
X-PureCVisor-Signature를 포함한다.
# daemon.conf[alert]enabled=truecpu_warn=80cpu_crit=95mem_warn=85mem_crit=95disk_warn=80disk_crit=90data_pool_warn=80data_pool_crit=90eval_period=30dedup_window=300webhook_url=https://hooks.slack.com/services/T.../B.../xxxwebhook_crit_url=https://events.example.com/purecvisor/criticalwebhook_secret=ENC:...webhook_format=slacktelegram_chat_id=8.5 알림 ACK & 에스컬레이션 (v1.0)
섹션 제목: “8.5 알림 ACK & 에스컬레이션 (v1.0)”미확인(unacknowledged) 알림은 10분 후 자동 재전송된다.
# 알림 히스토리 조회pcvctl alert list
# 알림 확인(ACK)pcvctl alert ack --id <alert-id>
# RESTcurl -s -H "Authorization: Bearer $TOKEN" \ http://127.0.0.1:8080/api/v1/alerts | python3 -m json.tool
# REST — per-alert ACK (OPERATOR 이상)curl -s -X POST -H "Authorization: Bearer $TOKEN" \ http://127.0.0.1:8080/api/v1/alerts/<alert-id>/ack
# RPCecho '{"jsonrpc":"2.0","method":"alert.history","params":{},"id":"1"}' \ | nc -U /var/run/purecvisor/daemon.sock
# RPC — per-alert ACKecho '{"jsonrpc":"2.0","method":"alert.ack","params":{"alert_id":501},"id":"1"}' \ | nc -U /var/run/purecvisor/daemon.sock웹 UI에서도 같은 동작을 한다.
관제 → 알림의 알림 이력 표 마지막 열이 확인 열이며,
미확인 행에는 확인 버튼이, 확인된 행에는 ACK 배지가 놓인다.
버튼을 누르면 해당 행만
즉시 배지로 바뀌고(필터·페이지 상태 유지) 포커스는 같은 표의 다음 미확인 버튼으로 옮겨간다.
표 머리의 전체 확인은 확인 대화상자를 거친 뒤 미확인 항목을 20건씩 나눠 처리하고 실패
건수를 토스트로 알린다.
ACK 권한은 OPERATOR 이상이라 VIEWER 세션에서는 두 버튼 모두
보이지 않는다.
글로벌 statusbar의 Critical 세그먼트는 +N warn · M unack으로 미확인 잔량을
같이 보여주고, 모바일 알림 탭의 각 행에도 같은 확인 버튼이 있다.
8.6 SLA 추적 (v1.0)
섹션 제목: “8.6 SLA 추적 (v1.0)”VM별 가동 시간을 추적하여 uptime_percent를 계산한다.
# RPCecho '{"jsonrpc":"2.0","method":"vm.sla","params":{"name":"web-prod"},"id":"1"}' \ | nc -U /var/run/purecvisor/daemon.sock8.7 per-VM 알림 라우팅 (v1.0)
섹션 제목: “8.7 per-VM 알림 라우팅 (v1.0)”VM별로 별도의 웹훅 대상을 지정할 수 있다.
# RPCecho '{"jsonrpc":"2.0","method":"alert.set_config","params":{ "vm_name":"web-prod", "webhook_url":"https://hooks.slack.com/services/...", "webhook_format":"slack"},"id":"1"}' | nc -U /var/run/purecvisor/daemon.sock8.8 복합 알림 규칙
섹션 제목: “8.8 복합 알림 규칙”AND/OR 조건으로 최대 8개 조건을 조합한 복합 알림 규칙을 생성할 수 있다.
{ "jsonrpc": "2.0", "method": "alert.set_config", "params": { "compound_rules": [ { "operator": "AND", "conditions": [ {"metric": "cpu_percent", "op": ">", "value": 90}, {"metric": "mem_percent", "op": ">", "value": 85} ] } ] }, "id": "1"}8.9 DLQ (Dead Letter Queue)
섹션 제목: “8.9 DLQ (Dead Letter Queue)”웹훅 발송 실패 시 DLQ에 최대 1000건까지 보관하며, 재시도 가능하다.
8.10 Prometheus 연동
섹션 제목: “8.10 Prometheus 연동”scrape_configs: - job_name: 'purecvisor' scrape_interval: 15s metrics_path: '/api/v1/metrics' static_configs: - targets: - '192.0.2.19:80' - '192.0.2.20:80' - '192.0.2.21:80'Grafana 대시보드: 192.0.2.61:3000 (Example Operations 통합 대시보드)
8.11 WebSocket 메트릭 Push (v1.0)
섹션 제목: “8.11 WebSocket 메트릭 Push (v1.0)”ws://127.0.0.1:8080/api/v1/ws/events로 10초 주기 실시간 메트릭을 push한다.
const ws = new WebSocket('ws://127.0.0.1:8080/api/v1/ws/events');ws.onmessage = (e) => { const data = JSON.parse(e.data); // { type: "metrics", cpu: 45.2, mem: 62.1, ... }};8.12 관측성 트레이싱 (retis, 2.0, D10)
섹션 제목: “8.12 관측성 트레이싱 (retis, 2.0, D10)”2.0은 실 네트워크 경로를 커널 레벨에서 추적·프로파일링하는 debug.trace.* 제어면을 추가했습니다.
백엔드는 retis이며, 5-tuple 필터로 특정 VM/테넌트/프로토콜/IP의 패킷 경로와 drop 지점을 잡습니다.
진단(디버그) 용도이므로 강한 안전 가드가 걸립니다.
- 타임박스 필수:
timebox_sec(1..3600초, 무기한 캡처 금지)을 반드시 지정해야 하며, 만료 시 수집이 자동 종료됩니다. - 동시성 1: 동시에 하나의 추적만 실행되도록 가드합니다.
- 오버레이 netns: 대상이 테넌트 오버레이 VM이면 해당 엔드포인트 netns에 진입해 추적합니다.
- 순환 보존 + 부팅 fail-safe purge: 산출물은 순환 보존되며, 부팅 시 잔여 추적 상태를 정리합니다.
- report:
debug.trace.report는 수집 결과에서 병목·drop 지점을 분석해 돌려줍니다.
retis가 설치되어 있지 않으면 추적은 조용히 degraded 상태로 빠집니다(가용성은 배포 환경에 따름).
RPC 전용(2.0):
debug.trace.*네임스페이스는 전용 REST 경로·CLI 서브커맨드·UI 페이지가 없습니다.
POST /api/v1/rpcJSON-RPC 패스스루(또는 UDS 직접nc -U)로만 호출하며, 5개 메서드 모두 ADMIN 권한이 필요합니다(전 테넌트 트래픽 관찰 표면).
| 메서드 | 파라미터 | 용도 |
|---|---|---|
debug.trace.start | {timebox_sec, [vm], [tenant], [proto], [src_ip], [dst_ip]} | 추적 시작(추적 ID·산출 폴더 반환) |
debug.trace.stop | 추적 ID 지정 | 추적 중지 |
debug.trace.status | 추적 ID 지정 | 추적 상태 |
debug.trace.list | {} | 추적 목록 |
debug.trace.report | 추적 ID 지정 | 추적 리포트(병목·drop 분석) |
# 특정 VM 트래픽 30초 추적 (POST /api/v1/rpc 패스스루)curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ http://127.0.0.1:8080/api/v1/rpc \ -d '{"jsonrpc":"2.0","method":"debug.trace.start","params":{ "timebox_sec":30,"vm":"web-prod"},"id":"1"}'8.13 브라우저 Web Push 알림 (2.0.0, SP2b)
섹션 제목: “8.13 브라우저 Web Push 알림 (2.0.0, SP2b)”2.0.0 현행 정본에서 데몬이 WARN/CRIT 알림을 브라우저 구독으로 직접 발송합니다.
중계 서비스를 두지 않고
RFC 8030(Web Push 프로토콜)·RFC 8291(aes128gcm 메시지 암호화)·RFC 8292(VAPID)를 데몬이 직접
구현하므로 신규 런타임 의존이 없습니다(이미 링크된 libcrypto·libsoup3·sqlite3만 사용).
브라우저
사업자의 push 서비스는 암호문만 중계하며 알림 본문을 읽지 못합니다.
구독 켜기
| 화면 | 위치 |
|---|---|
| 데스크톱 | 상단 우측 톱니(⚙) → 설정(Preferences) 모달의 알림 섹션 → 알림 구독 버튼 |
| 모바일 | 알림 탭 상단 푸시 알림 카드의 같은 버튼 |
두 곳은 같은 토글 컴포넌트라 버튼 라벨과 상태 문구가 동일합니다.
구독된 상태에서는 라벨이
구독 해제로 바뀝니다.
소유자를 서버가 호출자 신원으로 고정하므로 VIEWER도 자기 구독은
등록·해지·조회할 수 있습니다.
구독의 소유자와 상한
구독은 계정별로 귀속됩니다.
클라이언트가 소유자를 지정할 수 없고, 조회(push.mine)도 인자 없이
본인 것만 돌아옵니다(endpoint 요약 + SHA-256 지문이라 원문 왕복 없이 대조합니다).
한 사용자가
가질 수 있는 구독 행은 최대 10개이며(브라우저·기기마다 1행), 초과하면 400으로 거부합니다.
이미 본인이 가진 endpoint를 다시 등록하는 것(자가치유 재등록)은 행이 늘지 않으므로 상한과
무관합니다.
| 메서드 | REST | 권한 | 용도 |
|---|---|---|---|
push.vapid.get | GET /api/v1/push/vapid | VIEWER | 구독에 필요한 VAPID 공개키 조회 |
push.subscribe | POST /api/v1/push/subscribe | VIEWER | 브라우저 구독 등록(endpoint 기준 upsert) |
push.unsubscribe | POST /api/v1/push/unsubscribe | VIEWER | 구독 해지(비-ADMIN은 본인 소유 행만) |
push.mine | GET /api/v1/push/mine | VIEWER | 본인 구독만 조회(인자 없음) |
push.list | GET /api/v1/push/subscriptions | ADMIN | 전체 구독 목록(브라우저 키 p256dh/auth는 미포함) |
push.vapid.rotate | POST /api/v1/push/vapid/rotate | ADMIN | VAPID 키 재생성 + 전 구독 폐기 |
push.test | POST /api/v1/push/test | ADMIN | 테스트 알림 큐잉(운영 진단) |
설정 (daemon.conf)
[webpush]# 기본 true. false면 발송과 구독 등록이 모두 멈춘다enabled = true# "warn" | "crit" — 이 심각도 이상만 발송 (기본 warn)min_severity = warn# RFC 8292 VAPID sub. mailto: 또는 https: 스킴 필수, 미설정 시 생략contact = mailto:ops@purecvisor.example.commin_severity와 contact는 허용 목록·스킴 검증을 거치며, 위반하면 기동을 막지 않고 기본값으로
되돌린 뒤 경고를 남깁니다.
secure context 요건 (자주 걸리는 함정)
브라우저의 Service Worker와 Push API는 secure context에서만 동작합니다 — https://이거나
http://127.0.0.1 계열이어야 합니다.
자체서명 인증서로 HTTPS를 올린 노드에서는 브라우저가
Service Worker 등록 자체를 거부하고, 그러면 구독은 어떤 방법으로도 되지 않습니다.
이 경우 토글은
누르기 전에 잠기고 사유가 버튼 옆에 그대로 표시됩니다.
| 사유 | 표시되는 안내 |
|---|---|
| 인증서 미신뢰로 SW 등록 차단 | 인증서를 신뢰 목록에 추가한 뒤 재시도 안내(로컬 노드면 http://127.0.0.1 계열 주소는 인증서 없이 동작) |
| 그 외 SW 등록 실패 | 브라우저가 준 실패 사유 원문 |
브라우저 알림 권한이 denied | 사이트 권한을 허용으로 바꾸라는 안내 |
| Push API 미지원 브라우저 | 지원하지 않는다는 안내 |
“페이지를 새로고침하세요”는 인증서 문제에서는 영원히 틀린 안내이므로, 등록 실패가 인증서 계열로 판정되면 그 문구를 쓰지 않습니다.
iOS(Safari): 이 화면을 홈 화면에 PWA로 설치한 경우에만 푸시가 도착합니다.
일반 브라우저 탭 상태에서는 구독을 켜도 알림이 오지 않습니다.
구독 인수인계와 자가치유
- 브라우저가 endpoint를 교체하면(
pushsubscriptionchange) Service Worker가 자동으로 재구독하고, 열려 있는 페이지가 그 결과를 서버에 반영합니다 — 운영자가 다시 켤 필요가 없습니다. - ADMIN이
push.vapid.rotate를 돌리면 기존 구독은 전부 무효가 됩니다.
열려 있는 탭은 최대 1분 안에 스스로 재구독해 수렴합니다(옛 키로 만든 구독은 폐기하고 새로 만듭니다). - 구독 등록 시 endpoint는 서버의 SSRF 가드를 통과해야 합니다.
거부되면 사람이 읽는 사유가 토글 옆에 그대로 노출됩니다.