LabHub

블로그

K8s에서 DB 운영하기 — CNPG, Percona, Vitess, Helm 차트 완전 가이드

한국어English日本語中文

들어가며

Kubernetes에서 데이터베이스를 운영하는 것은 몇 년 전만 해도 논쟁적인 주제였습니다. "Stateless 워크로드에 최적화된 K8s에서 왜 굳이 DB를?" 라는 질문이 많았죠. 하지만 2026년 현재, StatefulSet과 Operator 생태계가 충분히 성숙하면서 K8s 위에서 DB를 운영하는 것이 표준적인 선택지가 되었습니다.

이 글에서는 주요 데이터베이스 Operator들과 Helm 차트를 비교하고, 실전에서 어떤 도구를 어떤 상황에 써야 하는지 정리합니다.


1. 왜 K8s에서 DB를 운영하는가

장점

단점

StatefulSet 기초

StatefulSet은 K8s에서 상태 유지가 필요한 워크로드를 위한 컨트롤러입니다. 일반 Deployment와 달리 다음을 보장합니다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ['ReadWriteOnce']
        storageClassName: gp3
        resources:
          requests:
            storage: 50Gi

하지만 StatefulSet만으로는 HA, 자동 Failover, 백업/복구, 모니터링 등을 구현하기 어렵습니다. 이 부분을 해결해주는 것이 바로 Operator 패턴입니다.


2. CloudNativePG (CNPG) — PostgreSQL 전용 Operator

개요

CloudNativePG(CNPG)는 EDB(EnterpriseDB)에서 개발을 시작하여 현재 CNCF Sandbox 프로젝트로 관리되는 PostgreSQL 전용 Kubernetes Operator입니다. 2026년 4월 기준 최신 버전은 1.29이며, PostgreSQL 확장 관리를 Image Catalog과 artifacts 생태계로 혁신적으로 개선했습니다.

핵심 특징

설치

Helm을 사용한 설치가 가장 간편합니다.

# Helm 리포지토리 추가
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update

# Operator 설치
helm upgrade --install cnpg \
  --namespace cnpg-system \
  --create-namespace \
  cnpg/cloudnative-pg

또는 매니페스트로 직접 설치할 수도 있습니다.

kubectl apply --server-side -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.0.yaml

HA 아키텍처

CNPG는 Primary 1대 + Standby N대 구조입니다. Primary가 쓰기를 담당하고, Standby는 스트리밍 복제로 데이터를 동기화합니다.

백업 및 복구

CNPG는 Barman 기반의 지속적 백업을 내장하고 있습니다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: prod-pg
spec:
  instances: 3
  storage:
    size: 100Gi
    storageClass: gp3
  backup:
    barmanObjectStore:
      destinationPath: s3://my-backup-bucket/prod-pg/
      s3Credentials:
        accessKeyId:
          name: aws-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: aws-creds
          key: SECRET_ACCESS_KEY
      wal:
        compression: gzip
    retentionPolicy: '30d'

ScheduledBackup으로 정기 백업을 예약할 수 있습니다.

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: prod-pg-daily
spec:
  schedule: '0 2 * * *'
  cluster:
    name: prod-pg
  backupOwnerReference: self

3. Percona Operator — 멀티 DB 지원

개요

Percona는 MySQL, MongoDB, PostgreSQL 세 가지 데이터베이스에 대해 각각 전용 Kubernetes Operator를 제공합니다. Apache 2.0 라이선스로 완전 오픈소스이며, 엔터프라이즈급 기능을 무료로 사용할 수 있습니다.

지원 DB별 특징

Percona Operator for MySQL (PXC)

Percona Operator for MongoDB (PSMDB)

Percona Operator for PostgreSQL (PPG)

통합 모니터링: PMM

Percona Monitoring and Management(PMM)는 MySQL, MongoDB, PostgreSQL 전체를 하나의 대시보드에서 모니터링할 수 있는 도구입니다.

apiVersion: pxc.percona.com/v1
kind: PerconaXtraDBCluster
metadata:
  name: prod-mysql
spec:
  crVersion: '1.15.0'
  pxc:
    size: 3
    image: percona/percona-xtradb-cluster:8.0
    resources:
      requests:
        memory: 2Gi
        cpu: '1'
    volumeSpec:
      persistentVolumeClaim:
        storageClassName: gp3
        resources:
          requests:
            storage: 100Gi
  haproxy:
    enabled: true
    size: 2
  pmm:
    enabled: true
    serverHost: monitoring-service
  backup:
    schedule:
      - name: daily-backup
        schedule: '0 3 * * *'
        keep: 7
        storageName: s3-backup
    storages:
      s3-backup:
        type: s3
        s3:
          bucket: my-backup-bucket
          credentialsSecret: aws-creds
          region: ap-northeast-2

멀티 클러스터 지원

