Prometheus on a node; panel under a URL prefix; open ports on migrate import
A panel without VPN (e.g. after the move) can use a Prometheus that lives on a node, such as the one with history left on the old server (node #0). Both directions go through the agent's WSS: the node's Prometheus scrapes 127.0.0.1:AGENT_METRICS_PORT/metrics, the agent checks its token and asks core (metrics → metrics_res, the same exposition as /metrics, at most once per 5 s per connection); the monitor tab reads it back through the agent, which forwards only GET /-/ready and /api/v1/query[_range]. The panel finds it by itself (local first, then the lowest-id node), and the install/remove buttons work on a panel without VPN. `monitor install` on a node without its own panel picks AGENT_METRICS_PORT, writes it to cli.env and restarts the agent; on the old server it keeps the collected data. The panel works behind nginx in a subfolder: relative asset and API paths, relative directory redirects, and the client address from X-Forwarded-For / X-Real-IP only from trusted proxies (loopback, TRUST_PROXY). migrate import opens the moved SUB_PORT/NODE_PORT in UFW when it is active.
This commit is contained in:
+34
-2
@@ -43,8 +43,10 @@ const (
|
||||
rpcTimeout = 15 * time.Second
|
||||
rpcSlow = 120 * time.Second // /awg/*: рестарт и upgrade идут долго
|
||||
hubMaxFrame = 4 * 1024 * 1024
|
||||
failWindow = 15 * time.Minute
|
||||
failMaxHits = 20
|
||||
// Метрики для Prometheus на ноде: каждый ответ опрашивает все серверы — не чаще этого.
|
||||
metricsEvery = 5 * time.Second
|
||||
failWindow = 15 * time.Minute
|
||||
failMaxHits = 20
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -110,6 +112,9 @@ type nodeConn struct {
|
||||
alive bool
|
||||
pending map[int64]*pending
|
||||
closed bool
|
||||
// Запрос метрик от ноды (Prometheus на ней): идёт ли сейчас и когда начат последний.
|
||||
metricsBusy bool
|
||||
metricsAt time.Time
|
||||
}
|
||||
|
||||
type deleteVeto struct {
|
||||
@@ -585,6 +590,10 @@ func (n *Nodes) serveWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if m.Get2("t") == "metrics" && isInt(m.Get2("id")) {
|
||||
go n.nodeMetrics(conn, m.Get2("id"))
|
||||
continue
|
||||
}
|
||||
if m.Get2("t") == "res" && isInt(m.Get2("id")) {
|
||||
rid := int64(m.Get2("id").(float64))
|
||||
conn.mu.Lock()
|
||||
@@ -608,6 +617,29 @@ func (n *Nodes) serveWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// nodeMetrics — Prometheus на ноде скрейпит метрики панели через её агента: та же экспозиция,
|
||||
// что /metrics ядра, со всеми серверами (docs/node-protocol.md, «Мониторинг на ноде»).
|
||||
func (n *Nodes) nodeMetrics(c *nodeConn, id any) {
|
||||
c.mu.Lock()
|
||||
t := time.Now()
|
||||
busy := c.metricsBusy || t.Sub(c.metricsAt) < metricsEvery
|
||||
if !busy {
|
||||
c.metricsBusy, c.metricsAt = true, t
|
||||
}
|
||||
c.mu.Unlock()
|
||||
res := jsjson.Object{{Key: "t", Val: "metrics_res"}, {Key: "id", Val: id}}
|
||||
if busy {
|
||||
res = append(res, jsjson.KV{Key: "status", Val: 429}, jsjson.KV{Key: "text", Val: "слишком часто\n"})
|
||||
} else {
|
||||
text := n.a.metrics.render()
|
||||
c.mu.Lock()
|
||||
c.metricsBusy = false
|
||||
c.mu.Unlock()
|
||||
res = append(res, jsjson.KV{Key: "status", Val: 200}, jsjson.KV{Key: "text", Val: text})
|
||||
}
|
||||
c.ws.Write(context.Background(), websocket.MessageText, jsjson.MustMarshal(res))
|
||||
}
|
||||
|
||||
// Start поднимает хаб (если настроен) и периодические ping/health.
|
||||
func (n *Nodes) Start() {
|
||||
if n.local {
|
||||
|
||||
Reference in New Issue
Block a user