1. 程式人生 > >kubernetes pod-name生成過程 原始碼分析

kubernetes pod-name生成過程 原始碼分析

kubernetes 版本

[[email protected] ~]# kubectl version
Client Version: version.Info{Major:"1", Minor:"11+", GitVersion:"v1.11.0-168+f47446a730ca03", GitCommit:"f47446a730ca037473fb3bf0c5abeea648c1ac12", GitTreeState:"clean", BuildDate:"2018-08-25T21:05:52Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"11+", GitVersion:"v1.11.0-168+f47446a730ca03", GitCommit:"f47446a730ca037473fb3bf0c5abeea648c1ac12", GitTreeState:"clean", BuildDate:"2018-08-25T21:05:52Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"linux/amd64"}
[
[email protected]
~]#

pod name生成規則

kubernetes生成pod有三種方式,如下圖
$GOPATH/src/k8s.io/kubernetes/pkg/kubelet/types/pod_update.go
ku-api
生成pod name的方式有以下幾種方式:
1.靜態podname的生成方式
2.kube-controller-manager生成方式
a. statefulset生成podName方式
b. deployment生成podName方式
c. job生成podName方式
d. daemonset生成podName方式
e. replicaset生成podName方式
f. cronjob生成podName方式
標準podName統一生成name的格式是controllerName-5個隨機字元轉,也有例外,比如statefulset,靜態podname的生成方式就不一樣了


下面一一講解

1.靜態podname的生成方式
這種方式一般是通過kubelet方式建立的,指定kubelet的指定方式--pod-manifest-path
例如:
–pod-manifest-path=/etc/kubernetes/manifests

kubelet
指定之後kubelet會拉取pod起來,並且監聽檔案是否變化,一有變化就馬上重建pod

–pod-manifest-path 對應的kubelet的命令列啟動引數接收值為 StaticPodPath,
原始碼
oo

dd
啟動一個協程計時器去監聽檔案是否變化
ds
listConfig()函式的具體實現
listConfig
extractFromDir(path)函式實現
extract


extractFromFile實現
modify
node
podName-01
這樣子就實現了修改podName的功能了:檔案指定的檔案name-主機名

2.kube-controller-manager生成方式
這一類podName的特點就是controllerName結合5個隨機字串組成,每一個資源物件都繼承podControl的方法,podControl有增刪改pod資訊的介面
pods-name

a. statefulset生成podName方式
首先檢視defaultStatefulSetControl物件的生成方法
demo-01

NewDefaultStatefulSetControl這個方法被NewStatefulSetController這個方法呼叫
NewStatefulSetController 方法呼叫了Informer實時監控記憶體的資訊,Informer這個框架在這裡就不多說了

podControl直接用的實現就是RealPodControl,
setstate
RealPodControl實現了PodControlInterface介面
接下來檢視RealPodControl在statefulsetController中如何使用,大概流程圖是這樣子的
worker—>processNextWorkItem—>sync—>syncStatefulSet—>UpdateStatefulSet—>updateStatefulSet—>ssc.podControl.CreateStatefulPod—>identityMatches—>getPodName

其中worker就是消費ssc的佇列,只要有事件發生,就會add事件到佇列裡
work-ssc
sync的具體實現
sync
syncStatefulSet具體實現
sync
UpdateStatefulSet
statt

如果是建立就執行CreateStatefulPod函式請求kube-apiserver建立pod,具體實現請看下圖
create ssc

updateStatefulSet函式的部分實現

...
	// Enforce the StatefulSet invariants
		if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) {
			continue
		}
...

在identityMatches函式實現了pod的name的修改
identityMatches
setname
這樣子就實現statefulset的podname生成規則了
我們再回到函式CreateStatefulPod

func (spc *realStatefulPodControl) CreateStatefulPod(set *apps.StatefulSet, pod *v1.Pod) error {
	// Create the Pod's PVCs prior to creating the Pod
	if err := spc.createPersistentVolumeClaims(set, pod); err != nil {
		spc.recordPodEvent("create", set, pod, err)
		return err
	}
	// If we created the PVCs attempt to create the Pod
	_, err := spc.client.CoreV1().Pods(set.Namespace).Create(pod)
	// sink already exists errors
	if apierrors.IsAlreadyExists(err) {
		return err
	}
	spc.recordPodEvent("create", set, pod, err)
	return err
}

