diff --git a/build/logcollector/entrypoint.sh b/build/logcollector/entrypoint.sh index 5a6cf254d7..df8ea466a1 100755 --- a/build/logcollector/entrypoint.sh +++ b/build/logcollector/entrypoint.sh @@ -1,21 +1,100 @@ -#!/bin/sh +#!/bin/bash set -e -set -o xtrace -export PATH="$PATH":/opt/fluent-bit/bin +export PATH="$PATH:/opt/fluent-bit/bin" -if [ "$1" = 'logrotate' ]; then +LOGROTATE_SCHEDULE="${LOGROTATE_SCHEDULE:-0 0 * * *}" + +is_logrotate_config_invalid() { + local config_file="$1" + if [ -z "$config_file" ] || [ ! -f "$config_file" ]; then + return 1 + fi + # Specifying -d runs in debug mode, so even in case of errors, it will exit with 0. + # We need to check the output for "error" but skip those lines that are related to the missing logrotate.status file. + # Filter out logrotate.status lines first, then check for remaining errors + ( + set +e + logrotate -d "$config_file" 2>&1 | grep -v "logrotate.status" | grep -qi "error" + ) + return $? +} + +run_logrotate() { + local logrotate_status_file="/data/db/logs/logrotate.status" + local logrotate_conf_file="/opt/percona/logcollector/logrotate/logrotate.conf" + local logrotate_additional_conf_files=() + local conf_d_dir="/opt/percona/logcollector/logrotate/conf.d" + + # Check if mongodb.conf exists and validate it + if [ -f "$conf_d_dir/mongodb.conf" ]; then + logrotate_conf_file="$conf_d_dir/mongodb.conf" + if is_logrotate_config_invalid "$logrotate_conf_file"; then + echo "ERROR: Logrotate configuration is invalid, fallback to default configuration" + logrotate_conf_file="/opt/percona/logcollector/logrotate/logrotate.conf" + fi + fi + + # Process all .conf files in conf.d directory (excluding mongodb.conf which is already handled) + if [ -d "$conf_d_dir" ]; then + for conf_file in "$conf_d_dir"/*.conf; do + # Check if glob matched any files (if no .conf files exist, the glob returns itself) + [ -f "$conf_file" ] || continue + # Skip mongodb.conf as it's already processed above + [ "$(basename "$conf_file")" = "mongodb.conf" ] && continue + if is_logrotate_config_invalid "$conf_file"; then + echo "ERROR: Logrotate configuration file $conf_file is invalid, it will be ignored" + else + logrotate_additional_conf_files+=("$conf_file") + fi + done + fi + # Ensure logrotate can run with current UID if [[ $EUID != 1001 ]]; then # logrotate requires UID in /etc/passwd sed -e "s^x:1001:^x:$EUID:^" /etc/passwd >/tmp/passwd cat /tmp/passwd >/etc/passwd rm -rf /tmp/passwd fi - exec go-cron "0 0 * * *" sh -c "logrotate -s /data/db/logs/logrotate.status /opt/percona/logcollector/logrotate/logrotate.conf;" -else - if [ "$1" = 'fluent-bit' ]; then - fluentbit_opt+='-c /opt/percona/logcollector/fluentbit/fluentbit.conf' - fi - exec "$@" $fluentbit_opt -fi + local logrotate_cmd="logrotate -s \"$logrotate_status_file\" \"$logrotate_conf_file\"" + for additional_conf in "${logrotate_additional_conf_files[@]}"; do + logrotate_cmd="$logrotate_cmd \"$additional_conf\"" + done + + set -o xtrace + exec go-cron "$LOGROTATE_SCHEDULE" sh -c "$logrotate_cmd" +} + +run_fluentbit() { + local fluentbit_opt=(-c /opt/percona/logcollector/fluentbit/fluentbit.conf) + mkdir -p /tmp/fluentbit/custom + set +e + local fluentbit_conf_dir="/opt/percona/logcollector/fluentbit/custom" + for conf_file in $fluentbit_conf_dir/*.conf; do + [ -f "$conf_file" ] || continue + if ! fluent-bit --dry-run -c "$conf_file" >/dev/null 2>&1; then + echo "ERROR: Fluentbit configuration file $conf_file is invalid, it will be ignored" + else + cp "$conf_file" /tmp/fluentbit/custom/ + fi + done + touch /tmp/fluentbit/custom/default.conf || true + + set -e + set -o xtrace + exec "$@" "${fluentbit_opt[@]}" +} + +case "$1" in + logrotate) + run_logrotate + ;; + fluent-bit) + run_fluentbit "$@" + ;; + *) + echo "Invalid argument: $1" + exit 1 + ;; +esac diff --git a/build/logcollector/fluentbit/fluentbit.conf b/build/logcollector/fluentbit/fluentbit.conf index 75ff9d06fe..5c77425f3f 100644 --- a/build/logcollector/fluentbit/fluentbit.conf +++ b/build/logcollector/fluentbit/fluentbit.conf @@ -1,2 +1,2 @@ @INCLUDE fluentbit_*.conf -@INCLUDE custom/*.conf +@INCLUDE /tmp/fluentbit/custom/*.conf diff --git a/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml b/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml index 5a82bef5a4..d81a961a27 100644 --- a/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml +++ b/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml @@ -806,6 +806,21 @@ spec: type: string imagePullPolicy: type: string + logrotate: + properties: + configuration: + type: string + extraConfig: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + schedule: + default: 0 0 * * * + type: string + type: object resources: properties: claims: diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 317179ad61..545531995e 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -1665,6 +1665,21 @@ spec: type: string imagePullPolicy: type: string + logrotate: + properties: + configuration: + type: string + extraConfig: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + schedule: + default: 0 0 * * * + type: string + type: object resources: properties: claims: diff --git a/deploy/cr.yaml b/deploy/cr.yaml index b6ce3e7e4d..24dddf8a82 100644 --- a/deploy/cr.yaml +++ b/deploy/cr.yaml @@ -884,3 +884,8 @@ spec: requests: memory: 100M cpu: 200m +# logrotate: +# configuration: "" +# extraConfig: +# name: logrotate-config +# schedule: "0 0 * * *" diff --git a/deploy/crd.yaml b/deploy/crd.yaml index adb8c7f124..60ae943a38 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -1665,6 +1665,21 @@ spec: type: string imagePullPolicy: type: string + logrotate: + properties: + configuration: + type: string + extraConfig: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + schedule: + default: 0 0 * * * + type: string + type: object resources: properties: claims: diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 3564f8de61..fa5e5709d8 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -1665,6 +1665,21 @@ spec: type: string imagePullPolicy: type: string + logrotate: + properties: + configuration: + type: string + extraConfig: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + schedule: + default: 0 0 * * * + type: string + type: object resources: properties: claims: diff --git a/e2e-tests/version-service/conf/crd.yaml b/e2e-tests/version-service/conf/crd.yaml index adb8c7f124..60ae943a38 100644 --- a/e2e-tests/version-service/conf/crd.yaml +++ b/e2e-tests/version-service/conf/crd.yaml @@ -1665,6 +1665,21 @@ spec: type: string imagePullPolicy: type: string + logrotate: + properties: + configuration: + type: string + extraConfig: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + schedule: + default: 0 0 * * * + type: string + type: object resources: properties: claims: diff --git a/pkg/apis/psmdb/v1/psmdb_types.go b/pkg/apis/psmdb/v1/psmdb_types.go index 00421fae13..6d3da2d03d 100644 --- a/pkg/apis/psmdb/v1/psmdb_types.go +++ b/pkg/apis/psmdb/v1/psmdb_types.go @@ -1579,6 +1579,23 @@ type LogCollectorSpec struct { ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` Env []corev1.EnvVar `json:"env,omitempty"` EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"` + LogRotate *LogRotateSpec `json:"logrotate,omitempty"` +} + +// LogRotateSpec defines the configuration for the logrotate container. +type LogRotateSpec struct { + // Configuration allows overriding the default logrotate configuration. + Configuration string `json:"configuration,omitempty"` + // ExtraConfig allows specifying logrotate configuration file in addition to the main configuration file. + // This should be a reference to a ConfigMap or a Secret in the same namespace. + // Key must contain the .conf extension to be processed correctly. + // + // NOTE: mongodb.conf is reserved for the default configuration specified by .configuration field. + ExtraConfig corev1.LocalObjectReference `json:"extraConfig,omitempty"` + // Schedule allows specifying the schedule for logrotate. + // This should be a valid cron expression. + //+kubebuilder:default:="0 0 * * *" + Schedule string `json:"schedule,omitempty"` } func (cr *PerconaServerMongoDB) IsLogCollectorEnabled() bool { diff --git a/pkg/apis/psmdb/v1/zz_generated.deepcopy.go b/pkg/apis/psmdb/v1/zz_generated.deepcopy.go index ad638f185e..62f8de5145 100644 --- a/pkg/apis/psmdb/v1/zz_generated.deepcopy.go +++ b/pkg/apis/psmdb/v1/zz_generated.deepcopy.go @@ -602,6 +602,11 @@ func (in *LogCollectorSpec) DeepCopyInto(out *LogCollectorSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.LogRotate != nil { + in, out := &in.LogRotate, &out.LogRotate + *out = new(LogRotateSpec) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogCollectorSpec. @@ -614,6 +619,22 @@ func (in *LogCollectorSpec) DeepCopy() *LogCollectorSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LogRotateSpec) DeepCopyInto(out *LogRotateSpec) { + *out = *in + out.ExtraConfig = in.ExtraConfig +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogRotateSpec. +func (in *LogRotateSpec) DeepCopy() *LogRotateSpec { + if in == nil { + return nil + } + out := new(LogRotateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MinioRetryer) DeepCopyInto(out *MinioRetryer) { *out = *in diff --git a/pkg/controller/perconaservermongodb/psmdb_controller.go b/pkg/controller/perconaservermongodb/psmdb_controller.go index 3581695328..7c50d6959c 100644 --- a/pkg/controller/perconaservermongodb/psmdb_controller.go +++ b/pkg/controller/perconaservermongodb/psmdb_controller.go @@ -40,6 +40,7 @@ import ( "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/backup" psmdbconfig "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/config" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector/logrotate" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/pmm" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/secret" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/tls" @@ -364,6 +365,12 @@ func (r *ReconcilePerconaServerMongoDB) Reconcile(ctx context.Context, request r } } + if cr.CompareVersion("1.22.0") >= 0 { + if err := r.reconcileLogRotateConfigMaps(ctx, cr); err != nil { + return reconcile.Result{}, errors.Wrap(err, "reconcile log rotate config map") + } + } + err = r.reconcileUsers(ctx, cr, repls) if err != nil { return reconcile.Result{}, errors.Wrap(err, "failed to reconcile users") @@ -1263,6 +1270,40 @@ func (r *ReconcilePerconaServerMongoDB) reconcileLogCollectorConfigMaps(ctx cont return nil } +func (r *ReconcilePerconaServerMongoDB) reconcileLogRotateConfigMaps(ctx context.Context, cr *api.PerconaServerMongoDB) error { + if !cr.IsLogCollectorEnabled() { + if err := deleteConfigMapIfExists(ctx, r.client, cr, logrotate.ConfigMapName(cr.Name)); err != nil { + return errors.Wrap(err, "failed to delete log rotate config map when log collector is disabled") + } + return nil + } + + if cr.Spec.LogCollector.LogRotate == nil || cr.Spec.LogCollector.LogRotate.Configuration == "" { + if err := deleteConfigMapIfExists(ctx, r.client, cr, logrotate.ConfigMapName(cr.Name)); err != nil { + return errors.Wrap(err, "failed to delete log rotate config map when the configuration is empty") + } + return nil + } + + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: logrotate.ConfigMapName(cr.Name), + Namespace: cr.GetNamespace(), + Labels: naming.ClusterLabels(cr), + }, + Data: map[string]string{ + logrotate.MongodbConfig: cr.Spec.LogCollector.LogRotate.Configuration, + }, + } + + err := r.createOrUpdateConfigMap(ctx, cr, cm) + if err != nil { + return errors.Wrap(err, "create or update config map") + } + + return nil +} + func deleteConfigMapIfExists(ctx context.Context, cl client.Client, cr *api.PerconaServerMongoDB, cmName string) error { configMap := &corev1.ConfigMap{} diff --git a/pkg/controller/perconaservermongodb/statefulset.go b/pkg/controller/perconaservermongodb/statefulset.go index ddf43ffaee..831aabe5b8 100644 --- a/pkg/controller/perconaservermongodb/statefulset.go +++ b/pkg/controller/perconaservermongodb/statefulset.go @@ -13,7 +13,9 @@ import ( api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" "github.com/percona/percona-server-mongodb-operator/pkg/naming" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb" + psmdbconfig "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/config" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector/logrotate" ) func (r *ReconcilePerconaServerMongoDB) reconcileStatefulSet(ctx context.Context, cr *api.PerconaServerMongoDB, rs *api.ReplsetSpec, ls map[string]string) (*appsv1.StatefulSet, error) { @@ -118,6 +120,21 @@ func (r *ReconcilePerconaServerMongoDB) getStatefulsetFromReplset(ctx context.Co return nil, errors.Wrap(err, "check if log collection custom configuration exists") } + var logRotateCustomConfig, logRotateExtraConfig psmdbconfig.CustomConfig + if cr.CompareVersion("1.22.0") >= 0 { + logRotateCustomConfig, err = r.getCustomConfig(ctx, cr.Namespace, logrotate.ConfigMapName(cr.Name)) + if err != nil { + return nil, errors.Wrap(err, "check if log rotate configuration exists") + } + + if cr.Spec.LogCollector != nil && cr.Spec.LogCollector.LogRotate != nil && cr.Spec.LogCollector.LogRotate.ExtraConfig.Name != "" { + logRotateExtraConfig, err = r.getCustomConfig(ctx, cr.Namespace, cr.Spec.LogCollector.LogRotate.ExtraConfig.Name) + if err != nil { + return nil, errors.Wrap(err, "check if log rotate extra configuration exists") + } + } + } + usersSecret := new(corev1.Secret) err = r.client.Get(ctx, types.NamespacedName{Name: api.UserSecretName(cr), Namespace: cr.Namespace}, usersSecret) if client.IgnoreNotFound(err) != nil { @@ -130,16 +147,20 @@ func (r *ReconcilePerconaServerMongoDB) getStatefulsetFromReplset(ctx context.Co return nil, errors.Wrap(err, "check ssl secrets") } + configs := psmdb.StatefulConfigParams{ + MongoDConf: mongodCustomConfig, + LogCollectionConf: logCollectionCustomConfig, + LogRotateConf: logRotateCustomConfig, + LogRotateExtraConf: logRotateExtraConfig, + } + secrets := psmdb.StatefulSpecSecretParams{ + UsersSecret: usersSecret, + SSLSecret: sslSecret, + } sfsSpec, err := psmdb.StatefulSpec( ctx, cr, rs, ls, r.initImage, - psmdb.StatefulConfigParams{ - MongoDConf: mongodCustomConfig, - LogCollectionConf: logCollectionCustomConfig, - }, - psmdb.StatefulSpecSecretParams{ - UsersSecret: usersSecret, - SSLSecret: sslSecret, - }, + configs, + secrets, ) if err != nil { return nil, errors.Wrapf(err, "create StatefulSet.Spec %s", sfs.Name) diff --git a/pkg/controller/perconaservermongodb/statefulset_test.go b/pkg/controller/perconaservermongodb/statefulset_test.go index 834a1457aa..71c1ee47cd 100644 --- a/pkg/controller/perconaservermongodb/statefulset_test.go +++ b/pkg/controller/perconaservermongodb/statefulset_test.go @@ -13,12 +13,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/yaml" api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" "github.com/percona/percona-server-mongodb-operator/pkg/naming" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector/logrotate" "github.com/percona/percona-server-mongodb-operator/pkg/version" ) @@ -72,11 +74,13 @@ func TestReconcileStatefulSet(t *testing.T) { } tests := []struct { - name string - cr *api.PerconaServerMongoDB - rsName string - component string - ls map[string]string + name string + cr *api.PerconaServerMongoDB + rsName string + component string + ls map[string]string + crUpdate func(cr *api.PerconaServerMongoDB) + additionalObjs []client.Object expectedSts *appsv1.StatefulSet }{ @@ -136,34 +140,84 @@ func TestReconcileStatefulSet(t *testing.T) { component: naming.ComponentHidden, expectedSts: expectedSts(t, "reconcile-statefulset/cfg-hidden.yaml"), }, + { + name: "rs0-logrotate", + cr: defaultCR.DeepCopy(), + rsName: "rs0", + component: naming.ComponentMongod, + expectedSts: expectedSts(t, "reconcile-statefulset/rs0-logrotate.yaml"), + crUpdate: func(cr *api.PerconaServerMongoDB) { + cr.Spec.LogCollector.LogRotate = &api.LogRotateSpec{ + Configuration: "test-config", + ExtraConfig: corev1.LocalObjectReference{ + Name: "extra-config", + }, + Schedule: "0 0 */2 * *", + } + }, + additionalObjs: []client.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: logrotate.ConfigMapName(crName), + Namespace: ns, + }, + Data: map[string]string{ + logrotate.MongodbConfig: "custom-config", + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "extra-config", + Namespace: ns, + }, + Data: map[string]string{ + "custom.conf": "custom-config", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - r := buildFakeClient(tt.cr, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: crName + "-ssl", - Namespace: tt.cr.Namespace, - }, - Data: map[string][]byte{ - "ca.crt": []byte("fake-ca-cert"), - "tls.crt": []byte("fake-tls-cert"), - "tls.key": []byte("fake-tls-key"), - }, - }, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: crName + "-ssl-internal", - Namespace: tt.cr.Namespace, + + mockObjs := []client.Object{ + tt.cr, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName + "-ssl", + Namespace: tt.cr.Namespace, + }, + Data: map[string][]byte{ + "ca.crt": []byte("fake-ca-cert"), + "tls.crt": []byte("fake-tls-cert"), + "tls.key": []byte("fake-tls-key"), + }, }, - }, &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: logcollector.ConfigMapName(tt.cr.Name), - Namespace: tt.cr.Namespace, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName + "-ssl-internal", + Namespace: tt.cr.Namespace, + }, }, - Data: map[string]string{ - "fluentbit_custom.conf": "config", + + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: logcollector.ConfigMapName(tt.cr.Name), + Namespace: tt.cr.Namespace, + }, + Data: map[string]string{ + "fluentbit_custom.conf": "config", + }, }, - }) + } + + mockObjs = append(mockObjs, tt.additionalObjs...) + r := buildFakeClient(mockObjs...) + + if tt.crUpdate != nil { + tt.crUpdate(tt.cr) + } rs := tt.cr.Spec.Replset(tt.rsName) diff --git a/pkg/controller/perconaservermongodb/testdata/reconcile-statefulset/rs0-logrotate.yaml b/pkg/controller/perconaservermongodb/testdata/reconcile-statefulset/rs0-logrotate.yaml new file mode 100644 index 0000000000..13195f6884 --- /dev/null +++ b/pkg/controller/perconaservermongodb/testdata/reconcile-statefulset/rs0-logrotate.yaml @@ -0,0 +1,354 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app.kubernetes.io/component: mongod + app.kubernetes.io/instance: reconcile-statefulset-cr + app.kubernetes.io/managed-by: percona-server-mongodb-operator + app.kubernetes.io/name: percona-server-mongodb + app.kubernetes.io/part-of: percona-server-mongodb + app.kubernetes.io/replset: rs0 + name: reconcile-statefulset-cr-rs0 + namespace: reconcile-statefulset + ownerReferences: + - apiVersion: psmdb.percona.com/v1 + controller: true + kind: PerconaServerMongoDB + name: reconcile-statefulset-cr + uid: "" + resourceVersion: "1" +spec: + replicas: 3 + selector: + matchLabels: + app.kubernetes.io/component: mongod + app.kubernetes.io/instance: reconcile-statefulset-cr + app.kubernetes.io/managed-by: percona-server-mongodb-operator + app.kubernetes.io/name: percona-server-mongodb + app.kubernetes.io/part-of: percona-server-mongodb + app.kubernetes.io/replset: rs0 + serviceName: reconcile-statefulset-cr-rs0 + template: + metadata: + annotations: + percona.com/configuration-hash: e0844d27afffb9c39679f2f44ffe3d7e43d4608f2939c9b53ea870c45ed554cbd5869b9777a1e0b836e3f097b38dd5ab + labels: + app.kubernetes.io/component: mongod + app.kubernetes.io/instance: reconcile-statefulset-cr + app.kubernetes.io/managed-by: percona-server-mongodb-operator + app.kubernetes.io/name: percona-server-mongodb + app.kubernetes.io/part-of: percona-server-mongodb + app.kubernetes.io/replset: rs0 + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: mongod + app.kubernetes.io/instance: reconcile-statefulset-cr + app.kubernetes.io/managed-by: percona-server-mongodb-operator + app.kubernetes.io/name: percona-server-mongodb + app.kubernetes.io/part-of: percona-server-mongodb + app.kubernetes.io/replset: rs0 + topologyKey: kubernetes.io/hostname + containers: + - args: + - --bind_ip_all + - --auth + - --dbpath=/data/db + - --port=27017 + - --replSet=rs0 + - --storageEngine=wiredTiger + - --relaxPermChecks + - --sslAllowInvalidCertificates + - --clusterAuthMode=x509 + - --tlsMode=preferTLS + - --shardsvr + - --enableEncryption + - --encryptionKeyFile=/etc/mongodb-encryption/encryption-key + - --wiredTigerCacheSizeGB=0.25 + - --wiredTigerIndexPrefixCompression=true + - --quiet + command: + - /opt/percona/ps-entry.sh + env: + - name: SERVICE_NAME + value: reconcile-statefulset-cr + - name: NAMESPACE + value: reconcile-statefulset + - name: MONGODB_PORT + value: "27017" + - name: MONGODB_REPLSET + value: rs0 + - name: LOGCOLLECTOR_ENABLED + value: "true" + - name: TEST_ENV1 + value: test-value1 + - name: TEST_ENV2 + value: test-value2 + envFrom: + - secretRef: + name: internal-reconcile-statefulset-cr-users + optional: false + - configMapRef: + name: test-configmap + optional: true + image: perconalab/percona-server-mongodb-operator:main-mongod8.0 + imagePullPolicy: Always + livenessProbe: + exec: + command: + - /opt/percona/mongodb-healthcheck + - k8s + - liveness + - --ssl + - --sslInsecure + - --sslCAFile + - /etc/mongodb-ssl/ca.crt + - --sslPEMKeyFile + - /tmp/tls.pem + - --startupDelaySeconds + - "7200" + failureThreshold: 4 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + name: mongod + ports: + - containerPort: 27017 + name: mongodb + readinessProbe: + exec: + command: + - /opt/percona/mongodb-healthcheck + - k8s + - readiness + - --component + - mongod + failureThreshold: 8 + initialDelaySeconds: 10 + periodSeconds: 3 + successThreshold: 1 + timeoutSeconds: 2 + resources: + limits: + cpu: 600m + memory: 1Gi + requests: + cpu: 300m + memory: 1Gi + securityContext: + runAsNonRoot: true + runAsUser: 1001 + volumeMounts: + - mountPath: /data/db + name: mongod-data + - mountPath: /etc/mongodb-secrets + name: reconcile-statefulset-cr-mongodb-keyfile + readOnly: true + - mountPath: /etc/mongodb-ssl + name: ssl + readOnly: true + - mountPath: /etc/mongodb-ssl-internal + name: ssl-internal + readOnly: true + - mountPath: /opt/percona + name: bin + - mountPath: /.mongodb + name: mongosh + - mountPath: /etc/mongodb-encryption + name: my-cluster-name-mongodb-encryption-key + readOnly: true + - mountPath: /etc/users-secret + name: users-secret-file + workingDir: /data/db + - args: + - pbm-agent-entrypoint + command: + - /opt/percona/pbm-entry.sh + env: + - name: PBM_AGENT_MONGODB_USERNAME + valueFrom: + secretKeyRef: + key: MONGODB_BACKUP_USER_ESCAPED + name: internal-reconcile-statefulset-cr-users + optional: false + - name: PBM_AGENT_MONGODB_PASSWORD + valueFrom: + secretKeyRef: + key: MONGODB_BACKUP_PASSWORD_ESCAPED + name: internal-reconcile-statefulset-cr-users + optional: false + - name: PBM_MONGODB_REPLSET + value: rs0 + - name: PBM_MONGODB_PORT + value: "27017" + - name: PBM_AGENT_SIDECAR + value: "true" + - name: PBM_AGENT_SIDECAR_SLEEP + value: "5" + - name: SHARDED + value: "TRUE" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: PBM_MONGODB_URI + value: mongodb://$(PBM_AGENT_MONGODB_USERNAME):$(PBM_AGENT_MONGODB_PASSWORD)@localhost:$(PBM_MONGODB_PORT)/?tls=true&tlsCertificateKeyFile=/tmp/tls.pem&tlsCAFile=/etc/mongodb-ssl/ca.crt&tlsInsecure=true + - name: PBM_AGENT_TLS_ENABLED + value: "true" + image: perconalab/percona-server-mongodb-operator:main-backup + imagePullPolicy: Always + name: backup-agent + resources: {} + securityContext: + runAsNonRoot: true + runAsUser: 1001 + volumeMounts: + - mountPath: /etc/mongodb-ssl + name: ssl + readOnly: true + - mountPath: /opt/percona + name: bin + readOnly: true + - mountPath: /data/db + name: mongod-data + - name: logs + image: perconalab/fluentbit:main-logcollector + command: [ "/opt/percona/logcollector/entrypoint.sh" ] + args: [ "fluent-bit" ] + env: + - name: LOG_DATA_DIR + value: /data/db/logs + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + resources: + requests: + cpu: 200m + memory: 100M + volumeMounts: + - mountPath: /data/db + name: mongod-data + - mountPath: /opt/percona + name: bin + - mountPath: /opt/percona/logcollector/fluentbit/custom + name: log-collector-volume + - name: logrotate + image: perconalab/fluentbit:main-logcollector + command: [ "/opt/percona/logcollector/entrypoint.sh" ] + args: [ "logrotate" ] + env: + - name: MONGODB_HOST + value: localhost + - name: MONGODB_PORT + value: "27017" + - name: MONGODB_USER + valueFrom: + secretKeyRef: + key: MONGODB_CLUSTER_ADMIN_USER_ESCAPED + name: internal-reconcile-statefulset-cr-users + optional: false + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + key: MONGODB_CLUSTER_ADMIN_PASSWORD_ESCAPED + name: internal-reconcile-statefulset-cr-users + optional: false + - name: LOGROTATE_SCHEDULE + value: "0 0 */2 * *" + resources: + requests: + cpu: 200m + memory: 100M + volumeMounts: + - mountPath: /data/db + name: mongod-data + - mountPath: /opt/percona + name: bin + - mountPath: /opt/percona/logcollector/logrotate/conf.d + name: logrotate-config + initContainers: + - command: + - /init-entrypoint.sh + image: perconalab/percona-server-mongodb-operator:main + imagePullPolicy: Always + name: mongo-init + resources: + limits: + cpu: 600m + memory: 1Gi + requests: + cpu: 300m + memory: 1Gi + volumeMounts: + - mountPath: /data/db + name: mongod-data + - mountPath: /opt/percona + name: bin + restartPolicy: Always + securityContext: + fsGroup: 1001 + serviceAccountName: default + terminationGracePeriodSeconds: 60 + volumes: + - name: reconcile-statefulset-cr-mongodb-keyfile + secret: + defaultMode: 288 + optional: false + secretName: reconcile-statefulset-cr-mongodb-keyfile + - emptyDir: {} + name: bin + - emptyDir: {} + name: mongosh + - configMap: + name: reconcile-statefulset-cr-log-collector-config + optional: true + name: log-collector-volume + - name: logrotate-config + projected: + sources: + - configMap: + name: reconcile-statefulset-cr-logrotate-config + optional: true + - configMap: + name: extra-config + optional: true + - name: my-cluster-name-mongodb-encryption-key + secret: + defaultMode: 288 + optional: false + secretName: my-cluster-name-mongodb-encryption-key + - name: ssl + secret: + defaultMode: 288 + optional: false + secretName: reconcile-statefulset-cr-ssl + - name: ssl-internal + secret: + defaultMode: 288 + optional: true + secretName: reconcile-statefulset-cr-ssl-internal + - name: users-secret-file + secret: + secretName: internal-reconcile-statefulset-cr-users + updateStrategy: + type: OnDelete + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: mongod-data + namespace: reconcile-statefulset + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 3Gi diff --git a/pkg/psmdb/config/config_test.go b/pkg/psmdb/config/config_test.go new file mode 100644 index 0000000000..2291bd7b5f --- /dev/null +++ b/pkg/psmdb/config/config_test.go @@ -0,0 +1,53 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" +) + +func TestVolumeProjection(t *testing.T) { + objectName := "test-object" + localObj := corev1.LocalObjectReference{ + Name: objectName, + } + testCases := []struct { + description string + volumeSource VolumeSourceType + expected corev1.VolumeProjection + }{ + { + description: "VolumeSourceConfigMap", + volumeSource: VolumeSourceConfigMap, + expected: corev1.VolumeProjection{ + ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: localObj, + Optional: ptr.To(true), + }, + }, + }, + { + description: "VolumeSourceSecret", + volumeSource: VolumeSourceSecret, + expected: corev1.VolumeProjection{ + Secret: &corev1.SecretProjection{ + LocalObjectReference: localObj, + Optional: ptr.To(true), + }, + }, + }, + { + description: "VolumeSourceNone", + volumeSource: VolumeSourceNone, + expected: corev1.VolumeProjection{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + assert.Equal(t, tc.expected, tc.volumeSource.VolumeProjection(objectName)) + }) + } +} diff --git a/pkg/psmdb/config/const.go b/pkg/psmdb/config/const.go index e30b1a4e39..4e3431ef4d 100644 --- a/pkg/psmdb/config/const.go +++ b/pkg/psmdb/config/const.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -92,6 +93,30 @@ func (s VolumeSourceType) VolumeSource(name string) corev1.VolumeSource { } } +func (s VolumeSourceType) VolumeProjection(name string) corev1.VolumeProjection { + localObj := corev1.LocalObjectReference{ + Name: name, + } + switch s { + case VolumeSourceConfigMap: + return corev1.VolumeProjection{ + ConfigMap: &corev1.ConfigMapProjection{ + LocalObjectReference: localObj, + Optional: ptr.To(true), + }, + } + case VolumeSourceSecret: + return corev1.VolumeProjection{ + Secret: &corev1.SecretProjection{ + LocalObjectReference: localObj, + Optional: ptr.To(true), + }, + } + default: + return corev1.VolumeProjection{} + } +} + type HashableObject interface { GetRuntimeObject() client.Object GetHashHex() (string, error) diff --git a/pkg/psmdb/logcollector/container.go b/pkg/psmdb/logcollector/container.go index 5249ac383e..3a815c082f 100644 --- a/pkg/psmdb/logcollector/container.go +++ b/pkg/psmdb/logcollector/container.go @@ -2,13 +2,13 @@ package logcollector import ( "fmt" - "strconv" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/config" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector/logrotate" ) const ( @@ -35,7 +35,7 @@ func Containers(cr *api.PerconaServerMongoDB, mongoPort int32) ([]corev1.Contain return nil, err } - logRotationCont, err := logRotationContainer(cr, mongoPort) + logRotationCont, err := logrotate.Container(cr, mongoPort) if err != nil { return nil, err } @@ -108,72 +108,3 @@ func logContainer(cr *api.PerconaServerMongoDB) (*corev1.Container, error) { return &container, nil } - -func logRotationContainer(cr *api.PerconaServerMongoDB, mongoPort int32) (*corev1.Container, error) { - if cr.Spec.LogCollector == nil { - return nil, errors.New("logcollector can't be nil") - } - - boolFalse := false - - usersSecretName := api.UserSecretName(cr) - - envs := []corev1.EnvVar{ - { - Name: "MONGODB_HOST", - Value: "localhost", - }, - { - Name: "MONGODB_PORT", - Value: strconv.Itoa(int(mongoPort)), - }, - { - Name: "MONGODB_USER", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: "MONGODB_CLUSTER_ADMIN_USER_ESCAPED", - LocalObjectReference: corev1.LocalObjectReference{ - Name: usersSecretName, - }, - Optional: &boolFalse, - }, - }, - }, - { - Name: "MONGODB_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: "MONGODB_CLUSTER_ADMIN_PASSWORD_ESCAPED", - LocalObjectReference: corev1.LocalObjectReference{ - Name: usersSecretName, - }, - Optional: &boolFalse, - }, - }, - }, - } - - container := corev1.Container{ - Name: "logrotate", - Image: cr.Spec.LogCollector.Image, - Env: envs, - ImagePullPolicy: cr.Spec.LogCollector.ImagePullPolicy, - SecurityContext: cr.Spec.LogCollector.ContainerSecurityContext, - Resources: cr.Spec.LogCollector.Resources, - Args: []string{ - "logrotate", - }, - Command: []string{"/opt/percona/logcollector/entrypoint.sh"}, - VolumeMounts: []corev1.VolumeMount{ - { - Name: config.MongodDataVolClaimName, - MountPath: config.MongodContainerDataDir, - }, - { - Name: config.BinVolumeName, - MountPath: config.BinMountPath, - }, - }, - } - return &container, nil -} diff --git a/pkg/psmdb/logcollector/container_test.go b/pkg/psmdb/logcollector/container_test.go index 374848beac..54a08bbc7a 100644 --- a/pkg/psmdb/logcollector/container_test.go +++ b/pkg/psmdb/logcollector/container_test.go @@ -9,6 +9,7 @@ import ( api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/config" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector/logrotate" "github.com/percona/percona-server-mongodb-operator/pkg/version" ) @@ -25,8 +26,16 @@ func TestContainers(t *testing.T) { }, }, } + testLogRotate := &api.LogRotateSpec{ + Configuration: "my-logrotate-config", + ExtraConfig: corev1.LocalObjectReference{ + Name: "my-logrotate-extra-config", + }, + Schedule: "0 0 * * *", + } tests := map[string]struct { logCollector *api.LogCollectorSpec + logRotate *api.LogRotateSpec secrets *corev1.Secret expectedContainerNames []string expectedContainers []corev1.Container @@ -47,7 +56,7 @@ func TestContainers(t *testing.T) { ImagePullPolicy: corev1.PullIfNotPresent, }, expectedContainerNames: []string{"logs", "logrotate"}, - expectedContainers: expectedContainers("", nil, nil), + expectedContainers: expectedContainers("", nil, nil, nil), }, "logcollector enabled with configuration": { logCollector: &api.LogCollectorSpec{ @@ -57,7 +66,7 @@ func TestContainers(t *testing.T) { Configuration: "my-config", }, expectedContainerNames: []string{"logs", "logrotate"}, - expectedContainers: expectedContainers("my-config", nil, nil), + expectedContainers: expectedContainers("my-config", nil, nil, nil), }, "logcollector enabled with env variable": { logCollector: &api.LogCollectorSpec{ @@ -69,11 +78,25 @@ func TestContainers(t *testing.T) { EnvFrom: testEnvFrom, }, expectedContainerNames: []string{"logs", "logrotate"}, - expectedContainers: expectedContainers("my-config", testEnvVar, testEnvFrom), + expectedContainers: expectedContainers("my-config", testEnvVar, testEnvFrom, nil), + }, + "logcollector enabled with logrotate": { + logCollector: &api.LogCollectorSpec{ + Enabled: true, + Image: "log-test-image", + ImagePullPolicy: corev1.PullIfNotPresent, + }, + logRotate: testLogRotate, + expectedContainerNames: []string{"logs", "logrotate"}, + expectedContainers: expectedContainers("", nil, nil, testLogRotate), }, } for name, tt := range tests { + logColl := tt.logCollector + if tt.logRotate != nil { + logColl.LogRotate = tt.logRotate + } t.Run(name, func(t *testing.T) { cr := &api.PerconaServerMongoDB{ ObjectMeta: metav1.ObjectMeta{ @@ -82,7 +105,7 @@ func TestContainers(t *testing.T) { }, Spec: api.PerconaServerMongoDBSpec{ CRVersion: version.Version(), - LogCollector: tt.logCollector, + LogCollector: logColl, Secrets: &api.SecretsSpec{ Users: "users-secret", }, @@ -109,7 +132,12 @@ func TestContainers(t *testing.T) { } } -func expectedContainers(configuration string, envVars []corev1.EnvVar, envFrom []corev1.EnvFromSource) []corev1.Container { +func expectedContainers( + configuration string, + envVars []corev1.EnvVar, + envFrom []corev1.EnvFromSource, + logrotateConfig *api.LogRotateSpec, +) []corev1.Container { envs := []corev1.EnvVar{ {Name: "LOG_DATA_DIR", Value: config.MongodContainerDataLogsDir}, { @@ -193,5 +221,20 @@ func expectedContainers(configuration string, envVars []corev1.EnvVar, envFrom [ }, } + if logrotateConfig != nil { + if logrotateConfig.Configuration != "" || logrotateConfig.ExtraConfig.Name != "" { + logRotateC.VolumeMounts = append(logRotateC.VolumeMounts, corev1.VolumeMount{ + Name: logrotate.VolumeName, + MountPath: "/opt/percona/logcollector/logrotate/conf.d", + }) + } + if logrotateConfig.Schedule != "" { + logRotateC.Env = append(logRotateC.Env, corev1.EnvVar{ + Name: "LOGROTATE_SCHEDULE", + Value: logrotateConfig.Schedule, + }) + } + } + return []corev1.Container{logsC, logRotateC} } diff --git a/pkg/psmdb/logcollector/logrotate/container.go b/pkg/psmdb/logcollector/logrotate/container.go new file mode 100644 index 0000000000..87b5113331 --- /dev/null +++ b/pkg/psmdb/logcollector/logrotate/container.go @@ -0,0 +1,111 @@ +package logrotate + +import ( + "errors" + "fmt" + "strconv" + + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + + api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/config" +) + +const ( + ConfigMapNameSuffix = "logrotate-config" + VolumeName = "logrotate-config" + MongodbConfig = "mongodb.conf" + + configDir = "/opt/percona/logcollector/logrotate/conf.d" +) + +func ConfigMapName(prefix string) string { + if prefix == "" { + return ConfigMapNameSuffix + } + return fmt.Sprintf("%s-%s", prefix, ConfigMapNameSuffix) +} + +func Container(cr *api.PerconaServerMongoDB, mongoPort int32) (*corev1.Container, error) { + if cr.Spec.LogCollector == nil { + return nil, errors.New("logcollector can't be nil") + } + + usersSecretName := api.UserSecretName(cr) + + envs := []corev1.EnvVar{ + { + Name: "MONGODB_HOST", + Value: "localhost", + }, + { + Name: "MONGODB_PORT", + Value: strconv.Itoa(int(mongoPort)), + }, + { + Name: "MONGODB_USER", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "MONGODB_CLUSTER_ADMIN_USER_ESCAPED", + LocalObjectReference: corev1.LocalObjectReference{ + Name: usersSecretName, + }, + Optional: ptr.To(false), + }, + }, + }, + { + Name: "MONGODB_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "MONGODB_CLUSTER_ADMIN_PASSWORD_ESCAPED", + LocalObjectReference: corev1.LocalObjectReference{ + Name: usersSecretName, + }, + Optional: ptr.To(false), + }, + }, + }, + } + + container := corev1.Container{ + Name: "logrotate", + Image: cr.Spec.LogCollector.Image, + Env: envs, + ImagePullPolicy: cr.Spec.LogCollector.ImagePullPolicy, + SecurityContext: cr.Spec.LogCollector.ContainerSecurityContext, + Resources: cr.Spec.LogCollector.Resources, + Args: []string{ + "logrotate", + }, + Command: []string{"/opt/percona/logcollector/entrypoint.sh"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: config.MongodDataVolClaimName, + MountPath: config.MongodContainerDataDir, + }, + { + Name: config.BinVolumeName, + MountPath: config.BinMountPath, + }, + }, + } + + if cr.Spec.LogCollector.LogRotate != nil { + if cr.Spec.LogCollector.LogRotate.Configuration != "" || cr.Spec.LogCollector.LogRotate.ExtraConfig.Name != "" { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: VolumeName, + MountPath: configDir, + }) + } + if cr.Spec.LogCollector.LogRotate.Schedule != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "LOGROTATE_SCHEDULE", + Value: cr.Spec.LogCollector.LogRotate.Schedule, + }) + } + } + + return &container, nil +} diff --git a/pkg/psmdb/statefulset.go b/pkg/psmdb/statefulset.go index 3b75c9a2bb..8d08c2a1bc 100644 --- a/pkg/psmdb/statefulset.go +++ b/pkg/psmdb/statefulset.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" @@ -15,6 +16,7 @@ import ( "github.com/percona/percona-server-mongodb-operator/pkg/naming" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/config" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/logcollector/logrotate" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/pmm" ) @@ -41,8 +43,31 @@ type StatefulSpecSecretParams struct { } type StatefulConfigParams struct { - MongoDConf config.CustomConfig - LogCollectionConf config.CustomConfig + MongoDConf config.CustomConfig + LogCollectionConf config.CustomConfig + LogRotateConf config.CustomConfig + LogRotateExtraConf config.CustomConfig +} + +func (p *StatefulConfigParams) HashHex(cr *api.PerconaServerMongoDB) string { + var b strings.Builder + if p.MongoDConf.Type.IsUsable() { + b.WriteString(p.MongoDConf.HashHex) + } + + if cr.CompareVersion("1.21.0") >= 0 && p.LogCollectionConf.Type.IsUsable() { + b.WriteString(p.LogCollectionConf.HashHex) + } + + if cr.CompareVersion("1.22.0") >= 0 { + if p.LogRotateConf.Type.IsUsable() { + b.WriteString(p.LogRotateConf.HashHex) + } + if p.LogRotateExtraConf.Type.IsUsable() { + b.WriteString(p.LogRotateExtraConf.HashHex) + } + } + return b.String() } // StatefulSpec returns spec for stateful set @@ -151,6 +176,13 @@ func StatefulSpec(ctx context.Context, cr *api.PerconaServerMongoDB, replset *ap VolumeSource: configs.LogCollectionConf.Type.VolumeSource(logCollectionConfigName), }) } + if cr.CompareVersion("1.22.0") >= 0 { + vol := logRotateConfigVolume(configs, cr) + if vol != nil { + volumes = append(volumes, *vol) + } + } + encryptionEnabled, err := replset.IsEncryptionEnabled() if err != nil { return appsv1.StatefulSetSpec{}, errors.Wrap(err, "failed to check if encryption is enabled") @@ -217,12 +249,8 @@ func StatefulSpec(ctx context.Context, cr *api.PerconaServerMongoDB, replset *ap annotations = make(map[string]string) } - if configs.MongoDConf.Type.IsUsable() { - annotations["percona.com/configuration-hash"] = configs.MongoDConf.HashHex - } - - if cr.CompareVersion("1.21.0") >= 0 && configs.LogCollectionConf.Type.IsUsable() { - annotations["percona.com/configuration-hash"] = annotations["percona.com/configuration-hash"] + configs.LogCollectionConf.HashHex + if hash := configs.HashHex(cr); hash != "" { + annotations["percona.com/configuration-hash"] = hash } volumeClaimTemplates := []corev1.PersistentVolumeClaim{} @@ -389,6 +417,27 @@ func StatefulSpec(ctx context.Context, cr *api.PerconaServerMongoDB, replset *ap }, nil } +func logRotateConfigVolume(configs StatefulConfigParams, cr *api.PerconaServerMongoDB) *corev1.Volume { + logrotateConfigVolumeProjections := []corev1.VolumeProjection{} + if configs.LogRotateConf.Type.IsUsable() { + logrotateConfigVolumeProjections = append(logrotateConfigVolumeProjections, configs.LogRotateConf.Type.VolumeProjection(logrotate.ConfigMapName(cr.GetName()))) + } + if configs.LogRotateExtraConf.Type.IsUsable() { + logrotateConfigVolumeProjections = append(logrotateConfigVolumeProjections, configs.LogRotateExtraConf.Type.VolumeProjection(cr.Spec.LogCollector.LogRotate.ExtraConfig.Name)) + } + if len(logrotateConfigVolumeProjections) > 0 { + return &corev1.Volume{ + Name: logrotate.VolumeName, + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: logrotateConfigVolumeProjections, + }, + }, + } + } + return nil +} + // backupAgentContainer creates the container object for a backup agent func backupAgentContainer(ctx context.Context, cr *api.PerconaServerMongoDB, replsetName string, port int32, tlsEnabled bool, sslSecret *corev1.Secret) corev1.Container { fvar := false