Java Operator SDK
Operators 代表Kubernetes管理集群和非集群资源。这个Java Operator SDK (JOSDK) 旨在通过使用一个对Java开发人员来说应该感觉自然的API,使编写Kubernetes操作员变得尽可能容易,并且不必担心许多低级细节,因为SDK会自动处理。
生成项目骨架
该项目包括一个Maven插件,用于生成项目的骨架:
mvn io.javaoperatorsdk:bootstrapper:[version]:create
-DprojectGroupId=org.acme
-DprojectArtifactId=getting-started
这个命令使用了Java Operator SDK的bootstrapper插件来创建一个项目的骨架。具体命令是mvn io.javaoperatorsdk:bootstrapper:[version]:create -DprojectGroupId=org.acme -DprojectArtifactId=getting-started
。
其中[version]
是指Java Operator SDK的版本号,-DprojectGroupId=org.acme
指定了项目的groupId为org.acme
,-DprojectArtifactId=getting-started
指定了项目的artifactId为getting-started
。
运行这个命令后,bootstrapper插件会自动生成一个基本的项目结构,包括Maven的pom.xml文件和一些示例代码,以便你可以开始编写Kubernetes操作员。这个骨架项目可以作为一个起点,你可以根据需要进行修改和扩展。
快速入门
使用SDK的最简单方法是启动并执行我们的示例之一。有一个专门的页面来描述如何开始。
以下是开发代码并将operator部署到Kubernetes集群的主要步骤。在samples/mysql-schema/目录下可以找到更详细和具体的版本。
- 配置kubectl以与您选择的Kubernetes集群一起使用。
- 应用自定义资源定义(Custom Resource Definition)。
- 在根目录下使用
mvn install
编译整个项目(包括框架和示例)。 - 运行您选择的示例的主类,并查看示例的README文件以了解其功能。在本地运行时,框架将使用您的Kubernetes客户端配置(在~/.kube/config中)建立与集群的连接。这就是为什么提前设置kubectl非常重要。
- 您可以在本地开发模式下使用这个代码进行测试。
- 构建Docker镜像并将其推送到镜像仓库。
- 应用RBAC配置。
- 应用部署配置。
- 验证operator是否正在运行。请不要再在本地运行它,以避免处理来自集群API服务器的事件时发生冲突。
模式和最佳实践
本文档介绍了构建和运行操作员的模式和最佳实践,以及如何使用Java Operator SDK (JOSDK)实现它们。请参阅Operator SDK中的最佳实践。
实现Reconciler
始终协调所有资源
协调可以由多个来源的事件触发。检查事件并只协调控制器管理的相关资源或资源子集可能很诱人。然而,这被认为是操作员的反模式,因为Kubernetes的分布式性质使得很难确保始终接收到所有事件。如果由于某种原因,您的操作员没有接收到一些事件,如果您不协调整个状态,您可能会根据关于集群状态的不适当假设进行操作。这就是为什么始终协调所有资源很重要,无论多么诱人只考虑子集。幸运的是,JOSDK尽可能地使其易于使用和高效,通过提供智能缓存来避免过度访问Kubernetes API服务器,并确保仅在需要时触发您的调和器。
由于在业界对这个话题有共识,JOSDK从框架的第2个版本开始不再提供Reconciler实现中的事件访问。
事件源和缓存
如上所述,在协调期间,最佳实践是协调控制器管理的所有相关资源。这意味着我们希望将期望状态与集群的实际状态进行比较。直接从Kubernetes API服务器读取资源的实际状态意味着会产生很大的负载。因此,通常的做法是为相关资源创建一个监视器,并缓存其最新状态。这是使用Informer模式完成的。在Java Operator SDK中,informer被封装在EventSource中,以将其集成到框架的事件系统中。这是通过InformerEventSource类实现的。
仅当实际资源已经在缓存中时,才会将新事件传播到Reconciler。因此,Reconciler实现只需要将期望状态与由缓存资源提供的观察到的状态进行比较。如果在缓存中找不到资源,则需要创建该资源。如果实际状态与期望状态不匹配,则需要更新该资源。
幂等性
由于在触发Reconciler时应协调所有资源,并且在任何给定资源上可以多次触发协调,特别是在使用重试策略时,因此Reconciler实现必须是幂等的,这意味着相同的观察到的状态应该产生完全相同的结果。这也意味着操作员通常应以无状态方式运行。幸运的是,由于操作员通常管理声明性资源,因此通常很容易确保幂等性。
同步或异步方式处理资源
根据您的用例,您的协调逻辑可能需要等待相当长的时间,而操作员等待资源达到其期望状态。例如,您的Reconciler可能需要等待Pod准备就绪,然后再执行其他操作。可以通过同步或异步方式来解决此问题。
异步方式是仅在Reconciler确定无法在此时完成其全部逻辑时退出协调逻辑。这将释放资源以处理其他主资源事件。但是,这需要放置足够的事件源以监视操作员等待的所有资源的状态更改。当正确执行此操作时,任何状态更改都将再次触发Reconciler,它将有机会完成其处理。
同步方式是定期轮询资源的状态,直到它们达到所需状态。如果在您的Reconciler实现的协调方法的上下文中执行此操作,这将阻止当前线程,可能需要很长时间。因此,通常建议使用异步处理方式。
为什么要自动重试?
默认情况下,自动重试已启用,并且可以根据您的需求进行配置。虽然完全关闭此功能是可能的,但我们建议不要这样做。为您的Reconciler配置自动重试的主要原因是由于Kubernetes的分布式性质,错误经常发生:瞬态网络错误可以通过自动重试轻松处理。同样,当与Kubernetes资源一起工作时,不同的操作者可能会修改资源,因此在处理Kubernetes资源时可能会发生冲突。这样的冲突通常可以通过再次协调资源来自然地解决。如果自动完成,整个过程可以完全透明地进行。
管理状态
由于Kubernetes资源的声明性本质,仅处理Kubernetes资源的操作员可以以无状态方式运行,即它们不需要维护有关这些资源状态的信息,因为应该可以从其表示中完全重建资源状态(毕竟,这就是声明性的含义)。但是,当处理外部资源时,这通常不再成立,可能需要操作员跟踪此外部状态,以便在发生另一个协调时可用。虽然这样的状态可以放在主资源的状态子资源中,但如果需要跟踪大量状态,则可能很难管理。它也违反了最佳实践,即资源的状态应该表示实际资源状态,而其规范表示期望状态。因此,不建议将不严格表示资源实际状态的状态放入其中。相反,建议将这种状态放入用于此目的的单独资源中,例如Kubernetes Secret或ConfigMap甚至是专用的自定义资源,其结构更容易验证。
在Informer错误和缓存同步超时的情况下停止(或不停止)操作员
可以配置,如果任何Informer错误发生在启动时,操作员是否应停止。默认情况下,如果在启动时出现错误,例如informer没有权限列出目标资源(包括主资源或辅助资源),则操作员将立即停止。通过将上述标志设置为false,可以更改此行为,从而操作员即使某些informer未启动也会启动。在这种情况下-与稍后遇到问题的informer启动时一样-将无限期地使用指数回退重试连接。仅当资源无法反序列化时,操作员才会停止,当前。更改此标志的典型用例是监视控制器的名称空间列表。在某些名称空间中某些资源的权限问题可能存在时,最好启动操作员,以便在处理其他名称空间时可以处理其他名称空间。
stopOnInformerErrorDuringStartup对缓存同步超时行为产生影响。如果为true,则操作员将在缓存同步超时时停止。如果为false,则在超时后,即使一个或多个事件源缓存未同步,控制器也将开始协调资源。
使用示例Operators
我们在目录下提供了一些示例,旨在演示不同场景下使用不同组件的用法,但主要是更真实的示例:
- webpage: 简单的示例,创建包含HTML代码的自定义资源的NGINX Web服务器。
- mysql-schema: 管理MySQL数据库中模式的操作员。演示如何管理非Kubernetes资源。
- tomcat: 具有两个控制器的操作员,管理Tomcat实例和运行在Tomcat中的Web应用程序。这个示例的目的是展示如何管理多个相关的自定义资源和/或更多的控制器。
实现示例Operator
使用Maven将以下依赖项添加到您的项目中:
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework</artifactId>
<version>{see https://search.maven.org/search?q=a:operator-framework%20AND%20g:io.javaoperatorsdk for latest version}</version>
</dependency>
或者,您也可以使用Gradle,但是需要将SDK声明为注解处理器,以便生成控制器和自定义资源类之间的映射关系:
dependencies {
implementation "io.javaoperatorsdk:operator-framework:${javaOperatorVersion}"
annotationProcessor "io.javaoperatorsdk:operator-framework:${javaOperatorVersion}"
}
在添加了依赖项后,定义一个主方法来初始化Operator并注册一个控制器。
public class Runner {
public static void main(String[] args) {
Operator operator = new Operator();
operator.register(new WebPageReconciler());
operator.start();
}
}
控制器实现业务逻辑,并描述了处理CRD所需的所有类。
@ControllerConfiguration
public class WebPageReconciler implements Reconciler<WebPage> {
// Return the changed resource, so it gets updated. See javadoc for details.
@Override
public UpdateControl<CustomService> reconcile(CustomService resource,
Context context) {
// ... your logic ...
return UpdateControl.patchStatus(resource);
}
}
一个示例的自定义资源POJO表示
@Group("sample.javaoperatorsdk")
@Version("v1")
public class WebPage extends CustomResource<WebPageSpec, WebPageStatus> implements
Namespaced {
}
public class WebServerSpec {
private String html;
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
}
禁用CustomResource实现的验证
operator默认会查询部署的CRD以检查CustomResource实现是否与集群中已知的匹配。这需要向集群发起额外的查询,并且有时需要提升operator的权限才能从集群中读取CRD。这种验证主要是为了帮助新手operator开发人员入门并避免常见错误。高级用户或生产部署可能希望跳过此步骤。可以通过将CHECK_CRD_ENV_KEY环境变量设置为false来实现。
自动生成CRD
要从您的注解自定义资源类自动生成CRD清单,您只需要将以下依赖项添加到您的项目中(在背景中使用了注解处理器),使用Maven:
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-apt</artifactId>
<scope>provided</scope>
</dependency>
或者使用Gradle:
dependencies {
annotationProcessor 'io.fabric8:crd-generator-apt:<version>'
...
}
CRD将在target/classes/META-INF/fabric8目录中生成(如果使用测试范围,则在target/test-classes/META-INF/fabric8目录中生成),其中CRD名称后缀为生成的规范版本。例如,
- mycrs.java-operator-sdk.io-v1.yml
- mycrs.java-operator-sdk.io-v1beta1.yml
注意:使用quarkus-operator-sdk扩展的Quarkus用户无需添加任何额外的依赖项来生成他们的CRD,因为扩展本身会处理这个过程。
Quarkus
还提供了一个Quarkus扩展,以便于基于Quarkus的操作员开发。
在您的项目中添加以下依赖项:这个依赖项
<dependency>
<groupId>io.quarkiverse.operatorsdk</groupId>
<artifactId>quarkus-operator-sdk</artifactId>
<version>{see https://search.maven.org/search?q=a:quarkus-operator-sdk for latest version}
</version>
</dependency>
创建一个应用程序,Quarkus将自动创建和注入一个KubernetesClient(或OpenShiftClient)、Operator、ConfigurationService和ResourceController实例,您的应用程序可以使用它们。下面是您需要编写的最小代码,以使您的操作员和控制器运行起来:
@QuarkusMain
public class QuarkusOperator implements QuarkusApplication {
@Inject
Operator operator;
public static void main(String... args) {
Quarkus.run(QuarkusOperator.class, args);
}
@Override
public int run(String... args) throws Exception {
operator.start();
Quarkus.waitForExit();
return 0;
}
}
Spring Boot
您还可以让Spring Boot自动连接您的应用程序并自动注册控制器。
将以下依赖项添加到您的项目中:这个依赖项
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-spring-boot-starter</artifactId>
<version>{see https://search.maven.org/search?q=a:operator-framework-spring-boot-starter%20AND%20g:io.javaoperatorsdk for
latest version}
</version>
</dependency>
创建应用
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
您还需要一个 @Configuration 来确保您的协调器已注册:
@Configuration
public class Config {
@Bean
public WebPageReconciler customServiceController() {
return new WebPageReconciler();
}
@Bean(initMethod = "start", destroyMethod = "stop")
@SuppressWarnings("rawtypes")
public Operator operator(List<Reconciler> controllers) {
Operator operator = new Operator();
controllers.forEach(operator::register);
return operator;
}
}
Spring Boot 测试支持
添加以下依赖项可以让您模拟需要加载 spring 容器的测试操作员,但不需要真正访问 Kubernetes 集群。
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-spring-boot-starter-test</artifactId>
<version>{see https://search.maven.org/search?q=a:operator-framework-spring-boot-starter%20AND%20g:io.javaoperatorsdk for
latest version}
</version>
</dependency>
模拟operator:
@SpringBootTest
@EnableMockOperator
public class SpringBootStarterSampleApplicationTest {
@Test
void contextLoads() {
}
}