作者
張鵬,騰訊雲容器產品工程師,擁有多年雲原生項目開發落地經驗。目前主要負責騰訊雲 TKE 雲原生 AI 產品的開發工作。
謝遠東,騰訊高級工程師,Kubeflow Member、Fluid(CNCF Sandbox) 核心開發者,負責騰訊雲 TKE 在 AI 場景的研發和支持工作。
概述
隨着 Kubernetes 的日趨成熟,越來越多的公司、企業開始使用 K8s 來構建自己的雲原生平台,基於 kubernetes 良好的擴展性以及成熟穩定的架構,你可以快速部署並管理自己的雲原生應用。
目前我們也在基於 kubernetes 打造一個雲原生 AI 平台(我們稱它為:SKAI),該平台具備極致彈性、多雲兼容性、高易用、可觀測性、可復現性的特點,旨在利用雲原生的思想和技術,為 AI 場景的數據處理、模型訓練、模型上線推理等需求構建彈性可擴展的系統架構,從而提升資源利用率。
為了使我們的平台更加的雲原生,我們沒有選擇常用的 web 框架來構建 API 服務,而是使用 kubernetes 擴展來構建整個平台,這樣使我們的平台能更好的和 kubernetes 融合,可以無縫適配任何基於 k8s 的多雲混合雲環境。
為什么選擇 Aggregated APIServer?
選擇獨立 API 還是 Aggregated APIServer ?
盡管使用 gin、go-restful 等 go 語言 web 框架可以輕易地構建出一個穩定的 API 接口服務,但以 kubernetes 原生的方式構建 API 接口服務還是有很多優勢,例如:
- 能利用 kubernetes 原生的認證、授權、准入等機制,有更高的開發效率;
- 能更好的和 K8s 系統融合,借助 K8s 生態更快的推廣自己的產品,方便用戶上手;
- 借助於 K8s 成熟的 API 工具及規范,構建出的 API 接口更加規范整齊;
但是在很多場景下,我們還是不能確定到底使用聚合 API(Aggregated APIServer)還是獨立 API 來構建我們的服務,官方為我們提供了兩種選擇的對比;如果你不能確定使用聚合 API 還是獨立 API,下面的表格或許對你有幫助:
考慮 API 聚合的情況 | 優選獨立 API 的情況 |
---|---|
你在開發新的 API | 你已經有一個提供 API 服務的程序並且工作良好 |
你希望可以是使用 kubectl 來讀寫你的新資源類別 |
不要求 kubectl 支持 |
你希望在 Kubernetes UI (如儀表板)中和其他內置類別一起查看你的新資源類別 | 不需要 Kubernetes UI 支持 |
你希望復用 Kubernetes API 支持特性 | 你不需要這類特性 |
你有意願取接受 Kubernetes 對 REST 資源路徑所作的格式限制,例如 API 組和名字空間。(參閱 API 概述) | 你需要使用一些特殊的 REST 路徑以便與已經定義的 REST API 保持兼容 |
你的 API 是聲明式的 | 你的 API 不符合聲明式模型 |
你的資源可以自然地界定為集群作用域或集群中某個名字空間作用域 | 集群作用域或名字空間作用域這種二分法很不合適;你需要對資源路徑的細節進行控制 |
首先我們希望我們的 SKAI 平台能更好的和 k8s 結合,並且它是一個聲明式的 API,盡可能的復用 Kubernets API 的特性,顯然聚合 API 對我們來說更加適合。
選擇 CRDs 還是 Aggregated APIServer?
除了聚合 API,官方還提供了另一種方式以實現對標准 kubernetes API 接口的擴展:CRD(Custom Resource Definition ),能達到與聚合 API 基本一樣的功能,而且更加易用,開發成本更小,但相較而言聚合 API 則更為靈活。針對這兩種擴展方式如何選擇,官方也提供了相應的參考。
通常,如果存在以下情況,CRD 可能更合適:
- 定制資源的字段不多;
- 你在組織內部使用該資源或者在一個小規模的開源項目中使用該資源,而不是在商業產品中使用;
聚合 API 可提供更多的高級 API 特性,也可對其他特性進行定制;例如,對存儲層進行定制、對 protobuf 協議支持、對 logs、patch 等操作支持。
兩種方式的核心區別是定義 api-resource 的方式不同。在 Aggregated APIServer 方式中,api-resource 是通過代碼向 API 注冊資源類型,而 Custom Resource 是直接通過 yaml 文件向 API 注冊資源類型。
簡單來說就是 CRD 是讓 kube-apiserver 認識更多的對象類別(Kind),Aggregated APIServer 是構建自己的 APIServer 服務。雖然 CRD 更簡單,但是缺少更多的靈活性,更詳細的 CRDs 與 Aggregated API 的對比可參考官方文檔。
對於我們而言,我們希望使用更多的高級 API 特性,例如 "logs" 或 "exec",而不僅僅局限於 CRUD ,所以我們最終選擇了 Aggregated APIServer 。
APIServer 擴展的基本原理
kube-apiserver 作為整個 Kubernetes 集群操作 etcd 的唯一入口,負責 Kubernetes 各資源的認證&鑒權,校驗以及 CRUD 等操作,提供 RESTful APIs,供其它組件調用:
kube-apiserver 其實包含三種 APIServer:
- AggregatorServer:負責處理
apiregistration.k8s.io
組下的 APIService 資源請求,同時將來自用戶的請求攔截轉發給 Aggregated APIServer(AA); - KubeAPIServer:負責對請求的一些通用處理,包括:認證、鑒權以及各個內建資源(pod, deployment,service)的 REST 服務等;
- ApiExtensionsServer:負責 CustomResourceDefinition(CRD)apiResources 以及 apiVersions 的注冊,同時處理 CRD 以及相應 CustomResource(CR)的REST請求(如果對應 CR 不能被處理的話則會返回404),也是 apiserver Delegation 的最后一環;
三個 APIServer 通過 delegation 的關系關聯,在 kube-apiserver 初始化創建的過程中,首先創建的是 APIExtensionsServer,它的 delegationTarget 是一個空的 Delegate,即什么都不做,繼而將 APIExtensionsServer 的 GenericAPIServer,作為 delegationTarget 傳給了 KubeAPIServer,創建出了 KubeAPIServer,再然后,將 kubeAPIServer 的 GenericAPIServer 作為 delegationTarget 傳給了 AggregatorServer,創建出了 AggregatorServer,所以他們之間 delegation 的關系為: Aggregator -> KubeAPIServer -> APIExtensions,如下圖所示:
如何快速構建 Aggregated APIServer?
雖然官方提供了一個 sample-apiserver,我們可以參考實現自己的 Aggregated APIServer。但完全手工編寫太過復雜,也不便於后期維護,我們最終選擇了官方推薦的工具 apiserver-builder,apiserver-builder 可以幫助我們快速創建項目骨架,並且使用 apiserver-builder 構建的項目目錄結構比較清晰,更利於后期維護。
安裝 apiserver-builder 工具
通過 Go Get 安裝
$ GO111MODULE=on go get sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot
通過安裝包安裝
- 下載最新版本
- 解壓到 /usr/local/apiserver-builder/
- 如果此目錄不存在,則創建此目錄
- 添加/usr/local/apiserver-builder/bin到您的路徑
export PATH=$PATH:/usr/local/apiserver-builder/bin
- 運行
apiserver-boot -h
初始化項目
完成 apiserver-boot 安裝后,可通過如下命令來初始化一個 Aggregated APIServer 項目:
$ mkdir skai-demo
$ cd skai-demo
$ apiserver-boot init repo --domain skai.io
執行后會生成如下目錄:
.
├── BUILD.bazel
├── Dockerfile
├── Makefile
├── PROJECT
├── WORKSPACE
├── bin
├── cmd
│ ├── apiserver
│ │ └── main.go
│ └── manager
│ └── main.go -> ../../main.go
├── go.mod
├── hack
│ └── boilerplate.go.txt
├── main.go
└── pkg
└── apis
└── doc.go
- hack 目錄存放自動腳本
- cmd/apiserver 是 aggregated server的啟動入口
- cmd/manager 是 controller 的啟動入口
- pkg/apis 存放 CR 相關的結構體定義,會在下一步自動生成
生成自定義資源
$ apiserver-boot create group version resource --group animal --version v1alpha1 --kind Cat --non-namespaced=false
Create Resource [y/n]
y
Create Controller [y/n]
n
可根據自己的需求選擇是否生成 Controller,我們這里暫時選擇不生成, 對於需要通過 namespace 隔離的 resource 需要增加 --non-namespaced=false 的參數,默認都是 true。
執行完成后代碼結構如下:
└── pkg
└── apis
├── animal
│ ├── doc.go
│ └── v1alpha1
│ ├── cat_types.go
│ ├── doc.go
│ └── register.go
└── doc.go
可以看到在 pkg/apis 下生成了 animal 的 group 並在 v1alpha1 版本下新增了 cat_types.go
文件,此文件包含了我們資源的基礎定義,我們在 spec 中增加字段定義,並在已經實現的 Validate
方法中完成基礎字段的校驗。
// Cat
// +k8s:openapi-gen=true
type Cat struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CatSpec `json:"spec,omitempty"`
Status CatStatus `json:"status,omitempty"`
}
// CatSpec defines the desired state of Cat
type CatSpec struct {
Name string `json:"name"`
}
func (in *Cat) Validate(ctx context.Context) field.ErrorList {
allErrs := field.ErrorList{}
if len(in.Spec.Name) == 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "name"), in.Spec.Name, "must be specify"))
}
return allErrs
}
部署運行
完成以上步驟,你其實已經擁有一個完整的 Aggregated APIServer,接下來我們試着將它運行起來;apiserver-boot 本身提供了兩種運行模式:in-cluster、local; local 模式下只作為單獨的 API 服務部署在本地方便做調試,過於簡單這里不做過多介紹,主要關注一下 in-cluster 模式;in-cluster 可以將你的 Aggregated APIServer 部署在任何 K8s 集群中,例如:minikube,騰訊 TKE,EKS 等,我們這里使用 EKS 集群作為演示。
創建EKS集群&配置好本地kubeconfig;
執行部署命令 ;
$ apiserver-boot run in-cluster --image=xxx/skai.io/skai-demo:0.0.1 --name=skai-demo --namespace=default
在執行部署命令過程中,apiserver-boot 主要幫我們做了如下幾件事情:
- 自動生成 APIServer Dockerfile 文件;
- 通過 APIServer Dockerfile 構建服務鏡像,並將鏡像推送到指定倉庫;
- 在config目錄下生成 CA 及其他 APIServer 部署需要的證書文件;
- 在config目錄下生成 APIServer 部署需要的 Deployment、Service、APIService、ServiceAccount 等 yaml 文件;
- 將上一步生成的 yaml 文件部署到集群中;
功能驗證
確認 Resource 注冊成功
$ kubectl api-versions |grep animal
animal.skai.io/v1alpha1
確認 Aggregated APIServer 能正常工作
$ kubectl get apiservice v1alpha1.animal.skai.io
NAME SERVICE AVAILABLE AGE
v1alpha1.animal.skai.io default/skai-demo True 19h
創建並查看新增的 Resource
創建
$ cat lucky.yaml
apiVersion: animal.skai.io/v1alpha1
kind: Cat
metadata:
name: mycat
namespace: default
spec:
name: lucky
# 創建自定義 resource
$ kubectl apply -f lucky.yaml
查找
# 查找自定義 resource 列表
$ kubectl get cat
NAME CREATED AT
mycat 2021-11-17T09:08:10Z
# 查找自定義資源詳情
$ kubectl get cat mycat -oyaml
apiVersion: animal.skai.io/v1alpha1
kind: Cat
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"animal.skai.io/v1alpha1","kind":"Cat","metadata":{"annotations":{},"name":"mycat"},"spec":{"name":"lucky"}}
creationTimestamp: "2021-11-17T09:08:10Z"
name: mycat
resourceVersion: "17"
uid: 98af0905-f01d-4042-bad3-71b96c0919f4
spec:
name: lucky
status: {}
總結
本文從實戰角度出發介紹我們開發 SKAI 平台過程中選擇 Aggregated API 的原因,以及 kube-apisever 的擴展原理,最后介紹了 apiserver-builder 工具,並演示如何一步一步構建起自己的 Aggregated API,並將它部署到 EKS 集群中。希望該篇 Aggregated APIServer 最佳實踐可以幫助即將使用 K8s API 擴展來構建雲原生應用的開發者。
關於我們
更多關於雲原生的案例和知識,可關注同名【騰訊雲原生】公眾號~
福利:
①公眾號后台回復【手冊】,可獲得《騰訊雲原生路線圖手冊》&《騰訊雲原生最佳實踐》~
②公眾號后台回復【系列】,可獲得《15個系列100+篇超實用雲原生原創干貨合集》,包含Kubernetes 降本增效、K8s 性能優化實踐、最佳實踐等系列。
【騰訊雲原生】雲說新品、雲研新術、雲游新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!