該函式通過client-go向kube-apiserver發出建立pod物件的請求時,實際上已經有生成podName的規則了,也就是statefulset的name加上5個隨機字串,但是由於statefuleset的特殊性,因此把返回來的podname重新按照statefuleset的方式生成了

接下來我們看看生成podName的規則如何實現
追蹤spc.client.CoreV1().Pods(set.Namespace).Create(pod)函式,實際上就是向kube-apiserver發出POST方法建立POD資源物件的請求
因此基於這個思路,我們來看POST請求的具體實現
$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go

func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, error) {
	admit := a.group.Admit

	optionsExternalVersion := a.group.GroupVersion
	if a.group.OptionsExternalVersion != nil {
		optionsExternalVersion = *a.group.OptionsExternalVersion
	}
	...
	case "POST": // Create a resource.
			var handler restful.RouteFunction
			if isNamedCreater {
				handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
			} else {
				handler = restfulCreateResource(creater, reqScope, admit)
			}
			handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, handler)
			article := getArticleForNoun(kind, " ")
			doc := "create" + article + kind
			if isSubresource {
				doc = "create " + subresource + " of" + article + kind
			}
			route := ws.POST(action.Path).To(handler).
				Doc(doc).
				Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
				Operation("create" + namespaced + kind + strings.Title(subresource) + operationSuffix).
				Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
				Returns(http.StatusOK, "OK", producedObject).
			// TODO: in some cases, the API may return a v1.Status instead of the versioned object
			// but currently go-restful can't handle multiple different objects being returned.
				Returns(http.StatusCreated, "Created", producedObject).
				Returns(http.StatusAccepted, "Accepted", producedObject).
				Reads(defaultVersionedObject).
				Writes(producedObject)
			addParams(route, action.Params)
			routes = append(routes, route)
			...

檢視restfulCreateResource的實現

func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
	return func(req *restful.Request, res *restful.Response) {
		handlers.CreateResource(r, scope, admit)(res.ResponseWriter, req.Request)
	}
}
// CreateResource returns a function that will handle a resource creation.
func CreateResource(r rest.Creater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
	return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
}

$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go

func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		// For performance tracking purposes.
		trace := utiltrace.New("Create " + req.URL.Path)
		defer trace.LogIfLong(500 * time.Millisecond)

		if isDryRun(req.URL) {
			scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
			return
		}

		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
		timeout := parseTimeout(req.URL.Query().Get("timeout"))

		var (
			namespace, name string
			err             error
		)
		if includeName {
			namespace, name, err = scope.Namer.Name(req)
		} else {
			namespace, err = scope.Namer.Namespace(req)
		}
		if err != nil {
			scope.err(err, w, req)
			return
		}
			...
				trace.Step("About to store object in database")
		result, err := finishRequest(timeout, func() (runtime.Object, error) {
			return r.Create(
				ctx,
				name,
				obj,
				rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
				includeUninitialized,
			)
		})
		...

finishRequest該函式儲存物件到etcd裡
這時我們得了解$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go檔案的registerResourceHandlers函式storage rest.Storage入參的含義,

// Install handlers for API resources.
func (a *APIInstaller) Install() ([]metav1.APIResource, *restful.WebService, []error) {
	var apiResources []metav1.APIResource
	var errors []error
	ws := a.newWebService()
	glog.Infof("a.group.Storage===== : %s \n", a.group.Storage)
	// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
	paths := make([]string, len(a.group.Storage))
	var i int = 0
	for path := range a.group.Storage {
		paths[i] = path
		glog.Infof("a.group.Storage[%s]=%s \n", path,a.group.Storage[path])
		i++
	}
	sort.Strings(paths)
	for _, path := range paths {
		apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
		if err != nil {
			errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
		}
		if apiResource != nil {
			apiResources = append(apiResources, *apiResource)
		}
	}
	return apiResources, ws, errors
}

追蹤發現它就是k8s的資源物件,竟然是資源物件,那就要檢視這個資源物件的實現這個介面的具體實現在哪裡
檢視具體實現形式
$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/registry/rest/rest.go

// that objects may implement any of the below interfaces.
type Storage interface {
	// New returns an empty object that can be used with Create and Update after request data has been put into it.
	// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
	New() runtime.Object
}

