使用 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 錯誤