Fabric8 v2 的第一个版本一直在使用基于 JAX-RS 的 Kubernetes 客户端,该客户端使用 Apache CXF 。客户端很棒,但我们一直想提供更薄、依赖更少的东西 (以便更容易采用) 。我们还想给它一个 fecelift 并围绕它构建一个 DSL,以便它变得更易于使用和阅读。
新客户端目前位于:https: //github.com/fabric8io/kubernetes-client ,它提供以下模块:
- Kubernetes 客户端。
- 一个 Openshift 客户端。
- 以上所有的模拟框架(基于 EasyMock )
初见客户
让我们快速浏览一下如何使用客户端创建、列出和删除内容:
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
上面的片段几乎是不言自明的 (这就是使用 DSL 的好处), 但我还有一篇博文要填写,所以我会提供尽可能多的细节。
客户端域模型
您可以将客户端视为两件事的结合:
- Kubernetes 域模型。
- 围绕模型的 DSL。
域模型是一组对象,代表在客户端和 Kubernetes / Openshift 之间交换的数据。数据的原始格式是 JSON。这些 JSON 对象非常复杂且结构非常严格,因此手工制作它们并非易事。
我们需要有一种方法在 Java 中操作这些 JSON 对象 (并能够利用代码完成等), 但也尽可能接近原始格式。使用 JSON 对象的 POJO 表示可用于操作,但它不太像 JSON,而且对于具有深层嵌套的 JSON 也不是真正有用的。因此,我们决定在那些使用与原始 JSON 完全相同的结构的 POJO 之上生成流畅的构建器。
例如,这里是 Kubernetes 服务的 JSON 对象:
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
使用 Fluent Builders 的 Java 等价物可能是:
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
域模型存在于它自己的项目中: Fabric8 的 Kubernetes 模型 。该模型是经过一个漫长的过程从 Kubernetes 和 Openshift 代码生成的:
- Go 源码转换 JSON schema
- JSON 模式转换 POJO
- 一代流利的建设者
Fluent builder 是由一个名为 sundrio 的小项目生成的,我将在以后的帖子中介绍它。
获取客户端实例
获取 默认客户端 实例的实例非常简单,因为提供了一个空的构造函数。当使用空构造函数时,客户端将使用默认设置:
- Kubernetes 网址
- 系统属性“ kubernetes.master ”
- 环境变量“ KUBERNETES_MASTER ”
- 来自用户主目录中的“ .kube/config ”文件。
- 使用 DNS:“ https://kubernetes.default.svc ”
- 服务帐户路径“ /var/run/secrets/kubernetes.io/serviceaccount/ ”
可以通过传递 Config 对象的实例来提供更细粒度的配置。
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
客户端扩展和适配器
为了支持 Kubernetes 扩展(例如 Openshift ),客户端使用 Extension 和 Adapter 的概念。这个想法很简单。扩展客户端扩展 默认客户端 并实现 扩展 。只要能通过Java的 ServiceLoader 找到一个 Adapter ,每个client实例都可以适配 Extension (原谅我爹)。
下面是一个示例,说明如何使客户端的任何实例适应 OpenshiftClient 的实例:
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
只有当 /oapi 存在于 Kubernetes 客户端返回的根路径列表中时,上面的代码才有效(即客户端指向一个开放式轮班安装)。如果不是,它将抛出 IllegalArugementException。
如果用户正在编写绑定到 Openshift 的代码,他总是可以直接实例化 默认 openshift 客户端 的实例。
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
测试和模拟
模拟正在与外部系统对话的客户端是一种很常见的情况。当客户端是扁平的 (不支持方法链) 时,模拟是微不足道的,并且有大量的框架可以用于这项工作。但是,当使用 DSL 时,事情变得更加复杂,需要大量样板代码将各个部分连接在一起。如果原因不明显,我们就说使用模拟,您定义每次方法调用的模拟行为。与等效的 Flat 对象相比,DSL 往往具有更多的方法(具有更少的参数)。仅此一项就增加了定义行为所需的工作。此外,这些方法通过返回中间对象链接在一起,这意味着它们也需要被模拟,这进一步增加了工作量和复杂性。
为了删除所有样板文件并使模拟客户端变得非常简单,我们将客户端的 DSL 与模拟框架的 DSL 相结合: EasyMock 。这意味着此 DSL 的入口点是 Kubernetes 客户端 DSL 本身,但终端方法已被修改,以便它们返回“Expectation Setters”。一个例子应该使这更容易理解。
//Instantiate the client
KubernetesClient client = new DefaultKubernetesClient();
//Create a service
Service myservice = ...;
client.services().inNamespace("fabric8").create(myservice);
//Create a service inline
Service jenkins = client.services().inNamespace("fabric8").createNew()
.withNewMetadata()
.withName("jenkins")
.addToLabels("component", "jenkins")
.endMetadata()
.done();
//List services
ServiceList serviceList = client.services().inNamespace("fabric8").list();
//Watch services
client.services().inNamespace("fabric8").watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
logger.info("{}: {}", action, resource);
}
});
//Delete by label
Boolean deleted = client.services().withLabel("component", "jenkins").delete();
//Close client
client.close();
模拟框架可以轻松地与其他 Fabric8 组件结合使用,例如 CDI 扩展 。您只需要创建返回模拟的 @Produces 方法。