技术堆栈可以是Python,NodeJS或Ruby。 由于此博客的名称为“ A Java Geek”,因此选择Java是正常的。
作为一个用例,我们将实现sidecar模式:每当对pod进行调度时,也会随之调度sidecar pod。 如果将前者删除,则后者也必须删除。
选择合适的工具
为了用Java执行REST调用,首先需要生成绑定。 有几种方法可以得到这些:。 最繁琐的操作是手动完成:一个需要仔细掌握所有可能的JSON请求和响应组合,开发相应的Java对象,选择JSON序列化框架以及HTTP客户端。 。 第二个最佳选择是使用专有代码生成器,例如Swagger或Apiary 。 这就要求API提供程序以一种可能的格式提供模型。 不利的一面是,需要使用相关工具。 有时,格式或多或少是开放的,例如OpenAPI规范 。 在这种情况下,可以从实现该格式的工具中选择该工具。 。 在最好的情况下,已经提供了绑定。
ApiClientclient=Config.defaultClient();
CoreV1Apicore=newCoreV1Api(client);
V1PodListpods=
core.listPodForAllNamespaces(null,null,null,null,null,null,null,null); (1)
- 注意所有需要传递的
null
参数
这就是“包装器代码非常接近REST API”的意思。幸运的是,还有另一个选择。Fabric8组织在Github上提供了流畅的Java API。与上述代码等效的代码是:
KubernetesClientclient=newDefaultKubernetesClient();
PodListpods=client.pods().inAnyNamespace().list(); (1)
- 流利,无需传递无用的
null
参数
Fabric8快速概述
简而言之,使用Fabric8的API,所有Kubernetes资源都可以在KubernetesClient
实例上使用, 例如 :
-
client.namespaces()
-
client.services()
-
client.nodes()
- 等等
根据资源的性质,它可以由名称空间限定范围-或不可以:
-
client.pods().inAnyNamespace()
-
client.pods().inNamespace("ns")
此时,可以调用动词:
-
列出所有命名空间中的所有Pod
-
client.pods().inAnyNamespace().list();
删除名称空间 -
client.pods().delete(client.pods().inNamespace("ns").list().getItems());
创建一个名为 -
client.namespaces() .createNew() .withApiVersion("v1") .withNewMetadata() .withName("ns") .endMetadata() .done();
ns
中的所有pod
ns
的新名称空间
实施控制回路
请记住,Kubernetes控制器只是一个控制循环,它监视集群的状态,并将其与所需状态进行协调。 为了了解调度/删除事件,需要Observer
模式。 应用程序将订阅此类事件,并且相关回调将在发生时触发。
此类图是API的非常简化的图:
![Watcher简化的类图](https://i-blog.csdnimg.cn/blog_migrate/09b201cf81275e274a5300b56bfad70d.png)
实际实现观察者只是以下几行的事情:
publicclassDummyWatcherimplementsWatcher<Pod>{
@Override
publicvoideventReceived(Actionaction,Podpod){
switch(action){
caseADDED: (1)
break;
caseMODIFIED: (2)
break;
caseDELETED: (3)
break;
caseERROR: (4)
break;
}
}
@Override
publicvoidonClose(KubernetesClientExceptioncause){
(5)
}
}
client.pods()
.inAnyNamespace()
.watch(DummyWatcher());
- 添加新广告连播时起作用
- 在修改现有广告连播时采取行动
- 删除广告连播时采取行动
- 发生错误时采取行动
- 清理任何资源。 如果客户端正确关闭,
cause
将为null
细腻的细节
至此,我们拥有了实现sidecar模式所需的一切。 我不会显示完整的代码- 可以在GitHub上找到 ,但是需要强调一些关键的事情。
标记边车
从本质上讲,观察者需要在添加新的吊舱时添加一个Sidecar吊舱,并在移除它时删除该Sidecare。 这种基本方法行不通:如果安排了Sidecar吊舱,则将触发观察者,并且它将向Sidecar添加新的Sidecar吊舱。 而且这种情况还会持续下去......因此,``标记''小汽车吊舱至关重要。 当检测到此类Pod时,不应触发创建逻辑。
有几种标记边车吊舱的方法:
- 在sidecar pod的名称后加上特定的字符串, 例如
sidecar
- 添加特定标签:
client.pods() .inNamespace("ns") .createNew() .withNewMetadata() .addToLabels("sidecar","true") .endMetadata() .done();
连同吊舱一起卸下边车
吊舱应只有一个侧车。 如上所述,应在添加Pod时创建它,而在删除后者时应删除它。
因此,应将对主吊舱的引用添加到侧车中。 这样,当吊舱被删除时-当它不是小杂物时,我们应该找到分配的小杂物并将其删除。
第一种幼稚的方法是在删除主吊舱时显式删除边车。 但是,这是一项繁重的工作,花了很多时间。 Kubernetes允许将Pod的生命周期绑定到另一个Pod的生命周期。 然后,删除逻辑由Kubernetes本身处理。 这由ownerReference
的概念ownerReference
。
该API使其易于实现:
client.pods()
.inNamespace("ns")
.createNew()
.withNewMetadata()
.addNewOwnerReference()
.withApiVersion("v1")
.withKind("Pod")
.withName(podName)
.withUid(pod.getMetadata().getUid())
.endOwnerReference()
.endMetadata()
.done();
始终保持边车
添加Sidecar并不意味着它将永远保持这种方式。 例如,可以删除属于部署的吊舱。 部署的目标是重新创建一个Pod,以达到所需的副本数。
同样,如果在保留主吊舱的同时删除了边车,则应使用正确的自己的引用生成新的边车。
结论
在这篇文章中,我们描述了如何在JVM上使用Java语言实现Kubernetes控制器。 借助Fabric8的API,它变得非常简单。 主要问题来自调度/删除逻辑中的极端情况。 在本系列的下一篇也是最后一篇文章中,我们将最终看到如何部署和运行代码。
翻译自: https://blog.frankel.ch/your-own-kubernetes-controller/2/