使用aggregation API擴展你的kubernetes API

Overview

What is Kubernetes aggregation

Kubernetes apiserver aggregation AA 是Kubernetes提供的一種擴展API的方法,目前並沒有GA

Difference between CRD and AA

眾所周知,kubernetes擴展API的方法大概為三種:CRD、AA、手動擴展源碼。根據CNCF分享中Min Kim說的AA更關注於實踐,而用戶無需了解底層的原理,這裡使用過 kubebuildercode-generator 的用戶是很能體會到這點。官方也給出了CRD與AA的區別

API Access Control

Authentication
  • CR: All strategies supported. Configured by root apiserver.
  • AA: Supporting all root apiserver’s authenticating strategies but it has to be done via authentication token review api except for authentication proxy which will cause an extra cost of network RTT.
Authorization
  • CR: All strategies supported. Configured by root apiserver.
  • AA: Delegating authorization requests to root apiserver via SubjectAccessReview api. Note that this approach will also cost a network RTT.
Admission Control
  • CR: You could extend via dynamic admission control webhook (which is costing network RTT).
  • AA: While You can develop and customize your own admission controller which is dedicated to your AA. While You can’t reuse root-apiserver’s built-in admission controllers nomore.

API Schema

Note: CR’s integration with OpenAPI schema is being enhanced in the future releases and it will have a stronger integration with OpenAPI mechanism.

Validating
  • CR: (landed in 1.12) Defined via OpenAPIv3 Schema grammar. more
  • AA: You can customize any validating flow you want.
Conversion
  • CR: (landed in 1.13) The CR conversioning (basically from storage version to requested version) could be done via conversioning webhook.
  • AA: Develop any conversion you want.
SubResource
  • CR: Currently only status and scale sub-resource supported.
  • AA: You can customize any sub-resouce you want.
OpenAPI Schema
  • CR: (landed in 1.13) The corresponding CRD’s OpenAPI schema will be automatically synced to root-apiserver’s openapi doc api.
  • AA: OpenAPI doc has to be manually generated by code-generating tools.

Authentication

要想很好的使用AA,就需要對kubernetes與 AA 之間認證機制進行有一定的了解,這裡涉及到一些概念

  • 客戶端證書認證
  • token認證
  • 請求頭認證

在下面的說明中,所有出現的APIServer都是指Kubernetes集群組件APIServer也可以為 root APIServer;所有的AA都是指 extension apiserver,就是自行開發的 AA。

客戶端證書

客戶端證書就是CA簽名的證書,由客戶端指定CA證書,在客戶端連接時進行身份驗證,在Kubernetes APIserver也使用了相同的機制。

默認情況下,APIServer在啟動時指定參數 --client-ca-file ,這時APIServer會創建一個名為 extension-apiserver-authentication ,命名空間為 kube-system 下的 configMap。

$ kubectl get cm -A
NAMESPACE     NAME                                 DATA   AGE
kube-system   extension-apiserver-authentication   6      21h

kubectl get cm extension-apiserver-authentication -n kube-system -o yaml

由上面的命令可以看出這個configMap將被填充到客戶端(AA Pod實例)中,使用此CA證書作為用於驗證客戶端身份的CA。這樣客戶端會讀取這個configMap,與APIServer進行身份認證。

I0622 14:24:00.509486       1 secure_serving.go:178] Serving securely on [::]:443
I0622 14:24:00.509556       1 configmap_cafile_content.go:202] Starting client-ca::kube-system::extension-apiserver-authentication::requestheader-client-ca-file

token認證

Token認證是指通過HTTP Header傳入 Authorization: Bearer $TOKEN 的方式進行客戶端認證,這也是Kubernetes集群內認證常用的方法。

在這種情況下,允許對APIServer進行認證也同樣可以對AA進行認證。如果不想 AA 對同一集群進行身份驗證,或AA在集群外部運行,可以將參數 --authentication-kubeconfig 以指定要使用的不同 Kubeconfig 認證。

下面實例是AA的啟動參數

./bin/apiserver -h|grep authentication-kubeconfig
      --authentication-kubeconfig string                        kubeconfig file pointing at the 'core' kubernetes server with enough righ
ts to create tokenreviews.authentication.k8s.io. This is optional. If empty, all token requests are considered to be anonymous and no cli
ent CA is looked up in the cluster.

請求頭認證

RequestHeader 認證是指,APIServer對來自AA代理連接進行的身份認證。

