## What this PR does Fixes #2514. When `external: true` is set on a non-sharded `MongoDB`, Cozystack rendered a single `LoadBalancer` Service whose selector matched **all** replica-set members (`app.kubernetes.io/replset: rs0`). Round-robin balancing sent ~⅔ of writes to secondaries and they were rejected with `MongoServerError: not primary`. Drivers couldn't recover on their own either: connecting through the LB returned the in-cluster DNS names of all members from `isMaster`, none of which resolve from outside the cluster, so automatic primary discovery failed. This PR replaces the manual `external-svc.yaml` (for replica-set mode) with the Percona MongoDB operator's native `replsets[].expose` feature: ```yaml replsets: - name: rs0 expose: enabled: true type: LoadBalancer externalTrafficPolicy: Local ``` The operator then: - creates one `LoadBalancer` Service **per pod** (`<release>-rs0-0`, `-1`, `-2`) with `statefulset.kubernetes.io/pod-name` selectors, - watches `Service.status.loadBalancer.ingress` for each pod and rewrites the member `host` in `rs.conf` to the external IPs. External clients use a standard replica-set URI (`mongodb://...:27017,...:27017,...:27017/?replicaSet=rs0`) and the driver discovers the primary correctly from outside the cluster. For **sharded** mode the existing single `LoadBalancer` is preserved — it front-ends stateless `mongos`, which can route writes to the right shard primary itself. `external-svc.yaml` is now scoped to that case only. ### Tested - `helm unittest .` in `packages/apps/mongodb/`: 91/91 pass. - Applied on a dev stand: - `replsets[0].expose` is set as expected in the rendered PSMDB CR. - The Percona operator (`v1.21.1`) accepts the spec and creates 3 per-pod `LoadBalancer` Services with `statefulset.kubernetes.io/pod-name` selectors. The previous single round-robin Service is no longer rendered. - With an LB IP pool present, the operator promotes the allocated LB IPs into the PSMDB `status.host` field and into `rs.conf`. End-to-end mongosh write loop wasn't run on the dev stand because the underlying network does not advertise/route allocated LoadBalancer IPs (no working L2/BGP path), so the rewritten `rs.conf` members are unreachable in that environment. The structural change is otherwise fully verified and the issue's reproduction scenario expects a real LB-capable network. ### Release note ```release-note fix(mongodb): expose each replica-set member through its own LoadBalancer when `external: true`, so external clients can discover the primary via the standard replica-set connection string and writes no longer fail with "not primary" errors. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * External LoadBalancer services now only render when both external and sharding are enabled. * Replica set external exposure is now conditional based on the external setting. * Service configuration now uses LoadBalancer type with Local traffic policy for external access. * Service selectors refined for proper mongos targeting in sharded deployments. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Managed MongoDB Service
MongoDB is a popular document-oriented NoSQL database known for its flexibility and scalability. The Managed MongoDB Service provides a self-healing replicated cluster managed by the Percona Operator for MongoDB.
Deployment Details
This managed service is controlled by the Percona Operator for MongoDB, ensuring efficient management and seamless operation.
- Docs: https://docs.percona.com/percona-operator-for-mongodb/
- Github: https://github.com/percona/percona-server-mongodb-operator
Deployment Modes
Replica Set Mode (default)
By default, MongoDB deploys as a replica set with the specified number of replicas. This mode is suitable for most use cases requiring high availability.
Sharded Cluster Mode
Enable sharding: true for horizontal scaling across multiple shards.
Each shard is a replica set, and mongos routers handle query routing.
Notes
External Access
When external: true is enabled:
- Replica Set mode: Traffic is load-balanced across all replica set members. This works well for read operations, but write operations require connecting to the primary. MongoDB drivers handle primary discovery automatically using the replica set connection string.
- Sharded mode: Traffic is routed through mongos routers, which handle both reads and writes correctly.
Credentials
On first install, the credentials secret will be empty until the Percona operator initializes the cluster.
Run helm upgrade after MongoDB is ready to populate the credentials secret with the actual password.
Data lifecycle
When the MongoDB release is uninstalled, the operator finalizers reclaim release-scoped resources:
Reclaimed by the percona.com/delete-psmdb-pvc finalizer:
- All PVCs backing the replica set storage. Whether the underlying PersistentVolume and on-disk data are actually deleted depends on the StorageClass
reclaimPolicy(Deleteremoves them,Retainleaves them for manual cleanup). - Operator-managed secrets:
<release>-percona-server-mongodb-users— operator users credentialsinternal-<release>— internal operator stateinternal-<release>-users— operator-internal users data<release>-mongodb-encryption-key— at-rest encryption key
Reclaimed by helm uninstall:
<release>-credentials— connection string for application code<release>-user-<username>— per-user passwords<release>-s3-creds— backup destination credentials (if backups are configured)
Not reclaimed automatically:
- TLS secrets
<release>-ssland<release>-ssl-internal(issued by cert-manager) remain in the namespace after uninstall. Delete them manually if no longer needed.
Recovery from a stuck deletion:
If the psmdb-operator is uninstalled before MongoDB CRs are deleted, the finalizers cannot run and the PerconaServerMongoDB CR hangs in Terminating. To recover, clear the finalizers manually:
kubectl --namespace <namespace> patch psmdb <release> --type merge --patch '{"metadata":{"finalizers":[]}}'
Note that this skips the operator-driven cleanup — PVCs and operator-managed secrets will remain orphaned and must be removed manually.
If you need to retain data, take a backup before deletion. Refer to the Percona Operator for MongoDB documentation for backup/restore workflows.
Upgrading from earlier versions
Earlier versions of this chart referenced a namespace-shared system users secret (percona-server-mongodb-users). Upgrading to a release that scopes this secret per CR (<release>-percona-server-mongodb-users) triggers a password rotation for the operator-managed system users. The rotation is performed in place by the Percona operator via db.changeUserPassword() against the running mongod (operator log: Secret data changed. Updating users...); pods are not restarted and the cluster stays available.
Rotated automatically on upgrade:
- The five operator-managed system accounts:
databaseAdmin,userAdmin,backup,clusterAdmin,clusterMonitor. - Secret
<release>-percona-server-mongodb-users(newly created, per-CR) andinternal-<release>-usersreceive the new values. - Secret
<release>-credentialsis regenerated; itspasswordandurikeys reflect the newdatabaseAdminpassword.
Not affected:
- Custom users defined under
users:in chart values. Their<release>-user-<name>secrets are not touched. - The at-rest encryption key (
<release>-mongodb-encryption-key) and replica set keyfile (<release>-mongodb-keyfile) are unchanged, so on-disk data remains readable.
Action required after upgrade:
Workloads that mount <release>-credentials keep using the cached old password until they re-read the secret. Restart those pods, or run a controller such as Reloader to roll them automatically. Without this, application connections fail with authentication errors once their existing sessions expire.
Orphaned legacy secret:
The previous namespace-shared secret percona-server-mongodb-users is no longer referenced by any MongoDB CR after upgrade, but the operator does not garbage-collect it. If multiple MongoDB releases in the same namespace previously shared it, all of them rotate to their own per-CR secrets — passwords are no longer shared across CRs in the namespace, which is the intended outcome. Confirm no other consumers reference it, then remove it manually:
kubectl --namespace <namespace> delete secret percona-server-mongodb-users
Parameters
Common parameters
| Name | Description | Type | Value |
|---|---|---|---|
replicas |
Number of MongoDB replicas in replica set. | int |
3 |
resources |
Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in resourcesPreset is applied. |
object |
{} |
resources.cpu |
CPU available to each replica. | quantity |
"" |
resources.memory |
Memory (RAM) available to each replica. | quantity |
"" |
resourcesPreset |
Default sizing preset used when resources is omitted. |
string |
small |
size |
Persistent Volume Claim size available for application data. | quantity |
10Gi |
storageClass |
StorageClass used to store the data. | string |
"" |
external |
Enable external access from outside the cluster. | bool |
false |
version |
MongoDB major version to deploy. | string |
v8 |
Sharding configuration
| Name | Description | Type | Value |
|---|---|---|---|
sharding |
Enable sharded cluster mode. When disabled, deploys a replica set. | bool |
false |
shardingConfig |
Configuration for sharded cluster mode. | object |
{} |
shardingConfig.configServers |
Number of config server replicas. | int |
3 |
shardingConfig.configServerSize |
PVC size for config servers. | quantity |
3Gi |
shardingConfig.mongos |
Number of mongos router replicas. | int |
2 |
shardingConfig.shards |
List of shard configurations. | []object |
[...] |
shardingConfig.shards[i].name |
Shard name. | string |
"" |
shardingConfig.shards[i].replicas |
Number of replicas in this shard. | int |
0 |
shardingConfig.shards[i].size |
PVC size for this shard. | quantity |
"" |
Users configuration
| Name | Description | Type | Value |
|---|---|---|---|
users |
Users configuration map. | map[string]object |
{} |
users[name].password |
Password for the user (auto-generated if omitted). | string |
"" |
Databases configuration
| Name | Description | Type | Value |
|---|---|---|---|
databases |
Databases configuration map. | map[string]object |
{} |
databases[name].roles |
Roles assigned to users. | object |
{} |
databases[name].roles.admin |
List of users with admin privileges (readWrite + dbAdmin). | []string |
[] |
databases[name].roles.readonly |
List of users with read-only privileges. | []string |
[] |
Backup parameters
| Name | Description | Type | Value |
|---|---|---|---|
backup |
Backup configuration. | object |
{} |
backup.enabled |
Enable regular backups. | bool |
false |
backup.schedule |
Cron schedule for automated backups. | string |
0 2 * * * |
backup.retentionPolicy |
Retention policy (e.g. "30d"). | string |
30d |
backup.destinationPath |
Destination path for backups (e.g. s3://bucket/path/). | string |
s3://bucket/path/to/folder/ |
backup.endpointURL |
S3 endpoint URL for uploads. | string |
http://minio-gateway-service:9000 |
backup.s3AccessKey |
Access key for S3 authentication. | string |
"" |
backup.s3SecretKey |
Secret key for S3 authentication. | string |
"" |
Bootstrap (recovery) parameters
| Name | Description | Type | Value |
|---|---|---|---|
bootstrap |
Bootstrap configuration. | object |
{} |
bootstrap.enabled |
Whether to restore from a backup. | bool |
false |
bootstrap.recoveryTime |
Timestamp for point-in-time recovery; empty means latest. | string |
"" |
bootstrap.backupName |
Name of backup to restore from. | string |
"" |