MAATRIX GAMES / Блог / Дашборд статуса сети из нескольких игровых серверов

Дашборд статуса сети из нескольких игровых серверов

MAATRIX GAMES

Когда у тебя один сервер, всё просто: зашёл по RCON, глянул онлайн, если что — перезапустил. Когда серверов пять, десять или сеть филиалов по локациям (UK, US, RU), ручной обход превращается в рутину, а падение одного из них можно узнать только от игроков в тикетах. Решение — единый дашборд, который сам опрашивает все серверы по их нативным протоколам и API и показывает статус, онлайн и время отклика на одном экране.

Зачем нужен единый дашборд, если серверов несколько

Если ты держишь сеть из нескольких проектов — например, кластер Minecraft-серверов с лобби и мини-играми, или несколько Rust-вайпов на разных картах, — у тебя уже есть скрытая проблема: каждый сервер живёт сам по себе. Мониторинг у хостера покажет, что виртуалка жива, но не покажет, что процесс игры завис, порт не отвечает на query или что онлайн упал до нуля из-за краша плагина.

Единая панель статуса закрывает три задачи разом:

  • быстрый визуальный обзор — какие серверы живы, сколько игроков онлайн, какая задержка ответа;
  • история — можно посмотреть, в какое время сутки чаще всего лежит сервер, и это часто указывает на конкретный крон-джоб или пиковую нагрузку;
  • единая точка для алертов — вместо того чтобы городить мониторинг на каждом сервере отдельно, ты пишешь один опросчик и одни правила уведомлений.

Для сети из 3-5 серверов хватит скрипта на Python и статичной HTML-страницы. Для 10+ серверов уже имеет смысл смотреть в сторону Prometheus + Grafana — об этом расскажу отдельно и оставлю ссылку в конце.

Какие данные тянуть с каждого сервера

У разных движков разный протокол опроса статуса, и это первое, с чем сталкиваешься при агрегации сети из разных игр.

A2S (Source Engine Query) — стандартный протокол Valve, работает по UDP на игровом порту (не нужен отдельный порт). Его понимают CS2, Rust, ARK: Survival, ARK: Survival Ascended, Team Fortress 2, Left 4 Dead 2, Squad, Insurgency и вообще большинство игр на движках, завязанных на Steamworks-сеть. Запрос A2S_INFO возвращает имя сервера, карту, число игроков из max, версию. Отдельно A2S_PLAYERS отдаёт список ников и их пинг.

Query-протокол Minecraft (Server List Ping) — это тот же пакет, что видит клиент в списке серверов: онлайн/макс, версия, MOTD, иконка. У Bedrock-редакции протокол свой (UDP, RakNet), поэтому под Minecraft Bedrock нужен отдельный опросчик — обычная Java-логика его не разберёт.

Встроенный HTTP/REST API — всё более частый случай у современных проектов. У Palworld, например, есть официальный REST API на отдельном порту (по умолчанию 8212) с basic-auth по паролю админа: эндпоинты вида /v1/api/info и /v1/api/players. У FiveM (GTA V) сервер по умолчанию отдаёт info.json, players.json и dynamic.json по HTTP на игровом порту. Точные пути и порты меняются от версии к версии — перед тем как завязывать на них мониторинг, проверь актуальную документацию конкретного проекта, а не бери мои цифры на веру.

RCON — это не про статус, а про управление (команды), но иногда проще получить онлайн через list или players по RCON, если query-протокол на сервере намеренно отключён из соображений безопасности (это частая практика против DDoS-разведки и накрутки серверлистов).

Если своего опросчика писать не хочется, у большинства query-протоколов есть готовые запросы через мониторинг-ботов — база в статье Server Query API для мониторинг-ботов, а если нужно углубиться конкретно в Source-протокол — смотри протокол A2S и запросы статуса сервера.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Архитектура: опросчик, хранилище, веб-панель

