Skip to content

Commit 01ddff7

Browse files
app/vlselect: introduce -vmalert.proxyURL flag to proxy /api/v1/notifiers, /api/v1/alerts and /api/v1/rules requests to VMAlert
1 parent 8b2cdbd commit 01ddff7

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

app/vlselect/main.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"flag"
77
"fmt"
88
"net/http"
9+
nethttputil "net/http/httputil"
10+
"net/url"
911
"strings"
1012
"time"
1113

@@ -29,6 +31,8 @@ var (
2931

3032
disableSelect = flag.Bool("select.disable", false, "Whether to disable /select/* HTTP endpoints")
3133
disableInternal = flag.Bool("internalselect.disable", false, "Whether to disable /internal/select/* HTTP endpoints")
34+
35+
vmalertProxyURL = flag.String("vmalert.proxyURL", "", "Optional URL for proxying requests to vmalert")
3236
)
3337

3438
func getDefaultMaxConcurrentRequests() int {
@@ -48,6 +52,7 @@ func getDefaultMaxConcurrentRequests() int {
4852
// Init initializes vlselect
4953
func Init() {
5054
concurrencyLimitCh = make(chan struct{}, *maxConcurrentRequests)
55+
initVMAlertProxy()
5156
}
5257

5358
// Stop stops vlselect
@@ -268,6 +273,36 @@ func processSelectRequest(ctx context.Context, w http.ResponseWriter, r *http.Re
268273
logsql.ProcessStreamsRequest(ctx, w, r)
269274
logsqlStreamsDuration.UpdateDuration(startTime)
270275
return true
276+
case "/select/api/v1/notifiers":
277+
notifiersRequests.Inc()
278+
if len(*vmalertProxyURL) > 0 {
279+
r.URL.Path = path[len("/select"):]
280+
proxyVMAlertRequests(w, r)
281+
return true
282+
}
283+
w.Header().Set("Content-Type", "application/json")
284+
fmt.Fprint(w, `{"status":"success","data":{"notifiers":[]}}`)
285+
return true
286+
case "/select/api/v1/alerts":
287+
alertsRequests.Inc()
288+
if len(*vmalertProxyURL) > 0 {
289+
r.URL.Path = path[len("/select"):]
290+
proxyVMAlertRequests(w, r)
291+
return true
292+
}
293+
w.Header().Set("Content-Type", "application/json")
294+
fmt.Fprint(w, `{"status":"success","data":{"alerts":[]}}`)
295+
return true
296+
case "/select/api/v1/rules":
297+
rulesRequests.Inc()
298+
if len(*vmalertProxyURL) > 0 {
299+
r.URL.Path = path[len("/select"):]
300+
proxyVMAlertRequests(w, r)
301+
return true
302+
}
303+
w.Header().Set("Content-Type", "application/json")
304+
fmt.Fprint(w, `{"status":"success","data":{"rules":[]}}`)
305+
return true
271306
default:
272307
return false
273308
}
@@ -286,6 +321,39 @@ func getMaxQueryDuration(r *http.Request) time.Duration {
286321
return d
287322
}
288323

324+
func proxyVMAlertRequests(w http.ResponseWriter, r *http.Request) {
325+
defer func() {
326+
err := recover()
327+
if err == nil || err == http.ErrAbortHandler {
328+
// Suppress http.ErrAbortHandler panic.
329+
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1353
330+
return
331+
}
332+
// Forward other panics to the caller.
333+
panic(err)
334+
}()
335+
r.Host = vmalertProxyHost
336+
vmalertProxy.ServeHTTP(w, r)
337+
}
338+
339+
// initVMAlertProxy must be called after flag.Parse(), since it uses command-line flags.
340+
func initVMAlertProxy() {
341+
if len(*vmalertProxyURL) == 0 {
342+
return
343+
}
344+
proxyURL, err := url.Parse(*vmalertProxyURL)
345+
if err != nil {
346+
logger.Fatalf("cannot parse -vmalert.proxyURL=%q: %s", *vmalertProxyURL, err)
347+
}
348+
vmalertProxyHost = proxyURL.Host
349+
vmalertProxy = nethttputil.NewSingleHostReverseProxy(proxyURL)
350+
}
351+
352+
var (
353+
vmalertProxyHost string
354+
vmalertProxy *nethttputil.ReverseProxy
355+
)
356+
289357
var (
290358
logsqlFacetsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/facets"}`)
291359
logsqlFacetsDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/facets"}`)
@@ -322,4 +390,8 @@ var (
322390

323391
// no need to track duration for tail requests, as they usually take long time
324392
logsqlTailRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/tail"}`)
393+
394+
rulesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/api/v1/rules"}`)
395+
alertsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/api/v1/alerts"}`)
396+
notifiersRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/api/v1/notifiers"}`)
325397
)

docs/victorialogs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
1818

1919
## tip
2020

21+
* FEATURE: proxy requests to `/api/v1/notifiers`, `/api/v1/alerts` and `/api/v1/rules` to VMAlert, when `-vmalert.proxyURL` flag is set. See [#8272](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8272).
22+
2123
## [v1.25.0](https://github.com/VictoriaMetrics/VictoriaLogs/releases/tag/v1.25.0)
2224

2325
Released at 2025-07-07

docs/victorialogs/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ rsync -avh --progress --delete <username>@<host>:<path-to-victorialogs-backup> <
261261
It is also possible to use **the disk snapshot** in order to perform a backup. This feature could be provided by your operating system,
262262
cloud provider, or third-party tools. Note that the snapshot must be **consistent** to ensure reliable backup.
263263

264+
## vmalert
265+
266+
VictoriaLogs is capable of proxying requests to [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/)
267+
when `-vmalert.proxyURL` flag is set. Use this feature for the following cases:
268+
* for proxying requests from [Grafana Alerting UI](https://grafana.com/docs/grafana/latest/alerting/);
269+
* for accessing vmalerts UI through VictoriaLogs Web interface.
270+
271+
For accessing vmalerts UI through VictoriaLogs configure `-vmalert.proxyURL` flag and visit
272+
`http://<victorialogs-addr>:9428/vmalert/` link.
273+
264274
## Multitenancy
265275

266276
VictoriaLogs supports multitenancy. A tenant is identified by `(AccountID, ProjectID)` pair, where `AccountID` and `ProjectID` are arbitrary 32-bit unsigned integers.

0 commit comments

Comments
 (0)