默認情況下,AA 從 extension-apiserver-authentication 中提到的 ConfigMap 中 提取 requestheader 客戶端 CA 證書與 CN。如果主 Kubernetes APIServer 配置了選項 --requestheader-client-ca-file ,則它會填充此內容。

跳過客戶端認證 --authentication-skip-lookup

授權

默認情況下,AA 伺服器會通過自動注入到 Kubernetes 集群上運行的 pod 的連接資訊和憑據,來連接到主 Kubernetes API 伺服器。

E0622 11:20:12.375512       1 errors.go:77] Post "//192.168.0.1:443/apis/authorization.k8s.io/v1/subjectaccessreviews": write tcp 192.168.0.36:39324->192.168.0.1:443: write: connection reset by peer

如果AA在集群外部部署,可以指定--authorization-kubeconfig 通過kubeconfig進行認證,這就類似於二進位部署中的資訊。

默認情況下,Kubernetes 集群會啟用RBAC,這就意味著AA 創建多個clusterrolebinding。

下面日誌是 AA 對於集群中資源訪問無許可權的情況

E0622 09:01:26.750320       1 reflector.go:178] pkg/mod/k8s.io/[email protected]/tools/cache/reflector.go:125: Failed to list *v1.MutatingWebhookConfiguration: mutatingwebhookconfigurations.admissionregistration.k8s.io is forbidden: User "system:serviceaccount:default:default" cannot list resource "mutatingwebhookconfigurations" in API group "admissionregistration.k8s.io" at the cluster scope
E0622 09:01:29.357897       1 reflector.go:178] pkg/mod/k8s.io/[email protected]/tools/cache/reflector.go:125: Failed to list *v1.Namespace: namespaces is forbidden: User "system:serviceaccount:default:default" cannot list resource "namespaces" in API group "" at the cluster scope
E0622 09:01:39.998496       1 reflector.go:178] pkg/mod/k8s.io/[email protected]/tools/cache/reflector.go:125: Failed to list *v1.ValidatingWebhookConfiguration: validatingwebhookconfigurations.admissionregistration.k8s.io is forbidden: User "system:serviceaccount:default:default" cannot list resource "validatingwebhookconfigurations" in API group "admissionregistration.k8s.io" at the cluster scope

需要手動在namespace kube-system 中創建rolebindding到 role extension-apiserver-authentication-reader 。這樣就可以訪問到configMap了。

apiserver-builder

apiserver-builder 項目就是創建AA的工具,可以參考 installing.md 來安裝

初始化項目

初始化命令

  • <your-domain> 這個是你的API資源的組,參考 k8s.io/api
    • 如果組的名稱是域名就設置為主域名,例如內置組
      • /apis/authentication.k8s.io
      • /apis/batch
  • 生成的go mod 包名為你所在的目錄的名稱
    • 例如,在firewalld目錄下,go.mod 的名稱為 firewalld
apiserver-boot init repo --domain <your-domain>

例如

apiserver-boot init repo --domain fedoraproject.org

註:這裡–domain設置為主域名就可以了,後面生成的group會按照格式 +

apiserver-boot must be run from the directory containing the go package to bootstrap. This must
 be under $GOPATH/src/<package>.

必須在 $GOPATH/src 下創建你的項目,我這裡的為 GOPATH=go/src ,這時創建項目必須在目錄 go/src/src/{project} 下創建

創建一個GVK

apiserver-boot create group version resource \
	--group firewalld \
	--version v1 \
	--kind PortRule

在創建完成之後會生成 api-like的類型,我們只需要填充自己需要的就可以了

type PortRule struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   PortRuleSpec   `json:"spec,omitempty"`
	Status PortRuleStatus `json:"status,omitempty"`
}

// PortRuleSpec defines the desired state of PortRule
type PortRuleSpec struct { // 這裡內容都為空的,自己添加即可
	Name        string `json:"name"`
	Host        string `json:"host"`
	Port        int    `json:"port"`
	IsPremanent bool   `json:"isPremanent,omitempty"`
}

// PortRuleStatus defines the observed state of PortRule
type PortRuleStatus struct {
}

生成程式碼

apiserver-boot 沒有專門用來生成程式碼的命令,可以執行任意生成命令即可,這裡使用生成二進位執行文件命令,這個過程相當長。

apiserver-boot build executables

如果編譯錯誤可以使用 --generate=false 跳過生成,這樣就可以節省大量時間。

運行方式

