新聞中心
概述
K8s Watch API 就是為資源提供的一種持續(xù)監(jiān)聽其變化的機(jī)制,當(dāng)資源有任何變化的時(shí)候,都可以實(shí)時(shí)、順序、可靠的傳遞給客戶端,使得用戶可以針對(duì)目標(biāo)資源進(jìn)行靈活應(yīng)用與操作。

創(chuàng)新互聯(lián)成立與2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站制作、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元大安做網(wǎng)站,已為上家服務(wù),為大安各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18982081108
那 K8s Watch 機(jī)制是怎么實(shí)現(xiàn)的呢?底層具體依賴了哪些技術(shù)?
本文將從 HTTP 協(xié)議、APIServer 啟動(dòng)、ETCD Watch 封裝、服務(wù)端 Watch 實(shí)現(xiàn)、客戶端 Watch 實(shí)現(xiàn)等方面,對(duì) K8s Watch 實(shí)現(xiàn)機(jī)制進(jìn)行了解析。
流程概覽如下:
本文及后續(xù)相關(guān)文章都基于 K8s v1.23
從 HTTP 說(shuō)起
Content-Length
如下圖所示,HTTP 發(fā)送請(qǐng)求 Request 或服務(wù)端 Response,會(huì)在 HTTP header 中攜帶 Content-Length,以表明此次傳輸?shù)目倲?shù)據(jù)長(zhǎng)度。如果 Content-Length 長(zhǎng)度與實(shí)際傳輸長(zhǎng)度不一致,則會(huì)發(fā)生異常(大于實(shí)際值會(huì)超時(shí), 小于實(shí)際值會(huì)截?cái)嗖⒖赡軐?dǎo)致后續(xù)的數(shù)據(jù)解析混亂)。
curl baidu.com -v
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: baidu.com
> Accept: */*
< HTTP/1.1 200 OK
< Date: Thu, 17 Mar 2022 04:15:25 GMT
< Server: Apache
< Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
< ETag: "51-47cf7e6ee8400"
< Accept-Ranges: bytes
< Content-Length: 81
< Cache-Control: max-age=86400
< Expires: Fri, 18 Mar 2022 04:15:25 GMT
< Connection: Keep-Alive
< Content-Type: text/html
如果服務(wù)端提前不知道要傳輸數(shù)據(jù)的總長(zhǎng)度,怎么辦?
Chunked Transfer Encoding
HTTP 從 1.1 開始增加了分塊傳輸編碼(Chunked Transfer Encoding),將數(shù)據(jù)分解成一系列數(shù)據(jù)塊,并以一個(gè)或多個(gè)塊發(fā)送,這樣服務(wù)器可以發(fā)送數(shù)據(jù)而不需要預(yù)先知道發(fā)送內(nèi)容的總大小。數(shù)據(jù)塊長(zhǎng)度以十六進(jìn)制的形式表示,后面緊跟著 \r\n,之后是分塊數(shù)據(jù)本身,后面也是 \r\n,終止塊則是一個(gè)長(zhǎng)度為 0 的分塊。
> GET /test HTTP/1.1
> Host: baidu.com
> Accept-Encoding: gzip
< HTTP/1.1 200 OK
< Server: Apache
< Date: Sun, 03 May 2015 17:25:23 GMT
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: keep-alive
< Content-Encoding: gzip
4\r\n (bytes to send)
Wiki\r\n (data)
6\r\n (bytes to send)
pedia \r\n (data)
E\r\n (bytes to send)
in \r\n
\r\n
chunks.\r\n (data)
0\r\n (final byte - 0)
\r\n (end message)
為了實(shí)現(xiàn)以流(Streaming)的方式 Watch 服務(wù)端資源變更,HTTP1.1 Server 端會(huì)在 Header 里告訴 Client 要變更 Transfer-Encoding 為 chunked,之后進(jìn)行分塊傳輸,直到 Server 端發(fā)送了大小為 0 的數(shù)據(jù)。
HTTP/2
HTTP/2 并沒有使用 Chunked Transfer Encoding 進(jìn)行流式傳輸,而是引入了以 Frame(幀) 為單位來(lái)進(jìn)行傳輸,其數(shù)據(jù)完全改變了原來(lái)的編解碼方式,整個(gè)方式類似很多 RPC協(xié)議。Frame 由二進(jìn)制編碼,幀頭固定位置的字節(jié)描述 Body 長(zhǎng)度,就可以讀取 Body 體,直到 Flags 遇到 END_STREAM。這種方式天然支持服務(wù)端在 Stream 上發(fā)送數(shù)據(jù),不需要通知客戶端做什么改變。
+-----------------------------------------------+
| Body Length (24) | ----Frame Header
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------+
|R| Stream Identifier (31) |
+=+=================================================+
| Frame Payload (0...) ... ----Frame Data
+---------------------------------------------------+
K8s 為了充分利用 HTTP/2 在 Server-Push、Multiplexing 上的高性能 Stream 特性,在實(shí)現(xiàn) RESTful Watch 時(shí),提供了 HTTP1.1/HTTP2 的協(xié)議協(xié)商(ALPN, Application-Layer Protocol Negotiation) 機(jī)制,在服務(wù)端優(yōu)先選中 HTTP2,協(xié)商過程如下:
curl https://{kube-apiserver}/api/v1/watch/namespaces/default/pods/mysql-0 -v
* ALPN, offering h2
* ALPN, offering http/1.1
* SSL verify...
* ALPN, server accepted to use h2
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f2b921a6a90)
> GET /api/v1/watch/namespaces/default/pods/mysql-0 HTTP/2
> Host: 9.165.12.1
> user-agent: curl/7.79.1
> accept: */*
> authorization: Bearer xxx
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 200
< cache-control: no-cache, private
< content-type: application/json
< date: Thu, 17 Mar 2022 04:46:36 GMT
{"type":"ADDED","object":{"kind":"Pod","apiVersion":"v1","metadata":xxx}}APIServer 啟動(dòng)
APIServer 啟動(dòng)采用 Cobra 命令行,解析相關(guān) flags 參數(shù),經(jīng)過 Complete(填充默認(rèn)值)->Validate(校驗(yàn)) 邏輯后,通過 Run 啟動(dòng)服務(wù)。啟動(dòng)入口如下:
// kubernetes/cmd/kube-apiserver/app/server.go
// NewAPIServerCommand creates a *cobra.Command object with default parameters
func NewAPIServerCommand() *cobra.Command {
s := options.NewServerRunOptions()
cmd := &cobra.Command{
Use: "kube-apiserver",
...
RunE: func(cmd *cobra.Command, args []string) error {
...
// set default options
completedOptions, err := Complete(s)
if err != nil {
return err
}
// validate options
if errs := completedOptions.Validate(); len(errs) != 0 {
return utilerrors.NewAggregate(errs)
}
return Run(completedOptions, genericapiserver.SetupSignalHandler())
},
}
...
return cmd
}
在 Run 函數(shù)中,按序分別初始化 APIServer 鏈(APIExtensionsServer、KubeAPIServer、AggregatorServer),分別服務(wù)于 CRD(用戶自定義資源)、K8s API(內(nèi)置資源)、API Service(API 擴(kuò)展資源) 對(duì)應(yīng)的資源請(qǐng)求。相關(guān)代碼如下:
// kubernetes/cmd/kube-apiserver/app/server.go
// 創(chuàng)建 APIServer 鏈(APIExtensionsServer、KubeAPIServer、AggregatorServer),分別服務(wù) CRD、K8s API、API Service
func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan struct{}) (*aggregatorapiserver.APIAggregator, error) {
// 創(chuàng)建 APIServer 通用配置
kubeAPIServerConfig, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions)
if err != nil {
return nil, err
}
...
// 第一:創(chuàng)建 APIExtensionsServer
apiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler))
if err != nil {
return nil, err
}
// 第二:創(chuàng)建 KubeAPIServer
kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)
if err != nil {
return nil, err
}
...
// 第三:創(chuàng)建 AggregatorServer
aggregatorServer, err := createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)
if err != nil {
// we don't need special handling for innerStopCh because the aggregator server doesn't create any go routines
return nil, err
}
return aggregatorServer, nil
}
之后,經(jīng)過非阻塞(NonBlockingRun) 方式啟動(dòng) SecureServingInfo.Serve,并配置 HTTP2(默認(rèn)開啟) 相關(guān)傳輸選項(xiàng),最后啟動(dòng) Serve 監(jiān)聽客戶端請(qǐng)求。
K8s APIServer 為了安全考慮,只支持客戶端 HTTPS 請(qǐng)求,不支持 HTTP。
ETCD 資源封裝
ETCD 實(shí)現(xiàn) Watch 機(jī)制,經(jīng)歷了從 ETCD2 到 ETCD3 實(shí)現(xiàn)方式的轉(zhuǎn)變。ETCD2 通過長(zhǎng)輪詢 Long-Polling 的方式監(jiān)聽資源事件的變更;ETCD3 則通過基于 HTTP2 的 gRPC 實(shí)現(xiàn) Watch stream,性能得到了很大的提升。
Polling(輪詢):由于 http1.x 沒有服務(wù)端 push 的機(jī)制,為了 Watch 服務(wù)端的數(shù)據(jù)變化,最簡(jiǎn)單的辦法當(dāng)然是客戶端去 pull:客戶端每隔定長(zhǎng)時(shí)間去服務(wù)端拉數(shù)據(jù)同步,無(wú)論服務(wù)端有沒有數(shù)據(jù)變化。但是必然存在通知不及時(shí)和大量無(wú)效的輪詢的問題。
Long-Polling(長(zhǎng)輪詢):就是在這個(gè) Polling 的基礎(chǔ)上的優(yōu)化,當(dāng)客戶端發(fā)起 Long-Polling 時(shí),如果服務(wù)端沒有相關(guān)數(shù)據(jù),會(huì) hold 住請(qǐng)求,直到服務(wù)端有數(shù)據(jù)要發(fā)或者超時(shí)才會(huì)返回。
在上一步配置 APIServerConfig 時(shí),封裝了底層存儲(chǔ)用的 ETCD。以 kubeAPIServerConfig 為例,說(shuō)明 K8s 內(nèi)置資源是如何封裝 ETCD 底層存儲(chǔ)的。
首先,通過 buildGenericConfig 實(shí)例化 RESTOptionsGetter,用于封裝 RESTStorage。之后通過 InstallLegacyAPI -> NewLegacyRESTStorage 實(shí)例化 K8s 內(nèi)置資源的 RESTStorage,包括 podStorage、nsStorage、pvStorage、serviceStorage 等,用于 APIServer 在處理客戶端資源請(qǐng)求時(shí),調(diào)用的后端資源存儲(chǔ)。
InstallLegacyAPI 源碼如下:
// kubernetes/pkg/controlplane/instance.go
// 注冊(cè) K8s 的內(nèi)置資源,并封裝到對(duì)應(yīng)的 RESTStorage(如 podStorage/pvStorage)
func (m *Instance) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic.RESTOptionsGetter) error {
...
legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(c.ExtraConfig.APIResourceConfigSource, restOptionsGetter)
if err != nil {
return fmt.Errorf("error building core storage: %v", err)
}
if len(apiGroupInfo.VersionedResourcesStorageMap) == 0 { // if all core storage is disabled, return.
return nil
}
controllerName := "bootstrap-controller"
coreClient := corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
bootstrapController, err := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient, coreClient, coreClient.RESTClient())
if err != nil {
return fmt.Errorf("error creating bootstrap controller: %v", err)
}
m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook)
m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook)
...
return nil
}
在實(shí)例化 ETCD 底層存儲(chǔ)中,通過開關(guān) EnableWatchCache 來(lái)控制是否啟用 Watch 緩存。如果啟用了,則會(huì)先走 StorageWithCacher 邏輯,然后才走 UndecoratedStorage 真正調(diào)用底層 ETCD3 存儲(chǔ)。
K8s 當(dāng)前只支持 ETCD3,不再支持 ETCD2。K8s 充分信任 ETCD3 的 Watch 機(jī)制,保證資源狀態(tài)與 ETCD 底層存儲(chǔ)的一致性。
整個(gè)調(diào)用過程如下:
K8s 各類資源(CRD/Core/Aggregator) 都統(tǒng)一以 RESTful 風(fēng)格暴露 HTTP 請(qǐng)求接口,并支持多種類型的編解碼格式,如 json/yaml/protobuf。
客戶端 Watch 實(shí)現(xiàn)
經(jīng)過上面的步驟,APIServer 服務(wù)端已準(zhǔn)備好 K8s 各類資源的 RESTStorage(底層封裝了 ETCD3),此時(shí)客戶端可通過 RESTful HTTP 接口向 APIServer 發(fā)出資源請(qǐng)求,包括 GET/POST/PATCH/WATCH/DELETE 等操作。
客戶端 Watch 包括:
(1). kubectl get xxx -w,獲取某類資源、并持續(xù)監(jiān)聽資源變化;
(2). client-go 中 Reflector ListAndWatch APIServer 各類資源;
我們以 kubectl get pod -w 為例,說(shuō)明客戶端是如何實(shí)現(xiàn)資源的 Watch 操作。
首先,kubectl 也是通過 Cobra 命令行解析參數(shù)(--watch,或 --watch-only),然后調(diào)用 Run 調(diào)用 cli-runtime 包下面的 Watch 接口,之后通過 RESTClient.Watch 向 APIServer 發(fā)起 Watch 請(qǐng)求,獲得一個(gè)流式 watch.Interface,然后不斷從其中 ResultChan 獲取 watch.Event。之后,根據(jù)客戶端發(fā)送的編解碼類型(json/yaml/protobuf),從 stream 中按幀(Frame) 讀取并解碼(Decode) 數(shù)據(jù),輸出顯示到命令行終端。
客戶端通過 RESTClient 發(fā)起 Watch 請(qǐng)求,代碼如下:
// kubernetes/staging/src/k8s.io/cli-runtime/pkg/resource/helper.go
func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) {
options.Watch = true
return m.RESTClient.Get().
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
VersionedParams(options, metav1.ParameterCodec).
Watch(context.TODO())
}
客戶端 Watch 實(shí)現(xiàn)過程小結(jié)如下:
服務(wù)端 Watch 實(shí)現(xiàn)
服務(wù)端 APIServer 啟動(dòng)后,一直在持續(xù)監(jiān)聽著各類資源的變更事件。在接收到某類資源的 Watch 請(qǐng)求后,調(diào)用 RESTStorage 的 Watch 接口,通過開關(guān) EnableWatchCache 來(lái)控制是否啟用 Watch 緩存,最終通過 etcd3.Watch 封裝實(shí)現(xiàn)了 ETCD 底層的 Event 變更事件。
RESTStorage 就是在 APIServer 啟動(dòng)時(shí)候,提前注冊(cè)、封裝的 ETCD 資源存儲(chǔ)。
etcd3.watcher 通過兩個(gè) channel(incomingEventChan、resultChan,默認(rèn)容量都為 100) 實(shí)現(xiàn) ETCD 底層事件到 watch.Event 的轉(zhuǎn)換,然后通過 serveWatch 流式監(jiān)聽返回的 watch.Interface,不斷從 resultChan 中取出變更事件。之后,根據(jù)客戶端發(fā)送的編解碼類型(json/yaml/protobuf),編碼(Encode) 數(shù)據(jù),按幀(Frame) 組裝后發(fā)送到 stream 中給客戶端。
服務(wù)端通過 serveWatch 流式監(jiān)聽返回的 watch.Interface,代碼如下:
// kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/get.go
func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatch bool, minRequestTimeout time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
...
if opts.Watch || forceWatch {
...
watcher, err := rw.Watch(ctx, &opts)
if err != nil {
scope.err(err, w, req)
return
}
requestInfo, _ := request.RequestInfoFrom(ctx)
metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() {
serveWatch(watcher, scope, outputMediaType, req, w, timeout)
})
return
}
...
}
}
K8s 在 v1.11 之后將 WATCH/WATCHLIST 類型的 action.Verb 廢棄了,統(tǒng)一都交由 LIST -> restfulListResource 處理。
服務(wù)端 Watch 實(shí)現(xiàn)過程小結(jié)如下:
APIServer 除了支持 HTTP2,也支持 WebSocket 通信。當(dāng)客戶端請(qǐng)求包含了 Upgrade: websocket,Connection: Upgrade 時(shí),則服務(wù)端會(huì)通過 WebSocket 與客戶端進(jìn)行數(shù)據(jù)傳輸。
值得注意的是,底層 ETCD 事件通過 transform 函數(shù)轉(zhuǎn)換為 watch.Event,包括以下幾種類型(Type):
小結(jié)
本文通過分析 K8s 中 APIServer 啟動(dòng)、ETCD watch 封裝、服務(wù)端 Watch 實(shí)現(xiàn)、客戶端 Watch 實(shí)現(xiàn)等核心流程,對(duì) K8s Watch 實(shí)現(xiàn)機(jī)制進(jìn)行了解析。通過源碼、圖文方式說(shuō)明了相關(guān)流程邏輯,以期更好的理解 K8s Watch 實(shí)現(xiàn)細(xì)節(jié)。
K8s 底層完全信任 ETCD(ListAndWatch),將各類資源統(tǒng)一抽象為了 RESTful 的存儲(chǔ)(Storage),通過 Watch 機(jī)制獲取各類資源的變更事件,然后通過 Informer 機(jī)制分發(fā)給下游監(jiān)聽的 ResourceEventHandler,最終由 Controller 實(shí)現(xiàn)資源的業(yè)務(wù)邏輯處理。隨著 ETCD3 在 HTTP/2 基礎(chǔ)上不斷優(yōu)化完善,K8s 將提供更高效、更穩(wěn)定的編排能力。
文章題目:K8s如何提供更高效穩(wěn)定的編排能力?K8sWatch實(shí)現(xiàn)機(jī)制淺析
網(wǎng)站網(wǎng)址:http://www.dlmjj.cn/article/cdohhce.html


咨詢
建站咨詢
