diff --git a/api/v1alpha1/tenantcontrolplane_types.go b/api/v1alpha1/tenantcontrolplane_types.go
index 7891a4b..96b8c15 100644
--- a/api/v1alpha1/tenantcontrolplane_types.go
+++ b/api/v1alpha1/tenantcontrolplane_types.go
@@ -240,7 +240,7 @@ type ProbeSet struct {
type ControlPlaneProbes struct {
// Liveness defines default parameters for liveness probes of all Control Plane components.
Liveness *ProbeSpec `json:"liveness,omitempty"`
- // Readiness defines default parameters for the readiness probe of kube-apiserver.
+ // Readiness defines default parameters for readiness probes of all Control Plane components.
Readiness *ProbeSpec `json:"readiness,omitempty"`
// Startup defines default parameters for startup probes of all Control Plane components.
Startup *ProbeSpec `json:"startup,omitempty"`
diff --git a/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml b/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
index f72bbef..f493647 100644
--- a/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
+++ b/charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
@@ -7361,7 +7361,7 @@ versions:
type: integer
type: object
readiness:
- description: Readiness defines default parameters for the readiness probe of kube-apiserver.
+ description: Readiness defines default parameters for readiness probes of all Control Plane components.
properties:
failureThreshold:
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
diff --git a/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml b/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
index f9c260d..b74dbb8 100644
--- a/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
+++ b/charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
@@ -7369,7 +7369,7 @@ spec:
type: integer
type: object
readiness:
- description: Readiness defines default parameters for the readiness probe of kube-apiserver.
+ description: Readiness defines default parameters for readiness probes of all Control Plane components.
properties:
failureThreshold:
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
diff --git a/docs/content/reference/api.md b/docs/content/reference/api.md
index 47d3bc3..b0a1be0 100644
--- a/docs/content/reference/api.md
+++ b/docs/content/reference/api.md
@@ -44720,7 +44720,7 @@ Override TimeoutSeconds, PeriodSeconds, and FailureThreshold for resource-constr
readiness |
object |
- Readiness defines default parameters for the readiness probe of kube-apiserver.
+ Readiness defines default parameters for readiness probes of all Control Plane components.
|
false |
@@ -45305,7 +45305,7 @@ Must be 1 for liveness and startup probes.
`TenantControlPlane.spec.controlPlane.deployment.probes.readiness`
-Readiness defines default parameters for the readiness probe of kube-apiserver.
+Readiness defines default parameters for readiness probes of all Control Plane components.
diff --git a/internal/builders/controlplane/deployment.go b/internal/builders/controlplane/deployment.go
index 458b571..5efd8d9 100644
--- a/internal/builders/controlplane/deployment.go
+++ b/internal/builders/controlplane/deployment.go
@@ -52,7 +52,7 @@ const (
)
func applyProbeOverrides(probe *corev1.Probe, spec *kamajiv1alpha1.ProbeSpec) {
- if spec == nil {
+ if probe == nil || spec == nil {
return
}
@@ -63,6 +63,44 @@ func applyProbeOverrides(probe *corev1.Probe, spec *kamajiv1alpha1.ProbeSpec) {
probe.FailureThreshold = pointer.Deref(spec.FailureThreshold, probe.FailureThreshold)
}
+// defaultProbe builds the standard HTTPS probe shared by all control plane
+// components; only the path and port differ between components and probe types.
+func defaultProbe(path string, port int32) *corev1.Probe {
+ return &corev1.Probe{
+ ProbeHandler: corev1.ProbeHandler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Path: path,
+ Port: intstr.FromInt32(port),
+ Scheme: corev1.URISchemeHTTPS,
+ },
+ },
+ InitialDelaySeconds: 0,
+ TimeoutSeconds: 1,
+ PeriodSeconds: 10,
+ SuccessThreshold: 1,
+ FailureThreshold: 3,
+ }
+}
+
+// applyProbeSetOverrides applies the global probe overrides first, then the
+// per-component overrides, to every probe of the container. Probe pointers that
+// are nil are skipped (applyProbeOverrides is nil-safe).
+func applyProbeSetOverrides(c *corev1.Container, probes *kamajiv1alpha1.ControlPlaneProbes, set *kamajiv1alpha1.ProbeSet) {
+ if probes == nil {
+ return
+ }
+
+ applyProbeOverrides(c.LivenessProbe, probes.Liveness)
+ applyProbeOverrides(c.ReadinessProbe, probes.Readiness)
+ applyProbeOverrides(c.StartupProbe, probes.Startup)
+
+ if set != nil {
+ applyProbeOverrides(c.LivenessProbe, set.Liveness)
+ applyProbeOverrides(c.ReadinessProbe, set.Readiness)
+ applyProbeOverrides(c.StartupProbe, set.Startup)
+ }
+}
+
type DataStoreOverrides struct {
Resource string
DataStore kamajiv1alpha1.DataStore
@@ -368,43 +406,12 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeSchedulerImage(tenantControlPlane.Spec.Kubernetes.Version)
podSpec.Containers[index].Command = []string{"kube-scheduler"}
podSpec.Containers[index].Args = utilities.ArgsFromMapToSlice(args)
- podSpec.Containers[index].LivenessProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/healthz",
- Port: intstr.FromInt32(10259),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
- podSpec.Containers[index].StartupProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/healthz",
- Port: intstr.FromInt32(10259),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
+ podSpec.Containers[index].LivenessProbe = defaultProbe("/healthz", 10259)
+ podSpec.Containers[index].ReadinessProbe = defaultProbe("/healthz", 10259)
+ podSpec.Containers[index].StartupProbe = defaultProbe("/healthz", 10259)
if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
- applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Liveness)
- applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Startup)
-
- if probes.Scheduler != nil {
- applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Scheduler.Liveness)
- applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Scheduler.Startup)
- }
+ applyProbeSetOverrides(&podSpec.Containers[index], probes, probes.Scheduler)
}
if containerSecurityContexts := tenantControlPlane.Spec.ControlPlane.Deployment.ContainerSecurityContexts; containerSecurityContexts != nil {
@@ -480,43 +487,12 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeControllerManagerImage(tenantControlPlane.Spec.Kubernetes.Version)
podSpec.Containers[index].Command = []string{"kube-controller-manager"}
podSpec.Containers[index].Args = utilities.ArgsFromMapToSlice(args)
- podSpec.Containers[index].LivenessProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/healthz",
- Port: intstr.FromInt32(10257),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
- podSpec.Containers[index].StartupProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/healthz",
- Port: intstr.FromInt32(10257),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
+ podSpec.Containers[index].LivenessProbe = defaultProbe("/healthz", 10257)
+ podSpec.Containers[index].ReadinessProbe = defaultProbe("/healthz", 10257)
+ podSpec.Containers[index].StartupProbe = defaultProbe("/healthz", 10257)
if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
- applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Liveness)
- applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Startup)
-
- if probes.ControllerManager != nil {
- applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.ControllerManager.Liveness)
- applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.ControllerManager.Startup)
- }
+ applyProbeSetOverrides(&podSpec.Containers[index], probes, probes.ControllerManager)
}
if containerSecurityContexts := tenantControlPlane.Spec.ControlPlane.Deployment.ContainerSecurityContexts; containerSecurityContexts != nil {
@@ -614,59 +590,12 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
podSpec.Containers[index].Args = args
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeAPIServerImage(tenantControlPlane.Spec.Kubernetes.Version)
podSpec.Containers[index].Command = []string{"kube-apiserver"}
- podSpec.Containers[index].LivenessProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/livez",
- Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
- podSpec.Containers[index].ReadinessProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/readyz",
- Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
- podSpec.Containers[index].StartupProbe = &corev1.Probe{
- ProbeHandler: corev1.ProbeHandler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "/livez",
- Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
- Scheme: corev1.URISchemeHTTPS,
- },
- },
- InitialDelaySeconds: 0,
- TimeoutSeconds: 1,
- PeriodSeconds: 10,
- SuccessThreshold: 1,
- FailureThreshold: 3,
- }
+ podSpec.Containers[index].LivenessProbe = defaultProbe("/livez", tenantControlPlane.Spec.NetworkProfile.Port)
+ podSpec.Containers[index].ReadinessProbe = defaultProbe("/readyz", tenantControlPlane.Spec.NetworkProfile.Port)
+ podSpec.Containers[index].StartupProbe = defaultProbe("/livez", tenantControlPlane.Spec.NetworkProfile.Port)
if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
- applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Liveness)
- applyProbeOverrides(podSpec.Containers[index].ReadinessProbe, probes.Readiness)
- applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Startup)
-
- if probes.APIServer != nil {
- applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.APIServer.Liveness)
- applyProbeOverrides(podSpec.Containers[index].ReadinessProbe, probes.APIServer.Readiness)
- applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.APIServer.Startup)
- }
+ applyProbeSetOverrides(&podSpec.Containers[index], probes, probes.APIServer)
}
if containerSecurityContexts := tenantControlPlane.Spec.ControlPlane.Deployment.ContainerSecurityContexts; containerSecurityContexts != nil {
diff --git a/internal/builders/controlplane/deployment_test.go b/internal/builders/controlplane/deployment_test.go
index 43c6cc5..2c19e68 100644
--- a/internal/builders/controlplane/deployment_test.go
+++ b/internal/builders/controlplane/deployment_test.go
@@ -121,6 +121,11 @@ var _ = Describe("Controlplane Deployment", func() {
Expect(probe.InitialDelaySeconds).To(Equal(int32(0)))
Expect(probe.SuccessThreshold).To(Equal(int32(1)))
})
+
+ It("should not panic when probe is nil", func() {
+ spec := &kamajiv1alpha1.ProbeSpec{PeriodSeconds: pointer.To(int32(20))}
+ Expect(func() { applyProbeOverrides(nil, spec) }).ToNot(Panic())
+ })
})
Describe("mergeAPIServerArgs", func() {
@@ -200,4 +205,83 @@ var _ = Describe("Controlplane Deployment", func() {
}))
})
})
+
+ Describe("control plane probes", func() {
+ // helper: find a container by name in a built PodSpec
+ containerByName := func(spec *corev1.PodSpec, name string) corev1.Container {
+ for _, c := range spec.Containers {
+ if c.Name == name {
+ return c
+ }
+ }
+ Fail("container not found: " + name)
+
+ return corev1.Container{}
+ }
+
+ It("renders a readiness probe for kube-scheduler on /healthz:10259", func() {
+ podSpec := &corev1.PodSpec{}
+ d.buildScheduler(podSpec, kamajiv1alpha1.TenantControlPlane{})
+
+ c := containerByName(podSpec, "kube-scheduler")
+ Expect(c.ReadinessProbe).ToNot(BeNil())
+ Expect(c.ReadinessProbe.HTTPGet.Path).To(Equal("/healthz"))
+ Expect(c.ReadinessProbe.HTTPGet.Port.IntValue()).To(Equal(10259))
+ Expect(c.ReadinessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS))
+ Expect(c.ReadinessProbe.PeriodSeconds).To(Equal(int32(10)))
+ })
+
+ It("renders a readiness probe for kube-controller-manager on /healthz:10257", func() {
+ podSpec := &corev1.PodSpec{}
+ d.buildControllerManager(podSpec, kamajiv1alpha1.TenantControlPlane{})
+
+ c := containerByName(podSpec, "kube-controller-manager")
+ Expect(c.ReadinessProbe).ToNot(BeNil())
+ Expect(c.ReadinessProbe.HTTPGet.Path).To(Equal("/healthz"))
+ Expect(c.ReadinessProbe.HTTPGet.Port.IntValue()).To(Equal(10257))
+ Expect(c.ReadinessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS))
+ })
+
+ It("cascades global then component readiness overrides onto the scheduler", func() {
+ tcp := kamajiv1alpha1.TenantControlPlane{}
+ tcp.Spec.ControlPlane.Deployment.Probes = &kamajiv1alpha1.ControlPlaneProbes{
+ Readiness: &kamajiv1alpha1.ProbeSpec{PeriodSeconds: pointer.To(int32(20))},
+ Scheduler: &kamajiv1alpha1.ProbeSet{
+ Readiness: &kamajiv1alpha1.ProbeSpec{PeriodSeconds: pointer.To(int32(30))},
+ },
+ }
+
+ podSpec := &corev1.PodSpec{}
+ d.buildScheduler(podSpec, tcp)
+
+ c := containerByName(podSpec, "kube-scheduler")
+ Expect(c.ReadinessProbe.PeriodSeconds).To(Equal(int32(30))) // component wins over global
+ })
+
+ It("applies a global-only readiness override to the scheduler", func() {
+ tcp := kamajiv1alpha1.TenantControlPlane{}
+ tcp.Spec.ControlPlane.Deployment.Probes = &kamajiv1alpha1.ControlPlaneProbes{
+ Readiness: &kamajiv1alpha1.ProbeSpec{PeriodSeconds: pointer.To(int32(20))},
+ }
+
+ podSpec := &corev1.PodSpec{}
+ d.buildScheduler(podSpec, tcp)
+
+ c := containerByName(podSpec, "kube-scheduler")
+ Expect(c.ReadinessProbe.PeriodSeconds).To(Equal(int32(20)))
+ })
+
+ It("leaves the kube-apiserver probes unchanged (regression guard)", func() {
+ podSpec := &corev1.PodSpec{}
+ tcp := kamajiv1alpha1.TenantControlPlane{}
+ tcp.Spec.NetworkProfile.Port = 6443
+ d.buildKubeAPIServer(podSpec, tcp, "")
+
+ c := containerByName(podSpec, "kube-apiserver")
+ Expect(c.LivenessProbe.HTTPGet.Path).To(Equal("/livez"))
+ Expect(c.ReadinessProbe.HTTPGet.Path).To(Equal("/readyz"))
+ Expect(c.StartupProbe.HTTPGet.Path).To(Equal("/livez"))
+ Expect(c.ReadinessProbe.HTTPGet.Port.IntValue()).To(Equal(6443))
+ })
+ })
})