Percona Operator는 크로스 리전 복제를 지원하여 여러 K8s 클러스터 간에 데이터를 동기화할 수 있습니다. 이를 통해 재해 복구(DR) 구성이 가능합니다.


4. Vitess — MySQL 수평 샤딩

개요

Vitess는 YouTube에서 대규모 MySQL 워크로드를 처리하기 위해 개발되었으며, 현재 CNCF Graduated 프로젝트입니다. MySQL과 호환되는 인터페이스를 제공하면서 투명한 샤딩, 커넥션 풀링, 온라인 리샤딩을 지원합니다.

PlanetScale은 Vitess를 상용화한 대표적인 DBaaS 서비스입니다.

아키텍처 구성 요소

구성 요소역할
VTGate쿼리 라우터, 애플리케이션이 연결하는 엔드포인트
VTTablet각 MySQL 인스턴스를 감싸는 프록시
Topology Service클러스터 메타데이터 저장 (etcd 등)
VTOrcOrchestrator, 자동 Failover 담당
VTAdmin웹 기반 관리 UI

언제 Vitess를 선택해야 하는가

Vitess on K8s 설치

PlanetScale에서 제공하는 Vitess Operator를 사용합니다.

# Vitess Operator 설치
kubectl apply -f https://github.com/planetscale/vitess-operator/releases/latest/download/operator.yaml

Keyspace(논리적 데이터베이스)와 Shard를 선언적으로 관리합니다.

apiVersion: planetscale.com/v2
kind: VitessCluster
metadata:
  name: prod-vitess
spec:
  images:
    vtgate: vitess/lite:v19
    vttablet: vitess/lite:v19
    vtbackup: vitess/lite:v19
    vtctld: vitess/lite:v19
    vtorc: vitess/lite:v19
  cells:
    - name: zone1
      gateway:
        replicas: 2
        resources:
          requests:
            cpu: '1'
            memory: 1Gi
  keyspaces:
    - name: commerce
      turndownPolicy: Immediate
      partitionings:
        - equal:
            parts: 2
            shardTemplate:
              databaseInitScriptSecret:
                name: commerce-schema
                key: init.sql
              tabletPools:
                - cell: zone1
                  type: replica
                  replicas: 3
                  dataVolumeClaimTemplate:
                    storageClassName: gp3
                    resources:
                      requests:
                        storage: 50Gi

주의사항

Vitess는 매우 강력하지만 학습 곡선이 가파릅니다. 단순한 CRUD 애플리케이션에는 과도한 선택일 수 있으며, 샤딩 전략(Vschema) 설계에 충분한 검토가 필요합니다.


5. 주요 Helm 차트 비교

Operator 없이 Helm 차트만으로도 K8s에 DB를 배포할 수 있습니다. Bitnami가 가장 널리 사용되는 Helm 차트 제공자였으나, 2025년 이후 중요한 변화가 있습니다.

Bitnami 라이선스 변경 (2025)

2025년 9월 이후 대부분의 Bitnami Helm 차트 OCI 패키지가 Broadcom 유료 구독 뒤로 이동했습니다. 공개 docker.io/bitnami 이미지는 "Bitnami Legacy" 저장소로 옮겨져 더 이상 업데이트, 수정, 보안 패치를 받지 않습니다.

대안으로 Chainguard에서 Bitnami를 포크한 40개 이상의 보안 강화 Helm 차트를 제공하고 있으며, 커뮤니티 기반 대안도 늘어나는 추세입니다.

주요 Helm 차트 비교표

차트DB기본 구성HA 지원백업 내장비고
bitnami/postgresqlPostgreSQLPrimary + Read ReplicaRepmgr 기반X레거시 주의
bitnami/postgresql-haPostgreSQLPrimary + StandbyPgpool-II 연동XHA 전용 차트
bitnami/mysqlMySQLPrimary + Secondary반동기 복제XInnoDB Cluster 옵션
bitnami/redisRedisMaster + ReplicaSentinel 기반XCluster 모드 별도
bitnami/mongodbMongoDBReplicaSet내장XSharded 별도 차트
bitnami/mariadbMariaDBPrimary + SecondaryGalera 옵션XMySQL 호환

Helm 차트 사용이 적합한 경우

예시: PostgreSQL HA Helm 차트

helm install prod-pg bitnami/postgresql-ha \
  --set postgresql.replicaCount=3 \
  --set postgresql.resources.requests.memory=2Gi \
  --set postgresql.resources.requests.cpu=1 \
  --set persistence.size=100Gi \
  --set persistence.storageClass=gp3 \
  --set pgpool.replicaCount=2 \
  --set metrics.enabled=true

6. CNPG vs Percona vs Vitess vs Helm 차트 종합 비교

기능 비교표

