Skip to content

Add /config handler which exposes the current YAML config of cortex. #2165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* [FEATURE] Added sharding support to compactor when using the experimental TSDB blocks storage. #2113
* [FEATURE] Add ability to override YAML config file settings using environment variables. #2147
* `-config.expand-env`
* [FEATURE] Add /config HTTP endpoint which exposes the current Cortex configuration as YAML. #2165
* [ENHANCEMENT] Add `status` label to `cortex_alertmanager_configs` metric to gauge the number of valid and invalid configs. #2125
* [ENHANCEMENT] Cassandra Authentication: added the `custom_authenticators` config option that allows users to authenticate with cassandra clusters using password authenticators that are not approved by default in [gocql](https://github.com/gocql/gocql/blob/81b8263d9fe526782a588ef94d3fa5c6148e5d67/conn.go#L27) #2093
* [ENHANCEMENT] Experimental TSDB: Export TSDB Syncer metrics from Compactor component, they are prefixed with `cortex_compactor_`. #2023
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration/config-file-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ slug: configuration-file

Cortex can be configured using a YAML file - specified using the `-config.file` flag - or CLI flags. In case you combine both, CLI flags take precedence over the YAML config file.

The current configuration of any Cortex component can be seen by visiting the `/config` HTTP path.

## Reference

To specify which configuration file to load, pass the `-config.file` flag at the command line. The file is written in [YAML format](https://en.wikipedia.org/wiki/YAML), defined by the scheme below. Brackets indicate that a parameter is optional.
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration/config-file-reference.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ slug: configuration-file

Cortex can be configured using a YAML file - specified using the `-config.file` flag - or CLI flags. In case you combine both, CLI flags take precedence over the YAML config file.

The current configuration of any Cortex component can be seen by visiting the `/config` HTTP path.

## Reference

To specify which configuration file to load, pass the `-config.file` flag at the command line. The file is written in [YAML format](https://en.wikipedia.org/wiki/YAML), defined by the scheme below. Brackets indicate that a parameter is optional.
Expand Down
45 changes: 45 additions & 0 deletions integration/api_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"github.com/thanos-io/thanos/pkg/runutil"

"github.com/cortexproject/cortex/integration/e2e"
"github.com/cortexproject/cortex/integration/e2ecortex"
)

func TestConfigAPIEndpoint(t *testing.T) {
s, err := e2e.NewScenario(networkName)
require.NoError(t, err)
defer s.Close()

// Start Cortex in single binary mode, reading the config from file.
require.NoError(t, copyFileToSharedDir(s, "docs/configuration/single-process-config.yaml", cortexConfigFile))
flags := map[string]string{
"-config.file": filepath.Join(e2e.ContainerSharedDir, cortexConfigFile),
}

cortex1 := e2ecortex.NewSingleBinary("cortex-1", flags, "", 9009)
require.NoError(t, s.StartAndWaitReady(cortex1))

// Get config from /config API endpoint.
res, err := e2e.GetRequest(fmt.Sprintf("http://%s/config", cortex1.Endpoint(9009)))
require.NoError(t, err)

defer runutil.ExhaustCloseWithErrCapture(&err, res.Body, "config API response")
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
require.Equal(t, http.StatusOK, res.StatusCode)

// Start again Cortex in single binary with the exported config
// and ensure it starts (pass the readiness probe).
require.NoError(t, writeFileToSharedDir(s, cortexConfigFile, body))
cortex2 := e2ecortex.NewSingleBinary("cortex-2", flags, "", 9009)
require.NoError(t, s.StartAndWaitReady(cortex2))
}
24 changes: 22 additions & 2 deletions pkg/cortex/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/weaveworks/common/middleware"
"github.com/weaveworks/common/server"
"google.golang.org/grpc/health/grpc_health_v1"
"gopkg.in/yaml.v2"

"github.com/cortexproject/cortex/pkg/alertmanager"
"github.com/cortexproject/cortex/pkg/chunk"
Expand Down Expand Up @@ -167,9 +168,28 @@ func (m *moduleName) UnmarshalYAML(unmarshal func(interface{}) error) error {
return m.Set(s)
}

func (t *Cortex) initServer(cfg *Config) (err error) {
func (t *Cortex) initServer(cfg *Config) error {
var err error
t.server, err = server.New(cfg.Server)
return
if err != nil {
return err
}

t.server.HTTP.HandleFunc("/config", func(w http.ResponseWriter, _ *http.Request) {
out, err := yaml.Marshal(cfg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "text/yaml")
w.WriteHeader(http.StatusOK)
if _, err := w.Write(out); err != nil {
level.Error(util.Logger).Log("msg", "error writing response", "err", err)
}
})

return nil
}

func (t *Cortex) stopServer() (err error) {
Expand Down
12 changes: 6 additions & 6 deletions pkg/ring/lifecycler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ type LifecyclerConfig struct {

// Config for the ingester lifecycle control
ListenPort *int `yaml:"-"`
NumTokens int `yaml:"num_tokens,omitempty"`
HeartbeatPeriod time.Duration `yaml:"heartbeat_period,omitempty"`
ObservePeriod time.Duration `yaml:"observe_period,omitempty"`
JoinAfter time.Duration `yaml:"join_after,omitempty"`
MinReadyDuration time.Duration `yaml:"min_ready_duration,omitempty"`
NumTokens int `yaml:"num_tokens"`
HeartbeatPeriod time.Duration `yaml:"heartbeat_period"`
ObservePeriod time.Duration `yaml:"observe_period"`
JoinAfter time.Duration `yaml:"join_after"`
MinReadyDuration time.Duration `yaml:"min_ready_duration"`
InfNames []string `yaml:"interface_names"`
FinalSleep time.Duration `yaml:"final_sleep"`
TokensFilePath string `yaml:"tokens_file_path,omitempty"`
TokensFilePath string `yaml:"tokens_file_path"`

// For testing, you can override the address and ID of this ingester
Addr string `yaml:"address" doc:"hidden"`
Expand Down
14 changes: 14 additions & 0 deletions pkg/util/flagext/day.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,17 @@ func (v *DayValue) Set(s string) error {
func (v *DayValue) IsSet() bool {
return v.set
}

// UnmarshalYAML implements yaml.Unmarshaler.
func (v *DayValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
return v.Set(s)
}

// MarshalYAML implements yaml.Marshaler.
func (v DayValue) MarshalYAML() (interface{}, error) {
return v.Time.Time().Format("2006-01-02"), nil
}
54 changes: 54 additions & 0 deletions pkg/util/flagext/day_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package flagext

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)

func TestDayValueYAML(t *testing.T) {
// Test embedding of DayValue.
{
type TestStruct struct {
Day DayValue `yaml:"day"`
}

var testStruct TestStruct
require.NoError(t, testStruct.Day.Set("1985-06-02"))
expected := []byte(`day: "1985-06-02"
`)

actual, err := yaml.Marshal(testStruct)
require.NoError(t, err)
assert.Equal(t, expected, actual)

var actualStruct TestStruct
err = yaml.Unmarshal(expected, &actualStruct)
require.NoError(t, err)
assert.Equal(t, testStruct, actualStruct)
}

// Test pointers of DayValue.
{
type TestStruct struct {
Day *DayValue `yaml:"day"`
}

var testStruct TestStruct
testStruct.Day = &DayValue{}
require.NoError(t, testStruct.Day.Set("1985-06-02"))
expected := []byte(`day: "1985-06-02"
`)

actual, err := yaml.Marshal(testStruct)
require.NoError(t, err)
assert.Equal(t, expected, actual)

var actualStruct TestStruct
err = yaml.Unmarshal(expected, &actualStruct)
require.NoError(t, err)
assert.Equal(t, testStruct, actualStruct)
}
}
16 changes: 16 additions & 0 deletions pkg/util/flagext/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,21 @@ func (v *URLValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&s); err != nil {
return err
}

// An empty string means no URL has been configured.
if s == "" {
v.URL = nil
return nil
}

return v.Set(s)
}

// MarshalYAML implements yaml.Marshaler.
func (v URLValue) MarshalYAML() (interface{}, error) {
if v.URL == nil {
return "", nil
}

return v.URL.String(), nil
}
74 changes: 74 additions & 0 deletions pkg/util/flagext/url_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package flagext

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)

func TestURLValueYAML(t *testing.T) {
// Test embedding of URLValue.
{
type TestStruct struct {
URL URLValue `yaml:"url"`
}

var testStruct TestStruct
require.NoError(t, testStruct.URL.Set("http://google.com"))
expected := []byte(`url: http://google.com
`)

actual, err := yaml.Marshal(testStruct)
require.NoError(t, err)
assert.Equal(t, expected, actual)

var actualStruct TestStruct
err = yaml.Unmarshal(expected, &actualStruct)
require.NoError(t, err)
assert.Equal(t, testStruct, actualStruct)
}

// Test pointers of URLValue.
{
type TestStruct struct {
URL *URLValue `yaml:"url"`
}

var testStruct TestStruct
testStruct.URL = &URLValue{}
require.NoError(t, testStruct.URL.Set("http://google.com"))
expected := []byte(`url: http://google.com
`)

actual, err := yaml.Marshal(testStruct)
require.NoError(t, err)
assert.Equal(t, expected, actual)

var actualStruct TestStruct
err = yaml.Unmarshal(expected, &actualStruct)
require.NoError(t, err)
assert.Equal(t, testStruct, actualStruct)
}

// Test no url set in URLValue.
{
type TestStruct struct {
URL URLValue `yaml:"url"`
}

var testStruct TestStruct
expected := []byte(`url: ""
`)

actual, err := yaml.Marshal(testStruct)
require.NoError(t, err)
assert.Equal(t, expected, actual)

var actualStruct TestStruct
err = yaml.Unmarshal(expected, &actualStruct)
require.NoError(t, err)
assert.Equal(t, testStruct, actualStruct)
}
}