運行方式無非三種,本地運行,集群內運行,集群外運行

running_locally

本地運行需要有一個etcd服務,不用配置ca證書,這裡使用docker運行

docker run -d --name Etcd-server \
    --publish 2379:2379 \
    --publish 2380:2380 \
    --env ALLOW_NONE_AUTHENTICATION=yes \
    --env ETCD_ADVERTISE_CLIENT_URLS=//etcd-server:2379 \
    bitnami/etcd:latest

然後執行命令,執行成功後會彈出對應的訪問地址

apiserver-boot build executables
apiserver-boot run local

running_in_cluster

構建鏡像

需要先構建容器鏡像,apiserver-boot build container --image <image> 這將生成程式碼,構建 apiserver 和controller二進位文件,然後構建容器映像。構建完成後還需要將對應的鏡像push到倉庫(可選)

apiserver-boot build config \
	--name <servicename> \
	--namespace <namespace to run in> \
	--image <image to run>

注,這個操作需要在支援Linux內核的環境下構建,wsl不具備內核功能故會報錯,需要替換為wsl2,而工具是下載的,如果需要wsl1+Docker Desktop構建,需要自己修改

構建配置
apiserver-boot build config \
	--name <servicename> \
	--namespace <namespace to run in> \
	--image <image to run>

構建配置的操作會執行以下幾個步驟:

  • <project/config/certificates 目錄下創建一個 CA證書
  • 在目錄 <project/config/*.yaml 下生成kubernetes所需的資源清單。

註:

實際上這個清單並不能完美適配任何環境,需要手動修改一下配置

運行的Pod中包含apiserver與controller,如果使用kubebuilder創建的controller可以自行修改資源清單

修改apiserver的配置

下面參數是有關於 AA 認證的參數

--proxy-client-cert-file=/etc/kubernetes/pki/firewalld.crt \
--proxy-client-key-file=/etc/kubernetes/pki/firewalld.key \
--requestheader-allowed-names=kube-apiserver-kubelet-client,firewalld.default.svc,firewalld-certificate-authority \
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt \
--requestheader-extra-headers-prefix=X-Remote-Extra- \
  • --requestheader-username-headers:用於存儲用戶名的標頭
  • --requestheader-group-headers:用於存儲組的標題
  • --requestheader-extra-headers-prefix:附加到所有額外標頭的前綴
  • --proxy-client-key-file :私鑰文件
  • --proxy-client-cert-file:客戶端證書文件
  • --requestheader-client-ca-file:簽署客戶端證書文件的 CA 的證書
  • --requestheader-allowed-names:簽名客戶端證書中的CN)

由以上資訊得知,實際上 apiserver-boot 所生成的ca用不上,需要kubernetes自己的ca進行簽署,這裡簡單提供兩個命令,使用kubernetes集群證書進行頒發證書。這裡kubernetes集群證書使用kubernetes-generator 生產的。這裡根據這個ca再次生成用於 AA 認證的證書。

openssl req -new \
    -key firewalld.key \
    -subj "/CN=firewalld.default.svc" \
    -config <(cat /etc/pki/tls/openssl.cnf <(printf "[aa]\nsubjectAltName=DNS:firewalld, DNS:firewalld.default.svc, DNS:firewalld-certificate-authority, DNS:kubernetes.default.svc")) \
    -out firewalld.csr

openssl ca \
	-in firewalld.csr \
	-cert front-proxy-ca.crt \
	-keyfile front-proxy-ca.key \
	-out firewalld.crt \
	-days 3650 \
	-extensions aa \
	-extfile <(cat /etc/pki/tls/openssl.cnf  <(printf "[aa]\nsubjectAltName=DNS:firewalld, DNS:firewalld.default.svc, DNS:firewalld-certificate-authority, DNS:kubernetes.default.svc"))

完成後重新生成所需的yaml資源清單即可,通過資源清單來測試下擴展的API

apiVersion: firewalld.fedoraproject.org/v1
kind: PortRule
metadata:
  name: portrule-example
spec:
  name: "nginx"
  host: "10.0.0.3"
  port: 80

$ kubectl apply -f http.yaml 
portrule.firewalld.fedoraproject.org/portrule-example created
$ kubectl get portrule
NAME               CREATED AT
portrule-example   2022-06-22T15:12:59Z

更詳細的說明建議閱讀下Reference,都是官方提供的詳細說明文檔

Reference

aggregation layer

apiserver-builder doc