항목CNPGPerconaVitessHelm 차트
지원 DBPostgreSQLMySQL, MongoDB, PGMySQL (샤딩)다양
라이선스Apache 2.0Apache 2.0Apache 2.0차트별 상이
CNCF 상태Sandbox-Graduated-
HA 자동 FailoverOOO차트에 따라 다름
자동 백업O (Barman)O (다중 스토리지)OX (별도 구성)
PITROO부분 지원X
수평 샤딩XMongoDB만O (핵심 기능)X
모니터링 통합PrometheusPMM + PrometheusVTAdmin차트별 메트릭
커넥션 풀링PgBouncer 내장ProxySQL/HAProxyVTGate 내장별도 구성
운영 난이도
프로덕션 적합도높음높음높음 (대규모)중간
CRD 기반 관리OOOX

선택 가이드


7. 운영 유의점

스토리지 (PV/PVC)

스토리지는 K8s DB 운영에서 가장 중요한 요소입니다.

# 권장 StorageClass 예시 (AWS EBS gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-db
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: '5000'
  throughput: '250'
  encrypted: 'true'
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Retain

핵심 원칙:

성능 튜닝

# Pod에 CPU pinning 및 NUMA 인식 설정 예시
spec:
  containers:
    - name: postgres
      resources:
        requests:
          cpu: '4'
          memory: 8Gi
        limits:
          cpu: '4'
          memory: 8Gi
      # Guaranteed QoS 클래스 확보
      # requests == limits로 설정

추가 팁:

spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: app
                operator: In
                values:
                  - postgres
          topologyKey: kubernetes.io/hostname

리소스 제한

모니터링

# CNPG에서 PodMonitor 활성화
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: prod-pg
spec:
  instances: 3
  monitoring:
    enablePodMonitor: true
    customQueriesConfigMap:
      - name: pg-custom-queries
        key: queries
  storage:
    size: 100Gi

필수 모니터링 지표:

백업 전략

3-2-1 백업 규칙을 K8s 환경에 적용합니다.

# CNPG 복구 클러스터 예시
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: recovery-cluster
spec:
  instances: 2
  storage:
    size: 100Gi
  bootstrap:
    recovery:
      source: prod-pg
      recoveryTarget:
        targetTime: '2026-04-10T08:00:00Z'
  externalClusters:
    - name: prod-pg
      barmanObjectStore:
        destinationPath: s3://my-backup-bucket/prod-pg/
        s3Credentials:
          accessKeyId:
            name: aws-creds
            key: ACCESS_KEY_ID
          secretAccessKey:
            name: aws-creds
            key: SECRET_ACCESS_KEY

8. 실전 예제: CNPG로 PostgreSQL HA 클러스터 구성

실제 프로덕션 환경에서 사용할 수 있는 CNPG 클러스터 전체 YAML입니다.

Step 1: Namespace 및 Secret 생성

kubectl create namespace database

kubectl create secret generic pg-superuser \
  --namespace database \
  --from-literal=username=postgres \
  --from-literal=password=CHANGE_ME_TO_STRONG_PASSWORD

kubectl create secret generic aws-creds \
  --namespace database \
  --from-literal=ACCESS_KEY_ID=your-access-key \
  --from-literal=SECRET_ACCESS_KEY=your-secret-key

Step 2: PostgreSQL 클러스터 정의

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: prod-pg
  namespace: database
spec:
  description: 'Production PostgreSQL HA Cluster'
  imageName: ghcr.io/cloudnative-pg/postgresql:16.4

  instances: 3

  startDelay: 30
  stopDelay: 30
  primaryUpdateStrategy: unsupervised

  postgresql:
    parameters:
      shared_buffers: '2GB'
      effective_cache_size: '6GB'
      work_mem: '64MB'
      maintenance_work_mem: '512MB'
      max_connections: '200'
      max_wal_size: '2GB'
      min_wal_size: '1GB'
      wal_buffers: '64MB'
      random_page_cost: '1.1'
      effective_io_concurrency: '200'
      log_statement: 'ddl'
      log_min_duration_statement: '1000'
    pg_hba:
      - host all all 10.0.0.0/8 scram-sha-256

  bootstrap:
    initdb:
      database: appdb
      owner: appuser
      secret:
        name: pg-superuser

  storage:
    size: 100Gi
    storageClass: gp3-db

  walStorage:
    size: 30Gi
    storageClass: gp3-db

  resources:
    requests:
      memory: 8Gi
      cpu: '4'
    limits:
      memory: 8Gi
      cpu: '4'

  affinity:
    enablePodAntiAffinity: true
    topologyKey: kubernetes.io/hostname

  monitoring:
    enablePodMonitor: true

  backup:
    barmanObjectStore:
      destinationPath: s3://my-backup-bucket/prod-pg/
      s3Credentials:
        accessKeyId:
          name: aws-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: aws-creds
          key: SECRET_ACCESS_KEY
      wal:
        compression: gzip
        maxParallel: 4
      data:
        compression: gzip
        jobs: 4
    retentionPolicy: '30d'

  nodeMaintenanceWindow:
    inProgress: false
    reusePVC: true

