Repository: cloudnative-pg/cloudnative-pg

Issue:
[Bug]: Pooler deployments not setting initContainer resources to be the same as pgbouncer
### Is there an existing issue already for this bug?

- [x] I have searched for an existing issue, and could not find anything. I believe this is a new bug.

### I have read the troubleshooting guide

- [x] I have read the troubleshooting guide and I think this is a new bug.

### I am running a supported version of CloudNativePG

- [x] I have read the troubleshooting guide and I think this is a new bug.

### Contact Details

_No response_

### Version

1.26 (latest patch)

### What version of Kubernetes are you using?

1.31

### What is your Kubernetes environment?

Cloud: Amazon EKS

### How did you install the operator?

YAML manifest

### What happened?

We deploy pooler pods with lower `requests.cpu: 500m / request.memory: 50Mi` to aid with consolidation.

But the `bootstrap-controller` init container is using `requests.cpu: 1 / requests.memory: 1Gi` and k8s scheduler is using this to place the pod. 

The deployment should set the initContainer resources to match the pgbouncer resources.





### Cluster resource

```shell

```

### Relevant log output

```shell

```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Discussion:
@gabriele-wolfox: I tested the patch, in order to check whether or not the pooler pods get restarted once we upgrade the operator image. 
No restarts happen.
@chris-gooch: Hi @armru ive got a strange issue with this. One eks cluster that deployed using manifests is setting bootstrap-controller resources as 0:0. 

Another EKS cluster that deployed using helm is setting bootstrap-controller as 1000:2000 even when I try with my earlier workaround to force the container resources. 

Any clues how to debug this? 

Solution patch:
diff --git a/api/v1/pooler_funcs.go b/api/v1/pooler_funcs.go
index 807e9da8..f1ca476e 100644
--- a/api/v1/pooler_funcs.go
+++ b/api/v1/pooler_funcs.go
@@ -19,6 +19,8 @@ SPDX-License-Identifier: Apache-2.0
 
 package v1
 
+import corev1 "k8s.io/api/core/v1"
+
 // IsPaused returns whether all database should be paused or not.
 func (in PgBouncerSpec) IsPaused() bool {
 	return in.Paused != nil && *in.Paused
@@ -58,3 +60,16 @@ func (in *Pooler) IsAutomatedIntegration() bool {
 	}
 	return true
 }
+
+// GetResourcesRequirements returns the resource requirements for the Pooler
+func (in *Pooler) GetResourcesRequirements() corev1.ResourceRequirements {
+	if in.Spec.Template == nil {
+		return corev1.ResourceRequirements{}
+	}
+
+	if in.Spec.Template.Spec.Resources == nil {
+		return corev1.ResourceRequirements{}
+	}
+
+	return *in.Spec.Template.Spec.Resources
+}
diff --git a/pkg/podspec/builder.go b/pkg/podspec/builder.go
index b4402cf3..b38ed71d 100644
--- a/pkg/podspec/builder.go
+++ b/pkg/podspec/builder.go
@@ -386,6 +386,28 @@ func (builder *Builder) WithInitContainerSecurityContext(
 	return builder
 }
 
+// WithInitContainerResources ensures that, if in the current status there is
+// an init container with the passed name and the resources are empty, the resources will be
+// set to the ones passed.
+// If `overwrite` is true the resources are overwritten even when they're not empty
+func (builder *Builder) WithInitContainerResources(
+	name string,
+	resources corev1.ResourceRequirements,
+	overwrite bool,
+) *Builder {
+	builder.WithInitContainer(name)
+
+	for idx, value := range builder.status.Spec.InitContainers {
+		if value.Name == name {
+			if overwrite || value.Resources.Limits == nil && value.Resources.Requests == nil {
+				builder.status.Spec.InitContainers[idx].Resources = resources
+			}
+		}
+	}
+
+	return builder
+}
+
 // Build gets the final Pod template
 func (builder *Builder) Build() *apiv1.PodTemplateSpec {
 	return &builder.status
diff --git a/pkg/specs/pgbouncer/deployments.go b/pkg/specs/pgbouncer/deployments.go
index bc0993f8..2df9f7d7 100644
--- a/pkg/specs/pgbouncer/deployments.go
+++ b/pkg/specs/pgbouncer/deployments.go
@@ -94,6 +94,7 @@ func Deployment(pooler *apiv1.Pooler, cluster *apiv1.Cluster) (*appsv1.Deploymen
 		WithInitContainerCommand(specs.BootstrapControllerContainerName,
 			[]string{"/manager", "bootstrap", "/controller/manager"},
 			true).
+		WithInitContainerResources(specs.BootstrapControllerContainerName, pooler.GetResourcesRequirements(), true).
 		WithInitContainerSecurityContext(specs.BootstrapControllerContainerName,
 			specs.CreateContainerSecurityContext(cluster.GetSeccompProfile()),
 			true).



Test patch:
diff --git a/internal/controller/pooler_status_test.go b/internal/controller/pooler_status_test.go
index bf8434cf..9bbba948 100644
--- a/internal/controller/pooler_status_test.go
+++ b/internal/controller/pooler_status_test.go
@@ -23,6 +23,7 @@ import (
 	"context"
 
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 
@@ -95,6 +96,39 @@ var _ = Describe("pooler_status unit tests", func() {
 		Expect(pooler.Status.Instances).To(Equal(dep.Status.Replicas))
 	})
 
+	It("should correctly set pod resources to the bootstrap init container", func() {
+		cluster := newFakeCNPGCluster(env.client, "test-namespace")
+
+		pooler := &v1.Pooler{
+			Spec: v1.PoolerSpec{
+				Template: &v1.PodTemplateSpec{
+					Spec: corev1.PodSpec{
+						Resources: &corev1.ResourceRequirements{
+							Requests: corev1.ResourceList{
+								corev1.ResourceCPU:    resource.MustParse("100m"),
+								corev1.ResourceMemory: resource.MustParse("128Mi"),
+							},
+							Limits: corev1.ResourceList{
+								corev1.ResourceCPU:    resource.MustParse("200m"),
+								corev1.ResourceMemory: resource.MustParse("256Mi"),
+							},
+						},
+					},
+				},
+			},
+		}
+
+		dep, err := pgbouncer.Deployment(pooler, cluster)
+		Expect(err).ToNot(HaveOccurred())
+		// check that the init container has the correct resources
+		Expect(dep.Spec.Template.Spec.InitContainers).To(HaveLen(1))
+		initResources := dep.Spec.Template.Spec.InitContainers[0].Resources
+		Expect(initResources.Requests).To(HaveKeyWithValue(corev1.ResourceCPU, resource.MustParse("100m")))
+		Expect(initResources.Requests).To(HaveKeyWithValue(corev1.ResourceMemory, resource.MustParse("128Mi")))
+		Expect(initResources.Limits).To(HaveKeyWithValue(corev1.ResourceCPU, resource.MustParse("200m")))
+		Expect(initResources.Limits).To(HaveKeyWithValue(corev1.ResourceMemory, resource.MustParse("256Mi")))
+	})
+
 	It("should correctly interact with the api server", func() {
 		ctx := context.Background()
 		namespace := newFakeNamespace(env.client)