將Watch與Kubernetes API結合使用
- Kubernete
- Docker
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
,意思是“使用默認值”,只有兩個例外: timeout
和watch.
對於監視呼叫,必須將後者設置為true
否則,這將是常規的休息電話。 timeout,
充當監視“生存時間”的角色,這意味著服務器將在其過期後停止發送事件並終止連接。
timeout
參數找到合適的值(以秒為單位)需要一些反複試驗,因為這取決於客戶端應用程序的確切要求。另外,檢查您的Kubernetes集群配置也很重要。通常情況下,手錶有5分鐘的硬限制,因此超過該限制將無法獲得理想的效果。
4.接收事件
仔細觀察Watch
類,我們可以看到它同時實現了標準JRE的Iterator
和Iterable
,因此我們可以在for-each
或hasNext()-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
。