使用 Python 編寫 Kubernetes 驗證 Webhook

語言: CN / TW / HK

在我對 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 中,有兩種型別的准入控制器,稱為 ValidatingAdmissionWebhookMutatingAdmissionWebhook

正如他們的名字所暗示的那樣;一個只驗證請求,另一個在不符合規範的情況下修改請求。

現在我不會詳細介紹它們。如果您想閱讀更多內容,這裡有一個指向官方文件的連結,其中包含更廣泛的解釋。

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 使這變得非常容易。

網際網路上有很多示例,您可以從中汲取靈感來編寫它。

它最終會歸結為兩件事。

你需要:

  1. 分析請求並根據設定的規則驗證或改變它

  2. 將 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

引導應用程式

  1. 建立證書

  2. 建立 Docker 映象

  3. 將 Secret 清單應用到叢集

  4. 應用部署、服務和 Webhook 配置清單

  5. 測試網路鉤子

為了進行測試,您可以嘗試使用 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/