feat(sidecar): add resource requirements and limits (#307)

Closes #253

Signed-off-by: MichaluxPL <68371308+MichaluxPL@users.noreply.github.com>
Signed-off-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>
Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Co-authored-by: MichaluxPL <68371308+MichaluxPL@users.noreply.github.com>
Co-authored-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Co-authored-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
This commit is contained in:
Leonardo Cecchi 2025-05-09 17:32:02 +02:00 committed by GitHub
parent c24d7aed3e
commit 4bb347121d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 312 additions and 33 deletions

View File

@ -33,6 +33,7 @@ README
RPO RPO
RTO RTO
RecoveryWindow RecoveryWindow
ResourceRequirements
RetentionPolicy RetentionPolicy
SAS SAS
SFO SFO
@ -64,6 +65,7 @@ cmctl
cnpg cnpg
codebase codebase
containerPort containerPort
cpu
creds creds
csi csi
customresourcedefinition customresourcedefinition
@ -102,6 +104,7 @@ repos
retentionCheckInterval retentionCheckInterval
retentionPolicy retentionPolicy
rolebinding rolebinding
rollout
sc sc
secretKeyRef secretKeyRef
selfsigned selfsigned

View File

@ -33,6 +33,10 @@ type InstanceSidecarConfiguration struct {
// +kubebuilder:default:=1800 // +kubebuilder:default:=1800
// +optional // +optional
RetentionPolicyIntervalSeconds int `json:"retentionPolicyIntervalSeconds,omitempty"` RetentionPolicyIntervalSeconds int `json:"retentionPolicyIntervalSeconds,omitempty"`
// Resources define cpu/memory requests and limits for the sidecar that runs in the instance pods.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
} }
// ObjectStoreSpec defines the desired state of ObjectStore. // ObjectStoreSpec defines the desired state of ObjectStore.

View File

