將Watch與Kubernetes API結合使用

1.簡介

在本教程中,我們將繼續探索Java Kubernetes API。這次,我們將展示如何使用Watches有效監視集群事件。

2.什麼是Kubernetes Watches?

在之前涉及Kubernetes API的文章中,我們已經展示瞭如何恢復有關給定資源或它們的集合的信息。如果我們想要的只是在給定的時間點獲得這些資源的狀態,那很好。但是,考慮到Kubernetes集群本質上是高度動態的,這通常是不夠的。

通常,我們還希望監視這些資源並在事件發生時跟踪事件。例如,我們可能對跟踪pod生命週期事件或部署狀態更改感興趣。儘管我們可以使用輪詢,但是這種方法會受到一些限制。首先,隨著要監視的資源數量的增加,它無法很好地擴展。其次,我們冒著丟失在輪詢週期之間發生的事件的風險。

為了解決這些問題,Kubernetes具有的概念Watches,它可通過所有資源集合的API調用watch的查詢參數。如果其值為false或忽略,則GET操作的行為與往常一樣:服務器處理該請求並返回與給定條件匹配的資源實例的列表。但是,傳遞watch=true會極大地改變其行為:

  • 現在,響應包含一系列修改事件,其中包含修改的類型和受影響的對象
  • 在發送了第一批事件後,將使用稱為長輪詢的技術將連接保持打開狀態

3.創建Watch

Java Kubernetes API通過Watch Watches ,它具有一個靜態方法: createWatch.此方法採用三個參數:

  • ApiClient ,處理對Kubernetes API服務器的實際REST調用
  • 一個Call實例,它描述了要監視的資源集合
  • 具有預期資源類型的TypeToken

我們使用庫中可用的任何xxxApi listXXXCall()方法之一創建一個Call例如,要創建一個檢測Pod Watch ,我們將使用listPodForAllNamespacesCall()

CoreV1Api api = new CoreV1Api(client);

 Call call = api.listPodForAllNamespacesCall(null, null, null, null, null, null, null, null, 10, true, null);

 Watch<V1Pod> watch = Watch.createWatch(

 client,

 call,

 new TypeToken<Response<V1Pod>>(){}.getType()));

在這裡,我們null ,意思是“使用默認值”,只有兩個例外: timeoutwatch.對於監視呼叫,必須將後者設置為true否則,這將是常規的休息電話。 timeout,充當監視“生存時間”的角色,這意味著服務器將在其過期後停止發送事件並終止連接

timeout參數找到合適的值(以秒為單位)需要一些反複試驗,因為這取決於客戶端應用程序的確切要求。另外,檢查您的Kubernetes集群配置也很重要。通常情況下,手錶有5分鐘的硬限制,因此超過該限制將無法獲得理想的效果。

4.接收事件

仔細觀察Watch類,我們可以看到它同時實現了標準JRE的IteratorIterable ,因此我們可以在for-eachhasNext()-next()循環中createWatch()

for (Response<V1Pod> event : watch) {

 V1Pod pod = event.object;

 V1ObjectMeta meta = pod.getMetadata();

 switch (event.type) {

 case "ADDED":

 case "MODIFIED":

 case "DELETED":

 // ... process pod data

 break;

 default:

 log.warn("Unknown event type: {}", event.type);

 }

 }

每個事件的type字段告訴我們對象發生了什麼類型的事件-在我們的案例中是Pod。一旦消耗了所有事件,就必須對Watch.createWatch()進行新調用以再次開始接收事件。在示例代碼中,我們while循環中Watch創建和結果處理。其他方法也是可能的,例如使用ExecutorService或類似方法在後台接收更新。

5.使用資源版本和書籤

上面的代碼有一個問題,就是每次我們創建一個新的Watch,有一個初始事件流,其中包含給定類型的所有現有資源實例。發生這種情況是因為服務器假定我們之前沒有關於它們的任何信息,因此只將它們全部發送出去

但是,這樣做不利於有效處理事件的目的,因為我們僅在初始加載後需要新事件。為了防止再次接收所有數據,監視機制還支持兩個附加概念:資源版本和書籤。

5.1。資源版本

Kubernetes中的每個資源在resourceVersion字段,它只是服務器在每次更改時設置的不透明字符串。此外,由於資源集合也是資源,因此存在與之關聯resourceVersion隨著從集合中添加,刪除和/或修改新資源,此字段將相應更改。

