#作者:曹付江
文章目录
- 1、什么是 Admission Controllers?
- 2、如何创建 Admission Controllers?
- 3、Admission 控制器的最佳实践
K8s 中的操作与安全标准执行机制:

1、什么是 Admission Controllers?
Admission controllers 是一段拦截请求的代码,它会在请求被持久化之前验证或更改(变更)这些请求。Admission controllers 与外部 WebHook 连接,处理入驻请求。因此,它们也被称为 Admission WebHook。该 WebHook 在对象创建、修改或删除最终确定之前接收 K8s 的入驻请求。
我们可以定义两种类型的 Admission controllers:
- MutatingAdmissionWebhook:该控制器既可以验证也可以更改请求。
- ValidatingAdmissionWebhook:该控制器只能验证请求。
Admission Controller 是在需要强制执行特定组织政策时最有效的选项之一,例如: - 确保所有容器都有资源限制,以避免资源占用过多。
- 阻止某些常见的镜像标签。
- 强制执行资源的命名规范。
- 限制仅允许来自受信任注册表的批准容器镜像。
检查内建的 Admission controllers:
2、如何创建 Admission Controllers?
要创建一个 Mutating Admission Webhook,我们需要以下内容:
- 用于 Webhook 的 Web 应用程序
- 运行 Web 应用程序的服务帐户
- 用于托管 Web 应用程序的 Deployment
- 用于将流量路由到 Web 应用程序的 Service
- 一个 ClusterRole,定义 API 级别的访问权限(此博客未详细介绍)
- 一个 ClusterRoleBinding,将服务帐户与 ClusterRole 关联
- 一个 Secret,包含 Web 应用程序用于提供 TLS 的证书。(K8s 会向我们的服务发起 HTTPS 调用,因此需要有效的 SSL/TLS 证书)
WebHook 服务器
在本文章中,我将使用基于 Python 的 Webhook,使用 Sanic 框架。
FROM sanicframework/sanic:LTS
RUN pip install jsonpatch
COPY apps.py apps.py
ENTRYPOINT ["sanic", "apps:app", "--host=0.
上面的 Dockerfile 将创建用于运行 Web 服务器的镜像。这里我们挂载了一个卷(来自 secret),该卷将用于 TLS 证书。
apps.py 的内容:
import base64
import json as nativejson
from copy import deepcopy
from pprint import pformatimport jsonpatch
from sanic import Request, Sanic
from sanic.log import logger
from sanic.response import jsonapp = Sanic(name=__name__)@app.post("/validate")
async def validate(request: Request):allowed = Truemessage = ""try:pass # add logic to validate th# update allowed & message accordinglyexcept KeyError:passreturn json({"response": {"allowed": allowed,"uid": request.json["request"]["uid"],"status": {"message": message},}})@app.post("/mutate")
async def mutate(request: Request):logger.info(f"Obtained Request \n {pformat(request.json)}")original_spec = request.json["request"]["object"]modified_spec = deepcopy(spec)try:pass# add logic to update# update the modified_specexcept KeyError:passpatch = jsonpatch.JsonPatch.from_diff(spec, modified_spec)return json({"response": {"allowed": True,"uid": request.json["request"]["uid"],"patch": base64.b64encode(nativejson.dumps(patch).encode()).decode(),"patchtype": "JSONPatch",}})if __name__ == "__main__":app.run(host="0.0.0.0", port=443)
Kubernetes 对验证的期望是返回一个简单的响应,值为 False 或 True。因此,在上述代码中,我们返回 allowed 为 True 或 False。在变更 Webhook 的情况下,我们还返回补丁(新旧对象的差异)。
创建 Webhook 的清单:
apiVersion: v1
kind: ServiceAccount
metadata:name: admission-controllernamespace: admission-webhook-ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: admission-demo-rbac
subjects:- kind: ServiceAccountname: admission-controllernamespace: admission-webhook-ns
roleRef:kind: ClusterRolename: cluster-adminapiGroup: rbac.authorization.k8s.io---
apiVersion: v1
kind: Service
metadata:name: admission-webhook-svcnamespace: admission-webhook-nslabels:app: admission-webhook-demo
spec:ports:- port: 443targetPort: 3030selector:app: admission-webhook-demo
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:name: admission-controller-certnamespace: admission-webhook-ns
spec:dnsNames:- admission-webhook-svc- admission-webhook-svc.admission-webhook-ns- admission-webhook-svc.admission-webhook-ns.svcissuerRef:kind: ClusterIssuername: ca-issuersecretName: admission-controller-secret
---
apiVersion: apps/v1
kind: Deployment
metadata:name: admission-webhook-deploymentnamespace: admission-webhook-nslabels:app: admission-webhook-demo
spec:replicas: 1selector:matchLabels:app: admission-webhook-demotemplate:metadata:labels:app: admission-webhook-demospec:serviceAccountName: admission-controllercontainers:- name: admission-webhook-demoimage: asrathore08/admission-controller:latestimagePullPolicy: Alwaysresources:limits:cpu: 1000mmemory: 512MivolumeMounts:- name: webhook-certsmountPath: /mnt/certsreadOnly: true- name: admission-controller-confmountPath: /mnt/confvolumes:- name: webhook-certssecret:secretName: admission-controller-secret- name: admission-controller-confconfigMap:name: admission-controller-configmap
在 Kubernetes 中注册 Webhook:
创建一个 ValidatingWebhookConfiguration,指向我们的 webhook 服务器。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:name: admission-service-delete-pod-validatenamespace: admission-webhook-nslabels:app: admission-webhook-demoannotations:cert-manager.io/inject-ca-from: admission-webhook-ns/admission-controller-cert
webhooks:
- name: validate.webhook.democlientConfig:service:name: admission-webhook-svcnamespace: admission-webhook-nspath: "/validate"admissionReviewVersions: ["v1"]sideEffects: Nonerules:- operations: ["CREATE", "UPDATE"]apiGroups: [""]apiVersions: ["v1", "v1beta1"]resources: ["configmaps"]scope: "Namespaced"namespaceSelector:matchLabels:demo-admission-validation: enabledfailurePolicy: Ignore
注意:这里我使用了 cert-manager CRD 来生成 TLS/SSL 证书。注解 cert-manager.io/inject-ca-from 有助于获取证书。
创建一个 MutatingWebhookConfiguration,指向我们的 webhook 服务器:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:name: admission-service-configmap-mutatenamespace: admission-webhook-nslabels:app: mutating-admission-webhookannotations:cert-manager.io/inject-ca-from: admission-webhook-ns/admission-controller-cert
webhooks:
- name: mutate.webhook.democlientConfig:service:name: admission-webhook-svcnamespace: admission-webhook-nspath: "/mutate"admissionReviewVersions: ["v1"]sideEffects: Nonerules:- operations: ["CREATE", "UPDATE"]apiGroups: [""]apiVersions: ["v1", "v1beta1"]resources: ["configmaps"]scope: "Namespaced"namespaceSelector:matchLabels:demo-admission-validation: enabledfailurePolicy: Ignore
上面,我们定义了一个 mutating 和一个 validating webhook。我们只启用了这些 webhooks 处理在具有标签 demo-admission-validation 设置为 enabled 的命名空间中创建或更新的对象。此功能允许我们控制 webhook 的作用范围。
- MutatingAdmissionWebhook — 转换器
- ValidatingAdmissionWebhook — 审核员
注意:在上述 webhook 中,我将资源限制为 configmap。K8s 上的 Apache Spark 使用 configmap 来存储 Spark 配置。这种 webhook 可以控制用户为某些配置提供的值,并且可以为某些配置注入特定的默认值。
一旦部署,Webhook 将接收以下请求体的请求。
#
{"kind": "AdmissionReview","request": {"kind": {"kind": "Pod","version": "v1","group": ""},"resource": {"resource": "pods","version": "v1","group": ""},"uid": "b06b6ec2-681d-11e9-a645-06b44ed6a042","object": {"status": {},"spec": {"dnsPolicy": "ClusterFirst","securityContext": {},"serviceAccountName": "","schedulerName": "default-scheduler","serviceAccount": "","priority": 0,"terminationGracePeriodSeconds": 30,"restartPolicy": "Always","containers": [{"name": "","image": "","imagePullPolicy": "Always","ports": [{"protocol": "TCP","containerPort": 80}],"resources": {}}]},"metadata": {}},"namespace": "","userInfo": {"username": "","groups": ["system:masters","system:authenticated"]},"oldObject": null,"dryRun": false,"operation": "CREATE"},"apiVersion": "admission.k8s.io/v1beta1"
}
控制器将以特定格式返回响应。响应格式如下所述。
# Response
{"body": {"kind": "AdmissionReview","apiVersion": "admission.k8s.io/v1","response": {"uid": "request.uid","allowed": "True","patch": "patch_base64","patchType": "JSONPatch"},"headers": {"Content-Type": "application/json"},"statusCode": 200
}# patch_base64[{"op": "replace","path": "/spec/containers/0/image","value": "xxxx.dkr.ecr.us-west-2.amazonaws.com/nginx:latest"}
]
3、Admission 控制器的最佳实践
- 我们应该确保 webhook 尽可能轻量化。
- 我们应该在开发环境中彻底测试我们的 webhook,以确保它们不会无意中阻止合法的请求。
- 我们应该详细记录 admission 控制决策,并对被拒绝的请求进行故障排除。