Держи архитектуру простой — три отдельных куска, которые можно менять независимо:

  1. Poller (опросчик) — скрипт, который раз в 30-60 секунд стучится в каждый сервер из списка, ловит таймауты и пишет результат.
  2. Хранилище — куда падают результаты опроса: SQLite для 3-10 серверов вполне достаточно, для десятков серверов и долгой истории лучше взять time-series базу.
  3. Веб-панель — отдаёт последние данные в браузер: либо статичная страница с JS, которая раз в N секунд дёргает JSON-эндпоинт, либо готовый Grafana-дашборд поверх той же базы.

Список серверов держи в отдельном конфиге, а не хардкодь в коде опросчика — так проще добавлять новые точки сети без правки логики:

# servers.yaml
servers:
  - name: "MC Survival RU"
    type: minecraft
    host: 45.12.34.56
    port: 25565

  - name: "Rust Vanilla EU"
    type: a2s
    host: 45.12.34.57
    port: 28015

  - name: "FiveM RP Server"
    type: fivem
    host: 45.12.34.58
    port: 30120

  - name: "Palworld Coop"
    type: palworld
    host: 45.12.34.59
    port: 8212
    rest_password: "changeme"

Пишем опросчик на Python

Для A2S и Minecraft есть зрелые библиотеки, которые снимают всю возню с бинарным протоколом. Ставим:

pip install python-a2s mcstatus requests pyyaml

Пример опроса разных типов серверов в одном цикле — сознательно упрощаю обработку ошибок, в проде нужен нормальный логгинг и ретраи:

import time
import yaml
import a2s
from mcstatus import JavaServer
import requests

def poll_a2s(host, port, timeout=3):
    try:
        info = a2s.info((host, port), timeout=timeout)
        return {
            "status": "up",
            "players": info.player_count,
            "max_players": info.max_players,
            "map": info.map_name,
        }
    except Exception:
        return {"status": "down"}

def poll_minecraft(host, port, timeout=3):
    try:
        server = JavaServer.lookup(f"{host}:{port}", timeout=timeout)
        status = server.status()
        return {
            "status": "up",
            "players": status.players.online,
            "max_players": status.players.max,
            "latency_ms": round(status.latency, 1),
        }
    except Exception:
        return {"status": "down"}

def poll_fivem(host, port, timeout=3):
    try:
        r = requests.get(f"http://{host}:{port}/dynamic.json", timeout=timeout)
        data = r.json()
        return {
            "status": "up",
            "players": int(data.get("clients", 0)),
            "max_players": int(data.get("sv_maxclients", 0)),
        }
    except Exception:
        return {"status": "down"}

def poll_server(server):
    if server["type"] == "a2s":
        return poll_a2s(server["host"], server["port"])
    if server["type"] == "minecraft":
        return poll_minecraft(server["host"], server["port"])
    if server["type"] == "fivem":
        return poll_fivem(server["host"], server["port"])
    return {"status": "unknown"}

def main():
    with open("servers.yaml") as f:
        config = yaml.safe_load(f)

    while True:
        results = []
        for server in config["servers"]:
            data = poll_server(server)
            data["name"] = server["name"]
            data["ts"] = int(time.time())
            results.append(data)
            print(data)
        # здесь пишем results в базу — см. следующий раздел
        time.sleep(60)

if __name__ == "__main__":
    main()

Опросчик держи отдельным systemd-сервисом или запускай через screen/tmux на управляющей машине — она необязательно должна быть одной из игровых нод, для сети из нескольких хостов удобнее вынести опрос на отдельный лёгкий VPS, чтобы падение одного игрового сервера не утаскивало за собой и мониторинг.

Храним историю и рисуем графики

Для SQLite схема простая — одна таблица с временными срезами:

CREATE TABLE IF NOT EXISTS status_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    server_name TEXT NOT NULL,
    status TEXT NOT NULL,
    players INTEGER,
    max_players INTEGER,
    latency_ms REAL,
    ts INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_server_ts ON status_history(server_name, ts);

Добавь в опросчик запись через sqlite3 и не забудь чистить старые строки — например, хранить детальную историю за 30 дней, а для более старых периодов агрегировать в почасовые сводки, иначе база будет расти без остановки на сети из десятка серверов с опросом раз в минуту.