當我們進行返回集合的and包含resourceVersion參數的API調用時,服務器將使用其值作為查詢的“起點”。對於Watch API調用,這意味著將僅包括在創建已知版本之後發生的事件。

但是,我們如何使resourceVersion包含在調用中?簡單:我們只需執行一次初始同步調用即可檢索資源的初始列表,其中包括集合的resourceVersion,然後在後續的Watch調用中使用它:

String resourceVersion = null;

 while (true) {

 if (resourceVersion == null) {

 V1PodList podList = api.listPodForAllNamespaces(null, null, null, null, null, "false",

 resourceVersion, null, 10, null);

 resourceVersion = podList.getMetadata().getResourceVersion();

 }

 try (Watch<V1Pod> watch = Watch.createWatch(

 client,

 api.listPodForAllNamespacesCall(null, null, null, null, null, "false",

 resourceVersion, null, 10, true, null),

 new TypeToken<Response<V1Pod>>(){}.getType())) {



 for (Response<V1Pod> event : watch) {

 // ... process events

 }

 } catch (ApiException ex) {

 if (ex.getCode() == 504 || ex.getCode() == 410) {

 resourceVersion = extractResourceVersionFromException(ex);

 }

 else {

 resourceVersion = null;

 }

 }

 }

在這種情況下,異常處理代碼非常重要resourceVersion不存在時,Kubernetes服務器將返回504或410錯誤代碼。在這種情況下,返回的消息通常包含當前版本。不幸的是,這些信息並不是以任何結構化的方式出現的,而是作為錯誤消息本身的一部分。

提取代碼(也稱為醜陋的hack)為此目的使用了正則表達式,但是由於錯誤消息傾向於與實現相關,因此代碼會退回到null值。這樣,主循環返回到其起點,使用新的resourceVersion恢復新列表並恢復監視操作。

無論如何,即使有這樣的警告,關鍵點在於現在事件列表不會在每隻手錶上都從頭開始。

5.2。書籤

**書籤是一項可選功能,可Watch調用返回的事件流上BOOKMARK**事件。此事件的元數據中包含一個resourceVersion值,我們可以在隨後的Watch調用中將其用作新的起點。

由於這是一項可選功能,我們必須通過在API調用allowWatchBookmarks true傳遞給allowWatchBookmarks來顯式啟用它。該選項僅在創建Watch時有效,否則將被忽略。另外,服務器可能會完全忽略它,因此客戶端完全不應依賴於接收那些事件。

resourceVersion的以前的方法進行比較時,書籤使我們可以通過昂貴的同步調用來擺脫大部分麻煩:

String resourceVersion = null;



 while (true) {

 // Get a fresh list whenever we need to resync

 if (resourceVersion == null) {

 V1PodList podList = api.listPodForAllNamespaces(true, null, null, null, null,

 "false", resourceVersion, null, null, null);

 resourceVersion = podList.getMetadata().getResourceVersion();

 }



 while (true) {

 try (Watch<V1Pod> watch = Watch.createWatch(

 client,

 api.listPodForAllNamespacesCall(true, null, null, null, null,

 "false", resourceVersion, null, 10, true, null),

 new TypeToken<Response<V1Pod>>(){}.getType())) {

 for (Response<V1Pod> event : watch) {

 V1Pod pod = event.object;

 V1ObjectMeta meta = pod.getMetadata();

 switch (event.type) {

 case "BOOKMARK":

 resourceVersion = meta.getResourceVersion();

 break;

 case "ADDED":

 case "MODIFIED":

 case "DELETED":

 // ... event processing omitted

 break;

 default:

 log.warn("Unknown event type: {}", event.type);

 }

 }

 }

 } catch (ApiException ex) {

 resourceVersion = null;

 break;

 }

 }

 }

在這裡,我們只需要在第一次通過時以及在內部循環中獲得ApiException時就獲得完整列表。請注意, BOOKMARK事件與其他事件具有相同的對像類型,因此在這裡我們不需要任何特殊的強制轉換。但是,我們關心的唯一字段是resourceVersion ,我們將其保存用於下一個Watch調用。

六,結論

在本文中,我們介紹了使用Java API客戶端Watches