Admission Controller
Kubernetes Admission Controller(准入控制器)是什麼?
如下圖所示:
當我們向 k8s api-server 提交了請求之後,需要經過認證鑑權、mutation admission、validation 校驗等一系列過程,最後才會將資源物件持久化到 etcd 中(其它元件諸如 controller 或 scheduler 等操作的也是持久化之後的物件)。而所謂的 Kubernetes Admission Controller 其實就是在這個過程中所提供的 webhook 機制,讓使用者能夠在資源物件被持久化之前任意地修改資源物件並進行自定義的校驗。
使用 Kubernetes Admission Controller ,你可以:
- 安全性:強制實施整個名稱空間或叢集範圍內的安全規範。例如,禁止容器以root身份執行或確保容器的根檔案系統始終以只讀方式掛載;只允許從企業已知的特定註冊中心拉取映象,拒絕未知的映象源;拒絕不符合安全標準的部署。
- 治理:強制遵守某些實踐,例如具有良好的標籤、註釋、資源限制或其他設定。一些常見的場景包括:在不同的物件上強制執行標籤驗證,以確保各種物件使用適當的標籤,例如將每個物件分配給團隊或專案,或指定應用程式標籤的每個部署;自動向物件新增註釋。
- 配置管理:驗證叢集中物件的配置,並防止任何明顯的錯誤配置影響到您的叢集。准入控制器可以用於檢測和修復部署了沒有語義標籤的映象,例如:自動新增資源限制或驗證資源限制;確保向Pod新增合理的標籤;確保在生產部署的映象不使用 latest tag 或帶有 -dev 字尾的 tag。
Admission Controller(准入控制器)提供了兩種 webhook:
Mutation admission webhook
:修改資源物件Validation admission webhook
:校驗資源物件
所謂的 webhook 其實就是你需要部署一個 HTTPS Server ,然後 k8s 會將 admission 的請求傳送給你的 server,當然你的 server 需要按照約定格式返回響應。
使用 Kubernetes Admission Controller,你需要:
- 確保 k8s 的 api-server 開啟
admission plugins
。 - 準備好 TLS/SSL 證書,用於 HTTPS,可以是自籤的。
- 構建自己的 HTTPS server,實現處理邏輯。
- 配置
MutatingWebhookConfiguration
或者ValidatingWebhookConfiguration
,你得告訴 k8s 怎麼跟你的 server 通訊。
注入 sidacar 示例
接下來,我們來實現一個最簡單的為 pod 注入 sidacar 的示例。
1. 確保 k8s 的 api-server 開啟 admission plugins
首先需要確認你的 k8s 叢集支援 admission controller 。
執行 kubectl api-resources | grep admission
:
mutatingwebhookconfigurations admissionregistration.k8s.io/v1 false MutatingWebhookConfiguration
validatingwebhookconfigurations admissionregistration.k8s.io/v1 false ValidatingWebhookConfiguration
得到以上結果就說明你的 k8s 叢集支援 admission controller。
然後需要確認 api-server 開啟 admission plugins,根據你的 api-server 的啟動方式,確認如下引數:
--enable-admission-plugins=MutatingAdmissionWebhook,ValidatingAdmissionWebhook
plugins 可以有多個,用逗號分隔,本示例其實只需要 MutatingAdmissionWebhook
,至於其它的 plugins 用途請參考官方文件。
2. 準備 TLS/SSL 證書
這裡我們使用自籤的證書,先建立一個證書目錄,比如 ~/certs
,以下操作都在這個目錄下進行。
建立我們自己的 root CA
openssl genrsa -des3 -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt
建立證書
openssl genrsa -out mylocal.com.key 2048
openssl req -new -key mylocal.com.key -out mylocal.com.csr
使用我們自己的 root CA 去籤我們的證書
注意:由於我們會把 HTTPS server 部署在本地進行測試,所以我們在簽名的時候要額外指定自己的內網IP。echo subjectAltName = IP:192.168.100.22 > extfile.cnf openssl x509 -req -in mylocal.com.csr \ -CA rootCA.crt -CAkey rootCA.key \ -CAcreateserial -out mylocal.com.crt \ -days 500 -extfile extfile.cnf
執行完後你會得到以下檔案:
rootCA.key
:根 CA 私鑰rootCA.crt
:根 CA 證書(後面 k8s 需要用到)rootCA.srl
:追蹤發放的證書mylocal.com.key
:自籤域名的私鑰(HTTPS server 需要用到)mylocal.com.csr
:自籤域名的證書籤名請求檔案mylocal.com.crt
:自籤域名的證書(HTTPS server 需要用到)
3. 構建自己的 HTTPS Server
Webhook 的請求和響應都要是 JSON 格式的 AdmissionReview 物件。
注意:AdmissionReview v1 版本和 v1beta1 版本有區別,我們這裡使用 v1 版本。
// AdmissionReview describes an admission review request/response.
type AdmissionReview struct {
metav1.TypeMeta `json:",inline"`
// Request describes the attributes for the admission request.
// +optional
Request *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"`
// Response describes the attributes for the admission response.
// +optional
Response *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"`
}
我們需要處理的邏輯其實就是解析 AdmissionRequest
,然後構造 AdmissionResponse
最後返回響應。
// AdmissionResponse describes an admission response.
type AdmissionResponse struct {
// UID is an identifier for the individual request/response.
// This must be copied over from the corresponding AdmissionRequest.
UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
// Allowed indicates whether or not the admission request was permitted.
Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"`
// The patch body. Currently we only support "JSONPatch" which implements RFC 6902.
// +optional
Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"`
// The type of Patch. Currently we only allow "JSONPatch".
// +optional
PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"`
// ...
}
AdmissionResponse
中的 PatchType
欄位必須是 JSONPatch
,Patch
欄位必須是 rfc6902 JSON Patch 格式。
我們使用 go 編寫一個最簡單的 HTTPS Server 示例如下,該示例會修改 pod 的 spec.containers 陣列,向其中追加一個 sidecar 容器:
package main
import (
"encoding/json"
"log"
"net/http"
v1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
)
// patchOperation is an operation of a JSON patch, see https://tools.ietf.org/html/rfc6902 .
type patchOperation struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
var (
certFile = "/Users/wy/certs/mylocal.com.crt"
keyFile = "/Users/wy/certs/mylocal.com.key"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
var admissionReview v1.AdmissionReview
err := json.NewDecoder(req.Body).Decode(&admissionReview)
if err != nil {
log.Fatal(err)
}
var patches []patchOperation
patches = append(patches, patchOperation{
Op: "add",
Path: "/spec/containers/-",
Value: &corev1.Container{
Image: "busybox",
Name: "sidecar",
},
})
patchBytes, err := json.Marshal(patches)
if err != nil {
log.Fatal(err)
}
var PatchTypeJSONPatch v1.PatchType = "JSONPatch"
admissionReview.Response = &v1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: true,
Patch: patchBytes,
PatchType: &PatchTypeJSONPatch,
}
// Return the AdmissionReview with a response as JSON.
bytes, err := json.Marshal(&admissionReview)
if err != nil {
log.Fatal(err)
}
w.Write(bytes)
})
log.Printf("About to listen on 8443. Go to https://127.0.0.1:8443/")
err := http.ListenAndServeTLS(":8443", certFile, keyFile, nil)
log.Fatal(err)
}
4. 配置 MutatingWebhookConfiguration
我們需要告訴 k8s 往哪裡傳送請求以及其它資訊,這就需要配置 MutatingWebhookConfiguration
。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: test-sidecar-injector
webhooks:
- name: sidecar-injector.mytest.io
admissionReviewVersions:
- v1 # 版本一定要與 HTTPS Server 處理的版本一致
sideEffects: "NoneOnDryRun"
reinvocationPolicy: "Never"
timeoutSeconds: 30
objectSelector: # 選擇特定資源觸發 webhook
matchExpressions:
- key: run
operator: In
values:
- "nginx"
rules: # 觸發規則
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: "*"
clientConfig:
caBundle: ${CA_PEM_B64}
url: https://192.168.100.22:8443/ # 指向我本地的IP地址
# service: # 如果把 server 部署到叢集內部則可以透過 service 引用
其中的 ${CA_PEM_B64} 需要填入第一步的 rootCA.crt 檔案的 base64 編碼,我們可以執行以下命令得到:
openssl base64 -A -in rootCA.crt
在上例中,我們還配置了 webhook 觸發的資源要求和規則,比如這裡的規則是建立 pods 並且 pod 的 labels 標籤必須滿足 matchExpressions
。
最後測試,我們可以執行 kubectl run nginx --image=nginx
,成功之後再檢視提交的 pod ,你會發現 containers 中包含有我們注入的 sidecar 。
結語
透過本文相信你已經瞭解了 Admission Controller 的基本使用過程,諸多開源框架,比如 Istio 等也廣泛地使用了 Admission Controller。