共計 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 網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!