diff --git a/app/vmauth/README.md b/app/vmauth/README.md index 335c2804e..0afa6fb9d 100644 --- a/app/vmauth/README.md +++ b/app/vmauth/README.md @@ -1,8 +1,8 @@ # vmauth -`vmauth` is a simple auth proxy and router for [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). -It reads username and password from [Basic Auth headers](https://en.wikipedia.org/wiki/Basic_access_authentication), -matches them against configs pointed by `-auth.config` command-line flag and proxies incoming HTTP requests to the configured per-user `url_prefix` on successful match. +`vmauth` is a simple auth proxy, router and [load balancer](#load-balancing) for [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). +It reads auth credentials from `Authorization` http header ([Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) and `Bearer token` is supported), +matches them against configs pointed by [-auth.config](#auth-config) command-line flag and proxies incoming HTTP requests to the configured per-user `url_prefix` on successful match. ## Quick start @@ -27,9 +27,14 @@ Feel free [contacting us](mailto:info@victoriametrics.com) if you need customize accounting and rate limiting such as [vmgateway](https://docs.victoriametrics.com/vmgateway.html). +## Load balancing + +Each `url_prefix` in the [-auth.config](#auth-config) may contain either a single url or a list of urls. In the latter case `vmauth` balances load among the configured urls in a round-robin manner. This feature is useful for balancing the load among multiple `vmselect` and/or `vminsert` nodes in [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html). + + ## Auth config -Auth config is represented in the following simple `yml` format: +`-auth.config` is represented in the following simple `yml` format: ```yml @@ -61,31 +66,44 @@ users: # The user for querying account 123 in VictoriaMetrics cluster # See https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) - # will be proxied to http://vmselect:8481/select/123/prometheus . - # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8481/select/123/prometheus/api/v1/select + # will be load-balanced among http://vmselect1:8481/select/123/prometheus and http://vmselect2:8481/select/123/prometheus + # For example, http://vmauth:8427/api/v1/query is proxied to the following urls in a round-robin manner: + # - http://vmselect1:8481/select/123/prometheus/api/v1/select + # - http://vmselect2:8481/select/123/prometheus/api/v1/select - username: "cluster-select-account-123" password: "***" - url_prefix: "http://vmselect:8481/select/123/prometheus" + url_prefix: + - "http://vmselect1:8481/select/123/prometheus" + - "http://vmselect2:8481/select/123/prometheus" # The user for inserting Prometheus data into VictoriaMetrics cluster under account 42 # See https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) - # will be proxied to http://vminsert:8480/insert/42/prometheus . - # For example, http://vmauth:8427/api/v1/write is proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write + # will be load-balanced between http://vminsert1:8480/insert/42/prometheus and http://vminsert2:8480/insert/42/prometheus + # For example, http://vmauth:8427/api/v1/write is proxied to the following urls in a round-robin manner: + # - http://vminsert1:8480/insert/42/prometheus/api/v1/write + # - http://vminsert2:8480/insert/42/prometheus/api/v1/write - username: "cluster-insert-account-42" password: "***" - url_prefix: "http://vminsert:8480/insert/42/prometheus" + url_prefix: + - "http://vminsert1:8480/insert/42/prometheus" + - "http://vminsert2:8480/insert/42/prometheus" # A single user for querying and inserting data: # - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range - # and http://vmauth:8427/api/v1/label//values are proxied to http://vmselect:8481/select/42/prometheus. - # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8480/select/42/prometheus/api/v1/query + # and http://vmauth:8427/api/v1/label//values are proxied to the following urls in a round-robin manner: + # - http://vmselect1:8481/select/42/prometheus + # - http://vmselect2:8481/select/42/prometheus + # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect1:8480/select/42/prometheus/api/v1/query + # or to http://vmselect2:8480/select/42/prometheus/api/v1/query . # - Requests to http://vmauth:8427/api/v1/write are proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write - username: "foobar" url_map: - src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"] - url_prefix: "http://vmselect:8481/select/42/prometheus" + url_prefix: + - "http://vmselect1:8481/select/42/prometheus" + - "http://vmselect2:8481/select/42/prometheus" - src_paths: ["/api/v1/write"] url_prefix: "http://vminsert:8480/insert/42/prometheus" ``` diff --git a/app/vmauth/auth_config.go b/app/vmauth/auth_config.go index a5ac3186d..d368bb766 100644 --- a/app/vmauth/auth_config.go +++ b/app/vmauth/auth_config.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "regexp" + "strconv" "strings" "sync" "sync/atomic" @@ -31,11 +32,11 @@ type AuthConfig struct { // UserInfo is user information read from authConfigPath type UserInfo struct { - BearerToken string `yaml:"bearer_token"` - Username string `yaml:"username"` - Password string `yaml:"password"` - URLPrefix *yamlURL `yaml:"url_prefix"` - URLMap []URLMap `yaml:"url_map"` + BearerToken string `yaml:"bearer_token"` + Username string `yaml:"username"` + Password string `yaml:"password"` + URLPrefix *URLPrefix `yaml:"url_prefix"` + URLMap []URLMap `yaml:"url_map"` requests *metrics.Counter } @@ -43,7 +44,7 @@ type UserInfo struct { // URLMap is a mapping from source paths to target urls. type URLMap struct { SrcPaths []*SrcPath `yaml:"src_paths"` - URLPrefix *yamlURL `yaml:"url_prefix"` + URLPrefix *URLPrefix `yaml:"url_prefix"` } // SrcPath represents an src path @@ -52,25 +53,74 @@ type SrcPath struct { re *regexp.Regexp } -type yamlURL struct { - u *url.URL +// URLPrefix represents pased `url_prefix` +type URLPrefix struct { + n uint32 + urls []*url.URL } -func (yu *yamlURL) UnmarshalYAML(f func(interface{}) error) error { - var s string - if err := f(&s); err != nil { +func (up *URLPrefix) getNextURL() *url.URL { + n := atomic.AddUint32(&up.n, 1) + idx := n % uint32(len(up.urls)) + return up.urls[idx] +} + +// UnmarshalYAML unmarshals up from yaml. +func (up *URLPrefix) UnmarshalYAML(f func(interface{}) error) error { + var v interface{} + if err := f(&v); err != nil { return err } - u, err := url.Parse(s) - if err != nil { - return fmt.Errorf("cannot unmarshal %q into url: %w", s, err) + var urls []string + switch x := v.(type) { + case string: + urls = []string{x} + case []interface{}: + if len(x) == 0 { + return fmt.Errorf("`url_prefix` must contain at least a single url") + } + us := make([]string, len(x)) + for i, xx := range x { + s, ok := xx.(string) + if !ok { + return fmt.Errorf("`url_prefix` must contain array of strings; got %T", xx) + } + us[i] = s + } + urls = us + default: + return fmt.Errorf("unexpected type for `url_prefix`: %T; want string or []string", v) } - yu.u = u + pus := make([]*url.URL, len(urls)) + for i, u := range urls { + pu, err := url.Parse(u) + if err != nil { + return fmt.Errorf("cannot unmarshal %q into url: %w", u, err) + } + pus[i] = pu + } + up.urls = pus return nil } -func (yu *yamlURL) MarshalYAML() (interface{}, error) { - return yu.u.String(), nil +// MarshalYAML marshals up to yaml. +func (up *URLPrefix) MarshalYAML() (interface{}, error) { + var b []byte + if len(up.urls) == 1 { + u := up.urls[0].String() + b = strconv.AppendQuote(b, u) + return string(b), nil + } + b = append(b, '[') + for i, pu := range up.urls { + u := pu.String() + b = strconv.AppendQuote(b, u) + if i+1 < len(up.urls) { + b = append(b, ',') + } + } + b = append(b, ']') + return string(b), nil } func (sp *SrcPath) match(s string) bool { @@ -201,11 +251,9 @@ func parseAuthConfig(data []byte) (map[string]*UserInfo, error) { return nil, fmt.Errorf("duplicate auth token found for bearer_token=%q, username=%q: %q", authToken, ui.BearerToken, ui.Username) } if ui.URLPrefix != nil { - urlPrefix, err := sanitizeURLPrefix(ui.URLPrefix.u) - if err != nil { + if err := ui.URLPrefix.sanitize(); err != nil { return nil, err } - ui.URLPrefix.u = urlPrefix } for _, e := range ui.URLMap { if len(e.SrcPaths) == 0 { @@ -214,11 +262,9 @@ func parseAuthConfig(data []byte) (map[string]*UserInfo, error) { if e.URLPrefix == nil { return nil, fmt.Errorf("missing `url_prefix` in `url_map`") } - urlPrefix, err := sanitizeURLPrefix(e.URLPrefix.u) - if err != nil { + if err := e.URLPrefix.sanitize(); err != nil { return nil, err } - e.URLPrefix.u = urlPrefix } if len(ui.URLMap) == 0 && ui.URLPrefix == nil { return nil, fmt.Errorf("missing `url_prefix`") @@ -248,6 +294,17 @@ func getAuthToken(bearerToken, username, password string) string { return "Basic " + token64 } +func (up *URLPrefix) sanitize() error { + for i, pu := range up.urls { + puNew, err := sanitizeURLPrefix(pu) + if err != nil { + return err + } + up.urls[i] = puNew + } + return nil +} + func sanitizeURLPrefix(urlPrefix *url.URL) (*url.URL, error) { // Remove trailing '/' from urlPrefix for strings.HasSuffix(urlPrefix.Path, "/") { diff --git a/app/vmauth/auth_config_test.go b/app/vmauth/auth_config_test.go index e192189c5..1b6ad3d4a 100644 --- a/app/vmauth/auth_config_test.go +++ b/app/vmauth/auth_config_test.go @@ -59,7 +59,21 @@ users: f(` users: - username: foo - url_prefix: [bar] + url_prefix: + bar: baz +`) + f(` +users: +- username: foo + url_prefix: + - [foo] +`) + + // empty url_prefix + f(` +users: +- username: foo + url_prefix: [] `) // Username and bearer_token in a single config @@ -117,6 +131,15 @@ users: url_prefix: foo.bar `) + // empty url_prefix in url_map + f(` +users: +- username: a + url_map: + - src_paths: ['/foo/bar'] + url_prefix: [] +`) + // Missing src_paths in url_map f(` users: @@ -162,6 +185,25 @@ users: }, }) + // Multiple url_prefix entries + f(` +users: +- username: foo + password: bar + url_prefix: + - http://node1:343/bbb + - http://node2:343/bbb +`, map[string]*UserInfo{ + getAuthToken("", "foo", "bar"): { + Username: "foo", + Password: "bar", + URLPrefix: mustParseURLs([]string{ + "http://node1:343/bbb", + "http://node2:343/bbb", + }), + }, + }) + // Multiple users f(` users: @@ -188,7 +230,7 @@ users: - src_paths: ["/api/v1/query","/api/v1/query_range","/api/v1/label/[^./]+/.+"] url_prefix: http://vmselect/select/0/prometheus - src_paths: ["/api/v1/write"] - url_prefix: http://vminsert/insert/0/prometheus + url_prefix: ["http://vminsert1/insert/0/prometheus","http://vminsert2/insert/0/prometheus"] `, map[string]*UserInfo{ getAuthToken("foo", "", ""): { BearerToken: "foo", @@ -198,8 +240,11 @@ users: URLPrefix: mustParseURL("http://vmselect/select/0/prometheus"), }, { - SrcPaths: getSrcPaths([]string{"/api/v1/write"}), - URLPrefix: mustParseURL("http://vminsert/insert/0/prometheus"), + SrcPaths: getSrcPaths([]string{"/api/v1/write"}), + URLPrefix: mustParseURLs([]string{ + "http://vminsert1/insert/0/prometheus", + "http://vminsert2/insert/0/prometheus", + }), }, }, }, @@ -238,12 +283,20 @@ func areEqualConfigs(a, b map[string]*UserInfo) error { return nil } -func mustParseURL(u string) *yamlURL { - pu, err := url.Parse(u) - if err != nil { - panic(fmt.Errorf("BUG: cannot parse %q: %w", u, err)) +func mustParseURL(u string) *URLPrefix { + return mustParseURLs([]string{u}) +} + +func mustParseURLs(us []string) *URLPrefix { + pus := make([]*url.URL, len(us)) + for i, u := range us { + pu, err := url.Parse(u) + if err != nil { + panic(fmt.Errorf("BUG: cannot parse %q: %w", u, err)) + } + pus[i] = pu } - return &yamlURL{ - u: pu, + return &URLPrefix{ + urls: pus, } } diff --git a/app/vmauth/example_config.yml b/app/vmauth/example_config.yml index 31877984e..ece9b9994 100644 --- a/app/vmauth/example_config.yml +++ b/app/vmauth/example_config.yml @@ -26,30 +26,43 @@ users: # The user for querying account 123 in VictoriaMetrics cluster # See https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) - # will be proxied to http://vmselect:8481/select/123/prometheus . - # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8481/select/123/prometheus/api/v1/select + # will be load-balanced among http://vmselect1:8481/select/123/prometheus and http://vmselect2:8481/select/123/prometheus + # For example, http://vmauth:8427/api/v1/query is proxied to the following urls in a round-robin manner: + # - http://vmselect1:8481/select/123/prometheus/api/v1/select + # - http://vmselect2:8481/select/123/prometheus/api/v1/select - username: "cluster-select-account-123" password: "***" - url_prefix: "http://vmselect:8481/select/123/prometheus" + url_prefix: + - "http://vmselect1:8481/select/123/prometheus" + - "http://vmselect2:8481/select/123/prometheus" # The user for inserting Prometheus data into VictoriaMetrics cluster under account 42 # See https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) - # will be proxied to http://vminsert:8480/insert/42/prometheus . - # For example, http://vmauth:8427/api/v1/write is proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write + # will be load-balanced between http://vminsert1:8480/insert/42/prometheus and http://vminsert2:8480/insert/42/prometheus + # For example, http://vmauth:8427/api/v1/write is proxied to the following urls in a round-robin manner: + # - http://vminsert1:8480/insert/42/prometheus/api/v1/write + # - http://vminsert2:8480/insert/42/prometheus/api/v1/write - username: "cluster-insert-account-42" password: "***" - url_prefix: "http://vminsert:8480/insert/42/prometheus" + url_prefix: + - "http://vminsert1:8480/insert/42/prometheus" + - "http://vminsert2:8480/insert/42/prometheus" # A single user for querying and inserting data: # - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range - # and http://vmauth:8427/api/v1/label//values are proxied to http://vmselect:8481/select/42/prometheus. - # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8480/select/42/prometheus/api/v1/query + # and http://vmauth:8427/api/v1/label//values are proxied to the following urls in a round-robin manner: + # - http://vmselect1:8481/select/42/prometheus + # - http://vmselect2:8481/select/42/prometheus + # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect1:8480/select/42/prometheus/api/v1/query + # or to http://vmselect2:8480/select/42/prometheus/api/v1/query . # - Requests to http://vmauth:8427/api/v1/write are proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write - username: "foobar" url_map: - src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"] - url_prefix: "http://vmselect:8481/select/42/prometheus" + url_prefix: + - "http://vmselect1:8481/select/42/prometheus" + - "http://vmselect2:8481/select/42/prometheus" - src_paths: ["/api/v1/write"] url_prefix: "http://vminsert:8480/insert/42/prometheus" diff --git a/app/vmauth/target_url.go b/app/vmauth/target_url.go index a361b561f..5e5d81f56 100644 --- a/app/vmauth/target_url.go +++ b/app/vmauth/target_url.go @@ -7,6 +7,11 @@ import ( "strings" ) +func (up *URLPrefix) mergeURLs(requestURI *url.URL) *url.URL { + pu := up.getNextURL() + return mergeURLs(pu, requestURI) +} + func mergeURLs(uiURL, requestURI *url.URL) *url.URL { targetURL := *uiURL targetURL.Path += requestURI.Path @@ -40,12 +45,12 @@ func createTargetURL(ui *UserInfo, uOrig *url.URL) (*url.URL, error) { for _, e := range ui.URLMap { for _, sp := range e.SrcPaths { if sp.match(u.Path) { - return mergeURLs(e.URLPrefix.u, &u), nil + return e.URLPrefix.mergeURLs(&u), nil } } } if ui.URLPrefix != nil { - return mergeURLs(ui.URLPrefix.u, &u), nil + return ui.URLPrefix.mergeURLs(&u), nil } return nil, fmt.Errorf("missing route for %q", u.String()) } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f04c803b2..39f144b43 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,7 @@ sort: 15 * FEATURE: vmalert: add a command-line flag `-rule.configCheckInterval` for automatic re-reading of `-rule` files without the need to send SIGHUP signal. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/512). * FEATURE: vmagent: respect the `sample_limit` and `-promscrape.maxScrapeSize` values when scraping targets in [stream parsing mode](https://docs.victoriametrics.com/vmagent.html#stream-parsing-mode). See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1331). +* FEATURE: vmauth: add ability to specify mutliple `url_prefix` entries for balancing the load among multiple `vmselect` and/or `vminsert` nodes in a cluster. See [these docs](https://docs.victoriametrics.com/vmauth.html#load-balancing). * BUGFIX: reduce CPU usage by up to 2x during querying a database with big number of active daily time series. The issue has been introduced in `v1.59.0`. diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md index 9aa8eedbf..30728c421 100644 --- a/docs/Cluster-VictoriaMetrics.md +++ b/docs/Cluster-VictoriaMetrics.md @@ -138,7 +138,7 @@ A minimal cluster must contain the following nodes: It is recommended to run at least two nodes for each service for high availability purposes. -An http load balancer such as `nginx` must be put in front of `vminsert` and `vmselect` nodes: +An http load balancer such as [vmauth](https://docs.victoriametrics.com/vmauth.html) or `nginx` must be put in front of `vminsert` and `vmselect` nodes: - requests starting with `/insert` must be routed to port `8480` on `vminsert` nodes. - requests starting with `/select` must be routed to port `8481` on `vmselect` nodes. diff --git a/docs/vmauth.md b/docs/vmauth.md index dfe62be62..cbca61e7a 100644 --- a/docs/vmauth.md +++ b/docs/vmauth.md @@ -4,9 +4,9 @@ sort: 5 # vmauth -`vmauth` is a simple auth proxy and router for [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). -It reads username and password from [Basic Auth headers](https://en.wikipedia.org/wiki/Basic_access_authentication), -matches them against configs pointed by `-auth.config` command-line flag and proxies incoming HTTP requests to the configured per-user `url_prefix` on successful match. +`vmauth` is a simple auth proxy, router and [load balancer](#load-balancing) for [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). +It reads auth credentials from `Authorization` http header ([Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) and `Bearer token` is supported), +matches them against configs pointed by [-auth.config](#auth-config) command-line flag and proxies incoming HTTP requests to the configured per-user `url_prefix` on successful match. ## Quick start @@ -31,9 +31,14 @@ Feel free [contacting us](mailto:info@victoriametrics.com) if you need customize accounting and rate limiting such as [vmgateway](https://docs.victoriametrics.com/vmgateway.html). +## Load balancing + +Each `url_prefix` in the [-auth.config](#auth-config) may contain either a single url or a list of urls. In the latter case `vmauth` balances load among the configured urls in a round-robin manner. This feature is useful for balancing the load among multiple `vmselect` and/or `vminsert` nodes in [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html). + + ## Auth config -Auth config is represented in the following simple `yml` format: +`-auth.config` is represented in the following simple `yml` format: ```yml @@ -65,31 +70,44 @@ users: # The user for querying account 123 in VictoriaMetrics cluster # See https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) - # will be proxied to http://vmselect:8481/select/123/prometheus . - # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8481/select/123/prometheus/api/v1/select + # will be load-balanced among http://vmselect1:8481/select/123/prometheus and http://vmselect2:8481/select/123/prometheus + # For example, http://vmauth:8427/api/v1/query is proxied to the following urls in a round-robin manner: + # - http://vmselect1:8481/select/123/prometheus/api/v1/select + # - http://vmselect2:8481/select/123/prometheus/api/v1/select - username: "cluster-select-account-123" password: "***" - url_prefix: "http://vmselect:8481/select/123/prometheus" + url_prefix: + - "http://vmselect1:8481/select/123/prometheus" + - "http://vmselect2:8481/select/123/prometheus" # The user for inserting Prometheus data into VictoriaMetrics cluster under account 42 # See https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) - # will be proxied to http://vminsert:8480/insert/42/prometheus . - # For example, http://vmauth:8427/api/v1/write is proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write + # will be load-balanced between http://vminsert1:8480/insert/42/prometheus and http://vminsert2:8480/insert/42/prometheus + # For example, http://vmauth:8427/api/v1/write is proxied to the following urls in a round-robin manner: + # - http://vminsert1:8480/insert/42/prometheus/api/v1/write + # - http://vminsert2:8480/insert/42/prometheus/api/v1/write - username: "cluster-insert-account-42" password: "***" - url_prefix: "http://vminsert:8480/insert/42/prometheus" + url_prefix: + - "http://vminsert1:8480/insert/42/prometheus" + - "http://vminsert2:8480/insert/42/prometheus" # A single user for querying and inserting data: # - Requests to http://vmauth:8427/api/v1/query, http://vmauth:8427/api/v1/query_range - # and http://vmauth:8427/api/v1/label//values are proxied to http://vmselect:8481/select/42/prometheus. - # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect:8480/select/42/prometheus/api/v1/query + # and http://vmauth:8427/api/v1/label//values are proxied to the following urls in a round-robin manner: + # - http://vmselect1:8481/select/42/prometheus + # - http://vmselect2:8481/select/42/prometheus + # For example, http://vmauth:8427/api/v1/query is proxied to http://vmselect1:8480/select/42/prometheus/api/v1/query + # or to http://vmselect2:8480/select/42/prometheus/api/v1/query . # - Requests to http://vmauth:8427/api/v1/write are proxied to http://vminsert:8480/insert/42/prometheus/api/v1/write - username: "foobar" url_map: - src_paths: ["/api/v1/query", "/api/v1/query_range", "/api/v1/label/[^/]+/values"] - url_prefix: "http://vmselect:8481/select/42/prometheus" + url_prefix: + - "http://vmselect1:8481/select/42/prometheus" + - "http://vmselect2:8481/select/42/prometheus" - src_paths: ["/api/v1/write"] url_prefix: "http://vminsert:8480/insert/42/prometheus" ```