Если хочется красивых графиков без написания своего фронтенда — самый быстрый путь для сети из многих серверов не SQLite, а Prometheus в связке с Grafana: пишешь простой exporter, который отдаёт метрики онлайна и статуса в формате Prometheus, а дальше Grafana сама рисует таймлайны, аптайм и алерты. Подробный разбор именно под игровой хостинг — в статье Grafana и Prometheus дашборд для игрового хостинга. Для 3-5 серверов это избыточно, для 15+ — уже оправданная инвестиция времени.

Если остаёшься на своём стеке, минимальная веб-панель — это FastAPI-эндпоинт, отдающий последний срез по каждому серверу, и статичный HTML с fetch раз в 30 секунд:

from fastapi import FastAPI
import sqlite3

app = FastAPI()

@app.get("/api/status")
def status():
    conn = sqlite3.connect("monitor.db")
    cur = conn.execute("""
        SELECT server_name, status, players, max_players, MAX(ts)
        FROM status_history GROUP BY server_name
    """)
    rows = cur.fetchall()
    return [
        {"name": r[0], "status": r[1], "players": r[2], "max_players": r[3], "ts": r[4]}
        for r in rows
    ]

На фронте — таблица или сетка карточек, где цвет фона зависит от status (up зелёный, down красный), и подпись "N/max игроков". Усложнять до React ради пяти карточек статуса не обязательно — обычный fetch и setInterval в паре десятков строк JS справляются полностью.

Уведомления о падении сервера

Дашборд, на который никто не смотрит в момент падения, бесполезен — нужен активный алерт. Самый простой вариант — сравнивать в опросчике текущий статус с предыдущим и при переходе up → down слать webhook в Discord или Telegram:

def notify_discord(webhook_url, server_name, status):
    color = 0xE74C3C if status == "down" else 0x2ECC71
    payload = {
        "embeds": [{
            "title": f"{server_name}: {status.upper()}",
            "color": color,
        }]
    }
    requests.post(webhook_url, json=payload, timeout=5)

Важный нюанс — не слать алерт при первом же неответившем пакете: UDP-запросы теряются и без реальной проблемы, особенно на нагруженном сервере. Практика, которая снимает большую часть ложных срабатываний — считать сервер упавшим только после 2-3 подряд неудачных опросов с интервалом в десятки секунд, а не по одному таймауту.

Разбор именно вебхук-уведомлений с готовыми шаблонами под Discord и Telegram — в статье уведомления о критичных событиях сервера через webhook.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

Создать сервер

Частые вопросы

Можно ли опрашивать серверы разных хостеров в одном дашборде?

Да, опросчику всё равно, где физически стоит сервер — важен только доступный IP и открытый порт для query/API. Единственное ограничение — сетевые задержки и фаервол-правила на стороне каждого хостера, их проверяй индивидуально.

Как часто опрашивать серверы, чтобы не создавать лишнюю нагрузку?

Раз в 30-60 секунд — разумный баланс для большинства query-протоколов. A2S и Minecraft Server List Ping — лёгкие UDP/TCP-запросы, десятки серверов с таким интервалом не создадут заметной нагрузки ни на них, ни на опросчик.

Что делать, если у игры вообще нет query-протокола?

Смотри, отдаёт ли сервер что-то через RCON (список игроков командой), или проверяй статус процесса по SSH/API хостинга — как крайний случай, можно опрашивать открытость игрового порта TCP/UDP-пингом, это хуже, чем данные об онлайне, но лучше, чем ничего.

Нужен ли отдельный сервер под сам дашборд?

Не обязательно, но желательно — если опросчик и панель живут на одной из игровых машин, её перезагрузка или высокая нагрузка от игроков временно ослепляет весь мониторинг сети. Лёгкого VPS с 1 ГБ RAM для опроса 10-20 серверов достаточно с запасом.

Как быть с Minecraft Bedrock — тот же код подойдёт?

Нет, у Bedrock свой RakNet-протокол поверх UDP, обычный mcstatus.JavaServer его не разберёт — используй mcstatus.BedrockServer из той же библиотеки, у неё отдельный класс именно под это.