СТАТЬЯ

Монитор ресурсов через WEB, или мониторим свою VPS с помощью Grafana с самописными метриками

woolf1514
woolf1514 Newbie · 176 сообщений
Подписаться
5,5 тыс.
[ обложка статьи ]
Режим чтения
Прежде чем перейти к настройке, стоит сказать пару слов о том, зачем всё это затевалось. Помимо красивого дашборда в браузере, ценность такого стека в том, что собранные метрики можно использовать для алертинга: Grafana позволяет настроить правила оповещения, которые сработают, когда, например, занятое место на диске превысит 90% или RAM подберётся к потолку. Уведомления можно направить в Telegram или на почту — тогда вы узнаете о проблеме до того, как сервер ляжет. В этой статье разберём сам pipeline сбора и визуализации самописных метрик, а настройку пороговых алертов оставим как логичное продолжение — все данные для этого уже будут под рукой.

Какую задачу решаю?

Есть свой vps.

Есть желание самому создавать метрики и мониторить их.

Текущие реализации монитора ресурсов на vps мне не нравятся.

Хочется сделать так, чтоб ты вбил адрес в браузере и увидел некий "дайджест" по состоянию твоей машины.

Стек
  • Grafana
  • Python 3.9
  • Фреймворк prometheus_client для того, чтоб реализовывать свои метрики
  • prometheus DB
  • VPS
  • Свой домен
Настройка Grafana

Не буду расписывать тут как установить графану, ибо мануалов в сети более, чем достаточно. Например официальный

Укажу лишь нюансы, которые могут понадобиться. Мне захотелось вывести графану в отдельный поддомен типа https://domain.com/grafs

Для этого в конфиге nginx настраиваем обратный прокси:

Bash:
nano /etc/nginx/sites-available/domain.com

    # Настройки для графаны
    location /grafs {
        proxy_pass http://localhost:3000;
        rewrite ^/grafs/(.*) /$1 break;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }
Так же поправим конфиг графаны:
Bash:
sudo nano /etc/grafana/grafana.ini

[server]
domain = mydomain.com
root_url = %(protocol)s://%(domain)s:%(http_port)s/grafs
serve_from_sub_path = true
Супер! Теперь графана будет крутиться на домене в subpath.

Делаем метрики

Для начала я решил вывести 3 основных показателя:
  • % использования CPU
  • % занятого места на диске
  • % занятой RAM
Начнем с функции do_alarm - подробней в моей предыдущей статье
Python:
def do_alarm(t_alarmtext):
    headers = {"Content-type": "application/json"}
    payload = {"text": f"{t_alarmtext}", "chat_id": f"{admin_id}"}
    requests.post(url=webhook_url, data=json.dumps(payload), headers=headers)
Данные из системы будем тянуть с помощью фреймворка psutil
Python:
def get_cpu_procent():
    #print(f'psutil.cpu_percent(): {psutil.cpu_percent()}')
    return psutil.cpu_percent()


def get_virtual_memory_procent_usage():
    #print(f'psutil.virtual_memory().percent: {psutil.virtual_memory().percent}')
    return psutil.virtual_memory().percent


def get_disc_usage():
    #print(f'psutil.disk_usage.used.precent:{psutil.disk_usage("/").percent}')
    return psutil.disk_usage("/").percent
* за комментированные строчки тут для дебага.

Инициализируем вебсервер и добавляем в него три метрики:
Python:
class CustomCollector(object):
    def __init__(self):
        pass

    def collect(self):
        cpu_usage = GaugeMetricFamily('system_cpu_percent_usage', '% использования cpu')
        cpu_usage_metric = get_cpu_procent()
        cpu_usage.add_metric([], cpu_usage_metric)

        ram_usage = GaugeMetricFamily('system_ram_percent_usage', '% использования оперативы')
        ram_usage_metric = get_virtual_memory_procent_usage()
        ram_usage.add_metric([], ram_usage_metric)

        disk_usage = GaugeMetricFamily('system_disk_usage', '% занятого места на диске')
        disk_usage_metric = get_disc_usage()
        disk_usage.add_metric([], disk_usage_metric)

        yield cpu_usage
        yield ram_usage
        yield disk_usage
А теперь блок main
Python:
if __name__ == '__main__':
    try:
        start_http_server(8000)
        REGISTRY.register(CustomCollector())
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        logger.info('Program stopped')
    except Exception as e:
        t_alarmtext = f'prometheus-custom-collector (app.py):\n {str(e)}'
        do_alarm(t_alarmtext)
        logger.error(f'Other except error Exception', exc_info=True)
