拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 将Watch与Kubernetes API结合使用

将Watch与Kubernetes API结合使用

白鹭 - 2021-11-24 650 0 0

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

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *