作者 | Nicolas Fränkel
译者 | 天道酬勤,责编 | 徐威龙
封图 | CSDN 下载于视觉中国
在本文中,我们将开始开发自己的Kubernetes控制器。
技术栈可以是Python、NodeJS或Ruby。因为这个博客被命名为为“ Java极客”,因此选择Java是很正常的。
作为一个用例,我们将实现sidecar模式:每当一个pod被调度时,sidecar pod也会随之被调度。如果将前者删除,则后者也必须删除。
选择合适的工具
为了用Java执行REST调用,首先需要生成绑定。有几种方法可以做到这些。最繁琐的操作是手动执行此操作:需要仔细掌握所有可能的JSON请求和响应组合,开发相应的Java对象,选择JSON序列化框架以及HTTP客户端。第二个最佳选择是使用专有代码生成器,例如Swagger(https://swagger.io/)或Apiary(https://apiary.io/)。这就要求API提供程序以一种可能的格式提供模型。不利的一面是,需要使用相关工具。有时,格式或多或少是开放的,例如OpenAPI规范(https://swagger.io/specification/)。在这种情况下,可以从实现该格式的工具中选择工具。在最好的情况下,可能已经提供了绑定。
Kubernetes就是这种情况: 该项目为各种语言提供了自己的绑定(https://kubernetes.io/docs/reference/using-api/client-libraries/)。问题在于语言包装器与REST API非常接近,对于作者来说太熟悉了。 例如,这是列出所有名称空间中所有pod的方式:
ApiClient client = Config.defaultClient(); CoreV1Api core = new CoreV1Api(client); V1PodList pods = core.listPodForAllNamespaces(null, null, null, null, null, null, null, null);
注意:所有的null参数需要传递。
这就是“包装器代码非常接近REST API”的意思。幸运的是,还有另一个选择。Fabric8组织在Github上提供了流畅的Java API。与上述代码等效的代码是:
KubernetesClient client = new DefaultKubernetesClient(); PodList pods = client.pods().inAnyNamespace().list();
注意:无需传递无用的null参数。
Fabric8组织在Github上提供了流畅的Java API:
https://github.com/fabric8io/kubernetes-client)
Fabric8快速概述
简单来说,使用Fabric8的API,所有Kubernetes资源都可以在KubernetesClient实例上使用,例如:
client.namespaces()
client.services()
client.nodes()
根据资源的性质,它的作用域可以是一个名称空间,也可以不是:
client.pods().inAnyNamespace()
client.pods().inNamespace("ns")
此时,可以调用这个动作:
client.pods().inAnyNamespace().list();
client.pods().delete(client.pods().inNamespace("ns").list().getItems());
client .namespaces ()
.createNew ()
.withApiVersion (" v1 ")
.withNewMetadata ()
.withName (" ns ")
.endMetadata ()
.done
();
实现控制循环
注意,Kubernetes控制器只是一个控制循环,它监视集群的状态,并将其与所需状态进行协调。为了能够调度/删除事件,需要使用Observer模式。应用程序将订阅此类事件,当他们发生时,相关回调将被触发。
下图是一个非常简单的API图:
要真正实现一个监视程序,只需执行以下几行代码:
public class DummyWatcher implements Watcher<Pod> { @Override public void eventReceived(Action action, Pod pod) { switch (action) { case ADDED: //注意1 break; case MODIFIED: //注意2 break; case DELETED: //注意3 break; case ERROR: //注意4 break; } } @Override public void onClose(KubernetesClientException cause) { //注意5 } } client.pods() .inAnyNamespace() .watch(DummyWatcher());
添加新pod时起作用
修改现有pod时起作用
删除pod时起作用
发生错误时起作用
清理任何资源。如果客户端正确关闭,cause将为null
具体细节
现在,我们拥有了实现sidecar模式所需的一切。我不会展示全部代码,它可以在GitHub上找到:https://github.com/nfrankel/jvm-controller,但需要重点强调一些关键内容。
从本质上讲,观察者需要在添加新的pod时添加一个sidecar pod,并在移除它时删除它。这种基本方法行不通:如果安排了sidecar pod,则将触发观察者,并向sidecar添加新的sidecar pod。而且这种情况还会持续下去。因此,标记sidecar pod至关重要。检测到此类pod时,不应触发创建逻辑。
在sidecar pod的名称后加上特定的字符串,例如sidecar
添加如下特定标签:
client.pods() .inNamespace("ns") .createNew() .withNewMetadata() .addToLabels("sidecar", "true") .endMetadata() .done();
2) 连同pod一起移除sidecar
pod应只有一个sidecar。如上所述,应在添加Pod时创建它,而在删除后者时应删除它。
因此,应将对主pod的引用添加到sidecar中。这样,当pod被删除时,如果它不是一个sidecar,我们应该找到分配的sidecar并将其删除。
第一种简单的方法是在删除主pod时显式删除sidecar。但是,这是一项繁重的工作,需要花很多时间。Kubernetes允许将pod的生命周期绑定到另一个Pod的生命周期。然后,删除逻辑由Kubernetes本身处理。这由ownerReference的概念支持。
client.pods() .inNamespace("ns") .createNew() .withNewMetadata() .addNewOwnerReference() .withApiVersion("v1") .withKind("Pod") .withName(podName) .withUid(pod.getMetadata().getUid()) .endOwnerReference() .endMetadata() .done();
添加一个sidecar并不意味着它将永远保持这种方式。例如,可以删除属于部署的pod。部署的目标是重新创建一个pod,以达到所需的副本数量。
同样,如果在保留主pod的同时删除了sidecar,则应使用正确的引用生成一个新的sidecar。
结论
在这篇文章中,我们描述了如何在JVM上使用Java语言实现Kubernetes控制器。借助Fabric8的API,实现的操作非常简单 。主要问题来自调度/删除逻辑中的极端情况。在本系列的下一篇(也是最后一篇)文章中,我们将最终看到如何部署和运行代码。
这篇文章的完整源代码可以在Github上以Maven格式找到:
https://github.com/nfrankel/jvm-controller
希望这篇文章对你有用,欢迎评论区和我们讨论。
原文:https://blog.frankel.ch/your-own-kubernetes-controller/2/
【End】