Исходный код скрипта можно взять тут - 4teraflops/system_metrics_4_vps

Запуск скрипта: python3 app.py. Для удобства можно скрипт сделать демоном, либо положить в докер.

Добавляем метрики в Grafana

Опять же, ка кдобавить datasource в графану мануалов в сети великое множество, например этот = повторяться не буду.

Но как говорил Ваилий Иванович петьке: "Но есть один нюанс!"

Нюанс в том, что подключеная Datasource к Grafana должна смотреть еще и наши самописные метрики.

Для этго идем в конфиг прометеуса и добавляем ему джобу
Bash:
nano /etc/prometheus/prometheus.yml

  - job_name: 'prometheus-custom'
    scrape_interval: 5s
    static_configs:
      - targets: ['localhost:8000']
Визуализация

Итак, наши метрики готовы. После всех установок и нстроек у вас будет крутиться 3 сервиса. Порты не должны смотреть наружу:
Чтоб убедиться в том, что все работает корректно, посмотрим что выдают наши самописные метрики: curl localhost:8000

Если метрики работают корректно, то будет что-то такое:
# HELP python_gc_objects_collected_total Objects collected during gc

# TYPE python_gc_objects_collected_total counter

python_gc_objects_collected_total{generation="0"} 57.0

python_gc_objects_collected_total{generation="1"} 292.0

python_gc_objects_collected_total{generation="2"} 0.0

# HELP python_gc_objects_uncollectable_total Uncollectable object found during GC

# TYPE python_gc_objects_uncollectable_total counter

python_gc_objects_uncollectable_total{generation="0"} 0.0

python_gc_objects_uncollectable_total{generation="1"} 0.0

python_gc_objects_uncollectable_total{generation="2"} 0.0

# HELP python_gc_collections_total Number of times this generation was collected

# TYPE python_gc_collections_total counter

python_gc_collections_total{generation="0"} 62.0

python_gc_collections_total{generation="1"} 5.0

python_gc_collections_total{generation="2"} 0.0

# HELP python_info Python platform information

# TYPE python_info gauge

python_info{implementation="CPython",major="3",minor="8",patchlevel="0",version="3.8.0"} 1.0

# HELP process_virtual_memory_bytes Virtual memory size in bytes.

# TYPE process_virtual_memory_bytes gauge

process_virtual_memory_bytes 2.51322368e+08

# HELP process_resident_memory_bytes Resident memory size in bytes.

# TYPE process_resident_memory_bytes gauge

process_resident_memory_bytes 2.2777856e+07

# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.

# TYPE process_start_time_seconds gauge

process_start_time_seconds 1.61753591402e+09

# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.

# TYPE process_cpu_seconds_total counter

process_cpu_seconds_total 81.83

# HELP process_open_fds Number of open file descriptors.

# TYPE process_open_fds gauge

process_open_fds 7.0

# HELP process_max_fds Maximum number of open file descriptors.

# TYPE process_max_fds gauge

process_max_fds 1024.0

# HELP system_cpu_percent_usage % использования cpu

# TYPE system_cpu_percent_usage gauge

system_cpu_percent_usage 2.9

# HELP system_ram_percent_usage % использования оперативы

# TYPE system_ram_percent_usage gauge

system_ram_percent_usage 50.7

# HELP system_disk_usage % занятого места на диске

# TYPE system_disk_usage gauge

system_disk_usage 68.0
Все самописные метрики не просто так начинаются с одного слова "system" - благодаря этому метрики лягут в один раздел.

Если залогиниться в графане на своем домене, затем создать дашборд, создать график, то вы увидите там:
Снимок экрана от 2021-04-06 15-24-10.png


Наши метрики работают. Осталось настроить визуализацию. Много ума для этого не надо. Просто тыкаешься и что-то в итоге получится, т.к. интерфейс интуитивный.
Снимок экрана от 2021-04-06 15-26-07.png
Полезно · 3

Комментарии

1
Я до сих пор не увидел в тексте содержимое файла config.py.

Поэтому:
#from config import admin_id, webhook_url
webhook_url='127.0.0.1'
...
#payload = {"text": f"{t_alarmtext}", "chat_id": f"{admin_id}"}
payload = {"text": f"{t_alarmtext}"}


И смотрим наши метрики по адресу: 127.0.0.1:8000
 
Последнее редактирование: