InfluxDB + Grafana를 통해 Proxmox Host CPU 온도 모니터링 시각화 및 알림 구축

InfluxDB + Grafana를 통해 Proxmox Host CPU 온도 모니터링 시각화 및 알림 구축

Influx DB 와 Grafana 를 통해서 Proxmox 를 모니터링 할 수 있도록 구성했습니다. 기본적으로 제공하는 데이터 이외에도, CPU 온도에 대한 패널을 추가하고 telegram을 통해 알림을 설정하는 과정까지를 담았습니다.

Proxmox 모니터링하기

우선, InfluxDB + Grafana 를 통해서 Proxmox 모니터링은 아래 사이트를 따라가시면 쉽게 해결하실 수 있습니다.

서버포럼 - InfluxDB 및 Grafana로 Proxmox 모니터링하기
안녕하세요. 오늘 열씸히 집에서도 일하고있는 달소입니다. ESXi부터 진행했던 홈서버가 어느덧 Proxmox에서 어느정도 정착을 하고 운영을 잘하고있는상황에서 많은 유저분들께서 Proxmox를 사용하고 계시니 Proxmox…

이렇게 세팅을 하고 나면 초기에는 다음과 같은 뷰를 얻을 수 있습니다.

서버포럼 달소님 스크린샷

CPU 온도 모니터링 추가하기

사실 이 정도의 정보로도 모니터링하는데 충분합니다.

그러나, 제 경우에 CPU 온도 / VM 별 CPU | Memory 상태 / Docker container 별 상태를 한 곳에서 보고 싶었습니다.

특히, CPU 온도의 경우 서버를 위한 장비가 아닌 mini PC 를 통해서 작동하고 있는데, ZBOX 의 경우 팬 조차 존재하지 않는 제품이기에 주기적인 모니터링이 필요했으며 , X300 의 경우 소음으로 인해 팬 속도 제한을 걸어둔 상태인데 예상보다 CPU 온도가 너무 치솟을 경우 이 속도를 추후에 조절하고자 모니터링이 필요했습니다.

또한, 해당 방법을 잘 응용하면 CPU 사용량과 온도 그리고 메모리 사용량을 Router로 부터도 수집하여 제 서버를 구성하는 장치들의 상태를 일괄적으로 Grafana를 통해서 확인할 수 있을 것을 기대하고 작업을 시작했습니다.

다행히 시스템의 장치 온도를 얻어서 Influx DB 로 송신하는 툴은 이미 존재합니다. 아래 레포지토리를 가시면 확인하실 수 있습니다.

GitHub - MightySlaytanic/pve-monitoring: Proxmox VE temperature and disk-health stats upload to influxdb2
Proxmox VE temperature and disk-health stats upload to influxdb2 - GitHub - MightySlaytanic/pve-monitoring: Proxmox VE temperature and disk-health stats upload to influxdb2

위 레포와 함께 아래 가이드를 따라하면, 최종적으로 이렇게 작동하게됩니다.

가이드도 잘 작성된 상태라서 해당 가이드를 쭉 따라가시면 CPU 온도를 얻어내실 수 있습니다. (스크립트 자체도 단순합니다.) 다만, 제 경우에 X300 에서 Ryzen 4750G 를 사용하고 있는데 해당 툴에서 정의해둔 규격과는 다소 다릅니다. AMD 의 경우 CPU Core 에 대한 정보, PCH 에 대한 정보들이 따로 존재하지 않습니다. 때문에 일부분 수정해야합니다.

제 경우에는 Core 에 대한 온도는 크게 의미 없으며, 통합적으로 정보를 얻어내기면 하면 되기에 스크립트를 일부 수정했습니다.

스크립트 고치기

  • pve_temp_stats_to_influxdb2.py
#!/root/scripts/venv/bin/python3

# Note about python shabang string above: if you're running on a Debian 11
# system use the standard "#!/usr/bin/python3" string while if you are on
# a Debian 12 system you need to create a virtual-env with influxdb_client
# and use the shabang string pointing to the python3 interpreter within
# the virtual-env, such as "#!/root/scripts/venv/bin/python3"
#
# For example, to create the virtual-env in /root/scripts/venv and install
# the required package do the following:
#
# python3 -m venv /root/scripts/venv
# . /root/scripts/venv/bin/activate
# pip3 install influxdb-client
# deactivate

import re
import sys
import json
import argparse
from datetime import datetime
from os import getenv
from subprocess import run,PIPE

from influxdb_client import InfluxDBClient
from influxdb_client.client.write_api import SYNCHRONOUS
from influxdb_client.client.exceptions import InfluxDBError

INFLUX_HOST = getenv("INFLUX_HOST")
INFLUX_PORT = getenv("INFLUX_PORT")
INFLUX_TOKEN = getenv("INFLUX_TOKEN")
INFLUX_ORGANIZATION = getenv("INFLUX_ORGANIZATION")
INFLUX_BUCKET = getenv("INFLUX_BUCKET")
HOST = getenv("HOST_TAG")

