Skip to content

Commit

Permalink
second update thanos-io#513 after the review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
kabakaev committed Oct 30, 2018
1 parent 8f247d6 commit c8b4393
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 19 deletions.
34 changes: 32 additions & 2 deletions cmd/thanos/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math"
"net"
"net/http"
"path"
"time"

"github.com/go-kit/kit/log"
Expand Down Expand Up @@ -53,6 +54,10 @@ func registerQuery(m map[string]setupFunc, app *kingpin.Application, name string
caCert := cmd.Flag("grpc-client-tls-ca", "TLS CA Certificates to use to verify gRPC servers").Default("").String()
serverName := cmd.Flag("grpc-client-server-name", "Server name to verify the hostname on the returned gRPC certificates. See https://tools.ietf.org/html/rfc4366#section-3.1").Default("").String()

webRoutePrefix := cmd.Flag("web.route-prefix", "Prefix for API and UI endpoints. This allows thanos UI to be served on a sub-path. This option is analogous to --web.route-prefix of Promethus.").Default("").String()
webExternalPrefix := cmd.Flag("web.external-prefix", "Static prefix for all HTML links and redirect URLs in the UI query web interface. Actual endpoints are still served on / or the web.route-prefix. This allows thanos UI to be served behind a reverse proxy that strips a URL sub-path.").Default("").String()
webPrefixHeaderName := cmd.Flag("web.prefix-header", "Name of HTTP request header used for dynamic prefixing of UI links and redirects. This option is ignored if web.external-prefix argument is set. Security risk: enable this option only if a reverse proxy in front of thanos is resetting the header. The --web.prefix-header=X-Forwarded-Prefix option can be useful, for example, if Thanos UI is served via Traefik reverse proxy with PathPrefixStrip option enabled, which sends the stripped prefix value in X-Forwarded-Prefix header. This allows thanos UI to be served on a sub-path.").Default("").String()

queryTimeout := modelDuration(cmd.Flag("query.timeout", "Maximum time to process query by query node.").
Default("2m"))

Expand Down Expand Up @@ -120,6 +125,9 @@ func registerQuery(m map[string]setupFunc, app *kingpin.Application, name string
*caCert,
*serverName,
*httpBindAddr,
*webRoutePrefix,
*webExternalPrefix,
*webPrefixHeaderName,
*maxConcurrentQueries,
time.Duration(*queryTimeout),
*replicaLabel,
Expand Down Expand Up @@ -230,6 +238,9 @@ func runQuery(
caCert string,
serverName string,
httpBindAddr string,
webRoutePrefix string,
webExternalPrefix string,
webPrefixHeaderName string,
maxConcurrentQueries int,
queryTimeout time.Duration,
replicaLabel string,
Expand Down Expand Up @@ -361,10 +372,29 @@ func runQuery(
// Start query API + UI HTTP server.
{
router := route.New()
ui.NewQueryUI(logger, nil).Register(router)

// redirect from / to /webRoutePrefix
if webRoutePrefix != "" {
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, webRoutePrefix, http.StatusFound)
})
}

flagsMap := map[string]string{
"grpc-address": grpcBindAddr,
"http-address": httpBindAddr,
"grpc-server-tls-cert": srvCert,
"grpc-server-tls-key": srvKey,
"grpc-server-tls-client-ca": srvClientCA,
"web.route-prefix": webRoutePrefix,
"web.external-prefix": webExternalPrefix,
"web.prefix-header": webPrefixHeaderName,
}

ui.NewQueryUI(logger, flagsMap).Register(router.WithPrefix(webRoutePrefix))

api := v1.NewAPI(logger, reg, engine, queryableCreator, enableAutodownsampling)
api.Register(router.WithPrefix("/api/v1"), tracer, logger)
api.Register(router.WithPrefix(path.Join(webRoutePrefix, "/api/v1")), tracer, logger)

router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
Expand Down
30 changes: 28 additions & 2 deletions cmd/thanos/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ func registerRule(m map[string]setupFunc, app *kingpin.Application, name string)

alertQueryURL := cmd.Flag("alert.query-url", "The external Thanos Query URL that would be set in all alerts 'Source' field").String()

webRoutePrefix := cmd.Flag("web.route-prefix", "Prefix for API and UI endpoints. This allows thanos UI to be served on a sub-path. This option is analogous to --web.route-prefix of Promethus.").Default("").String()
webExternalPrefix := cmd.Flag("web.external-prefix", "Static prefix for all HTML links and redirect URLs in the UI query web interface. Actual endpoints are still served on / or the web.route-prefix. This allows thanos UI to be served behind a reverse proxy that strips a URL sub-path.").Default("").String()
webPrefixHeaderName := cmd.Flag("web.prefix-header", "Name of HTTP request header used for dynamic prefixing of UI links and redirects. This option is ignored if web.external-prefix argument is set. Security risk: enable this option only if a reverse proxy in front of thanos is resetting the header. The --web.prefix-header=X-Forwarded-Prefix option can be useful, for example, if Thanos UI is served via Traefik reverse proxy with PathPrefixStrip option enabled, which sends the stripped prefix value in X-Forwarded-Prefix header. This allows thanos UI to be served on a sub-path.").Default("").String()

objStoreConfig := regCommonObjStoreFlags(cmd, "")

queries := cmd.Flag("query", "Addresses of statically configured query API servers (repeatable).").
Expand Down Expand Up @@ -138,6 +142,9 @@ func registerRule(m map[string]setupFunc, app *kingpin.Application, name string)
*key,
*clientCA,
*httpBindAddr,
*webRoutePrefix,
*webExternalPrefix,
*webPrefixHeaderName,
time.Duration(*evalInterval),
*dataDir,
*ruleFiles,
Expand Down Expand Up @@ -166,6 +173,9 @@ func runRule(
key string,
clientCA string,
httpBindAddr string,
webRoutePrefix string,
webExternalPrefix string,
webPrefixHeaderName string,
evalInterval time.Duration,
dataDir string,
ruleFiles []string,
Expand Down Expand Up @@ -487,11 +497,27 @@ func runRule(
// Start UI & metrics HTTP server.
{
router := route.New()
router.Post("/-/reload", func(w http.ResponseWriter, r *http.Request) {

// redirect from / to /webRoutePrefix
if webRoutePrefix != "" {
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, webRoutePrefix, http.StatusFound)
})
}

router.WithPrefix(webRoutePrefix).Post("/-/reload", func(w http.ResponseWriter, r *http.Request) {
reload <- struct{}{}
})

ui.NewRuleUI(logger, mgr, alertQueryURL.String()).Register(router)
flagsMap := map[string]string{
"grpc-address": grpcBindAddr,
"http-address": httpBindAddr,
"web.route-prefix": webRoutePrefix,
"web.external-prefix": webExternalPrefix,
"web.prefix-header": webPrefixHeaderName,
}

ui.NewRuleUI(logger, mgr, alertQueryURL.String(), flagsMap).Register(router.WithPrefix(webRoutePrefix))

mux := http.NewServeMux()
registerMetrics(mux, reg)
Expand Down
41 changes: 41 additions & 0 deletions docs/components/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ $ thanos query \
--cluster.peers "thanos-cluster.example.org" \
```

## Expose UI on a sub-path

It is possible to expose thanos-query UI and optinally API on a sub-path.
The sub-path can be defined either statically or dynamically via an HTTP header.
Static path prefix definition follows the pattern used in Prometheus,
where `web.route-prefix` option defines HTTP request path prefix (endpoints prefix)
and `web.external-prefix` prefixes the URLs in HTML code and the HTTP redirect responces.

Additionally, Thanos supports dynamic prefix configuration, which
[is not yet implemented by Prometheus](https://github.com/prometheus/prometheus/issues/3156).
Dynamic prefixing simplifies setup when `thanos query` is exposed on a sub-path behind
a reverse proxy, for example, via a Kubernetes ingress controller
[Traefik](https://docs.traefik.io/basics/#frontends)
or [nginx](https://github.com/kubernetes/ingress-nginx/pull/1805).
If `PathPrefixStrip: /some-path` option or `traefik.frontend.rule.type: PathPrefixStrip`
Kubernetes Ingress annotation is set, then `Traefik` writes the stripped prefix into X-Forwarded-Prefix header.
Then, `thanos query --web.prefix-header=X-Forwarded-Prefix` will serve correct HTTP redirects and links prefixed by the stripped path.

## Deployment

## Flags
Expand Down Expand Up @@ -116,6 +134,29 @@ Flags:
Server name to verify the hostname on the
returned gRPC certificates. See
https://tools.ietf.org/html/rfc4366#section-3.1
--web.route-prefix="" Prefix for API and UI endpoints. This allows
thanos UI to be served on a sub-path. This
option is analogous to --web.route-prefix of
Promethus.
--web.external-prefix="" Static prefix for all HTML links and redirect
URLs in the UI query web interface. Actual
endpoints are still served on / or the
web.route-prefix. This allows thanos UI to be
served behind a reverse proxy that strips a URL
sub-path.
--web.prefix-header="" Name of HTTP request header used for dynamic
prefixing of UI links and redirects. This
option is ignored if web.external-prefix
argument is set. Security risk: enable this
option only if a reverse proxy in front of
thanos is resetting the header. The
--web.prefix-header=X-Forwarded-Prefix option
can be useful, for example, if Thanos UI is
served via Traefik reverse proxy with
PathPrefixStrip option enabled, which sends the
stripped prefix value in X-Forwarded-Prefix
header. This allows thanos UI to be served on a
sub-path.
--query.timeout=2m Maximum time to process query by query node.
--query.max-concurrent=20 Maximum number of queries processed
concurrently by query node.
Expand Down
23 changes: 23 additions & 0 deletions docs/components/rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,29 @@ Flags:
--alert.query-url=ALERT.QUERY-URL
The external Thanos Query URL that would be set
in all alerts 'Source' field
--web.route-prefix="" Prefix for API and UI endpoints. This allows
thanos UI to be served on a sub-path. This
option is analogous to --web.route-prefix of
Promethus.
--web.external-prefix="" Static prefix for all HTML links and redirect
URLs in the UI query web interface. Actual
endpoints are still served on / or the
web.route-prefix. This allows thanos UI to be
served behind a reverse proxy that strips a URL
sub-path.
--web.prefix-header="" Name of HTTP request header used for dynamic
prefixing of UI links and redirects. This
option is ignored if web.external-prefix
argument is set. Security risk: enable this
option only if a reverse proxy in front of
thanos is resetting the header. The
--web.prefix-header=X-Forwarded-Prefix option
can be useful, for example, if Thanos UI is
served via Traefik reverse proxy with
PathPrefixStrip option enabled, which sends the
stripped prefix value in X-Forwarded-Prefix
header. This allows thanos UI to be served on a
sub-path.
--objstore.config-file=<bucket.config-yaml-path>
Path to YAML file that contains object store
configuration.
Expand Down
26 changes: 19 additions & 7 deletions pkg/ui/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ui
import (
"html/template"
"net/http"
"path"

"time"

Expand Down Expand Up @@ -50,13 +51,11 @@ func NewQueryUI(logger log.Logger, flagsMap map[string]string) *Query {
}
}

// Register registers new GET routes for subpages and retirects from / to /graph.
func (q *Query) Register(r *route.Router) {
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/graph", http.StatusFound)
})

instrf := prometheus.InstrumentHandlerFunc

r.Get("/", instrf("root", q.root))
r.Get("/graph", instrf("graph", q.graph))
r.Get("/status", instrf("status", q.status))
r.Get("/flags", instrf("flags", q.flags))
Expand All @@ -67,12 +66,23 @@ func (q *Query) Register(r *route.Router) {
// - what sidecars we see currently
}

// root redirects "/" requests to "/graph", taking into account the path prefix value
func (q *Query) root(w http.ResponseWriter, r *http.Request) {
prefix := GetWebPrefix(q.logger, q.flagsMap, r)

http.Redirect(w, r, path.Join(prefix, "/graph"), http.StatusFound)
}

func (q *Query) graph(w http.ResponseWriter, r *http.Request) {
q.executeTemplate(w, "graph.html", nil)
prefix := GetWebPrefix(q.logger, q.flagsMap, r)

q.executeTemplate(w, r, "graph.html", prefix, nil)
}

func (q *Query) status(w http.ResponseWriter, r *http.Request) {
q.executeTemplate(w, "status.html", struct {
prefix := GetWebPrefix(q.logger, q.flagsMap, r)

q.executeTemplate(w, r, "status.html", prefix, struct {
Birth time.Time
CWD string
Version thanosVersion
Expand All @@ -91,5 +101,7 @@ func (q *Query) status(w http.ResponseWriter, r *http.Request) {
}

func (q *Query) flags(w http.ResponseWriter, r *http.Request) {
q.executeTemplate(w, "flags.html", q.flagsMap)
prefix := GetWebPrefix(q.logger, q.flagsMap, r)

q.executeTemplate(w, r, "flags.html", prefix, q.flagsMap)
}
27 changes: 20 additions & 7 deletions pkg/ui/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"html/template"
"math"
"net/http"
"path"
"regexp"
"sort"

Expand All @@ -17,13 +18,16 @@ import (
type Rule struct {
*BaseUI

flagsMap map[string]string

ruleManager *rules.Manager
queryURL string
}

func NewRuleUI(logger log.Logger, ruleManager *rules.Manager, queryURL string) *Rule {
func NewRuleUI(logger log.Logger, ruleManager *rules.Manager, queryURL string, flagsMap map[string]string) *Rule {
return &Rule{
BaseUI: NewBaseUI(logger, "rule_menu.html", ruleTmplFuncs(queryURL)),
flagsMap: flagsMap,
ruleManager: ruleManager,
queryURL: queryURL,
}
Expand Down Expand Up @@ -104,20 +108,29 @@ func (ru *Rule) alerts(w http.ResponseWriter, r *http.Request) {
rules.StateFiring: "danger",
},
}
ru.executeTemplate(w, "alerts.html", alertStatus)

prefix := GetWebPrefix(ru.logger, ru.flagsMap, r)

ru.executeTemplate(w, r, "alerts.html", prefix, alertStatus)
}

func (ru *Rule) rules(w http.ResponseWriter, r *http.Request) {
ru.executeTemplate(w, "rules.html", ru.ruleManager)
prefix := GetWebPrefix(ru.logger, ru.flagsMap, r)

ru.executeTemplate(w, r, "rules.html", prefix, ru.ruleManager)
}

func (ru *Rule) Register(r *route.Router) {
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/alerts", http.StatusFound)
})
// root redirects / requests to /graph, taking into account the path prefix value
func (ru *Rule) root(w http.ResponseWriter, r *http.Request) {
prefix := GetWebPrefix(ru.logger, ru.flagsMap, r)

http.Redirect(w, r, path.Join(prefix, "/alerts"), http.StatusFound)
}

func (ru *Rule) Register(r *route.Router) {
instrf := prometheus.InstrumentHandlerFunc

r.Get("/", instrf("root", ru.root))
r.Get("/alerts", instrf("alerts", ru.alerts))
r.Get("/rules", instrf("rules", ru.rules))

Expand Down
Loading

0 comments on commit c8b4393

Please sign in to comment.