Repository: VictoriaMetrics/VictoriaMetrics
Pull request #2672 (2022-06-01 18:42:05)

lib/promscrape/discovery/kubernetes: follow-up after 0b5c874


Patch:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 21ab0626c..a66a2b289 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -15,6 +15,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
 
 ## tip
 
+* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): Allows using kubeconfig file within `kubernetes_sd_configs`. It may be useful for kubernetes cluster monitoring by `vmagent` outside kubernetes cluster. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1464).
 * FEATURE: allow overriding default limits for in-memory cache `indexdb/tagFilters` via flag `-storage.cacheSizeIndexDBTagFilters`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2663).
 * FEATURE: add support of `lowercase` and `uppercase` relabeling actions in the same way as [Prometheus 2.36.0 does](https://github.com/prometheus/prometheus/releases/tag/v2.36.0). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2664).
 * FEATURE: support query tracing, which allows determining bottlenecks during query processing. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1403).
diff --git a/lib/promauth/config.go b/lib/promauth/config.go
index e96f01bb6..bb54ef7d6 100644
--- a/lib/promauth/config.go
+++ b/lib/promauth/config.go
@@ -470,13 +470,12 @@ func NewConfig(baseDir string, az *Authorization, basicAuth *BasicAuthConfig, be
 						return nil, fmt.Errorf("cannot load TLS certificate from `cert_file`=%q, `key_file`=%q: %w", tlsConfig.CertFile, tlsConfig.KeyFile, err)
 					}
 					return &cert, nil
-				} else {
-					cert, err := tls.X509KeyPair(tlsConfig.Cert, tlsConfig.Key)
-					if err != nil {
-						return nil, fmt.Errorf("cannot load TLS certificate: %w", err)
-					}
-					return &cert, nil
 				}