CORE_OFFSET=2

CPU_TEMP = getenv("CPU_TEMP").split(':')

if __name__ == '__main__':
    parser = argparse.ArgumentParser(usage="PVE Sensors Stats to influxdb2 uploader")

    parser.add_argument(
        "-t",
        "--test",
        help="Just print the results without uploading to influxdb2",
        action="store_true"
    )

    args = parser.parse_args()

    measurements = []
    stats = {}

    data = json.loads(run(["/usr/bin/sensors -j"], stdout=PIPE, stderr=None, text=True, shell=True).stdout)

    if CPU_TEMP[0]: 
        stats[CPU_TEMP[0]] = int(data[CPU_TEMP[1]][CPU_TEMP[2]][CPU_TEMP[3]])

    measurements.append({
        "measurement": "temp",
        "tags": {"host": HOST, "service": "lm_sensors"},
        "fields": stats
    })

    if args.test:
        print(f"\nMeasurements for host {HOST}")
        print(json.dumps(measurements, indent=4))
    else:
        try:
            client = InfluxDBClient(url=f"http://{INFLUX_HOST}:{INFLUX_PORT}", token=INFLUX_TOKEN, org=INFLUX_ORGANIZATION, timeout=30000)
            write_api = client.write_api(write_options=SYNCHRONOUS)

            write_api.write(
                INFLUX_BUCKET,
                INFLUX_ORGANIZATION,
                measurements
            )

        except TimeoutError as e:
            failure = True
            print(e,file=sys.stderr)
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] TimeoutError: Could not upload data to {INFLUX_HOST}:{INFLUX_PORT} for host {HOST}",file=sys.stderr)
            exit(-1)
        except InfluxDBError as e:
            failure = True
            print(e,file=sys.stderr)
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] InfluxDBError: Could not upload data to {INFLUX_HOST}:{INFLUX_PORT} for host {HOST}",file=sys.stderr)
            exit(-1)
        except Exception as e:
            failure = True
            print(e, file=sys.stderr)
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Connection Error: Could not upload data to {INFLUX_HOST}:{INFLUX_PORT} for host {HOST}",file=sys.stderr)
            exit(-1)

        client.close()
  • pve_temp_stats_to_influxdb2.sh
#!/bin/bash

export INFLUX_HOST="influx_IP_or_DNS"
export INFLUX_PORT="influx_PORT"
export INFLUX_ORGANIZATION="influx_organization"
export INFLUX_BUCKET="influx_bucket"
export INFLUX_TOKEN="influx_token"
export HOST_TAG="measurements_host_tag"
export CPU_CORES="6"

# Execute "sensors -j" and then use the information to set the following environment variables.
# In case some of them, like ACPITZ stuff, are not available, set them to ""
# For example if you want the PCH temperature to be stored in a pch field and you see the following
# within "sensors -j" output
#   "pch_cannonlake-virtual-0":{
#      "Adapter": "Virtual device",
#      "temp1":{
#         "temp1_input": 61.000
#      }
#   }
# AMD Ryzen 4750G output
#  "k10temp-pci-00c3":{
#     "Adapter": "PCI adapter",
#     "Tctl":{
#        "temp1_input": 62.875
#     }
#  },
# Set PCH_INFO as the following, by looking at the chain of values that lead to the temperature of PCH
export CPU_TEMP="cpu_temp:k10temp-pci-00c3:Tctl:temp1_input"

# Debian 11 without Python Virtual Environment
# python3 /home/scripts/pve_temp_stats_to_influxdb2.py $*

# Debian 12 with Python Virtual Environment in /path/to/venv
/path/to/venv/bin/python3 /home/scripts/pve_temp_stats_to_influxdb2.py $*

크게 바뀐 부분은 없고, 제게 존재하지 않는 정보 (PCH_INFO, ACPITZ_INFO,CORETEMP_NAME, NVME_INFO) 를 삭제하고 python 스크립트에서도 마찬가지로 제거했습니다. (nvme 의 경우 획득은 되나, 딱히 제게는 의미가 없어서 제거해두었습니다. 필요하시다면 가이드대로 정보 획득해서 추가해주시면 됩니다.)

Grafana에서 패널 추가하기

이제 InfluxDB 에 데이터가 수신되고 있으니, Grafana에 추가할 차례입니다. 최종적으로 결과는 이렇게 나올겁니다.

Grafana 대시보드에서 Add 를 통해 패널을 하나 추가합니다.

쿼리의 경우 다음과 같이 작성하면 됩니다. (가이드대로 따라하셨다면, r._field 값에 pch가 필요할 것입니다.)

from(bucket: "${Bucket}")
  |> range(start: v.timeRangeStart, stop:v.timeRangeStop)
  |> filter(fn: (r) => r["host"] =~ /${server:regex}/)
  |> filter(fn: (r) => r._measurement == "temp" and r._field == "cpu_temp")

