编写控制器
控制器是 Halo 的关键组件,它们负责对每个自定义模型对象进行操作,协调所需状态和当前状态,参考: 控制器概述。
控制器通常在具有一般事件序列的控制循环中运行:
- 观察:每个控制器将被设计为观察一组自定义模型对象,例如文章的控制器会观察文章对象,插件的控制器会观察插件自定义模型对象等。
- 比较:控制器将对象配置的期望状态与其当前状态进行比较,以确定是否需要更改,例如插件的
spec.enabled
为true
,而插件的当前状态是未启动,则插件控制器会处理启动 插件的逻辑。 - 操作:控制器将根据比较的结果执行相应的操作,以确保对象的实际状态与其期望状态一致,例如插件期望启动,插件控制器会处理启动插件的逻辑。
- 重复:上述所有步骤都由控制器重复执行直到与期望状态一致。
这是一个描述控制器作用的例子:房间里的温度自动调节器。
当你设置了温度,告诉了温度自动调节器你的期望状态(Desired State)。 房间的实际温度是当前状态(Current State)。 通过对设备的开关控制,温度自动调节器让其当前状态接近期望状态,未到达期望状态则继续调节,直到达到期望状态。
在 Halo 中控制器的运行部分已经有一个默认实现,你只需要编写控制器的调谐的逻辑也就是 控制器概述 中的所说的 Reconciler 即可。
编写 Reconciler
Reconciler 是控制器的核心,它是一个接口,你需要实现它的 reconcile()
方法,该方法接收一个 Reconciler.Request
对象,它包含了当前自定义模型对象的名称,你可以通过它来获取自定义模型对象的当前状态和期望状态,然后编写调谐的逻辑。
@Component
public class PostReconciler implements Reconciler<Reconciler.Request> {
@Override
public Result reconcile(Request request) {
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Post())
.build();
}
}
以上是一个简单的 Reconciler 实现,它实现了 reconcile()
方法,然后在 setupWith()
方法中将其通过 ControllerBuilder
构建为一个控制器并指定了
它要观察的自定义模型对象为Post
,当文章自定义模型对象发生变化时,reconcile()
方法就会被调用,从 Request request
参数中你可以获得当前发生变化的文章自定义模型对象的名称,然后你就可以通过名称来查询到自定义模型对象进行调谐了。
构建控制器
setupWith()
方法用于根据当前类的 reconcile
方法构建控制器,你可以通过 ControllerBuilder
提供的方法来构建并定制控制器:
public class ControllerBuilder {
private final String name;
private Duration minDelay;
private Duration maxDelay;
private final Reconciler<Reconciler.Request> reconciler;
private Supplier<Instant> nowSupplier;
private Extension extension;
private ExtensionMatcher onAddMatcher;
private ExtensionMatcher onDeleteMatcher;
private ExtensionMatcher onUpdateMatcher;
private ListOptions syncAllListOptions;
private boolean syncAllOnStart = true;
private int workerCount = 1;
}
name
:控制器的名称,用于标识控制器。minDelay
:控制器的最小延迟,用于控制控制器的最小调谐间隔,默认为 5 毫秒。maxDelay
:控制器的最大延迟,用于控制控制器的最大调谐间隔,默认为 1000 秒。reconciler
:控制器的调谐器,用于执行调谐逻辑,你需要实现Reconciler
接口。nowSupplier
:用于获取当前时间的供应商,用于控制器的时间戳,默认使用Instant.now()
获取当前时间。extension
:控制器要观察的自定义模型对象。onAddMatcher
:用于匹配添加事件的匹配器,当自定义模型对象被创建时会触发。onDeleteMatcher
:用于匹配删除事件的匹配器,当自定义模型对象被删除时会触发。onUpdateMatcher
:用于匹配更新事件的匹配器,当自定义模型对象被更新时会触发。syncAllListOptions
:用于同步所有自定义模型对象的 查询条件,仅当syncAllOnStart
为true
时生效。syncAllOnStart
:是否在控制器启动时同步所有自定义模型对象,默认为true
,可以配合syncAllListOptions
使用以缩小需要同步的对象范围避免不必要的同步,例如只同步某个用户创建的文章或者某个固定名称的 ConfigMap 对象。如果你的控制器不需要同步所有对象,可以将其设置为false
。workerCount
:控制器的工作线程数,用于控制控制器的并发度,如果你的控制器需要处理大量的对象,可以将其设置为大于 1 的值,以提高控制器的处理能力,但需要注意的是并发度越高,系统的负载也会越高。这里的并发度是指控制器的并发度,但是每个控制器还是单线程执行的。
ExtensionMatcher
onAddMatcher/onUpdateMatcher/onDeleteMatcher
都是 ExtensionMatcher
类型,用于决定当自定义模型对象发生变化时是否触发控制器:
public interface ExtensionMatcher {
boolean match(Extension extension);
}
这里match
方法的 Extension
参数类型与 ControllerBuilder
中的 extension
类型始终是一致的,因此可以直接通过强制类型转换来得到需要的类型。
比如我们想要观察文章对象,但是只想观察文章对象中 visible
字段为 PUBLIC
的文章,可以这样
public class PostReconciler implements Reconciler<Reconciler.Request> {
@Override
public Result reconcile(Request request) {
return Result.doNotRetry();
}
@Override
public Controller setupWith(ControllerBuilder builder) {
// 只想观察 VisibleEnum.PUBLIC 的文章
ExtensionMatcher extensionMatcher = extension -> {
var post = (Post) extension;
return VisibleEnum.PUBLIC.equals(post.getSpec().getVisible());
};
return builder
.extension(new Post())
.onAddMatcher(extensionMatcher)
.onUpdateMatcher(extensionMatcher)
.onDeleteMatcher(extensionMatcher)
.build();
}
}
控制启动时同步的范围
如果想要在控制器启动时控制同步对象的范围,可以通过 syncAllListOptions
和 syncAllOnStart
来实现,例如只同步某个用户创建的文章:
public class PostReconciler implements Reconciler<Reconciler.Request> {
@Override
public Result reconcile(Request request) {
return Result.doNotRetry();
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new Post())
.syncAllListOptions(ListOptions.builder()
.fieldQuery(QueryFactory.equal("spec.owner", "guqing"))
.build()
)
.syncAllOnStart(true)
.build();
}
}
Reconciler 的返回值
reconcile()
方法的返回值是一个 Result
对象,它包含了调谐的结 果,你可以通过它来告诉控制器是否需要重试,如果需要重试则控制器会在稍后再次调用 reconcile()
方法,而这个过程会一直重复,直到 reconcile()
方法返回成功为止,这个过程被称之为调谐循环(Reconciliation Loop)。
record Result(boolean reEnqueue, Duration retryAfter) {}
Result
对象包含了两个属性:reEnqueue 和 retryAfter,reEnqueue 用于标识是否需要重试,retryAfter 用于标识重试的时间间隔,如果 reEnqueue 为 true 则会在 retryAfter 指定的时间间隔后再次调用 reconcile()
方法,如果 reEnqueue 为 false 则不会再次调用 reconcile()
方法。
在没有特殊需要时,retryAfter
可以不指定,控制器会有一套默认的重试策略。
如果直接返回 null
则会被视为成功,效果等同于返回 new Result(false, null)
。
Reconciler 的异常处理
当 reconcile()
方法抛出异常时,控制器会将异常记录到日志中,然后会将 Request request
对象重新放入队列中,等待下次调用 reconcile()
方法,这个过程会一直重复,直到 reconcile()
成功,对于默认重试策略,每次重试间隔会越来越长,直到达到最长间隔后不再增加。
控制器示例
本章节将通过一个简单的示例来演示如何编写控制器。