Developing your own Kubernetes controller in Java

栏目: IT技术 · 发布时间: 4年前

/ KUBERNETES , CONTROLLER , JAVA , FABRIC8 , SIDECAR

Your own Kubernetes controller - Developing in Java

In theprevious post, we laid out the foundations to create our own custom Kubernetes controller. We detailed what a controller was, and that its only requirement is to be able to communicate with HTTP/JSON. In this post, we are going to finally start developing it.

The technology stack can be Python, NodeJS or Ruby. Because this blog is named "A Java Geek", it’s normal to choose Java.

As a use-case, we will implement the sidecar pattern: every time a pod gets scheduled, a sidecar pod will be scheduled along it as well. If the former is removed, the latter needs to be as well.

Choosing the right tool

In order to execute REST calls in Java, one needs to generate the bindings first. There are several ways to get those: . The most tedious one is to do that manually: one needs to carefully get hold of all possible JSON request and response combinations, develop the corresponding Java objects, choose the JSON serialization framework, as well as the HTTP client. . The next best option is to use proprietary code generators such as Swagger or Apiary . This requires the API provider to provide the model in one of the possible formats. On the downside, one needs to use the relevant tool. Sometimes, the format is more or less open, such as the OpenAPI specification . In that case, the tool can be chosen among those that implement the format. . In the best of cases, the bindings are already provided.

This is the case with Kubernetes: the project provides their own bindings, for a variety of languages . The issue is that the language wrapper is very close to the REST API, too close for my taste. For example, this is how one lists all pods across all namespaces:

ApiClient client = Config.defaultClient();
CoreV1Api core = new CoreV1Api(client);
V1PodList pods =
    core.listPodForAllNamespaces(null, null, null, null, null, null, null, null); (1)
1 Notice all the null parameters that need to be passed

This is what was meant by "the wrapper code being very close to the REST API"". Fortunately, there’s another option available. The Fabric8 organization offers a fluent Java API on Github . The code equivalent to the above one is:

KubernetesClient client = new DefaultKubernetesClient();
PodList pods = client.pods().inAnyNamespace().list();    (1)
1 Fluent, no need to pass useless null parameters

A quick overview of Fabric8

In a few words, with Fabric8’s API, all Kubernetes resources are available on the KubernetesClient instance e.g. :

  • client.namespaces()
  • client.services()
  • client.nodes()
  • etc.

Depending on the nature of the resource, it can be scoped by a namespace - or not:

  • client.pods().inAnyNamespace()
  • client.pods().inNamespace("ns")

At that point, the verb can be invoked:

List all pods in all namespaces
client.pods().inAnyNamespace().list();
Delete all pods in the namespace ns
client.pods().delete(client.pods().inNamespace("ns").list().getItems());
Create a new namespace with the name ns
client.namespaces()
  .createNew()
    .withApiVersion("v1")
    .withNewMetadata()
      .withName("ns")
    .endMetadata()
  .done();

Implementing the control loop

Remember that a Kubernetes controller is just a control loop that watches the state of the cluster, and reconciles it with the desired state. In order to be aware of scheduling/deleting events, one needs the Observer pattern. The application will subscribe to such events, and the relevant callbacks will be triggered when they happen.

This class diagram is a very simplified diagram of the API:

Developing your own Kubernetes controller in Java

To actually implement a watcher is just a matter of the following lines:

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());
1 Act when a new pod is added
2 Act when an existing pod is modified
3 Act when a pod is deleted
4 Act in case of error
5 Clean up any resource. If the client closes correctly, cause will be null

The nitty-gritty details

At this point, we have everything that is required to implement the sidecar pattern. I won’t show the whole code - it’s available on GitHub , but a few key things need to be highlighted.

Tagging the sidecar

At its core, the watcher needs to add a sidecar pod when a new pod is added, and remove the sidecare when it’s removed. This basic approach doesn’t work: if a sidecar pod is scheduled, then the watcher will be triggered, and it will add a new sidecar pod to the sidecar. And this will go on and on…​ Thus, it’s of utmost importance to "tag" sidecars pods. When such a pod is detected, the creation logic shouldn’t be triggered.

There are several ways to tag a sidecar pod:

  • Suffixing sidecar pods' name with a specific string e.g. sidecar
  • Adding specific labels:
    client.pods()
      .inNamespace("ns")
      .createNew()
        .withNewMetadata()
          .addToLabels("sidecar", "true")
        .endMetadata()
      .done();

Removing the sidecar along with the pod

A pod should have one and only one sidecar. It should be created when the pod is added, and should be deleted when the later is deleted as described above.

Hence, a reference to the main pod should be added to the sidecar. This way, when a pod is deleted - and when it’s not a sidecar, we should find the assigned sidecar and delete it as well.

The first naive approach is to explicitly delete the sidecar when the main pod is deleted. However, this is a lot of work, for not much. Kubernetes allows to bind the lifecycle a pod to the lifecycle of another. The deletion logic is then handled by Kubernetes itself. This is backed by the concept of ownerReference .

The API makes it straightforward to implement:

client.pods()
  .inNamespace("ns")
  .createNew()
    .withNewMetadata()
      .addNewOwnerReference()
        .withApiVersion("v1")
        .withKind("Pod")
        .withName(podName)
        .withUid(pod.getMetadata().getUid())
      .endOwnerReference()
    .endMetadata()
  .done();

Always keep a sidecar

Adding a sidecar doesn’t mean it will stay forever this way. For example, a pod belonging to a deployment can be deleted. It’s the goal of the deployment to re-create a pod to reach the desired number of replicas.

Likewise, if a sidecar is deleted while the main pod is kept, a new sidecar should be spawned with the correct own reference.

Conclusion

In this post, we described how one could implement a Kubernetes controller with the Java language on the JVM. With the help of Fabric8’s API, it has been quite straightforward. The main issues come from the edge cases in the scheduling/deleting logic. In the next and final post of this series, we will finally see how to deploy and run the code.

The complete source code for this post can be found on Github

Follow @nicolas_frankel


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Android群英传

Android群英传

徐宜生 / 电子工业出版社 / 2015-9 / 69.00元

《Android群英传》对具有一定Android开发基础的读者,以通俗易懂的语言介绍了Android开发的进阶技巧。《Android群英传》共分为13章,讲解了Android体系与系统架构、Android开发工具新接触、Android控件架构与自定义控件详解、ListView使用技巧、Android Scroll分析、Android绘图机制与处理技巧、Android动画机制与使用技巧、Activi......一起来看看 《Android群英传》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具