operator-framework / java-operator-sdk

Java SDK for building Kubernetes Operators
https://javaoperatorsdk.io/
Apache License 2.0
746 stars 186 forks source link

Consider composable non-CRD blocks for workflows / dependencies #2256

Open emonical opened 4 months ago

emonical commented 4 months ago

Currently, all dependencies using the workflow model (seem to) require either declaring them in their own CRD, or expanding on the blocks used inside a single Reconciler.

It would be nice to be able to define a block of dependencies that is reusable in scope. In my particular case, this is so that we can create separate building blocks for commonly reused entities (see datastores, etc) that do not necessarily meet the case for having a standalone CRD.

This is for two reasons:

  1. The entities themselves maybe managed by other operators and we are considering dependency / ready states for the particular operator.
  2. The the individual deployments themselves do not reflect something that is of a desired scope for its own CRD. In several cases, this may be because we are developing an application and the particular implementation under the hood may change, or it is something that doesn't necessarily warrant its own set of description.
  3. The development of commons libraries for shared patterns between operators or, in the case of multi-controller operators, between different pieces.

Additionally, having a mechanism by which dependencies can be mapped at the level of the individual resource, especially in the case of multiple config maps, secrets, volumeclaims, etc, can then be declared at the point of the dependent resource.

class RedisConfigMap<T: HasMetadata>(val app: App) : AbstractNamedCRUDDependentResource<ConfigMap, T>(ConfigMap::class.java)
{
    companion object {
        fun <T: HasMetadata> withDiscriminator(app: App) : AbstractNamedCRUDDependentResource<ConfigMap, T> {
            return RedisConfigMap<T>(app).withDiscriminator()
        }
    }

    override fun desired(primary: T?, context: Context<T>?): ConfigMap {
        return ConfigMapBuilder()
            .withNewMetadata()
            .withNamespace(primary!!.metadata.name)
            .withName(name())
            .endMetadata()
            .addToData("redis-config", "")
            .build()
    }

    override fun name(): String {
        return "${app.appName}-redis-config"
    }

Is an example of a piece of code I am working on (it's Kotlin, so, bear with me -- and thank you glasskube!)

It would be nice if, instead of piling the configuration into annotations on the Resolver, individual resources could also support declaring their dependencies.

metacosm commented 4 months ago

Would you mind elaborating on how you think what you're proposing should look like? On the topic of resources declaring dependencies, you can already do something similar by using the workflow dependsOn feature (a given dependent will only be reconciled when all of the dependents it depends on are reconciled).

csviri commented 4 months ago

See also discussions around here: https://discord.com/channels/723455000604573736/1032917332235911169/1212475397413928970

metacosm commented 4 months ago

See also discussions around here: https://discord.com/channels/723455000604573736/1032917332235911169/1212475397413928970

Please post it here. Discord is opaque and we should be having these discussions here, not in some other service that people might not have access to.

csviri commented 4 months ago

So, if I understand properly this would be kinda Group of dependencies, that can be used as a library in another project. Or more precisely a sub-workflow?

It would be nice if, instead of piling the configuration into annotations on the Resolver, individual resources could also support declaring their dependencies.

Not following this part, could you elaborate pls? Do you mean a dependent resource would declare what is needed for to be declared? (That is basically what "depends_on" doing in workflow in resource level)

csviri commented 4 months ago

See also discussions around here: https://discord.com/channels/723455000604573736/1032917332235911169/1212475397413928970

Please post it here. Discord is opaque and we should be having these discussions here, not in some other service that people might not have access to.

Everybody has access to the Discord, it's open. The issue should do the summary properly after initial discussion IMO.

metacosm commented 4 months ago

See also discussions around here: https://discord.com/channels/723455000604573736/1032917332235911169/1212475397413928970

Please post it here. Discord is opaque and we should be having these discussions here, not in some other service that people might not have access to.

Everybody has access to the Discord it's open. The issue should do the summary properly after initial discussion IMO.

Discord is not open. People shouldn't need to create another account to follow a discussion. I have an account and following the link doesn't help me at all (I'm not using Discord's official app) so even in this case, this is making things difficult. Discord should only be there for quick help, not discussions of features that would benefit from wider community input. We need to have a trace linked to the project itself (i.e. here) instead of in some external proprietary tool that might go away (or change their policies with respect to content), though, granted, the same could be said of GitHub itself, as well. 😄 Let's not multiply the channels where information exists and force people to hunt for that information.

csviri commented 4 months ago

@metacosm could we pls discuss this under a different issue or GitHub discussion or on Discord :) this is not related to this issue.

(but in general I agree that the issue should be independently clear from Discord discussions, will not post links in future)

emonical commented 4 months ago

@metacosm @csviri: Oh, I'm using the depends on feature in my both my ControllerConfiguration implementation and a custom workflow implementation.

fun <T: HasMetadata> workflowContext() : Workflow<T> {
    val redisConfigMap = RedisConfigMap.withDiscriminator<T>(app = App.EXAMPLE)
    val redisDeployment = RedisDeployment.withDiscriminator<T>(app = App.EXAMPLE)
    val redisService = RedisService.withDiscriminator<T>(app = App.EXAMPLE)

    return WorkflowBuilder<T>()
        .addDependentResource(redisConfigMap)
        .addDependentResource(redisDeployment).dependsOn(redisConfigMap)
        .addDependentResource(redisService)
        .build()
}

This is an example of a redis declaration we're using. It doesn't meet our usecase for a custom resource. There are also cases where I may wish to move either to or from another operators CRD for implementation.

It would be nice if there was a mechanism by which we could wrap that whole block as a dependency without necessarily managing that block as it's own CRD, managed by it's own Controller/Reconciler, since, in it's own right it doesn't really need it's own management semantics.

It's possible I am missing something in implementation, but, it would be nice to be able do something more like this.

@Dependencies(
dependencies = [
...
]
Redis extends DependentWorkflow

@ControllerConfiguration(
dependencies = [
@Dependent(type = Redis::class)
]
class TrivialReconciler() {
override fun reconcile(resource: TrivialResource, context: Context<TrivialResource>?): UpdateControl<TrivialResource> {
... behavior
}

This is some utterly rough pseudocode, but, then, subworkflow could be referenced as a dependency in it's own right, without encapsulating that behavior in the controller configuration for it's parent.

csviri commented 3 months ago

@emonical I think I understand what you mean, and see the point that the reusability of a group of dependents might bring some value. This is something that can be done with standalone dependencies by just passing the Workflow builder into a shared service, that will add the dependents to the workflow. Maybe if there is more demand for a feature like this, we can think about this to add it also to the managed dependent resources. But I see this feature would bring much complexity, and it is very limited in terms of target audience - especially since as we mentioned this is usually solved by having a separate CRD for a certain functionality.

emonical commented 3 months ago

That's the path I'm planning on going down. Perhaps consider making the add/set methods on DependentResourceNode public?

csviri commented 3 months ago

There is a plan to improve the builder, this can be also considered. cc @metacosm