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
生成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會拉取pod起來,並且監聽檔案是否變化,一有變化就馬上重建pod
–pod-manifest-path 對應的kubelet的命令列啟動引數接收值為 StaticPodPath,
原始碼
啟動一個協程計時器去監聽檔案是否變化
listConfig()函式的具體實現
extractFromDir(path)函式實現
extractFromFile實現
這樣子就實現了修改podName的功能了:檔案指定的檔案name-主機名
2.kube-controller-manager生成方式
這一類podName的特點就是controllerName結合5個隨機字串組成,每一個資源物件都繼承podControl的方法,podControl有增刪改pod資訊的介面
a. statefulset生成podName方式
首先檢視defaultStatefulSetControl物件的生成方法
NewDefaultStatefulSetControl這個方法被NewStatefulSetController這個方法呼叫
NewStatefulSetController 方法呼叫了Informer實時監控記憶體的資訊,Informer這個框架在這裡就不多說了
podControl直接用的實現就是RealPodControl,
RealPodControl實現了PodControlInterface介面
接下來檢視RealPodControl在statefulsetController中如何使用,大概流程圖是這樣子的
worker—>processNextWorkItem—>sync—>syncStatefulSet—>UpdateStatefulSet—>updateStatefulSet—>ssc.podControl.CreateStatefulPod—>identityMatches—>getPodName
其中worker就是消費ssc的佇列,只要有事件發生,就會add事件到佇列裡
sync的具體實現
syncStatefulSet具體實現
UpdateStatefulSet
如果是建立就執行CreateStatefulPod函式請求kube-apiserver建立pod,具體實現請看下圖
updateStatefulSet函式的部分實現
...
// Enforce the StatefulSet invariants
if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) {
continue
}
...
在identityMatches函式實現了pod的name的修改
這樣子就實現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呼叫的地方入手
追蹤發現有以下這幾個地方使用到
接著跟蹤
$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這個介面以及實現了以下介面
接下來檢視Interface這個介面到底是誰實現了,顧名思義,這個就是etcdv2以及etcdv3實現了具體實現程式碼在/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage這個路徑下的etcd etcd3兩個資料夾,這裡就不去詳細講解了
講到這裡,基本上流程走完了,BeforeCreate這個函式裡的
方法就是生成podName的具體實現過程
...
if len(objectMeta.GetGenerateName()) > 0 && len(objectMeta.GetName()) == 0 {
objectMeta.SetName(strategy.GenerateName(objectMeta.GetGenerateName()))
}
...
再分析發現每個k8s資源物件都實現了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該介面
同時每個k8s資源都有Strategy策略方法,是為了儲存到etcd的時候選擇儲存策略
到此為止,分析了statefuleset的生成podName的方法已經搞定,接下來繼續
b. deployment生成podName方式
deployment繼承的介面是controller.RSControlInterface,原因是需要擴容縮容,滾動升級,回滾等操作,具體生成podName的流程如下,ReplicaSetController實現了controller.PodControlInterface,基本都一樣
DeploymentController.syncHandler —>syncDeployment–>getReplicaSetsForDeployment—> NewReplicaSetControllerRefManager—> NewBaseController —>syncHandler(屬性值)—>syncReplicaSet—>manageReplicas —>CreatePodsWithControllerRef—>createPods
getNewReplicaSet函式
CreatePodsWithControllerRef
createPods 原理跟statefulset一樣,都是在BreforeCreate方法中新增5個隨機字串,這裡就不多說了
原理和statefulset一樣,都是增加佇列,消費佇列,邏輯,流程都是差不多的,接下來的 c. job生成podName方式 d. daemonset生成podName方式 e. replicaset生成podName方式 f. cronjob生成podName方式也差不多,這裡就不再詳細分析了
容器名字生成規則
原始碼
$GOPATH/src/k8s.io/kubernetes/pkg/kubelet/dockershim/naming.go
SanboxName生成規則
ContainerName生成規則
例子
容器名字生成規則具體請參考:
容器生成規則