使用aggregation API擴充套件你的kubernetes API

Cylon 發表於 2022-06-22
Kubernetes

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 "https://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=http://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