1.什么是Admission Controller


Admission Controller(准入控制)是Kubernetes API Server用于拦截请求的一种手段。Admission可以做到对请求的资源对象进行校验,修改。service mesh最近很火的项目Istio天生支持Kubernetes,利用的就是admission对服务实例自动注入sidecar。

假如对Kubernetes有一定的了解的话,应该会知道在Kubernetes中还有authn/authz,为什么还会引入admission这种机制?

1)authn/authz 是Kubernetes的认证鉴权,运行在filter中,只能获取http请求header以及证书,并不能获取请求的body。所以authn/authz只能对客户端进行认证和鉴权,不可以对请求的对象进行任何操作,因为这里根本还获取不到对象。

2)Admission运行在API Server的增删改查handler中,可以自然地操作API resource。


下面将对Admission Controller工作流做一番详解。 


API Server接收到客户端请求后首先进行认证鉴权,认证鉴权通过后才会进行后续的endpoint handler处理。

1)当API Server接收到对象后首先根据http的路径可以知道对象的版本号,然后将request body反序列化成versioned object.

2)versioned object转化为internal object,即没有版本的内部类型,这种资源类型是所有versioned类型的超集。只有转化为internal后才能适配所有的客户端versioned object的校验。

3)Admission Controller具体的admit操作,可以通过这里修改资源对象,例如为Pod挂载一个默认的Service Account等。

4)API Server internal object validation,校验某个资源对象数据和格式是否合法,例如:Service Name的字符个数不能超过63等。

5)Admission Controller validate,可以自定义任何的对象校验规则。

6)internal object转化为versioned object,并且持久化存储到etcd。


注:以上versioned object和internal object直接的转换关系会在《深度剖析Kubernetes API Server三部曲 - part 2》详细解释,欢迎持续关注。


2.如何使用admission controller


Kubernetes 1.10之前的版本可以使用--admission-control打开Admission Controller。同时--admission-control的顺序决定Admission运行的先后。其实这种方式对于用户来讲其实是挺复杂的,因为这要求用户对所有的Admission Controllers需要完全了解。


如果使用Kubernetes 1.10之后的版本,--admission-control已经废弃,建议使用

--enable-admission-plugins --disable-admission-plugins 指定需要打开或者关闭的Admission Controller。 同时用户指定的顺序并不影响实际Admission Controllers的执行顺序,对用户来讲非常友好。


值得一提的是,有些Admission Controller可能会使用Alpha版本的API,这时必须首先使能其使用的API版本。否则Admission Controller不能工作,可能会影响系统功能。


2.1 webhook admission


目前Kubernetes中已经有非常多的Admission插件, 但是并不能保证满足所有开发者的需求。 众所周知,Kbernetes之所以受到推崇,它的可扩展能力功不可没。Admission也提供了一种webhook的扩展机制。 


● MutatingAdmissionWebhook:在对象持久化之前进行修改

● ValidatingAdmissionWebhook:在对象持久化之前进行


可能有读者接触过另外一种动态可扩展的机制Initializers,不过至今还是Apha特性,社区讨论有可能会把它移除。所以选择动态Admission首选webhook。


2.2 如何使用webhook admission


Webhook Admission属于同步调用,需要用户部署自己的webhook server,创建自定义的配置资源对象: ValidatingWebhookConfiguration或MutatingWebhookConfiguration。


● 开发webhook server

这里我推荐参考社区e2e测试用的server,对细节源代码感兴趣的读者可以自行参考

https://github.com/kubernetes/kubernetes/blob/v1.10.0-beta.1/test/images/webhook/main.go,这里面利用golang 标准库实现的一个基本的http server,并注册多个路由,同时服务于多种resource的准入控制。重点关注一下资源对象的decode过程,这是k8s apimachinery的高级功能。利用了apimachinery的scheme的能力,使用之前必须要将api注册到scheme中,代码详见:

(https://github.com/kubernetes/kubernetes/blob/v1.10.0-beta.1/test/images/webhook/scheme.go)。一个典型的webhook修改资源对象(Pod)的样例代码如下所示。


   func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
  
glog.V(2).Info("mutating pods")
  
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
  
if ar.Request.Resource != podResource {
     
glog.Errorf("expect resource to be %s", podResource)
     
return nil
  
}

  
raw := ar.Request.Object.Raw
  
pod := corev1.Pod{}
  
deserializer := codecs.UniversalDeserializer()
// pod的解码,利用apimachinery
  
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
     
glog.Error(err)
     
return toAdmissionResponse(err)
   }
  
