feat(stacks): karpenter node isolation per organization/stack#494
Draft
sylr wants to merge 1 commit into
Draft
Conversation
Provision dedicated Karpenter node pools per organization (shared
"<org>" pool) or per stack ("<org>-<stack>" pool), driven by
karpenter.* settings and the formance.com/organization + /customer
Stack labels. EC2NodeClass/NodePool are cloned from named cluster-scoped
reference objects (unstructured + injected tags/labels/taints) so EC2
instances carry customer/organization/stack tags for cost attribution.
Stack workloads are pinned onto the dedicated nodes via a nodeSelector +
tolerations mutator in the applications deployment path.
Integrated into the existing stacks reconciler (no new CRD, no second
controller). Cleanup is label-driven (app.kubernetes.io/managed-by=
formance-operator) because the module cleanup path namespace-filters and
cannot see cluster-scoped objects; org pools use additive non-controller
owner refs so they survive until the last owning stack is gone.
Constraint: cluster-scoped dependent may only reference a cluster-scoped
owner (the Stack CR is cluster-scoped, so owner refs are valid)
Constraint: Karpenter CRDs are optional; feature no-ops when absent and a
CustomResourceDefinition watch re-enables it without an operator restart
Rejected: WithOwn-based cleanup | removeAllModulesOwnedObjects
namespace-filters and never sees cluster-scoped NodePool/EC2NodeClass
Rejected: typed Karpenter Go deps | avoids v1beta1->v1 churn; feature
only materializes a configured spec, so unstructured suffices
Directive: taints/tolerations/nodeSelector key on pool identity
(organization[/stack]) only, never customer -- a shared org pool may see
differing customer values which would break scheduling; customer stays a
tag/node-label for cost attribution only
Confidence: medium
Scope-risk: moderate
Not-tested: envtest integration against real Karpenter CRDs; migration
Jobs are not pinned (taints only repel, so they run on shared nodes)
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Formance runs many customer stacks in a shared Kubernetes cluster. Today there's no way to attribute compute cost to a specific customer/organization/stack — all pods land on shared nodes. This adds the operator's ability to provision dedicated Karpenter node pools per organization (shared
<org>pool) or per stack (<org>-<stack>pool), so EC2 instances carryformance.com/{customer,organization,stack}tags for cost allocation and a stack's workloads run only on its dedicated nodes.What it does
Driven by
karpenter.*Settings and theformance.com/organization+formance.com/customerStack labels:karpenter.isolation: organization | stack.karpenter.ec2-node-class.reference) and injectingspec.tags.karpenter.node-pool.reference), pointing it at the cloned EC2NodeClass, and injecting node labels + taints.Settings
karpenter.enabled*)karpenter.isolationorganization(default) orstackkarpenter.ec2-node-class.referencekarpenter.node-pool.referencekarpenter.api.{ec2-node-class,node-pool}.group-versionkarpenter.k8s.aws/v1,karpenter.sh/v1)Design notes
stacksreconciler (a secondFor(&Stack{})would collide on the auto-derived controller name).v1beta1→v1churn; the feature only materializes a configured spec.app.kubernetes.io/managed-by=formance-operator) —removeAllModulesOwnedObjectsnamespace-filters and can't see cluster-scoped objects, soWithOwncleanup would silently orphan pools.CustomResourceDefinitionwatch re-enables the feature without an operator restart.Review
Two independent Codex review passes were incorporated (plan-level and implementation-level): label-driven cleanup for cluster-scoped objects,
watchRBAC verb for the CRD watch, mutator idempotency + availability gating, optimistic-concurrency cleanup, and identity-only scheduling keys.Testing
go build ./...,go vet,gofmtcleanresolve_test.go(modes/defaults/pending),karpenter_test.go(clone injection, owner refs, GC on mode change / disable of cluster-scoped objects, org pool surviving a second owner), mutator idempotency test inapplicationsOut of scope (follow-ups)
🤖 Generated with Claude Code