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。