feat: make startup probe configurable for the sidecar

Signed-off-by: Tudor Golubenco <tudor@xata.io>
This commit is contained in:
Tudor Golubenco 2025-09-04 12:04:11 -07:00
parent b556fea179
commit 3ac67534d6
4 changed files with 127 additions and 3 deletions

View File

@ -80,7 +80,7 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
.PHONY: build
build: manifests generate fmt vet ## Build manager binary.
go build -o bin/manager cmd/main.go
go build -o bin/manager cmd/manager/main.go
.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.

View File

@ -1,6 +1,7 @@
package config
import (
"strconv"
"strings"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
@ -57,6 +58,29 @@ type PluginConfiguration struct {
ReplicaSourceBarmanObjectName string
ReplicaSourceServerName string
// Probe configuration
StartupProbeConfig *ProbeConfig
}
// ProbeConfig holds configuration for Kubernetes probes
type ProbeConfig struct {
InitialDelaySeconds int32
TimeoutSeconds int32
PeriodSeconds int32
FailureThreshold int32
SuccessThreshold int32
}
// DefaultProbeConfig returns the default probe configuration
func DefaultProbeConfig() *ProbeConfig {
return &ProbeConfig{
InitialDelaySeconds: 0,
TimeoutSeconds: 10,
PeriodSeconds: 10,
FailureThreshold: 10,
SuccessThreshold: 1,
}
}
// GetBarmanObjectKey gets the namespaced name of the barman object
@ -166,11 +190,50 @@ func NewFromCluster(cluster *cnpgv1.Cluster) *PluginConfiguration {
// used for wal_restore in the designed primary of a replica cluster
ReplicaSourceServerName: replicaSourceServerName,
ReplicaSourceBarmanObjectName: replicaSourceBarmanObjectName,
// probe configuration
StartupProbeConfig: parseProbeConfig(helper.Parameters),
}
return result
}
// parseProbeConfig parses probe configuration from plugin parameters
func parseProbeConfig(parameters map[string]string) *ProbeConfig {
config := DefaultProbeConfig()
if val, ok := parameters["startupProbe.initialDelaySeconds"]; ok {
if parsed, err := strconv.ParseInt(val, 10, 32); err == nil {
config.InitialDelaySeconds = int32(parsed)
}
}
if val, ok := parameters["startupProbe.timeoutSeconds"]; ok {
if parsed, err := strconv.ParseInt(val, 10, 32); err == nil {
config.TimeoutSeconds = int32(parsed)
}
}
if val, ok := parameters["startupProbe.periodSeconds"]; ok {
if parsed, err := strconv.ParseInt(val, 10, 32); err == nil {
config.PeriodSeconds = int32(parsed)
}
}
if val, ok := parameters["startupProbe.failureThreshold"]; ok {
if parsed, err := strconv.ParseInt(val, 10, 32); err == nil {
config.FailureThreshold = int32(parsed)
}
}
if val, ok := parameters["startupProbe.successThreshold"]; ok {
if parsed, err := strconv.ParseInt(val, 10, 32); err == nil {
config.SuccessThreshold = int32(parsed)
}
}
return config
}
func getRecoveryParameters(cluster *cnpgv1.Cluster) map[string]string {
recoveryPluginConfiguration := getRecoverySourcePlugin(cluster)
if recoveryPluginConfiguration == nil {

View File

@ -129,6 +129,7 @@ func (impl LifecycleImplementation) reconcileJob(
env: env,
certificates: certificates,
resources: resources,
probeConfig: pluginConfiguration.StartupProbeConfig,
})
}
@ -136,6 +137,7 @@ type sidecarConfiguration struct {
env []corev1.EnvVar
certificates []corev1.VolumeProjection
resources corev1.ResourceRequirements
probeConfig *config.ProbeConfig
}
func reconcileJob(
@ -221,6 +223,7 @@ func (impl LifecycleImplementation) reconcilePod(
env: env,
certificates: certificates,
resources: resources,
probeConfig: pluginConfiguration.StartupProbeConfig,
})
}
@ -304,8 +307,6 @@ func reconcilePodSpec(
envs = append(envs, config.env...)
baseProbe := &corev1.Probe{
FailureThreshold: 10,
TimeoutSeconds: 10,
ProbeHandler: corev1.ProbeHandler{
Exec: &corev1.ExecAction{
Command: []string{"/manager", "healthcheck", "unix"},
@ -313,6 +314,19 @@ func reconcilePodSpec(
},
}
// Apply configurable probe settings if available
if config.probeConfig != nil {
baseProbe.InitialDelaySeconds = config.probeConfig.InitialDelaySeconds
baseProbe.TimeoutSeconds = config.probeConfig.TimeoutSeconds
baseProbe.PeriodSeconds = config.probeConfig.PeriodSeconds
baseProbe.FailureThreshold = config.probeConfig.FailureThreshold
baseProbe.SuccessThreshold = config.probeConfig.SuccessThreshold
} else {
// Fallback to default values
baseProbe.FailureThreshold = 10
baseProbe.TimeoutSeconds = 10
}
// fixed values
sidecarTemplate.Name = "plugin-barman-cloud"
sidecarTemplate.Image = viper.GetString("sidecar-image")

View File

@ -172,6 +172,53 @@ var _ = Describe("LifecycleImplementation", func() {
})
Describe("reconcilePod", func() {
It("returns a patch for a valid pod with probe configuration", func(ctx SpecContext) {
// Configure plugin with custom probe settings
pluginConfiguration.StartupProbeConfig = &config.ProbeConfig{
InitialDelaySeconds: 1,
TimeoutSeconds: 15,
PeriodSeconds: 2,
FailureThreshold: 5,
SuccessThreshold: 1,
}
pod := &corev1.Pod{
TypeMeta: podTypeMeta,
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "postgres",
},
},
},
}
podJSON, err := json.Marshal(pod)
Expect(err).NotTo(HaveOccurred())
request := &lifecycle.OperatorLifecycleRequest{
ObjectDefinition: podJSON,
}
response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, sidecarConfiguration{
probeConfig: pluginConfiguration.StartupProbeConfig,
})
Expect(err).NotTo(HaveOccurred())
Expect(response).NotTo(BeNil())
Expect(response.JsonPatch).NotTo(BeEmpty())
// Verify the patch contains the expected probe configuration
Expect(string(response.JsonPatch)).To(ContainSubstring("startupProbe"))
Expect(string(response.JsonPatch)).To(ContainSubstring("\"initialDelaySeconds\":1"))
Expect(string(response.JsonPatch)).To(ContainSubstring("\"timeoutSeconds\":15"))
Expect(string(response.JsonPatch)).To(ContainSubstring("\"periodSeconds\":2"))
Expect(string(response.JsonPatch)).To(ContainSubstring("\"failureThreshold\":5"))
Expect(string(response.JsonPatch)).To(ContainSubstring("\"successThreshold\":1"))
})
It("returns a patch for a valid pod", func(ctx SpecContext) {
pod := &corev1.Pod{
TypeMeta: podTypeMeta,