* feat(service): add ipFamilyPolicy and ipFamilies to control-plane Service
Expose the native Kubernetes Service IP-family fields on
spec.controlPlane.service so a Tenant Control Plane can be exposed as
dual-stack, IPv4-only, or IPv6-only. Konnectivity shares the same Service
and is covered implicitly. When unset, behaviour is unchanged.
Cross-field CEL enforces SingleStack<=1 family and family uniqueness. No
immutability, preserving the Kubernetes-supported single-stack to
dual-stack upgrade.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* feat(service): pass IP-family config through to the generated Service
Copy spec.controlPlane.service.ipFamilyPolicy and ipFamilies onto the
managed Service only when set, leaving API-server-defaulted values in
place otherwise.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* test(service): cover IP-family CEL and enum validation on TenantControlPlane
Envtest admission specs: IPv6-only and dual-stack accepted; SingleStack
with two families, duplicate families, and an invalid family value are
rejected with their respective CRD validation messages.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* docs(service): clarify dual-stack ipFamilyPolicy expects two ipFamilies
Document that PreferDualStack/RequireDualStack describe a dual-stack
Service and expect two ipFamilies entries, and that an unsatisfiable
RequireDualStack surfaces as a reconcile error, so both over- and
under-specification failure directions are predictable.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* feat(certs): include all control-plane Service IPs in the API server SANs
Derive the API server certificate SANs from every IP the control-plane
Service answers on -- all of its ClusterIPs (both families of a dual-stack
Service) and all LoadBalancer ingress IPs -- so a dual-stack Service is
reachable over both families. The endpoint advertised to clients stays the
primary address; the extra entries only permit TLS over the secondary family.
IPs are deduplicated canonically against the management/advertised address
and existing SANs, so a single-stack Service adds nothing and the certificate
does not churn.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* fix(dns): align IPv6 DNS service IP with kubeadm's 10th-address convention
The DNS service IP defaulting added +16 to the last byte for IPv6 (yielding
fd00::10) while kubeadm and CoreDNS use the 10th address of the service
subnet (fd00::a). The kubelet's cluster DNS then disagreed with the actual
CoreDNS Service ClusterIP, breaking DNS for IPv6-only tenants. Use +10 for
both families, matching the IPv4 path and kubeadm.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* test(service): satisfy goconst and usetesting linters
Extract the repeated ClusterIP literals (IPv4 and IPv6) into constants and
use t.Context() instead of context.Background() in the ControlPlaneServiceIPs
tests.
Refs: clastix/kamaji#1245
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
---------
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
Co-authored-by: Claude <noreply@anthropic.com>
* feat(api): add allocateLoadBalancerNodePorts to ServiceSpec
Optional *bool, nil keeps the Kubernetes default (true) so existing
TenantControlPlanes are unchanged.
Refs #1204
Co-authored-by: Claude <noreply@anthropic.com>
* feat(service): honor allocateLoadBalancerNodePorts in LoadBalancer service
Propagate ServiceSpec.AllocateLoadBalancerNodePorts to the generated
Service and clear any already-assigned NodePort when allocation is
disabled, since Kubernetes does not deallocate it automatically.
Closes#1204
Co-authored-by: Claude <noreply@anthropic.com>
* feat(service): treat unset allocateLoadBalancerNodePorts as the default
An unset (nil) AllocateLoadBalancerNodePorts field in the TCP spec now
means "use the Kubernetes LoadBalancer default" (true). The builder
writes that default explicitly (ptr.To(true)) so that:
- Clearing the field reverts the Service to the default declaratively,
rather than leaving it at whatever value was previously configured.
- Writing the same `true` the API server already defaults to produces no
diff on DeepEqual, preventing perpetual reconcile churn.
The previous "nil means do not touch" semantics made it impossible to
revert a previously-false allocation without an explicit `true` in the
spec, and had no effect on a freshly-created Service since the API
server would just re-default the field to true anyway.
Updated tests to cover all five cases: false (clears NodePort), true
(preserves NodePort), unset defaults to true (preserves NodePort),
unset does not churn against a server-defaulted true, and clearing a
previously-false field reverts to the default.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
* feat(service): require LoadBalancer for allocateLoadBalancerNodePorts
Add a CEL validation rejecting allocateLoadBalancerNodePorts unless serviceType is LoadBalancer, mirroring the loadBalancerClass/loadBalancerSourceRanges guards. Update the field doc, regenerate CRDs and API docs, and cover the rule with an envtest case.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(service): use ptr.Deref for allocateLoadBalancerNodePorts default
Addresses review feedback: replace the manual nil-check with ptr.Deref, the established pattern in the codebase, and avoid aliasing the TCP spec pointer into the Service spec.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
---------
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
Co-authored-by: Claude <noreply@anthropic.com>
advertiseAddress is passed verbatim to the API server's --advertise-address
flag, which only accepts an IP address. A DNS name was silently admitted and
only surfaced as a tenant API server CrashLoopBackOff ("failed to parse IP"),
three layers removed from the misconfiguration.
Add a CEL validation (consistent with the existing isCIDR rules on
serviceCidr/podCidr in the same struct) so the value is rejected at admission
time, and clarify the field documentation: it must be an IP, it feeds
--advertise-address, and the supported way to use a hostname is a DNS record
pointing at a stable VIP plus networkProfile.certSANs.
Closes#1197
Signed-off-by: Jakob Hahn <jakob.hahn@hetzner.com>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(deployment): guard applyProbeOverrides against nil probe
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(deployment): render readiness probe for scheduler and controller-manager (#1192)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(api): readiness probe defaults apply to all control plane components
The global probes.readiness now cascades to scheduler and controller-manager
as well as kube-apiserver, matching liveness/startup. Update the godoc and
regenerate the CRD descriptions accordingly.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add securityContexts to control plane components
* feat: add configurable security context to Konnectivity server component
* test: add e2e test for tcp security contexts
* test(refactor): use getControlPlanePods function where possible
---------
Co-authored-by: Sebastian Diez <sebastian.diez@breuninger.de>
* Add support for multiple service and pod CIDRs and dualstack controlplanes
* Added backwards compatability as to not break existing control planes which do not set the new cidr fields
* Added helper functions in utilities.go
* Cleanup unique strings function. Cleanup tests. Generated manifests
* issued make apidocs
* add advertiseAddress to NetworkProfileSpec
* use advertiseAddress for --advertise-address
* regenerate CRDs with advertiseAddress
* regenerate API docs
---------
Co-authored-by: bsctl <bsctl@clastix.io>
* feat(deployment): make startup probe failure threshold configurable
Add StartupProbeFailureThreshold field to TenantControlPlane CRD
DeploymentSpec, allowing users to configure how many consecutive
startup probe failures are tolerated before a container is considered
failed. The value is applied to all control plane components
(kube-apiserver, controller-manager, and scheduler).
Defaults to 3 (preserving current behavior). With PeriodSeconds=10,
the total startup timeout equals FailureThreshold * 10 seconds.
Setting this to 30 gives 5 minutes, which is useful for
resource-constrained environments.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
* chore: regenerate CRD manifests for startupProbeFailureThreshold
Run `make manifests` to update Helm CRD files with the new
startupProbeFailureThreshold field in DeploymentSpec.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
* feat(deployment): expand configurable probes to all probe types
Replace StartupProbeFailureThreshold with a full Probes config
supporting liveness, readiness, and startup probes with configurable
TimeoutSeconds, PeriodSeconds, and FailureThreshold parameters.
Use ptr.Deref for safe pointer dereferencing.
Ref: #471
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
* chore: regenerate CRD manifests and API documentation
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
* feat(deployment): add per-component probe overrides and expand ProbeSpec
Add cascading probe configuration: global defaults → per-component
overrides (apiServer, controllerManager, scheduler). Expand ProbeSpec
with InitialDelaySeconds and SuccessThreshold fields.
Ref: #471
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
* chore: regenerate CRD manifests and API documentation
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---------
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add ObservedGeneration to all status types
Add ObservedGeneration field to DataStoreStatus, KubeconfigGeneratorStatus,
and TenantControlPlaneStatus to track which generation the controller has
processed. This enables clients and tools like kstatus to determine if the
controller has reconciled the latest spec changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: follow Cluster API pattern for ObservedGeneration
Move ObservedGeneration setting for TenantControlPlane from intermediate
status updates to the final successful reconciliation completion. This
follows Cluster API conventions where ObservedGeneration indicates the
controller has fully processed the given generation.
Previously, ObservedGeneration was set on every status update during
resource processing, which could mislead clients into thinking the spec
was fully reconciled when the controller was still mid-reconciliation
or had hit a transient error.
Now:
- DataStore: Sets ObservedGeneration before single status update (simple controller)
- KubeconfigGenerator: Sets ObservedGeneration before single status update (simple controller)
- TenantControlPlane: Sets ObservedGeneration only after ALL resources processed successfully
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: verify ObservedGeneration equals Generation after reconciliation
Add assertion to e2e test to verify that status.observedGeneration
equals metadata.generation after a TenantControlPlane is successfully
reconciled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: regenerate CRDs with ObservedGeneration field
Run make crds to regenerate CRDs with the new ObservedGeneration
field in status types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Run make manifests
* Run make apidoc
* Remove rbac role
* Remove webhook manifest
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This change extends Gateway API support to Konnectivity addons.
When `spec.controlPlane.gateway` is configured and Konnectivity addon is
enabled, Kamaji automatically creates two TLSRoutes:
1. A Control plane TLSRoute (port 6443, sectionName "kube-apiserver")
2. A Konnectivity TLSRoute (port 8132, sectionName "konnectivity-server")
Both routes use the hostname specified in `gateway.hostname` and reference
the same Gateway resource via `parentRefs`, with `port` and `sectionName`
set automatically by Kamaji.
This patch also adds CEL validation to prevent users from specifying
`port` or `sectionName` in Gateway `parentRefs`, as these fields are now
managed automatically by Kamaji.
Signed-off-by: Parth Yadav <parth@coredge.io>
* feat: add support for multiple Datastores
* docs: add guide for datastore overrides
* feat(datastore): add e2e test for dataStoreOverrides
* ci: reclaim disk space from runner to fix flaky tests
* Feat: Gateway Routes Specs, plus resource and status init progress
* Generated content, RBAC and start of e2e
* latest code POC Working but e2e fails
* Use Gateway API v1.2.0
* Remove draft comment
* Use TCPRoute
* Revert the charts folder to reduce noise
* Use the correct controller-gen version
* Rename fields and fix tcp/tls typos
* Rename TLSRouteSpec to GatewayRouteSpec
* Remove last instance of tcproute
* Renaming more fields to match the gateway api naming
* Remove ownership of the gateway
* Revert Ko to 0.14.1 and makefile comments
* service discovery, webhooks, and deadcode removal.
* add conditional check for gateway api resources and mark is as owned!
* removing duplicated code and note for maybe a refactor later
* E2E now works!
* e2e suite modifications to support Gateway API v1alpha2 TLSRoute
* Suggestions commit, naming and other related.
* First pass at the status update
* Rename route to gateway
* Only allow one hostname in gateway
* Update status types
* WIP: testing conditions
* Update status API
* Add tests
* Detect endpoint
* Update manifests
* Remove old code and use proper condition check
* Fix compilation error
* Watch the Gateway resources
* Rename fields
* Add missing port
* Add ingress endpoint to the kubeadm
* Error if access points are empty
* Check the spec and status to delay the creation of the kubeadm
* Use the spec for the hostname
* Update api/v1alpha1/tenantcontrolplane_types.go
Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
* PR fixes, CEL k8s validations, proper status updates checks
* more context and separation of functions
* resolve all pr comments, with indexer
* merge master - go {sum,mod} updates dependabot
* Feat: Gateway Routes Specs, plus resource and status init progress
* Use Gateway API v1.2.0
* merge master - go {sum,mod} updates dependabot
* sum go mod tidy
* leftover comments
* clean go.sum
* fix: missing generated crds spec
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* docs: gateway api support
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* golint comments
* linting and test fix.
* Gateway API resource watching was made conditional to prevent crashes when CRDs are absent, and TLSRoute creation now returns an error when the service isn't ready instead of creating invalid resources with empty rules.
* unit test was incorrect after all the fixes we did, gracefull errors are not expected due to conditional adds
* fix(conditional-indexer): Gateway Indexer should also be conditional
* fix(conditional-indexer): Gateway Indexer should also be conditional
---------
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Hadrien Kohl <hadrien.kohl@gmail.com>
Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
This commit extends CRD API: Added hostNetwork field to KonnectivityAgentSpec struct.
It's false by default so it's backwards compatible.
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
This patch switches default kubelet-preferred-address-types to
"InternalIP,ExternalIP,Hostname" to avoid failures in kube-apiserver
connection to kubelet when node hostnames are not resolvable by the
external DNS server. This improves out-of-the-box reliability across
most environments by choosing node `InternalIP` as the preferred mode
to reach Kubelet.
Signed-off-by: Parth Yadav <parthyadav3105@gmail.com>
* feat(api): introducing sleeping status
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* chore(helm)!: introducing sleeping status
Marking this commit as breaking since a CustomResourceDefinition update
is required for users dealing with scale to zero since the introduction
of the new enum for the status field.
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* docs: introducing sleeping status
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
---------
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* feat: automatically set dns service address
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* feat(helm): automatically set dns service address
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* docs: automatically set dns service address
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
---------
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
* feat(crd): add LoadBalancerSourceRanges field and integrate with service resource
* test(crd): add tests for CEL validation logic
* feat(webhook): implement LoadBalancerSourceRanges validation logic for CIDRs
* test(webhook): add tests for webhook validation logic
* test: modify Makefile for envtest setup
* docs: add LoadBalancerSourceRanges field to API reference