+				cert, err := tls.X509KeyPair(tlsConfig.Cert, tlsConfig.Key)
+				if err != nil {
+					return nil, fmt.Errorf("cannot load TLS certificate: %w", err)
+				}
+				return &cert, nil
 			}
 			// Check whether the configured TLS cert can be loaded.
 			if _, err := getTLSCert(nil); err != nil {
diff --git a/lib/promscrape/discovery/kubernetes/api.go b/lib/promscrape/discovery/kubernetes/api.go
index 906470338..36ffa67be 100644
--- a/lib/promscrape/discovery/kubernetes/api.go
+++ b/lib/promscrape/discovery/kubernetes/api.go
@@ -1,10 +1,7 @@
 package kubernetes
 
 import (
-	"encoding/base64"
 	"fmt"
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
-	"gopkg.in/yaml.v2"
 	"net"
 	"os"
 	"strings"
@@ -12,164 +9,6 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
 )
 
-// apiConfig contains config for API server
-type apiConfig struct {
-	aw *apiWatcher
-}
-
-type Config struct {
-	Kind        string      `yaml:"kind,omitempty"`
-	APIVersion  string      `yaml:"apiVersion,omitempty"`
-	Preferences Preferences `yaml:"preferences"`
-	Clusters    []struct {
-		Name    string   `yaml:"name"`
-		Cluster *Cluster `yaml:"cluster"`
-	} `yaml:"clusters"`
-	AuthInfos []struct {
-		Name     string    `yaml:"name"`
-		AuthInfo *AuthInfo `yaml:"user"`
-	} `yaml:"users"`
-	Contexts []struct {
-		Name    string   `yaml:"name"`
-		Context *Context `yaml:"context"`
-	} `yaml:"contexts"`
-	CurrentContext string `yaml:"current-context"`
-}
-
-type Preferences struct {
-	Colors bool `yaml:"colors,omitempty"`
-}
-
-type Cluster struct {
-	LocationOfOrigin         string
-	Server                   string `yaml:"server"`
-	TLSServerName            string `yaml:"tls-server-name,omitempty"`
-	InsecureSkipTLSVerify    bool   `yaml:"insecure-skip-tls-verify,omitempty"`
-	CertificateAuthority     string `yaml:"certificate-authority,omitempty"`
-	CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`
-	ProxyURL                 string `yaml:"proxy-url,omitempty"`
-}
-
-// AuthInfo contains information that describes identity information.  This is use to tell the kubernetes cluster who you are.
-type AuthInfo struct {
-	LocationOfOrigin      string
-	ClientCertificate     string   `yaml:"client-certificate,omitempty"`
-	ClientCertificateData string   `yaml:"client-certificate-data,omitempty"`
-	ClientKey             string   `yaml:"client-key,omitempty"`
-	ClientKeyData         string   `yaml:"client-key-data,omitempty"`
-	Token                 string   `yaml:"token,omitempty"`
-	TokenFile             string   `yaml:"tokenFile,omitempty"`
-	Impersonate           string   `yaml:"act-as,omitempty"`
-	ImpersonateUID        string   `yaml:"act-as-uid,omitempty"`
-	ImpersonateGroups     []string `yaml:"act-as-groups,omitempty"`
-	ImpersonateUserExtra  []string `yaml:"act-as-user-extra,omitempty"`
-	Username              string   `yaml:"username,omitempty"`
-	Password              string   `yaml:"password,omitempty"`
-}
-
-// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
-type Context struct {
-	LocationOfOrigin string
-	Cluster          string `yaml:"cluster"`
-	AuthInfo         string `yaml:"user"`
-	Namespace        string `yaml:"namespace,omitempty"`
-}
-
-type ApiConfig struct {
-	basicAuth *promauth.BasicAuthConfig
-	server    string
-	token     string
-	tokenFile string
-	tlsConfig *promauth.TLSConfig
-}
-
-func buildConfig(sdc *SDConfig) (*ApiConfig, error) {
-
-	data, err := fs.ReadFileOrHTTP(sdc.KubeConfig)
-	if err != nil {
-		return nil, fmt.Errorf("cannot read kubeConfig from %q: %w", sdc.KubeConfig, err)
-	}
-	var config Config
-	if err = yaml.Unmarshal(data, &config); err != nil {
-		return nil, fmt.Errorf("cannot parse %q: %w", sdc.KubeConfig, err)
-	}
-
-	var authInfos = make(map[string]*AuthInfo)
-	for _, obj := range config.AuthInfos {
-		authInfos[obj.Name] = obj.AuthInfo
-	}
-	var clusterInfos = make(map[string]*Cluster)
-	for _, obj := range config.Clusters {
-		clusterInfos[obj.Name] = obj.Cluster
-	}
-	var contexts = make(map[string]*Context)
-	for _, obj := range config.Contexts {
-		contexts[obj.Name] = obj.Context
-	}
-
-	contextName := config.CurrentContext
-	configContext, exists := contexts[contextName]
-	if !exists {
-		return nil, fmt.Errorf("context %q does not exist", contextName)
-	}
-
-	clusterInfoName := configContext.Cluster
-	configClusterInfo, exists := clusterInfos[clusterInfoName]
-	if !exists {
-		return nil, fmt.Errorf("cluster %q does not exist", clusterInfoName)
-	}
-
-	authInfoName := configContext.AuthInfo
-	configAuthInfo, exists := authInfos[authInfoName]
-	if authInfoName != "" && !exists {
-		return nil, fmt.Errorf("auth info %q does not exist", authInfoName)
-	}
-
-	var apiConfig ApiConfig
-
-	apiConfig.tlsConfig = &promauth.TLSConfig{
-		CAFile:             configClusterInfo.CertificateAuthority,
-		ServerName:         configClusterInfo.TLSServerName,
-		InsecureSkipVerify: configClusterInfo.InsecureSkipTLSVerify,
-	}
-
-	if len(configClusterInfo.CertificateAuthorityData) != 0 {
-		apiConfig.tlsConfig.CA, err = base64.StdEncoding.DecodeString(configClusterInfo.CertificateAuthorityData)
-		if err != nil {
-			return nil, fmt.Errorf("cannot base64-decode configClusterInfo.CertificateAuthorityData %q: %w", configClusterInfo.CertificateAuthorityData, err)
-		}
-	}
-
-	if configAuthInfo != nil {
-		apiConfig.tlsConfig.CertFile = configAuthInfo.ClientCertificate
-		apiConfig.tlsConfig.KeyFile = configAuthInfo.ClientKey
-		apiConfig.token = configAuthInfo.Token
-		apiConfig.tokenFile = configAuthInfo.TokenFile
-		if len(configAuthInfo.ClientCertificateData) != 0 {
-			apiConfig.tlsConfig.Cert, err = base64.StdEncoding.DecodeString(configAuthInfo.ClientCertificateData)
-			if err != nil {
-				return nil, fmt.Errorf("cannot base64-decode configAuthInfo.ClientCertificateData %q: %w", configClusterInfo.CertificateAuthorityData, err)
-			}
-		}
-		if len(configAuthInfo.ClientKeyData) != 0 {
-			apiConfig.tlsConfig.Key, err = base64.StdEncoding.DecodeString(configAuthInfo.ClientKeyData)
-			if err != nil {
-				return nil, fmt.Errorf("cannot base64-decode configAuthInfo.ClientKeyData %q: %w", configClusterInfo.CertificateAuthorityData, err)
-			}
-		}
-		if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
-			apiConfig.basicAuth = &promauth.BasicAuthConfig{
-				Username: configAuthInfo.Username,
-				Password: promauth.NewSecret(configAuthInfo.Password),
-			}
-		}
-	}
-
-	apiConfig.server = configClusterInfo.Server
-
-	return &apiConfig, nil
-}
-
 func newAPIConfig(sdc *SDConfig, baseDir string, swcFunc ScrapeWorkConstructorFunc) (*apiConfig, error) {
 	role := sdc.role()
 	switch role {
@@ -183,17 +22,18 @@ func newAPIConfig(sdc *SDConfig, baseDir string, swcFunc ScrapeWorkConstructorFu
 	}
 	apiServer := sdc.APIServer
 
-	if len(sdc.KubeConfig) != 0 {
-		config, err := buildConfig(sdc)
+	if len(sdc.KubeConfig) > 0 {
+		fmt.Println("building")
+		kc, err := buildConfig(sdc)
 		if err != nil {
-			return nil, fmt.Errorf("cannot parse kube config: %w", err)
+			return nil, fmt.Errorf("cannot build kube config: %w", err)
 		}
-		acNew, err := promauth.NewConfig(".", nil, config.basicAuth, config.token, config.tokenFile, nil, config.tlsConfig)
+		ac, err = promauth.NewConfig(".", nil, kc.basicAuth, kc.token, kc.tokenFile, nil, kc.tlsConfig)
 		if err != nil {
 			return nil, fmt.Errorf("cannot initialize service account auth: %w; probably, `kubernetes_sd_config->api_server` is missing in Prometheus configs?", err)
 		}
-		ac = acNew
-		apiServer = config.server
+		apiServer = kc.server
+		sdc.ProxyURL = kc.proxyURL
 	}
 
 	if len(apiServer) == 0 {
diff --git a/lib/promscrape/discovery/kubernetes/kubeconfig.go b/lib/promscrape/discovery/kubernetes/kubeconfig.go
new file mode 100644
index 000000000..0971640fa
--- /dev/null
+++ b/lib/promscrape/discovery/kubernetes/kubeconfig.go
@@ -0,0 +1,261 @@
+package kubernetes
+
+import (
+	"encoding/base64"
+	"fmt"
+	"strings"
+
+	"gopkg.in/yaml.v2"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
+)
+
+// apiConfig contains config for API server
+type apiConfig struct {
+	aw *apiWatcher
+}
+
+// Config represent configuration file for kubernetes API server connection
+// https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/api/v1/types.go#L28
+type Config struct {
+	Kind       string `yaml:"kind,omitempty"`
+	APIVersion string `yaml:"apiVersion,omitempty"`
+	Clusters   []struct {
+		Name    string   `yaml:"name"`
+		Cluster *Cluster `yaml:"cluster"`
+	} `yaml:"clusters"`
+	AuthInfos []struct {
+		Name     string    `yaml:"name"`
+		AuthInfo *AuthInfo `yaml:"user"`
+	} `yaml:"users"`
+	Contexts []struct {
+		Name    string   `yaml:"name"`
+		Context *Context `yaml:"context"`
+	} `yaml:"contexts"`
+	CurrentContext string `yaml:"current-context"`
+}
+
+// Cluster contains information about how to communicate with a kubernetes cluster
+type Cluster struct {
+	Server                   string     `yaml:"server"`
+	TLSServerName            string     `yaml:"tls-server-name,omitempty"`
+	InsecureSkipTLSVerify    bool       `yaml:"insecure-skip-tls-verify,omitempty"`
+	CertificateAuthority     string     `yaml:"certificate-authority,omitempty"`
+	CertificateAuthorityData string     `yaml:"certificate-authority-data,omitempty"`
+	ProxyURL                 *proxy.URL `yaml:"proxy-url,omitempty"`
+}
+
+// AuthInfo contains information that describes identity information.  This is use to tell the kubernetes cluster who you are.
+type AuthInfo struct {
+	ClientCertificate     string `yaml:"client-certificate,omitempty"`
+	ClientCertificateData string `yaml:"client-certificate-data,omitempty"`
+	ClientKey             string `yaml:"client-key,omitempty"`
+	ClientKeyData         string `yaml:"client-key-data,omitempty"`
+	// TODO add support for it
+	Exec                 *ExecConfig `yaml:"exec,omitempty"`
+	Token                string      `yaml:"token,omitempty"`
+	TokenFile            string      `yaml:"tokenFile,omitempty"`
+	Impersonate          string      `yaml:"act-as,omitempty"`
+	ImpersonateUID       string      `yaml:"act-as-uid,omitempty"`
+	ImpersonateGroups    []string    `yaml:"act-as-groups,omitempty"`
+	ImpersonateUserExtra []string    `yaml:"act-as-user-extra,omitempty"`
+	Username             string      `yaml:"username,omitempty"`
+	Password             string      `yaml:"password,omitempty"`
+}
+
+func (au *AuthInfo) validate() error {
+	errContext := "field: %s is not supported currently, open an issue with feature request for it"
+	if au.Exec != nil {
+		return fmt.Errorf(errContext, "exec")
+	}
+	if len(au.ImpersonateUID) > 0 {
+		return fmt.Errorf(errContext, "act-as-uid")
+	}
+	if len(au.Impersonate) > 0 {
+		return fmt.Errorf(errContext, "act-as")
+	}
+	if len(au.ImpersonateGroups) > 0 {
+		return fmt.Errorf(errContext, "act-as-groups")
+	}
+	if len(au.ImpersonateUserExtra) > 0 {
+		return fmt.Errorf(errContext, "act-as-user-extra")
+	}
+	if len(au.Password) > 0 && len(au.Username) == 0 {
+		return fmt.Errorf("username cannot be empty, if password defined")
+	}
+	return nil
+}
+
+// ExecConfig contains information about os.command, that returns auth token for kubernetes cluster connection
+type ExecConfig struct {
+	// Command to execute.
+	Command string `json:"command"`
+	// Arguments to pass to the command when executing it.
+	Args []string `json:"args"`
+	// Env defines additional environment variables to expose to the process. These
+	// are unioned with the host's environment, as well as variables client-go uses
+	// to pass argument to the plugin.
+	Env []ExecEnvVar `json:"env"`
+
+	// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
+	// the same encoding version as the input.
+	APIVersion string `json:"apiVersion,omitempty"`
+
+	// This text is shown to the user when the executable doesn't seem to be
+	// present. For example, `brew install foo-cli` might be a good InstallHint for
+	// foo-cli on Mac OS systems.
+	InstallHint string `json:"installHint,omitempty"`
+
+	// ProvideClusterInfo determines whether or not to provide cluster information,
+	// which could potentially contain very large CA data, to this exec plugin as a
+	// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
+	// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
+	// reading this environment variable.
+	ProvideClusterInfo bool `json:"provideClusterInfo"`
+
+	// InteractiveMode determines this plugin's relationship with standard input. Valid
+	// values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this
+	// exec plugin wants to use standard input if it is available), or "Always" (this exec
+	// plugin requires standard input to function). See ExecInteractiveMode values for more
+	// details.
+	//
+	// If APIVersion is client.authentication.k8s.io/v1alpha1 or
+	// client.authentication.k8s.io/v1beta1, then this field is optional and defaults
+	// to "IfAvailable" when unset. Otherwise, this field is required.
+	//+optional
+	InteractiveMode string `json:"interactiveMode,omitempty"`
+}
+
+// ExecEnvVar is used for setting environment variables when executing an exec-based
+// credential plugin.
+type ExecEnvVar struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
+// Context is a tuple of references to a cluster and AuthInfo
+type Context struct {
+	Cluster  string `yaml:"cluster"`
+	AuthInfo string `yaml:"user"`
+}
+
+type kubeConfig struct {
+	basicAuth *promauth.BasicAuthConfig
+	server    string
+	token     string
+	tokenFile string
+	tlsConfig *promauth.TLSConfig
+	proxyURL  *proxy.URL
+}
+
+func buildConfig(sdc *SDConfig) (*kubeConfig, error) {
+
+	data, err := fs.ReadFileOrHTTP(sdc.KubeConfig)
+	if err != nil {
+		return nil, fmt.Errorf("cannot read kubeConfig from %q: %w", sdc.KubeConfig, err)
+	}
+	var config Config
+	if err = yaml.Unmarshal(data, &config); err != nil {
+		return nil, fmt.Errorf("cannot parse %q: %w", sdc.KubeConfig, err)
+	}
+
+	authInfos := make(map[string]*AuthInfo)
+	for _, obj := range config.AuthInfos {
+		authInfos[obj.Name] = obj.AuthInfo
+	}
+	clusterInfos := make(map[string]*Cluster)
+	for _, obj := range config.Clusters {
+		clusterInfos[obj.Name] = obj.Cluster
+	}
+	contexts := make(map[string]*Context)
+	for _, obj := range config.Contexts {
+		contexts[obj.Name] = obj.Context
+	}
+
+	contextName := config.CurrentContext
+	configContext := contexts[contextName]
+	if configContext == nil {
+		return nil, fmt.Errorf("context %q does not exist", contextName)
+	}
+
+	clusterInfoName := configContext.Cluster
+	configClusterInfo := clusterInfos[clusterInfoName]
+	if configClusterInfo == nil {
+		return nil, fmt.Errorf("cluster %q does not exist", clusterInfoName)
+	}
+
+	if len(configClusterInfo.Server) == 0 {
+		return nil, fmt.Errorf("kubernetes server address cannot be empty, define it for context: %s", contextName)
+	}
+
+	authInfoName := configContext.AuthInfo
+	configAuthInfo := authInfos[authInfoName]
+	if authInfoName != "" && configAuthInfo == nil {
+		return nil, fmt.Errorf("auth info %q does not exist", authInfoName)
+	}
+
+	var tlsConfig *promauth.TLSConfig
+	var basicAuth *promauth.BasicAuthConfig
+	var token, tokenFile string
+	isHTTPS := strings.HasPrefix(configClusterInfo.Server, "https://")
+
+	if isHTTPS {
+		tlsConfig = &promauth.TLSConfig{
+			CAFile:             configClusterInfo.CertificateAuthority,
+			ServerName:         configClusterInfo.TLSServerName,
+			InsecureSkipVerify: configClusterInfo.InsecureSkipTLSVerify,
+		}
+	}
+
+	if len(configClusterInfo.CertificateAuthorityData) > 0 && isHTTPS {
+		tlsConfig.CA, err = base64.StdEncoding.DecodeString(configClusterInfo.CertificateAuthorityData)
+		if err != nil {
+			return nil, fmt.Errorf("cannot base64-decode configClusterInfo.CertificateAuthorityData %q: %w", configClusterInfo.CertificateAuthorityData, err)
+		}
+	}
+
+	if configAuthInfo != nil {
+		if err := configAuthInfo.validate(); err != nil {
+			return nil, fmt.Errorf("invalid user auth configuration for context: %s, err: %w", contextName, err)
+		}
+		if isHTTPS {
+			tlsConfig.CertFile = configAuthInfo.ClientCertificate
+			tlsConfig.KeyFile = configAuthInfo.ClientKey
+
+			if len(configAuthInfo.ClientCertificateData) > 0 {
+				tlsConfig.Cert, err = base64.StdEncoding.DecodeString(configAuthInfo.ClientCertificateData)
+				if err != nil {
+					return nil, fmt.Errorf("cannot base64-decode configAuthInfo.ClientCertificateData %q: %w", configClusterInfo.CertificateAuthorityData, err)
+				}
+			}
+			if len(configAuthInfo.ClientKeyData) > 0 {
+				tlsConfig.Key, err = base64.StdEncoding.DecodeString(configAuthInfo.ClientKeyData)
+				if err != nil {
+					return nil, fmt.Errorf("cannot base64-decode configAuthInfo.ClientKeyData %q: %w", configClusterInfo.CertificateAuthorityData, err)
+				}
+			}
+		}
+
+		if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
+			basicAuth = &promauth.BasicAuthConfig{
+				Username: configAuthInfo.Username,
+				Password: promauth.NewSecret(configAuthInfo.Password),
+			}
+		}
+		token = configAuthInfo.Token
+		tokenFile = configAuthInfo.TokenFile
+	}
+
+	kc := kubeConfig{
+		basicAuth: basicAuth,
+		server:    configClusterInfo.Server,
+		token:     token,
+		tokenFile: tokenFile,
+		tlsConfig: tlsConfig,
+		proxyURL:  configClusterInfo.ProxyURL,
+	}
+
+	return &kc, nil
+}
diff --git a/lib/promscrape/discovery/kubernetes/kubernetes.go b/lib/promscrape/discovery/kubernetes/kubernetes.go
index edd69b860..d5ed48f3a 100644
--- a/lib/promscrape/discovery/kubernetes/kubernetes.go
+++ b/lib/promscrape/discovery/kubernetes/kubernetes.go
@@ -21,7 +21,8 @@ type SDConfig struct {
 	APIServer string `yaml:"api_server,omitempty"`
 
 	// Use role() function for accessing the Role field
-	Role       string `yaml:"role"`
+	Role string `yaml:"role"`
+	// if defined any cluster connection information from HTTPClientConfig will be ignored
 	KubeConfig string `yaml:"kubeconfig_file"`
 
 	HTTPClientConfig promauth.HTTPClientConfig `yaml:",inline"`


Test patch:
diff --git a/lib/promscrape/discovery/kubernetes/api_test.go b/lib/promscrape/discovery/kubernetes/api_test.go
deleted file mode 100644
index a012093c2..000000000
--- a/lib/promscrape/discovery/kubernetes/api_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package kubernetes
-
-import (
-	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
-	"reflect"
-	"testing"
-)
-
-func TestParseKubeConfig(t *testing.T) {
-
-	type testCase struct {
-		name           string
-		sdc            *SDConfig
-		expectedConfig *ApiConfig
-	}
-
-	var cases = []testCase{
-		{
-			name: "token",
-			sdc: &SDConfig{
-				KubeConfig: "testdata/kubeconfig_token.yaml",
-			},
-			expectedConfig: &ApiConfig{
-				token:     "abc",
-				tlsConfig: &promauth.TLSConfig{},
-			},
-		},
-		{
-			name: "cert",
-			sdc: &SDConfig{
-				KubeConfig: "testdata/kubeconfig_cert.yaml",
-			},
-			expectedConfig: &ApiConfig{
-				server: "localhost:8000",
-				tlsConfig: &promauth.TLSConfig{
-					CA:   []byte("authority"),
-					Cert: []byte("certificate"),
-					Key:  []byte("key"),
-				},
-			},
-		},
-		{
-			name: "basic",
-			sdc: &SDConfig{
-				KubeConfig: "testdata/kubeconfig_basic.yaml",
-			},
-			expectedConfig: &ApiConfig{
-				basicAuth: &promauth.BasicAuthConfig{
-					Password: promauth.NewSecret("secret"),
-					Username: "user1",
-				},
-				tlsConfig: &promauth.TLSConfig{},
-			},
-		},
-	}
-	for _, tc := range cases {
-		t.Run(tc.name, func(t *testing.T) {
-			ac, err := buildConfig(tc.sdc)
-			if err != nil {
-				t.Fatalf("unexpected error: %v", err)
-			}
-			if !reflect.DeepEqual(ac, tc.expectedConfig) {
-				t.Fatalf("unexpected result, got: %v, want: %v", ac, tc.expectedConfig)
-			}
-		})
-	}
-
-}
diff --git a/lib/promscrape/discovery/kubernetes/kubeconfig_test.go b/lib/promscrape/discovery/kubernetes/kubeconfig_test.go
new file mode 100644
index 000000000..cb50ff94c
--- /dev/null
+++ b/lib/promscrape/discovery/kubernetes/kubeconfig_test.go
@@ -0,0 +1,84 @@
+package kubernetes
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+)
+
+func TestParseKubeConfigSuccess(t *testing.T) {
+
+	type testCase struct {
+		name           string
+		sdc            *SDConfig
+		expectedConfig *kubeConfig
+	}
+
+	var cases = []testCase{
+		{
+			name: "token",
+			sdc: &SDConfig{
+				KubeConfig: "testdata/good_kubeconfig/with_token.yaml",
+			},
+			expectedConfig: &kubeConfig{
+				server: "http://some-server:8080",
+				token:  "abc",
+			},
+		},
+		{
+			name: "cert",
+			sdc: &SDConfig{
+				KubeConfig: "testdata/good_kubeconfig/with_tls.yaml",
+			},
+			expectedConfig: &kubeConfig{
+				server: "https://localhost:6443",
+				tlsConfig: &promauth.TLSConfig{
+					CA:   []byte("authority"),
+					Cert: []byte("certificate"),
+					Key:  []byte("key"),
+				},
+			},
+		},
+		{
+			name: "basic",
+			sdc: &SDConfig{
+				KubeConfig: "testdata/good_kubeconfig/with_basic.yaml",
+			},
+			expectedConfig: &kubeConfig{
+				server: "http://some-server:8080",
+				basicAuth: &promauth.BasicAuthConfig{
+					Password: promauth.NewSecret("secret"),
+					Username: "user1",
+				},
+			},
+		},
+	}
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			ac, err := buildConfig(tc.sdc)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if !reflect.DeepEqual(ac, tc.expectedConfig) {
+				t.Fatalf("unexpected result, got: %v, want: %v", ac, tc.expectedConfig)
+			}
+		})
+	}
+}
+
+func TestParseKubeConfigFail(t *testing.T) {
+	f := func(name, kubeConfigPath string) {
+		t.Helper()
+		t.Run(name, func(t *testing.T) {
+			sdc := &SDConfig{
+				KubeConfig: kubeConfigPath,
+			}
+			if _, err := buildConfig(sdc); err == nil {
+				t.Fatalf("unexpected result for config file: %s, must return error", kubeConfigPath)
+			}
+		})
+	}
+	f("unsupported options", "testdata/bad_kubeconfig/unsupported_fields")
+	f("missing server address", "testdata/bad_kubeconfig/missing_server.yaml")
+}
diff --git a/lib/promscrape/discovery/kubernetes/testdata/bad_kubeconfig/missing_server.yaml b/lib/promscrape/discovery/kubernetes/testdata/bad_kubeconfig/missing_server.yaml
new file mode 100644
index 000000000..ad8da1866
--- /dev/null
+++ b/lib/promscrape/discovery/kubernetes/testdata/bad_kubeconfig/missing_server.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+clusters:
+  - cluster:
+    name: k8s
+contexts:
+  - context:
+      cluster: k8s
+    name: user1@k8s
+current-context: user1@k8s
+kind: Config
+preferences: {}
\ No newline at end of file
diff --git a/lib/promscrape/discovery/kubernetes/testdata/bad_kubeconfig/unsupported_fields.yaml b/lib/promscrape/discovery/kubernetes/testdata/bad_kubeconfig/unsupported_fields.yaml
new file mode 100644
index 000000000..e875e374e
--- /dev/null
+++ b/lib/promscrape/discovery/kubernetes/testdata/bad_kubeconfig/unsupported_fields.yaml
@@ -0,0 +1,30 @@
+apiVersion: v1
+clusters:
+  - cluster:
+      server: "http://some-server:8080"
+    name: k8s
+contexts:
+  - context:
+      cluster: k8s
+      user: user1
+    name: user1@k8s
+current-context: user1@k8s
+kind: Config
+preferences: {}
+users:
+  - name: user1
+    exec:
+      apiVersion: client.authentication.k8s.io/v1alpha1
+      args:
+        - eks
+        - get-token
+        - --cluster-name
+        - some-cluster
+        - --region
+        - us-east-2
+      command: aws
+      env:
+        - name: AWS_STS_REGIONAL_ENDPOINTS
+          value: regional
+      interactiveMode: IfAvailable
+      provideClusterInfo: false
\ No newline at end of file
diff --git a/lib/promscrape/discovery/kubernetes/testdata/kubeconfig_basic.yaml b/lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_basic.yaml
similarity index 79%
rename from lib/promscrape/discovery/kubernetes/testdata/kubeconfig_basic.yaml
rename to lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_basic.yaml
index a3fa85de3..c666a0905 100644
--- a/lib/promscrape/discovery/kubernetes/testdata/kubeconfig_basic.yaml
+++ b/lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_basic.yaml
@@ -1,7 +1,7 @@
 apiVersion: v1
 clusters:
   - cluster:
-      server: ""
+      server: "http://some-server:8080"
     name: k8s
 contexts:
   - context:
diff --git a/lib/promscrape/discovery/kubernetes/testdata/kubeconfig_cert.yaml b/lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_tls.yaml
similarity index 83%
rename from lib/promscrape/discovery/kubernetes/testdata/kubeconfig_cert.yaml
rename to lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_tls.yaml
index e14136938..df0c86c69 100644
--- a/lib/promscrape/discovery/kubernetes/testdata/kubeconfig_cert.yaml
+++ b/lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_tls.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
 clusters:
   - cluster:
       certificate-authority-data: YXV0aG9yaXR5
-      server: localhost:8000
+      server: https://localhost:6443
     name: k8s
 contexts:
   - context:
diff --git a/lib/promscrape/discovery/kubernetes/testdata/kubeconfig_token.yaml b/lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_token.yaml
similarity index 79%
rename from lib/promscrape/discovery/kubernetes/testdata/kubeconfig_token.yaml
rename to lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_token.yaml
index 6c715ba38..e79af6900 100644
--- a/lib/promscrape/discovery/kubernetes/testdata/kubeconfig_token.yaml
+++ b/lib/promscrape/discovery/kubernetes/testdata/good_kubeconfig/with_token.yaml
@@ -1,7 +1,7 @@
 apiVersion: v1
 clusters:
   - cluster:
-      server: ""
+      server: "http://some-server:8080"
     name: k8s
 contexts:
   - context: