如何启用Initializers

72次阅读
没有评论

共计 10234 个字符,预计需要花费 26 分钟才能阅读完成。

本篇内容主要讲解“如何启用 Initializers”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“如何启用 Initializers”吧!

Admission Controll 的最佳配置

配置过 kube-apiserver 的同学一定记得这个配置 –admission-control 或者 –admission-control-config-file, 你可以在这里顺序的配置你想要的准入控制器,默认是 AlwaysAdmit。

在 Kubernetes 1.9 中,所有允许的控制器列表如已经支持多达 32 个:

AlwaysAdmit,

AlwaysDeny,

AlwaysPullImages,

DefaultStorageClass,

DefaultTolerationSeconds,

DenyEscalatingExec,

DenyExecOnPrivileged,

EventRateLimit,

ExtendedResourceToleration,

ImagePolicyWebhook,

InitialResources,

Initializers,

LimitPodHardAntiAffinityTopology,

LimitRanger,

MutatingAdmissionWebhook,

NamespaceAutoProvision,

NamespaceExists,

NamespaceLifecycle,

NodeRestriction,

OwnerReferencesPermissionEnforcement,

PVCProtection,

PersistentVolumeClaimResize,

PersistentVolumeLabel,

PodNodeSelector,

PodPreset,

PodSecurityPolicy,

PodTolerationRestriction,

Priority,

ResourceQuota,

SecurityContextDeny,

ServiceAccount,

ValidatingAdmissionWebhook

注意,在我写这博客的时候 Dynamic Admission Controll 官方文档还没来得及更新到 1.9 对应内容,官方文档中还是写的 GenericAdmissionWebhook,实际上 Webhook 类已经分为 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 了,而没有 GenericAdmissionWebhook 这一项,其实它就是 ValidatingAdmissionWebhook 在 Kubernetes 1.9 后作的 rename 而已。

这么多的准入控制器,如果你并不想去了解那么多(虽然我不推荐你这么做,每一项的具体含义请参考 admission-controllers 官方文档),没关系,Kubernetes 也有推荐项给你。

如果你使用 Kubernetes 1.6 ~ 1.8,官方推荐配置如下:

--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds

如果你使用 Kubernetes 1.9,官方推荐配置如下:

--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ValidatingAdmissionWebhook,ResourceQuota,DefaultTolerationSeconds,MutatingAdmissionWebhook

再次强调一点,–admission-control 配置的控制器列表是有顺序的,越靠前的越先执行,一旦某个控制器返回的结果是 reject 的,那么整个准入控制阶段立刻结束,所以这里的配置顺序也是有讲究的,配置顺序不好,会导致性能会差些。

built-in 准入控制的缺陷

即便 Kubernetes 提供了这么多的准入控制器,也不可能满足所有企业的需求,因此 Kubernetes 提供了三个 Dynamic Admission Controller:

Initializers(Alpha, Default disable in 1.9)

MutatingAdmissionWebhook(Belta, Default enable in 1.9)

ValidatingAdmissionWebhook(Alpha in 1.8, Belta in 1.9, Default enable in 1.9)

这三个 Dynamic Admission Controller 都是为了解决其他内置插件化准入控制器的两个缺陷:

在 kube-apiserver 编译时打包进去的,如果有定制化修改,需要重新编译 kube-apiserver。

如果需要修改 –admission-controll 中的控制器列表(包括顺序),都需要重启 kube-apiserver。

如果你没做 Kubernetes Master HA,会导致 Kubernetes Master 中断服务;

如果你做了 Kubernetes Master HA,就完全没问题了吗?当然也不完全是,服务不会中断,但是存在一段时间会存在不同的 kube-apiserver 有不同的 –admission-controll 配置,导致同样的请求如果分发到不一样配置的 kube-apiserver,就不能做到幂等性了。当然,这好像影响也并不大。

Initializers 工作机制 Initializers 有什么用

我们什么时候需要用 Initializers 呢?当集群管理员需要强制对某些请求或者所有请求都进行校验或者修改的时候,就可以考虑使用 Initializers。

通过 Initializers,你可以给每个即将创建的 Pod 都插入一个 SideCar 容器。

通过 Initializers,给所有 Pod 都插入一个带有测试数据的 volume 用于业务测试。

通过 Initializers,检查 Secret 的长度是否满足要求,以此来保证密码的复杂度,如果不满足就拒绝 create pod 请求。

另外我之前思考的关于 Harbor 镜像安全的问题:在多租户环境中,某个用户在某个 Node 上 pull 了一个带有敏感数据的镜像并且启动为 Pod 了。此时,另外一个用户只要知道这个 image name,并且设置 imagePullPolicy 为 IfNotPresent,那么这个用户的 Pod 就可能会被调度到这个节点(如果 scheduler 配置了 ImageLocalityPriority priority policy,非默认配置,但在经常会配置,以提高 pod 启动速度),然后就把别人的敏感镜像跑起来了,这在公有云中是不可被接受的。

我们如何解决这个问题呢?在私有云中,会通过 DevOps 平台做好权限的控制,用户只能选择自己的 app 进行部署,并不能指定别人的镜像名称。在 Kubernetes 层面,有办法解决这个问题吗?嗯,利用 Initializers 就能很好解决(幸运的是,Kubernetes 已经提供了 AlwaysPullImages 这个 Admission Controller),所有用户创建的 Pod 请求,都经过你的 Initializers 进行检查和修改,强制修改 Pod ImagePullPolicy 为 Always 即可。

如何启用 Initializers

前面提到,需要在每个 kube-apiserver 实例(考虑到 Kubernetes Master HA)中 –admission-controll 中添加 Initializers。

另外,还需要在每个 kube-apiserver 实例的 –runtime-config 中添加 admissionregistration.k8s.io/v1alpha1。

Initializers 的工作原理

首先部署你自己写的 Initializers controller。这个 controller 通过 watch 你想要的 resource type,捕获后对这些 resource 的 POST 请求做修改。我们以 envoy-initializer 为例:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
 initializers:
 pending: []
 labels:
 app: envoy-initializer
 name: envoy-initializer
spec:
 replicas: 1
 template:
 metadata:
 labels:
 app: envoy-initializer
 name: envoy-initializer
 spec:
 containers:
 - name: envoy-initializer
 image: gcr.io/hightowerlabs/envoy-initializer:0.0.1
 imagePullPolicy: Always
 args:
 -  -annotation=initializer.kubernetes.io/envoy 
 -  -require-annotation=true

部署 envoy-initializer 时,千万要注意设置 metadata.initializers.pending 为空,防止 envoy-initializer 的部署被自己 stuck 了。

然后你要创建你的 initializerConfigurationAPI Object, 比如你想通过 Initializers 给每个之后创建的 Deployment 注入一个 envoy proxy sidecar 容器:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:
 name: envoy
initializers:
 - name: envoy.initializer.kubernetes.io
 rules:
 - apiGroups:
 -  * 
 apiVersions:
 -  * 
 resources:
 - deployments

initializerConfiguration 创建后,你需要等待几秒,然后再通过 Deployment 部署你的应用, 这个时候对应的 Initializers 就会自动 append 到 Deployment 的 metadata.initializers.pending 数组中,以上面的 example 为例,就是附加 metadata.initializers.pending[0]=envoy.initializer.kubernetes.io

apiVersion: apps/v1beta1
kind: Deployment
metadata:
 annotations:
  initializer.kubernetes.io/envoy :  true 
 labels:
 app: helloworld
 envoy:  true 
 name: helloworld-with-annotation
spec:
 replicas: 1
 template:
 metadata:
 labels:
 app: helloworld
 envoy:  true 
 name: helloworld-with-annotation
 spec:
 containers:
 - name: helloworld
 image: gcr.io/hightowerlabs/helloworld:0.0.1
 imagePullPolicy: Always
 args:
 -  -http=127.0.0.1:8080

注意:metadata.initializers.pending 不为 null 的时候,默认是无法通过 api 获取到该 deployment object 的,因此 Initializers controller list wath 对象的时候需要在 request url 中添加参数?includeUninitialized=true。

然后这一创建 Deployment 对象的 event 被你自定义的 Initializers controller 捕获到了,Initializers controller 就按照你的逻辑对该 Deployment 进行修改,比如注入 sidecar container 和 volume 等,并且会从对象的 metadata.initializers.pending 中删除掉自己对应的 Initializers controller。

如果有多个 Initializers 映射到这个对象,那么就会串行的按照上面的逻辑处理。因此如果是不需要对 Object 做修改操作的 Admission Controller,建议通过 webhook 的方式处理(并行的),那样性能会更高。initializers 的串行方式注定性能会低,所以最好不要创建多的 initializers。

当该 Object 的 metadata.initializers.pending 为 null 的时候,就认为已经完成初始化流程,接下来 scheduler 和 controller-managers 管理的 controllers 就能看到这些 Object,继续后面的调度和自动驾驶逻辑。

注意:当你通过 kubectl 或者 rest api 提交创建对象请求的时候,如果这个对象有相应的 Initializers,那么这个对象会保持 uninitialized 状态,需要要等待 Initializers Controllers 执行完对应的逻辑后才会返回,并且有个超时时间为 30s。

Initializers 注意事项

基于上面对 Initializers 工作机制的理解,我们发现它也有缺陷或者注意事项:

如果你部署的 Initializers Controllers 不能正常工作了或者性能很低,在高并发场景下会导致大量的相关对象停留在 uninitialized 状态,无法进行后续的调度。这可能会影响你的业务,比如你使用了 HPA 对相关 Deployment 对象进行弹性扩容,当负债上来的时候,你的 Initializers Controllers 不能正常工作了,会导致你的应用不能弹性伸缩,后果可想而知!所以写一个高性能的稳定的 Initializers Controllers 是你必须的技能。

