diff --git a/config/config_test.go b/config/config_test.go index fb5947227..afdfcbc52 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -984,6 +984,11 @@ func TestKubernetesEmptyAPIServer(t *testing.T) { require.NoError(t, err) } +func TestKubernetesWithKubeConfig(t *testing.T) { + _, err := LoadFile("testdata/kubernetes_kubeconfig_without_apiserver.good.yml", false, log.NewNopLogger()) + require.NoError(t, err) +} + func TestKubernetesSelectors(t *testing.T) { _, err := LoadFile("testdata/kubernetes_selectors_endpoints.good.yml", false, log.NewNopLogger()) require.NoError(t, err) @@ -1074,6 +1079,13 @@ var expectedErrors = []struct { filename: "kubernetes_http_config_without_api_server.bad.yml", errMsg: "to use custom HTTP client configuration please provide the 'api_server' URL explicitly", }, { + filename: "kubernetes_kubeconfig_with_apiserver.bad.yml", + errMsg: "cannot use 'kubeconfig_file' and 'api_server' simultaneously", + }, { + filename: "kubernetes_kubeconfig_with_http_config.bad.yml", + errMsg: "cannot use a custom HTTP client configuration together with 'kubeconfig_file'", + }, + { filename: "kubernetes_bearertoken.bad.yml", errMsg: "at most one of bearer_token & bearer_token_file must be configured", }, { diff --git a/config/testdata/kubernetes_kubeconfig_with_apiserver.bad.yml b/config/testdata/kubernetes_kubeconfig_with_apiserver.bad.yml new file mode 100644 index 000000000..09097f844 --- /dev/null +++ b/config/testdata/kubernetes_kubeconfig_with_apiserver.bad.yml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: prometheus + kubernetes_sd_configs: + - role: endpoints + api_server: 'https://localhost:1234' + kubeconfig_file: /user1/.kube/config diff --git a/config/testdata/kubernetes_kubeconfig_with_http_config.bad.yml b/config/testdata/kubernetes_kubeconfig_with_http_config.bad.yml new file mode 100644 index 000000000..d7a52d2ff --- /dev/null +++ b/config/testdata/kubernetes_kubeconfig_with_http_config.bad.yml @@ -0,0 +1,9 @@ +scrape_configs: + - job_name: prometheus + kubernetes_sd_configs: + - role: endpoints + kubeconfig_file: /user1/.kube/config + basic_auth: + username: user + password: password + diff --git a/config/testdata/kubernetes_kubeconfig_without_apiserver.good.yml b/config/testdata/kubernetes_kubeconfig_without_apiserver.good.yml new file mode 100644 index 000000000..c3f743d1a --- /dev/null +++ b/config/testdata/kubernetes_kubeconfig_without_apiserver.good.yml @@ -0,0 +1,5 @@ +scrape_configs: +- job_name: prometheus + kubernetes_sd_configs: + - role: endpoints + kubeconfig_file: /user1/.kube/config diff --git a/discovery/kubernetes/kubernetes.go b/discovery/kubernetes/kubernetes.go index bda9e2962..ef3e8e6a1 100644 --- a/discovery/kubernetes/kubernetes.go +++ b/discovery/kubernetes/kubernetes.go @@ -39,6 +39,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" "github.com/prometheus/prometheus/discovery" "github.com/prometheus/prometheus/discovery/targetgroup" @@ -114,6 +115,7 @@ func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { type SDConfig struct { APIServer config.URL `yaml:"api_server,omitempty"` Role Role `yaml:"role"` + KubeConfig string `yaml:"kubeconfig_file"` HTTPClientConfig config.HTTPClientConfig `yaml:",inline"` NamespaceDiscovery NamespaceDiscovery `yaml:"namespaces,omitempty"` Selectors []SelectorConfig `yaml:"selectors,omitempty"` @@ -130,6 +132,7 @@ func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Di // SetDirectory joins any relative file paths with dir. func (c *SDConfig) SetDirectory(dir string) { c.HTTPClientConfig.SetDirectory(dir) + c.KubeConfig = config.JoinDir(dir, c.KubeConfig) } type roleSelector struct { @@ -167,6 +170,14 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err != nil { return err } + if c.APIServer.URL != nil && c.KubeConfig != "" { + // Api-server and kubeconfig_file are mutually exclusive + return errors.Errorf("cannot use 'kubeconfig_file' and 'api_server' simultaneously") + } + if c.KubeConfig != "" && !reflect.DeepEqual(c.HTTPClientConfig, config.DefaultHTTPClientConfig) { + // Kubeconfig_file and custom http config are mutually exclusive + return errors.Errorf("cannot use a custom HTTP client configuration together with 'kubeconfig_file'") + } if c.APIServer.URL == nil && !reflect.DeepEqual(c.HTTPClientConfig, config.DefaultHTTPClientConfig) { return errors.Errorf("to use custom HTTP client configuration please provide the 'api_server' URL explicitly") } @@ -256,7 +267,12 @@ func New(l log.Logger, conf *SDConfig) (*Discovery, error) { kcfg *rest.Config err error ) - if conf.APIServer.URL == nil { + if conf.KubeConfig != "" { + kcfg, err = clientcmd.BuildConfigFromFlags("", conf.KubeConfig) + if err != nil { + return nil, err + } + } else if conf.APIServer.URL == nil { // Use the Kubernetes provided pod service account // as described in https://kubernetes.io/docs/admin/service-accounts-admin/ kcfg, err = rest.InClusterConfig() diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 902f08df9..939d66e8f 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -1476,9 +1476,12 @@ See below for the configuration options for Kubernetes discovery: # One of endpoints, service, pod, node, or ingress. role: +# Optional path to a kubeconfig file. +# Note that api_server and kube_config are mutually exclusive. +[ kubeconfig_file: ] + # Optional authentication information used to authenticate to the API server. -# Note that `basic_auth` and `authorization` options are -# mutually exclusive. +# Note that `basic_auth` and `authorization` options are mutually exclusive. # password and password_file are mutually exclusive. # Optional HTTP basic authentication information. diff --git a/go.sum b/go.sum index 921055b86..c0e1b2894 100644 --- a/go.sum +++ b/go.sum @@ -599,6 +599,7 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.0/go.mod h1:BwN2XG2lMszOoquQaFdPET8FRQfrXiZsWmcMO9rkaVY=