檢視Storage這個介面以及所有的介面實現形式,猜測是每個k8s資源物件都實現了這些介面
順著這個猜測去檢視Storage這個介面所在的目錄發現了RESTCreateStrategy,而這裡有個函式BeforeCreate就是生成資源物件的名字的,而且呼叫了隨機生成5個字串的包k8s.io/apiserver/pkg/storage/names,那就基本上可以知道BeforeCreate這個函式是生成podname的方法,那BeforeCreate就是關鍵我們就從BeforeCreate呼叫的地方入手
ddsee

追蹤發現有以下這幾個地方使用到
use
接著跟蹤
$GOPATH/src/k8s.io/kubernetes/pkg/registry/core/service/storage/rest.go 158行使用到了,但是這個是服務物件,不是我們分析的POD的物件,這個檔案猜測是針對service物件的restful api,沒有完全實現Interface這個介面$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage/interfaces.go

這裡是呼叫BeforeCreate的方法的地方

// Create inserts a new item according to the unique key from the object.
func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
	if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil {
		return nil, err
	}
	// at this point we have a fully formed object.  It is time to call the validators that the apiserver
	// handling chain wants to enforce.
	if createValidation != nil {
		if err := createValidation(obj.DeepCopyObject()); err != nil {
			return nil, err
		}
	}

接著檢視發現
$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go的物件繼承Interface這個介面以及實現了以下介面
storage
接下來檢視Interface這個介面到底是誰實現了,顧名思義,這個就是etcdv2以及etcdv3實現了具體實現程式碼在/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage這個路徑下的etcd etcd3兩個資料夾,這裡就不去詳細講解了
講到這裡,基本上流程走完了,BeforeCreate這個函式裡的
ddsesd
方法就是生成podName的具體實現過程

...
if len(objectMeta.GetGenerateName()) > 0 && len(objectMeta.GetName()) == 0 {
		objectMeta.SetName(strategy.GenerateName(objectMeta.GetGenerateName()))
	}
...

實現
再分析發現每個k8s資源物件都實現了Store這個物件的方法

store
基本上都有rest storage 目錄以及策略的方法方便操作以及儲存資料到etcd
$GOPATH/src/k8s.io/kubernetes/pkg/registry/core/rest/storage_core.go
這裡有各種資源彙集生成NewLegacyRESTStorage
在註冊路由的時候使用到
$GOPATH/src/k8s.io/kubernetes/pkg/master/master.go

func (m *Master) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) {
	legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter)
	if err != nil {
		glog.Fatalf("Error building core storage: %v", err)
	}

	controllerName := "bootstrap-controller"
	coreClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
	bootstrapController := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient, coreClient)
	m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook)
	m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook)

	if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
		glog.Fatalf("Error in registering group versions: %v", err)
	}
}

同時每個k8s資源都實現RESTStorageProvider該介面
jiekou
同時每個k8s資源都有Strategy策略方法,是為了儲存到etcd的時候選擇儲存策略
storage
到此為止,分析了statefuleset的生成podName的方法已經搞定,接下來繼續
b. deployment生成podName方式
deployment繼承的介面是controller.RSControlInterface,原因是需要擴容縮容,滾動升級,回滾等操作,具體生成podName的流程如下,ReplicaSetController實現了controller.PodControlInterface,基本都一樣
DeploymentController.syncHandler —>syncDeployment–>getReplicaSetsForDeployment—> NewReplicaSetControllerRefManager—> NewBaseController —>syncHandler(屬性值)—>syncReplicaSet—>manageReplicas —>CreatePodsWithControllerRef—>createPods
getNewReplicaSet函式
dsa

CreatePodsWithControllerRef
CreatePodsWithControllerRef

createPods 原理跟statefulset一樣,都是在BreforeCreate方法中新增5個隨機字串,這裡就不多說了
pods

原理和statefulset一樣,都是增加佇列,消費佇列,邏輯,流程都是差不多的,接下來的 c. job生成podName方式 d. daemonset生成podName方式 e. replicaset生成podName方式 f. cronjob生成podName方式也差不多,這裡就不再詳細分析了

容器名字生成規則

原始碼
$GOPATH/src/k8s.io/kubernetes/pkg/kubelet/dockershim/naming.go
demo
dd-demo
SanboxName生成規則
demd

ContainerName生成規則
con
例子
docker-ps

容器名字生成規則具體請參考:
容器生成規則