@ -35,6 +35,7 @@ func (in *InstanceSidecarConfiguration) DeepCopyInto(out *InstanceSidecarConfigu
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
in.Resources.DeepCopyInto(&out.Resources)
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceSidecarConfiguration. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceSidecarConfiguration.

View File

@ -511,6 +511,66 @@ spec:
- name - name
type: object type: object
type: array type: array
resources:
description: Resources define cpu/memory requests and limits for
the sidecar that runs in the instance pods.
properties:
claims:
description: |-
Claims lists the names of resources, defined in spec.resourceClaims,
that are used by this container.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This field is immutable. It can only be set for containers.
items:
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
properties:
name:
description: |-
Name must match the name of one entry in pod.spec.resourceClaims of
the Pod where this field is used. It makes that resource available
inside a container.
type: string
request:
description: |-
Request is the name chosen for a request in the referenced claim.
If empty, everything from the claim is made available, otherwise
only the result of this request.
type: string
required:
- name
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: |-
Limits describes the maximum amount of compute resources allowed.
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: |-
Requests describes the minimum amount of compute resources required.
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
otherwise to an implementation-defined value. Requests cannot exceed Limits.
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
type: object
type: object
retentionPolicyIntervalSeconds: retentionPolicyIntervalSeconds:
default: 1800 default: 1800
description: |- description: |-

View File

@ -5,7 +5,14 @@ metadata:
spec: spec:
retentionPolicy: "1m" retentionPolicy: "1m"
instanceSidecarConfiguration: instanceSidecarConfiguration:
retentionPolicyIntervalSeconds: 30 retentionPolicyIntervalSeconds: 1800
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
configuration: configuration:
endpointCA: endpointCA:
name: minio-server-tls name: minio-server-tls
@ -27,4 +34,3 @@ spec:
- "--min-chunk-size=5MB" - "--min-chunk-size=5MB"
- "--read-timeout=60" - "--read-timeout=60"
- "-vv" - "-vv"

View File

@ -123,15 +123,29 @@ func (impl LifecycleImplementation) reconcileJob(
return nil, err return nil, err
} }
return reconcileJob(ctx, cluster, request, env, certificates) resources, err := impl.collectSidecarResourcesForRecoveryJob(ctx, pluginConfiguration)
if err != nil {
return nil, err
}
return reconcileJob(ctx, cluster, request, sidecarConfiguration{
env: env,
certificates: certificates,
resources: resources,
})
}
type sidecarConfiguration struct {
env []corev1.EnvVar
certificates []corev1.VolumeProjection
resources corev1.ResourceRequirements
} }
func reconcileJob( func reconcileJob(
ctx context.Context, ctx context.Context,
cluster *cnpgv1.Cluster, cluster *cnpgv1.Cluster,
request *lifecycle.OperatorLifecycleRequest, request *lifecycle.OperatorLifecycleRequest,
env []corev1.EnvVar, config sidecarConfiguration,
certificates []corev1.VolumeProjection,
) (*lifecycle.OperatorLifecycleResponse, error) { ) (*lifecycle.OperatorLifecycleResponse, error) {
contextLogger := log.FromContext(ctx).WithName("lifecycle") contextLogger := log.FromContext(ctx).WithName("lifecycle")
if pluginConfig := cluster.GetRecoverySourcePlugin(); pluginConfig == nil || pluginConfig.Name != metadata.PluginName { if pluginConfig := cluster.GetRecoverySourcePlugin(); pluginConfig == nil || pluginConfig.Name != metadata.PluginName {
@ -169,8 +183,7 @@ func reconcileJob(
corev1.Container{ corev1.Container{
Args: []string{"restore"}, Args: []string{"restore"},
}, },
env, config,
certificates,
); err != nil { ); err != nil {
return nil, fmt.Errorf("while reconciling pod spec for job: %w", err) return nil, fmt.Errorf("while reconciling pod spec for job: %w", err)
} }
@ -202,7 +215,16 @@ func (impl LifecycleImplementation) reconcilePod(
return nil, err return nil, err
} }
return reconcilePod(ctx, cluster, request, pluginConfiguration, env, certificates) resources, err := impl.collectSidecarResourcesForPod(ctx, pluginConfiguration)
if err != nil {
return nil, err
}
return reconcilePod(ctx, cluster, request, pluginConfiguration, sidecarConfiguration{
env: env,
certificates: certificates,
resources: resources,
})
} }
func reconcilePod( func reconcilePod(
@ -210,8 +232,7 @@ func reconcilePod(
cluster *cnpgv1.Cluster, cluster *cnpgv1.Cluster,
request *lifecycle.OperatorLifecycleRequest, request *lifecycle.OperatorLifecycleRequest,
pluginConfiguration *config.PluginConfiguration, pluginConfiguration *config.PluginConfiguration,
env []corev1.EnvVar, config sidecarConfiguration,
certificates []corev1.VolumeProjection,
) (*lifecycle.OperatorLifecycleResponse, error) { ) (*lifecycle.OperatorLifecycleResponse, error) {
pod, err := decoder.DecodePodJSON(request.GetObjectDefinition()) pod, err := decoder.DecodePodJSON(request.GetObjectDefinition())
if err != nil { if err != nil {
@ -232,8 +253,7 @@ func reconcilePod(
corev1.Container{ corev1.Container{
Args: []string{"instance"}, Args: []string{"instance"},
}, },
env, config,
certificates,
); err != nil { ); err != nil {
return nil, fmt.Errorf("while reconciling pod spec for pod: %w", err) return nil, fmt.Errorf("while reconciling pod spec for pod: %w", err)
} }
@ -256,9 +276,8 @@ func reconcilePodSpec(
cluster *cnpgv1.Cluster, cluster *cnpgv1.Cluster,
spec *corev1.PodSpec, spec *corev1.PodSpec,
mainContainerName string, mainContainerName string,
sidecarConfig corev1.Container, sidecarTemplate corev1.Container,
additionalEnvs []corev1.EnvVar, config sidecarConfiguration,
certificates []corev1.VolumeProjection,
) error { ) error {
envs := []corev1.EnvVar{ envs := []corev1.EnvVar{
{ {
@ -285,7 +304,7 @@ func reconcilePodSpec(
}, },
} }
envs = append(envs, additionalEnvs...) envs = append(envs, config.env...)
baseProbe := &corev1.Probe{ baseProbe := &corev1.Probe{
FailureThreshold: 10, FailureThreshold: 10,
@ -298,11 +317,11 @@ func reconcilePodSpec(
} }
// fixed values // fixed values
sidecarConfig.Name = "plugin-barman-cloud" sidecarTemplate.Name = "plugin-barman-cloud"
sidecarConfig.Image = viper.GetString("sidecar-image") sidecarTemplate.Image = viper.GetString("sidecar-image")
sidecarConfig.ImagePullPolicy = cluster.Spec.ImagePullPolicy sidecarTemplate.ImagePullPolicy = cluster.Spec.ImagePullPolicy
sidecarConfig.StartupProbe = baseProbe.DeepCopy() sidecarTemplate.StartupProbe = baseProbe.DeepCopy()
sidecarConfig.SecurityContext = &corev1.SecurityContext{ sidecarTemplate.SecurityContext = &corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(false), AllowPrivilegeEscalation: ptr.To(false),
RunAsNonRoot: ptr.To(true), RunAsNonRoot: ptr.To(true),
Privileged: ptr.To(false), Privileged: ptr.To(false),
@ -314,20 +333,21 @@ func reconcilePodSpec(
Drop: []corev1.Capability{"ALL"}, Drop: []corev1.Capability{"ALL"},
}, },
} }
sidecarTemplate.Resources = config.resources
// merge the main container envs if they aren't already set // merge the main container envs if they aren't already set
for _, container := range spec.Containers { for _, container := range spec.Containers {
if container.Name == mainContainerName { if container.Name == mainContainerName {
for _, env := range container.Env { for _, env := range container.Env {
found := false found := false
for _, existingEnv := range sidecarConfig.Env { for _, existingEnv := range sidecarTemplate.Env {
if existingEnv.Name == env.Name { if existingEnv.Name == env.Name {
found = true found = true
break break
} }
} }
if !found { if !found {
sidecarConfig.Env = append(sidecarConfig.Env, env) sidecarTemplate.Env = append(sidecarTemplate.Env, env)
} }
} }
break break
@ -337,18 +357,18 @@ func reconcilePodSpec(
// merge the default envs if they aren't already set // merge the default envs if they aren't already set
for _, env := range envs { for _, env := range envs {
found := false found := false
for _, existingEnv := range sidecarConfig.Env { for _, existingEnv := range sidecarTemplate.Env {
if existingEnv.Name == env.Name { if existingEnv.Name == env.Name {
found = true found = true
break break
} }
} }
if !found { if !found {
sidecarConfig.Env = append(sidecarConfig.Env, env) sidecarTemplate.Env = append(sidecarTemplate.Env, env)
} }
} }
if err := injectPluginSidecarPodSpec(spec, &sidecarConfig, mainContainerName); err != nil { if err := injectPluginSidecarPodSpec(spec, &sidecarTemplate, mainContainerName); err != nil {
return err return err
} }
@ -358,7 +378,7 @@ func reconcilePodSpec(
Name: barmanCertificatesVolumeName, Name: barmanCertificatesVolumeName,
VolumeSource: corev1.VolumeSource{ VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{ Projected: &corev1.ProjectedVolumeSource{
Sources: certificates, Sources: config.certificates,
}, },
}, },
}) })

View File

@ -17,6 +17,9 @@ func (impl LifecycleImplementation) collectAdditionalEnvs(
) ([]corev1.EnvVar, error) { ) ([]corev1.EnvVar, error) {
var result []corev1.EnvVar var result []corev1.EnvVar
// TODO: check if the environment variables are clashing and in
// that case raise an error
if len(pluginConfiguration.BarmanObjectName) > 0 { if len(pluginConfiguration.BarmanObjectName) > 0 {
envs, err := impl.collectObjectStoreEnvs( envs, err := impl.collectObjectStoreEnvs(
ctx, ctx,
@ -45,6 +48,20 @@ func (impl LifecycleImplementation) collectAdditionalEnvs(
result = append(result, envs...) result = append(result, envs...)
} }
if len(pluginConfiguration.ReplicaSourceBarmanObjectName) > 0 {
envs, err := impl.collectObjectStoreEnvs(
ctx,
types.NamespacedName{
Name: pluginConfiguration.ReplicaSourceBarmanObjectName,
Namespace: namespace,
},
)
if err != nil {
return nil, err
}
result = append(result, envs...)
}
return result, nil return result, nil
} }

View File

@ -0,0 +1,61 @@
package operator
import (
"context"
corev1 "k8s.io/api/core/v1"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
)
func (impl LifecycleImplementation) collectSidecarResourcesForRecoveryJob(
ctx context.Context,
configuration *config.PluginConfiguration,
) (corev1.ResourceRequirements, error) {
if len(configuration.RecoveryBarmanObjectName) > 0 {
var barmanObjectStore barmancloudv1.ObjectStore
if err := impl.Client.Get(ctx, configuration.GetRecoveryBarmanObjectKey(), &barmanObjectStore); err != nil {
return corev1.ResourceRequirements{}, err
}
return barmanObjectStore.Spec.InstanceSidecarConfiguration.Resources, nil
}
return corev1.ResourceRequirements{}, nil
}
func (impl LifecycleImplementation) collectSidecarResourcesForPod(
ctx context.Context,
configuration *config.PluginConfiguration,
) (corev1.ResourceRequirements, error) {
if len(configuration.BarmanObjectName) > 0 {
// On a replica cluster that also archives, the designated primary
// will use both the replica source object store and the object store
// of the cluster.
// In this case, we use the cluster object store for configuring
// the resources of the sidecar container.
var barmanObjectStore barmancloudv1.ObjectStore
if err := impl.Client.Get(ctx, configuration.GetBarmanObjectKey(), &barmanObjectStore); err != nil {
return corev1.ResourceRequirements{}, err
}
return barmanObjectStore.Spec.InstanceSidecarConfiguration.Resources, nil
}
if len(configuration.RecoveryBarmanObjectName) > 0 {
// On a replica cluster that doesn't archive, the designated primary
// uses only the replica source object store.
// In this case, we use the replica source object store for configuring
// the resources of the sidecar container.
var barmanObjectStore barmancloudv1.ObjectStore
if err := impl.Client.Get(ctx, configuration.GetRecoveryBarmanObjectKey(), &barmanObjectStore); err != nil {
return corev1.ResourceRequirements{}, err
}
return barmanObjectStore.Spec.InstanceSidecarConfiguration.Resources, nil
}
return corev1.ResourceRequirements{}, nil
}

View File

@ -107,7 +107,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: jobJSON, ObjectDefinition: jobJSON,
} }
response, err := reconcileJob(ctx, cluster, request, nil, nil) response, err := reconcileJob(ctx, cluster, request, sidecarConfiguration{})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).NotTo(BeNil()) Expect(response).NotTo(BeNil())
Expect(response.JsonPatch).NotTo(BeEmpty()) Expect(response.JsonPatch).NotTo(BeEmpty())
@ -128,7 +128,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: jobJSON, ObjectDefinition: jobJSON,
} }
response, err := reconcileJob(ctx, cluster, request, nil, nil) response, err := reconcileJob(ctx, cluster, request, sidecarConfiguration{})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })
@ -138,7 +138,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: []byte("invalid-json"), ObjectDefinition: []byte("invalid-json"),
} }
response, err := reconcileJob(ctx, cluster, request, nil, nil) response, err := reconcileJob(ctx, cluster, request, sidecarConfiguration{})
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })
@ -165,7 +165,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: jobJSON, ObjectDefinition: jobJSON,
} }
response, err := reconcileJob(ctx, cluster, request, nil, nil) response, err := reconcileJob(ctx, cluster, request, sidecarConfiguration{})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })
@ -185,7 +185,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: podJSON, ObjectDefinition: podJSON,
} }
response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, nil, nil) response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, sidecarConfiguration{})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).NotTo(BeNil()) Expect(response).NotTo(BeNil())
Expect(response.JsonPatch).NotTo(BeEmpty()) Expect(response.JsonPatch).NotTo(BeEmpty())
@ -203,7 +203,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: []byte("invalid-json"), ObjectDefinition: []byte("invalid-json"),
} }
response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, nil, nil) response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, sidecarConfiguration{})
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })

