1、概述
在使用controller-runtime框架進行kubernetes二次開發的時候,程序中通常都會使用GetConfigOrDie()方法獲取集群配置以便與kubernetes集群進行連接,示例如下:
opts := ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
LeaderElection: enableLeaderElection,
Port: 9443,
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opts)
本文主要通過剖析GetConfigOrDie()方法來進行講解controller-runtime框架的獲取kubernetes集群認證配置文件的加載順序。
2、 GetConfigOrDie()源碼剖析
源碼文件位置:sigs.k8s.io/controller-runtime/pkg/client/config/config.go
注意:本文中不特殊說明源碼文件位置的代碼片段都是sigs.k8s.io/controller-runtime/pkg/client/config/config.go文件,本文sigs.k8s.io/controller-runtime框架版本為v0.9.6。
// GetConfigOrDie creates a *rest.Config for talking to a Kubernetes apiserver.
// If --kubeconfig is set, will use the kubeconfig file at that location. Otherwise will assume running
// in cluster and use the cluster provided kubeconfig.
//
// Will log an error and exit if there is an error creating the rest.Config.
func GetConfigOrDie() *rest.Config {
config, err := GetConfig()
if err != nil {
log.Error(err, "unable to get kubeconfig")
os.Exit(1)
}
return config
}
可以看到GetConfigOrDie()方法如同它的名字,假設獲取*rest.Config失敗便會以異常狀態退出程序。
func GetConfig() (*rest.Config, error) {
return GetConfigWithContext("")
}
func GetConfigWithContext(context string) (*rest.Config, error) {
cfg, err := loadConfig(context)
if err != nil {
return nil, err
}
if cfg.QPS == 0.0 {
cfg.QPS = 20.0
cfg.Burst = 30.0
}
return cfg, nil
}
以上方法可以配置*rest.Config.QPS,訪問kubernetes集群時的認證配置文件的加載順序核心邏輯方法為loadConfig方法,邏輯如下:
var (
kubeconfig string
log = logf.RuntimeLog.WithName("client").WithName("config")
)
func init() {
// 啟動程序時客戶端傳參kubeconfig
flag.StringVar(&kubeconfig, "kubeconfig", "",
"Paths to a kubeconfig. Only required if out-of-cluster.")
}
// loadConfig loads a REST Config as per the rules specified in GetConfig.
func loadConfig(context string) (*rest.Config, error) {
// If a flag is specified with the config location, use that
// 1、如果flag初始化了kubeconfig,則從kubeconfig中讀取集群配置
if len(kubeconfig) > 0 {
return loadConfigWithContext("", &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, context)
}
// If the recommended kubeconfig env variable is not specified,
// try the in-cluster config.
// 2、 否則從環境變量KUBECONFIG讀取,若沒有則從集群內部讀取,這種場景適用於部署到kubernetes中的場景
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if len(kubeconfigPath) == 0 {
if c, err := loadInClusterConfig(); err == nil {
return c, nil
}
}
// If the recommended kubeconfig env variable is set, or there
// is no in-cluster config, try the default recommended locations.
//
// NOTE: For default config file locations, upstream only checks
// $HOME for the user's home directory, but we can also try
// os/user.HomeDir when $HOME is unset.
//
// TODO(jlanford): could this be done upstream?
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// 是否存在環境變量HOME
if _, ok := os.LookupEnv("HOME"); !ok {
u, err := user.Current()
if err != nil {
return nil, fmt.Errorf("could not get current user: %v", err)
}
// 3、如果通過KUBECONFIG環境變量和集群內部這兩個方式都沒有找到(包括從集群內部讀取集群配置報錯),則會讀取默認配置$HOME/.kube/config,如果不存在環境變量HOME,則會讀取配置文件user.HomeDir/.kube/config
loadingRules.Precedence = append(loadingRules.Precedence, path.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName))
}
return loadConfigWithContext("", loadingRules, context)
}
func loadConfigWithContext(apiServerURL string, loader clientcmd.ClientConfigLoader, context string) (*rest.Config, error) {
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
loader,
&clientcmd.ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
Server: apiServerURL,
},
CurrentContext: context,
}).ClientConfig()
}
接下來再看下從集群內部和通過環境變量(包括讀取默認配置)獲取集群配置的代碼邏輯。
1)集群內部獲取集群配置:
InClusterConfig方法,邏輯很簡單:在Pod容器內,通過serviceaccout提供的token值和容器內部集群的環境變量生成*rest.Config對象。
InClusterConfig方法源碼文件位置:k8s.io/client-go/rest/config.go
// InClusterConfig returns a config object which uses the service account
// kubernetes gives to pods. It's intended for clients that expect to be
// running inside a pod running on kubernetes. It will return ErrNotInCluster
// if called from a process not running in a kubernetes environment.
func InClusterConfig() (*Config, error) {
const (
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
if len(host) == 0 || len(port) == 0 {
return nil, ErrNotInCluster
}
token, err := ioutil.ReadFile(tokenFile)
if err != nil {
return nil, err
}
tlsClientConfig := TLSClientConfig{}
if _, err := certutil.NewPool(rootCAFile); err != nil {
klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
} else {
tlsClientConfig.CAFile = rootCAFile
}
return &Config{
// TODO: switch to using cluster DNS.
Host: "https://" + net.JoinHostPort(host, port),
TLSClientConfig: tlsClientConfig,
BearerToken: string(token),
BearerTokenFile: tokenFile,
}, nil
}
2)通過環境變量(包括讀取默認配置)獲取集群配置:
不管是flag初始化了kubeconfig,還是從環境變量KUBECONFIG讀取,或者是讀取默認配置(user.HomeDir/.kube/config或user.HomeDir/.kube/config) 都會實例化ClientConfigLoadingRules結構體對象。
本小節源碼文件位置:k8s.io/client-go/tools/clientcmd/loader.go
type ClientConfigLoadingRules struct {
ExplicitPath string
Precedence []string
// MigrationRules is a map of destination files to source files. If a destination file is not present, then the source file is checked.
// If the source file is present, then it is copied to the destination file BEFORE any further loading happens.
MigrationRules map[string]string
// DoNotResolvePaths indicates whether or not to resolve paths with respect to the originating files. This is phrased as a negative so
// that a default object that doesn't set this will usually get the behavior it wants.
DoNotResolvePaths bool
// DefaultClientConfig is an optional field indicating what rules to use to calculate a default configuration.
// This should match the overrides passed in to ClientConfig loader.
DefaultClientConfig ClientConfig
// WarnIfAllMissing indicates whether the configuration files pointed by KUBECONFIG environment variable are present or not.
// In case of missing files, it warns the user about the missing files.
WarnIfAllMissing bool
}
其中:
- ExplicitPath: 表示kubeconfig的路徑,優先級高於Precedence;
- Precedence: 表示從KUBECONFIG等環境變量中獲取到的路徑;
- MigrationRules: 表示舊路徑到新路徑的映射關系;
- DoNotResolvePaths: 表示是否需要把相對路徑轉換成絕對路徑;
- DefaultClientConfig: 表示默認的配置。
創建ClientConfigLoadingRules結構體對象方法NewDefaultClientConfigLoadingRules()
const (
RecommendedConfigPathFlag = "kubeconfig"
RecommendedConfigPathEnvVar = "KUBECONFIG"
RecommendedHomeDir = ".kube"
RecommendedFileName = "config"
RecommendedSchemaName = "schema"
)
var (
RecommendedConfigDir = filepath.Join(homedir.HomeDir(), RecommendedHomeDir)
RecommendedHomeFile = filepath.Join(RecommendedConfigDir, RecommendedFileName)
RecommendedSchemaFile = filepath.Join(RecommendedConfigDir, RecommendedSchemaName)
)
// NewDefaultClientConfigLoadingRules returns a ClientConfigLoadingRules object with default fields filled in. You are not required to
// use this constructor
func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules {
chain := []string{}
warnIfAllMissing := false
envVarFiles := os.Getenv(RecommendedConfigPathEnvVar)
// 從環境變量KUBECONFIG獲取集群配置
if len(envVarFiles) != 0 {
fileList := filepath.SplitList(envVarFiles)
// prevent the same path load multiple times
chain = append(chain, deduplicate(fileList)...)
warnIfAllMissing = true
// 從讀取$HOME/kube/config文件中獲取集群配置
} else {
chain = append(chain, RecommendedHomeFile)
}
return &ClientConfigLoadingRules{
Precedence: chain,
MigrationRules: currentMigrationRules(),
WarnIfAllMissing: warnIfAllMissing,
}
}
3、總結
controller-runtime框架通過GetConfigOrDie()方法獲取kubernetes集群認證配置文件的加載順序如下:
- 1、如果flag初始化了kubeconfig,則從kubeconfig中讀取集群配置;
- 2、 否則從環境變量KUBECONFIG讀取,若沒有則從集群內部讀取,這種場景適用於部署到kubernetes中的場景,在Pod容器內,通過serviceaccout提供的token值和容器內部集群的環境變量生成*rest.Config對象。;
- 3、如果通過KUBECONFIG環境變量和集群內部這兩個方式都沒有找到(包括從集群內部讀取集群配置報錯),則會讀取默認配置$HOME/.kube/config(如果不存在環境變量HOME,則會從user.HomeDir/.kube/config中讀取集群配置)。
