Professional Documents
Culture Documents
致谢
Introduction
kubernetes
kube-apiserver 的设计与实现
kube-apiserver 中 apiserver service 的实现
node controller 源码分析
job controller 源码分析
garbage collector controller 源码分析
daemonset controller 源码分析
statefulset controller 源码分析
deployment controller 源码分析
replicaset controller 源码分析
kube-scheduler 源码分析
kube-scheduler predicates 与 priorities 调度算法源码分析
kube-scheduler 优先级与抢占机制源码分析
kubernetes service 原理解析
kube-proxy 源码分析
kube-proxy iptables 模式源码分析
kube-proxy ipvs 模式源码分析
kubelet 架构浅析
kubelet 启动流程分析
kubelet 创建 pod 的流程
kubelet 状态上报的方式
kubelet 中事件处理机制
kubelet statusManager 源码分析
kubernetes 中 Qos 的设计与实现
kubelet 中垃圾回收机制的设计与实现
致谢
书栈网仅提供文档编写、整理、归类等功能,以及对文档内容的生成和导出工具。
文档内容由网友们编写和整理,书栈网难以确认文档内容知识点是否错漏。如果您在阅读文档获取
知识的时候,发现文档内容有不恰当的地方,请向我们反馈,让我们共同携手,将知识准确、高效且有
效地传递给每一个人。
同时,如果您在日常工作、生活和学习中遇到有价值有营养的知识文档,欢迎分享到书栈网,为知
识的传承献上您的一份力量!
如果当前文档生成时间太久,请到书栈网获取最新的文档,以跟上知识更新换代的步伐。
内容来源:田飞雨 https://github.com/gosoon/source-code-reading-notes
文档地址:http://www.bookstack.cn/books/source-code-reading-notes
书栈官网:https://www.bookstack.cn
书栈开源:https://github.com/TruthHun
分享,让知识传承更久远! 感谢知识的创造者,感谢知识的分享者,也感谢每一位阅读到此处的
读者,因为我们都将成为知识的传承者。
1、关于本书
本书主要记录工作过程中阅读过的一些开源项目的源码,并加以自己的分析与注解,目前专注于 k8s
云原生实践,包括但不限于 docker、kubernetes、etcd、promethus、istio、knative、
serverless 等相关云原生项目。
2、内容更新
本书会不定期更新,目前专注于 k8s 源码的阅读,输出的阅读笔记会同步至多个平台,主要有以下几
个:
本书 github 地址:https://github.com/gosoon/source-code-reading-notes
在线阅读:https://blog.tianfeiyu.com/source-code-reading-notes/
个人博客:https://blog.tianfeiyu.com
简书:田飞雨
知乎专栏:kubernetes 源码分析
腾讯云—云+社区:田飞雨的专栏
微信公众号:田飞雨
3、贡献
如果你对云原生领域的开源项目感兴趣,欢迎参与本书编写!
kube-apiserver 的设计与实现
kube-apiserver 中 apiserver service 的实现
node controller 源码分析
job controller 源码分析
garbage collector controller 源码分析
daemonset controller 源码分析
statefulset controller 源码分析
deployment controller 源码分析
replicaset controller 源码分析
kube-scheduler 源码分析
kube-scheduler predicates 与 priorities 调度算法源码分析
kube-scheduler 优先级与抢占机制源码分析
kubernetes service 原理解析
kube-proxy 源码分析
kube-proxy iptables 模式源码分析
kube-proxy ipvs 模式源码分析
kubelet 架构浅析
kubelet 启动流程分析
kubelet 创建 pod 的流程
kubelet 状态上报的方式
kubelet 中事件处理机制
kubelet statusManager 源码分析
kubernetes 中 Qos 的设计与实现
kubelet 中垃圾回收机制的设计与实现
提供 Kubernetes API,包括认证授权、数据校验以及集群状态变更等,供客户端及其他组件
调用;
代理集群中的一些附加组件组件,如 Kubernetes UI、metrics-server、npd 等;
创建 kubernetes 服务,即提供 apiserver 的 Service,kubernetes Service;
资源在不同版本之间的转换;
kube-apiserver 处理流程
kube-apiserver 主要通过对外提供 API 的方式与其他组件进行交互,可以调用 kube-
apiserver 的接口 $ curl -k https://<masterIP>:6443 或者通过其提供的 swagger-ui 获
取到,其主要有以下三种 API:
Decoder
Admission
在解码完成后,需要通过验证集群的全局约束来检查是否可以创建或更新对象,并根据集群配置设置默
认值。在 k8s.io/kubernetes/plugin/pkg/admission 目录下可以看到 kube-apiserver 可
以使用的所有全局约束插件,kube-apiserver 在启动时通过设置 --enable-admission-
plugins 参数来开启需要使用的插件,通过 ValidatingAdmissionWebhook 或
MutatingAdmissionWebhook 添加的插件也都会在此处进行工作。
Validation
kube-apiserver 中的组件
kube-apiserver 共由 3 个组件构成(Aggregator、KubeAPIServer、
APIExtensionServer),这些组件依次通过 Delegation 处理请求:
Aggregator:暴露的功能类似于一个七层负载均衡,将来自用户的请求拦截转发给其他服务
器,并且负责整个 APIServer 的 Discovery 功能;
KubeAPIServer :负责对请求的一些通用处理,认证、鉴权等,以及处理各个内建资源的
REST 服务;
APIExtensionServer:主要处理 CustomResourceDefinition(CRD)和
CustomResource(CR)的 REST 请求,也是 Delegation 的最后一环,如果对应 CR 不能
被处理的话则会返回 404。
Aggregator
启用 API Aggregation
1. --proxy-client-cert-file=/etc/kubernetes/certs/proxy.crt
2. --proxy-client-key-file=/etc/kubernetes/certs/proxy.key
3. --requestheader-client-ca-file=/etc/kubernetes/certs/proxy-ca.crt
4. --requestheader-allowed-names=aggregator
5. --requestheader-extra-headers-prefix=X-Remote-Extra-
6. --requestheader-group-headers=X-Remote-Group
7. --requestheader-username-headers=X-Remote-User
KubeAPIServer
APIExtensionServer
kube-apiserver 启动流程分析
kubernetes 版本:v1.16
Run
Run 方法的主要逻辑为:
k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:147
CreateServerChain
k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:165
aggregatorConfig, err :=
createAggregatorConfig(*kubeAPIServerConfig.GenericConfig,
completedOptions.ServerRunOptions, kubeAPIServerConfig.
ExtraConfig.VersionedInformers, serviceResolver, proxyTransport,
32. pluginInitializer)
33. if err != nil {
34. return nil, err
35. }
36.
37. // 6、初始化 AggregatorServer
aggregatorServer, err := createAggregatorServer(aggregatorConfig,
38. kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)
39. if err != nil {
40. return nil, err
41. }
42.
43. // 7、判断是否启动非安全端口的 http server
44. if insecureServingInfo != nil {
insecureHandlerChain :=
kubeserver.BuildInsecureHandlerChain(aggregatorServer.GenericAPIServer.UnprotectedHandl
45. kubeAPIServerConfig.GenericConfig)
if err := insecureServingInfo.Serve(insecureHandlerChain,
46. kubeAPIServerConfig.GenericConfig.RequestTimeout, stopCh); err != nil {
47. return nil, err
48. }
49. }
50. return aggregatorServer, nil
51. }
CreateKubeAPIServerConfig
k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:271
1. func CreateKubeAPIServerConfig(
2. s completedServerRunOptions,
3. nodeTunneler tunneler.Tunneler,
4. proxyTransport *http.Transport,
5. ) (......) {
6.
7. // 1、构建 genericConfig
buildGenericConfig
主要逻辑为:
k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:386
1. func buildGenericConfig(
2. s *options.ServerRunOptions,
3. proxyTransport *http.Transport,
4. ) (......) {
5. // 1、为 genericConfig 设置默认值
6. genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs)
genericConfig.MergedResourceConfig =
7. master.DefaultAPIResourceConfigSource()
8.
if lastErr = s.GenericServerRunOptions.ApplyTo(genericConfig); lastErr !=
9. nil {
10. return
11. }
12. ......
13.
14. genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(......)
15. genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
16. genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
17. sets.NewString("watch", "proxy"),
18. sets.NewString("attach", "exec", "proxy", "log", "portforward"),
19. )
20.
21. kubeVersion := version.Get()
22. genericConfig.Version = &kubeVersion
23.
24. storageFactoryConfig := kubeapiserver.NewStorageFactoryConfig()
25. storageFactoryConfig.ApiResourceConfig = genericConfig.MergedResourceConfig
createAPIExtensionsServer
k8s.io/kubernetes/cmd/kube-apiserver/app/apiextensions.go:94
func createAPIExtensionsServer(apiextensionsConfig
*apiextensionsapiserver.Config, delegateAPIServer
genericapiserver.DelegationTarget) (*
1. apiextensionsapiserver.CustomResourceDefinitions, error) {
2. return apiextensionsConfig.Complete().New(delegateAPIServer)
3. }
k8s.io/kubernetes/staging/src/k8s.io/apiextensions-
apiserver/pkg/apiserver/apiserver.go:132
apiGroupInfo.VersionedResourcesStorageMap[v1beta1.SchemeGroupVersion.Version] =
21. storage
22. }
23. if apiResourceConfig.VersionEnabled(v1.SchemeGroupVersion) {
24. ......
25. }
26.
27. // 3、注册 APIGroup
28. if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
29. return nil, err
30. }
31.
32. // 4、初始化需要使用的 controller
crdClient, err :=
33. internalclientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
34. if err != nil {
35. return nil, fmt.Errorf("failed to create clientset: %v", err)
36. }
s.Informers = internalinformers.NewSharedInformerFactory(crdClient,
37. 5*time.Minute)
38.
39. ......
establishingController :=
establish.NewEstablishingController(s.Informers.Apiextensions().InternalVersion().
40. CustomResourceDefinitions(), crdClient.Apiextensions())
41. crdHandler, err := NewCustomResourceDefinitionHandler(......)
42. if err != nil {
43. return nil, err
44. }
45. s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/",
46. crdHandler)
47.
crdController :=
NewDiscoveryController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefi
48. versionDiscoveryHandler, groupDiscoveryHandler)
namingController :=
status.NewNamingConditionController(s.Informers.Apiextensions().InternalVersion().
49. crdClient.Apiextensions())
nonStructuralSchemaController :=
nonstructuralschema.NewConditionController(s.Informers.Apiextensions().InternalVersion
50. CustomResourceDefinitions(), crdClient.Apiextensions())
apiApprovalController :=
apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers
51. InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
52. finalizingController := finalizer.NewCRDFinalizer(
53. s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
54. crdClient.Apiextensions(),
55. crdHandler,
56. )
57. var openapiController *openapicontroller.Controller
if
utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourcePublishOpenA
58. {
openapiController =
59. openapicontroller.NewController(s.Informers.Apiextensions().InternalVersion().CustomRes
60. }
61.
62. // 5、将 informer 以及 controller 添加到 PostStartHook 中
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers",
63. func(context genericapiserver.PostStartHookContext) error {
64. s.Informers.Start(context.StopCh)
65. return nil
66. })
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-controllers",
67. func(context genericapiserver.PostStartHookContext) error {
68. ......
69. go crdController.Run(context.StopCh)
70. go namingController.Run(context.StopCh)
71. go establishingController.Run(context.StopCh)
72. go nonStructuralSchemaController.Run(5, context.StopCh)
73. go apiApprovalController.Run(5, context.StopCh)
74. go finalizingController.Run(5, context.StopCh)
75. return nil
76. })
77.
s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced",
78. func(context genericapiserver.PostStartHookContext) error {
return wait.PollImmediateUntil(100*time.Millisecond, func() (bool,
79. error) {
return
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions().Informer
80. nil
81. }, context.StopCh)
82. })
83.
84. return s, nil
85. }
CreateKubeAPIServer
kubeAPIServerConfig.Complete().New
主要逻辑为:
k8s.io/kubernetes/cmd/kube-apiserver/app/server.go:214
k8s.io/kubernetes/pkg/master/master.go:325
m.InstallLegacyAPI
关于 legacyRESTStorageProvider.NewLegacyRESTStorage 以及
m.GenericAPIServer.InstallLegacyAPIGroup 方法的详细说明在后文中会继续进行讲解。
k8s.io/kubernetes/pkg/master/master.go:406
bootstrapController := c.NewBootstrapController(legacyRESTStorage,
9. coreClient, coreClient, coreClient, coreClient.RESTClient())
m.GenericAPIServer.AddPostStartHookOrDie(controllerName,
10. bootstrapController.PostStartHook)
m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName,
11. bootstrapController.PreShutdownHook)
12.
if err :=
m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix,
13. &apiGroupInfo); err != nil {
14. return fmt.Errorf("Error in registering group versions: %v", err)
15. }
16. return nil
17. }
createAggregatorServer
主要逻辑为:
k8s.io/kubernetes/cmd/kube-apiserver/app/aggregator.go:124
34. aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(),
35. ),
36. )
37. if err != nil {
38. return nil, err
39. }
40.
41. return aggregatorServer, nil
42. }
aggregatorConfig.Complete().NewWithDelegate
k8s.io/kubernetes/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go:158
至此,CreateServerChain 中流程已经分析完,其中的调用链如下所示:
1. |--> CreateNodeDialer
2. |
3. |--> CreateKubeAPIServerConfig
4. |
5. CreateServerChain --|--> createAPIExtensionsConfig
6. |
|
7. |--> c.GenericConfig.New
|--> createAPIExtensionsServer -->
8. apiextensionsConfig.Complete().New --|
|
9. |--> s.GenericAPIServer.InstallAPIGroup
10. |
|
11. |--> c.GenericConfig.New --> legacyRESTStorageProvider.NewLegacyRESTStorage
|
12. |
|--> CreateKubeAPIServer -->
13. kubeAPIServerConfig.Complete().New --|--> m.InstallLegacyAPI
|
14. |
|
15. |--> m.InstallAPIs
16. |
17. |
18. |--> createAggregatorConfig
19. |
|
20. |--> c.GenericConfig.New
|
21. |
|--> createAggregatorServer -->
aggregatorConfig.Complete().NewWithDelegate --|-->
22. apiservicerest.NewRESTStorage
23. |
prepared.Run
k8s.io/kubernetes/staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go:269
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go:316
24. <-delayedStopCh
25. s.HandlerChainWaitGroup.Wait()
26. return nil
27. }
s.NonBlockingRun
s.NonBlockingRun 的主要逻辑为:
1、判断是否要启动审计日志服务;
2、调用 s.SecureServingInfo.Serve 配置并启动 https server;
3、执行 postStartHooks;
4、向 systemd 发送 ready 信号;
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go:351
26. close(s.readinessStopCh)
27. close(internalStopCh)
28. if stoppedCh != nil {
29. <-stoppedCh
30. }
31. s.HandlerChainWaitGroup.Wait()
32. close(auditStopCh)
33. }()
34.
35. // 3、执行 postStartHooks
36. s.RunPostStartHooks(stopCh)
37.
38. // 4、向 systemd 发送 ready 信号
39. if _, err := systemd.SdNotify(true, "READY=1\n"); err != nil {
klog.Errorf("Unable to send systemd daemon successful start message:
40. %v\n", err)
41. }
42.
43. return nil
44. }
storageFactory 的构建
上文已经提到过,apiserver 最终实现的 handler 对应的后端数据是以 Store 的结构保存的,
这里以 /api 开头的路由举例,通过 NewLegacyRESTStorage 方法创建各个资源的
RESTStorage。RESTStorage 是一个结构体,具体的定义
在 k8s.io/apiserver/pkg/registry/generic/registry/store.go 下,结构体内主要包
含 NewFunc 返回特定资源信息、 NewListFunc 返回特定资源列表、 CreateStrategy 特定资源
创建时的策略、 UpdateStrategy 更新时的策略以及 DeleteStrategy 删除时的策略等重要方法。
在 NewLegacyRESTStorage 内部,可以看到创建了多种资源的 RESTStorage。
NewLegacyRESTStorage
k8s.io/kubernetes/pkg/registry/core/rest/storage_core.go:102
serviceNodePortAllocator, err :=
portallocator.NewPortAllocatorCustom(c.ServiceNodePortRange, func(max int,
67. rangeSpec string) (allocator.Interface, error) {
68. ......
69. })
70. if err != nil {
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{},
71. fmt.Errorf("cannot create cluster port allocator: %v", err)
72. }
73. restStorage.ServiceNodePortAllocator = serviceNodePortRegistry
74.
75. controllerStorage, err := controllerstore.NewStorage(restOptionsGetter)
76. if err != nil {
77. return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
78. }
79.
80. serviceRest, serviceRestProxy := servicestore.NewREST(......)
81.
82. // 3、restStorageMap 保存 resource http path 与 RESTStorage 对应关系
83. restStorageMap := map[string]rest.Storage{
84. "pods": podStorage.Pod,
85. "pods/attach": podStorage.Attach,
86. "pods/status": podStorage.Status,
87. "pods/log": podStorage.Log,
88. "pods/exec": podStorage.Exec,
89. "pods/portforward": podStorage.PortForward,
90. "pods/proxy": podStorage.Proxy,
91. ......
"componentStatuses":
92. componentstatus.NewStorage(componentStatusStorage{c.StorageFactory}.serversToValidate
93. }
94. ......
95. }
podstore.NewStorage
k8s.io/kubernetes/pkg/registry/core/pod/storage/storage.go:71
2. store := &genericregistry.Store{
3. NewFunc: func() runtime.Object { return &api.Pod{} },
NewListFunc: func() runtime.Object { return &api.PodList{}
4. },
5. ......
6. }
7. options := &generic.StoreOptions{
8. RESTOptions: optsGetter,
9. AttrFunc: pod.GetAttrs,
TriggerFunc: map[string]storage.IndexerFunc{"spec.nodeName":
10. pod.NodeNameTriggerFunc},
11. }
12.
13. // 调用 store.CompleteWithOptions
14. if err := store.CompleteWithOptions(options); err != nil {
15. return PodStorage{}, err
16. }
17. statusStore := *store
18. statusStore.UpdateStrategy = pod.StatusStrategy
19. ephemeralContainersStore := *store
20. ephemeralContainersStore.UpdateStrategy = pod.EphemeralContainersStrategy
21.
22. bindingREST := &BindingREST{store: store}
23.
24. // PodStorage 对象
25. return PodStorage{
26. Pod: &REST{store, proxyTransport},
27. Binding: &BindingREST{store: store},
28. LegacyBinding: &LegacyBindingREST{bindingREST},
Eviction: newEvictionStorage(store,
29. podDisruptionBudgetClient),
30. Status: &StatusREST{store: &statusStore},
EphemeralContainers: &EphemeralContainersREST{store:
31. &ephemeralContainersStore},
32. Log: &podrest.LogREST{Store: store, KubeletConn: k},
Proxy: &podrest.ProxyREST{Store: store, ProxyTransport:
33. proxyTransport},
34. Exec: &podrest.ExecREST{Store: store, KubeletConn: k},
35. Attach: &podrest.AttachREST{Store: store, KubeletConn: k},
PortForward: &podrest.PortForwardREST{Store: store,
36. KubeletConn: k},
37. }, nil
38. }
store.CompleteWithOptions
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.g
o:1192
57. e.NewListFunc,
58. attrFunc,
59. options.TriggerFunc,
60. )
61. if err != nil {
62. return err
63. }
64. e.StorageVersioner = opts.StorageConfig.EncodeVersioner
65.
66. if opts.CountMetricPollPeriod > 0 {
67. stopFunc := e.startObservingCount(opts.CountMetricPollPeriod)
68. previousDestroy := e.DestroyFunc
69. e.DestroyFunc = func() {
70. stopFunc()
71. if previousDestroy != nil {
72. previousDestroy()
73. }
74. }
75. }
76. }
77.
78. return nil
79. }
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/options/etcd.go:253
7. ret := generic.RESTOptions{
8. StorageConfig: storageConfig,
9. Decorator: generic.UndecoratedStorage,
10. DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
11. EnableGarbageCollection: f.Options.EnableGarbageCollection,
12. ResourcePrefix: f.StorageFactory.ResourcePrefix(resource),
13. CountMetricPollPeriod: f.Options.StorageConfig.CountMetricPollPeriod,
14. }
15. if f.Options.EnableWatchCache {
16. sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
17. if err != nil {
18. return generic.RESTOptions{}, err
19. }
20. cacheSize, ok := sizes[resource]
21. if !ok {
22. cacheSize = f.Options.DefaultWatchCacheSize
23. }
24. // 调用 generic.StorageDecorator
25. ret.Decorator = genericregistry.StorageWithCacher(cacheSize)
26. }
27.
28. return ret, nil
29. }
在 genericregistry.StorageWithCacher 中又调用了不同的方法最终会调用
factory.Create 来初始化存储实例,其调用链为: genericregistry.StorageWithCacher -->
generic.NewRawStorage --> factory.Create 。
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/fa
ctory.go:30
newETCD3Storage
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory/et
cd3.go:209
路由注册
k8s.io/kubernetes/pkg/master/master.go:406
a.registerResourceHandlers
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go:181
3.
4. ......
5.
// 1、判断该 resource 实现了哪些 REST 操作接口,以此来判断其支持的 verbs 以便为其添
6. 加路由
7. creater, isCreater := storage.(rest.Creater)
8. namedCreater, isNamedCreater := storage.(rest.NamedCreater)
9. lister, isLister := storage.(rest.Lister)
10. getter, isGetter := storage.(rest.Getter)
11. getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
12. gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
13. collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
14. updater, isUpdater := storage.(rest.Updater)
15. patcher, isPatcher := storage.(rest.Patcher)
16. watcher, isWatcher := storage.(rest.Watcher)
17. connecter, isConnecter := storage.(rest.Connecter)
18. storageMeta, isMetadata := storage.(rest.StorageMetadata)
storageVersionProvider, isStorageVersionProvider := storage.
19. (rest.StorageVersionProvider)
20. if !isMetadata {
21. storageMeta = defaultStorageMetadata{}
22. }
23. exporter, isExporter := storage.(rest.Exporter)
24. if !isExporter {
25. exporter = nil
26. }
27.
28. ......
29.
30. // 2、为 resource 添加对应的 actions 并根据是否支持 namespace
31. switch {
32. case !namespaceScoped:
33. ......
34.
actions = appendIf(actions, action{"LIST", resourcePath,
35. resourceParams, namer, false}, isLister)
actions = appendIf(actions, action{"POST", resourcePath,
36. resourceParams, namer, false}, isCreater)
actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath,
37. resourceParams, namer, false}, isCollectionDeleter)
actions = appendIf(actions, action{"WATCHLIST", "watch/" +
38. resourcePath, resourceParams, namer, false}, allowWatchList)
39.
100. Operation("create"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb),
101. mediaTypes...)...).
102. Returns(http.StatusOK, "OK", producedObject).
103. Returns(http.StatusCreated, "Created", producedObject).
104. Returns(http.StatusAccepted, "Accepted", producedObject).
105. Reads(defaultVersionedObject).
106. Writes(producedObject)
if err := AddObjectParams(ws, route, versionedCreateOptions); err
107. != nil {
restfulCreateNamedResource
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go:1087
createHandler
createHandler 是将数据写入到后端存储的方法,对于资源的操作都有相关的权限控制,在
createHandler 中首先会执行 decoder 和 admission 操作,然后调用 create 方
法完成 resource 的创建,在 create 方法中会进行 validate 以及最终将数据保存到后端
存储中。 admit 操作即执行 kube-apiserver 中的 admission-plugins,admission-
plugins 在 CreateKubeAPIServerConfig 中被初始化为了 admissionChain,其初始化的调
createHandler 中所有的操作就是本文开头提到的请求流程,如下所示:
k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go:46
65. }
总结
本文主要分析 kube-apiserver 的启动流程,kube-apiserver 中包含三个 server,分别为
KubeAPIServer、APIExtensionsServer 以及 AggregatorServer,三个 server 是通过委
托模式连接在一起的,初始化过程都是类似的,首先为每个 server 创建对应的 config,然后初始
化 http server,http server 的初始化过程为首先初始化 GoRestfulContainer ,然后安装
server 所包含的 API,安装 API 时首先为每个 API Resource 创建对应的后端存储
RESTStorage,再为每个 API Resource 支持的 verbs 添加对应的 handler,并将 handler
注册到 route 中,最后将 route 注册到 webservice 中,启动流程中 RESTFul API 的实现流
程是其核心,至于 kube-apiserver 中认证鉴权等 filter 的实现、多版本资源转换、
kubernetes service 的实现等一些细节会在后面的文章中继续进行分析。
参考:
https://mp.weixin.qq.com/s/hTEWatYLhTnC5X0FBM2RWQ
https://bbbmj.github.io/2019/04/13/Kubernetes/code-analytics/kube-
apiserver/
https://mp.weixin.qq.com/s/TQuqAAzBjeWHwKPJZ3iJhA
https://blog.openshift.com/kubernetes-deep-dive-api-server-part-1/
https://www.jianshu.com/p/daa4ff387a78
创建 kubernetes service;
创建 default、kube-system 和 kube-public 命名空间,如果启用了 NodeLease 特性
还会创建 kube-node-lease 命名空间;
提供基于 Service ClusterIP 的修复及检查功能;
提供基于 Service NodePort 的修复及检查功能;
kubernetes 版本:v1.16
k8s.io/kubernetes/pkg/master/master.go:406
3. if err != nil {
4. return fmt.Errorf("Error building core storage: %v", err)
5. }
6.
7. // 初始化 bootstrap-controller
8. controllerName := "bootstrap-controller"
coreClient :=
9. corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
10. bootstrapController := c.NewBootstrapController(......)
m.GenericAPIServer.AddPostStartHookOrDie(controllerName,
11. bootstrapController.PostStartHook)
m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName,
12. bootstrapController.PreShutdownHook)
13.
if err :=
m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix,
14. &apiGroupInfo); err != nil {
15. return fmt.Errorf("Error in registering group versions: %v", err)
16. }
17. return nil
18. }
NewBootstrapController
k8s.io/kubernetes/pkg/master/controller.go:89
k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/net/interface.go:323
bootstrapController.Start
k8s.io/kubernetes/pkg/master/controller.go:146
c.RunKubernetesNamespaces
k8s.io/kubernetes/pkg/master/controller.go:199
c.RunKubernetesService
k8s.io/kubernetes/pkg/master/controller.go:210
c.UpdateKubernetesService
c.UpdateKubernetesService 的主要逻辑为:
k8s.io/kubernetes/pkg/master/controller.go:230
13. }
14. return nil
15. }
c.EndpointReconciler.ReconcileEndpoints
k8s.io/kubernetes/pkg/master/reconcilers/lease.go:144
24.
25. shouldCreate = true
26. e = &corev1.Endpoints{
27. ObjectMeta: metav1.ObjectMeta{
28. Name: serviceName,
29. Namespace: corev1.NamespaceDefault,
30. },
31. }
32. }
33.
34. // 2、从 etcd 中获取所有的 master
35. masterIPs, err := r.masterLeases.ListLeases()
36. if err != nil {
37. return err
38. }
39.
40. if len(masterIPs) == 0 {
return fmt.Errorf("no master IPs were listed in storage, refusing to
41. erase all endpoints for the kubernetes service")
42. }
43.
44. // 3、检查 endpoint 中 master 信息,如果与 etcd 中的不一致则进行更新
formatCorrect, ipCorrect, portsCorrect :=
45. checkEndpointSubsetFormatWithLease(e, masterIPs, endpointPorts, reconcilePorts)
46. if formatCorrect && ipCorrect && portsCorrect {
47. return nil
48. }
49.
50. if !formatCorrect {
51. e.Subsets = []corev1.EndpointSubset{{
52. Addresses: []corev1.EndpointAddress{},
53. Ports: endpointPorts,
54. }}
55. }
56. if !formatCorrect || !ipCorrect {
57. e.Subsets[0].Addresses = make([]corev1.EndpointAddress, len(masterIPs))
58. for ind, ip := range masterIPs {
59. e.Subsets[0].Addresses[ind] = corev1.EndpointAddress{IP: ip}
60. }
61.
62. e.Subsets = endpointsv1.RepackSubsets(e.Subsets)
63. }
64.
65. if !portsCorrect {
66. e.Subsets[0].Ports = endpointPorts
67. }
68.
69. if shouldCreate {
if _, err = r.epAdapter.Create(corev1.NamespaceDefault, e);
70. errors.IsAlreadyExists(err) {
71. err = nil
72. }
73. } else {
74. _, err = r.epAdapter.Update(corev1.NamespaceDefault, e)
75. }
76. return err
77. }
repairClusterIPs.RunUntil
repairClusterIP 主要解决的问题有:
k8s.io/kubernetes/pkg/registry/core/service/ipallocator/controller/repair.go:134
54. if !helper.IsServiceIPSet(&svc) {
55. continue
56. }
57. ip := net.ParseIP(svc.Spec.ClusterIP)
58. ......
59.
60. actualAlloc := c.selectAllocForIP(ip, rebuilt, secondaryRebuilt)
61. switch err := actualAlloc.Allocate(ip); err {
62. // 6、检查 ip 是否泄漏
63. case nil:
64. actualStored := c.selectAllocForIP(ip, stored, secondaryStored)
65. if actualStored.Has(ip) {
66. actualStored.Release(ip)
67. } else {
68. ......
69. }
70. delete(c.leaks, ip.String())
71. // 7、ip 重复分配
72. case ipallocator.ErrAllocated:
73. ......
74. // 8、ip 超出范围
75. case err.(*ipallocator.ErrNotInRange):
76. ......
77. // 9、ip 已经分配完
78. case ipallocator.ErrFull:
79. ......
80. default:
81. ......
82. }
83. }
84. // 10、对比是否有泄漏 ip
85. c.checkLeaked(stored, rebuilt)
86. if c.shouldWorkOnSecondary() {
87. c.checkLeaked(secondaryStored, secondaryRebuilt)
88. }
89.
90. // 11、更新快照
91. err = c.saveSnapShot(rebuilt, c.alloc, snapshot)
92. if err != nil {
93. return err
94. }
95.
96. if c.shouldWorkOnSecondary() {
err := c.saveSnapShot(secondaryRebuilt, c.secondaryAlloc,
97. secondarySnapshot)
98. if err != nil {
99. return nil
100. }
101. }
102. return nil
103. }
repairNodePorts.RunUnti
k8s.io/kubernetes/pkg/registry/core/service/portallocator/controller/repair.go:84
24. }
25.
26. rebuilt, err := portallocator.NewPortAllocator(c.portRange)
27. if err != nil {
28. return fmt.Errorf("unable to create port allocator: %v", err)
29. }
30.
31. // 5、检查每个 Service ClusterIP 的 port,保证其处于正常状态
32. for i := range list.Items {
33. svc := &list.Items[i]
34. ports := collectServiceNodePorts(svc)
35. if len(ports) == 0 {
36. continue
37. }
38. for _, port := range ports {
39. switch err := rebuilt.Allocate(port); err {
40. // 6、检查 port 是否泄漏
41. case nil:
42. if stored.Has(port) {
43. stored.Release(port)
44. } else {
45. ......
46. }
47. delete(c.leaks, port)
48. // 7、port 重复分配
49. case portallocator.ErrAllocated:
50. ......
51. // 8、port 超出分配范围
52. case err.(*portallocator.ErrNotInRange):
53. ......
54. // 9、port 已经分配完
55. case portallocator.ErrFull:
56. ......
57. default:
58. ......
59. }
60. }
61. }
62. // 10、检查 port 是否泄漏
63. stored.ForEach(func(port int) {
64. count, found := c.leaks[port]
65. switch {
总结
本文主要分析了 kube-apiserver 中 apiserver service 的实现,apiserver service 是
通过 bootstrap controller 控制的,bootstrap controller 会保证 apiserver
service 以及其 endpoint 处于正常状态,需要注意的是,apiserver service 的 endpoint
根据启动时指定的参数分为三种控制方式,本文仅分析了 lease 的实现方式,如果使用 master-
count 方式,需要将每个 master 实例的 port、apiserver-count 等配置参数改为一致。
NodeLifecycleController 的功能
NodeLifecycleController 主要功能是定期监控 node 的状态并根据 node 的 condition 添
加对应的 taint 标签或者直接驱逐 node 上的 pod。
taint 的作用
taint 使用效果(Effect):
NodeLifecycleController 中的 feature-gates
NodeLifecycleController 源码分析
kubernetes 版本:v1.16
startNodeLifecycleController
其中在启动时指定的几个参数默认值分别为:
k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go:163
9. ctx.ComponentConfig.NodeLifecycleController.NodeStartupGracePeriod.Duration,
10. ctx.ComponentConfig.NodeLifecycleController.NodeMonitorGracePeriod.Duration,
11. ctx.ComponentConfig.NodeLifecycleController.PodEvictionTimeout.Duration,
12. ctx.ComponentConfig.NodeLifecycleController.NodeEvictionRate,
13. ctx.ComponentConfig.NodeLifecycleController.SecondaryNodeEvictionRate,
14. ctx.ComponentConfig.NodeLifecycleController.LargeClusterSizeThreshold,
15. ctx.ComponentConfig.NodeLifecycleController.UnhealthyZoneThreshold,
16. ctx.ComponentConfig.NodeLifecycleController.EnableTaintManager,
17. utilfeature.DefaultFeatureGate.Enabled(features.TaintBasedEvictions),
18. utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition),
19. )
20. if err != nil {
21. return nil, true, err
22. }
23. go lifecycleController.Run(ctx.Stop)
24. return nil, true, nil
25. }
NewNodeLifecycleController
42.
43. // kube-controller-manager 启动时指定的几个参数
44. nodeMonitorPeriod time.Duration
45. nodeStartupGracePeriod time.Duration
46. nodeMonitorGracePeriod time.Duration
47. podEvictionTimeout time.Duration
48. evictionLimiterQPS float32
49. secondaryEvictionLimiterQPS float32
50. largeClusterThreshold int32
51. unhealthyZoneThreshold float32
52.
53. // 启动时默认开启的几个 feature-gates
54. runTaintManager bool
55. useTaintBasedEvictions bool
56. taintNodeByCondition bool
57.
58. nodeUpdateQueue workqueue.Interface
59. }
NewNodeLifecycleController 的主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:268
10.
11. // 2、注册计算 node 驱逐速率以及 zone 状态的方法
12. nc.enterPartialDisruptionFunc = nc.ReducedQPSFunc
13. nc.enterFullDisruptionFunc = nc.HealthyQPSFunc
14. nc.computeZoneStateFunc = nc.ComputeZoneState
15.
// 3、为 podInformer 注册 EventHandler,监听到的对象会被放到
16. nc.taintManager.PodUpdated 中
17. podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
18. AddFunc: func(obj interface{}) {
19. pod := obj.(*v1.Pod)
20. if nc.taintManager != nil {
21. nc.taintManager.PodUpdated(nil, pod)
22. }
23. },
24. UpdateFunc: func(prev, obj interface{}) {
25. prevPod := prev.(*v1.Pod)
26. newPod := obj.(*v1.Pod)
27. if nc.taintManager != nil {
28. nc.taintManager.PodUpdated(prevPod, newPod)
29. }
30. },
31. DeleteFunc: func(obj interface{}) {
32. pod, isPod := obj.(*v1.Pod)
33. if !isPod {
34. deletedState, ok := obj.(cache.DeletedFinalStateUnknown)
35. if !ok {
36. return
37. }
38. pod, ok = deletedState.Obj.(*v1.Pod)
39. if !ok {
40. return
41. }
42. }
43. if nc.taintManager != nil {
44. nc.taintManager.PodUpdated(pod, nil)
45. }
46. },
47. })
48. nc.podInformerSynced = podInformer.Informer().HasSynced
49. podInformer.Informer().AddIndexers(cache.Indexers{
50. nodeNameKeyIndex: func(obj interface{}) ([]string, error) {
89. nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
Run
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:455
19. }
20.
21. // 3、根据是否启用 TaintBasedEvictions 执行不同的处理逻辑
22. if nc.useTaintBasedEvictions {
go wait.Until(nc.doNoExecuteTaintingPass, scheduler.NodeEvictionPeriod,
23. stopCh)
24. } else {
25. go wait.Until(nc.doEvictionPass, scheduler.NodeEvictionPeriod, stopCh)
26. }
27.
28. // 4、执行 nc.monitorNodeHealth
29. go wait.Until(func() {
30. if err := nc.monitorNodeHealth(); err != nil {
31. klog.Errorf("Error monitoring node health: %v", err)
32. }
33. }, nc.nodeMonitorPeriod, stopCh)
34.
35. <-stopCh
36. }
下文会详细分析以上 5 种方法的具体实现。
nc.taintManager.Run
主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go:185
tc.worker
k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go:243
16. select {
17. case nodeUpdate := <-tc.nodeUpdateChannels[worker]:
18. tc.handleNodeUpdate(nodeUpdate)
19. tc.nodeUpdateQueue.Done(nodeUpdate)
20. default:
21. break priority
22. }
23. }
24. tc.handlePodUpdate(podUpdate)
25. tc.podUpdateQueue.Done(podUpdate)
26. }
27. }
28. }
tc.handleNodeUpdate
tc.handleNodeUpdate 的主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go:417
tc.handlePodUpdate
主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go:377
5. }
6.
7. if pod.Spec.NodeName != podUpdate.nodeName {
8. return
9. }
10.
podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name:
11. pod.Name}
12.
13. nodeName := pod.Spec.NodeName
14. if nodeName == "" {
15. return
16. }
17. taints, ok := func() ([]v1.Taint, bool) {
18. tc.taintedNodesLock.Lock()
19. defer tc.taintedNodesLock.Unlock()
20. taints, ok := tc.taintedNodes[nodeName]
21. return taints, ok
22. }()
23.
24. if !ok {
25. return
26. }
27. // 调用 tc.processPodOnNode 进行处理
tc.processPodOnNode(podNamespacedName, nodeName, pod.Spec.Tolerations,
28. taints, time.Now())
29. }
tc.processPodOnNode
1. tolerations:
2. - key: "key1"
3. operator: "Equal"
4. value: "value1"
5. effect: "NoExecute"
6. tolerationSeconds: 3600
k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler/taint_manager.go:339
12.
13. // 2、获取最小容忍时间
14. minTolerationTime := getMinTolerationTime(usedTolerations)
15. if minTolerationTime < 0 {
16. return
17. }
18.
19. // 3、若存在最小容忍时间则将其加入到延时队列中
20. startTime := now
21. triggerTime := startTime.Add(minTolerationTime)
scheduledEviction :=
22. tc.taintEvictionQueue.GetWorkerUnsafe(podNamespacedName.String())
23. if scheduledEviction != nil {
24. startTime = scheduledEviction.CreatedAt
25. if startTime.Add(minTolerationTime).Before(triggerTime) {
26. return
27. }
28. tc.cancelWorkWithEvent(podNamespacedName)
29. }
tc.taintEvictionQueue.AddWork(NewWorkArgs(podNamespacedName.Name,
30. podNamespacedName.Namespace), startTime, triggerTime)
31. }
nc.doNodeProcessingPassWorker
删除不需要的 taints;
1. labels:
2. beta.kubernetes.io/arch: amd64
3. beta.kubernetes.io/os: linux
4. kubernetes.io/arch: amd64
5. kubernetes.io/os: linux
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:502
nc.doNoExecuteTaintingPass
nc.doNoExecuteTaintingPass 的主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:582
12. }
13.
14. // 2、获取 node 的 NodeReadyCondition
_, condition := nodeutil.GetNodeCondition(&node.Status,
15. v1.NodeReady)
16. taintToAdd := v1.Taint{}
17. oppositeTaint := v1.Taint{}
18.
19. // 3、判断 Condition 状态,并为其添加对应的 taint
20. switch condition.Status {
21. case v1.ConditionFalse:
22. taintToAdd = *NotReadyTaintTemplate
23. oppositeTaint = *UnreachableTaintTemplate
24. case v1.ConditionUnknown:
25. taintToAdd = *UnreachableTaintTemplate
26. oppositeTaint = *NotReadyTaintTemplate
27. default:
28. return true, 0
29. }
30.
31. // 4、更新 node 的 taint
result := nodeutil.SwapNodeControllerTaint(nc.kubeClient,
32. []*v1.Taint{&taintToAdd}, []*v1.Taint{&oppositeTaint}, node)
33. if result {
34. zone := utilnode.GetZoneKey(node)
35. evictionsNumber.WithLabelValues(zone).Inc()
36. }
37.
38. return result, 0
39. })
40. }
41. }
nc.doEvictionPass
nc.doEvictionPass 的主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:626
nc.monitorNodeHealth
nc.monitorNodeHealth 主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:664
67.
68. case v1.ConditionFalse:
69. // 9、false 状态添加 NotReady taint
70. if nc.useTaintBasedEvictions {
if taintutils.TaintExists(node.Spec.Taints,
71. UnreachableTaintTemplate) {
72. taintToAdd := *NotReadyTaintTemplate
if !nodeutil.SwapNodeControllerTaint(nc.kubeClient,
73. []*v1.Taint{&taintToAdd}, []*v1.Taint{UnreachableTaintTemplate}, node) {
74. ......
75. }
76. } else if nc.markNodeForTainting(node) {
77. ......
78. }
79. // 10、或者当超过 podEvictionTimeout 后直接驱逐 node 上的 pod
80. } else {
if
decisionTimestamp.After(nc.nodeHealthMap[node.Name].readyTransitionTimestamp.Add(
81. {
82. if nc.evictPods(node) {
83. ......
84. }
85. }
86. }
87. case v1.ConditionUnknown:
88. // 11、unknown 状态时添加 UnreachableTaint
89. if nc.useTaintBasedEvictions {
if taintutils.TaintExists(node.Spec.Taints,
90. NotReadyTaintTemplate) {
91. taintToAdd := *UnreachableTaintTemplate
if !nodeutil.SwapNodeControllerTaint(nc.kubeClient,
92. []*v1.Taint{&taintToAdd}, []*v1.Taint{NotReadyTaintTemplate}, node) {
93. ......
94. }
95. } else if nc.markNodeForTainting(node) {
96. ......
97. }
98. } else {
if
decisionTimestamp.After(nc.nodeHealthMap[node.Name].probeTimestamp.Add(nc.podEvictionTi
99. {
100. if nc.evictPods(node) {
101. ......
102. }
103. }
104. }
105. case v1.ConditionTrue:
106. // 12、true 状态时移除所有 UnreachableTaint 和 NotReadyTaint
107. if nc.useTaintBasedEvictions {
108. removed, err := nc.markNodeAsReachable(node)
109. if err != nil {
110. ......
111. }
112. // 13、从 PodEviction 队列中移除
113. } else {
114. if nc.cancelPodEviction(node) {
115. ......
116. }
117. }
118. }
119.
// 14、ReadyCondition 由 true 变为 false 时标记 node 上的 pod 为
120. notready
if currentReadyCondition.Status != v1.ConditionTrue &&
121. observedReadyCondition.Status == v1.ConditionTrue {
nodeutil.RecordNodeStatusChange(nc.recorder, node,
122. "NodeNotReady")
if err = nodeutil.MarkAllPodsNotReady(nc.kubeClient, node); err
123. != nil {
utilruntime.HandleError(fmt.Errorf("Unable to mark all pods
124. NotReady on node %v: %v", node.Name, err))
125. }
126. }
127. }
128. }
129. // 15、处理中断情况
130. nc.handleDisruption(zoneToNodeConditions, nodes)
131.
132. return nil
133. }
nc.tryUpdateNodeHealth
nc.tryUpdateNodeHealth 的主要逻辑为:
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:851
nc.handleDisruption
nc.handleDisruption 主要逻辑为:
的驱逐速率;
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:1017
79. return
80. }
81.
82. // 7、根据 zoneState 为每个 zone 设置驱逐速率
83. for k, v := range nc.zoneStates {
84. newState := newZoneStates[k]
85. if v == newState {
86. continue
87. }
88.
89. nc.setLimiterInZone(k, len(zoneToNodeConditions[k]), newState)
90. nc.zoneStates[k] = newState
91. }
92. }
93. }
nc.computeZoneStateFunc
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:1262
nc.setLimiterInZone
k8s.io/kubernetes/pkg/controller/nodelifecycle/node_lifecycle_controller.go:1115
19. nc.zoneNoExecuteTainter[zone].SwapLimiter(
20. nc.enterFullDisruptionFunc(zoneSize))
21. } else {
22. nc.zonePodEvictor[zone].SwapLimiter(
23. nc.enterFullDisruptionFunc(zoneSize))
24. }
25. }
26. }
小结
monitorNodeHealth 中的主要流程如下所示:
1. monitorNodeHealth
2. |
3. |
4. useTaintBasedEvictions
5. |
6. |
7. ---------------------------------------------
8. yes | | no
9. | |
10. v v
11. addPodEvictorForNewZone evictPods
12. | |
13. | |
14. v v
15. zoneNoExecuteTainter zonePodEvictor
16. (RateLimitedTimedQueue) (RateLimitedTimedQueue)
17. | |
18. | |
19. | |
20. v v
21. doNoExecuteTaintingPass doEvictionPass
22. (consumer) (consumer)
NodeLifecycleController 中三个核心组件之间的交互流程如下所示:
1. monitorNodeHealth
2. |
3. |
4. | 为 node 添加 NoExecute taint
5. |
6. |
7. v 为 node 添加
8. watch nodeList NoSchedule taint
taintManager ------> APIServer <-----------
9. nc.doNodeProcessingPassWorker
10. |
11. |
12. |
13. v
14. 驱逐 node 上不容忍
15. node taint 的 pod
至此,NodeLifecycleController 的核心代码已经分析完。
总结
本文主要分析了 NodeLifecycleController 的设计与实现,NodeLifecycleController 主要
是监控 node 状态,当 node 异常时驱逐 node 上的 pod,其行为与其他组件有一定关系,node
的状态由 kubelet 上报,node 异常时为 node 添加 taint 标签后,scheduler 调度 pod 也
会有对应的行为。为了保证由于网络等问题引起的 pod 驱逐行为,NodeLifecycleController 会
为 node 进行分区并会为每个区设置不同的驱逐速率,即实际上会以 rate-limited 的方式添加
taint,在某些情况下可以避免 pod 被大量驱逐。
参考:
https://kubernetes.io/zh/docs/concepts/configuration/taint-and-toleration/
https://kubernetes.io/docs/reference/command-line-tools-reference/feature-
gates/
job 的基本功能
创建
job 的一个示例如下所示:
1. apiVersion: batch/v1
2. kind: Job
3. metadata:
4. name: pi
5. spec:
6. backoffLimit: 6 // 标记为 failed 前的重试次数,默认为 6
completions: 4 // 要完成job 的 pod 数,若没有设定该值则默认等于
7. parallelism 的值
8. parallelism: 2 // 任意时间最多可以启动多少个 pod 同时运行,默认为 1
9. activeDeadlineSeconds: 120 // job 运行时间
10. ttlSecondsAfterFinished: 60 // job 在运行完成后 60 秒就会自动删除掉
11. template:
12. spec:
13. containers:
14. - command:
15. - sh
16. - -c
17. - 'echo ''scale=5000; 4*a(1)'' | bc -l '
18. image: resouer/ubuntu-bc
19. name: pi
20. restartPolicy: Never
扩缩容
删除
自动清理机制
kubernetes 版本:v1.16
startJobController
k8s.io/kubernetes/cmd/kube-controller-manager/app/batch.go:33
Run
k8s.io/kubernetes/pkg/controller/job/job_controller.go:139
syncJob
1. status:
2. completionTime: "2019-12-18T14:16:47Z"
3. conditions:
4. - lastProbeTime: "2019-12-18T14:16:47Z"
5. lastTransitionTime: "2019-12-18T14:16:47Z"
6. status: "True" // status 为 true
7. type: Complete // Complete
8. startTime: "2019-12-18T14:15:35Z"
9. succeeded: 2
k8s.io/kubernetes/pkg/controller/job/job_controller.go:436
156. }
jm.manageJob
k8s.io/kubernetes/pkg/controller/job/job_controller.go:684
121. }
122.
123. return active, nil
124. }
总结
以上就是 jobController 源码中主要的逻辑,从上文分析可以看到 jobController 的代码比较
清晰,若看过前面写的几个 controller 分析会发现每个 controller 在功能实现上有很多类似的
地方。
kubernetes 中的删除策略
kubernetes 中有三种删除策略: Orphan 、 Foreground 和 Background ,三种删除策略
的意义分别为:
Orphan 策略:非级联删除,删除对象时,不会自动删除它的依赖或者是子对象,这些依赖被
称作是原对象的孤儿对象,例如当执行以下命令时会使用 Orphan 策略进行删除,此时 ds
的依赖对象 controllerrevision 不会被删除;
Foreground 策略:在该模式下,对象首先进入“删除中”状态,即会设置对象的
deletionTimestamp 字段并且对象的 metadata.finalizers 字段包含了值
“foregroundDeletion”,此时该对象依然存在,然后垃圾收集器会删除该对象的所有依赖对
象,垃圾收集器在删除了所有“Blocking” 状态的依赖对象(指其子对象中
ownerReference.blockOwnerDeletion=true 的对象)之后,然后才会删除对象本身;
finalizer 机制
1. {
2. ......
3. "metadata": {
4. ......
5. "finalizers": [
6. "foregroundDeletion"
7. ]
8. }
9. ......
10. }
GarbageCollectorController 源码分析
kubernetes 版本:v1.16
startGarbageCollectorController
k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go:443
2. if !ctx.ComponentConfig.GarbageCollectorController.EnableGarbageCollector {
3. return nil, false, nil
4. }
5. // 1、初始化 discoveryClient
6. gcClientset := ctx.ClientBuilder.ClientOrDie("generic-garbage-collector")
discoveryClient :=
7. cacheddiscovery.NewMemCacheClient(gcClientset.Discovery())
8.
9. config := ctx.ClientBuilder.ConfigOrDie("generic-garbage-collector")
10. metadataClient, err := metadata.NewForConfig(config)
11. if err != nil {
12. return nil, true, err
13. }
14.
15. // 2、获取 deletableResource
deletableResources :=
16. garbagecollector.GetDeletableResources(discoveryClient)
17. ignoredResources := make(map[schema.GroupResource]struct{})
for _, r := range
18. ctx.ComponentConfig.GarbageCollectorController.GCIgnoredResources {
ignoredResources[schema.GroupResource{Group: r.Group, Resource:
19. r.Resource}] = struct{}{}
20. }
21.
22. // 3、初始化 garbageCollector 对象
23. garbageCollector, err := garbagecollector.NewGarbageCollector(
24. ......
25. )
26. if err != nil {
return nil, true, fmt.Errorf("failed to start the generic garbage
27. collector: %v", err)
28. }
29. // 4、启动 garbage collector
workers :=
30. int(ctx.ComponentConfig.GarbageCollectorController.ConcurrentGCSyncs)
31. go garbageCollector.Run(workers, ctx.Stop)
32.
33. // 5、监听集群中的 DeletableResources
34. go garbageCollector.Sync(gcClientset.Discovery(), 30*time.Second, ctx.Stop)
35.
36. // 6、注册 debug 接口
37. return garbagecollector.NewDebugHandler(garbageCollector), true, nil
38. }
在 startGarbageCollectorController 中主要调用了四种方
法 garbagecollector.NewGarbageCollector 、 garbageCollector.Run 、 garbageCollector
.Sync 和 garbagecollector.NewDebugHandler 来完成核心功能,下面主要针对这四种方法进
行说明。
garbagecollector.NewGarbageCollector
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:74
gb.syncMonitors
k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:179
3. defer gb.monitorLock.Unlock()
4.
5. ......
6. for resource := range resources {
7. if _, ok := gb.ignoredResources[resource.GroupResource()]; ok {
8. continue
9. }
10. ......
11. kind, err := gb.restMapper.KindFor(resource)
12. if err != nil {
errs = append(errs, fmt.Errorf("couldn't look up resource %q: %v",
13. resource, err))
14. continue
15. }
16. // 为 resource 的 controller 注册 eventHandler
17. c, s, err := gb.controllerFor(resource, kind)
18. if err != nil {
errs = append(errs, fmt.Errorf("couldn't start monitor for resource
19. %q: %v", resource, err))
20. continue
21. }
22. current[resource] = &monitor{store: s, controller: c}
23. added++
24. }
25. gb.monitors = current
26.
27. for _, monitor := range toRemove {
28. if monitor.stopCh != nil {
29. close(monitor.stopCh)
30. }
31. }
32. return utilerrors.NewAggregate(errs)
33. }
gb.controllerFor
k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:127
2. handlers := cache.ResourceEventHandlerFuncs{
3. AddFunc: func(obj interface{}) {
4. event := &event{
5. eventType: addEvent,
6. obj: obj,
7. gvk: kind,
8. }
9. // 将对应的 event push 到 graphChanges 队列中
10. gb.graphChanges.Add(event)
11. },
12. UpdateFunc: func(oldObj, newObj interface{}) {
13. event := &event{
14. eventType: updateEvent,
15. obj: newObj,
16. oldObj: oldObj,
17. gvk: kind,
18. }
19. // 将对应的 event push 到 graphChanges 队列中
20. gb.graphChanges.Add(event)
21. },
22. DeleteFunc: func(obj interface{}) {
if deletedFinalStateUnknown, ok := obj.
23. (cache.DeletedFinalStateUnknown); ok {
24. obj = deletedFinalStateUnknown.Obj
25. }
26. event := &event{
27. eventType: deleteEvent,
28. obj: obj,
29. gvk: kind,
30. }
31. // 将对应的 event push 到 graphChanges 队列中
32. gb.graphChanges.Add(event)
33. },
34. }
35. shared, err := gb.sharedInformers.ForResource(resource)
36. if err != nil {
37. return nil, nil, err
38. }
shared.Informer().AddEventHandlerWithResyncPeriod(handlers,
39. ResourceResyncTime)
40. return shared.Informer().GetController(), shared.Informer().GetStore(), nil
41. }
NewGarbageCollector 中的调用逻辑如下所示:
1. |--> ctx.ClientBuilder.
2. | ClientOrDie
3. |
4. |
5. |--> cacheddiscovery.
6. | NewMemCacheClient
|
7. |--> gb.sharedInformers.
|
8. | ForResource
|
9. |
startGarbage ----|--> garbagecollector. --> gb.syncMonitors -->
10. gb.controllerFor --|
CollectorController | NewGarbageCollector
11. |
|
12. |
|
13. |--> shared.Informer().
|
14. AddEventHandlerWithResyncPeriod
15. |--> garbageCollector.Run
16. |
17. |
18. |--> garbageCollector.Sync
19. |
20. |
21. |--> garbagecollector.NewDebugHandler
garbageCollector.Run
startGarbageCollectorController 中的第二个核心方法
garbageCollector.Run , garbageCollector.Run 的主要作用是启动所有的生产者和消费者,
其首先会调用 gc.dependencyGraphBuilder.Run 启动所有的生产者,即 monitors,然后再启
动一个 goroutine 处理 graphChanges 队列中的事件并分别放到 attemptToDelete 和
attemptToOrphan 两个队列中,dependencyGraphBuilder 即上文提到的
GraphBuilder, run 方法会调用 gc.runAttemptToDeleteWorker 和
gc.runAttemptToOrphanWorker 启动多个 goroutine 处理 attemptToDelete 和
attemptToOrphan 两个队列中的事件。
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:124
gc.dependencyGraphBuilder.Run
uidToNode
GraphBuilder 主要有三个功能:
1、监控集群中所有的可删除资源;
2、基于 informers 中的资源在 uidToNode 数据结构中维护着所有对象的依赖关系;
3、处理 graphChanges 中的事件并放到 attemptToDelete 和 attemptToOrphan 两个
队列中;
k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:281
gb.startMonitors
k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:232
gb.runProcessGraphChanges
k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:526
5.
6. func (gb *GraphBuilder) processGraphChanges() bool {
7. // 1、从 graphChanges 取出一个 event
8. item, quit := gb.graphChanges.Get()
9. if quit {
10. return false
11. }
12. defer gb.graphChanges.Done(item)
13. event, ok := item.(*event)
14. if !ok {
15. utilruntime.HandleError(fmt.Errorf("expect a *event, got %v", item))
16. return true
17. }
18. obj := event.obj
19. accessor, err := meta.Accessor(obj)
20. if err != nil {
21. utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err))
22. return true
23. }
24.
25. // 2、若存在 node 对象,从 uidToNode 中取出该 event 的 node 对象
26. existingNode, found := gb.uidToNode.Read(accessor.GetUID())
27. if found {
28. existingNode.markObserved()
29. }
30. switch {
31. // 3、若 event 为 add 或 update 类型以及对应的 node 对象不存在时
case (event.eventType == addEvent || event.eventType == updateEvent) &&
32. !found:
33. // 4、为 node 创建 event 对象
34. newNode := &node{
35. ......
36. }
37. // 5、在 uidToNode 中添加该 node 对象
38. gb.insertNode(newNode)
39.
40. // 6、检查并处理 node 的删除操作
41. gb.processTransitions(event.oldObj, accessor, newNode)
42.
43. // 7、若 event 为 add 或 update 类型以及对应的 node 对象存在时
case (event.eventType == addEvent || event.eventType == updateEvent) &&
44. found:
86. }
87. gb.attemptToDelete.Add(ownerNode)
88. }
89. }
90. return true
91. }
processTransitions
k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go:509
gc.runAttemptToDeleteWorker
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:280
5.
6. func (gc *GarbageCollector) attemptToDeleteWorker() bool {
7. item, quit := gc.attemptToDelete.Get()
8. gc.workerLock.RLock()
9. defer gc.workerLock.RUnlock()
10. if quit {
11. return false
12. }
13. defer gc.attemptToDelete.Done(item)
14. n, ok := item.(*node)
15. if !ok {
16. utilruntime.HandleError(fmt.Errorf("expect *node, got %#v", item))
17. return true
18. }
19. err := gc.attemptToDeleteItem(n)
20. if err != nil {
21. if _, ok := err.(*restMappingError); ok {
22. klog.V(5).Infof("error syncing item %s: %v", n, err)
23. } else {
utilruntime.HandleError(fmt.Errorf("error syncing item %s: %v", n,
24. err))
25. }
26. gc.attemptToDelete.AddRateLimited(item)
27. } else if !n.isObserved() {
28. gc.attemptToDelete.AddRateLimited(item)
29. }
30. return true
31. }
gc.attemptToDeleteItem
gc.attemptToDeleteItem 的主要逻辑为:
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:404
16. }
17.
18. // 3、判断该 node 最新状态的 uid 是否等于本地缓存中的 uid
19. if latest.GetUID() != item.identity.UID {
20. gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
21. item.markObserved()
22. return nil
23. }
24.
25. // 4、判断该 node 当前是否处于删除 dependents 状态中
26. if item.isDeletingDependents() {
27. return gc.processDeletingDependentsItem(item)
28. }
29.
30. // 5、检查 node 是否还存在 ownerReferences
31. ownerReferences := latest.GetOwnerReferences()
32. if len(ownerReferences) == 0 {
33. return nil
34. }
35.
36. // 6、对 ownerReferences 进行分类
solid, dangling, waitingForDependentsDeletion, err :=
37. gc.classifyReferences(item, ownerReferences)
38. if err != nil {
39. return err
40. }
41. switch {
42. // 7、存在不处于删除状态的 owner
43. case len(solid) != 0:
44. if len(dangling) == 0 && len(waitingForDependentsDeletion) == 0 {
45. return nil
46. }
ownerUIDs := append(ownerRefsToUIDs(dangling),
47. ownerRefsToUIDs(waitingForDependentsDeletion)...)
patch := deleteOwnerRefStrategicMergePatch(item.identity.UID,
48. ownerUIDs...)
49. _, err = gc.patch(item, patch, func(n *node) ([]byte, error) {
50. return gc.deleteOwnerRefJSONMergePatch(n, ownerUIDs...)
51. })
52. return err
53. // 8、node 的 owner 处于 waitingForDependentsDeletion 状态并且 node
54. // 的 dependents 未被完全删除
gc.runAttemptToOrphanWorker
3、以上两步中若有失败的会进行重试;
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:574
garbageCollector.Sync
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:164
GetDeletableResources
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:636
4. if err != nil {
5. if discovery.IsGroupDiscoveryFailedError(err) {
6. ......
7. } else {
8. ......
9. }
10. }
11. if preferredResources == nil {
12. return map[schema.GroupVersionResource]struct{}{}
13. }
14. // 2、调用 discovery.FilteredBy 过滤出 deletableResources
deletableResources :=
discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete",
15. "list", "watch"}}, preferredResources)
deletableGroupVersionResources := map[schema.GroupVersionResource]struct{}
16. {}
17. for _, rl := range deletableResources {
18. gv, err := schema.ParseGroupVersion(rl.GroupVersion)
19. if err != nil {
20. continue
21. }
22. for i := range rl.APIResources {
deletableGroupVersionResources[schema.GroupVersionResource{Group:
gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] =
23. struct{}{}
24. }
25. }
26.
27. return deletableGroupVersionResources
28. }
ServerPreferredResources
k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/discovery_client.go:285
36. }
gv := schema.GroupResource{Group: apiGroup.Name, Resource:
37. apiResource.Name}
if _, ok := grAPIResources[gv]; ok && version.Version !=
38. apiGroup.PreferredVersion.Version {
39. continue
40. }
41. grVersions[gv] = version.Version
42. grAPIResources[gv] = apiResource
43. }
44. }
45. }
46.
47. for groupResource, apiResource := range grAPIResources {
48. version := grVersions[groupResource]
groupVersion := schema.GroupVersion{Group: groupResource.Group,
49. Version: version}
50. apiResourceList := gvAPIResourceLists[groupVersion]
apiResourceList.APIResources = append(apiResourceList.APIResources,
51. *apiResource)
52. }
53.
54. if len(failedGroups) == 0 {
55. return result, nil
56. }
57.
58. return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
59. }
GetDeletableResources 方法中的调用流程为:
1. |--> d.ServerGroups
2. |
3. |--> discoveryClient. --|
4. | ServerPreferredResources |
| |-->
5. fetchGroupVersionResources
6. GetDeletableResources --|
7. |
8. |--> discovery.FilteredBy
gc.resyncMonitors
k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go:116
garbagecollector.NewDebugHandler
garbagecollector.NewDebugHandler 主要功能是对外提供一个接口供用户查询当前集群中所有资
源的依赖关系,依赖关系可以以图表的形式展示。
具体使用方法如下所示:
依赖关系图如下所示:
示例
在此处会有一个小示例验证一下源码中的删除阻塞逻辑,当以 Foreground 策略删除一个对象时,
该对象会处于阻塞状态等待其依依赖被删除,此时有三种方式避免该对象处于删除阻塞状态,一是将依
赖对象直接删除,二是将依赖对象自身的 OwnerReferences 中 owner 字段删除,三是将该依赖
对象 OwnerReferences 字段中对应 owner 的 BlockOwnerDeletion 设置为 false,下面
会验证下这三种方式,首先创建一个 deployment,deployment 创建出的 rs 默认不会有
foregroundDeletion finalizers ,此时使用 kubectl edit 手动加上 foregroundDeletion
finalizers ,当 deployment 正常运行时,如下所示:
22. ownerReferences:
23. - apiVersion: apps/v1
24. blockOwnerDeletion: true
25. controller: true
26. kind: Deployment
27. name: nginx-deployment
28. uid: 40a1044e-03d1-48bc-8806-cb79d781c946
29. finalizers:
30. - foregroundDeletion // 为 rs 手动添加的 Foreground 策略
31. ......
32. spec:
33. replicas: 2
34. ......
35. status:
36. ......
总结
GarbageCollectorController 是一种典型的生产者消费者模型,所有 deletableResources
的 informer 都是生产者,每种资源的 informer 监听到变化后都会将对应的事件 push 到
graphChanges 中,graphChanges 是 GraphBuilder 对象中的一个数据结构,GraphBuilder
会启动另外的 goroutine 对 graphChanges 中的事件进行分类并放在其 attemptToDelete 和
attemptToOrphan 两个队列中,garbageCollector 会启动多个 goroutine 对
attemptToDelete 和 attemptToOrphan 两个队列中的事件进行处理,处理的结果就是回收一些
需要被删除的对象。最后,再用一个流程图总结一下 GarbageCollectorController 的主要流程:
1. monitors (producer)
2. |
3. |
4. ∨
5. graphChanges queue
6. |
7. |
8. ∨
9. processGraphChanges
10. |
11. |
12. ∨
13. -------------------------------
14. | |
15. | |
16. ∨ ∨
17. attemptToDelete queue attemptToOrphan queue
18. | |
19. | |
20. ∨ ∨
21. AttemptToDeleteWorker AttemptToOrphanWorker
22. (consumer) (consumer)
DaemonSet 的基本操作
创建
扩缩容
更新
1. // 更新镜像
2. $ kubectl set image ds/nginx-ds nginx-ds=nginx:1.16
3.
4. // 查看更新状态
5. $ kubectl rollout status ds/nginx-ds
回滚
1. // 查看 ds 历史版本信息
2. $ kubectl get controllerrevision
3. NAME CONTROLLER REVISION AGE
4. nginx-ds-5c4b75bdbb daemonset.apps/nginx-ds 2 122m
5. nginx-ds-7cd7798dcd daemonset.apps/nginx-ds 1 133m
6.
7. // 回滚到版本 1
8. $ kubectl rollout undo daemonset nginx-ds --to-revision=1
9.
10. // 查看回滚状态
11. $ kubectl rollout status ds/nginx-ds
暂停
daemonset 目前不支持暂停操作。
删除
daemonset 也支持两种删除操作。
1. // 非级联删除
2. $ kubectl delete ds/nginx-ds --cascade=false
3.
4. // 级联删除
5. $ kubectl delete ds/nginx-ds
DaemonSetController 源码分析
kubernetes 版本:v1.16
k8s.io/kubernetes/cmd/kube-controller-manager/app/apps.go:36
10. ctx.ClientBuilder.ClientOrDie("daemon-set-controller"),
11. flowcontrol.NewBackOff(1*time.Second, 15*time.Minute),
12. )
13. if err != nil {
return nil, true, fmt.Errorf("error creating DaemonSets controller:
14. %v", err)
15. }
go
dsc.Run(int(ctx.ComponentConfig.DaemonSetController.ConcurrentDaemonSetSyncs),
16. ctx.Stop)
17. return nil, true, nil
18. }
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:263
18.
19. <-stopCh
20. }
syncDaemonSet
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:1212
17.
18. // 3、从 nodeLister 获取所有 node
19. nodeList, err := dsc.nodeLister.List(labels.Everything())
20. ......
21.
22. everything := metav1.LabelSelector{}
23. if reflect.DeepEqual(ds.Spec.Selector, &everything) {
dsc.eventRecorder.Eventf(ds, v1.EventTypeWarning, SelectingAllReason,
24. "This daemon set is selecting all pods. A non-empty selector is required. ")
25. return nil
26. }
27.
28. // 4、获取 dsKey
29. dsKey, err := controller.KeyFunc(ds)
30. if err != nil {
31. return fmt.Errorf("couldn't get key for object %#v: %v", ds, err)
32. }
33.
34. // 5、判断 ds 是否处于删除状态
35. if ds.DeletionTimestamp != nil {
36. return nil
37. }
38.
39. // 6、获取 current 和 old controllerRevision
40. cur, old, err := dsc.constructHistory(ds)
41. if err != nil {
return fmt.Errorf("failed to construct revisions of DaemonSet: %v",
42. err)
43. }
44. hash := cur.Labels[apps.DefaultDaemonSetUniqueLabelKey]
45.
46. // 7、判断是否满足 expectations 机制
47. if !dsc.expectations.SatisfiedExpectations(dsKey) {
48. return dsc.updateDaemonSetStatus(ds, nodeList, hash, false)
49. }
50.
51. // 8、执行实际的 sync 操作
52. err = dsc.manage(ds, nodeList, hash)
53. if err != nil {
54. return err
55. }
56.
57. // 9、判断是否为更新操作,并执行对应的更新操作
58. if dsc.expectations.SatisfiedExpectations(dsKey) {
59. switch ds.Spec.UpdateStrategy.Type {
60. case apps.OnDeleteDaemonSetStrategyType:
61. case apps.RollingUpdateDaemonSetStrategyType:
62. err = dsc.rollingUpdate(ds, nodeList, hash)
63. }
64. if err != nil {
65. return err
66. }
67. }
68. // 10、清理过期的 controllerrevision
69. err = dsc.cleanupHistory(ds, old)
70. if err != nil {
71. return fmt.Errorf("failed to clean up revisions of DaemonSet: %v", err)
72. }
73.
74. // 11、更新 ds 状态
75. return dsc.updateDaemonSetStatus(ds, nodeList, hash, true)
76. }
manage
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:952
getNodesToDaemonPods
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:820
podsShouldBeOnNode
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:866
7. // 2、获取该节点上的指定ds的pod列表
8. daemonPods, exists := nodeToDaemonPods[node.Name]
9. dsKey, err := cache.MetaNamespaceKeyFunc(ds)
10. if err != nil {
11. utilruntime.HandleError(err)
12. return
13. }
14.
15. // 3、从 suspended list 中移除在该节点上 ds 的 pod
16. dsc.removeSuspendedDaemonPods(node.Name, dsKey)
17.
18. switch {
19. // 4、对于需要创建 pod 但是不能调度 pod 的 node,先把 pod 放入到 suspended 队列中
20. case wantToRun && !shouldSchedule:
21. dsc.addSuspendedDaemonPods(node.Name, dsKey)
22. // 5、需要创建 pod 且 pod 未运行,则创建 pod
23. case shouldSchedule && !exists:
24. nodesNeedingDaemonPods = append(nodesNeedingDaemonPods, node.Name)
25. // 6、需要 pod 一直运行
26. case shouldContinueRunning:
27. var daemonPodsRunning []*v1.Pod
28. for _, pod := range daemonPods {
29. if pod.DeletionTimestamp != nil {
30. continue
31. }
32. // 7、如果 pod 运行状态为 failed,则删除该 pod
33. if pod.Status.Phase == v1.PodFailed {
34. backoffKey := failedPodsBackoffKey(ds, node.Name)
35.
36. now := dsc.failedPodsBackoff.Clock.Now()
inBackoff :=
37. dsc.failedPodsBackoff.IsInBackOffSinceUpdate(backoffKey, now)
38. if inBackoff {
39. delay := dsc.failedPodsBackoff.Get(backoffKey)
40. dsc.enqueueDaemonSetAfter(ds, delay)
41. continue
42. }
43.
44. dsc.failedPodsBackoff.Next(backoffKey, now)
45. podsToDelete = append(podsToDelete, pod.Name)
46. } else {
47. daemonPodsRunning = append(daemonPodsRunning, pod)
48. }
49. }
50. // 8、如果节点上已经运行 daemon pod 数 > 1,保留运行时间最长的 pod,其余的删除
51. if len(daemonPodsRunning) > 1 {
52. sort.Sort(podByCreationTimestampAndPhase(daemonPodsRunning))
53. for i := 1; i < len(daemonPodsRunning); i++ {
54. podsToDelete = append(podsToDelete, daemonPodsRunning[i].Name)
55. }
56. }
57. // 9、如果 pod 不需要继续运行但 pod 已存在则需要删除 pod
58. case !shouldContinueRunning && exists:
59. for _, pod := range daemonPods {
60. if pod.DeletionTimestamp != nil {
61. continue
62. }
63. podsToDelete = append(podsToDelete, pod.Name)
64. }
65. }
66.
67. return nodesNeedingDaemonPods, podsToDelete, nil
68. }
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:1337
syncNodes
的发展中社区还是希望能通过默认调度器进行调度,所以才出现了第二种方式,原因主要有以下五点:
更详细的原因可以参考社区的文档:schedule-DS-pod-by-scheduler.md。
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:990
batchSize := integer.IntMin(createDiff,
27. controller.SlowStartInitialBatchSize)
for pos := 0; createDiff > pos; batchSize, pos =
28. integer.IntMin(2*batchSize, createDiff-(pos+batchSize)), pos+batchSize {
29. errorCount := len(errCh)
30. createWait.Add(batchSize)
31. for i := pos; i < pos+batchSize; i++ {
32. go func(ix int) {
33. defer createWait.Done()
34. var err error
35.
36. podTemplate := template.DeepCopy()
37.
// 4、若启动了 ScheduleDaemonSetPods 功能,则通过 kube-scheduler 创
38. 建 pod
if
39. utilfeature.DefaultFeatureGate.Enabled(features.ScheduleDaemonSetPods) {
podTemplate.Spec.Affinity =
40. util.ReplaceDaemonSetPodNodeNameNodeAffinity(
41. podTemplate.Spec.Affinity, nodesNeedingDaemonPods[ix])
42.
err =
43. dsc.podControl.CreatePodsWithControllerRef(ds.Namespace, podTemplate,
44. ds, metav1.NewControllerRef(ds, controllerKind))
45. } else {
46. // 5、否则直接设置 pod 的 .spec.NodeName 创建 pod
err =
dsc.podControl.CreatePodsOnNode(nodesNeedingDaemonPods[ix], ds.Namespace,
47. podTemplate,
48. ds, metav1.NewControllerRef(ds, controllerKind))
49. }
50.
51. // 6、创建 pod 时忽略 timeout err
52. if err != nil && errors.IsTimeout(err) {
53. return
54. }
55. if err != nil {
56. dsc.expectations.CreationObserved(dsKey)
57. errCh <- err
58. utilruntime.HandleError(err)
59. }
60. }(i)
61. }
62. createWait.Wait()
63.
64. // 7、将创建失败的 pod 数记录到 expectations 中
65. skippedPods := createDiff - (batchSize + pos)
66. if errorCount < len(errCh) && skippedPods > 0 {
67. dsc.expectations.LowerExpectations(dsKey, skippedPods, 0)
68. break
69. }
70. }
71.
72. // 8、并发删除 deleteDiff 中的 pod
73. deleteWait := sync.WaitGroup{}
74. deleteWait.Add(deleteDiff)
75. for i := 0; i < deleteDiff; i++ {
76. go func(ix int) {
77. defer deleteWait.Done()
if err := dsc.podControl.DeletePod(ds.Namespace, podsToDelete[ix],
78. ds); err != nil {
79. dsc.expectations.DeletionObserved(dsKey)
80. errCh <- err
81. utilruntime.HandleError(err)
82. }
83. }(i)
84. }
85. deleteWait.Wait()
86. errors := []error{}
87. close(errCh)
88. for err := range errCh {
89. errors = append(errors, err)
90. }
91. return utilerrors.NewAggregate(errors)
92. }
RollingUpdate
当为 RollingUpdate 时,主要逻辑为:
k8s.io/kubernetes/pkg/controller/daemon/update.go:43
21. }
22. oldPodsToDelete = append(oldPodsToDelete, pod.Name)
23. }
24. // 5、根据 maxUnavailable 值确定是否需要删除 pod
25. for _, pod := range oldAvailablePods {
26. if numUnavailable >= maxUnavailable {
27. break
28. }
29. oldPodsToDelete = append(oldPodsToDelete, pod.Name)
30. numUnavailable++
31. }
32. // 6、调用 syncNodes 方法删除 oldPodsToDelete 数组中的 pods
33. return dsc.syncNodes(ds, oldPodsToDelete, []string{}, hash)
34. }
1. |-> dsc.getNodesToDaemonPods
2. |
3. |
4. manage ---- |-> dsc.podsShouldBeOnNode ---> dsc.nodeShouldRunDaemonPod
5. |
6. |
7. |-> dsc.syncNodes
updateDaemonSetStatus
1. status:
2. currentNumberScheduled: 1 // 已经运行了 DaemonSet Pod的节点数量
3. desiredNumberScheduled: 1 // 需要运行该DaemonSet Pod的节点数量
4. numberMisscheduled: 0 // 不需要运行 DeamonSet Pod 但是已经运行了的节点数量
5. numberReady: 0 // DaemonSet Pod状态为Ready的节点数量
numberAvailable: 1 // DaemonSet Pod状态为Ready且运行时间超过
6. // Spec.MinReadySeconds 的节点数量
numberUnavailable: 0 // desiredNumberScheduled - numberAvailable
7. 的节点数量
8. observedGeneration: 3
9. updatedNumberScheduled: 1 // 已经完成DaemonSet Pod更新的节点数量
updateDaemonSetStatus 主要逻辑为:
k8s.io/kubernetes/pkg/controller/daemon/daemon_controller.go:1152
1. |-> dsc.getNodesToDaemonPods
2. |
3. |
|-> manage -->|-> dsc.podsShouldBeOnNode --->
4. dsc.nodeShouldRunDaemonPod
5. | |
6. | |
7. syncDaemonSet --> | |-> dsc.syncNodes
8. |
9. |-> rollingUpdate
10. |
11. |
12. |-> updateDaemonSetStatus
总结
在 daemonset controller 中可以看到许多功能都是 deployment 和 statefulset 已有的。
在创建 pod 的流程与 replicaset controller 创建 pod 的流程是相似的,都使用了
expectations 机制并且限制了在一个 syncLoop 中最多创建或删除的 pod 数。更新方式与
statefulset 一样都有 OnDelete 和 RollingUpdate 两种, OnDelete 方式与
statefulset 相似,都需要手动删除对应的 pod,而 RollingUpdate 方式与 statefulset
和 deployment 都有点区别, RollingUpdate 方式更新时不支持暂停操作并且 pod 是先删除再
创建的顺序进行。版本控制方式与 statefulset 的一样都是使用 controllerRevision 。最后
要说的一点是在 v1.12 及以后的版本中,使用 daemonset 创建的 pod 已不再使用直接指定
.spec.nodeName 的方式绕过调度器进行调度,而是走默认调度器通过 nodeAffinity 的方式调
度到每一个节点上。
参考:
https://yq.aliyun.com/articles/702305
Statefulset 的基本功能
statefulset 旨在与有状态的应用及分布式系统一起使用,statefulset 中的每个 pod 拥有一个
唯一的身份标识,并且所有 pod 名都是按照 {0..N-1} 的顺序进行编号。本文会主要分析
statefulset controller 的设计与实现,在分析源码前先介绍一下 statefulset 的基本使
用。
创建
扩容
缩容
更新
1. // 使用 RollingUpdate 策略更新
$ kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path":
2. "/spec/template/spec/containers/0/image", "value":"nginx:1.16"}]'
3.
4. statefulset.apps/web patched
5.
6. $ kubectl rollout status sts/web
7. Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 1 out of 2 new pods have been
8. updated...
9. Waiting for 1 pods to be ready...
10. partitioned roll out complete: 2 new pods have been updated...
1. // 使用 OnDelete 方式更新
$ kubectl patch statefulset nginx --type='json' -p='[{"op": "replace", "path":
2. "/spec/template/spec/containers/0/image", "value":"nginx:1.9"}]'
3.
4. // 删除 web-1
5. $ kubectl delete pod web-1
6.
7. // 查看 web-0 与 web-1 的镜像版本,此时发现 web-1 已经变为最新版本 nginx:1.9 了
$ kubectl get pod -l app=nginx -o jsonpath='{range .items[*]}{.metadata.name}
8. {"\t"}{.spec.containers[0].image}{"\n"}{end}'
9. web-0 nginx:1.16
10. web-1 nginx:1.9
回滚
1. // 查看 sts 的历史版本
2. $ kubectl rollout history statefulset web
3. statefulset.apps/web
4. REVISION
5. 0
6. 0
7. 5
8. 6
9.
10. $ kubectl get controllerrevision
11. NAME CONTROLLER REVISION AGE
12. web-6c4c79564f statefulset.apps/web 6 11m
13. web-c47b9997f statefulset.apps/web 5 4h13m
14.
15. // 回滚至最近的一个版本
16. $ kubectl rollout undo statefulset web --to-revision=5
因为 statefulset 的使用对象是有状态服务,大部分有状态副本集都会用到持久存储,
statefulset 下的每个 pod 正常情况下都会关联一个 pv 对象,对 statefulset 对象回滚非常
容易,但其使用的 pv 中保存的数据无法回滚,所以在生产环境中进行回滚时需要谨慎操作,
statefulset、pod、pvc 和 pv 关系图如下所示:
删除
1. // 1、非级联删除
2. $ kubectl delete statefulset web --cascade=false
3.
4. // 删除 sts 后 pod 依然处于运行中
5. $ kubectl get pod
6. NAME READY STATUS RESTARTS AGE
7. web-0 1/1 Running 0 4m38s
8. web-1 1/1 Running 0 17m
9.
10. // 重新创建 sts 后,会再次关联所有的 pod
11. $ kubectl create -f sts.yaml
12.
13. $ kubectl get sts
14. NAME READY AGE
15. web 2/2 28s
1. // 2、级联删除
2. $ kubectl delete statefulset web
3.
4. $ kubectl get pod -o wide -w
5. ......
web-0 1/1 Terminating 0 17m 10.1.0.18 minikube <none>
6. <none>
web-1 1/1 Terminating 0 36m 10.1.0.15 minikube <none>
7. <none>
web-1 0/1 Terminating 0 36m 10.1.0.15 minikube <none>
8. <none>
web-0 0/1 Terminating 0 17m 10.1.0.18 minikube <none>
9. <none>
Pod 管理策略
StatefulSetController 源码分析
kubernetes 版本:v1.16
k8s.io/kubernetes/cmd/kube-controller-manager/app/apps.go:55
10. ctx.ClientBuilder.ClientOrDie("statefulset-controller"),
).Run(int(ctx.ComponentConfig.StatefulSetController.ConcurrentStatefulSetSyncs),
11. ctx.Stop)
12. return nil, true, nil
13. }
sync
sync 方法的主要逻辑为:
k8s.io/kubernetes/pkg/controller/statefulset/stateful_set.go:408
syncStatefulSet
k8s.io/kubernetes/pkg/controller/statefulset/stateful_set.go:448
UpdateStatefulSet 方法的主要逻辑如下所示:
1、获取历史 revisions;
2、计算 currentRevision 和 updateRevision ,若 sts 处于更新过程中则
currentRevision 和 updateRevision 值不同;
3、调用 ssc.updateStatefulSet 执行实际的 sync 操作;
4、调用 ssc.updateStatefulSetStatus 更新 status subResource;
5、根据 sts 的 spec.revisionHistoryLimit 字段清理过期的 controllerrevision ;
k8s.io/kubernetes/pkg/controller/statefulset/stateful_set_control.go:75
updateStatefulSet
本文档使用 书栈网 · BookStack.CN 构建 - 173 -
statefulset controller 源码分析
实是多余的;
k8s.io/kubernetes/pkg/controller/statefulset/stateful_set_control.go:255
80.
81. for i := range condemned {
82. if !isHealthy(condemned[i]) {
83. unhealthy++
84. if ord := getOrdinal(condemned[i]); ord < firstUnhealthyOrdinal {
85. firstUnhealthyOrdinal = ord
86. firstUnhealthyPod = condemned[i]
87. }
88. }
89. }
90.
91. ......
92.
93. // 8、判断是否处于删除中
94. if set.DeletionTimestamp != nil {
95. return &status, nil
96. }
97.
98. // 9、默认设置为非并行模式
99. monotonic := !allowsBurst(set)
100.
101.
102. // 10、确保 replicas 数组中所有的 pod 是 running 的
103. for i := range replicas {
104. // 11、对于 failed 的 pod 删除并重新构建 pod object
105. if isFailed(replicas[i]) {
106. ......
if err := ssc.podControl.DeleteStatefulPod(set, replicas[i]); err
107. != nil {
108. return &status, err
109. }
110. if getPodRevision(replicas[i]) == currentRevision.Name {
111. status.CurrentReplicas--
112. }
113. if getPodRevision(replicas[i]) == updateRevision.Name {
114. status.UpdatedReplicas--
115. }
116. status.Replicas--
117. replicas[i] = newVersionedStatefulSetPod(
118. currentSet,
119. updateSet,
120. currentRevision.Name,
121. updateRevision.Name,
122. i)
123. }
124.
125. // 12、如果 pod.Status.Phase 不为“” 说明该 pod 未创建,则直接重新创建该 pod
126. if !isCreated(replicas[i]) {
if err := ssc.podControl.CreateStatefulPod(set, replicas[i]); err
127. != nil {
128. return &status, err
129. }
130. status.Replicas++
131. if getPodRevision(replicas[i]) == currentRevision.Name {
132. status.CurrentReplicas++
133. }
134. if getPodRevision(replicas[i]) == updateRevision.Name {
135. status.UpdatedReplicas++
136. }
137.
// 13、如果为Parallel,直接return status结束;如果为OrderedReady,循环处
138. 理下一个pod。
139. if monotonic {
140. return &status, nil
141. }
142. continue
143. }
144.
// 14、如果pod正在删除(pod.DeletionTimestamp不为nil),且
145. Spec.PodManagementPolicy不
146. // 为Parallel,直接return status结束,结束后会在下一个 syncLoop 继续进行处理,
147. // pod 状态的改变会触发下一次 syncLoop
148. if isTerminating(replicas[i]) && monotonic {
149. ......
150. return &status, nil
151. }
152.
// 15、如果pod状态不是Running & Ready,且Spec.PodManagementPolicy不为
153. Parallel,
154. // 直接return status结束
155. if !isRunningAndReady(replicas[i]) && monotonic {
156. ......
157. return &status, nil
158. }
159.
总结
参考:
https://github.com/kubernetes/kubernetes/issues/78007
https://github.com/kubernetes/kubernetes/issues/67250
https://www.cnblogs.com/linuxk/p/9767736.html
deployment 的功能
deployment 是 kubernetes 中用来部署无状态应用的一个对象,也是最常用的一种对象。
deployment 的基本功能
1. apiVersion: apps/v1
2. kind: Deployment
3. metadata:
4. name: nginx-deployment
5. spec:
6. progressDeadlineSeconds: 600 // 执行操作的超时时间
7. replicas: 20
8. revisionHistoryLimit: 10 // 保存的历史版本数量
9. selector:
10. matchLabels:
11. app: nginx-deployment
12. strategy:
13. rollingUpdate:
14. maxSurge: 25% // 升级过程中最多可以比原先设置多出的 pod 数量
15. maxUnavailable: 25% // 升级过程中最多有多少个 pod 处于无法提供服务的状态
16. type: RollingUpdate // 更新策略
17. template:
18. metadata:
19. labels:
20. app: nginx-deployment
21. spec:
22. containers:
23. - name: nginx-deployment
24. image: nginx:1.9
25. imagePullPolicy: IfNotPresent
26. ports:
27. - containerPort: 80
创建
滚动更新
回滚
1. // 查看历史版本
2. $ kubectl rollout history deployment/nginx-deployment
3. deployment.extensions/nginx-deployment
4. REVISION CHANGE-CAUSE
5. 4 <none>
6. 5 <none>
7.
8. // 指定版本回滚
9. $ kubectl rollout undo deployment/nginx-deployment --to-revision=2
扩缩容
暂停与恢复
删除
kubernetes 版本:v1.16
k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go:158
k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go:373
k8s.io/kubernetes/cmd/kube-controller-manager/app/apps.go:82
ctx.ComponentConfig.DeploymentController.ConcurrentDeploymentSyncs 指定了
deployment controller 中工作的 goroutine 数量,默认值为 5,即会启动五个 goroutine
从 workqueue 中取出 object 并进行 sync 操作,该参数的默认值定义在
k8s.io/kubernetes/pkg/controller/deployment/config/v1alpha1/defaults.go 中。
k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go:148
syncDeployment 的主要流程如下所示:
k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go:562
24.
25. // 3、获取 deployment 对应的所有 rs,通过 LabelSelector 进行匹配
26. rsList, err := dc.getReplicaSetsForDeployment(d)
27. if err != nil {
28. return err
29. }
30.
31. // 4、获取当前 Deployment 对象关联的 pod,并根据 rs.UID 对 pod 进行分类
32. podMap, err := dc.getPodMapForDeployment(d, rsList)
33. if err != nil {
34. return err
35. }
36.
37. // 5、如果该 deployment 处于删除状态,则更新其 status
38. if d.DeletionTimestamp != nil {
39. return dc.syncStatusOnly(d, rsList)
40. }
41.
42. // 6、检查是否处于 pause 状态
43. if err = dc.checkPausedConditions(d); err != nil {
44. return err
45. }
46.
47. if d.Spec.Paused {
48. return dc.sync(d, rsList)
49. }
50.
51. // 7、检查是否为回滚操作
52. if getRollbackTo(d) != nil {
53. return dc.rollback(d, rsList)
54. }
55.
56. // 8、检查 deployment 是否处于 scale 状态
57. scalingEvent, err := dc.isScalingEvent(d, rsList)
58. if err != nil {
59. return err
60. }
61. if scalingEvent {
62. return dc.sync(d, rsList)
63. }
64.
65. // 9、更新操作
从 syncDeployment 中也可知以上几个操作的优先级为:
删除
syncDeployment 中首先处理的是删除操作,删除操作是由客户端发起的,首先会在对象的
metadata 中设置 DeletionTimestamp 字段。
k8s.io/kubernetes/pkg/controller/deployment/sync.go:48
k8s.io/kubernetes/pkg/controller/deployment/sync.go:469
k8s.io/kubernetes/pkg/controller/deployment/sync.go:483
暂停和恢复
体实现。
syncDeploymentStatus 方法以及相关的代码在上文的删除操作中已经解释过了,此处不再进行分
析。
回滚
版本。
rollback 方法的主要逻辑如下:
k8s.io/kubernetes/pkg/controller/deployment/rollback.go:32
if rollbackTo.Revision = deploymentutil.LastRevision(allRSs);
14. rollbackTo.Revision == 0 {
15. // 4、清除回滚标志放弃回滚操作
16. return dc.updateDeploymentAndClearRollbackTo(d)
17. }
18. }
19. for _, rs := range allRSs {
20. v, err := deploymentutil.Revision(rs)
21. if err != nil {
22. ......
23. }
24.
25. if v == rollbackTo.Revision {
26. // 5、调用 rollbackToTemplate 进行回滚操作
27. performedRollback, err := dc.rollbackToTemplate(d, rs)
28. if performedRollback && err == nil {
29. ......
30. }
31. return err
32. }
33. }
34.
35. return dc.updateDeploymentAndClearRollbackTo(d)
36. }
k8s.io/kubernetes/pkg/controller/deployment/rollback.go:75
dc.emitRollbackWarningEvent(d,
12. deploymentutil.RollbackTemplateUnchanged, eventMsg)
13. }
14.
15. // 4、更新 deployment 并清除回滚标志
16. return performedRollback, dc.updateDeploymentAndClearRollbackTo(d)
17. }
扩缩容
isScalingEvent 的主要逻辑如下所示:
1、获取所有的 rs;
2、过滤出 activeRS,rs.Spec.Replicas > 0 的为 activeRS;
3、判断 rs 的 desired 值是否等于 deployment.Spec.Replicas,若不等于则需要为
rs 进行 scale 操作;
k8s.io/kubernetes/pkg/controller/deployment/sync.go:526
k8s.io/kubernetes/pkg/controller/deployment/sync.go:294
20. }
21. return nil
22. }
23.
24. // 4、此时说明 当前的 rs 副本并没有达到期望状态并且存在多个活跃的 rs 对象,
// 若 deployment 的更新策略为滚动更新,需要按照比例分别对各个活跃的 rs 进行扩容或者缩
25. 容
26. if deploymentutil.IsRollingUpdate(deployment) {
27. allRSs := controller.FilterActiveReplicaSets(append(oldRSs, newRS))
28. allRSsReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
29.
30. allowedSize := int32(0)
31.
32. // 5、计算最大可以创建出的 pod 数
33. if *(deployment.Spec.Replicas) > 0 {
allowedSize = *(deployment.Spec.Replicas) +
34. deploymentutil.MaxSurge(*deployment)
35. }
36.
37. // 6、计算需要扩容的 pod 数
38. deploymentReplicasToAdd := allowedSize - allRSsReplicas
39.
// 7、如果 deploymentReplicasToAdd > 0,ReplicaSet 将按照从新到旧的顺序依次
40. 进行扩容;
// 如果 deploymentReplicasToAdd < 0,ReplicaSet 将按照从旧到新的顺序依次进行
41. 缩容;
// 若 > 0,则需要先扩容 newRS,但当在先扩容然后立刻缩容时,若 <0,则需要先删除
42. oldRS 的 pod
43. var scalingOperation string
44. switch {
45. case deploymentReplicasToAdd > 0:
46. sort.Sort(controller.ReplicaSetsBySizeNewer(allRSs))
47. scalingOperation = "up"
48.
49. case deploymentReplicasToAdd < 0:
50. sort.Sort(controller.ReplicaSetsBySizeOlder(allRSs))
51. scalingOperation = "down"
52. }
53. deploymentReplicasAdded := int32(0)
54. nameToSize := make(map[string]int32)
55.
56. // 8、遍历所有的 rs,计算每个 rs 需要扩容或者缩容到的期望副本数
57. for i := range allRSs {
58. rs := allRSs[i]
59.
60. if deploymentReplicasToAdd != 0 {
61. // 9、调用 GetProportion 估算出 rs 需要扩容或者缩容的副本数
proportion := deploymentutil.GetProportion(rs, *deployment,
62. deploymentReplicasToAdd, deploymentReplicasAdded)
63.
64. nameToSize[rs.Name] = *(rs.Spec.Replicas) + proportion
65. deploymentReplicasAdded += proportion
66. } else {
67. nameToSize[rs.Name] = *(rs.Spec.Replicas)
68. }
69. }
70.
71. // 10、遍历所有的 rs,第一个最活跃的 rs.Spec.Replicas 加上上面循环中计算出
72. // 其他 rs 要加或者减的副本数,然后更新所有 rs 的 rs.Spec.Replicas
73. for i := range allRSs {
74. rs := allRSs[i]
75.
76. // 11、要扩容或者要删除的 rs 已经达到了期望状态
77. if i == 0 && deploymentReplicasToAdd != 0 {
78. leftover := deploymentReplicasToAdd - deploymentReplicasAdded
79. nameToSize[rs.Name] = nameToSize[rs.Name] + leftover
80. if nameToSize[rs.Name] < 0 {
81. nameToSize[rs.Name] = 0
82. }
83. }
84.
85. // 12、对 rs 进行 scale 操作
if _, _, err := dc.scaleReplicaSet(rs, nameToSize[rs.Name],
86. deployment, scalingOperation); err != nil {
87. return err
88. }
89. }
90. }
91. return nil
92. }
k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go:466
滚动更新
deployment 的更新方式有两种,其中滚动更新是最常用的,下面就看看其具体的实现。
3. switch d.Spec.Strategy.Type {
4. case apps.RecreateDeploymentStrategyType:
5. return dc.rolloutRecreate(d, rsList, podMap)
6. case apps.RollingUpdateDeploymentStrategyType:
7. // 调用 rolloutRolling 执行滚动更新
8. return dc.rolloutRolling(d, rsList)
9. }
10. ......
11. }
reconcileNewReplicaSet 主要逻辑如下:
k8s.io/kubernetes/pkg/controller/deployment/rolling.go:69
1、判断更新策略;
2、计算 maxSurge 值;
3、通过 allRSs 计算 currentPodCount 的值;
4、最后计算 scaleUpCount 值;
k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go:814
reconcileOldReplicaSets 的主要逻辑如下:
1. scale down
2.
3. or --> dc.scaleReplicaSetAndRecordEvent() --> dc.scaleReplicaSet()
4.
5. scale up
滚动更新示例
上面的代码看起来非常的枯燥,只看源码其实并不能完全理解整个滚动升级的流程,此处举个例子说明
一下:
1. // 向上取整为 3
2. maxSurge = replicas * deployment.spec.strategy.rollingUpdate.maxSurge(25%)= 2.5
3.
4. // 向下取整为 2
maxUnavailable = replicas *
5. deployment.spec.strategy.rollingUpdate.maxUnavailable(25%)= 2.5
6.
7. maxAvailable = replicas(10) + MaxSurge(3) = 13
1. // 13 = 10 + 3
2. allPodsCount := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
3.
4. // 8 = 10 - 2
5. minAvailable := *(deployment.Spec.Replicas) - maxUnavailable
6.
7. // ???
newRSUnavailablePodCount := *(newRS.Spec.Replicas) -
8. newRS.Status.AvailableReplicas
9.
10. // 13 - 8 - ???
11. maxScaledDown := allPodsCount - minAvailable - newRSUnavailablePodCount
1. $ kubectl get rs -w
2. NAME DESIRED CURRENT READY AGE
3. nginx-deployment-68b649bd8b 10 0 0 0s
4. nginx-deployment-68b649bd8b 10 10 0 0s
5. nginx-deployment-68b649bd8b 10 10 10 13s
6.
7. nginx-deployment-689bff574f 3 0 0 0s
8.
9. nginx-deployment-68b649bd8b 8 10 10 14s
10.
11. nginx-deployment-689bff574f 3 0 0 0s
12. nginx-deployment-689bff574f 3 3 3 1s
13.
14. nginx-deployment-689bff574f 5 3 0 0s
15.
16. nginx-deployment-68b649bd8b 8 8 8 14s
17.
18. nginx-deployment-689bff574f 5 3 0 0s
19. nginx-deployment-689bff574f 5 5 0 0s
20.
21. nginx-deployment-689bff574f 5 5 5 6s
22. ......
重新创建
rolloutRecreate 方法主要逻辑为:
EqualIgnoreHash 方法如下所示:
k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go:633
用该更新策略。
总结
本文主要介绍了 deployment 的基本功能以及从源码角度分析其实现,deployment 主要有更新、
回滚、扩缩容、暂停与恢复几个主要的功能。从源码中可以看到 deployment 在升级过程中一直会修
改 rs 的 replicas 以及 annotation 最终达到最终期望的状态,但是整个过程中并没有体现出
pod 的创建与删除,从开头三者的关系图中可知是 rs 控制 pod 的变化,在下篇文章中会继续介绍
rs 是如何控制 pod 的变化。
参考:
https://my.oschina.net/u/3797264/blog/2966086
https://draveness.me/kubernetes-deployment
ReplicaSetController 源码分析
启动流程
k8s.io/kubernetes/cmd/kube-controller-manager/app/apps.go:69
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:109
37. podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
38. AddFunc: rsc.addPod,
39. UpdateFunc: rsc.updatePod,
40. DeleteFunc: rsc.deletePod,
41. })
42. ......
43.
44. return rsc
45. }
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:177
26. if quit {
27. return false
28. }
29. defer rsc.queue.Done(key)
30.
31. // 5、执行 sync 操作
32. err := rsc.syncHandler(key.(string))
33. ......
34.
35. return true
36. }
EventHandler
addPod
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:255
updatePod
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:298
11. rsc.deletePod(curPod)
12. if labelChanged {
13. rsc.deletePod(oldPod)
14. }
15. return
16. }
17.
18. // 2、如果 pod 的 OwnerReference 发生改变,将 oldRS 入队
19. curControllerRef := metav1.GetControllerOf(curPod)
20. oldControllerRef := metav1.GetControllerOf(oldPod)
controllerRefChanged := !reflect.DeepEqual(curControllerRef,
21. oldControllerRef)
22. if controllerRefChanged && oldControllerRef != nil {
if rs := rsc.resolveControllerRef(oldPod.Namespace, oldControllerRef);
23. rs != nil {
24. rsc.enqueueReplicaSet(rs)
25. }
26. }
27.
28. // 3、获取 pod 关联的 rs,入队 rs
29. if curControllerRef != nil {
30. rs := rsc.resolveControllerRef(curPod.Namespace, curControllerRef)
31. if rs == nil {
32. return
33. }
34.
35. rsc.enqueueReplicaSet(rs)
if !podutil.IsPodReady(oldPod) && podutil.IsPodReady(curPod) &&
36. rs.Spec.MinReadySeconds > 0 {
rsc.enqueueReplicaSetAfter(rs,
37. (time.Duration(rs.Spec.MinReadySeconds)*time.Second)+time.Second)
38. }
39. return
40. }
41.
42.
43. // 4、查找匹配的 rs
44. if labelChanged || controllerRefChanged {
45. rss := rsc.getPodReplicaSets(curPod)
46. if len(rss) == 0 {
47. return
48. }
49. for _, rs := range rss {
50. rsc.enqueueReplicaSet(rs)
51. }
52. }
53. }
deletePod
1、确认该对象是否为 pod;
2、判断是否为孤儿 pod;
3、获取其对应的 rs 以及 rsKey;
4、更新 expectations 中 rsKey 的 del 值;
5、将 rs 入队;
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:372
AddRS 和 DeleteRS
以上两个操作仅仅是将对应的 rs 入队。
UpdateRS
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:232
至于 expectations 机制会在下文进行分析。
syncReplicaSet
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:562
在 syncReplicaSet 方法中有几个重要的操作分别
为: rsc.expectations.SatisfiedExpectations 、 rsc.manageReplicas 、 calculateStatus
,下面一一进行分析。
SatisfiedExpectations
k8s.io/kubernetes/pkg/controller/controller_utils.go:181
23. }
24.
25. // 4、判断 key 是否过期,ExpectationsTimeout 默认值为 5 * time.Minute
26. func (exp *ControlleeExpectations) isExpired() bool {
27. return clock.RealClock{}.Since(exp.timestamp) > ExpectationsTimeout
28. }
manageReplicas
该方法主要逻辑如下所示:
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:459
10. diff *= -1
11. 3、判断需要创建的 pod 数是否超过单次 sync 上限值 500
12. if diff > rsc.burstReplicas {
13. diff = rsc.burstReplicas
14. }
15.
16. 4、在 expectations 中进行记录,若该 key 已经存在会进行覆盖
17. rsc.expectations.ExpectCreations(rsKey, diff)
18.
19. 5、调用 slowStartBatch 创建所需要的 pod
successfulCreations, err := slowStartBatch(diff,
20. controller.SlowStartInitialBatchSize, func() error {
err := rsc.podControl.CreatePodsWithControllerRef(rs.Namespace,
21. &rs.Spec.Template, rs, metav1.NewControllerRef(rs, rsc.GroupVersionKind))
22. // 6、若为 timeout err 则忽略
23. if err != nil && errors.IsTimeout(err) {
24. return nil
25. }
26. return err
27. })
28.
29. // 7、计算未创建的 pod 数,并记录在 expectations 中
// 若 pod 创建成功,informer watch 到事件后会在 addPod handler 中更新
30. expectations
31. if skippedPods := diff - successfulCreations; skippedPods > 0 {
32. for i := 0; i < skippedPods; i++ {
33. rsc.expectations.CreationObserved(rsKey)
34. }
35. }
36. return err
37. } else if diff > 0 {
38. // 8、若 diff >0 说明需要删除多创建的 pod
39. if diff > rsc.burstReplicas {
40. diff = rsc.burstReplicas
41. }
42.
43. // 9、getPodsToDelete 会按照一定的策略找出需要删除的 pod 列表
44. podsToDelete := getPodsToDelete(filteredPods, diff)
45.
46. // 10、在 expectations 中进行记录,若该 key 已经存在会进行覆盖
47. rsc.expectations.ExpectDeletions(rsKey, getPodKeys(podsToDelete))
48.
49. // 11、进行并发删除的操作
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:658
7. wg.Add(batchSize)
8. for i := 0; i < batchSize; i++ {
9. go func() {
10. defer wg.Done()
11. if err := fn(); err != nil {
12. errCh <- err
13. }
14. }()
15. }
16. wg.Wait()
17. curSuccesses := batchSize - len(errCh)
18. successes += curSuccesses
19. if len(errCh) > 0 {
20. return successes, <-errCh
21. }
22. remaining -= batchSize
23. }
24. return successes, nil
25. }
上面的几个排序规则遵循互斥原则,从上到下进行匹配,符合条件则排序完成,代码如下所示:
k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go:684
k8s.io/kubernetes/pkg/controller/controller_utils.go:735
38. }
calculateStatus
1. status:
2. availableReplicas: 10
3. fullyLabeledReplicas: 10
4. observedGeneration: 1
5. readyReplicas: 10
6. replicas: 10
k8s.io/kubernetes/pkg/controller/replicaset/replica_set_utils.go:85
26. }
cond := NewReplicaSetCondition(apps.ReplicaSetReplicaFailure,
27. v1.ConditionTrue, reason, manageReplicasErr.Error())
28. SetCondition(&newStatus, cond)
29. } else if manageReplicasErr == nil && failureCond != nil {
30. RemoveCondition(&newStatus, apps.ReplicaSetReplicaFailure)
31. }
32.
33. newStatus.Replicas = int32(len(filteredPods))
34. newStatus.FullyLabeledReplicas = int32(fullyLabeledReplicasCount)
35. newStatus.ReadyReplicas = int32(readyReplicasCount)
36. newStatus.AvailableReplicas = int32(availableReplicasCount)
37. return newStatus
38. }
expectations 机制
1、与 rs 相关的:AddRS、UpdateRS、DeleteRS;
2、与 pod 相关的:AddPod、UpdatePod、DeletePod;
3、informer 二级缓存的同步;
总结
本文主要从源码层面分析了 replicaSetController 的设计与实现,但是不得不说其在设计方面考
虑了很多因素,文中只提到了笔者理解了或者思考后稍有了解的一些机制,至于其他设计思想还得自行
阅读代码体会。
下面以一个流程图总结下创建 rs 的主要流程。
1. SatisfiedExpectations
2. (expectations 中不存在
3. rsKey,rsNeedsSync
4. 为 true)
5. | 判断 add/del pod
6. | |
7. | ∨
| 创建 expectations 对
8. 象,
9. | 并设置 add/del 值
10. ∨ |
11. create rs --> syncReplicaSet --> manageReplicas --> ∨
(为 rs 创建 pod) 调用
12. slowStartBatch 批量创建 pod/
| 删除筛选出的多余
13. pod
14. | |
15. | ∨
| 更新 expectations
16. 对象
17. ∨
18. updateReplicaSetStatus
19. (更新 rs 的 status
20. subResource)
参考:
https://keyla.vip/k8s/3-master/controller/replica-set/
kube-scheduler 的设计
Kube-scheduler 是 kubernetes 的核心组件之一,也是所有核心组件之间功能比较单一的,其代
码也相对容易理解。kube-scheduler 的目的就是为每一个 pod 选择一个合适的 node,整体流程
可以概括为三步,获取未调度的 podList,通过执行一系列调度算法为 pod 选择一个合适的
node,提交数据到 apiserver,其核心则是一系列调度算法的设计与执行。
33.
34. Priority function: node 1: p=2
35. node 2: p=5
36.
37. +-------------------+-------------------------+
38. |
39. |
40. v
41. select max{node priority} = node 2
kube-scheduler 源码分析
kubernetes 中所有组件的启动流程都是类似的,首先会解析命令行参数、添加默认值,kube-
scheduler 的默认参数在
k8s.io/kubernetes/pkg/scheduler/apis/config/v1alpha1/defaults.go 中定义的。然后会执
行 run 方法启动主逻辑,下面直接看 kube-scheduler 的主逻辑 run 方法执行过程。
Run() 方法主要做了以下工作:
初始化 scheduler 对象
启动 kube-scheduler server,kube-scheduler 监听 10251 和 10259 端口,10251
端口不需要认证,可以获取 healthz metrics 等信息,10259 为安全端口,需要认证
启动所有的 informer
执行 sched.Run() 方法,执行主调度逻辑
k8s.io/kubernetes/cmd/kube-scheduler/app/server.go:160
cc.LeaderElectionBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface
14. cc.CoreEventClient.Events("")})
15. }
16.
17. ......
18. // 3、启动 http server
19. if cc.InsecureServing != nil {
20. separateMetrics := cc.InsecureMetricsServing != nil
handler := buildHandlerChain(newHealthzHandler(&cc.ComponentConfig,
21. separateMetrics, checks...), nil, nil)
22. if err := cc.InsecureServing.Serve(handler, 0, stopCh); err != nil {
23. return fmt.Errorf("failed to start healthz server: %v", err)
24. }
25. }
26. ......
27. // 4、启动所有 informer
28. go cc.PodInformer.Informer().Run(stopCh)
29. cc.InformerFactory.Start(stopCh)
30.
31. cc.InformerFactory.WaitForCacheSync(stopCh)
32.
33. run := func(ctx context.Context) {
34. sched.Run()
35. <-ctx.Done()
36. }
37.
ctx, cancel := context.WithCancel(context.TODO()) // TODO once Run()
38. accepts a context, it should be used here
39. defer cancel()
40. go func() {
41. select {
42. case <-stopCh:
43. cancel()
44. case <-ctx.Done():
45. }
46. }()
47.
48. // 5、选举 leader
创建 scheduler 配置文件
根据默认的 DefaultProvider 初始化 schedulerAlgorithmSource 然后加载默认的预选
及优选算法,然后初始化 GenericScheduler
若启动参数提供了 policy config 则使用其覆盖默认的预选及优选算法并初始化
GenericScheduler,不过该参数现已被弃用
k8s.io/kubernetes/pkg/scheduler/scheduler.go:166
k8s.io/kubernetes/pkg/scheduler/factory/factory.go:527
k8s.io/kubernetes/pkg/scheduler/scheduler.go:313
k8s.io/kubernetes/pkg/scheduler/scheduler.go:515
25. ......
26. }
27. ......
28. metrics.PodScheduleFailures.Inc()
29. } else {
30. klog.Errorf("error selecting node for pod: %v", err)
31. metrics.PodScheduleErrors.Inc()
32. }
33. return
34. }
35. ......
36. assumedPod := pod.DeepCopy()
37.
38. // 4.判断是否需要 VolumeScheduling 特性
allBound, err := sched.assumeVolumes(assumedPod,
39. scheduleResult.SuggestedHost)
40. if err != nil {
41. klog.Errorf("error assuming volumes: %v", err)
42. metrics.PodScheduleErrors.Inc()
43. return
44. }
45.
46. // 5.执行 "reserve" plugins
if sts := fwk.RunReservePlugins(pluginContext, assumedPod,
47. scheduleResult.SuggestedHost); !sts.IsSuccess() {
48. .....
49. }
50.
51. // 6.为 pod 设置 NodeName 字段,更新 scheduler 缓存
52. err = sched.assume(assumedPod, scheduleResult.SuggestedHost)
53. if err != nil {
54. ......
55. }
56.
57. // 7.异步请求 apiserver
58. go func() {
59. // Bind volumes first before Pod
60. if !allBound {
61. err := sched.bindVolumes(assumedPod)
62. if err != nil {
63. ......
64. return
65. }
66. }
67.
68. // 8.执行 "permit" plugins
permitStatus := fwk.RunPermitPlugins(pluginContext, assumedPod,
69. scheduleResult.SuggestedHost)
70. if !permitStatus.IsSuccess() {
71. ......
72. }
73. // 9.执行 "prebind" plugins
preBindStatus := fwk.RunPreBindPlugins(pluginContext, assumedPod,
74. scheduleResult.SuggestedHost)
75. if !preBindStatus.IsSuccess() {
76. ......
77. }
err := sched.bind(assumedPod, scheduleResult.SuggestedHost,
78. pluginContext)
79. ......
80. if err != nil {
81. ......
82. } else {
83. ......
84. // 10.执行 "postbind" plugins
fwk.RunPostBindPlugins(pluginContext, assumedPod,
85. scheduleResult.SuggestedHost)
86. }
87. }()
88. }
k8s.io/kubernetes/pkg/scheduler/scheduler.go:337
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:131
Schedule() :正常调度逻辑,包含预算与优选算法的执行
Preempt() :抢占策略,在 pod 调度发生失败的时候尝试抢占低优先级的 pod,函数返回发
生抢占的 node,被 抢占的 pods 列表,nominated node name 需要被移除的 pods 列表
以及 error
Predicates() :predicates 算法列表
Prioritizers() :prioritizers 算法列表
pkg/scheduler/algorithmprovider/defaults/defaults.go
17. )
18. }
19.
20. func defaultPriorities() sets.String {
21. return sets.NewString(
22. priorities.SelectorSpreadPriority,
23. priorities.InterPodAffinityPriority,
24. priorities.LeastRequestedPriority,
25. priorities.BalancedResourceAllocation,
26. priorities.NodePreferAvoidPodsPriority,
27. priorities.NodeAffinityPriority,
28. priorities.TaintTolerationPriority,
29. priorities.ImageLocalityPriority,
30. )
31. }
检查 pod pvc 信息
执行 prefilter plugins
获取 scheduler cache 的快照,每次调度 pod 时都会获取一次快照
执行 g.findNodesThatFit() 预选算法
执行 postfilter plugin
若 node 为 0 直接返回失败的 error,若 node 数为1 直接返回该 node
执行 g.priorityMetaProducer() 获取 metaPrioritiesInterface,计算 pod 的
metadata,检查该 node 上是否有相同 meta 的 pod
执行 PrioritizeNodes() 算法
执行 g.selectHost() 通过得分选择一个最佳的 node
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:186
至此,scheduler 的整个过程分析完毕。
总结
本文主要对于 kube-scheduler v1.16 的调度流程进行了分析,但其中有大量的细节都暂未提及,
包括预选算法以及优选算法的具体实现、优先级与抢占调度的实现、framework 的使用及实现,因篇
幅有限,部分内容会在后文继续说明。
参考:
predicates 调度算法源码分析
predicates 算法主要是对集群中的 node 进行过滤,选出符合当前 pod 运行的 nodes。
调度算法说明
上节已经提到默认的调度算法在 pkg/scheduler/algorithmprovider/defaults/defaults.go 中定
义了:
下面是对默认调度算法的一些说明:
predicates 算法 说明
GeneralPred 包含 PodFitsResources、PodFitsHost,、
GeneralPred
PodFitsHostPorts、PodMatchNodeSelector 四种算法
CheckNodeConditionPred 检查 NodeCondition
CheckNodePIDPressurePred 检查 NodePIDPressure
CheckNodeDiskPressurePred 检查 NodeDiskPressure
CheckNodeMemoryPressurePred 检查 NodeMemoryPressure
3、第三种类型是宿主机相关的过滤规则,主要是 PodToleratesNodeTaintsPred。
5、第五种类型是新增的过滤规则,与宿主机的运行状况有关,主要有 CheckNodeCondition、
CheckNodeMemoryPressure、CheckNodePIDPressure、CheckNodeDiskPressure 四种。若
启用了 TaintNodesByCondition FeatureGates 则在 predicates 算法中会将该四种算法移
predicates 调度算法也有一个顺序,要不然在一台资源已经严重不足的宿主机上,上来就开始计算
PodAffinityPredicate 是没有实际意义的,其默认顺序如下所示:
k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go:146
1. var (
predicatesOrdering = []string{CheckNodeConditionPred,
2. CheckNodeUnschedulablePred,
3. GeneralPred, HostNamePred, PodFitsHostPortsPred,
4. MatchNodeSelectorPred, PodFitsResourcesPred, NoDiskConflictPred,
PodToleratesNodeTaintsPred, PodToleratesNodeNoExecuteTaintsPred,
5. CheckNodeLabelPresencePred,
CheckServiceAffinityPred, MaxEBSVolumeCountPred,
6. MaxGCEPDVolumeCountPred, MaxCSIVolumeCountPred,
MaxAzureDiskVolumeCountPred, MaxCinderVolumeCountPred,
7. CheckVolumeBindingPred, NoVolumeZoneConflictPred,
CheckNodeMemoryPressurePred, CheckNodePIDPressurePred,
8. CheckNodeDiskPressurePred, EvenPodsSpreadPred, MatchInterPodAffinityPred}
9. )
源码分析
上节中已经说到调用预选以及优选算法的逻辑在
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:189 中,
11.
12. ......
13.
14. return
15. }
设定最多需要检查的节点数,作为预选节点数组的容量,避免总节点过多影响调度效率
通过 NodeTree() 不断获取下一个节点来判断该节点是否满足 pod 的调度条件
通过之前注册的各种 predicates 函数来判断当前节点是否符合 pod 的调度条件
最后返回满足调度条件的 node 列表,供下一步的优选操作
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:464
15.
16. // 2.获取该 pod 的 meta 值
17. meta := g.predicateMetaProducer(pod, g.nodeInfoSnapshot.NodeInfoMap)
18.
19. // 3.checkNode 为执行预选算法的函数
20. checkNode := func(i int) {
21. nodeName := g.cache.NodeTree().Next()
22.
23. // 4.podFitsOnNode 最终执行预选算法的函数
24. fits, failedPredicates, status, err := g.podFitsOnNode(
25. ......
26. )
27. if err != nil {
28. ......
29. }
30. if fits {
31. length := atomic.AddInt32(&filteredLen, 1)
32. if length > numNodesToFind {
33. cancel()
34. atomic.AddInt32(&filteredLen, -1)
35. } else {
filtered[length-1] =
36. g.nodeInfoSnapshot.NodeInfoMap[nodeName].Node()
37. }
38. } else {
39. ......
40. }
41. }
42.
43. // 5.启动 16 个 goroutine 并发执行 checkNode 函数
44. workqueue.ParallelizeUntil(ctx, 16, int(allNodes), checkNode)
45.
46. filtered = filtered[:filteredLen]
47. if len(errs) > 0 {
48. ......
49. }
50. }
51.
52. // 6.若配置了 extender 则再次进行过滤
53. if len(filtered) > 0 && len(g.extenders) != 0 {
54. ......
55. }
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:441
因为引入了抢占机制,此处主要说明一下执行两次预选函数的原因:
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:610
priorities 调度算法源码分析
priorities 调度算法是在 pridicates 算法后执行的,主要功能是对已经过滤出的 nodes 进行
打分并选出最佳的一个 node。
调度算法说明
默认调度算法的一些说明:
priorities 算法 说明
选择空闲资源(CPU 和 Memory)最多的节点,默认权重为1,其计算方
式为:score = (cpu((capacity-
LeastRequestedPriority
sum(requested))10/capacity) + memory((capacity-
sum(requested))10/capacity))/2
NodeAffinityPriority 节点亲和性选择策略,默认权重为1
源码分析
PrioritizeNodes() 通过并行运行各个优先级函数来对节点进行打分
每个优先级函数会给节点打分,打分范围为 0-10 分,0 表示优先级最低的节点,10表示优先
级最高的节点
每个优先级函数有各自的权重
优先级函数返回的节点分数乘以权重以获得加权分数
最后计算所有节点的总加权分数
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:691
34. })
35.
36. // 3.执行自定义配置
37. for i := range priorityConfigs {
38. ......
39. }
40.
41. wg.Wait()
42. if len(errs) != 0 {
43. return schedulerapi.HostPriorityList{}, errors.NewAggregate(errs)
44. }
45.
46. // 4.运行 Score plugins
scoresMap, scoreStatus := framework.RunScorePlugins(pluginContext, pod,
47. nodes)
48. if !scoreStatus.IsSuccess() {
49. return schedulerapi.HostPriorityList{}, scoreStatus.AsError()
50. }
51.
52. result := make(schedulerapi.HostPriorityList, 0, len(nodes))
53. // 5.为每个 node 汇总分数
54. for i := range nodes {
result = append(result, schedulerapi.HostPriority{Host: nodes[i].Name,
55. Score: 0})
56. for j := range priorityConfigs {
57. result[i].Score += results[j][i].Score * priorityConfigs[j].Weight
58. }
59.
60. for j := range scoresMap {
61. result[i].Score += scoresMap[j][i].Score
62. }
63. }
64.
65. // 6.执行 extender
66. if len(extenders) != 0 && nodes != nil {
67. ......
68. }
69. ......
70. return result, nil
71. }
总结
参考:
https://kubernetes.io/docs/concepts/configuration/scheduling-framework/
predicates-ordering.md
kube-scheduler 源码分析
kube-scheduler predicates 与 priorities 调度算法源码分析
为什么要有优先级与抢占机制
正常情况下,当一个 pod 调度失败后,就会被暂时 “搁置” 处于 pending 状态,直到 pod 被
更新或者集群状态发生变化,调度器才会对这个 pod 进行重新调度。但在实际的业务场景中会存在在
线与离线业务之分,若在线业务的 pod 因资源不足而调度失败时,此时就需要离线业务下掉一部分为
在线业务提供资源,即在线业务要抢占离线业务的资源,此时就需要 scheduler 的优先级和抢占机
制了,该机制解决的是 pod 调度失败时该怎么办的问题,若该 pod 的优先级比较高此时并不会
被”搁置”,而是会”挤走”某个 node 上的一些低优先级的 pod,这样就可以保证高优先级的 pod 调
度成功。
优先级与抢占机制源码分析
k8s.io/kubernetes/pkg/scheduler/internal/queue/scheduling_queue.go
4. }
5. // NewPriorityQueue creates a PriorityQueue object.
func NewPriorityQueue(stop <-chan struct{}, fwk framework.Framework)
6. *PriorityQueue {
7. return NewPriorityQueueWithClock(stop, util.RealClock{}, fwk)
8. }
9.
// NewPriorityQueueWithClock creates a PriorityQueue which uses the passed
10. clock for time.
func NewPriorityQueueWithClock(stop <-chan struct{}, clock util.Clock, fwk
11. framework.Framework) *PriorityQueue {
12. comp := activeQComp
13. if fwk != nil {
14. if queueSortFunc := fwk.QueueSortFunc(); queueSortFunc != nil {
15. comp = func(podInfo1, podInfo2 interface{}) bool {
16. pInfo1 := podInfo1.(*framework.PodInfo)
17. pInfo2 := podInfo2.(*framework.PodInfo)
18.
19. return queueSortFunc(pInfo1, pInfo2)
20. }
21. }
22. }
23.
24. pq := &PriorityQueue{
25. clock: clock,
26. stop: stop,
27. podBackoff: NewPodBackoffMap(1*time.Second, 10*time.Second),
activeQ: util.NewHeapWithRecorder(podInfoKeyFunc, comp,
28. metrics.NewActivePodsRecorder()),
unschedulableQ:
29. newUnschedulablePodsMap(metrics.NewUnschedulablePodsRecorder()),
30. nominatedPods: newNominatedPodMap(),
31. moveRequestCycle: -1,
32. }
33. pq.cond.L = &pq.lock
pq.podBackoffQ = util.NewHeapWithRecorder(podInfoKeyFunc,
34. pq.podsCompareBackoffCompleted, metrics.NewBackoffPodsRecorder())
35.
36. pq.run()
37.
38. return pq
39. }
k8s.io/kubernetes/pkg/scheduler/scheduler.go:516
k8s.io/kubernetes/pkg/scheduler/scheduler.go:352
23. sched.SchedulingQueue.DeleteNominatedPodIfExists(preemptor)
24. return "", err
25. }
26. // 删除被抢占的 pods
27. for _, victim := range victims {
28. if err := sched.PodPreemptor.DeletePod(victim); err != nil {
29. return "", err
30. }
31. ......
32. }
33. }
34.
35. // 删除被抢占 pods 的 NominatedNodeName 字段
36. for _, p := range nominatedPodsToClear {
37. rErr := sched.PodPreemptor.RemoveNominatedNodeName(p)
38. if rErr != nil {
39. ......
40. }
41. }
42. return nodeName, err
43. }
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:320
该函数中调用了多个函数:
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:996
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:1086
7.
8. potentialVictims := util.SortableList{CompFunc: util.MoreImportantPod}
9. nodeInfoCopy := nodeInfo.Clone()
10.
11. removePod := func(rp *v1.Pod) {
12. nodeInfoCopy.RemovePod(rp)
13. if meta != nil {
14. meta.RemovePod(rp, nodeInfoCopy.Node())
15. }
16. }
17. addPod := func(ap *v1.Pod) {
18. nodeInfoCopy.AddPod(ap)
19. if meta != nil {
20. meta.AddPod(ap, nodeInfoCopy)
21. }
22. }
23. // 先删除所有的低优先级 pod 检查是否能满足抢占 pod 的调度需求
24. podPriority := util.GetPodPriority(pod)
25. for _, p := range nodeInfoCopy.Pods() {
26. if util.GetPodPriority(p) < podPriority {
27. potentialVictims.Items = append(potentialVictims.Items, p)
28. removePod(p)
29. }
30. }
31. // 如果删除所有低优先级的 pod 不符合要求则直接过滤掉该 node
32. // podFitsOnNode 就是前文讲过用来执行预选函数的
if fits, _, _, err := g.podFitsOnNode(pluginContext, pod, meta,
33. nodeInfoCopy, fitPredicates, queue, false); !fits {
34. if err != nil {
35. ......
36. }
37. return nil, 0, false
38. }
39. var victims []*v1.Pod
40. numViolatingVictim := 0
41. potentialVictims.Sort()
42.
// 尝试尽量多地“删除”这些 pods,先从 PDB violating victims 中“删除”,再从 PDB
43. non-violating victims 中“删除”
violatingVictims, nonViolatingVictims :=
44. filterPodsWithPDBViolation(potentialVictims.Items, pdbs)
45.
46. // reprievePod 是“删除” pods 的函数
k8s.io/kubernetes/pkg/scheduler/core/generic_scheduler.go:867
func pickOneNodeForPreemption(nodesToVictims
1. map[*v1.Node]*schedulerapi.Victims) *v1.Node {
2. if len(nodesToVictims) == 0 {
3. return nil
4. }
5. minNumPDBViolatingPods := math.MaxInt32
以上就是对抢占机制代码的一个通读。
优先级与抢占机制的使用
1、创建 PriorityClass 对象:
1. apiVersion: scheduling.k8s.io/v1
2. kind: PriorityClass
3. metadata:
4. name: high-priority
5. value: 1000000
6. globalDefault: false
7. description: "This priority class should be used for XYZ service pods only."
在 pod 中使用:
1. apiVersion: v1
2. kind: Pod
3. metadata:
4. labels:
5. app: nginx-a
6. name: nginx-a
7. spec:
8. containers:
9. - image: nginx:1.7.9
10. imagePullPolicy: IfNotPresent
11. name: nginx-a
12. ports:
13. - containerPort: 80
14. protocol: TCP
15. resources:
16. requests:
17. memory: "64Mi"
18. cpu: 5
19. limits:
20. memory: "128Mi"
21. cpu: 5
22. priorityClassName: high-priority
在 deployment 中使用:
1. template:
2. spec:
3. containers:
4. - image: nginx
5. name: nginx-deployment
6. priorityClassName: high-priority
总结
这篇文章主要讲述 kube-scheduler 中的优先级与抢占机制,可以看到抢占机制比 predicates
与 priorities 算法都要复杂,其中的许多细节仍然没有提到,本文只是通读了大部分代码,某些代
码的实现需要精读,限于笔者时间的关系,对于 kube-scheduler 的代码暂时分享到此处。
参考:
https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
为什么需要 service
在 kubernetes 中,当创建带有多个副本的 deployment 时,kubernetes 会创建出多个 pod,
此时即一个服务后端有多个容器,那么在 kubernetes 中负载均衡怎么做,容器漂移后 ip 也会发
生变化,如何做服务发现以及会话保持?这就是 service 的作用,service 是一组具有相同
label pod 集合的抽象,集群内外的各个服务可以通过 service 进行互相通信,当创建一个
service 对象时也会对应创建一个 endpoint 对象,endpoint 是用来做容器发现的,service
只是将多个 pod 进行关联,实际的路由转发都是由 kubernetes 中的 kube-proxy 组件来实现,
因此,service 必须结合 kube-proxy 使用,kube-proxy 组件可以运行在 kubernetes 集群
中的每一个节点上也可以只运行在单独的几个节点上,其会根据 service 和 endpoints 的变动来
改变节点上 iptables 或者 ipvs 中保存的路由规则。
service 的工作原理
service 的负载均衡
上文已经提到 service 实际的路由转发都是由 kube-proxy 组件来实现的,service 仅以一种
VIP(ClusterIP) 的形式存在,kube-proxy 主要实现了集群内部从 pod 到 service 和集群
外部从 nodePort 到 service 的访问,kube-proxy 的路由转发规则是通过其后端的代理模块实
现的,kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs、
kernelspace,其发展历程如下所示:
在每种模式下都有自己的负载均衡策略,下文会详解介绍。
userspace 模式
iptables 模式
ipvs 模式
当集群规模比较大时,iptables 规则刷新会非常慢,难以支持大规模集群,因其底层路由表的实现是
链表,对路由规则的增删改查都要涉及遍历一次链表,ipvs 的问世正是解决此问题的,ipvs 是 LVS
的负载均衡模块,与 iptables 比较像的是,ipvs 的实现虽然也基于 netfilter 的钩子函数,
但是它却使用哈希表作为底层的数据结构并且工作在内核态,也就是说 ipvs 在重定向流量和同步代
理规则有着更好的性能,几乎允许无限的规模扩张。
此外,ipvs 也支持更多的负载均衡算法,例如:
rr:round-robin/轮询
lc:least connection/最少连接
dh:destination hashing/目标哈希
sh:source hashing/源哈希
service 的类型
service 支持的类型也就是 kubernetes 中服务暴露的方式,默认有四种 ClusterIP、
NodePort、LoadBalancer、ExternelName,此外还有 Ingress,下面会详细介绍每种类型
service 的具体使用场景。
ClusterIP
NodePort
LoadBalancer
ExternelName
Ingress
Ingress 的结构如下图所示:
service 的服务发现
虽然 service 的 endpoints 解决了容器发现问题,但不提前知道 service 的 Cluster IP,
怎么发现 service 服务呢?service 当前支持两种类型的服务发现机制,一种是通过环境变量,另
一种是通过 DNS。在这两种方案中,建议使用后者。
环境变量
DNS
service 的使用
ClusterIP 方式
1. apiVersion: v1
2. kind: Service
3. metadata:
4. name: my-nginx
5. spec:
6. clusterIP: 10.105.146.177
7. ports:
8. - port: 80
9. protocol: TCP
10. targetPort: 8080
11. selector:
12. app: my-nginx
13. sessionAffinity: None
14. type: ClusterIP
NodePort 方式
1. apiVersion: v1
2. kind: Service
3. metadata:
4. name: my-nginx
5. spec:
6. ports:
7. - nodePort: 30090
8. port: 80
9. protocol: TCP
10. targetPort: 8080
11. selector:
12. app: my-nginx
13. sessionAffinity: None
14. type: NodePort
1. apiVersion: v1
2. kind: Service
3. metadata:
4. name: my-nginx
5. spec:
6. clusterIP: None
7. ports:
8. - nodePort: 30090
9. port: 80
10. protocol: TCP
11. targetPort: 8080
12. selector:
13. app: my-nginx
总结
本文主要讲了 kubernetes 中 service 的原理、实现以及使用方式,service 目前主要有 5 种
服务暴露方式,service 的容器发现是通过 endpoints 来实现的,其服务发现主要是通过 DNS 实
现的,其负载均衡以及流量转发是通过 kube-proxy 实现的。在后面的文章我会继续介绍 kube-
proxy 的设计及实现。
参考:
https://www.cnblogs.com/xzkzzz/p/9559362.html
https://xigang.github.io/2019/07/21/kubernetes-service/
kube-proxy 启动流程
前面的文章已经说过 kubernetes 中所有组件都是通过其 run() 方法启动主逻辑的, run()
方法调用之前会进行解析命令行参数、添加默认值等。下面就直接看 kube-proxy 的 run() 方
法:
k8s.io/kubernetes/cmd/kube-proxy/app/server.go:290
k8s.io/kubernetes/cmd/kube-proxy/app/server_others.go:57
34.
35. // 4.检查该机器是否支持使用 ipvs 模式
36. canUseIPVS, _ := ipvs.CanUseIPVSProxier(kernelHandler, ipsetInterface)
37. if canUseIPVS {
38. ipvsInterface = utilipvs.New(execer)
39. }
40.
41. if cleanupAndExit {
42. return &ProxyServer{
43. ......
44. }, nil
45. }
46.
47. // 5.初始化 kube client 和 event client
48. client, eventClient, err := createClients(config.ClientConnection, master)
49. if err != nil {
50. return nil, err
51. }
52. ......
53.
54. // 6.初始化 healthzServer
55. var healthzServer *healthcheck.HealthzServer
56. var healthzUpdater healthcheck.HealthzUpdater
57. if len(config.HealthzBindAddress) > 0 {
healthzServer =
healthcheck.NewDefaultHealthzServer(config.HealthzBindAddress,
58. 2*config.IPTables.SyncPeriod.Duration, recorder, nodeRef)
59. healthzUpdater = healthzServer
60. }
61.
62. // 7.proxier 是一个 interface,每种模式都是一个 proxier
63. var proxier proxy.Provider
64.
65. // 8.根据 proxyMode 初始化 proxier
proxyMode := getProxyMode(string(config.Mode), kernelHandler,
66. ipsetInterface, iptables.LinuxKernelCompatTester{})
67. ......
68.
69. if proxyMode == proxyModeIPTables {
70. klog.V(0).Info("Using iptables Proxier.")
71. if config.IPTables.MasqueradeBit == nil {
return nil, fmt.Errorf("unable to read IPTables MasqueradeBit from
72. config")
73. }
74.
75. // 9.初始化 iptables 模式的 proxier
76. proxier, err = iptables.NewProxier(
77. .......
78. )
79. if err != nil {
80. return nil, fmt.Errorf("unable to create proxier: %v", err)
81. }
82. metrics.RegisterMetrics()
83. } else if proxyMode == proxyModeIPVS {
84. // 10.判断是够启用了 ipv6 双栈
85. if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
86. ......
87. // 11.初始化 ipvs 模式的 proxier
88. proxier, err = ipvs.NewDualStackProxier(
89. ......
90. )
91. } else {
92. proxier, err = ipvs.NewProxier(
93. ......
94. )
95. }
96. if err != nil {
97. return nil, fmt.Errorf("unable to create proxier: %v", err)
98. }
99. metrics.RegisterMetrics()
100. } else {
101. // 12.初始化 userspace 模式的 proxier
102. proxier, err = userspace.NewProxier(
103. ......
104. )
105. if err != nil {
106. return nil, fmt.Errorf("unable to create proxier: %v", err)
107. }
108. }
109.
110. iptInterface.AddReloadFunc(proxier.Sync)
111. return &ProxyServer{
112. ......
113. }, nil
114. }
k8s.io/kubernetes/cmd/kube-proxy/app/server.go:311
o.proxyServer.Run() 中会启动已经初始化好的所有服务:
k8s.io/kubernetes/cmd/kube-proxy/app/server.go:527
45. }
46. }
if s.ConntrackConfiguration.TCPCloseWaitTimeout != nil &&
47. s.ConntrackConfiguration.TCPCloseWaitTimeout.Duration > 0 {
timeout :=
48. int(s.ConntrackConfiguration.TCPCloseWaitTimeout.Duration / time.Second)
if err := s.Conntracker.SetTCPCloseWaitTimeout(timeout); err != nil
49. {
50. return err
51. }
52. }
53. }
54.
55. ......
56.
57. // 5.启动 informer 监听 Services 和 Endpoints 或者 EndpointSlices 信息
informerFactory := informers.NewSharedInformerFactoryWithOptions(s.Client,
58. s.ConfigSyncPeriod,
59. informers.WithTweakListOptions(func(options *metav1.ListOptions) {
60. options.LabelSelector = labelSelector.String()
61. }))
62.
63.
64. // 6.将 proxier 注册到 serviceConfig、endpointsConfig 中
serviceConfig :=
config.NewServiceConfig(informerFactory.Core().V1().Services(),
65. s.ConfigSyncPeriod)
66. serviceConfig.RegisterEventHandler(s.Proxier)
67. go serviceConfig.Run(wait.NeverStop)
68.
69. if utilfeature.DefaultFeatureGate.Enabled(features.EndpointSlice) {
endpointSliceConfig :=
config.NewEndpointSliceConfig(informerFactory.Discovery().V1alpha1().EndpointSlices
70. s.ConfigSyncPeriod)
71. endpointSliceConfig.RegisterEventHandler(s.Proxier)
72. go endpointSliceConfig.Run(wait.NeverStop)
73. } else {
endpointsConfig :=
config.NewEndpointsConfig(informerFactory.Core().V1().Endpoints(),
74. s.ConfigSyncPeriod)
75. endpointsConfig.RegisterEventHandler(s.Proxier)
76. go endpointsConfig.Run(wait.NeverStop)
77. }
78.
79. // 7.启动 informer
80. informerFactory.Start(wait.NeverStop)
81.
82. s.birthCry()
83.
84. // 8.启动 proxier 主循环
85. s.Proxier.SyncLoop()
86. return nil
87. }
回顾一下整个启动逻辑:
proxier 的初始化
看完了启动流程的逻辑代码,接着再看一下各代理模式的初始化,上文已经提到每种模式都是一个
proxier,即要实现 proxy.Provider 对应的 interface,如下所示:
k8s.io/kubernetes/pkg/proxy/iptables/proxier.go:249
31.
32. // 4.初始化 syncRunner,BoundedFrequencyRunner 是一个定时执行器,会定时执行
// proxier.syncProxyRules 方法,syncProxyRules 是每个 proxier 实际刷新iptables
33. 规则的方法
proxier.syncRunner = async.NewBoundedFrequencyRunner("sync-runner",
34. proxier.syncProxyRules, minSyncPeriod, syncPeriod, burstSyncs)
35. return proxier, nil
36. }
k8s.io/kubernetes/pkg/proxy/ipvs/proxier.go:316
初始化 iptables 规则
初始化 proxier
初始化 syncRunner 将 proxier.syncProxyRules 方法注入
k8s.io/kubernetes/pkg/proxy/userspace/proxier.go:187
3. }
4.
5. func NewCustomProxier(......) (*Proxier, error) {
6. ......
7.
8. // 1.设置打开文件数
9. err = setRLimit(64 * 1000)
10. if err != nil {
return nil, fmt.Errorf("failed to set open file handler limit: %v",
11. err)
12. }
13.
14. proxyPorts := newPortAllocator(pr)
15.
return createProxier(loadBalancer, listenIP, iptables, exec, hostIP,
16. proxyPorts, syncPeriod, minSyncPeriod, udpIdleTimeout, makeProxySocket)
17. }
18.
func createProxier(loadBalancer LoadBalancer, listenIP net.IP, iptables
iptables.Interface, exec utilexec.Interface, hostIP net.IP, proxyPorts
PortAllocator, syncPeriod, minSyncPeriod, udpIdleTimeout time.Duration,
19. makeProxySocket ProxySocketFunc) (*Proxier, error) {
20. if proxyPorts == nil {
21. proxyPorts = newPortAllocator(utilnet.PortRange{})
22. }
23.
24. // 2.初始化 iptables 规则
25. if err := iptablesInit(iptables); err != nil {
26. return nil, fmt.Errorf("failed to initialize iptables: %v", err)
27. }
28.
29. if err := iptablesFlush(iptables); err != nil {
30. return nil, fmt.Errorf("failed to flush iptables: %v", err)
31. }
32.
33. // 3.初始化 proxier
34. proxier := &Proxier{
35. ......
36. }
37.
38. // 4.初始化 syncRunner
proxier.syncRunner = async.NewBoundedFrequencyRunner("userspace-proxy-sync-
39. runner", proxier.syncProxyRules, minSyncPeriod, syncPeriod, numBurstSyncs)
proxier 接口实现
handler 的实现
上文已经提到过每种 proxier 都需要实现 interface 中的几个方法,首先看一下
ServiceHandler 、 EndpointsHandler 和 EndpointSliceHandler 相关的,对于
service、endpoints 和 endpointSlices 三种对象都实现了
OnAdd 、 OnUpdate 、 OnDelete 和 OnSynced 方法。
1. // 1.service 相关的方法
2. func (proxier *Proxier) OnServiceAdd(service *v1.Service) {
3. proxier.OnServiceUpdate(nil, service)
4. }
5.
6. func (proxier *Proxier) OnServiceUpdate(oldService, service *v1.Service) {
if proxier.serviceChanges.Update(oldService, service) &&
7. proxier.isInitialized() {
8. proxier.syncRunner.Run()
9. }
10. }
11.
12. func (proxier *Proxier) OnServiceDelete(service *v1.Service) {
13. proxier.OnServiceUpdate(service, nil)
14. }
15.
16. func (proxier *Proxier) OnServiceSynced(){
17. ......
18. proxier.syncProxyRules()
19. }
20.
21. // 2.endpoints 相关的方法
22. func (proxier *Proxier) OnEndpointsAdd(endpoints *v1.Endpoints) {
23. proxier.OnEndpointsUpdate(nil, endpoints)
24. }
25.
func (proxier *Proxier) OnEndpointsUpdate(oldEndpoints, endpoints
26. *v1.Endpoints) {
63. }
k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go:134
k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go:169
k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go:191
k8s.io/kubernetes/pkg/util/async/bounded_frequency_runner.go:211
9. bfr.timer.Stop()
10. bfr.timer.Reset(bfr.maxInterval)
11. return
12. }
13.
14. elapsed := bfr.timer.Since(bfr.lastRun) // how long since last run
15. nextPossible := bfr.minInterval - elapsed // time to next possible run
16. nextScheduled := bfr.maxInterval - elapsed // time to next periodic run
17.
18. if nextPossible < nextScheduled {
19. bfr.timer.Stop()
20. bfr.timer.Reset(nextPossible)
21. }
22. }
总结
本文主要介绍了 kube-proxy 的启动逻辑以及三种模式 proxier 的初始化,还有最终调用刷新
iptables 规则的 BoundedFrequencyRunner,可以看到其中的代码写的很巧妙。而每种模式下的
iptables 规则是如何创建、刷新以及转发的是如何实现的会在后面的文章中进行分析。
iptables 的功能
在前面的文章中已经介绍过 iptable 的一些基本信息,本文会深入介绍 kube-proxy iptables
模式下的工作原理,本文中多处会与 iptables 的知识相关联,若没有 iptables 基础,请先自行
补充。
iptables 的功能:
流量转发:DNAT 实现 IP 地址和端口的映射;
负载均衡:statistic 模块为每个后端设置权重;
会话保持:recent 模块设置会话保持时间;
iptables 有五张表和五条链,五条链分别对应为:
五张表分别为:
filter 表:用于控制到达某条链上的数据包是继续放行、直接丢弃(drop)还是拒绝
(reject);
nat 表:network address translation 网络地址转换,用于修改数据包的源地址和目的
地址;
mangle 表:用于修改数据包的 IP 头信息;
raw 表:iptables 是有状态的,其对数据包有链接追踪机制,连接追踪信息在
/proc/net/nf_conntrack 中可以看到记录,而 raw 是用来去除链接追踪机制的;
security 表:最不常用的表,用在 SELinux 上;
iptables 的工作流程如下图所示:
kube-proxy 的 iptables 模式
kube-proxy 组件负责维护 node 节点上的防火墙规则和路由规则,在 iptables 模式下,会根据
service 以及 endpoints 对象的改变来实时刷新规则,kube-proxy 使用了 iptables 的
在 nat 表中自定义的链以及追加的链如下所示:
在 filter 表定义的链以及追加的链如下所示如下所示:
kubernetes 自定义链中数据包的详细流转可以参考:
iptables 规则分析
clusterIP 访问方式
访问流程如下所示:
的请求将会被 drop 掉;
1. // 1.
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-
2. SERVICES
3.
4. // 2.
-A KUBE-SERVICES -d 10.110.243.155/32 -p tcp -m comment --comment "pks-
system/tenant-service: cluster IP" -m tcp --dport 7000 -j KUBE-SVC-
5. 5SB6FTEHND4GTL2W
6.
7. // 3.
-A KUBE-SVC-5SB6FTEHND4GTL2W -m statistic --mode random --probability
8. 0.50000000000 -j KUBE-SEP-CI5ZO3FTK7KBNRMG
9. -A KUBE-SVC-5SB6FTEHND4GTL2W -j KUBE-SEP-OVNLTDWFHTHII4SC
10.
11.
12. // 4.
13. -A KUBE-SEP-CI5ZO3FTK7KBNRMG -s 192.168.137.147/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-CI5ZO3FTK7KBNRMG -p tcp -m tcp -j DNAT --to-destination
14. 192.168.137.147:7000
15.
16. -A KUBE-SEP-OVNLTDWFHTHII4SC -s 192.168.98.213/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-OVNLTDWFHTHII4SC -p tcp -m tcp -j DNAT --to-destination
17. 192.168.98.213:7000
nodePort 方式
1、非本机访问
2、本机访问
1. // 1.
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-
2. SERVICES
3.
4. // 2.
5. ......
6. -A KUBE-SERVICES xxx
7. ......
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this
must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-
8. NODEPORTS
9.
10. // 3.
-A KUBE-NODEPORTS -p tcp -m comment --comment "pks-system/tenant-service:" -m
11. tcp --dport 30070 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "pks-system/tenant-service:" -m
12. tcp --dport 30070 -j KUBE-SVC-5SB6FTEHND4GTL2W
13.
14. // 4、
-A KUBE-SVC-5SB6FTEHND4GTL2W -m statistic --mode random --probability
15. 0.50000000000 -j KUBE-SEP-CI5ZO3FTK7KBNRMG
16. -A KUBE-SVC-5SB6FTEHND4GTL2W -j KUBE-SEP-VR562QDKF524UNPV
17.
18. // 5、
19. -A KUBE-SEP-CI5ZO3FTK7KBNRMG -s 192.168.137.147/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-CI5ZO3FTK7KBNRMG -p tcp -m tcp -j DNAT --to-destination
20. 192.168.137.147:7000
iptables 模式源码分析
kubernetes 版本:v1.16
更新proxier.endpointsMap,proxier.servieMap
创建自定义链
将当前内核中 filter 表和 nat 表中的全部规则导入到内存中
为每个 service 创建规则
为 clusterIP 设置访问规则
为 externalIP 设置访问规则
为 ingress 设置访问规则
为 nodePort 设置访问规则
为 endpoint 生成规则链
写入 DNAT 规则
删除不再使用的服务自定义链
使用 iptables-restore 同步规则
k8s.io/kubernetes/pkg/proxy/iptables/proxier.go:677
endpointUpdateResult :=
4. proxier.endpointsMap.Update(proxier.endpointsChanges)
5.
6. staleServices := serviceUpdateResult.UDPStaleClusterIP
7. for _, svcPortName := range endpointUpdateResult.StaleServiceNames {
if svcInfo, ok := proxier.serviceMap[svcPortName]; ok && svcInfo != nil
8. && svcInfo.Protocol() == v1.ProtocolUDP {
9. staleServices.Insert(svcInfo.ClusterIP().String())
10. for _, extIP := range svcInfo.ExternalIPStrings() {
11. staleServices.Insert(extIP)
12. }
13. }
14. }
15. ......
然后创建所需要的 iptable 链:
err := proxier.iptables.SaveInto(utiliptables.TableFilter,
1. proxier.existingFilterChainsData)
2. if err != nil {
3.
4. } else {
existingFilterChains =
utiliptables.GetChainLines(utiliptables.TableFilter,
5. proxier.existingFilterChainsData.Bytes())
6. }
7.
8. ......
err = proxier.iptables.SaveInto(utiliptables.TableNAT,
9. proxier.iptablesData)
10. if err != nil {
11.
12. } else {
existingNATChains = utiliptables.GetChainLines(utiliptables.TableNAT,
13. proxier.iptablesData.Bytes())
14. }
15.
16. writeLine(proxier.filterChains, "*filter")
17. writeLine(proxier.natChains, "*nat")
检查已经创建出的表是否存在:
1. masqRule := []string{
2. ......
3. }
4. if proxier.iptables.HasRandomFully() {
若服务使用了 externalIP,创建对应的规则:
9. }
10. if hasEndpoints {
11. ......
12. } else {
13. ......
14. }
15. }
若服务使用了 ingress,创建对应的规则:
若使用了 nodePort,创建对应的规则:
1. if svcInfo.NodePort() != 0 {
addresses, err :=
utilproxy.GetNodeAddresses(proxier.nodePortAddresses,
2. proxier.networkInterfacer)
3.
4. lps := make([]utilproxy.LocalPort, 0)
5. for address := range addresses {
6. ......
7. lps = append(lps, lp)
8. }
9.
10. for _, lp := range lps {
11. if proxier.portsMap[lp] != nil {
12.
13. } else if svcInfo.Protocol() != v1.ProtocolSCTP {
14. socket, err := proxier.portMapper.OpenLocalPort(&lp)
15. ......
16. if lp.Protocol == "udp" {
17. ......
18. }
19. replacementPortsMap[lp] = socket
20. }
21. }
22. if hasEndpoints {
23. ......
24. } else {
25. ......
26. }
27. }
1. endpoints = endpoints[:0]
2. endpointChains = endpointChains[:0]
3. var endpointChain utiliptables.Chain
4. for _, ep := range proxier.endpointsMap[svcName] {
5. epInfo, ok := ep.(*endpointsInfo)
6. ......
if chain, ok :=
7. existingNATChains[utiliptables.Chain(endpointChain)]; ok {
8. writeBytesLine(proxier.natChains, chain)
9. } else {
writeLine(proxier.natChains,
10. utiliptables.MakeChainLine(endpointChain))
11. }
12. activeNATChains[endpointChain] = true
13. }
1. if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
2. for _, endpointChain := range endpointChains {
3. ......
4. }
5. }
1. if len(proxier.clusterCIDR) > 0 {
2. ......
3. writeLine(proxier.natRules, args...)
4. }
删除不存在服务的自定义链,KUBE-SVC-xxx、KUBE-SEP-xxx、KUBE-FW-xxx、KUBE-XLB-xxx:
1. writeLine(proxier.filterRules,
2. ......
3. )
4.
5. writeLine(proxier.filterRules,
6. ......
7. )
8.
9. if len(proxier.clusterCIDR) != 0 {
10. writeLine(proxier.filterRules,
11. ......
12. )
13. writeLine(proxier.filterRules,
14. ......
15. )
16. }
在结尾添加标志:
1. writeLine(proxier.filterRules, "COMMIT")
2. writeLine(proxier.natRules, "COMMIT")
使用 iptables-restore 同步规则:
1. proxier.iptablesData.Reset()
2. proxier.iptablesData.Write(proxier.filterChains.Bytes())
3. proxier.iptablesData.Write(proxier.filterRules.Bytes())
4. proxier.iptablesData.Write(proxier.natChains.Bytes())
5. proxier.iptablesData.Write(proxier.natRules.Bytes())
6.
err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(),
7. utiliptables.NoFlushTables, utiliptables.RestoreCounters)
8. if err != nil {
9. ......
10. }
总结
本文主要讲了 kube-proxy iptables 模式的实现,可以看到其中的 iptables 规则是相当复杂
的,在实际环境中尽量根据已有服务再来梳理整个 iptables 规则链就比较清楚了,笔者对于
iptables 的知识也是现学的,文中如有不当之处望指正。上面分析完了整个 iptables 模式的功
能,但是 iptable 存在一些性能问题,比如有规则线性匹配时延、规则更新时延、可扩展性差等,为
了解决这些问题于是有了 ipvs 模式,在下篇文章中会继续介绍 ipvs 模式的实现。
参考:
https://www.jianshu.com/p/a978af8e5dd8
https://blog.csdn.net/ebay/article/details/52798074
https://blog.csdn.net/horsefoot/article/details/51249161
https://rootdeep.github.io/posts/kube-proxy-code-analysis/
https://www.cnblogs.com/charlieroro/p/9588019.html
ipvs
ipvs (IP Virtual Server) 是基于 Netfilter 的,作为 linux 内核的一部分实现了传输层
负载均衡,ipvs 集成在LVS(Linux Virtual Server)中,它在主机中运行,并在真实服务器集群
前充当负载均衡器。ipvs 可以将对 TCP/UDP 服务的请求转发给后端的真实服务器,因此 ipvs 天
然支持 Kubernetes Service。ipvs 也包含了多种不同的负载均衡算法,例如轮询、最短期望延
迟、最少连接以及各种哈希方法等,ipvs 的设计就是用来为大规模服务进行负载均衡的。
ipvs 的负载均衡方式
ipvs 有三种负载均衡方式,分别为:
NAT
TUN
DR
关于三种模式的原理可以参考:LVS 配置小结。
NAT 模式下的工作流程如下所示:
1. +--------+
2. | Client |
3. +--------+
4. (CIP) <-- Client's IP address
5. |
6. |
7. { internet }
8. |
9. |
10. (VIP) <-- Virtual IP address
11. +----------+
12. | Director |
13. +----------+
14. (PIP) <-- (Director's Private IP address)
15. |
16. |
区别:
联系:
ipset
根据官网的介绍,若有以下使用场景:
kube-proxy ipvs 模式
kube-proxy 的 ipvs 模式是在 2015 年由 k8s 社区的大佬 thockin 提出的(Try kube-
proxy via ipvs instead of iptables or userspace),在 2017 年由华为云团队实现的
(Implement IPVS-based in-cluster service load balancing)。前面的文章已经提到
了,在 kubernetes v1.8 中已经引入了 ipvs 模式。
NAT 表:
Filter 表:
ipvs 模式的启用
1. $ modprobe -- ip_vs
2. $ modprobe -- ip_vs_rr
3. $ modprobe -- ip_vs_wrr
4. $ modprobe -- ip_vs_sh
5. $ modprobe -- nf_conntrack_ipv4
6. $ cut -f1 -d " " /proc/modules | grep -e ip_vs -e nf_conntrack_ipv4
1. --proxy-mode=ipvs
例如下面的示例:
1. // kube-ipvs0 dummy 网卡
2. $ ip addr
3. ......
4. 4: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
5. link/ether de:be:c0:73:bc:c7 brd ff:ff:ff:ff:ff:ff
6. inet 10.96.0.1/32 brd 10.96.0.1 scope global kube-ipvs0
7. valid_lft forever preferred_lft forever
8. inet 10.96.0.10/32 brd 10.96.0.10 scope global kube-ipvs0
9. valid_lft forever preferred_lft forever
10. inet 10.97.4.140/32 brd 10.97.4.140 scope global kube-ipvs0
ipvs 模式下数据包的流向
clusterIP 访问方式
首先进入 PREROUTING 链
从 PREROUTING 链会转到 KUBE-SERVICES 链,10.244.0.0/16 为 ClusterIP 网段
在 KUBE-SERVICES 链打标记
从 KUBE-SERVICES 链再进入到 KUBE-CLUSTER-IP 链
KUBE-CLUSTER-IP 为 ipset 集合,在此处会进行 DNAT
然后会进入 INPUT 链
从 INPUT 链会转到 KUBE-FIREWALL 链,在此处检查标记
在 INPUT 链处,ipvs 的 LOCAL_IN Hook 发现此包在 ipvs 规则中则直接转发到
POSTROUTING 链
然后会进入 INPUT:
1. -A INPUT -j KUBE-FIREWALL
2.
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked
3. packets" -m mark --mark 0x8000/0x8000 -j DROP
nodePort 方式
PREROUTING --> KUBE-SERVICES --> KUBE-NODE-PORT --> INPUT --> KUBE-FIREWALL -->
1. POSTROUTING
首先进入 PREROUTING 链
从 PREROUTING 链会转到 KUBE-SERVICES 链
在 KUBE-SERVICES 链打标记
从 KUBE-SERVICES 链再进入到 KUBE-NODE-PORT 链
KUBE-NODE-PORT 为 ipset 集合,在此处会进行 DNAT
然后会进入 INPUT 链
从 INPUT 链会转到 KUBE-FIREWALL 链,在此处检查标记
在 INPUT 链处,ipvs 的 LOCAL_IN Hook 发现此包在 ipvs 规则中则直接转发到
POSTROUTING 链
kubernetes 版本:v1.16
1. func NewProxier(......) {
2. ......
proxier.syncRunner = async.NewBoundedFrequencyRunner("sync-runner",
3. proxier.syncProxyRules, minSyncPeriod, syncPeriod, burstSyncs)
4. ......
5. }
proxier.syncRunner() 执行流程:
15. staleServices.Insert(extIP)
16. }
17. }
18. }
19. ......
1. proxier.natChains.Reset()
2. proxier.natRules.Reset()
3. proxier.filterChains.Reset()
4. proxier.filterRules.Reset()
5.
6. writeLine(proxier.filterChains, "*filter")
7. writeLine(proxier.natChains, "*nat")
8.
9. // 创建kubernetes的表连接链数据
10. proxier.createAndLinkeKubeChain()
11.
12. // 创建 dummy interface kube-ipvs0
13. _, err := proxier.netlinkHandle.EnsureDummyDevice(DefaultDummyDevice)
14. if err != nil {
15. ......
16. return
17. }
18.
19. // 创建默认的 ipset 规则
20. for _, set := range proxier.ipsetList {
21. if err := ensureIPSet(set); err != nil {
22. return
23. }
24. set.resetEntries()
25. }
6.
7. for _, e := range proxier.endpointsMap[svcName] {
8. ep, ok := e.(*proxy.BaseEndpointInfo)
9. if !ok {
10. klog.Errorf("Failed to cast BaseEndpointInfo %q", e.String())
11. continue
12. }
13. ......
14.
if valid :=
15. proxier.ipsetList[kubeLoopBackIPSet].validateEntry(entry); !valid {
16. ......
17. }
18. proxier.ipsetList[kubeLoopBackIPSet].activeEntries.Insert(entry.String())
19. }
if valid := proxier.ipsetList[kubeClusterIPSet].validateEntry(entry);
1. !valid {
2. ......
3. }
4. proxier.ipsetList[kubeClusterIPSet].activeEntries.Insert(entry.String())
5. ......
6. if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
7. ......
8. }
9. // 绑定 ClusterIP to dummy interface
10. if err := proxier.syncService(svcNameString, serv, true); err == nil {
11. // 同步 endpoints 信息
12. if err := proxier.syncEndpoint(svcName, false, serv); err != nil {
13. ......
14. }
15. } else {
16. ......
17. }
20. proxier.ipsetList[kubeExternalIPSet].activeEntries.Insert(entry.String())
21.
22. ......
23. if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
24. ......
25. }
if err := proxier.syncService(svcNameString, serv, true); err ==
26. nil {
27. ......
if err := proxier.syncEndpoint(svcName, false, serv); err !=
28. nil {
29. ......
30. }
31. } else {
32. ......
33. }
34. }
if valid :=
4. proxier.ipsetList[kubeLoadBalancerSet].validateEntry(entry); !valid {
5. ......
6. }
7. proxier.ipsetList[kubeLoadBalancerSet].activeEntries.Insert(entry.String())
8.
9. if svcInfo.OnlyNodeLocalEndpoints() {
10. ......
11. }
12. if len(svcInfo.LoadBalancerSourceRanges()) != 0 {
13. ......
14. for _, src := range svcInfo.LoadBalancerSourceRanges() {
15. ......
16. }
17. ......
18. }
19. ......
if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP
20. {
21. ......
22. }
if err := proxier.syncService(svcNameString, serv, true); err
23. == nil {
24. ......
if err := proxier.syncEndpoint(svcName,
25. svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil {
26. ......
27. }
28. } else {
29. ......
30. }
31. }
32. }
1. if svcInfo.NodePort() != 0 {
2. ......
3.
4. var lps []utilproxy.LocalPort
5. for _, address := range nodeAddresses {
6. ......
nodePortLocalSet =
47. proxier.ipsetList[kubeNodePortLocalSetSCTP]
48. default:
49. ......
50. }
51. if nodePortLocalSet != nil {
52. entryInvalidErr := false
53. for _, entry := range entries {
54. ......
55. nodePortLocalSet.activeEntries.Insert(entry.String())
56. }
57. ......
58. }
59. }
60. for _, nodeIP := range nodeIPs {
61. ......
if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP
62. {
63. ......
64. }
if err := proxier.syncService(svcNameString, serv, false); err
65. == nil {
if err := proxier.syncEndpoint(svcName,
66. svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil {
67. ......
68. }
69. } else {
70. ......
71. }
72. }
73. }
74. }
9. proxier.iptablesData.Write(proxier.natRules.Bytes())
10. proxier.iptablesData.Write(proxier.filterChains.Bytes())
11. proxier.iptablesData.Write(proxier.filterRules.Bytes())
12.
err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(),
13. utiliptables.NoFlushTables, utiliptables.RestoreCounters)
14. if err != nil {
15. ......
16. }
17. ......
18. proxier.deleteEndpointConnections(endpointUpdateResult.StaleEndpoints)
19. }
总结
本文主要讲述了 kube-proxy ipvs 模式的原理与实现,iptables 模式与 ipvs 模式下在源码实
现上有许多相似之处,但二者原理不同,理解了原理分析代码则更加容易,笔者对于 ipvs 的知识也
是现学的,文中如有不当之处望指正。虽然 ipvs 的性能要比 iptables 更好,但社区中已有相关
的文章指出 BPF(Berkeley Packet Filter) 比 ipvs 的性能更好,且 BPF 将要取代
iptables,至于下一步如何发展,让我们拭目以待。
参考:
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.filter_rules.html
https://bestsamina.github.io/posts/2018-10-19-ipvs-based-kube-proxy-4-
scaled-k8s-lb/
https://www.bookstack.cn/read/k8s-source-code-analysis/core-kube-proxy-
ipvs.md
https://blog.51cto.com/goome/2369150
https://xigang.github.io/2019/07/21/kubernetes-service/
https://segmentfault.com/a/1190000016333317
https://cilium.io/blog/2018/04/17/why-is-the-kernel-community-replacing-
iptables/
一、概要
kubelet 是运行在每个节点上的主要的“节点代理”,每个节点都会启动 kubelet进程,用来处理
Master 节点下发到本节点的任务,按照 PodSpec 描述来管理Pod 和其中的容器(PodSpec 是用
来描述一个 pod 的 YAML 或者 JSON 对象)。
二、kubelet 的主要功能
1、kubelet 默认监听四个端口,分别为 10250 、10255、10248、4194。
1. $ curl http://127.0.0.1:10248/healthz
2. ok
1. $ curl http://127.0.0.1:4194/metrics
2、kubelet 主要功能:
容器健康检查:kubelet 创建了容器之后还要查看容器是否正常运行,如果容器运行出错,就要
根据 pod 设置的重启策略进行处理。
三、kubelet 组件中的模块
6、containerRefManager 容器引用的管理,相对简单的Manager,用来报告容器的创建,失
败等事件,通过定义 map 来实现了 containerID 与 v1.ObjectReferece 容器引用的映
射。
1. certificateManager
2. cgroupManager
3. containerManager
4. cpuManager
5. nodeContainerManager
6. configmapManager
7. containerReferenceManager
8. evictionManager
9. nvidiaGpuManager
10. imageGCManager
11. kuberuntimeManager
12. hostportManager
13. podManager
14. proberManager
15. secretManager
16. statusManager
17. volumeManager
18. tokenManager
其中比较重要的模块后面会进行一一分析。
参考:
Kubelet 组件解析
Kubelet 启动流程
kubernetes 版本:v1.16
NewKubeletCommand
1、解析命令行参数;
2、为 kubelet 初始化 feature gates 参数;
3、加载 kubelet 配置文件;
4、校验配置文件中的参数;
5、检查 kubelet 是否启用动态配置功能;
6、初始化 kubeletDeps,kubeletDeps 包含 kubelet 运行所必须的配置,是为了实现
dependency injection,其目的是为了把 kubelet 依赖的组件对象作为参数传进来,这样
可以控制 kubelet 的行为;
7、调用 Run 方法;
k8s.io/kubernetes/cmd/kubelet/app/server.go:111
27. utilflag.PrintFlags(cleanFlagSet)
28.
29. // 3、初始化 feature gates 配置
if err :=
utilfeature.DefaultMutableFeatureGate.SetFromMap(kubeletConfig.FeatureGates);
30. err != nil {
31. klog.Fatal(err)
32. }
33.
34. if err := options.ValidateKubeletFlags(kubeletFlags); err != nil {
35. klog.Fatal(err)
36. }
37.
if kubeletFlags.ContainerRuntime == "remote" &&
38. cleanFlagSet.Changed("pod-infra-container-image") {
klog.Warning("Warning: For remote container runtime, --pod-
infra-container-image is ignored in kubelet, which should be set in that
39. remote runtime instead")
40. }
41.
42. // 4、加载 kubelet 配置文件
if configFile := kubeletFlags.KubeletConfigFile; len(configFile) >
43. 0 {
44. kubeletConfig, err = loadConfigFile(configFile)
45. ......
46. }
47. // 5、校验配置文件中的参数
if err :=
kubeletconfigvalidation.ValidateKubeletConfiguration(kubeletConfig); err != nil
48. {
49. klog.Fatal(err)
50. }
51.
52. // 6、检查 kubelet 是否启用动态配置功能
53. var kubeletConfigController *dynamickubeletconfig.Controller
if dynamicConfigDir := kubeletFlags.DynamicConfigDir.Value();
54. len(dynamicConfigDir) > 0 {
var dynamicKubeletConfig
55. *kubeletconfiginternal.KubeletConfiguration
dynamicKubeletConfig, kubeletConfigController, err =
56. BootstrapKubeletConfigController(dynamicConfigDir,
func(kc *kubeletconfiginternal.KubeletConfiguration) error
57. {
58. return kubeletConfigFlagPrecedence(kc, args)
59. })
60. if err != nil {
61. klog.Fatal(err)
62. }
63. if dynamicKubeletConfig != nil {
64. kubeletConfig = dynamicKubeletConfig
if err :=
utilfeature.DefaultMutableFeatureGate.SetFromMap(kubeletConfig.FeatureGates);
65. err != nil {
66. klog.Fatal(err)
67. }
68. }
69. }
70. kubeletServer := &options.KubeletServer{
71. KubeletFlags: *kubeletFlags,
72. KubeletConfiguration: *kubeletConfig,
73. }
74. // 7、初始化 kubeletDeps
75. kubeletDeps, err := UnsecuredDependencies(kubeletServer)
76. if err != nil {
77. klog.Fatal(err)
78. }
79.
80. kubeletDeps.KubeletConfigController = kubeletConfigController
81. stopCh := genericapiserver.SetupSignalHandler()
82. if kubeletServer.KubeletFlags.ExperimentalDockershim {
if err := RunDockershim(&kubeletServer.KubeletFlags,
83. kubeletConfig, stopCh); err != nil {
84. klog.Fatal(err)
85. }
86. return
87. }
88.
89. // 8、调用 Run 方法
90. if err := Run(kubeletServer, kubeletDeps, stopCh); err != nil {
91. klog.Fatal(err)
92. }
93. },
94. }
95. kubeletFlags.AddFlags(cleanFlagSet)
96. options.AddKubeletConfigFlags(cleanFlagSet, kubeletConfig)
97. options.AddGlobalFlags(cleanFlagSet)
98. ......
99.
100. return cmd
101. }
Run
k8s.io/kubernetes/cmd/kubelet/app/server.go:408
run
k8s.io/kubernetes/cmd/kubelet/app/server.go:472
33. }
34.
35. // 5、判断是否为 standalone 模式
36. standaloneMode := true
37. if len(s.KubeConfig) > 0 {
38. standaloneMode = false
39. }
40.
41. // 6、初始化 kubeDeps
42. if kubeDeps == nil {
43. kubeDeps, err = UnsecuredDependencies(s)
44. if err != nil {
45. return err
46. }
47. }
48. if kubeDeps.Cloud == nil {
49. if !cloudprovider.IsExternal(s.CloudProvider) {
cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider,
50. s.CloudConfigFile)
51. if err != nil {
52. return err
53. }
54. ......
55. kubeDeps.Cloud = cloud
56. }
57. }
58.
59. hostName, err := nodeutil.GetHostname(s.HostnameOverride)
60. if err != nil {
61. return err
62. }
63. nodeName, err := getNodeName(kubeDeps.Cloud, hostName)
64. if err != nil {
65. return err
66. }
67. // 7、如果是 standalone 模式将所有 client 设置为 nil
68. switch {
69. case standaloneMode:
70. kubeDeps.KubeClient = nil
71. kubeDeps.EventClient = nil
72. kubeDeps.HeartbeatClient = nil
73.
225. }
226. }, 5*time.Second, wait.NeverStop)
227. }
228.
229. if s.RunOnce {
230. return nil
231. }
232.
233. // 17、向 systemd 发送启动信号
234. go daemon.SdNotify(false, "READY=1")
235.
236. select {
237. case <-done:
238. break
239. case <-stopCh:
240. break
241. }
242. return nil
243. }
RunKubelet
k8s.io/kubernetes/cmd/kubelet/app/server.go:989
15. })
16.
17. credentialprovider.SetPreferredDockercfgPath(kubeServer.RootDirectory)
18.
19. if kubeDeps.OSInterface == nil {
20. kubeDeps.OSInterface = kubecontainer.RealOS{}
21. }
22.
23. // 2、调用 createAndInitKubelet
24. k, err := createAndInitKubelet(&kubeServer.KubeletConfiguration,
25. ......
26. kubeServer.NodeStatusMaxImages)
27. if err != nil {
28. return fmt.Errorf("failed to create kubelet: %v", err)
29. }
30.
31. if kubeDeps.PodConfig == nil {
return fmt.Errorf("failed to create kubelet, pod source config was
32. nil")
33. }
34. podCfg := kubeDeps.PodConfig
35.
36. rlimit.RlimitNumFiles(uint64(kubeServer.MaxOpenFiles))
37.
38. if runOnce {
39. if _, err := k.RunOnce(podCfg.Updates()); err != nil {
40. return fmt.Errorf("runonce failed: %v", err)
41. }
42. klog.Info("Started kubelet as runonce")
43. } else {
44. // 3、调用 startKubelet
startKubelet(k, podCfg, &kubeServer.KubeletConfiguration, kubeDeps,
45. kubeServer.EnableCAdvisorJSONEndpoints, kubeServer.EnableServer)
46. klog.Info("Started kubelet")
47. }
48. return nil
49. }
createAndInitKubelet
始化;
k.BirthCry :向 apiserver 发送一条 kubelet 启动了的 event;
k.StartGarbageCollection :启动垃圾回收服务,回收 container 和 images;
k8s.io/kubernetes/cmd/kubelet/app/server.go:1089
1. func createAndInitKubelet(......) {
2. k, err = kubelet.NewMainKubelet(
3. ......
4. )
5. if err != nil {
6. return nil, err
7. }
8.
9. k.BirthCry()
10.
11. k.StartGarbageCollection()
12.
13. return k, nil
14. }
kubelet.NewMainKubelet
k8s.io/kubernetes/pkg/kubelet/kubelet.go:335
nodeIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc,
72. cache.Indexers{})
73. if kubeDeps.KubeClient != nil {
fieldSelector := fields.Set{api.ObjectNameField:
74. string(nodeName)}.AsSelector()
nodeLW :=
cache.NewListWatchFromClient(kubeDeps.KubeClient.CoreV1().RESTClient(),
75. "nodes", metav1.NamespaceAll, fieldSelector)
76. r := cache.NewReflector(nodeLW, &v1.Node{}, nodeIndexer, 0)
77. go r.Run(wait.NeverStop)
78. }
nodeInfo := &CachedNodeInfo{NodeLister:
79. corelisters.NewNodeLister(nodeIndexer)}
80.
81. ......
82.
83. // 4、初始化 containerRefManager、oomWatcher
84. containerRefManager := kubecontainer.NewRefManager()
85.
86. oomWatcher := oomwatcher.NewWatcher(kubeDeps.Recorder)
87. clusterDNS := make([]net.IP, 0, len(kubeCfg.ClusterDNS))
88. for _, ipEntry := range kubeCfg.ClusterDNS {
89. ip := net.ParseIP(ipEntry)
90. if ip == nil {
91. klog.Warningf("Invalid clusterDNS ip '%q'", ipEntry)
92. } else {
93. clusterDNS = append(clusterDNS, ip)
94. }
95. }
96. httpClient := &http.Client{}
97. parsedNodeIP := net.ParseIP(nodeIP)
98. protocol := utilipt.ProtocolIpv4
99. if parsedNodeIP != nil && parsedNodeIP.To4() == nil {
100. protocol = utilipt.ProtocolIpv6
101. }
102.
103. // 5、初始化 kubelet 对象
104. klet := &Kubelet{......}
105.
106. if klet.cloud != nil {
klet.cloudResourceSyncManager =
cloudresource.NewSyncManager(klet.cloud, nodeName,
107. klet.nodeStatusUpdateFrequency)
108. }
109.
110. // 6、初始化 secretManager、configMapManager
111. var secretManager secret.Manager
112. var configMapManager configmap.Manager
113. switch kubeCfg.ConfigMapAndSecretChangeDetectionStrategy {
114. case kubeletconfiginternal.WatchChangeDetectionStrategy:
115. secretManager = secret.NewWatchingSecretManager(kubeDeps.KubeClient)
configMapManager =
116. configmap.NewWatchingConfigMapManager(kubeDeps.KubeClient)
117. case kubeletconfiginternal.TTLCacheChangeDetectionStrategy:
118. secretManager = secret.NewCachingSecretManager(
kubeDeps.KubeClient,
119. manager.GetObjectTTLFromNodeFunc(klet.GetNode))
120. configMapManager = configmap.NewCachingConfigMapManager(
kubeDeps.KubeClient,
121. manager.GetObjectTTLFromNodeFunc(klet.GetNode))
122. case kubeletconfiginternal.GetChangeDetectionStrategy:
123. secretManager = secret.NewSimpleSecretManager(kubeDeps.KubeClient)
configMapManager =
124. configmap.NewSimpleConfigMapManager(kubeDeps.KubeClient)
125. default:
return nil, fmt.Errorf("unknown configmap and secret manager mode: %v",
126. kubeCfg.ConfigMapAndSecretChangeDetectionStrategy)
127. }
128.
129. klet.secretManager = secretManager
130. klet.configMapManager = configMapManager
131. if klet.experimentalHostUserNamespaceDefaulting {
132. klog.Infof("Experimental host user namespace defaulting is enabled.")
133. }
134.
135. machineInfo, err := klet.cadvisor.MachineInfo()
136. if err != nil {
137. return nil, err
138. }
139. klet.machineInfo = machineInfo
140.
141. imageBackOff := flowcontrol.NewBackOff(backOffPeriod, MaxContainerBackOff)
142.
143. // 7、初始化 livenessManager、podManager、statusManager、resourceAnalyzer
144. klet.livenessManager = proberesults.NewManager()
145.
181. klet.criHandler = ds
182. }
183.
184. server := dockerremote.NewDockerServer(remoteRuntimeEndpoint, ds)
185. if err := server.Start(); err != nil {
186. return nil, err
187. }
188.
189. supported, err := ds.IsCRISupportedLogDriver()
190. if err != nil {
191. return nil, err
192. }
193. if !supported {
194. klet.dockerLegacyService = ds
195. legacyLogProvider = ds
196. }
197. case kubetypes.RemoteContainerRuntime:
198. break
199. default:
200. return nil, fmt.Errorf("unsupported CRI runtime: %q", containerRuntime)
201. }
runtimeService, imageService, err :=
getRuntimeAndImageServices(remoteRuntimeEndpoint, remoteImageEndpoint,
202. kubeCfg.RuntimeRequestTimeout)
203. if err != nil {
204. return nil, err
205. }
206. klet.runtimeService = runtimeService
if utilfeature.DefaultFeatureGate.Enabled(features.RuntimeClass) &&
207. kubeDeps.KubeClient != nil {
208. klet.runtimeClassManager = runtimeclass.NewManager(kubeDeps.KubeClient)
209. }
210.
211. runtime, err := kuberuntime.NewKubeGenericRuntimeManager(......)
212. if err != nil {
213. return nil, err
214. }
215. klet.containerRuntime = runtime
216. klet.streamingRuntime = runtime
217. klet.runner = runtime
218.
219. runtimeCache, err := kubecontainer.NewRuntimeCache(klet.containerRuntime)
220. if err != nil {
256. int(kubeCfg.ContainerLogMaxFiles),
257. )
258. if err != nil {
return nil, fmt.Errorf("failed to initialize container log manager:
259. %v", err)
260. }
261. klet.containerLogManager = containerLogManager
262. } else {
263. klet.containerLogManager = logs.NewStubContainerLogManager()
264. }
// 11、初始化 serverCertificateManager、probeManager、tokenManager、
265. volumePluginMgr、pluginManager、volumeManager
if kubeCfg.ServerTLSBootstrap && kubeDeps.TLSOptions != nil &&
utilfeature.DefaultFeatureGate.Enabled(features.RotateKubeletServerCertificate)
266. {
klet.serverCertificateManager, err =
kubeletcertificate.NewKubeletServerCertificateManager(klet.kubeClient, kubeCfg,
267. klet.nodeName, klet. getLastObservedNodeAddresses, certDirectory)
268. if err != nil {
return nil, fmt.Errorf("failed to initialize certificate manager:
269. %v", err)
270. }
kubeDeps.TLSOptions.Config.GetCertificate = func(*tls.ClientHelloInfo)
271. (*tls.Certificate, error) {
272. cert := klet.serverCertificateManager.Current()
273. if cert == nil {
return nil, fmt.Errorf("no serving certificate available for
274. the kubelet")
275. }
276. return cert, nil
277. }
278. }
279.
280. klet.probeManager = prober.NewManager(......)
281. tokenManager := token.NewManager(kubeDeps.KubeClient)
282.
283. klet.volumePluginMgr, err =
NewInitializedVolumePluginMgr(klet, secretManager, configMapManager,
284. tokenManager, kubeDeps.VolumePlugins, kubeDeps.DynamicPluginProber)
285. if err != nil {
286. return nil, err
287. }
288. klet.pluginManager = pluginmanager.NewPluginManager(
296. klet.dnsConfigurer.SetupDNSinContainerizedMounter(experimentalMounterPath)
297. }
298. klet.volumeManager = volumemanager.NewVolumeManager(......)
299.
300. // 12、初始化 workQueue、podWorkers、evictionManager
301. klet.reasonCache = NewReasonCache()
302. klet.workQueue = queue.NewBasicWorkQueue(klet.clock)
klet.podWorkers = newPodWorkers(klet.syncPod, kubeDeps.Recorder,
303. klet.workQueue, klet.resyncInterval, backOffPeriod, klet.podCache)
304.
305. klet.backOff = flowcontrol.NewBackOff(backOffPeriod, MaxContainerBackOff)
klet.podKillingCh = make(chan *kubecontainer.PodPair,
306. podKillingChannelCapacity)
307.
evictionManager, evictionAdmitHandler :=
eviction.NewManager(klet.resourceAnalyzer, evictionConfig,
killPodNow(klet.podWorkers, kubeDeps.Recorder),
klet.podManager.GetMirrorPodByPod, klet.imageManager, klet.containerGC,
308. kubeDeps.Recorder, nodeRef, klet.clock)
309.
310. klet.evictionManager = evictionManager
311. klet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler)
312. if utilfeature.DefaultFeatureGate.Enabled(features.Sysctls) {
runtimeSupport, err :=
313. sysctl.NewRuntimeAdmitHandler(klet.containerRuntime)
314. if err != nil {
315. return nil, err
316. }
317.
safeAndUnsafeSysctls := append(sysctlwhitelist.SafeSysctlWhitelist(),
318. allowedUnsafeSysctls...)
319. sysctlsWhitelist, err := sysctl.NewWhitelist(safeAndUnsafeSysctls)
320. if err != nil {
321. return nil, err
322. }
323. klet.admitHandlers.AddPodAdmitHandler(runtimeSupport)
324. klet.admitHandlers.AddPodAdmitHandler(sysctlsWhitelist)
325. }
326.
327. // 13、为 pod 注册相关模块的 handler
activeDeadlineHandler, err := newActiveDeadlineHandler(klet.statusManager,
328. kubeDeps.Recorder, klet.clock)
329. if err != nil {
330. return nil, err
331. }
332. klet.AddPodSyncLoopHandler(activeDeadlineHandler)
333. klet.AddPodSyncHandler(activeDeadlineHandler)
334. if utilfeature.DefaultFeatureGate.Enabled(features.TopologyManager) {
335. klet.admitHandlers.AddPodAdmitHandler(klet.containerManager.GetTopologyPodAdmitHandler
336. }
criticalPodAdmissionHandler :=
preemption.NewCriticalPodAdmissionHandler(klet.GetActivePods,
337. killPodNow(klet.podWorkers, kubeDeps.Recorder),kubeDeps.Recorder)
klet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(klet.getNodeAn
338. criticalPodAdmissionHandler, klet.containerManager.UpdatePluginResources))
339. for _, opt := range kubeDeps.Options {
340. opt(klet)
341. }
342.
343. klet.appArmorValidator = apparmor.NewValidator(containerRuntime)
344. klet.softAdmitHandlers.AddPodAdmitHandler(lifecycle.NewAppArmorAdmitHandler(klet.
345. klet.softAdmitHandlers.AddPodAdmitHandler(lifecycle.NewNoNewPrivsAdmitHandler(klet
346.
347. if utilfeature.DefaultFeatureGate.Enabled(features.NodeLease) {
klet.nodeLeaseController = nodelease.NewController(klet.clock,
klet.heartbeatClient, string(klet.nodeName), kubeCfg.NodeLeaseDurationSeconds,
348. klet.onRepeatedHeartbeatFailure)
349. }
350.
351. klet.softAdmitHandlers.AddPodAdmitHandler(lifecycle.NewProcMountAdmitHandler(klet
352.
353. klet.kubeletConfiguration = *kubeCfg
354.
355. klet.setNodeStatusFuncs = klet.defaultNodeStatusFuncs()
356.
357. return klet, nil
358. }
startKubelet
k8s.io/kubernetes/cmd/kubelet/app/server.go:1070
至此,kubelet 对象以及其依赖模块在上面的几个方法中已经初始化完成了,除了单独启动了 gc 模
块外其余的模块以及主逻辑最后都会在 Run 方法启动, Run 方法的主要逻辑在下文中会进行
解释,此处总结一下 kubelet 启动逻辑中的调用关系如下所示:
1. |--> NewMainKubelet
2. |
|--> createAndInitKubelet
3. --|--> BirthCry
|
4. |
|--> RunKubelet --|
5. |--> StartGarbageCollection
6. | |
| |--> startKubelet -->
7. k.Run
8. |
9. NewKubeletCommand --> Run --> run --|--> http.ListenAndServe
10. |
11. |--> daemon.SdNotify
Run
Run 方法是启动 kubelet 的核心方法,其中会启动 kubelet 的依赖模块以及主循环逻辑,该
方法的主要逻辑为:
1、注册 logServer;
2、判断是否需要启动 cloud provider sync manager;
3、调用 kl.initializeModules 首先启动不依赖 container runtime 的一些模块;
4、启动 volume manager ;
5、执行 kl.syncNodeStatus 定时同步 Node 状态;
6、调用 kl.fastStatusUpdateOnce 更新容器运行时启动时间以及执行首次状态同步;
7、判断是否启用 NodeLease 机制;
8、执行 kl.updateRuntimeUp 定时更新 Runtime 状态;
9、执行 kl.syncNetworkUtil 定时同步 iptables 规则;
10、执行 kl.podKiller 定时清理异常 pod,当 pod 没有被 podworker 正确处理的时
候,启动一个goroutine 负责 kill 掉 pod;
11、启动 statusManager ;
12、启动 probeManager ;
13、启动 runtimeClassManager ;
14、启动 pleg ;
15、调用 kl.syncLoop 监听 pod 变化;
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1398
13. }
14.
15. // 3、调用 kl.initializeModules 首先启动不依赖 container runtime 的一些模块
16. if err := kl.initializeModules(); err != nil {
kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning,
17. events.KubeletSetupFailed, err.Error())
18. klog.Fatal(err)
19. }
20.
21. // 4、启动 volume manager
22. go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)
23.
24. if kl.kubeClient != nil {
25. // 5、执行 kl.syncNodeStatus 定时同步 Node 状态
go wait.Until(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency,
26. wait.NeverStop)
27.
28. // 6、调用 kl.fastStatusUpdateOnce 更新容器运行时启动时间以及执行首次状态同步
29. go kl.fastStatusUpdateOnce()
30.
31. // 7、判断是否启用 NodeLease 机制
32. if utilfeature.DefaultFeatureGate.Enabled(features.NodeLease) {
33. go kl.nodeLeaseController.Run(wait.NeverStop)
34. }
35. }
36.
37. // 8、执行 kl.updateRuntimeUp 定时更新 Runtime 状态
38. go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)
39.
40. // 9、执行 kl.syncNetworkUtil 定时同步 iptables 规则
41. if kl.makeIPTablesUtilChains {
42. go wait.Until(kl.syncNetworkUtil, 1*time.Minute, wait.NeverStop)
43. }
44.
45. // 10、执行 kl.podKiller 定时清理异常 pod
46. go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop)
47.
48. // 11、启动 statusManager、probeManager、runtimeClassManager
49. kl.statusManager.Start()
50. kl.probeManager.Start()
51.
52. if kl.runtimeClassManager != nil {
53. kl.runtimeClassManager.Start(wait.NeverStop)
54. }
55.
56. // 12、启动 pleg
57. kl.pleg.Start()
58.
59. // 13、调用 kl.syncLoop 监听 pod 变化
60. kl.syncLoop(updates, kl)
61. }
initializeModules
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1319
fastStatusUpdateOnce
k8s.io/kubernetes/pkg/kubelet/kubelet.go:2262
updateRuntimeUp
k8s.io/kubernetes/pkg/kubelet/kubelet.go:2168
22. }
23.
24. runtimeReady := s.GetRuntimeCondition(kubecontainer.RuntimeReady)
25. if runtimeReady == nil || !runtimeReady.Status {
26. kl.runtimeState.setRuntimeState(err)
27. return
28. }
29. kl.runtimeState.setRuntimeState(nil)
30. // 3、调用 kl.initializeRuntimeDependentModules 启动依赖模块
31. kl.oneTimeInitializer.Do(kl.initializeRuntimeDependentModules)
32. kl.runtimeState.setRuntimeSync(kl.clock.Now())
33. }
initializeRuntimeDependentModules
该方法的主要逻辑为:
1、启动 cadvisor ;
2、获取 CgroupStats;
3、启动 containerManager 、 evictionManager 、 containerLogManager ;
4、将 CSI Driver 和 Device Manager 注册到 pluginManager ,然后启动
pluginManager ;
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1361
18. }
19.
kl.evictionManager.Start(kl.StatsProvider, kl.GetActivePods,
20. kl.podResourcesAreReclaimed, evictionMonitoringPeriod)
21.
22. kl.containerLogManager.Start()
23.
kl.pluginManager.AddHandler(pluginwatcherapi.CSIPlugin,
24. plugincache.PluginHandler(csi.PluginHandler))
25.
kl.pluginManager.AddHandler(pluginwatcherapi.DevicePlugin,
26. kl.containerManager.GetPluginRegistrationHandler())
27. // 4、启动 pluginManager
28. go kl.pluginManager.Run(kl.sourcesReady, wait.NeverStop)
29. }
小结
syncLoop
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1821
5. defer housekeepingTicker.Stop()
6. plegCh := kl.pleg.Watch()
7. const (
8. base = 100 * time.Millisecond
9. max = 5 * time.Second
10. factor = 2
11. )
12. duration := base
13. for {
14. if err := kl.runtimeState.runtimeErrors(); err != nil {
15. time.Sleep(duration)
duration = time.Duration(math.Min(float64(max),
16. factor*float64(duration)))
17. continue
18. }
19. duration = base
20. kl.syncLoopMonitor.Store(kl.clock.Now())
if !kl.syncLoopIteration(updates, handler, syncTicker.C,
21. housekeepingTicker.C, plegCh) {
22. break
23. }
24. kl.syncLoopMonitor.Store(kl.clock.Now())
25. }
26. }
syncLoopIteration
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1888
40. }
41. }
42. case <-syncCh:
43. podsToSync := kl.getPodsToSync()
44. if len(podsToSync) == 0 {
45. break
46. }
47. handler.HandlePodSyncs(podsToSync)
48. case update := <-kl.livenessManager.Updates():
49. if update.Result == proberesults.Failure {
50. pod, ok := kl.podManager.GetPodByUID(update.PodUID)
51. if !ok {
52. break
53. }
54. handler.HandlePodSyncs([]*v1.Pod{pod})
55. }
56. case <-housekeepingCh:
57. if !kl.sourcesReady.AllReady() {
klog.V(4).Infof("SyncLoop (housekeeping, skipped): sources aren't
58. ready yet.")
59. } else {
60. if err := handler.HandlePodCleanups(); err != nil {
61. klog.Errorf("Failed cleaning pods: %v", err)
62. }
63. }
64. }
65. return true
66. }
1. |--> kl.cloudResourceSyncManager.Run
2. |
3. | |--> kl.setupDataDirs
4. | |--> kl.imageManager.Start
5. Run --|--> kl.initializeModules ---|--> kl.serverCertificateManager.Start
6. | |--> kl.oomWatcher.Start
7. | |--> kl.resourceAnalyzer.Start
8. |
9. |--> kl.volumeManager.Run
| |-->
10. kl.containerRuntime.Status
总结
本文主要介绍了 kubelet 的启动流程,可以看到 kubelet 启动流程中的环节非常多,kubelet
中也包含了非常多的模块,后续在分享 kubelet 源码的文章中会先以 Run 方法中启动的所有模
块为主,各个击破。
1、kubelet 的控制循环(syncLoop)
syncLoop 中首先定义了一个 syncTicker 和 housekeepingTicker,即使没有需要更新的 pod
配置,kubelet 也会定时去做同步和清理 pod 的工作。然后在 for 循环中一直调用
syncLoopIteration,如果在每次循环过程中出现比较严重的错误,kubelet 会记录到
runtimeState 中,遇到错误就等待 5 秒中继续循环。
8. housekeepingTicker := time.NewTicker(housekeepingPeriod)
9. defer housekeepingTicker.Stop()
10. // pod 的生命周期变化
11. plegCh := kl.pleg.Watch()
12. const (
13. base = 100 * time.Millisecond
14. max = 5 * time.Second
15. factor = 2
16. )
17. duration := base
18. for {
19. if rs := kl.runtimeState.runtimeErrors(); len(rs) != 0 {
20. time.Sleep(duration)
duration = time.Duration(math.Min(float64(max),
21. factor*float64(duration)))
22. continue
23. }
24. ...
25.
26. kl.syncLoopMonitor.Store(kl.clock.Now())
27. // 第二个参数为 SyncHandler 类型,SyncHandler 是一个 interface,
28. // 在该文件开头处定义
if !kl.syncLoopIteration(updates, handler, syncTicker.C,
29. housekeepingTicker.C, plegCh) {
30. break
31. }
32. kl.syncLoopMonitor.Store(kl.clock.Now())
33. }
34. }
3、处理新增 pod(HandlePodAddtions)
对于事件中的每个 pod,执行以下操作:
26. }
27. }
28.
29. mirrorPod, _ := kl.podManager.GetMirrorPodByPod(pod)
// 通过 dispatchWork 分发 pod 做异步处理,dispatchWork 主要工作就是把接收到的
30. 参数封装成 UpdatePodOptions,调用 UpdatePod 方法.
31. kl.dispatchWork(pod, kubetypes.SyncPodCreate, mirrorPod, start)
// 在 probeManager 中添加 pod,如果 pod 中定义了 readiness 和 liveness 健康
32. 检查,启动 goroutine 定期进行检测
33. kl.probeManager.AddPod(pod)
34. }
35. }
4、下发任务(dispatchWork)
dispatchWorker 的主要作用是把某个对 Pod 的操作(创建/更新/删除)下发给 podWorkers。
15. metrics.PodWorkerLatency.WithLabelValues(syncType.String()).Observe(metrics.SinceInMicr
16. }
17. },
18. })
20. metrics.ContainersPerPodCount.Observe(float64(len(pod.Spec.Containers)))
21. }
22. }
5、更新事件的 channel(UpdatePod)
podWorkers 子模块主要的作用就是处理针对每一个的 Pod 的更新事件,比如 Pod 的创建,删
除,更新。而 podWorkers 采取的基本思路是:为每一个 Pod 都单独创建一个 goroutine 和更
新事件的 channel,goroutine 会阻塞式的等待 channel 中的事件,并且对获取的事件进行处
理。而 podWorkers 对象自身则主要负责对更新事件进行下发。
27. }
28. }
7、完成创建容器前的准备工作(SyncPod)
在这个方法中,主要完成以下几件事情:
如果是删除 pod,立即执行并返回
同步 podStatus 到 kubelet.statusManager
检查 pod 是否能运行在本节点,主要是权限检查(是否能使用主机网络模式,是否可以以
privileged 权限运行等)。如果没有权限,就删除本地旧的 pod 并返回错误信息
创建 containerManagar 对象,并且创建 pod level cgroup,更新 Qos level
cgroup
如果是 static Pod,就创建或者更新对应的 mirrorPod
创建 pod 的数据目录,存放 volume 和 plugin 信息,如果定义了 pv,等待所有的
volume mount 完成(volumeManager 会在后台做这些事情),如果有 image secrets,
去 apiserver 获取对应的 secrets 数据
然后调用 kubelet.volumeManager 组件,等待它将 pod 所需要的所有外挂的 volume 都
准备好。
调用 container runtime 的 SyncPod 方法,去实现真正的容器创建逻辑
18.
19. // 更新 pod 状态
20. kl.statusManager.SetPodStatus(pod, apiPodStatus)
21.
22. // 如果 pod 非 running 状态则直接 kill 掉
if !runnable.Admit || pod.DeletionTimestamp != nil || apiPodStatus.Phase ==
23. v1.PodFailed {
24. ...
25. }
26.
27. // 加载网络插件
if rs := kl.runtimeState.networkErrors(); len(rs) != 0 &&
28. !kubecontainer.IsHostNetworkPod(pod) {
29. ...
30. }
31.
32. pcm := kl.containerManager.NewPodContainerManager()
33. if !kl.podIsTerminated(pod) {
34. ...
35. // 创建并更新 pod 的 cgroups
36. if !(podKilled && pod.Spec.RestartPolicy == v1.RestartPolicyNever) {
37. if !pcm.Exists(pod) {
38. ...
39. }
40. }
41. }
42.
43. // 为 static pod 创建对应的 mirror pod
44. if kubepod.IsStaticPod(pod) {
45. ...
46. }
47.
48. // 创建数据目录
49. if err := kl.makePodDataDirs(pod); err != nil {
50. ...
51. }
52.
53. // 挂载 volume
54. if !kl.podIsTerminated(pod) {
55. if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil {
56. ...
57. }
58. }
59.
60. // 获取 secret 信息
61. pullSecrets := kl.getPullSecretsForPod(pod)
62.
63. // 调用 containerRuntime 的 SyncPod 方法开始创建容器
result := kl.containerRuntime.SyncPod(pod, apiPodStatus, podStatus,
64. pullSecrets, kl.backOff)
65. kl.reasonCache.Update(pod.UID, result)
66. if err := result.Error(); err != nil {
67. ...
68. }
69.
70. return nil
71. }
8、创建容器
containerRuntime(pkg/kubelet/kuberuntime)子模块的 SyncPod 函数才是真正完成 pod
内容器实体的创建。 syncPod 主要执行以下几个操作:
48. }
49. }
50.
configPodSandboxResult :=
51. kubecontainer.NewSyncResult(kubecontainer.ConfigPodSandbox, podSandboxID)
52. result.AddSyncResult(configPodSandboxResult)
53. // 获取 PodSandbox 的配置(如:metadata,clusterDNS,容器的端口映射等)
podSandboxConfig, err := m.generatePodSandboxConfig(pod,
54. podContainerChanges.Attempt)
55. ...
56.
57. // 5、启动 init container
if container := podContainerChanges.NextInitContainerToStart; container !=
58. nil {
59. ...
if msg, err := m.startContainer(podSandboxID, podSandboxConfig,
container, pod, podStatus, pullSecrets, podIP,
60. kubecontainer.ContainerTypeInit); err != nil {
61. ...
62. }
63. }
64.
65. // 6、启动业务容器
66. for _, idx := range podContainerChanges.ContainersToStart {
67. ...
if msg, err := m.startContainer(podSandboxID, podSandboxConfig,
container, pod, podStatus, pullSecrets, podIP,
68. kubecontainer.ContainerTypeRegular); err != nil {
69. ...
70. }
71. }
72.
73. return
74. }
9、启动容器
最终由 startContainer 完成容器的启动,其主要有以下几个步骤:
1、拉取镜像
2、生成业务容器的配置信息
3、调用 docker api 创建容器
4、启动容器
35. }
36. ...
37.
38. // 3、启动业务容器
39. err = m.runtimeService.StartContainer(containerID)
40. if err != nil {
41. ...
42. }
43.
44. containerMeta := containerConfig.GetMetadata()
45. sandboxMeta := podSandboxConfig.GetMetadata()
legacySymlink := legacyLogSymlink(containerID, containerMeta.Name,
46. sandboxMeta.Name,
47. sandboxMeta.Namespace)
containerLog := filepath.Join(podSandboxConfig.LogDirectory,
48. containerConfig.LogPath)
49. if _, err := m.osInterface.Stat(containerLog); !os.IsNotExist(err) {
if err := m.osInterface.Symlink(containerLog, legacySymlink); err !=
50. nil {
glog.Errorf("Failed to create legacy symbolic link %q to container
51. %q log %q: %v",
52. legacySymlink, containerID, containerLog, err)
53. }
54. }
55.
56. // 4、执行 post start hook
57. if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {
58. kubeContainerID := kubecontainer.ContainerID{
59. Type: m.runtimeName,
60. ID: containerID,
61. }
62. // runner.Run 这个方法的主要作用就是在业务容器起来的时候,
63. // 首先会执行一个 container hook(PostStart 和 PreStop),做一些预处理工作。
64. // 只有 container hook 执行成功才会运行具体的业务服务,否则容器异常。
msg, handlerErr := m.runner.Run(kubeContainerID, pod, container,
65. container.Lifecycle.PostStart)
66. if handlerErr != nil {
67. ...
68. }
69. }
70.
71. return "", nil
72. }
总结
本文主要讲述了 kubelet 从监听到容器调度至本节点再到创建容器的一个过程,kubelet 最终调用
docker api 来创建容器的。结合上篇文章,可以看出 kubelet 从启动到创建 pod 的一个清晰过
程。
参考:
k8s源码分析-kubelet
Kubelet源码分析(一):启动流程分析
kubelet创建Pod流程解析
一、kubelet 上报哪些状态
在 k8s 中,一个 node 的状态包含以下几个信息:
Addresses
Condition
Capacity
Info
1、Addresses
主要包含以下几个字段:
2、Condition
3、Capacity
4、Info
二、kubelet 状态异常时的影响
如果一个 node 处于非 Ready 状态超过 pod-eviction-timeout 的值(默认为 5 分钟,在
kube-controller-manager 中定义),在 v1.5 之前的版本中 kube-controller-manager
会 force delete pod 然后调度该宿主上的 pods 到其他宿主,在 v1.5 之后的版本中,
kube-controller-manager 不会 force delete pod ,pod 会一直处于 Terminating
或 Unknown 状态直到 node 被从 master 中删除或 kubelet 状态变为 Ready。在 node
NotReady 期间,Daemonset 的 Pod 状态变为 Nodelost,Deployment、Statefulset 和
Static Pod 的状态先变为 NodeLost,然后马上变为 Unknown。Deployment 的 pod 会
recreate,Static Pod 和 Statefulset 的 Pod 会一直处于 Unknown 状态。
三、kubelet 状态上报的实现
kubelet 有两种上报状态的方式,第一种定期向 apiserver 发送心跳消息,简单理解就是启动一个
goroutine 然后定期向 APIServer 发送消息。
级,该特性在集群规模扩展性和性能上有明显提升。本文主要分析第一种上报方式的实现。
kubernetes 版本 :v1.13
kubernetes/pkg/kubelet/kubelet.go#Run
syncNodeStatus 是状态上报的入口函数,其后所调用的多个函数也都是在同一个文件中实现的。
kubernetes/pkg/kubelet/kubelet_node_status.go#syncNodeStatus
10. if kl.registerNode {
11. // This will exit immediately if it doesn't need to do anything.
12. kl.registerWithAPIServer()
13. }
14. if err := kl.updateNodeStatus(); err != nil {
15. klog.Errorf("Unable to update node status: %v", err)
16. }
17. }
kubernetes/pkg/kubelet/kubelet_node_status.go#updateNodeStatus
kubernetes/pkg/kubelet/kubelet_node_status.go#tryUpdateNodeStatus
6.
7. // 获取 node 信息
node, err := kl.heartbeatClient.CoreV1().Nodes().Get(string(kl.nodeName),
8. opts)
9. if err != nil {
10. return fmt.Errorf("error getting node %q: %v", kl.nodeName, err)
11. }
12.
13. originalNode := node.DeepCopy()
14. if originalNode == nil {
15. return fmt.Errorf("nil %q node object", kl.nodeName)
16. }
17.
18. podCIDRChanged := false
19. if node.Spec.PodCIDR != "" {
if podCIDRChanged, err = kl.updatePodCIDR(node.Spec.PodCIDR); err !=
20. nil {
21. klog.Errorf(err.Error())
22. }
23. }
24.
25. // 设置 node 状态
26. kl.setNodeStatus(node)
27.
28. now := kl.clock.Now()
if utilfeature.DefaultFeatureGate.Enabled(features.NodeLease) &&
29. now.Before(kl.lastStatusReportTime.Add(kl.nodeStatusReportFrequency)) {
if !podCIDRChanged && !nodeStatusHasChanged(&originalNode.Status,
30. &node.Status) {
31. kl.volumeManager.MarkVolumesAsReportedInUse(node.Status.VolumesInUse)
32. return nil
33. }
34. }
35.
36. // 更新 node 信息到 master
37. // Patch the current status on the API server
updatedNode, _, err :=
nodeutil.PatchNodeStatus(kl.heartbeatClient.CoreV1(),
38. types.NodeName(kl.nodeName), originalNode, node)
39. if err != nil {
40. return err
41. }
46. kl.volumeManager.MarkVolumesAsReportedInUse(updatedNode.Status.VolumesInUse)
47. return nil
48. }
kubernetes/pkg/kubelet/kubelet_node_status.go#setNodeStatus
NewMainKubelet(pkg/kubelet/kubelet.go) 中初始化的。
kubernetes/pkg/kubelet/kubelet.go#NewMainKubelet
kubernetes/pkg/kubelet/kubelet_node_status.go#defaultNodeStatusFuncs
// TODO(mtaufen): I decided not to move this setter for now, since all
31. it does is send an event
// and record state back to the Kubelet runtime object. In the future,
32. I'd like to isolate
// these side-effects by decoupling the decisions to send events and
33. partial status recording
34. // from the Node setters.
35. kl.recordNodeSchedulableEvent,
36. )
37. return setters
38. }
kubernetes/pkg/util/node/node.go#PatchNodeStatus
四、总结
本文主要讲述了 kubelet 上报状态的方式及其实现,node 状态上报的方式目前有两种,本文仅分析
了第一种状态上报的方式。在大规模集群中由于节点数量比较多,所有 node 都频繁报状态对 etcd
会有一定的压力,当 node 与 master 通信时由于网络导致心跳上报失败也会影响 node 的状态,
为了避免类似问题的出现才有 NodeLease 方式,对于该功能的实现后文会继续进行分析。
参考:
https://www.qikqiak.com/post/kubelet-sync-node-status/
https://www.jianshu.com/p/054450557818
https://blog.csdn.net/shida_csdn/article/details/84286058
https://kubernetes.io/docs/concepts/architecture/nodes/
1. $ grep -R -n -i "EventRecorder" .
可以看出,controller-manage、kube-proxy、kube-scheduler、kubelet 都使用了
EventRecorder,本文只讲述 kubelet 中对 Events 的使用。
1、Events 的定义
2、EventBroadcaster 的初始化
EventBroadcaster 是个接口类型,该接口有以下四个方法:
明了哪个节点的哪个组件
3、Events 的生成
4、Events 的广播
10. m.distributing.Done()
11. }
12.
13. // distribute sends event to all watchers. Blocking.
14. func (m *Broadcaster) distribute(event Event) {
15. m.lock.Lock()
16. defer m.lock.Unlock()
17. if m.fullChannelBehavior == DropIfChannelFull {
18. for _, w := range m.watchers {
19. select {
20. case w.result <- event:
21. case <-w.stopped:
22. default: // Don't block if the event can't be queued.
23. }
24. }
25. } else {
26. for _, w := range m.watchers {
27. select {
28. case w.result <- event:
29. case <-w.stopped:
30. }
31. }
32. }
33. }
5、Events 的处理
12. eventHandler(event)
13. }
14. }()
15. return watcher
16. }
18. }
19. if result.Skip {
20. return
21. }
22. tries := 0
23. for {
if recordEvent(sink, result.Event, result.Patch, result.Event.Count > 1,
24. eventCorrelator) {
25. break
26. }
27. tries++
28. if tries >= maxTriesPerEvent {
29. glog.Errorf("Unable to write event '%#v' (retry limit exceeded!)", event)
30. break
31. }
32. // 第一次重试增加随机性,防止 apiserver 重启的时候所有的事件都在同一时间发送事件
33. if tries == 1 {
34. time.Sleep(time.Duration(float64(sleepDuration) * randGen.Float64()))
35. } else {
36. time.Sleep(sleepDuration)
37. }
38. }
39. }
6、Events 简单实现
1、事件的产生
2、事件的发送
3、事件广播
4、事件缓存
5、事件过滤和聚合
1. package main
2.
3. import (
4. "fmt"
5. "sync"
6. "time"
7. )
8.
9. // watcher queue
10. const queueLength = int64(1)
11.
12. // Events xxx
13. type Events struct {
14. Reason string
15. Message string
16. Source string
17. Type string
18. Count int64
19. Timestamp time.Time
20. }
21.
22. // EventBroadcaster xxx
23. type EventBroadcaster interface {
24. Event(etype, reason, message string)
25. StartLogging() Interface
26. Stop()
27. }
28.
29. // eventBroadcaster xxx
30. type eventBroadcasterImpl struct {
31. *Broadcaster
32. }
33.
34. func NewEventBroadcaster() EventBroadcaster {
35. return &eventBroadcasterImpl{NewBroadcaster(queueLength)}
36. }
37.
38. func (eventBroadcaster *eventBroadcasterImpl) Stop() {
39. eventBroadcaster.Shutdown()
40. }
41.
42. // generate event
126. }
127. m.watchers = map[int64]*broadcasterWatcher{}
128. }
129.
130. func (m *Broadcaster) stopWatching(id int64) {
131. m.lock.Lock()
132. defer m.lock.Unlock()
133. w, ok := m.watchers[id]
134. if !ok {
135. return
136. }
137. delete(m.watchers, id)
138. close(w.result)
139. }
140.
141. // 调用 Watch()方法注册一个 watcher
142. func (m *Broadcaster) Watch() Interface {
143. watcher := &broadcasterWatcher{
144. result: make(chan Events, incomingQueuLength),
145. stopped: make(chan struct{}),
146. id: m.watchQueueLength,
147. m: m,
148. }
149. m.watchers[m.watchersQueue] = watcher
150. m.watchQueueLength++
151. return watcher
152. }
153.
154. // watcher 实现
155. type Interface interface {
156. Stop()
157. ResultChan() <-chan Events
158. }
159.
160. type broadcasterWatcher struct {
161. result chan Events
162. stopped chan struct{}
163. stop sync.Once
164. id int64
165. m *Broadcaster
166. }
167.
代码请参考:https://github.com/gosoon/k8s-learning-notes/tree/master/k8s-
package/events
7、总结
本篇文章没有接上篇继续更新 kube-controller-manager,kube-controller-manager 的源
码阅读笔记也会继续更新,笔者会同时阅读多个组件的源码,阅读笔记也会按组件进行交叉更新,交叉
更新的目的一是为了加深印象避免阅读完后又很快忘记,二是某些代码的功能难以理解,避免死磕,但
整体目标是将每个组件的核心代码阅读完。
statusManager 源码分析
kubernetes 版本:v1.16
statusManager 的初始化
k8s.io/kubernetes/pkg/kubelet/kubelet.go:335
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:118
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1398
1. status:
2. conditions:
3. ......
4. containerStatuses:
- containerID:
5. containerd://64e9d88459b38e90c2a4b4d87db5acd180c820c855a55aabe38e4e11b9b83576
6. image: docker.io/library/nginx:1.9
imageID:
7. sha256:f568d3158b1e871b713cb33aca5a9377bc21a1f644addf41368393d28c35e894
8. lastState: {}
9. name: nginx-pod
10. ready: true
11. restartCount: 0
12. started: true
13. state:
14. running:
15. startedAt: "2019-12-15T16:13:29Z"
16. podIP: 10.15.225.15
17. ......
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:147
syncPod
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:514
3. if !m.needsUpdate(uid, status) {
4. klog.V(1).Infof("Status for pod %q is up-to-date; skipping", uid)
5. return
6. }
7.
8. // 2、获取 pod 的 oldStatus
pod, err :=
m.kubeClient.CoreV1().Pods(status.podNamespace).Get(status.podName,
9. metav1.GetOptions{})
10. if errors.IsNotFound(err) {
11. return
12. }
13. if err != nil {
14. return
15. }
16.
17. translatedUID := m.podManager.TranslatePodUID(pod.UID)
18. // 3、检查 pod UID 是否已经改变
if len(translatedUID) > 0 && translatedUID != kubetypes.ResolvedPodUID(uid)
19. {
20. return
21. }
22.
23. // 4、同步 pod 最新的 status 至 apiserver
24. oldStatus := pod.Status.DeepCopy()
newPod, patchBytes, err := statusutil.PatchPodStatus(m.kubeClient,
25. pod.Namespace, pod.Name, *oldStatus, mergePodStatus(*oldStatus, status.status))
26. if err != nil {
27. return
28. }
29. pod = newPod
30.
31. m.apiStatusVersions[kubetypes.MirrorPodUID(pod.UID)] = status.version
32.
// 5、若 newPod 处于 terminated 状态则调用 apiserver 删除该 pod,删除后 pod 会重
33. 建
34. if m.canBeDeleted(pod, status.status) {
35. deleteOptions := metav1.NewDeleteOptions(0)
deleteOptions.Preconditions =
36. metav1.NewUIDPreconditions(string(pod.UID))
err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name,
37. deleteOptions)
38. if err != nil {
39. return
40. }
41. // 6、从 cache 中清除
42. m.deletePodStatus(uid)
43. }
44. }
needsUpdate
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:570
PodResourcesAreReclaimed
k8s.io/kubernetes/pkg/kubelet/kubelet_pods.go:900
syncBatch
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:469
23. continue
24. }
25. syncedUID = mirrorUID
26. }
27. if m.needsUpdate(types.UID(syncedUID), status) {
updatedStatuses = append(updatedStatuses,
28. podStatusSyncRequest{uid, status})
29. } else if m.needsReconcile(uid, status.status) {
30. delete(m.apiStatusVersions, syncedUID)
updatedStatuses = append(updatedStatuses,
31. podStatusSyncRequest{uid, status})
32. }
33. }
34. }()
35.
36. // 4、调用 m.syncPod 同步 pod 状态
37. for _, update := range updatedStatuses {
38. m.syncPod(update.podUID, update.status)
39. }
40. }
needsReconcile
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:598
6. }
7.
8. // 2、检查 pod 是否为 static pod,若为 static pod 则获取其对应的 mirrorPod
9. if kubetypes.IsStaticPod(pod) {
10. mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod)
11. if !ok {
12. return false
13. }
14. pod = mirrorPod
15. }
16.
17. podStatus := pod.Status.DeepCopy()
18.
19. // 3、格式化 pod status subResource
20. normalizeStatus(pod, podStatus)
21.
22. // 4、检查 podManager 中的 status 与 statusManager cache 中的 status 是否一致
23. if isPodStatusByKubeletEqual(podStatus, &status) {
24. return false
25. }
26.
27. return true
28. }
SetPodStatus
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:179
6. if !kubetypes.PodConditionByKubelet(c.Type) {
klog.Errorf("Kubelet is trying to update pod condition %q for pod
7. %q. "+
"But it is not owned by kubelet.", string(c.Type),
8. format.Pod(pod))
9. }
10. }
11.
12. status = *status.DeepCopy()
13.
14. m.updateStatusInternal(pod, status, pod.DeletionTimestamp != nil)
15. }
updateStatusInternal
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:362
12.
13. // 2、检查 ContainerStatuses 和 InitContainerStatuses 是否合法
if err := checkContainerStateTransition(oldStatus.ContainerStatuses,
14. status.ContainerStatuses, pod.Spec.RestartPolicy); err != nil {
15. return false
16. }
if err := checkContainerStateTransition(oldStatus.InitContainerStatuses,
17. status.InitContainerStatuses, pod.Spec.RestartPolicy); err != nil {
klog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace,
18. pod.Name, err)
19. return false
20. }
21.
// 3、为 status 设置 ContainersReady、PodReady、PodInitialized、PodScheduled
22. conditions
23. updateLastTransitionTime(&status, &oldStatus, v1.ContainersReady)
24.
25. updateLastTransitionTime(&status, &oldStatus, v1.PodReady)
26.
27. updateLastTransitionTime(&status, &oldStatus, v1.PodInitialized)
28.
29. updateLastTransitionTime(&status, &oldStatus, v1.PodScheduled)
30.
31. // 4、设置 status 的 StartTime
32. if oldStatus.StartTime != nil && !oldStatus.StartTime.IsZero() {
33. status.StartTime = oldStatus.StartTime
34. } else if status.StartTime.IsZero() {
35. now := metav1.Now()
36. status.StartTime = &now
37. }
38.
39. // 5、格式化 status
40. normalizeStatus(pod, &status)
41.
if isCached && isPodStatusByKubeletEqual(&cachedStatus.status, &status) &&
42. !forceUpdate {
43. return false
44. }
45.
46. // 6、将 newStatus 添加到 statusManager 的 cache podStatuses 中
47. newStatus := versionedPodStatus{
48. status: status,
49. version: cachedStatus.version + 1,
SetContainerReadiness
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:198
SetContainerStartup
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:255
TerminatePod
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:312
RemoveOrphanedStatuses
k8s.io/kubernetes/pkg/kubelet/status/status_manager.go:457
总结
本文主要介绍了 statusManager 的功能以及使用,其功能其实非常简单,当 pod 状态改变时
statusManager 会将状态同步到 apiserver,statusManager 也提供了多个接口供其他组件调
用,当其他组件需要改变 pod 的状态时会将 pod 的 status 信息发送到 statusManager 进行
同步。
kubernetes 中的 Qos
QoS(Quality of Service) 即服务质量,QoS 是一种控制机制,它提供了针对不同用户或者不同
数据流采用相应不同的优先级,或者是根据应用程序的要求,保证数据流的性能达到一定的水准。
kubernetes 中有三种 Qos,分别为:
三者的优先级如下所示,依次递增:
不同 Qos 的本质区别
三种 Qos 在调度和底层表现上都不一样:
配置 cgroup driver
1. .
2. |-- blkio
3. |-- cpu -> cpu,cpuacct
4. |-- cpu,cpuacct
5. | |-- init.scope
6. | |-- kubepods
7. | | |-- besteffort
8. | | |-- burstable
9. | | `-- podd15c4b83-c250-4f1e-94ff-8a4bf31c6f25
10. | |-- system.slice
11. | `-- user.slice
12. |-- cpuacct -> cpu,cpuacct
13. |-- cpuset
14. | |-- kubepods
15. | | |-- besteffort
16. | | |-- burstable
17. | | `-- podd15c4b83-c250-4f1e-94ff-8a4bf31c6f25
18. |-- devices
19. |-- hugetlb
20. |-- memory
21. | |-- init.scope
22. | |-- kubepods
23. | | |-- besteffort
24. | | |-- burstable
25. | | `-- podd15c4b83-c250-4f1e-94ff-8a4bf31c6f25
26. | |-- system.slice
27. | | |-- -.mount
28. | `-- user.slice
29. |-- net_cls -> net_cls,net_prio
30. |-- net_cls,net_prio
31. |-- net_prio -> net_cls,net_prio
32. |-- perf_event
33. |-- pids
34. `-- systemd
例如,当创建资源如下所示的 pod:
1. spec:
2. containers:
3. - name: nginx
4. image: nginx:latest
5. imagePullPolicy: IfNotPresent
6. resources:
7. requests:
8. cpu: 250m
9. memory: 1Gi
10. limits:
11. cpu: 500m
12. memory: 2Gi
1. /sys/fs/cgroup/cpu/kubepods/burstable/pod<UID>/container<UID>/cpu.shares = 256
/sys/fs/cgroup/cpu/kubepods/burstable/pod<UID>/container<UID>/cpu.cfs_quota_us
2. = 50000
/sys/fs/cgroup/memory/kubepods/burstable/pod<UID>/container<UID>/memory.limit_in_bytes
3. = 104857600
1. pod<UID>/cpu.shares = sum(pod.spec.containers.resources.requests[cpu])
2. pod<UID>/cpu.cfs_period_us = 100000
3. pod<UID>/cpu.cfs_quota_us = sum(pod.spec.containers.resources.limits[cpu])
pod<UID>/memory.limit_in_bytes =
4. sum(pod.spec.containers.resources.limits[memory])
1. pod<UID>/cpu.shares = sum(pod.spec.containers.resources.requests[cpu])
2. pod<UID>/cpu.cfs_period_us = 100000
3. pod<UID>/cpu.cfs_quota_us = sum(pod.spec.containers.resources.limits[cpu])
pod<UID>/memory.limit_in_bytes =
4. sum(pod.spec.containers.resources.limits[memory])
1. pod<UID>/cpu.shares = 2
2. pod<UID>/cpu.cfs_quota_us = -1
1. pod<UID>/cpu.shares = 102
2. pod<UID>/cpu.cfs_quota_us = 20000
Burstable cgroup :
BestEffort cgroup :
1. ROOT/besteffort/cpu.shares = 2
2. ROOT/besteffort/memory.limit_in_bytes =
Node.Allocatable - {(summation of memory requests of all `Guaranteed` and
3. `Burstable` pods)*(reservePercent / 100)}
小结
1. $ROOT
2. |
3. +- Pod1
4. | |
5. | +- Container1
6. | +- Container2
7. | ...
8. +- Pod2
9. | +- Container3
10. | ...
11. +- ...
12. |
13. +- burstable
14. | |
15. | +- Pod3
16. | | |
17. | | +- Container4
18. | | ...
19. | +- Pod4
20. | | +- Container5
21. | | ...
22. | +- ...
23. |
24. +- besteffort
25. | |
26. | +- Pod5
27. | | |
28. | | +- Container6
29. | | +- Container7
30. | | ...
31. | +- ...
QOSContainerManager 源码分析
kubernetes 版本:v1.16
qosContainerManager 的初始化
k8s.io/kubernetes/cmd/kubelet/app/server.go:471
5. }
k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux.go:200
qosContainerManager 的启动
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1361
cm.setupNode
5、为系统进程配置 oom_score_adj;
k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux.go:568
79. }
80. cont.ensureStateFunc = func(_ *fs.Manager) error {
return ensureProcessInContainerWithOOMScore(os.Getpid(),
81. qos.KubeletOOMScoreAdj, &manager)
82. }
83. systemContainers = append(systemContainers, cont)
84. } else {
85. cm.periodicTasks = append(cm.periodicTasks, func() {
if err := ensureProcessInContainerWithOOMScore(os.Getpid(),
86. qos.KubeletOOMScoreAdj, nil); err != nil {
87. klog.Error(err)
88. return
89. }
90. cont, err := getContainer(os.Getpid())
91. if err != nil {
92. klog.Errorf("failed to find cgroups of kubelet - %v", err)
93. return
94. }
95. cm.Lock()
96. defer cm.Unlock()
97.
98. cm.KubeletCgroupsName = cont
99. })
100. }
101. cm.systemContainers = systemContainers
102. return nil
103. }
cm.qosContainerManager.Start
cm.qosContainerManager.Start 主要逻辑为:
k8s.io/kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go:80
4.
5. // 1、检查 root cgroup 是否存在
6. if !cm.Exists(rootContainer) {
7. return fmt.Errorf("root container %v doesn't exist", rootContainer)
8. }
9.
10. // 2、为 Qos 配置 Top level cgroups
11. qosClasses := map[v1.PodQOSClass]CgroupName{
v1.PodQOSBurstable: NewCgroupName(rootContainer,
12. strings.ToLower(string(v1.PodQOSBurstable))),
v1.PodQOSBestEffort: NewCgroupName(rootContainer,
13. strings.ToLower(string(v1.PodQOSBestEffort))),
14. }
15.
16. // 3、为 Qos 创建 top level cgroups
17. for qosClass, containerName := range qosClasses {
18. resourceParameters := &ResourceConfig{}
19. // 4、为 BestEffort QoS cpu.shares 设置默认值,默认为 2
20. if qosClass == v1.PodQOSBestEffort {
21. minShares := uint64(MinShares)
22. resourceParameters.CpuShares = &minShares
23. }
24.
25. containerConfig := &CgroupConfig{
26. Name: containerName,
27. ResourceParameters: resourceParameters,
28. }
29.
30. // 5、配置 huge page size
31. m.setHugePagesUnbounded(containerConfig)
32.
33. // 6、为 Qos 创建 cgroup 目录
34. if !cm.Exists(containerName) {
35. if err := cm.Create(containerConfig); err != nil {
36. ......
37. }
38. } else {
39. if err := cm.Update(containerConfig); err != nil {
40. ......
41. }
42. }
43. }
44. ......
45.
46. // 7、每分钟定期更新 cgroup 配置
47. go wait.Until(func() {
48. err := m.UpdateCgroups()
49. if err != nil {
klog.Warningf("[ContainerManager] Failed to reserve QoS requests:
50. %v", err)
51. }
52. }, periodicQOSCgroupUpdateInterval, wait.NeverStop)
53.
54. return nil
55. }
m.UpdateCgroups
k8s.io/kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go:269
15.
16. // 1、更新 bestEffort 和 burstable Qos level cgroup 的 cpu.shares 值
17. if err := m.setCPUCgroupConfig(qosConfigs); err != nil {
18. return err
19. }
20.
21. // 2、调用 m.setHugePagesConfig 更新 huge pages
22. if err := m.setHugePagesConfig(qosConfigs); err != nil {
23. return err
24. }
25.
26. // 3、设置资源预留
27. if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.QOSReserved) {
28. for resource, percentReserve := range m.qosReserved {
29. switch resource {
30. case v1.ResourceMemory:
31. m.setMemoryReserve(qosConfigs, percentReserve)
32. }
33. }
34.
35. updateSuccess := true
36. for _, config := range qosConfigs {
37. err := m.cgroupManager.Update(config)
38. if err != nil {
39. updateSuccess = false
40. }
41. }
42. if updateSuccess {
klog.V(4).Infof("[ContainerManager]: Updated QoS cgroup
43. configuration")
44. return nil
45. }
46.
47. for resource, percentReserve := range m.qosReserved {
48. switch resource {
49. case v1.ResourceMemory:
50. m.retrySetMemoryReserve(qosConfigs, percentReserve)
51. }
52. }
53. }
54.
55. // 4、更新 cgroup 中的值
m.cgroupManager.Update
k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go:409
libcontainerCgroupConfig.PidsLimit =
20. *cgroupConfig.ResourceParameters.PidsLimit
21. }
22.
23. if err := setSupportedSubsystems(libcontainerCgroupConfig); err != nil {
return fmt.Errorf("failed to set supported cgroup subsystems for cgroup
24. %v: %v", cgroupConfig.Name, err)
25. }
26. return nil
27. }
setSupportedSubsystem
k8s.io/kubernetes/pkg/kubelet/cm/cgroup_manager_linux.go:345
15. }
16. ......
17. }
k8s.io/kubernetes/pkg/kubelet/cm/pod_container_manager_linux.go:79
k8s.io/kubernetes/vendor/github.com/opencontainers/runc/libcontainer/process_linux.g
o:282
3.
4. // 调用 p.manager.Apply 为进程配置 cgroup
5. if err := p.manager.Apply(p.pid()); err != nil {
return newSystemErrorWithCause(err, "applying cgroup configuration for
6. process")
7. }
8. if p.intelRdtManager != nil {
9. if err := p.intelRdtManager.Apply(p.pid()); err != nil {
return newSystemErrorWithCause(err, "applying Intel RDT
10. configuration for process")
11. }
12. }
13. ......
14. }
总结
kubernetes 中有三种 Qos,分别为 Guaranteed、Burstable、BestEffort,三种 Qos 以
node 上 allocatable 资源量为基于为 pod 进行分配,并通过多个 level cgroup 进行层层限
制,对 cgroup 的配置都是通过调用 runc/libcontainer/cgroups/fs 中的方法进行资源更新
的。对于 Qos level cgroup,kubelet 会根据以下事件动态更新:
1、kubelet 服务启动时;
2、在创建 pod level cgroup 之前,即创建 pod 前;
3、在删除 pod level cgroup 后;
4、定期检测是否需要为 qos level cgroup 预留资源;
参考:
https://kubernetes.io/docs/setup/production-environment/container-
runtimes/#cgroup-drivers
https://zhuanlan.zhihu.com/p/38359775
https://github.com/kubernetes/community/blob/master/contributors/design-
proposals/node/pod-resource-management.md
https://github.com/cri-o/cri-o/issues/842
https://yq.aliyun.com/articles/737784?spm=a2c4e.11153940.0.0.577f6149mYFkTR
kubernetes 中的垃圾回收机制主要有两部分组成:
kubelet 中与容器垃圾回收有关的主要有以下三个参数:
与镜像回收有关的主要有以下三个参数:
kubernetes 版本:v1.16
k8s.io/kubernetes/cmd/kubelet/app/server.go:1089
1. func createAndInitKubelet(......) {
2. k, err = kubelet.NewMainKubelet(
3. ......
4. )
5. if err != nil {
6. return nil, err
7. }
8.
9. k.BirthCry()
10.
11. k.StartGarbageCollection()
12.
13. return k, nil
14. }
k.StartGarbageCollection
k8s.io/kubernetes/pkg/kubelet/kubelet.go:1270
13. }
14.
15. klog.V(vLevel).Infof("Container garbage collection succeeded")
16. }
17. }, ContainerGCPeriod, wait.NeverStop)
18.
19. // 2、检查 ImageGCHighThresholdPercent 参数的值
20. if kl.kubeletConfiguration.ImageGCHighThresholdPercent == 100 {
21. return
22. }
23.
24. // 3、启动镜像垃圾回收服务
25. prevImageGCFailed := false
26. go wait.Until(func() {
27. if err := kl.imageManager.GarbageCollect(); err != nil {
28. ......
29. prevImageGCFailed = true
30. } else {
31. var vLevel klog.Level = 4
32. if prevImageGCFailed {
33. vLevel = 1
34. prevImageGCFailed = false
35. }
36. }
37. }, ImageGCPeriod, wait.NeverStop)
38. }
kl.containerGC.GarbageCollect
k8s.io/kubernetes/pkg/kubelet/kubelet.go
1. func NewMainKubelet(){
2. ......
// MinAge、MaxPerPodContainer、MaxContainers 分别上文章开头提到的与容器垃圾回收有
3. 关的
4. // 三个参数
5. containerGCPolicy := kubecontainer.ContainerGCPolicy{
6. MinAge: minimumGCAge.Duration,
7. MaxPerPodContainer: int(maxPerPodContainerCount),
8. MaxContainers: int(maxContainerCount),
9. }
10.
11. // 初始化 containerGC 模块
containerGC, err := kubecontainer.NewContainerGC(klet.containerRuntime,
12. containerGCPolicy, klet.sourcesReady)
13. if err != nil {
14. return nil, err
15. }
16. ......
17. }
k8s.io/kubernetes/pkg/kubelet/container/container_gc.go:68
cgc.runtime.GarbageCollect
k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_gc.go:378
cgc.evictContainers
在 cgc.evictContainers 方法中会回收所有可被回收的容器,其主要逻辑为:
k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_gc.go:222
cgc.evictSandboxes
k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_gc.go:274
4. if err != nil {
5. return err
6. }
7. // 2、获取 node 上所有的 sandboxes
8. sandboxes, err := cgc.manager.getKubeletSandboxes(true)
9. if err != nil {
10. return err
11. }
12.
13. // 3、收集所有 container 的 PodSandboxId
14. sandboxIDs := sets.NewString()
15. for _, container := range containers {
16. sandboxIDs.Insert(container.PodSandboxId)
17. }
18.
19. // 4、构建 sandboxes 与 pod 的对应关系并将其保存在 sandboxesByPodUID 中
20. sandboxesByPod := make(sandboxesByPodUID)
21. for _, sandbox := range sandboxes {
22. podUID := types.UID(sandbox.Metadata.Uid)
23. sandboxInfo := sandboxGCInfo{
24. id: sandbox.Id,
25. createTime: time.Unix(0, sandbox.CreatedAt),
26. }
27.
28. if sandbox.State == runtimeapi.PodSandboxState_SANDBOX_READY {
29. sandboxInfo.active = true
30. }
31.
32. if sandboxIDs.Has(sandbox.Id) {
33. sandboxInfo.active = true
34. }
35.
36. sandboxesByPod[podUID] = append(sandboxesByPod[podUID], sandboxInfo)
37. }
38.
39. // 5、对 sandboxesByPod 进行排序
40. for uid := range sandboxesByPod {
41. sort.Sort(sandboxByCreated(sandboxesByPod[uid]))
42. }
43.
44. // 6、遍历 sandboxesByPod,若 sandboxes 所在的 pod 处于 deleted 状态,
45. // 则删除该 pod 中所有的 sandboxes 否则只保留退出时间最短的一个 sandboxes
cgc.evictPodLogsDirectories
/var/log/containers/storage-provisioner_kube-system_storage-provisioner-acc8386e40
1. -> /var/log/pods/kube-system_storage-provisioner_b448e496-eb5d-4d71-b93f-ff7ff77d2
k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_gc.go:333
13. continue
14. }
err := osInterface.RemoveAll(filepath.Join(podLogsRootDirectory,
15. name))
16. if err != nil {
klog.Errorf("Failed to remove pod logs directory %q: %v", name,
17. err)
18. }
19. }
20. }
21. // 2、回收 deleted 状态 container logs 链接目录
logSymlinks, _ := osInterface.Glob(filepath.Join(legacyContainerLogsDir,
22. fmt.Sprintf("*.%s", legacyLogSuffix)))
23. for _, logSymlink := range logSymlinks {
24. if _, err := osInterface.Stat(logSymlink); os.IsNotExist(err) {
25. err := osInterface.Remove(logSymlink)
26. if err != nil {
klog.Errorf("Failed to remove container log dead symlink %q:
27. %v", logSymlink, err)
28. }
29. }
30. }
31. return nil
32. }
kl.imageManager.GarbageCollect
上面已经分析了容器回收的主要流程,下面会继续分析镜像回收的流
程, kl.imageManager.GarbageCollect 是镜像回收任务启动的方法,镜像回收流程是在
imageManager 中进行的,首先了解下 imageManager 的初始化,imageManager 也是在
NewMainKubelet 方法中进行初始化的。
k8s.io/kubernetes/pkg/kubelet/kubelet.go
1. func NewMainKubelet(){
2. ......
3. // 初始化时需要指定三个参数,三个参数已经在上文中提到过
4. imageGCPolicy := images.ImageGCPolicy{
5. MinAge: kubeCfg.ImageMinimumGCAge.Duration,
6. HighThresholdPercent: int(kubeCfg.ImageGCHighThresholdPercent),
7. LowThresholdPercent: int(kubeCfg.ImageGCLowThresholdPercent),
8. }
9. ......
kl.imageManager.GarbageCollect 方法的主要逻辑为:
k8s.io/kubernetes/pkg/kubelet/images/image_gc_manager.go:269
im.freeSpace
im.freeSpace 是回收未使用镜像的方法,其主要逻辑为:
k8s.io/kubernetes/pkg/kubelet/images/image_gc_manager.go:328
总结
本文主要分析了 kubelet 中垃圾回收机制的实现,kubelet 中会定期回收 node 上已经退出的容
器已经当 node 磁盘资源不足时回收不再使用的镜像来释放磁盘资源,容器以及镜像回收策略主要是
通过 kubelet 中几个参数的阈值进行控制的。