使用 Python 编写 Kubernetes 验证 Webhook
在我对 Kubernetes 的不断学习中,我的兴趣有所偏离并转向了它提供的扩展。
对Operator、自定义资源、定义、webhooks 以及 Kubernetes 提供的其他东西产生了更多的好奇心。
在阅读和研究了一段时间后,我决定尝试 构建自己的验证 webhook 。
作为一个知名的项目,它非常出色,需要考虑很多事情,直到一切都组合在一起并开始工作。
现在一起了解下什么是 webhook 或准入控制器?
An admission controller is a piece of code that intercepts requests to the Kubernetes API server before persistence of the object, but after the request is authenticated and authorized.
准入控制器是一段代码,它在对象持久化之前,但在请求经过身份验证和授权之后,拦截对Kubernetes API服务器的请求。
在 Kubernetes 中,有两种类型的准入控制器,称为 ValidatingAdmissionWebhook 和 MutatingAdmissionWebhook 。
正如他们的名字所暗示的那样;一个只验证请求,另一个在不符合规范的情况下修改请求。
现在我不会详细介绍它们。如果您想阅读更多内容,这里有一个指向官方文档的链接,其中包含更广泛的解释。
https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
webhooks 的伟大之处在于,您 可以使用您喜欢的任何语言编写自己的语言以及自定义逻辑,无论是用于验证还是更改请求 。
我决定从使用 Python 和 Flask 框架的验证 webhook 开始。
只编写 webhook 逻辑是一回事,但设置它并使其工作是另一回事。
为了引导整个事情,在我的情况下它需要:
-
Python 和 Flask 框架
-
Docker
-
SSL证书
-
还有一堆 Kubernetes 文件(服务、部署、密钥……)
一个简单的网络钩子就完成了这一切?!
虽然看起来有点乏味,但我保证这是一个非常有趣和有益的项目。
项目文件也可以在我的GitHub 存储库中找到。
https://github.com/k-mitevski/kubernetes-validating-webhook
让我们从头开始。
Python代码
有了这个基本功能,我用不到 50 行的 Python 代码编写了 webhook 代码。使用 Flask 使这变得非常容易。
互联网上有很多示例,您可以从中汲取灵感来编写它。
它最终会归结为两件事。
你需要:
-
分析请求并根据设定的规则验证或改变它
-
将 HTTP 响应发送回 Kubernetes 准入控制器
您添加到代码中的所有其他内容都是加分项。
此验证 webhook 将检查标签是否存在于部署创建中。
如果不是,则请求被阻止并显示错误消息。
我添加了从环境变量设置所需标签的选项,还包括日志记录。
最初,我想用 Django,但认为这对它来说太过分了,所以我选择用 Flask 来做。
它最终变得非常干净和简单:
from flask import Flask, request, jsonify
from os import environ
import logging
webhook = Flask(__name__)
webhook.config['LABEL'] = environ.get('LABEL')
webhook.logger.setLevel(logging.INFO)
if "LABEL" not in environ:
webhook.logger.error("Required environment variable for label isn't set. Exiting...")
exit(1)
@webhook.route('/validate', methods=['POST'])
def validating_webhook():
request_info = request.get_json()
uid = request_info["request"].get("uid")
if request_info["request"]["object"]["metadata"]["labels"].get(webhook.config['LABEL']):
webhook.logger.info(f'Object {request_info["request"]["object"]["kind"]}/{request_info["request"]["object"]["metadata"]["name"]} contains the required \"{webhook.config["LABEL"]}\" label. Allowing the request.')
return admission_response(True, uid, f"{webhook.config['LABEL']} label exists.")
else:
webhook.logger.error(f'Object {request_info["request"]["object"]["kind"]}/{request_info["request"]["object"]["metadata"]["name"]} doesn\'t have the required \"{webhook.config["LABEL"]}\" label. Request rejected!')
return admission_response(False, uid, f"The label \"{webhook.config['LABEL']}\" isn't set!")
def admission_response(allowed, uid, message):
return jsonify({"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response":
{"allowed": allowed,
"uid": uid,
"status": {"message": message}
}
})
if __name__ == '__main__':
webhook.run(host='0.0.0.0', port=5000)
第一件事是设置环境变量和日志记录。
初始if条件检查标签环境变量不为空,这是 webhook 工作所必需的。
从那里,在 POST 请求时,代码检查设置标签是否存在于 Deployment 的metadata字段中。如果标签不存在,准入控制器将拒绝部署。
我添加了记录器功能,因此它会根据请求的状态打印出一条日志消息。
第二个功能只是响应准入控制器。如果允许请求是true或 ,则使用 HTTP 响应false。
该Kubernetes文档,也有例子的反应应该是什么样子等。
https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response
如文档中所述,UID和allowed字段必须出现在AdmissionReview响应中。
现在有一件至关重要的事情……您必须在 来自 Webhook 和准入控制器的流量之间提供 SSL 加密 !
您选择如何提供证书和密钥取决于您。
您可以将它们打包到 docker 镜像中,也可以使用 Kubernetes 密钥 单独注入它们。我选择了后者。
在requirements.txt与库可以生成,或在 这里 存储库中找到。
https://github.com/k-mitevski/kubernetes-validating-webhook/blob/master/requirements.txt
Dockerfile 参考:
FROM ubuntu:20.10
RUN apt-get update -y && apt-get install -y python3-pip python-dev
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip3 install -r /app/requirements.txt
COPY validate.py /app
COPY wsgi.py /app
CMD gunicorn --certfile=/certs/webhook.crt --keyfile=/certs/webhook.key --bind 0.0.0.0:443 wsgi:webhook
对于镜像,您可以选择所需的任何基本镜像。
其他事情是安装 Python、Flask、Gunicorn、库,并复制代码。
重要的是证书和密钥。
Gunicorn 将查看/certs路径,因此当它们安装在 pod 上时,它们必须在那里可用。
注意:如果您不熟悉,Gunicorn是应用服务器,它将通过WSGI协议将请求转发到您的 webhook 应用程序。
Deployment文件
部署 webhook 需要四个部分才能完成。
首先是 webhook 部署:
apiVersion: apps/v1
kind: Deployment
metadata:
name: validation-webhook
labels:
app: validate
spec:
replicas: 1
selector:
matchLabels:
app: validate
template:
metadata:
labels:
app: validate
spec:
containers:
- name: webhook
image: kmitevski/webhook:gunicorn
ports:
- containerPort: 443
env:
- name: LABEL
value: development
volumeMounts:
- name: certs-volume
readOnly: true
mountPath: "/certs"
imagePullPolicy: Always
volumes:
- name: certs-volume
secret:
secretName: admission-tls
webhook 可用的服务:
apiVersion: v1
kind: Service
metadata:
name: validate
spec:
selector:
app: validate
ports:
- port: 443
包含证书和密钥的Secret清单:
apiVersion: v1
kind: Secret
metadata:
name: admission-tls
type: Opaque
data:
webhook.crt: YOUR ENCODED BASE64 CERT
webhook.key: YOUR ENCODED BASE64 KEY
最后是验证 Webhook 配置文件:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook
webhooks:
- name: validate.default.svc
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1","v1beta1"]
rules:
- apiGroups: ["apps", ""]
resources:
- "deployments"
apiVersions:
- "*"
operations:
- CREATE
clientConfig:
service:
name: validate
namespace: default
path: /validate/
caBundle: YOUR ENCODED BASE64 CERT
一起编译
为了将所有内容整合在一起并使 webhook 正常工作,您应该按照某种顺序部署事物。
我首先生成证书(您需要将其提供给 Gunicorn)和 webhook 配置。
我有很多尝试和错误要做,直到整个事情就位并开始工作。
生成证书
要生成证书,请使用该openssl工具:
openssl req -x509 -sha256 -newkey rsa:2048 -keyout webhook.key -out webhook.crt -days 1024 -nodes -addext "subjectAltName = DNS.1:validate.default.svc"
现在subjectAltName有一个问题,SAN –证书中必须包含 DNS 记录!
您必须将该 DNS 与您的服务名称和命名空间相匹配。记号是 service_name.namespace.svc 。
如果不这样做,您可能会感到苦涩,并希望 rm -rf * 整个事情。
我多次接近这个!
一切都会运行,但是当您决定测试 webhook 时,会弹出这个令人讨厌的消息:
open ssl x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0
要牢记这一点,您需要 在多个地方引用服务的名称 。
确保它保持不变,否则以后更改它将需要更改证书和 webhook 配置。
创建 Kubernetes 密钥
Kubernetes Secret 需要包含以 base64 格式编码的证书和密钥。
这在 Linux 上很容易做到:
cat webhook.key | base64 | tr -d '\n'
#LS0tLS1CRUdJTiBQUklWQVRFI....
cat webhook.crt | base64 | tr -d '\n'
#LS0tLS1CRUdJTiBDRVJUSUZJQ0FUR
复制webhook.crt输出并将其粘贴到密钥和 webhook 配置清单。
也复制webhook.key,尽管密钥只需要包含在 Secret 中。
复制时要小心,以免在提示中包含您的用户名:)。
Pod 规范中的挂载点设置在/certs.
您可以更改它,但请确保使用在 Gunicorn 证书和密钥路径中完成的更改创建一个新镜像。
创建镜像
文件夹和文件结构是这样的:
tree .
.
|-- Dockerfile
|-- kubernetes-manifests
| |-- label.yaml
| |-- webhook-config.yaml
| |-- webhook-deploy.yaml
| |-- webhook-secret.yaml
| `-- webhook-service.yaml
|-- requirements.txt
|-- validate.py
|-- webhook.crt
|-- webhook.key
`-- wsgi.py
我从根文件夹构建了 Docker 镜像。包括Dockerfile 所在的位置,以及 Python 文件和证书。
这wsgi.py只是 Gunicorn 运行 webhook 应用程序的帮助文件。
如果您打算使用自己的镜像,则需要使用以下命令作为示例来构建、标记并将其推送到存储库:
docker build -t webhook:gunicorn -f Dockerfile .
docker tag webhook:gunicorn kmitevski/webhook
docker push kmitevski/webhook:gunicorn
引导应用程序
-
创建证书
-
创建 Docker 镜像
-
将 Secret 清单应用到集群
-
应用部署、服务和 Webhook 配置清单
-
测试网络钩子
为了进行测试,您可以尝试使用 Nginx 镜像创建一个简单的部署。
这可以使用命令式方法轻松测试:
$ kubectl create deploy nginx --image=nginx
error: failed to create deployment: admission webhook "validate.default.svc" denied the request: The label "development" isn't set!
如果您检查 webhook pod 日志:
ERROR in validate: Object Deployment/nginx doesn't have the required "development" label. Request rejected!
现在尝试部署label.yaml清单。
$ kubectl apply -f label.yaml
deployment.apps/nginx created
INFO in validate: Object Deployment/nginx contains the required "development" label. Allowing the request.
终于成功了!!验证网络钩子有效!
本文作者:MITEVSKI
文章翻译:CloudNative.CC
文章来源:https://kmitevski.com/writing-a-kubernetes-validating-webhook-using-python/
- 用更云原生的方式做诊断|大规模 K8s 集群诊断利器深度解析
- 多个维度分析k8s多集群管理工具,到底哪个才真正适合你
- 使用 Kube-capacity CLI 查看 Kubernetes 资源请求、限制和利用率
- 使用 Go 在 Kubernetes 中构建自己的准入控制器
- 云原生数仓如何破解大规模集群的关联查询性能问题?
- 云原生趋势下的迁移与灾备思考
- 2022 年不容错过的六大云原生趋势!
- 使用 Prometheus 监控 Golang 应用程序
- 云原生时代下的机遇与挑战 DevOps如何破局
- 如何在云原生格局中理解Kubernetes合规性和安全框架
- 设计云原生应用程序的15条基本原则
- 使用 Operator SDK 为 Pod 标签编写Controller
- Kubernetes Visitor 模式
- 为什么云原生是第二次云革命
- 构建云原生安全的六个重要能力
- 扩展云原生策略的步骤有哪些?
- 七个值得关注的开源云原生工具
- K8S - 创建一个 kube-scheduler 插件
- 如何诊断 Kubernetes 应用程序中的 OOMKilled 错误
- 云原生 Kubernetes 分布式存储平台 Longhorn 初体验