percona-operator-for-mongodb Hidden 노드 기능 지원

김경준·2025년 10월 26일

소스

깃허브 링크 및 repo의 README 참고 가능

개요

몽고디비의 hidden member

hidden member는 간략히 설명하자면 클라이언트에게 노출되지 않는 멤버다. (ex 데이터 분석용으로 사용 가능)

mongodb에서 read member를 추가할 때, data의 sync 과정이 이루어진다. 그런데 sync 해야하는 데이터량이 너무 크면, 데이터 sync가 끝나지 않았어도 client에서 연결 및 쿼리 결과가 OK로 반환된다.

따라서 read member 노드를 추가할 때 hidden node로 추가하는 방식을 사용할 수 있다. hidden으로 추가한 후, read로 변경할 수 있다

percona operator for mongodb

쿠버네티스 클러스터에서 operator 패턴으로 mongodb 클러스터를 구축해서 사용할 수 있게 해주는 툴이다.
구성 가능한 구성은 2가지다 - 링크

  • replica set 구성 DB Pod 1 DB Pod 3 DB Pod 2 Write Write Client ApplicationMongoDB driver Write Read Read
  • sharding 구성 DB Pod 1 DB Pod 3 DB Pod 2 Read Read Read Write Write Write Write Client Application DB Proxy/Router (mongos)

hidden 기능 추가

비용 절감을 위해 몽고디비도 충분히 Auto Scaling을 적용할 수 있을것 같아서, 프로젝트를 진행한 적이 있는데 데이터가 전부 sync되지 않았는데 client에게 OK query result를 보내서 중지한 적이 있다.
그래서 hidden 노드를 추가하는 기능을 추가하면 어떨가 했는데 작년 24년 9월에 이미 기능추가 요청이 있었다 (링크)
PSMDB Operator v1.21.0 에서 해당 기능이 추가됐다.

테스트

github repo의 README를 참고

kind로 클러스터 생성

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

mongodb 접속 및 상태확인

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',
  }
]

hidden 노드 추가해보기

먼저 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대로 줄여보자. 기본적으로 레플리카셋의 파드는 홀수여야 하므로, unsafeFlasgsreplesetSize 값을 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
  }
]

hidden 멤버를 secondary로 변경하기

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
  }
]

정리 및 결론

  • 쿠버네티스 환경의 특성을 이용해서 몽고디비도 스케일링이 가능할 것 같다.
  • 쿠버네티스 환경에서 몽고디비를 직접 구성해서 관리하기는 어렵기에 Operator를 사용하면 간편하다.
  • Operator를 이용할 때, 오토스케일링을 할 경우 신규 secondary에 데이터 동기화가 안되었음에도 쿼리가 성공하는 이슈가 있었다.
  • psmdb-operator 1.21부터 hidden 노드를 추가할 수 있게 되었다.
  • hidden 노드를 추가한 뒤, 일정 시간 뒤에 rs.conf(), rs.reconfig()를 이용해 hidden 값을 false로 변경해주면 데이터 동기화 이슈를 방지할 수 있다.
  • hidden 멤버 외에도 non-voting 멤버도 존재한다. (예전에는 레플리카셋 3개 노드만 pvc를 붙이고, 오토스케일링으로 스케일아웃되는 파드는 pvc없이 로컬 volume을 사용하는 non-voting으로 진행했었다.)
profile
DevOps로 일하고 있습니다

0개의 댓글