目前 Initializers 准入控制仍属于 Alpha,你懂得。

你部署的 Initializers Controllers 是如此重要,所以建议你给它部署在 kube-system 或者单独的一个 namespace 中,给他分配足够的 ResourceQuota 和 LimitRanger,以保障它的稳定性。

如果你有多个 Initializers Controllers 关联到某类 resource,那么每次创建 resource 的时候,生成的 metadata.initializers.pending 数组元素顺序可能是不一样的,所以建议这些 Initializers Controllers 不应该有相互依赖。

再次强调一下,部署你的 Initializers Controllers 时,千万要注意设置 metadata.initializers.pending 为空,防止 Initializers Controllers 的部署被自己 stuck 了。

如何开发一个自定义的 Initializers

请参考大神 kelseyhightower 的项目 kubernetes-initializer-tutorial, 代码不到两百行,很简单。

...
type config struct {Containers []corev1.Container
 Volumes []corev1.Volume
func main() {
 // Watch uninitialized Deployments in all namespaces.
 restClient := clientset.AppsV1beta1().RESTClient()
 watchlist := cache.NewListWatchFromClient(restClient,  deployments , corev1.NamespaceAll, fields.Everything())
 // Wrap the returned watchlist to workaround the inability to include
 // the `IncludeUninitialized` list option when setting up watch clients.
 includeUninitializedWatchlist :=  cache.ListWatch{ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
 options.IncludeUninitialized = true
 return watchlist.List(options)
 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
 options.IncludeUninitialized = true
 return watchlist.Watch(options)
 resyncPeriod := 30 * time.Second
 _, controller := cache.NewInformer(includeUninitializedWatchlist,  v1beta1.Deployment{}, resyncPeriod,
 cache.ResourceEventHandlerFuncs{AddFunc: func(obj interface{}) {err := initializeDeployment(obj.(*v1beta1.Deployment), c, clientset)
 if err != nil {log.Println(err)
 stop := make(chan struct{})
 go controller.Run(stop)
 signalChan := make(chan os.Signal, 1)
 signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
 -signalChan
 log.Println(Shutdown signal received, exiting...)
 close(stop)
func initializeDeployment(deployment *v1beta1.Deployment, c *config, clientset *kubernetes.Clientset) error {if deployment.ObjectMeta.GetInitializers() != nil {pendingInitializers := deployment.ObjectMeta.GetInitializers().Pending
 if initializerName == pendingInitializers[0].Name {log.Printf( Initializing deployment: %s , deployment.Name)
 o, err := runtime.NewScheme().DeepCopy(deployment)
 if err != nil {
 return err
 initializedDeployment := o.(*v1beta1.Deployment)
 // Remove self from the list of pending Initializers while preserving ordering.
 if len(pendingInitializers) == 1 {initializedDeployment.ObjectMeta.Initializers = nil} else {initializedDeployment.ObjectMeta.Initializers.Pending = append(pendingInitializers[:0], pendingInitializers[1:]...)
 if requireAnnotation {a := deployment.ObjectMeta.GetAnnotations()
 _, ok := a[annotation]
 if !ok {log.Printf( Required  %s  annotation missing; skipping envoy container injection , annotation)
 _, err = clientset.AppsV1beta1().Deployments(deployment.Namespace).Update(initializedDeployment)
 if err != nil {
 return err
 return nil
 // Modify the Deployment s Pod template to include the Envoy container
 // and configuration volume. Then patch the original deployment.
 initializedDeployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, c.Containers...)
 initializedDeployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, c.Volumes...)
 oldData, err := json.Marshal(deployment)
 if err != nil {
 return err
 newData, err := json.Marshal(initializedDeployment)
 if err != nil {
 return err
 patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1beta1.Deployment{})
 if err != nil {
 return err
 _, err = clientset.AppsV1beta1().Deployments(deployment.Namespace).Patch(deployment.Name, types.StrategicMergePatchType, patchBytes)
 if err != nil {
 return err
 return nil
func configmapToConfig(configmap *corev1.ConfigMap) (*config, error) {
 var c config
 err := yaml.Unmarshal([]byte(configmap.Data[ config]),  c)
 if err != nil {
 return nil, err
 return  c, nil
}

Kubernetes 1.9 对 Initializers 的增强

kubectl annotate, apply, edit-last-applied, delete, describe, edit, get, label, set 命令可以增加 –include-uninitialized 来对 uninitialized 进行操作;

Initializers 的启用不需要手动配置 feature gate,admission controll 中配置后会自动添加到 feature gate 中;

Initializer 名称至少包含两个.,分隔成至少 3 段;

Fixes an initializer bug where update requests which had an empty pending initializers list were erroneously rejected.

到此,相信大家对“如何启用 Initializers”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计10234字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)