使用 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 中,有兩種類型的准入控制器,稱爲 ValidatingAdmissionWebhookMutatingAdmissionWebhook

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

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

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

webhooks 的偉大之處在於,您可以使用您喜歡的任何語言編寫自己的語言以及自定義邏輯,無論是用於驗證還是更改請求

我決定從使用 Python 和 Flask 框架的驗證 webhook 開始。

只編寫 webhook 邏輯是一回事,但設置它並使其工作是另一回事。

爲了引導整個事情,在我的情況下它需要:

一個簡單的網絡鉤子就完成了這一切?!

雖然看起來有點乏味,但我保證這是一個非常有趣和有益的項目。

項目文件也可以在我的 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 密鑰單獨注入它們。我選擇了後者。

Docker

在 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/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/j-RCNY3g-PCqfiWvLJDHEA