kubernetes 版本
[root@master-47-35 ~]# 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"}
[root@master-47-35 ~]#
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的功能了:文件指定的podName-主机名
2.kube-controller-manager生成方式
这一类podName的特点就是controllerName结合5个随机字符串组成,每一个资源对象都继承podControl的方法,podControl有增删改pod信息的接口
a. statefulset生成podName方式
首先查看defaultStatefulSetControl对象的生成方法
NewDefaultStatefulSetControl这个方法被NewStatefulSetController这个方法调用
NewStatefulSetController 方法调用了Informer实时监控内存的信息,Informer这个框架在这里就不多说了,就是一个实时监听内存中相关k8s资源对象的工具
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生成规则了:statefulsetName-1…
我们再回到函数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
接下来我们看看生成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这个接口以及所有的接口实现形式,猜测是etcd实现了这些接口
顺着这个猜测去查看Storage这个接口所在的目录发现了RESTCreateStrategy,而这里有个函数BeforeCreate就是生成资源对象的名字的,而且调用了随机生成5个字符串的包k8s.io/apiserver/pkg/storage/names
,那就基本上可以知道BeforeCreate这个函数是生成podname的方法,那我们可以知道BeforeCreate函数就是生成podName的关键,因此我们就从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
这里是Service调用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实现了Interface这个接口,具体实现代码在/src/k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage这个路径下的etcd etcd3两个文件夹,这里就不去详细讲解了
讲到这里,基本上流程走完了,接着,我们来看BeforeCreate这个函数里的方法就是生成podName的具体实现过程
方法就是生成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函数
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生成规则
容器名字生成规则具体请参考:
容器生成规则