View File

@ -510,6 +510,66 @@ spec:
- name - name
type: object type: object
type: array type: array
resources:
description: Resources define cpu/memory requests and limits for
the sidecar that runs in the instance pods.
properties:
claims:
description: |-
Claims lists the names of resources, defined in spec.resourceClaims,
that are used by this container.
This is an alpha field and requires enabling the
DynamicResourceAllocation feature gate.
This field is immutable. It can only be set for containers.
items:
description: ResourceClaim references one entry in PodSpec.ResourceClaims.
properties:
name:
description: |-
Name must match the name of one entry in pod.spec.resourceClaims of
the Pod where this field is used. It makes that resource available
inside a container.
type: string
request:
description: |-
Request is the name chosen for a request in the referenced claim.
If empty, everything from the claim is made available, otherwise
only the result of this request.
type: string
required:
- name
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
limits:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: |-
Limits describes the maximum amount of compute resources allowed.
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
type: object
requests:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: |-
Requests describes the minimum amount of compute resources required.
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
otherwise to an implementation-defined value. Requests cannot exceed Limits.
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
type: object
type: object
retentionPolicyIntervalSeconds: retentionPolicyIntervalSeconds:
default: 1800 default: 1800
description: |- description: |-

View File

@ -28,6 +28,7 @@ _Appears in:_
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#envvar-v1-core) array_ | The environment to be explicitly passed to the sidecar | | | | | `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#envvar-v1-core) array_ | The environment to be explicitly passed to the sidecar | | | |
| `retentionPolicyIntervalSeconds` _integer_ | The retentionCheckInterval defines the frequency at which the<br />system checks and enforces retention policies. | | 1800 | | | `retentionPolicyIntervalSeconds` _integer_ | The retentionCheckInterval defines the frequency at which the<br />system checks and enforces retention policies. | | 1800 | |
| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#resourcerequirements-v1-core)_ | Resources define cpu/memory requests and limits for the sidecar that runs in the instance pods. | | | |
#### ObjectStore #### ObjectStore

