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


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

查看所有标签

猜你喜欢:

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

增长黑客实战

增长黑客实战

范冰、张溪梦 / 电子工业出版社 / 2017-6 / 59.00

《增长黑客实战》围绕硅谷前沿的增长黑客职业,讲解增长理念的树立、增长团队的组建、流程制度的创立、技术营销的运用等团队运营成功实战经验。作者以自身创业经验为蓝本,结合真实案例,并融入一些伟大创业者的智慧,创建了一套思考、验证和追求卓越增长的理论体系。那些想要验证自己的创意、解决实际增长问题和拥有成功事业的人,可以将《增长黑客实战》当成一套清晰的实践指南、一幅组建增长团队的指导蓝图,或者一套值得反复玩......一起来看看 《增长黑客实战》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器