关于 InClusterConfig 的一些知识

我们在容器中,一般使用InClusterConfig来构造Kubernetes ClientSet,并访问K8s Apiserver。使用示例如下:

	// "k8s.io/client-go/kubernetes"
	// "k8s.io/client-go/rest"
	config, err := rest.InClusterConfig()
	if err != nil {
		klog.Fatalf("Failed to create config: %v", err)
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		klog.Fatalf("Failed to create client: %v", err)
	}

那工作原理是什么呢?都涉及到哪些知识点。InClusterConfig的代码路径为k8s.io/client-go/rest/config.go,函数代码并不复杂:

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
}

首先是在/var/run/secrets/kubernetes.io/serviceaccount/路径下查找两个文件tokenca.crt,这个路径是secret的默认挂载路径,我们一般没有主动创建secret,怎么会有这些文件呢?这里其实经历了两步:创建secret以及挂载secret。

secret

创建secret:这个是K8s Controller Manager中的ServiceAccountController做的,ServiceAccountController中有一个TokensController,其有个方法ensureReferencedToken,就是为ServiceAccount生成secret的,生成secret包含三个field: token、ca.crt、namespace。生成secret之后还有将此secret更新到ServiceAccount中去。

github.com/kubernetes-sigs/sig-storage-local-static-provisioner项目为例,我们创建ServiceAccount时,它长这个样子:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: local-storage-admin
  namespace: default

等ServiceAccount创建好之后,它变成了下面这个样子:

apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2020-09-18T09:39:22Z"
  name: local-storage-admin
  namespace: default
  resourceVersion: "732"
  uid: 7d438765-46af-4fc4-b90b-1d0b8bf1c117
secrets:
- name: local-storage-admin-token-2gs2n

下面引用的secret就是sa controller给创建的。

挂载secret:这个是webhook做的。参考代码路径为:k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission.go

service注入

还是就是读取两个环境变量:KUBERNETES_SERVICE_HOSTKUBERNETES_SERVICE_PORT,这个是kubernetes这个service的ClusterIP以及端口:

decent@ubuntu:~/yamls$ kubectl get service
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   4h40m

通过service通过环境变量的方式注入到容器中,是kubernete暴露service的一种方式,另一种方式是通过DNS查询的方式,每个service以及Pod都有自己的A记录格式,在创建Pod的时候,可以指定DNS服务器的地址(也就是core dns的service的clusterIP),通过coredns来解析service的A记录。

kubernetes这个service是kubelet无条件注入到每个pod中的,一般情况下,只有在同一个namespace中的service才会注入到pod。

权限控制

还是以项目sig-storage-local-static-provisioner为例,通过创建ClusterRole(因为是集群范围的),并将此ClusterRole与ServiceAccount绑定,然后在pod template里指定ServiceAccount名字。相关资源如下:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: local-storage-provisioner-pv-binding
  namespace: default
subjects:
- kind: ServiceAccount
  name: local-storage-admin
  namespace: default
roleRef:
  kind: ClusterRole
  name: system:persistent-volume-provisioner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: local-storage-provisioner-node-clusterrole
  namespace: default
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: local-storage-provisioner-node-binding
  namespace: default
subjects:
- kind: ServiceAccount
  name: local-storage-admin
  namespace: default
roleRef:
  kind: ClusterRole
  name: local-storage-provisioner-node-clusterrole
  apiGroup: rbac.authorization.k8s.io

上面定义了两个ClusterRoleBinding,将同一个ServiceAccount和两个ClusterRole绑定在了一起,一个是在这里定义的,另一个是系统默认配置的system:persistent-volume-provisioner.

还有就是K8s认证的一些东西,这里涉及到两个文件,一个是token,一个是CA,这个涉及到东西比较多,有时间再组织下。