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
。
0 评论