接下来,开始分析kubernetes的client相关代码。之前有篇”RESTClient,DynamicClient和ClientSet Demo”( https://fankangbest.github.io/2017/07/15/RESTClient-DynamicClient%E5%92%8CClientSet-Demo/  )的分析介绍三种client的用法,三种client都使用下面代码生成config:1
2
3
4
5
6
kubeconfig := flag.String("kubeconfig" , "/root/.kube/config" , "Path to a kube config. Only required if out-of-cluster." )
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("" , *kubeconfig)
if  err != nil  {
	fmt.Println("BuildConfigFromFlags error" )
}
这段代码就是读取/root/.kube/config文件,然后生成config。我们之前分析过kubernetes的kubeconfig机制( https://fankangbest.github.io/2017/07/09/Config%E6%9C%BA%E5%88%B6%E4%BD%BF%E7%94%A8-v1-5-2/  ),里面有介绍如何使用kubeconfig机制。
kubeconfig kubeconfig中定义了与apiserver相关的基本信息,可以通过读取kubeconfig生成相应的config,然后进一步生成client。kubeconfig结构如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion:  v1 
kind:  Config 
users: 
- name:  node 
  user: 
    client-certificate:  /etc/kubernetes/security/serverkey/server.crt 
    client-key:  /etc/kubernetes/security/serverkey/server.key 
clusters: 
- name:  local 
  cluster: 
    certificate-authority:  /etc/kubernetes/security/serverkey/ca.crt 
    server:  https://kubernetes:6443 
contexts: 
- context: 
    cluster:  local 
    user:  node 
  name:  my-context 
current-context:  my-context 
那么,kubernetes是如何把kubeconfig转换成client的config的呢?本次将详细分析。
BuildConfigFromFlags() 从config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)可以看出,通过BuildCOnfigFromFlags()可以将kubeconfig转换成client的config。来看下BuildCOnfigFromFlags(),定义在/pkg/client/unversioned/clientcmd/client_config.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func  BuildConfigFromFlags (masterUrl, kubeconfigPath string )  (*restclient.Config, error) 
	if  kubeconfigPath == ""  && masterUrl == ""  {
		glog.Warningf("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work." )
		kubeconfig, err := restclient.InClusterConfig()
		if  err == nil  {
			return  kubeconfig, nil 
		}
		glog.Warning("error creating inClusterConfig, falling back to default config: " , err)
	}
	
	
	return  NewNonInteractiveDeferredLoadingClientConfig(
		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
可以看出,BuildConfigFromFlags()先调用NewNonInteractiveDeferredLoadingClientConfig()生成DirectClientConfig,然后调用DirectClientCOnfig的ClientConfig()方法生成client的config。NewNonInteractiveDeferredLoadingClientConfig()的参数为ClientConfigLoadingRules和ConfigOverrides。
ClientConfigLoadingRules ClientConfigLoadingRules实现了ClientCOnfigLoader,定义在/pkg/client/unversioned/clientcmd/loader.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type  ClientConfigLoader interface  {
	ConfigAccess
	
	IsDefaultConfig(*restclient.Config) bool 
	
	Load() (*clientcmdapi.Config, error)
}
type  ClientConfigLoadingRules struct  {
	ExplicitPath string 
	Precedence   []string 
	
	
	MigrationRules map [string ]string 
	
	
	DoNotResolvePaths bool 
	
	
	DefaultClientConfig ClientConfig
}
其中:
ExplicitPath: 表示kubeconfig的路径,优先级低于Precedence; 
Precedence: 表示从KUBECONFIG等环境变量中获取到的路径; 
MigrationRules: 表示旧路径到新路径的映射关系; 
DoNotResolvePaths: 表示是否需要把相对路径转换成绝对路径; 
DefaultClientConfig: 表示默认的配置。 
 
接下来看下ClientConfigLoadingRules的主要方法。
Load() 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
func  (rules *ClientConfigLoadingRules)  Load ()  (*clientcmdapi.Config, error) 
	if  err := rules.Migrate(); err != nil  {
		return  nil , err
	}
	errlist := []error{}
	kubeConfigFiles := []string {}
	
	if  len (rules.ExplicitPath) > 0  {
		if  _, err := os.Stat(rules.ExplicitPath); os.IsNotExist(err) {
			return  nil , err
		}
		kubeConfigFiles = append (kubeConfigFiles, rules.ExplicitPath)
	} else  {
		kubeConfigFiles = append (kubeConfigFiles, rules.Precedence...)
	}
	kubeconfigs := []*clientcmdapi.Config{}
	
	
	for  _, filename := range  kubeConfigFiles {
		if  len (filename) == 0  {
			
			continue 
		}
		config, err := LoadFromFile(filename)
		if  os.IsNotExist(err) {
			
			continue 
		}
		if  err != nil  {
			errlist = append (errlist, fmt.Errorf("Error loading config file \"%s\": %v" , filename, err))
			continue 
		}
		kubeconfigs = append (kubeconfigs, config)
	}
	
	mapConfig := clientcmdapi.NewConfig()
	for  _, kubeconfig := range  kubeconfigs {
		
		
		mergo.Merge(mapConfig, kubeconfig)
	}
	
	
	nonMapConfig := clientcmdapi.NewConfig()
	for  i := len (kubeconfigs) - 1 ; i >= 0 ; i-- {
		kubeconfig := kubeconfigs[i]
		mergo.Merge(nonMapConfig, kubeconfig)
	}
	
	
	config := clientcmdapi.NewConfig()
	mergo.Merge(config, mapConfig)
	mergo.Merge(config, nonMapConfig)
	if  rules.ResolvePaths() {
		if  err := ResolveLocalPaths(config); err != nil  {
			errlist = append (errlist, err)
		}
	}
	return  config, utilerrors.NewAggregate(errlist)
}
Load()流程如下:
调用Migrate()把旧kubeconfig文件转换成新kubeconfig文件,这样可以保持兼容性; 
把ExplicitPath或Precedence合并成kubeConfigFiles; 
调用LoadFromFile()读取kubeconfigFiles中的kubeconfig文件,生成kubeconfigs; 
调用第三方包imdario/mergo的Merge(),把kubeconfigs合并成mapConfig; 
反序把kubeconfigs合并成nonMapConfig; 
把mapConfig和nonMapConfig合并成config; 
调用ResolveLocalPaths()解决config中相对路径的问题。 
 
其中步骤4, 5, 6过程感觉有些疑惑,还不知道为什么要这么做,待研究完imdario/mergo再回过头来看。
Migrate() 把旧的/root/.kube/.kubeconfig内容转移至/root/.kube/config。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func  (rules *ClientConfigLoadingRules)  Migrate ()  error 
	if  rules.MigrationRules == nil  {
		return  nil 
	}
	for  destination, source := range  rules.MigrationRules {
		if  _, err := os.Stat(destination); err == nil  {
			
			continue 
		} else  if  os.IsPermission(err) {
			
			continue 
		} else  if  !os.IsNotExist(err) {
			
			return  err
		}
		if  sourceInfo, err := os.Stat(source); err != nil  {
			if  os.IsNotExist(err) || os.IsPermission(err) {
				
				continue 
			}
			
			return  err
		} else  if  sourceInfo.IsDir() {
			return  fmt.Errorf("cannot migrate %v to %v because it is a directory" , source, destination)
		}
		in, err := os.Open(source)
		if  err != nil  {
			return  err
		}
		defer  in.Close()
		out, err := os.Create(destination)
		if  err != nil  {
			return  err
		}
		defer  out.Close()
		if  _, err = io.Copy(out, in); err != nil  {
			return  err
		}
	}
	return  nil 
}
LoadFromFile() 读取kubeconfig,然后生成config。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func  LoadFromFile (filename string )  (*clientcmdapi.Config, error) 
	kubeconfigBytes, err := ioutil.ReadFile(filename)
	if  err != nil  {
		return  nil , err
	}
	config, err := Load(kubeconfigBytes)
	if  err != nil  {
		return  nil , err
	}
	glog.V(6 ).Infoln("Config loaded from file" , filename)
	
	for  key, obj := range  config.AuthInfos {
		obj.LocationOfOrigin = filename
		config.AuthInfos[key] = obj
	}
	for  key, obj := range  config.Clusters {
		obj.LocationOfOrigin = filename
		config.Clusters[key] = obj
	}
	for  key, obj := range  config.Contexts {
		obj.LocationOfOrigin = filename
		config.Contexts[key] = obj
	}
	if  config.AuthInfos == nil  {
		config.AuthInfos = map [string ]*clientcmdapi.AuthInfo{}
	}
	if  config.Clusters == nil  {
		config.Clusters = map [string ]*clientcmdapi.Cluster{}
	}
	if  config.Contexts == nil  {
		config.Contexts = map [string ]*clientcmdapi.Context{}
	}
	return  config, nil 
}
Load() 把data解码到Config结构体中。1
2
3
4
5
6
7
8
9
10
11
12
13
func  Load (data []byte )  (*clientcmdapi.Config, error) 
	
	config := clientcmdapi.NewConfig()
	
	if  len (data) == 0  {
		return  config, nil 
	}
	decoded, _, err := clientcmdlatest.Codec.Decode(data, &unversioned.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config" }, config)
	if  err != nil  {
		return  nil , err
	}
	return  decoded.(*clientcmdapi.Config), nil 
}
ConfigOverrides ConfigOverrides定义在/pkg/client/unversioned/clientcmd/overrides.go中:1
2
3
4
5
6
7
8
9
10
11
type  ConfigOverrides struct  {
	AuthInfo clientcmdapi.AuthInfo
	
	ClusterDefaults clientcmdapi.Cluster
	ClusterInfo     clientcmdapi.Cluster
	Context         clientcmdapi.Context
	CurrentContext  string 
	Timeout         string 
}
ConfigOverrides很简单,就是携带了config的某些信息,这些信息的优先级最高。
DeferredLoadingClientConfig 使用NewNonInteractiveDeferredLoadingClientConfig()可以生成一个DeferredLoadingClientConfig,DeferredLoadingClientConfig定义在/pkg/client/unversioned/clientcmd/merged_client_builder.go中:1
2
3
4
5
6
7
8
9
10
11
type  DeferredLoadingClientConfig struct  {
	loader         ClientConfigLoader
	overrides      *ConfigOverrides
	fallbackReader io.Reader
	clientConfig ClientConfig
	loadingLock  sync.Mutex
	
	icc InClusterConfig
}
DeferredLoadingClientConfig可以把kubeconfig转换成DirectClientConfig,继而DirectClientConfig可以转换成client的config。之所以称为DeferredLoadingClientConfig,是因为DeferredLoadingClientConfig通过DirectClientConfig去生成client的config。
ClientConfig() ClientConfig()先调用createClientConfig()方法生成DirectClientConfig,然后再调用DirectClientConfig的ClientConfig()方法生成client的Config,并返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func  (config *DeferredLoadingClientConfig)  ClientConfig ()  (*restclient.Config, error) 
	
	mergedClientConfig, err := config.createClientConfig()
	if  err != nil  {
		return  nil , err
	}
	
	
	mergedConfig, err := mergedClientConfig.ClientConfig()
	switch  {
	case  err != nil :
		if  !IsEmptyConfig(err) {
			
			return  nil , err
		}
	case  mergedConfig != nil :
		
		
		if  !config.loader.IsDefaultConfig(mergedConfig) {
			return  mergedConfig, nil 
		}
	}
	
	if  config.icc.Possible() {
		glog.V(4 ).Infof("Using in-cluster configuration" )
		return  config.icc.ClientConfig()
	}
	
	return  mergedConfig, err
}
createClientConfig() createClientConfig()先通过loader.Load()把kubeconfig合并成mergedConfig(类型为Config,定义在/pkg/client/unversioned/clientcmd/api/types.go)。然后使用NewNonInteractiveClientConfig()生成DirectClientConfig,并把DirectClientConfig赋值给DeferredLoadingClientConfig的clientConfig,返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func  (config *DeferredLoadingClientConfig)  createClientConfig ()  (ClientConfig, error) 
	if  config.clientConfig == nil  {
		config.loadingLock.Lock()
		defer  config.loadingLock.Unlock()
		if  config.clientConfig == nil  {
			
			
			mergedConfig, err := config.loader.Load()
			if  err != nil  {
				return  nil , err
			}
			var  mergedClientConfig ClientConfig
			if  config.fallbackReader != nil  {
				mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader, config.loader)
			} else  {
				
				mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.loader)
			}
			config.clientConfig = mergedClientConfig
		}
	}
	return  config.clientConfig, nil 
}
NewNonInteractiveClientConfig() NewNonInteractiveClientConfig()或NewInteractiveClientConfig()可以生成DirectClientConfig,两者的区别是是否传入fallbackReader。fallbackReader是认证信息不足调用的(还未做深入研究)。两个函数定义在/pkg/client/unversioned/clientcmd/client_config.go中:1
2
3
4
5
6
7
8
9
func  NewDefaultClientConfig (config clientcmdapi.Config, overrides *ConfigOverrides)  ClientConfig 
	return  &DirectClientConfig{config, config.CurrentContext, overrides, nil , NewDefaultClientConfigLoadingRules()}
}
func  NewNonInteractiveClientConfig (config clientcmdapi.Config, contextName string , overrides *ConfigOverrides, configAccess ConfigAccess)  ClientConfig 
	return  &DirectClientConfig{config, contextName, overrides, nil , configAccess}
}
DirectClientConfig DirectClientConfig可以直接生成client的config,定义在/pkg/client/unversioned/clientcmd/client_config.go中:1
2
3
4
5
6
7
8
type  DirectClientConfig struct  {
	config         clientcmdapi.Config
	contextName    string 
	overrides      *ConfigOverrides
	fallbackReader io.Reader
	configAccess   ConfigAccess
}
ClientConfig() ClientConfig()方法使用clientConfig := &restclient.Config{},并把DirectClientConfig上的内容填充到clientConfig中,clientConfig就是之前称呼的client的config。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
func  (config *DirectClientConfig)  ClientConfig ()  (*restclient.Config, error) 
	
	
	
	
	configAuthInfo, err := config.getAuthInfo()
	if  err != nil  {
		return  nil , err
	}
	_, err = config.getContext()
	if  err != nil  {
		return  nil , err
	}
	configClusterInfo, err := config.getCluster()
	if  err != nil  {
		return  nil , err
	}
	if  err := config.ConfirmUsable(); err != nil  {
		return  nil , err
	}
	clientConfig := &restclient.Config{}
	clientConfig.Host = configClusterInfo.Server
	if  len (config.overrides.Timeout) > 0  {
		if  i, err := strconv.ParseInt(config.overrides.Timeout, 10 , 64 ); err == nil  && i >= 0  {
			clientConfig.Timeout = time.Duration(i) * time.Second
		} else  if  requestTimeout, err := time.ParseDuration(config.overrides.Timeout); err == nil  {
			clientConfig.Timeout = requestTimeout
		} else  {
			return  nil , fmt.Errorf("Invalid value for option '--request-timeout'. Value must be a single integer, or an integer followed by a corresponding time unit (e.g. 1s | 2m | 3h)" )
		}
	}
	if  u, err := url.ParseRequestURI(clientConfig.Host); err == nil  && u.Opaque == ""  && len (u.Path) > 1  {
		u.RawQuery = "" 
		u.Fragment = "" 
		clientConfig.Host = u.String()
	}
	if  len (configAuthInfo.Impersonate) > 0  {
		clientConfig.Impersonate = configAuthInfo.Impersonate
	}
	
	if  restclient.IsConfigTransportTLS(*clientConfig) {
		var  err error
		
		
		
		var  persister restclient.AuthProviderConfigPersister
		if  config.configAccess != nil  {
			authInfoName, _ := config.getAuthInfoName()
			persister = PersisterForUser(config.configAccess, authInfoName)
		}
		userAuthPartialConfig, err := getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
		if  err != nil  {
			return  nil , err
		}
		mergo.Merge(clientConfig, userAuthPartialConfig)
		serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
		if  err != nil  {
			return  nil , err
		}
		mergo.Merge(clientConfig, serverAuthPartialConfig)
	}
	return  clientConfig, nil 
}
getCluster() 在这些getXXX()方法中,会优先使用overrides中的信息。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func  (config *DirectClientConfig)  getCluster ()  (clientcmdapi.Cluster, error) 
	clusterInfos := config.config.Clusters
	clusterInfoName, required := config.getClusterName()
	var  mergedClusterInfo clientcmdapi.Cluster
	mergo.Merge(&mergedClusterInfo, config.overrides.ClusterDefaults)
	if  configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
		mergo.Merge(&mergedClusterInfo, configClusterInfo)
	} else  if  required {
		return  clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist" , clusterInfoName)
	}
	mergo.Merge(&mergedClusterInfo, config.overrides.ClusterInfo)
	
	
	caLen := len (config.overrides.ClusterInfo.CertificateAuthority)
	caDataLen := len (config.overrides.ClusterInfo.CertificateAuthorityData)
	if  config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0  && caDataLen == 0  {
		mergedClusterInfo.CertificateAuthority = "" 
		mergedClusterInfo.CertificateAuthorityData = nil 
	}
	return  mergedClusterInfo, nil 
}
func  (config *DirectClientConfig)  getClusterName ()  (string , bool ) 
	if  len (config.overrides.Context.Cluster) != 0  {
		return  config.overrides.Context.Cluster, true 
	}
	context, _ := config.getContext()
	return  context.Cluster, false 
}
总结 ClientConfigLoadingRules作为ClientConfigLoader可以把kubeconfig文件(可能多个)转换成clientcmdapi.Config(定义在types.go中);