Step 3: 정기 백업 스케줄

apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: prod-pg-daily
  namespace: database
spec:
  schedule: '0 2 * * *'
  backupOwnerReference: self
  cluster:
    name: prod-pg
  target: prefer-standby

Step 4: Pooler (PgBouncer) 설정

apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
  name: prod-pg-pooler-rw
  namespace: database
spec:
  cluster:
    name: prod-pg
  instances: 2
  type: rw
  pgbouncer:
    poolMode: transaction
    parameters:
      max_client_conn: '1000'
      default_pool_size: '50'

Step 5: 배포 및 확인

kubectl apply -f cluster.yaml
kubectl apply -f scheduled-backup.yaml
kubectl apply -f pooler.yaml

# 클러스터 상태 확인
kubectl get cluster -n database

# Pod 상태 확인
kubectl get pods -n database

# 클러스터 상세 정보
kubectl describe cluster prod-pg -n database

# Primary에 접속 테스트
kubectl exec -it prod-pg-1 -n database -- psql -U postgres -d appdb

9. 안티패턴 — K8s DB 운영에서 피해야 할 실수들

1) Deployment로 DB 배포하기

Deployment는 상태 비저장(Stateless) 워크로드용입니다. DB에 Deployment를 사용하면 Pod 재시작 시 데이터가 유실되거나, 여러 Pod가 같은 데이터 디렉터리에 접근하는 문제가 발생합니다. 반드시 StatefulSet 또는 Operator CRD를 사용하세요.

2) PVC 없이 emptyDir 사용

emptyDir은 Pod가 삭제되면 데이터가 함께 사라집니다. 테스트 환경이라도 DB 데이터에는 PVC를 사용하는 습관을 들이세요.

3) 백업 없이 운영

Operator가 HA를 제공하더라도 백업은 별도로 반드시 구성해야 합니다. HA는 인프라 장애에 대한 보호이고, 백업은 논리적 오류(잘못된 DELETE 문 등)에 대한 보호입니다.

4) 리소스 제한 미설정

DB Pod에 limits를 설정하지 않으면 다른 Pod의 리소스를 잠식하거나, OOM 시 예측 불가능한 시점에 Pod가 종료될 수 있습니다. requests와 limits를 동일하게 설정하여 Guaranteed QoS를 확보하세요.

5) reclaimPolicy: Delete 사용

기본 reclaimPolicy가 Delete인 StorageClass를 사용하면 PVC 삭제 시 PV(실제 데이터)도 함께 삭제됩니다. DB용 StorageClass는 반드시 Retain으로 설정하세요.

6) 단일 AZ에 모든 DB Pod 배치

Pod Anti-Affinity 없이 배포하면 모든 DB Pod가 같은 노드나 AZ에 배치될 수 있습니다. 해당 노드/AZ 장애 시 전체 DB가 다운됩니다.

# 올바른 Anti-Affinity 설정
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: cnpg.io/cluster
              operator: In
              values:
                - prod-pg
        topologyKey: topology.kubernetes.io/zone

7) 모니터링/알림 없이 운영

복제 지연이 증가하거나 디스크가 가득 차도 모르는 상황이 생길 수 있습니다. 최소한 다음 알림을 구성하세요.

8) DB 업그레이드 시 롤링 업데이트 미검증

PostgreSQL, MySQL 등의 메이저 버전 업그레이드는 반드시 별도 클러스터에서 테스트 후 진행하세요. Operator가 자동 업그레이드를 지원하더라도, 애플리케이션 호환성 테스트는 사람이 해야 합니다.

9) Secret을 평문으로 관리

DB 비밀번호를 YAML에 하드코딩하지 마세요. External Secrets Operator나 Sealed Secrets를 사용하여 Secret을 안전하게 관리하세요.

# External Secrets 예시
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: pg-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: SecretStore
  target:
    name: pg-superuser
  data:
    - secretKey: username
      remoteRef:
        key: prod/database/credentials
        property: username
    - secretKey: password
      remoteRef:
        key: prod/database/credentials
        property: password

마무리

K8s에서 DB를 운영하는 것은 더 이상 실험적인 선택이 아닙니다. CNPG, Percona, Vitess 같은 성숙한 Operator들이 복잡한 운영 작업을 자동화해주며, K8s의 선언적 관리 모델과 자연스럽게 통합됩니다.

핵심 요약:

Operator를 도입한다면 먼저 개발 환경에서 충분히 테스트하고, 장애 시나리오(Pod 삭제, 노드 다운, AZ 장애)를 시뮬레이션해본 후 프로덕션에 적용하세요.

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다