View File

@ -210,3 +210,49 @@ spec:
parameters: parameters:
barmanObjectName: minio-store-b barmanObjectName: minio-store-b
``` ```
## Configuring the plugin instance sidecar
The Barman Cloud Plugin runs as a sidecar container next to each PostgreSQL
instance pod. It manages backup, WAL archiving, and restore processes.
Configuration comes from multiple `ObjectStore` resources:
1. The one referenced in the
`.spec.plugins` section of the `Cluster`. This is the
object store used for WAL archiving and base backups.
2. The one referenced in the external cluster
used in the `.spec.replica.source` section of the `Cluster`. This is
used by the log-shipping designated primary to get the WAL files.
3. The one referenced in the
`.spec.bootstrap.recovery.source` section of the `Cluster`. Used by
the initial recovery job to create the cluster from an existing backup.
You can fine-tune sidecar behavior in the `.spec.instanceSidecarConfiguration`
of your ObjectStore. These settings apply to all PostgreSQL instances that use
this object store. Any updates take effect at the next `Cluster` reconciliation,
and could generate a rollout of the `Cluster`.
```yaml
apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: minio-store
spec:
configuration:
# [...]
instanceSidecarConfiguration:
retentionPolicyIntervalSeconds: 1800
resources:
requests:
memory: "XXX"
cpu: "YYY"
limits:
memory: "XXX"
cpu: "YYY"
```
:::note
If more than one `ObjectStore` applies, the `instanceSidecarConfiguration` of
the one set in `.spec.plugins` has priority.
:::