Дашборд статуса сети из нескольких игровых серверов
Когда у тебя один сервер, всё просто: зашёл по 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 ₽/мес.
Создать серверАрхитектура: опросчик, хранилище, веб-панель
Держи архитектуру простой — три отдельных куска, которые можно менять независимо:
- Poller (опросчик) — скрипт, который раз в 30-60 секунд стучится в каждый сервер из списка, ловит таймауты и пишет результат.
- Хранилище — куда падают результаты опроса: SQLite для 3-10 серверов вполне достаточно, для десятков серверов и долгой истории лучше взять time-series базу.
- Веб-панель — отдаёт последние данные в браузер: либо статичная страница с 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 из той же библиотеки, у неё отдельный класс именно под это.