Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
- "sample-operators/webpage"
- "sample-operators/leader-election"
- "sample-operators/operations"
- "sample-operators/kotlin-operator"
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,21 @@ projects want to advertise that fact here. For this reason, we ask that if you'd
to be featured in this section, please open a PR, adding a link to and short description of your
project, as shown below:

- [kroxylicious](https://github.com/kroxylicious/kroxylicious/tree/main/kroxylicious-kubernetes/kroxylicious-operator) Kafka proxy operator
- [ExposedApp operator](https://github.com/halkyonio/exposedapp-rhdblog): a sample operator
written to illustrate JOSDK concepts and its Quarkus extension in the ["Write Kubernetes
Operators in Java with the Java Operator SDK" blog series](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk#).
- [Keycloak operator](https://github.com/keycloak/keycloak/tree/main/operator): the official
Keycloak operator, built with Quarkus and JOSDK.
- [Apache Flink Kubernetes operator](https://github.com/apache/flink-kubernetes-operator) is the market leader among Flink operators.
- [Apache Flink Kubernetes Operator](https://github.com/apache/flink-kubernetes-operator) is the market leader among Flink operators.
- [Apache Spark Kubernetes Operator](https://github.com/apache/spark-kubernetes-operator) emerging operator for Spark.
- [kroxylicious](https://github.com/kroxylicious/kroxylicious/tree/main/kroxylicious-kubernetes/kroxylicious-operator) Kafka proxy operator
- [Debezium Operator](https://github.com/debezium/debezium-operator): Debezium Operator adds Change-Data-Capture capabilities to your Kubernetes or OpenShift cluster by providing an easy way to run and manage [Debezium Server](https://debezium.io/documentation/reference/stable/operations/debezium-server.html) instances.
- [Keycloak operator](https://github.com/keycloak/keycloak/tree/main/operator): the official Keycloak operator, built with Quarkus and JOSDK.
- [Apache Hive Kubernetes Operator](https://github.com/apache/hive/tree/master/packaging/src/kubernetes/src/java/org/apache/hive/kubernetes/operator)
- [OpenMetadata Operator](https://github.com/open-metadata/OpenMetadata/tree/main/openmetadata-k8s-operator) for AI context, data cataloging, and metadata management service
- [Strimzi Access operator](https://github.com/strimzi/kafka-access-operator). While the core Strimzi operator development predates
JOSDK, but new components like the Access operator is using the framework.
- [Wanaku](https://github.com/wanaku-ai/wanaku/tree/main/apps/wanaku-operator) MCP Router Operator
- [Apicurio Registry Operator](https://github.com/Apicurio/apicurio-registry/tree/main/operator)
- [Kaap](https://github.com/datastax/kaap) autoscaling Operator for Apache Pulsar
- [EureKubeOperator](https://medium.com/@heesuk.dev/implementing-kubernetes-operator-for-eureka-service-discovery-integration-by-java-operator-sdk-d21d8087c38e): integrates service discovery of Eureka and Kubernetes using the framework - developed by 11street. It is not released as an open source yet but is very interesting to read about this problem and how it is solved by an operator written with JOSDK.
- [Locust k8s operator](https://github.com/AbdelrhmanHamouda/locust-k8s-operator): Cloud native solution to run performance tests on any Kubernetes cluster.
- [Strimzi Schema Registry Operator](https://github.com/shangyuantech/strimzi-registry-ksql-operator): A Schema Registry Operator based on JOSDK for running the Confluent Schema Registry with a Strimzi-based Kafka cluster.
- [Airflow Dag Operator](https://github.com/cdmikechen/airflow-dag-operator): Use JOSDK(Quarkus Extension) to replace Airflow Git Sync strategy. The main idea of the project is to start a synchronization container on each airflow pod to synchronize the DAG/files into the DAG folder.
- [ExposedApp operator](https://github.com/halkyonio/exposedapp-rhdblog): a sample operator
written to illustrate JOSDK concepts and its Quarkus extension in the ["Write Kubernetes
Operators in Java with the Java Operator SDK" blog series](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk#).
- [Glasskube Operator](https://github.com/glasskube/operator): simplifies the deployment, maintenance and upgrade of popular open source business tools. It is written in Kotlin and uses the JOSDK and fabric8 Kubernetes client with Kotlin-based DSL.
- [Debezium Operator](https://github.com/debezium/debezium-operator): Debezium Operator adds Change-Data-Capture capabilities to your Kubernetes or Openshift cluster by providing an easy way to run and manage [Debezium Server](https://debezium.io/documentation/reference/stable/operations/debezium-server.html) instances.
2 changes: 1 addition & 1 deletion bootstrapper-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.5.1-SNAPSHOT</version>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>bootstrapper</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion caffeine-bounded-cache-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.5.1-SNAPSHOT</version>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>caffeine-bounded-cache-support</artifactId>
Expand Down
68 changes: 67 additions & 1 deletion docs/content/en/docs/documentation/eventing.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,70 @@ for [primary resources](https://github.com/operator-framework/java-operator-sdk/

See
also [CaffeineBoundedItemStores](https://github.com/operator-framework/java-operator-sdk/blob/main/caffeine-bounded-cache-support/src/main/java/io/javaoperatorsdk/operator/processing/event/source/cache/CaffeineBoundedItemStores.java)
for more details.
for more details.

### Sharing Informers Between Controllers (Informer Pool)

{{% alert title="Experimental" color="warning" %}}
Informer pooling is marked `@Experimental`: the feature itself is production ready, but its
configuration API may still change in a non-backwards-compatible way.
{{% /alert %}}

By default JOSDK maintains an *informer pool* so that informers are **shared** across controllers
and event sources. When several `InformerEventSource`s (whether belonging to different controllers,
or dynamically registered at runtime) watch the same resource type with an equivalent configuration,
they are all backed by a single underlying `SharedIndexInformer` instead of one informer each. This
reduces memory usage and the number of watch connections opened against the API server — which
matters in operators where many controllers watch the same secondary resource type (for example
`ConfigMap` or `Secret`).

Two event sources share an informer when their effective informer configuration matches on all of:

- the `KubernetesClient` they watch through, compared by instance: normally every event source
resolves the operator's own client, but an event source watching another cluster brings its own
(see [multi-cluster](#informereventsource-multi-cluster-support)). Two separate client instances
never share an informer, not even when they connect to the same API server — they may differ in
credentials, impersonation or TLS material, and the informer keeps using the client it was created
from,
- the resource type (or the group/version/kind for generic resources),
- the watched namespace,
- the label, field and shard selectors,
- the configured [item store](#bounded-caches-for-informers).

The `informerListLimit` is intentionally *not* part of this identity: if two otherwise-equivalent
event sources request a different list limit, the existing informer is reused (a warning is logged
and the first-configured limit is kept). Indexers are also not part of the identity: they are
registered on the shared informer under a name qualified with the controller and event source that
added them, so index names are private to an event source and cannot collide with those of another
one. You keep looking indexes up by the name you registered, and the indexers of an event source are
removed from the shared informer when it stops using it.

The pool is reference-counted: the shared informer is created on first use and only stopped once the
last event source using it is de-registered (or its controller stops). Dynamically registering an
event source for a resource that is already backed by a running informer reuses that informer, and
the initial state already in its cache is replayed to the newly added handler.

#### Selecting the pooling strategy

The strategy is provided by the
[`InformerPool`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/InformerPool.java)
configured on the `ConfigurationService`. Two implementations are available:

- [`DefaultInformerPool`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/DefaultInformerPool.java)
(the default): shares informers as described above.
- [`NonSharingInformerPool`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/NonSharingInformerPool.java):
never shares informers, creating a dedicated informer for every event source. Use this to opt out
of pooling and restore the pre-pooling behavior.

You can override the strategy through the `ConfigurationService`:

```java
Operator operator = new Operator(overrider ->
overrider.withInformerPool(new NonSharingInformerPool()));
```

A custom strategy has to extend
[`AbstractInformerPool`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/pool/AbstractInformerPool.java),
which is what `withInformerPool` accepts: it already creates the informers from an
`InformerClassifier` and starts them, leaving the subclass to decide only whether and how they are
shared. `InformerPool` itself is just the narrower contract that the event sources consume.
2 changes: 1 addition & 1 deletion micrometer-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.5.1-SNAPSHOT</version>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>micrometer-support</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions migration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.5.1-SNAPSHOT</version>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>migration</artifactId>
<name>Operator SDK - Migration Recipes</name>
<description>OpenRewrite migration recipes for Java Operator SDK</description>

<properties>
<openrewrite.version>8.87.5</openrewrite.version>
<openrewrite.version>8.88.0</openrewrite.version>
</properties>

<dependencies>
Expand Down
4 changes: 2 additions & 2 deletions operator-framework-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-bom</artifactId>
<version>5.5.1-SNAPSHOT</version>
<version>999-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Operator SDK - Bill of Materials</name>
<description>Java SDK for implementing Kubernetes operators</description>
Expand Down Expand Up @@ -54,7 +54,7 @@
<maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
<maven-source-plugin.version>3.4.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
<spotless.version>3.8.0</spotless.version>
<spotless.version>3.9.0</spotless.version>
<central-publishing-maven-plugin.version>0.11.0</central-publishing-maven-plugin.version>
</properties>

Expand Down
45 changes: 44 additions & 1 deletion operator-framework-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.5.1-SNAPSHOT</version>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand All @@ -30,6 +30,11 @@
<name>Operator SDK - Framework - Core</name>
<description>Core framework for implementing Kubernetes operators</description>

<properties>
<!-- Used only for test sources, to verify that the framework works properly with Kotlin. -->
<kotlin.version>2.4.10</kotlin.version>
</properties>

<dependencies>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
Expand Down Expand Up @@ -101,6 +106,13 @@
<artifactId>kube-api-test-client-inject</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<!-- Test-only, used to verify JOSDK works properly when used from Kotlin. -->
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -147,6 +159,37 @@
</execution>
</executions>
</plugin>
<plugin>
<!--
Compiles the Kotlin test sources under src/test/kotlin, used only to verify that JOSDK
works properly when used from Kotlin (see https://github.com/operator-framework/java-operator-sdk/issues/2967).
Bound to process-test-sources so the compiled Kotlin classes are on the classpath before
the regular Java test sources are compiled.
-->
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
<executions>
<execution>
<id>kotlin-test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<phase>process-test-sources</phase>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<!-- Not compiled by Kotlin, only needed so the Kotlin sources can reference the
existing Java test classes (e.g. TestCustomResource). -->
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.processing.event.source.informer.pool.AbstractInformerPool;
import io.javaoperatorsdk.operator.processing.event.source.informer.pool.DefaultInformerPool;
import io.javaoperatorsdk.operator.processing.event.source.informer.pool.InformerPool;

/**
* An abstract implementation of {@link ConfigurationService} meant to ease custom implementations
Expand All @@ -35,6 +38,7 @@ public class AbstractConfigurationService implements ConfigurationService {
private KubernetesClient client;
private Cloner cloner;
private ExecutorServiceManager executorServiceManager;
private AbstractInformerPool informerPool;

protected AbstractConfigurationService(Version version) {
this(version, null);
Expand Down Expand Up @@ -190,4 +194,16 @@ public ExecutorServiceManager getExecutorServiceManager() {
}
return executorServiceManager;
}

@Override
public synchronized InformerPool informerPool() {
// cached so that all controllers backed by this ConfigurationService share the same pool and
// can therefore share the underlying informers; synchronized so concurrent first-access from
// multiple controllers cannot create (and share out) more than one pool instance
if (informerPool == null) {
informerPool = new DefaultInformerPool();
informerPool.setConfigurationService(this);
}
return informerPool;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Experimental;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory;
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
import io.javaoperatorsdk.operator.processing.event.source.informer.pool.DefaultInformerPool;
import io.javaoperatorsdk.operator.processing.event.source.informer.pool.InformerPool;

/** An interface from which to retrieve configuration information. */
public interface ConfigurationService {
Expand Down Expand Up @@ -476,4 +479,27 @@ default boolean useSSAToPatchPrimaryResource() {
default boolean cloneSecondaryResourcesWhenGettingFromCache() {
return false;
}

/**
* The informer pool used to create and (when using the default, sharing pool) share the informers
* backing the event sources of all controllers managed by this {@code ConfigurationService}.
*
* <p><strong>Implementations must return the same instance on every call.</strong> The pool is
* effectively a per-{@code ConfigurationService} singleton: controllers share informers only if
* they resolve the same pool, and reference counting / informer shutdown are only correct if
* {@code getInformer} and {@code releaseInformer} operate on that same instance. This is
* intentionally not a {@code default} method, since a {@code default} could not cache the result
* and would hand out a fresh (unshared) pool on each call; {@link AbstractConfigurationService}
* provides a cached implementation backed by the default sharing pool.
*
* @return the informer pool for this configuration service
*/
@Experimental(
"Only the configuration API around informer pooling could still change in a"
+ " non-backwards-compatible way, the pooling itself is prod ready.")
default InformerPool informerPool() {
var pool = new DefaultInformerPool();
pool.setConfigurationService(this);
return pool;
}
Comment thread
csviri marked this conversation as resolved.
}
Loading
Loading