reviewResponse := v1beta1.AdmissionResponse{}
  
reviewResponse.Allowed = true
  
if pod.Name == "webhook-to-be-mutated" {
     
reviewResponse.Patch = []byte(addInitContainerPatch)
     
pt := v1beta1.PatchTypeJSONPatch
     
reviewResponse.PatchType = &pt
  
}
  
return &reviewResponse
  }


● 部署webhook server



# kubectl create –f webhook-server.yaml


apiVersion: v1
kind: Namespace
metadata:
  name:
e2e-tests-webhook-gbgt6
spec:
  finalizers:
 
- kubernetes
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app:
sample-webhook
   
webhook: "true"
 
name: sample-webhook-deployment
 
namespace: e2e-tests-webhook-gbgt6
spec:
  replicas:
1
 
selector:
    matchLabels:
      app:
sample-webhook
     
webhook: "true"
 
template:
    metadata:
      labels:
        app:
sample-webhook
       
webhook: "true"
   
spec:
      containers:
     
- args:
       
- --tls-cert-file=/webhook.local.config/certificates/tls.crt
        - --tls-private-key-file=/webhook.local.config/certificates/tls.key
        - --alsologtostderr
        - -v=4
        - 2>&1
       
image: gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.10v2
       
imagePullPolicy: IfNotPresent
       
name: sample-webhook
       
volumeMounts:
       
- mountPath: /webhook.local.config/certificates
         
name: webhook-certs
         
readOnly: true
     
volumes:
     
- name: webhook-certs
        
secret:
          defaultMode:
420
         
secretName: sample-webhook-secret
---
apiVersion: v1
kind: Service
metadata:
  labels:
    test:
webhook
 
name: e2e-test-webhook
 
namespace: e2e-tests-webhook-gbgt6
spec:
  ports:
 
- port: 443
   
protocol: TCP
   
targetPort: 443
 
selector:
    webhook:
"true"
 
sessionAffinity: None
 
type: ClusterIP

 

创建webhook server Deployment以及Service,供API Server调用。


● 创建MutatingWebhookConfiguration

 


# kubectl create –f webhook-config.yaml


apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  name:
e2e-test-mutating-webhook-pod
webhooks:
- clientConfig:
    caBundle:
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMyRENDQWNDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFkTVJzd0dRWURWUVFERXhKbE1tVXQKYzJWeWRtVnlMV05sY25RdFkyRXdIaGNOTVRnd056RTVNRGMwT1RJeFdoY05Namd3TnpFMk1EYzBPVEl4V2pBZApNUnN3R1FZRFZRUURFeEpsTW1VdGMyVnlkbVZ5TFdObGNuUXRZMkV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBCkE0SUJEd0F3Z2dFS0FvSUJBUURFVVFEWVN6SGl3SUFHU1dHSWRBSmVBbnMrNFhaYjlZc3VuQlBVTkJPdHZqeFoKV3NSbUxydE0zVU9lcEszeGsvMzZCSS96RkdXdUNpMlJ0TWUxSWtEa2tVMzNEZE83K0ExVyt2NVZNVnFqL0lDTApsc29USml3TFhTcGowTHNwSUNVdGtqT1dlRjVhK3lJVHgyR01TMG9ZbWtuaHB0RXMrc2tKQjFMWm1uVTBaWFpzClRKak9Lb05ueHdVaTl4QnRUTXBQRWw2cVhmb3dCWlpvYjlkUzNtNzFLbjJCdU5Ec0s3YnVRcGJvdk9XdUQyNDAKdzNLQVJnT04xcjA4Vm4zd1I1MHVXS09tSkVsLzRUZ2JnSTRkaG85WHNIWUhUdnk4R3JRMXhYZE43ZEhSTlpHNQo5aDhmOUUzdjg1VWxwSEVWQThqUHB4RE5SSm9qRXVGQk9raFJEZEY1QWdNQkFBR2pJekFoTUE0R0ExVWREd0VCCi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDWWl4VUsKYkhsRUpCK2t4THdqdktySDQ1OVVsNUJjb0VXZE1BNnArUC8yWXVZa2NuWC9GRVNjUFRxUS9vdkF3ejU1ZG1FUwpJTjVZOWd2ZlJxdWhZcEdWOHVFSWpzVkczTjdKQm1wM0NyclEyd3FYeHV3cndkVXV1dDltQSt2RkQ4Q2FQSE8xCmVad1J6NEkzTktFQ0xHMHJXQWxseEVvUm9tQ2UvaWZIUnRNRklTRk5sSnZVNlhIbzFDVWNFQ2FwOG9hYXN2cFcKT2JBQjVqQzc5WWJXN2lWVm54cjZGMnRvOG9oSEdNSEpXR1pwSTNKb***bGVOK01kVm5ySFdXSXBkOG9iS2E3TgpqSlZTczgzRmlDMzd4d2dqMUQyaTNHUnh5bHNKZEdJWTl4WVpQVmNNUTh6Z2FMMUpJUk1BdVZYbHczUkRzSDR0Cms5WmFybGY1NG9BOUN0Nk8KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
   