저는 시계열 데이터를 그래프로 보여주는 Time series 와 Gauge 를 사용하였습니다. 처음 패널을 생성하게 될 경우 Threshold 값이나, 그래프에 대해서는 기본 값으로 설정되어있는데, 적당히 설정해서 사용하시면 됩니다.

같은 방법을 사용하여 아래와 같이 Summary 탭에서 Host 별 CPU도 보여줄 수 있습니다.

해당 패널의 쿼리는 아래와 같이 설정하시면 됩니다. (내부 패널 디자인은 취향에 맞춰 설정하시면 됩니다.

from(bucket: "${Bucket}")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r._measurement == "temp" and r._field == "cpu_temp")
  |> filter(fn: (r) => r.host == "${server}")

Grafana 를 통해 Alert 보내기

단순히 모니터링 시각화를 할 뿐만 아니라, 위험 수치에 도달했을 때 alert 를 보내줬으면 하여, grafana 를 통해 alert 시스템까지 한번 구축해보겠습니다.

Telegram 을 통한 Alert

Grafana 의 경우 다양한 방법을 통해 alert 를 보낼 수 있도록 설정할 수 있습니다. 여기서는 telegram 을 통해 설정해보겠습니다.

우선 Telegram 을 Contact point 로 지정해둬야합니다. Contact point 에서 Add contact point 를 누르고 Telegram bot을 선택합니다.

Telegram bot 의 생성의 경우 아래를 참고해주세요.

(1) 텔레그램 봇 만들기
1. 텔레그램 앱에 들어가셔서 BotFather를 검색하여 들어갑니다. ![](https://wikidocs.net/images/page/213846/%EB%B4%87%ED%8C…

API 키의 경우 Botfather 를 통해서 획득할 수 있으며, Chat ID 의 경우 Token 의 앞부분에 위치한 숫자입니다.

TOKEN : 46589789:afasd78D899gaDFG934nkml
== ${ChatID}:${KEY}

입력을 완료하면 Test 버튼을 통해서 정상적으로 메시지가 잘 수신되는지 확인합니다.

정상적으로 잘 왔습니다.

정상적으로 수신이되면, Notification Policies 로 갑니다. Default Policy 의 경우 Default email 으로 지정이되어있는데, 이를 방금 만든 Telegram bot 으로 contract point를 지정해줍니다.

이후 다시 대시보드로 돌아갑니다. 현재는 CPU 온도에 대한 알림을 구축하고 있으니, 앞서 만든 패널을 누르고 More -> New alert rule 을 통해 새 alert 규칙을 추가합니다. (이렇게 하면 관련 쿼리를 그대로 들고와서 편합니다.)

Rule 을 생성하는 곳에 도달하면 다음과 같은 쿼리를 볼 수 있습니다. 해당 영역에서는 따로 Grafana Dashboard 의 변수를 사용할 수 없어 명확하게 지정되어있는 모습을 볼 수 있습니다.

그런데, 패널에서 들고오면 보통 Host 에 대한 필터링이 있습니다. 이렇게 필터링이 된 쿼리를 사용할 경우 alert rule 을 host마다 각각 만들어야합니다. 따라서 쿼리에서 host를 따로 필터링하는 부분을 제거해주세요. 제 경우 다음과 같이 쿼리를 작성했습니다.

from(bucket: "proxmox")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r._measurement == "temp" and r._field == "cpu_temp")

그 다음에는 alert 를 위한 표현식을 작성하는 부분입니다. A 라는 결과는 쿼리를 통해서 얻은 결과이며 B는 여기서 값을 정하는 과정(평균값, 최대/최소값, 마지막 값) 그리고 C 에서 Threshold를 설정하는 것입니다. (단순하게 잘 만들어놨네요.)

CPU 온도가 일정 이상 오르면 알림이 가도록, Threshold 의 IS ABOVE 값을 변경합니다. 저는 70도를 기준으로 잡았습니다. (테스트를 위해서라면 당장 울릴만한 수치로 한번 설정해보고 추후에 변경하는 것도 좋습니다.)

Evaluation Behavior 에서는 같은 간격마다 alert 에 대한 평가를 진행할 그룹을 만듭니다. 앞서 cron 을 통해 1분마다 결과를 얻어내도록 하였으니, 저는 매 1분마다 온도를 보고하도록 설정했습니다. 이 또한 각자의 생각에 따라 설정하면 됩니다.

pending period 의 경우 Threshold 로 설정된 값이 일정 시간동안 지속되는지 모니터링 후 fire 하도록 합니다. 예를 들어, 5m 으로 잡으면 5분간 70도 이상의 온도가 감지될 경우 메시지가 발송됩니다.

이렇게 계속 threshold 를 넘는지 체크하여 적합한 상황에 fire될 수 있도록 할 수 있습니다. 만약, 즉각적으로 보내고자 한다면 0을 지정하면 됩니다.

설정이 완료되고 테스트를 해보았습니다.

텔레그램으로 알림이 잘 수신되는 것을 볼 수 있습니다.