깃허브 링크 및 repo의 README 참고 가능
hidden member는 간략히 설명하자면 클라이언트에게 노출되지 않는 멤버다. (ex 데이터 분석용으로 사용 가능)
mongodb에서 read member를 추가할 때, data의 sync 과정이 이루어진다. 그런데 sync 해야하는 데이터량이 너무 크면, 데이터 sync가 끝나지 않았어도 client에서 연결 및 쿼리 결과가 OK로 반환된다.
따라서 read member 노드를 추가할 때 hidden node로 추가하는 방식을 사용할 수 있다. hidden으로 추가한 후, read로 변경할 수 있다
쿠버네티스 클러스터에서 operator 패턴으로 mongodb 클러스터를 구축해서 사용할 수 있게 해주는 툴이다.
구성 가능한 구성은 2가지다 - 링크
비용 절감을 위해 몽고디비도 충분히 Auto Scaling을 적용할 수 있을것 같아서, 프로젝트를 진행한 적이 있는데 데이터가 전부 sync되지 않았는데 client에게 OK query result를 보내서 중지한 적이 있다.
그래서 hidden 노드를 추가하는 기능을 추가하면 어떨가 했는데 작년 24년 9월에 이미 기능추가 요청이 있었다 (링크)
PSMDB Operator v1.21.0 에서 해당 기능이 추가됐다.
github repo의 README를 참고
cluster.yaml 파일 생성
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.32.8
# port forward 27017 on the host to 27017 on this node
extraPortMappings:
- containerPort: 27017
hostPort: 27017
# optional: set the bind address on the host
# 0.0.0.0 is the current default
listenAddress: "0.0.0.0"
# optional: set the protocol to one of TCP, UDP, SCTP.
# TCP is the default
protocol: TCP
- role: worker
image: kindest/node:v1.32.8
labels:
topology.kubernetes.io/zone: ap-northeast-2a
- role: worker
image: kindest/node:v1.32.8
labels:
topology.kubernetes.io/zone: ap-northeast-2b
- role: worker
image: kindest/node:v1.32.8
labels:
topology.kubernetes.io/zone: ap-northeast-2c
namespace 생성
kubectl create ns mongodb
helm repo 설치
helm repo add percona https://percona.github.io/percona-helm-charts/
helm repo update
psmdb operator 설치 (테스트 당시 최신버전 1.21.0)
helm search repo percona/psmdb-operator --versions
helm pull percona/psmdb-operator --version 1.21.0
tar xvf psmdb-operator-1.21.0.tgz
helm install psmdb-operator charts/psmdb-operator -f values/psmdb-operator/values.yaml
psmdb db 설치
helm search repo percona/psmdb-db --versions
helm pull percona/psmdb-db --version 1.21.0
tar xvf psmdb-db-1.21.0.tgz
helm install cluster-1 charts/psmdb-db -f values/psmdb-db/values.yaml
psmdb를 기본 설정으로 설치하면, 여러 secret이 생성된다.
kubectl get secret
NAME TYPE DATA AGE
cluster-1-psmdb-db-mongodb-encryption-key Opaque 1 6m2s
cluster-1-psmdb-db-mongodb-keyfile Opaque 1 6m2s
cluster-1-psmdb-db-secrets Opaque 10 6m2s
cluster-1-psmdb-db-ssl kubernetes.io/tls 3 6m2s
cluster-1-psmdb-db-ssl-internal kubernetes.io/tls 3 6m2s
internal-cluster-1-psmdb-db-users Opaque 20 6m2s
sh.helm.release.v1.cluster-1.v1 helm.sh/release.v1 1 6m2s
sh.helm.release.v1.psmdb-operator.v1 helm.sh/release.v1 1 7m42s
여기서 접속에 필요한 정보는 <클러스터 이름>-psmdb-db-secrets에서 확인할 수 있다. (MONGODB_CLUSTER_ADMIN_USER)
kubectl get secret cluster-1-psmdb-db-secrets -o go-template='{{range $k,$v := .data}}{{printf "%s: " $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{"\n"}}{{end}}'
MONGODB_BACKUP_PASSWORD: xjDLAXAfaEndA1W8w
MONGODB_BACKUP_USER: backup
MONGODB_CLUSTER_ADMIN_PASSWORD: ShdEOvY3nSLB1LXElmr
MONGODB_CLUSTER_ADMIN_USER: clusterAdmin
MONGODB_CLUSTER_MONITOR_PASSWORD: FLm5VAFHutGNyLOgE
MONGODB_CLUSTER_MONITOR_USER: clusterMonitor
MONGODB_DATABASE_ADMIN_PASSWORD: itUSHr20UuUo2M4Ih
MONGODB_DATABASE_ADMIN_USER: databaseAdmin
MONGODB_USER_ADMIN_PASSWORD: NbcMoEkJe7NeQ2QrVIC
MONGODB_USER_ADMIN_USER: userAdmin
실행된 pod를 확인하고
kubectl get pod
NAME READY STATUS RESTARTS AGE
cluster-1-psmdb-db-rs0-0 1/1 Running 0 6m29s
cluster-1-psmdb-db-rs0-1 1/1 Running 0 5m38s
cluster-1-psmdb-db-rs0-2 1/1 Running 0 4m54s
psmdb-operator-7d9c58954c-x9dzd 1/1 Running 0 8m9s
파드 하나에 접속한다. 0번 파드가 웬만하면 primary일 것이다.
kubectl exec -it cluster-1-psmdb-db-rs0-0 -- mongosh "mongodb://clusterAdmin:ShdEOvY3nSLB1LXElmr@localhost:27017/admin"
Defaulted container "mongod" out of: mongod, mongo-init (init)
Current Mongosh Log ID: 68fdc70b19fc7f4ffc248377
Connecting to: mongodb://<credentials>@localhost:27017/admin?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.5.6
Using MongoDB: 8.0.12-4
Using Mongosh: 2.5.6
mongosh 2.5.8 is available for download: https://www.mongodb.com/try/download/shell
For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/
rs0 [direct: primary] admin>
rs.status()로 현재 레플리카셋의 상태를 확인할 수 있다
rs0 [direct: primary] admin> rs.status().members
[
{
_id: 0,
name: 'cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
},
{
_id: 1,
name: 'cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
},
{
_id: 2,
name: 'cluster-1-psmdb-db-rs0-2.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
}
]
먼저 CR 목록에서 현재 설치된 psmdb-db 상태를 확인할 수 있다.
kubectl get perconaservermongodbs.psmdb.percona.com
NAME ENDPOINT STATUS AGE
cluster-1-psmdb-db cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local ready 18m
yaml로 출력하면 아래와 같다.
kubectl get perconaservermongodbs.psmdb.percona.com -oyaml
apiVersion: v1
items:
- apiVersion: psmdb.percona.com/v1
kind: PerconaServerMongoDB
metadata:
annotations:
meta.helm.sh/release-name: cluster-1
meta.helm.sh/release-namespace: mongodb
creationTimestamp: "2025-10-26T06:49:04Z"
finalizers:
- percona.com/delete-psmdb-pods-in-order
generation: 1
labels:
app.kubernetes.io/instance: cluster-1
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: psmdb-db
app.kubernetes.io/version: 1.21.0
helm.sh/chart: psmdb-db-1.21.0
name: cluster-1-psmdb-db
namespace: mongodb
resourceVersion: "2733"
uid: 4fc410d9-b4d7-47b8-bb0f-03ea8c8056a4
spec:
backup:
enabled: false
image: percona/percona-backup-mongodb:2.11.0
pitr:
enabled: false
crVersion: 1.21.0
enableVolumeExpansion: false
image: percona/percona-server-mongodb:8.0.12-4
imagePullPolicy: Always
logcollector:
enabled: false
image: percona/fluentbit:4.0.1
resources:
requests:
cpu: 200m
memory: 100M
multiCluster:
enabled: false
pause: false
pmm:
enabled: false
image: percona/pmm-client:3.4.1
serverHost: monitoring-service
replsets:
- affinity:
antiAffinityTopologyKey: kubernetes.io/hostname
arbiter:
affinity:
antiAffinityTopologyKey: kubernetes.io/hostname
enabled: false
resources:
limits:
cpu: 600m
memory: 1Gi
requests:
cpu: 300m
memory: 1Gi
size: 1
expose:
enabled: false
type: ClusterIP
hidden:
affinity:
antiAffinityTopologyKey: kubernetes.io/hostname
enabled: false
podDisruptionBudget:
maxUnavailable: 1
resources:
limits:
cpu: 600m
memory: 1Gi
requests:
cpu: 300m
memory: 1Gi
size: 2
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 3Gi
name: rs0
nonvoting:
affinity:
antiAffinityTopologyKey: kubernetes.io/hostname
enabled: false
podDisruptionBudget:
maxUnavailable: 1
resources:
limits:
cpu: 600m
memory: 1Gi
requests:
cpu: 300m
memory: 1Gi
size: 3
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 3Gi
podDisruptionBudget:
maxUnavailable: 1
resources:
limits:
cpu: 600m
memory: 1Gi
requests:
cpu: 300m
memory: 1Gi
size: 3
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 3Gi
secrets:
users: cluster-1-psmdb-db-secrets
sharding:
balancer:
enabled: true
configsvrReplSet:
affinity:
antiAffinityTopologyKey: kubernetes.io/hostname
expose:
enabled: false
type: ClusterIP
podDisruptionBudget:
maxUnavailable: 1
resources:
limits:
cpu: 600m
memory: 1Gi
requests:
cpu: 300m
memory: 1Gi
size: 3
volumeSpec:
persistentVolumeClaim:
resources:
requests:
storage: 3Gi
enabled: false
mongos:
affinity:
antiAffinityTopologyKey: kubernetes.io/hostname
expose:
type: ClusterIP
podDisruptionBudget:
maxUnavailable: 1
resources:
limits:
cpu: 600m
memory: 1Gi
requests:
cpu: 300m
memory: 1Gi
size: 3
unmanaged: false
unsafeFlags:
backupIfUnhealthy: false
mongosSize: false
replsetSize: false
terminationGracePeriod: false
tls: false
updateStrategy: SmartUpdate
upgradeOptions:
apply: disabled
schedule: 0 2 * * *
setFCV: false
versionServiceEndpoint: https://check.percona.com
이제 설치할 때 사용한 helm values 파일을 수정해서 적용해본다. 먼저 레플리카셋 멤버를 3대에서 2대로 줄여보자. 기본적으로 레플리카셋의 파드는 홀수여야 하므로, unsafeFlasgs의 replesetSize 값을 true로 변경해준다.
replsets:
rs0:
name: rs0
size: 2 # 3에서 2로 변경
unsafeFlags:
tls: false
replsetSize: true
수정 사항을 helm diff로 확인해본 뒤, 적용한다.
helm diff upgrade cluster-1 charts/psmdb-db -f values/psmdb-db/values.yaml | grep -E '^(\+|-)'
- size: 3
+ size: 2
mongosh 쉘에 접속해서 보면, 3번째 rs0 멤버가 제거된 것을 확인할 수 있다.
rs0 [direct: primary] admin> rs.status().members
[
{
_id: 0,
name: 'cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
},
{
_id: 1,
name: 'cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
}
]
이제 hidden 노드 추가를 위해 아래와 같이 values 파일을 수정해주자.
hidden:
enabled: true
# podSecurityContext: {}
# containerSecurityContext: {}
size: 1
변경사항을 적용한 후, hidden 파드가 추가되는 것을 확인할 수 있다.
kubectl get pod
NAME READY STATUS RESTARTS AGE
cluster-1-psmdb-db-rs0-0 1/1 Running 0 34m
cluster-1-psmdb-db-rs0-1 1/1 Running 0 33m
cluster-1-psmdb-db-rs0-hidden-0 0/1 Running 0 11s
psmdb-operator-7d9c58954c-x9dzd 1/1 Running 0 36m
실제 rs.conf()에서 확인해보면 hidden 값이 true임을 확인할 수 있다.
rs0 [direct: primary] admin> rs.conf().members
[
{
_id: 0,
host: 'cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 2,
tags: {
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker',
podName: 'cluster-1-psmdb-db-rs0-0'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 1,
host: 'cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 2,
tags: {
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker3',
podName: 'cluster-1-psmdb-db-rs0-1'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 2,
host: 'cluster-1-psmdb-db-rs0-hidden-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: true,
priority: 0,
tags: {
podName: 'cluster-1-psmdb-db-rs0-hidden-0',
serviceName: 'cluster-1-psmdb-db',
hidden: 'true',
nodeName: '1.32-worker2'
},
secondaryDelaySecs: Long('0'),
votes: 1
}
]
mongosh 쉘에 접근한 상태에서 rs.conf() 를 변수에 저장한다.
rs0 [direct: primary] admin> cfg = rs.conf()
hidden 값을 변경한다.
rs0 [direct: primary] admin> cfg.members[2].hidden=false
false
cfg 설정을 적용한다.
rs0 [direct: primary] admin> rs.reconfig(cfg)
rs.conf() 확인
rs0 [direct: primary] admin> rs.conf().members
[
{
_id: 0,
host: 'cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 2,
tags: {
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker',
podName: 'cluster-1-psmdb-db-rs0-0'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 1,
host: 'cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 2,
tags: {
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker3',
podName: 'cluster-1-psmdb-db-rs0-1'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 2,
host: 'cluster-1-psmdb-db-rs0-hidden-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 0,
tags: {
podName: 'cluster-1-psmdb-db-rs0-hidden-0',
serviceName: 'cluster-1-psmdb-db',
hidden: 'true',
nodeName: '1.32-worker2'
},
secondaryDelaySecs: Long('0'),
votes: 1
}
]
위 상태에서 다시 replset 개수를 3으로 변경해본다.
replsets:
rs0:
name: rs0
size: 3
---
helm upgrade cluster-1 charts/psmdb-db -f values/psmdb-db/values.yaml
파드가 총 4개가 된 것을 확인할 수 있다.
kubectl get pod
NAME READY STATUS RESTARTS AGE
cluster-1-psmdb-db-rs0-0 1/1 Running 0 88m
cluster-1-psmdb-db-rs0-1 1/1 Running 0 87m
cluster-1-psmdb-db-rs0-2 1/1 Running 0 102s
cluster-1-psmdb-db-rs0-hidden-0 1/1 Running 0 54m
psmdb-operator-7d9c58954c-x9dzd 1/1 Running 0 90m
그리고 rs.conf()를 확인해보면, hidden이 전부 false인 것을 확인할 수 있다. 여기서 rs.conf()와 CR로 적용되는 값은 별도로 유지되는 것을 확인할 수 있다.
rs0 [direct: primary] admin> rs.conf().members
[
{
_id: 0,
host: 'cluster-1-psmdb-db-rs0-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 2,
tags: {
podName: 'cluster-1-psmdb-db-rs0-0',
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 1,
host: 'cluster-1-psmdb-db-rs0-1.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 2,
tags: {
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker3',
podName: 'cluster-1-psmdb-db-rs0-1'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 2,
host: 'cluster-1-psmdb-db-rs0-hidden-0.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 0,
tags: {
serviceName: 'cluster-1-psmdb-db',
hidden: 'true',
nodeName: '1.32-worker2',
podName: 'cluster-1-psmdb-db-rs0-hidden-0'
},
secondaryDelaySecs: Long('0'),
votes: 1
},
{
_id: 3,
host: 'cluster-1-psmdb-db-rs0-2.cluster-1-psmdb-db-rs0.mongodb.svc.cluster.local:27017',
arbiterOnly: false,
buildIndexes: true,
hidden: false,
priority: 0,
tags: {
serviceName: 'cluster-1-psmdb-db',
nodeName: '1.32-worker2',
podName: 'cluster-1-psmdb-db-rs0-2'
},
secondaryDelaySecs: Long('0'),
votes: 0
}
]
rs.conf(), rs.reconfig()를 이용해 hidden 값을 false로 변경해주면 데이터 동기화 이슈를 방지할 수 있다.non-voting 멤버도 존재한다. (예전에는 레플리카셋 3개 노드만 pvc를 붙이고, 오토스케일링으로 스케일아웃되는 파드는 pvc없이 로컬 volume을 사용하는 non-voting으로 진행했었다.)