service:
      name:
e2e-test-webhook
     
namespace: e2e-tests-webhook-gbgt6
     
path: /mutating-pods
 
failurePolicy: Ignore
 
name: adding-init-container.k8s.io
 
namespaceSelector: {}
 
rules:
 
- apiGroups:
   
- ""
   
apiVersions:
   
- v1
   
operations:
   
- CREATE
   
resources:
   
- pods


rules表示对于core/v1/pods资源对象创建的时候调用mutating webhook。server的地址及路径通过clientConfig指明。

/mutating-pods是指调用webhook server执行mutatePods,为pod增加init initContainers


func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
  
glog.V(2).Info("mutating pods")
  
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
  
if ar.Request.Resource != podResource {
     
glog.Errorf("expect resource to be %s", podResource)
     
return nil
  
}

  
raw := ar.Request.Object.Raw
  
pod := corev1.Pod{}
  
deserializer := codecs.UniversalDeserializer()
  
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
     
glog.Error(err)
     
return toAdmissionResponse(err)
   }
  
reviewResponse := v1beta1.AdmissionResponse{}
  
reviewResponse.Allowed = true
  
if pod.Name == "webhook-to-be-mutated" {
     
reviewResponse.Patch = []byte(addInitContainerPatch)
     
pt := v1beta1.PatchTypeJSONPatch
     
reviewResponse.PatchType = &pt
  
}
  
return &reviewResponse
}

 

创建Pod


kubectl create –f pod.yaml


apiVersion: v1
kind: Pod
metadata:
  name:
webhook-to-be-mutated
 
namespace: e2e-tests-webhook-gbgt6
spec:
  containers:
 
- image: k8s.gcr.io/pause:3.1
   
name: example


查询Pod



# kubectl get pod webhook-to-be-mutated –n e2e-tests-webhook-gbgt6 -oyaml



apiVersion: v1
kind: Pod
metadata:
  creationTimestamp:
2018-07-19T07:49:37Z
 
name: webhook-to-be-mutated
 
namespace: e2e-tests-webhook-gbgt6
 
resourceVersion: "806"
 
selfLink: /api/v1/namespaces/e2e-tests-webhook-gbgt6/pods/webhook-to-be-mutated
 
uid: 48d2e91d-8b28-11e8-b16d-286ed488dc10
spec:
  containers:
 
- image: k8s.gcr.io/pause:3.1
   
imagePullPolicy: IfNotPresent
   
name: example
   
resources: {}
   
terminationMessagePath: /dev/termination-log
   
terminationMessagePolicy: File
   
volumeMounts:
   
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
     
name: default-token-jhqlb
     
readOnly: true
 
dnsPolicy: ClusterFirst
 
initContainers:
 
- image: webhook-added-image
   
imagePullPolicy: Always
   
name: webhook-added-init-container
   
resources: {}
   
terminationMessagePath: /dev/termination-log
   
terminationMessagePolicy: File
 
nodeName: 127.0.0.1
 
priority: 0
 
restartPolicy: Always
 
schedulerName: default-scheduler
 
securityContext: {}
 
serviceAccount: default
 
serviceAccountName: default
 
terminationGracePeriodSeconds: 30
 
tolerations:
 
- effect: NoExecute
   
key: node.kubernetes.io/not-ready
   
operator: Exists
   
tolerationSeconds: 300
  -
effect: NoExecute
   
key: node.kubernetes.io/unreachable
   
operator: Exists
   
tolerationSeconds: 300
 
volumes:
 
- name: default-token-jhqlb
   
secret:
      defaultMode:
420
     
secretName: default-token-jhqlb

 

可以看出,创建成功的pod已经多了一个名字为webhook-added-init-container的initContainers。


最后我们来总结下webhook Admission的优势


webhook可动态扩展Admission能力,满足自定义客户的需求

不需要重启API Server,可通过创建webhook configuration热加载webhook admission。