Skip to content

필사 모드: Kubernetes에서 데이터베이스 운영 완전 가이드: StatefulSet, Operator, 백업/복구 전략

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

TL;DR

- **StatefulSet**: DB처럼 상태가 있는 애플리케이션을 위한 K8s 워크로드 (안정적 네트워크 ID + 영구 스토리지)

- **DB Operator**: CloudNativePG(PostgreSQL), Percona Operator(MySQL/MongoDB)로 운영 자동화

- **스토리지**: PV/PVC/StorageClass로 영구 데이터 관리, CSI 드라이버 활용

- **고가용성**: Primary-Replica 구성, 자동 Failover, PodDisruptionBudget

- **백업/복구**: pgBackRest, Velero, PITR(Point-in-Time Recovery) 전략

- **모니터링**: PMM, pg_exporter + Prometheus + Grafana 대시보드

목차

1. [K8s에서 DB를 운영해야 할까?](#1-k8s에서-db를-운영해야-할까)

2. [StatefulSet 심화](#2-statefulset-심화)

3. [스토리지 전략](#3-스토리지-전략)

4. [Headless Service와 DNS](#4-headless-service와-dns)

5. [Database Operator](#5-database-operator)

6. [고가용성 (HA)](#6-고가용성-ha)

7. [백업과 복구](#7-백업과-복구)

8. [모니터링](#8-모니터링)

9. [성능 튜닝](#9-성능-튜닝)

10. [보안](#10-보안)

11. [VM에서 K8s로 마이그레이션](#11-vm에서-k8s로-마이그레이션)

12. [프로덕션 체크리스트](#12-프로덕션-체크리스트)

13. [실전 퀴즈](#13-실전-퀴즈)

14. [참고 자료](#14-참고-자료)

1. K8s에서 DB를 운영해야 할까?

1.1 장단점 비교

K8s에서 DB 운영 결정 매트릭스:

운영해야 하는 경우: 운영하지 말아야 하는 경우:

+ 멀티클라우드/하이브리드 환경 - 관리형 DB 서비스 사용 가능

+ 인프라 일관성이 중요 - DBA 리소스 부족

+ GitOps/IaC 파이프라인 통합 - 초대형 규모의 단일 DB

+ 개발/테스트 환경 자동화 - 극도로 낮은 레이턴시 요구

+ 비용 최적화가 필수 - K8s 경험 부족

+ 데이터 주권 규제 - 단순한 아키텍처로 충분

| 기준 | K8s DB 운영 | 관리형 서비스 (RDS/CloudSQL) |

|------|------------|---------------------------|

| 초기 설정 복잡도 | 높음 | 낮음 |

| 운영 자동화 | Operator로 가능 | 기본 제공 |

| 비용 | 효율적 (리소스 공유) | 프리미엄 비용 |

| 멀티클라우드 | 용이 | 벤더 종속 |

| 커스터마이징 | 완전 자유 | 제한적 |

| 백업/복구 | 직접 구성 필요 | 기본 제공 |

| 확장성 | 수동/반자동 | 자동 확장 |

1.2 어떤 DB가 K8s에 적합한가?

K8s 친화도 순서:

매우 적합:

- PostgreSQL (CloudNativePG 생태계 우수)

- MongoDB (ReplicaSet 구조가 K8s와 자연스러움)

- Redis (Sentinel/Cluster 모드)

적합:

- MySQL (Percona/Oracle Operator 사용)

- Elasticsearch (ECK Operator)

- Cassandra (K8ssandra Operator)

주의 필요:

- Oracle DB (라이선스, 복잡성)

- SQL Server (Windows 컨테이너 제한)

- 대규모 단일 인스턴스 DB

2. StatefulSet 심화

2.1 StatefulSet vs Deployment

Deployment: StatefulSet:

- Pod 이름: random (abc-xyz) - Pod 이름: 순차적 (db-0, db-1, db-2)

- 병렬 생성/삭제 - 순차적 생성/삭제 (0 -> 1 -> 2)

- 공유 볼륨 또는 없음 - Pod별 전용 PVC (자동 생성)

- 교체 가능한 Pod - 고유한 ID를 가진 Pod

- Stateless 앱에 적합 - Stateful 앱(DB)에 적합

2.2 StatefulSet YAML 예시

apiVersion: apps/v1

kind: StatefulSet

metadata:

name: postgres

namespace: database

spec:

serviceName: postgres-headless # Headless Service 이름

replicas: 3

podManagementPolicy: OrderedReady # 순차적 생성 (기본값)

updateStrategy:

type: RollingUpdate

rollingUpdate:

maxUnavailable: 1 # K8s 1.24+

selector:

matchLabels:

app: postgres

template:

metadata:

labels:

app: postgres

spec:

terminationGracePeriodSeconds: 120 # DB 종료에 충분한 시간

securityContext:

fsGroup: 999 # postgres 그룹

runAsUser: 999 # postgres 사용자

containers:

- name: postgres

image: postgres:16-alpine

ports:

- containerPort: 5432

name: postgresql

env:

- name: POSTGRES_DB

value: myapp

- name: POSTGRES_USER

valueFrom:

secretKeyRef:

name: postgres-secret

key: username

- name: POSTGRES_PASSWORD

valueFrom:

secretKeyRef:

name: postgres-secret

key: password

- name: PGDATA

value: /var/lib/postgresql/data/pgdata

resources:

requests:

cpu: "500m"

memory: "1Gi"

limits:

cpu: "2"

memory: "4Gi"

volumeMounts:

- name: data

mountPath: /var/lib/postgresql/data

livenessProbe:

exec:

command:

- pg_isready

- -U

- postgres

initialDelaySeconds: 30

periodSeconds: 10

readinessProbe:

exec:

command:

- pg_isready

- -U

- postgres

initialDelaySeconds: 5

periodSeconds: 5

volumeClaimTemplates:

- metadata:

name: data

spec:

accessModes: ["ReadWriteOnce"]

storageClassName: fast-ssd

resources:

requests:

storage: 100Gi

2.3 PodManagementPolicy

OrderedReady (기본값): 순차적 생성/삭제

Pod 0 Ready -> Pod 1 생성 -> Pod 1 Ready -> Pod 2 생성

podManagementPolicy: OrderedReady

Parallel: 모든 Pod 동시 생성/삭제

초기 부트스트래핑에 유의 (DB는 보통 OrderedReady 사용)

podManagementPolicy: Parallel

2.4 UpdateStrategy

updateStrategy:

type: RollingUpdate

rollingUpdate:

Partition: 이 값 이상의 ordinal Pod만 업데이트

카나리 배포에 활용 (Pod 2만 먼저 업데이트)

partition: 2

OnDelete: 수동으로 Pod 삭제 시에만 업데이트

DB 업그레이드 시 세밀한 제어 가능

updateStrategy:

type: OnDelete

3. 스토리지 전략

3.1 PV / PVC / StorageClass

StorageClass 정의 (AWS EBS gp3)

apiVersion: storage.k8s.io/v1

kind: StorageClass

metadata:

name: fast-ssd

provisioner: ebs.csi.aws.com

parameters:

type: gp3

iops: "5000"

throughput: "250" # MB/s

encrypted: "true"

reclaimPolicy: Retain # DB 데이터는 반드시 Retain!

volumeBindingMode: WaitForFirstConsumer

allowVolumeExpansion: true # 온라인 볼륨 확장 허용

StorageClass 정의 (GCP PD SSD)

apiVersion: storage.k8s.io/v1

kind: StorageClass

metadata:

name: fast-ssd

provisioner: pd.csi.storage.gke.io

parameters:

type: pd-ssd

replication-type: regional-pd # 리전 PD (고가용성)

reclaimPolicy: Retain

volumeBindingMode: WaitForFirstConsumer

allowVolumeExpansion: true

3.2 로컬 스토리지 vs 클라우드 볼륨

성능 비교:

Local NVMe SSD:

- 랜덤 읽기: 500K+ IOPS

- 레이턴시: 0.1ms 이하

- 단점: Pod 이동 불가, 노드 장애 시 데이터 손실 위험

Cloud EBS gp3:

- 기본: 3,000 IOPS / 125 MB/s

- 최대: 16,000 IOPS / 1,000 MB/s

- 장점: Pod 이동 가능, 스냅샷 지원

Cloud EBS io2:

- 최대: 64,000 IOPS

- 99.999% 내구성

- 비용이 높지만 미션 크리티컬 DB에 적합

3.3 볼륨 확장

PVC 크기 확장 (StorageClass에 allowVolumeExpansion: true 필요)

kubectl patch pvc data-postgres-0 -n database \

-p '{"spec": {"resources": {"requests": {"storage": "200Gi"}}}}'

확장 상태 확인

kubectl get pvc data-postgres-0 -n database -o yaml | grep -A 5 status

4. Headless Service와 DNS

4.1 Headless Service 정의

apiVersion: v1

kind: Service

metadata:

name: postgres-headless

namespace: database

spec:

clusterIP: None # Headless Service의 핵심

selector:

app: postgres

ports:

- port: 5432

targetPort: 5432

name: postgresql

4.2 DNS 규칙

StatefulSet의 각 Pod는 예측 가능한 DNS 이름을 갖습니다.

DNS 패턴:

pod-name.service-name.namespace.svc.cluster.local

예시:

postgres-0.postgres-headless.database.svc.cluster.local

postgres-1.postgres-headless.database.svc.cluster.local

postgres-2.postgres-headless.database.svc.cluster.local

읽기/쓰기 분리를 위한 추가 Service

apiVersion: v1

kind: Service

metadata:

name: postgres-primary

namespace: database

spec:

selector:

app: postgres

role: primary

ports:

- port: 5432

targetPort: 5432

apiVersion: v1

kind: Service

metadata:

name: postgres-replica

namespace: database

spec:

selector:

app: postgres

role: replica

ports:

- port: 5432

targetPort: 5432

5. Database Operator

5.1 왜 Operator가 필요한가?

DB 운영은 단순한 배포를 넘어 복잡한 Day-2 운영이 필요합니다.

Operator가 자동화하는 작업:

1. 클러스터 초기화 (Primary + Replica 구성)

2. 자동 Failover (Primary 장애 시 Replica 승격)

3. 백업/복구 (스케줄링, PITR)

4. 롤링 업그레이드 (무중단)

5. 수평 확장 (Replica 추가/제거)

6. 모니터링 통합

7. 인증서 관리 (TLS)

8. 설정 변경 (재시작 없이)

5.2 PostgreSQL - CloudNativePG (CNPG)

CloudNativePG 설치

kubectl apply --server-side -f \

https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yaml

CloudNativePG 클러스터 정의

apiVersion: postgresql.cnpg.io/v1

kind: Cluster

metadata:

name: myapp-db

namespace: database

spec:

instances: 3

imageName: ghcr.io/cloudnative-pg/postgresql:16.4

postgresql:

parameters:

max_connections: "200"

shared_buffers: "1GB"

effective_cache_size: "3GB"

work_mem: "16MB"

maintenance_work_mem: "256MB"

wal_buffers: "16MB"

random_page_cost: "1.1"

effective_io_concurrency: "200"

max_wal_size: "2GB"

checkpoint_completion_target: "0.9"

bootstrap:

initdb:

database: myapp

owner: app_user

secret:

name: myapp-db-credentials

storage:

size: 100Gi

storageClass: fast-ssd

resources:

requests:

memory: "2Gi"

cpu: "1"

limits:

memory: "4Gi"

cpu: "2"

backup:

barmanObjectStore:

destinationPath: "s3://my-backup-bucket/cnpg/"

s3Credentials:

accessKeyId:

name: aws-creds

key: ACCESS_KEY_ID

secretAccessKey:

name: aws-creds

key: SECRET_ACCESS_KEY

wal:

compression: gzip

data:

compression: gzip

retentionPolicy: "30d"

monitoring:

enablePodMonitor: true

5.3 MySQL - Percona XtraDB Cluster Operator

Percona Operator 설치

kubectl apply -f https://raw.githubusercontent.com/percona/percona-xtradb-cluster-operator/v1.15.0/deploy/bundle.yaml

Percona XtraDB Cluster 정의

apiVersion: pxc.percona.com/v1

kind: PerconaXtraDBCluster

metadata:

name: myapp-mysql

namespace: database

spec:

crVersion: "1.15.0"

secretsName: myapp-mysql-secrets

pxc:

size: 3

image: percona/percona-xtradb-cluster:8.0.36

resources:

requests:

memory: 2G

cpu: "1"

limits:

memory: 4G

cpu: "2"

volumeSpec:

persistentVolumeClaim:

storageClassName: fast-ssd

resources:

requests:

storage: 100Gi

affinity:

antiAffinityTopologyKey: "kubernetes.io/hostname"

haproxy:

enabled: true

size: 3

image: percona/haproxy:2.8.5

resources:

requests:

memory: 512M

cpu: "500m"

backup:

image: percona/percona-xtradb-cluster-operator:1.15.0-pxc8.0-backup

storages:

s3-backup:

type: s3

s3:

bucket: my-backup-bucket

credentialsSecret: aws-creds

region: ap-northeast-2

schedule:

- name: daily-backup

schedule: "0 3 * * *"

keep: 7

storageName: s3-backup

5.4 MongoDB - Community Operator

MongoDB Community Operator 설치

kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes-operator/master/config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml

kubectl apply -k https://github.com/mongodb/mongodb-kubernetes-operator/config/rbac/

kubectl create -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes-operator/master/config/manager/manager.yaml

MongoDB ReplicaSet 정의

apiVersion: mongodbcommunity.mongodb.com/v1

kind: MongoDBCommunity

metadata:

name: myapp-mongodb

namespace: database

spec:

members: 3

type: ReplicaSet

version: "7.0.14"

security:

authentication:

modes: ["SCRAM"]

users:

- name: app-user

db: admin

passwordSecretRef:

name: mongodb-password

roles:

- name: readWrite

db: myapp

- name: clusterAdmin

db: admin

scramCredentialsSecretName: app-user-scram

statefulSet:

spec:

template:

spec:

containers:

- name: mongod

resources:

requests:

cpu: "1"

memory: 2Gi

limits:

cpu: "2"

memory: 4Gi

volumeClaimTemplates:

- metadata:

name: data-volume

spec:

storageClassName: fast-ssd

resources:

requests:

storage: 100Gi

5.5 Operator 비교

| 특성 | CloudNativePG | Percona XtraDB | MongoDB Community |

|------|--------------|----------------|-------------------|

| DB | PostgreSQL | MySQL | MongoDB |

| 라이선스 | Apache 2.0 | Apache 2.0 | SSPL + Apache |

| HA 방식 | Streaming Replication | Galera Cluster | ReplicaSet |

| 자동 Failover | 지원 | 지원 | 지원 |

| 백업 | Barman/S3 | xtrabackup/S3 | mongodump 연동 |

| 모니터링 | PodMonitor | PMM 통합 | 기본 메트릭 |

| 성숙도 | 매우 높음 | 높음 | 중간 |

6. 고가용성 (HA)

6.1 Primary-Replica 구성

PostgreSQL HA 아키텍처 (CloudNativePG):

[CNPG Operator]

|

v

[Primary Pod] ----Streaming Replication----> [Replica Pod 1]

| [Replica Pod 2]

|

[Headless Service]

|

postgres-primary (쓰기) ----> Primary만 라우팅

postgres-replica (읽기) ----> Replica만 라우팅

6.2 자동 Failover

Failover 시나리오:

1. Primary Pod 장애 발생

2. Operator가 감지 (liveness probe 실패)

3. 가장 최신 LSN을 가진 Replica 선택

4. 선택된 Replica를 Primary로 승격

5. 나머지 Replica가 새 Primary를 추종

6. Service 엔드포인트 업데이트

7. 장애 Pod 재생성 후 새 Replica로 합류

전체 과정: 보통 30초 이내

6.3 PodDisruptionBudget

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: postgres-pdb

namespace: database

spec:

minAvailable: 2 # 최소 2개 Pod 유지

selector:

matchLabels:

app: postgres

6.4 노드 장애 대비

Pod Topology Spread Constraints

spec:

template:

spec:

topologySpreadConstraints:

- maxSkew: 1

topologyKey: topology.kubernetes.io/zone

whenUnsatisfiable: DoNotSchedule

labelSelector:

matchLabels:

app: postgres

- maxSkew: 1

topologyKey: kubernetes.io/hostname

whenUnsatisfiable: DoNotSchedule

labelSelector:

matchLabels:

app: postgres

7. 백업과 복구

7.1 백업 유형

논리적 백업 (pg_dump/mysqldump):

+ 이식성 높음 (다른 버전/플랫폼으로 복원 가능)

+ 개별 테이블/스키마 백업 가능

- 대용량 DB에서 느림

- 복원 시 인덱스 재생성 필요

물리적 백업 (pgBackRest/xtrabackup):

+ 대용량 DB에서 빠름

+ 증분 백업 지원

+ PITR(Point-in-Time Recovery) 가능

- 같은 메이저 버전에서만 복원

- 전체 클러스터 단위 백업

7.2 pgBackRest (PostgreSQL)

CloudNativePG의 백업 설정

apiVersion: postgresql.cnpg.io/v1

kind: Cluster

metadata:

name: myapp-db

spec:

backup:

barmanObjectStore:

destinationPath: "s3://my-backup-bucket/cnpg/myapp-db/"

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

immediateCheckpoint: true

retentionPolicy: "30d"

스케줄 백업

apiVersion: postgresql.cnpg.io/v1

kind: ScheduledBackup

metadata:

name: myapp-db-daily

spec:

schedule: "0 3 * * *"

cluster:

name: myapp-db

backupOwnerReference: self

method: barmanObjectStore

7.3 PITR (Point-in-Time Recovery)

PITR로 특정 시점 복구

apiVersion: postgresql.cnpg.io/v1

kind: Cluster

metadata:

name: myapp-db-recovered

spec:

instances: 3

bootstrap:

recovery:

source: myapp-db-backup

recoveryTarget:

targetTime: "2026-03-24T10:30:00Z" # 복구 시점

externalClusters:

- name: myapp-db-backup

barmanObjectStore:

destinationPath: "s3://my-backup-bucket/cnpg/myapp-db/"

s3Credentials:

accessKeyId:

name: aws-creds

key: ACCESS_KEY_ID

secretAccessKey:

name: aws-creds

key: SECRET_ACCESS_KEY

7.4 Velero를 활용한 전체 백업

Velero 설치

velero install \

--provider aws \

--bucket my-velero-bucket \

--secret-file ./credentials-velero \

--plugins velero/velero-plugin-for-aws:v1.10.0

네임스페이스 단위 백업

velero backup create database-backup \

--include-namespaces database \

--snapshot-volumes=true \

--volume-snapshot-locations default

복구

velero restore create --from-backup database-backup

8. 모니터링

8.1 Prometheus + Grafana

PostgreSQL Exporter (pg_exporter)

apiVersion: apps/v1

kind: Deployment

metadata:

name: postgres-exporter

namespace: database

spec:

replicas: 1

selector:

matchLabels:

app: postgres-exporter

template:

metadata:

labels:

app: postgres-exporter

annotations:

prometheus.io/scrape: "true"

prometheus.io/port: "9187"

spec:

containers:

- name: exporter

image: prometheuscommunity/postgres-exporter:0.15.0

ports:

- containerPort: 9187

env:

- name: DATA_SOURCE_URI

value: "postgres-primary.database.svc:5432/myapp?sslmode=disable"

- name: DATA_SOURCE_USER

valueFrom:

secretKeyRef:

name: postgres-secret

key: username

- name: DATA_SOURCE_PASS

valueFrom:

secretKeyRef:

name: postgres-secret

key: password

8.2 주요 모니터링 지표

DB 모니터링 핵심 메트릭:

성능:

- 쿼리 수/초 (QPS)

- 쿼리 레이턴시 (p50, p95, p99)

- 활성 커넥션 수

- 캐시 히트율 (Buffer Cache Hit Ratio)

복제:

- 복제 지연 (Replication Lag)

- WAL 수신 지연

- Replica 상태

스토리지:

- 디스크 사용량

- IOPS / 처리량

- WAL 크기

리소스:

- CPU 사용률

- 메모리 사용량

- Pod 재시작 횟수

운영:

- Dead tuples 비율

- Vacuum 실행 상태

- 락 대기 수

8.3 Percona Monitoring and Management (PMM)

PMM Server 설치

apiVersion: apps/v1

kind: Deployment

metadata:

name: pmm-server

namespace: monitoring

spec:

replicas: 1

selector:

matchLabels:

app: pmm-server

template:

spec:

containers:

- name: pmm-server

image: percona/pmm-server:2

ports:

- containerPort: 443

volumeMounts:

- name: pmm-data

mountPath: /srv

volumes:

- name: pmm-data

persistentVolumeClaim:

claimName: pmm-data

9. 성능 튜닝

9.1 리소스 Requests/Limits

DB Pod 리소스 설정 가이드

resources:

requests:

CPU: 보장받을 최소 CPU

DB는 CPU 경합에 민감하므로 넉넉히

cpu: "2"

Memory: shared_buffers + work_mem * max_connections + OS

memory: "4Gi"

limits:

CPU limit은 설정하지 않거나 여유있게

(throttling이 쿼리 지연 유발)

cpu: "4"

Memory limit은 OOM Kill 방지를 위해 request보다 여유있게

memory: "8Gi"

9.2 Affinity와 Anti-Affinity

DB Pod 스케줄링 최적화

spec:

template:

spec:

DB 전용 노드에만 스케줄링

nodeSelector:

node-type: database

또는 Toleration으로 전용 노드

tolerations:

- key: "dedicated"

operator: "Equal"

value: "database"

effect: "NoSchedule"

Replica를 다른 노드/존에 분배

affinity:

podAntiAffinity:

requiredDuringSchedulingIgnoredDuringExecution:

- labelSelector:

matchExpressions:

- key: app

operator: In

values:

- postgres

topologyKey: "kubernetes.io/hostname"

9.3 커널 파라미터 튜닝

initContainer로 커널 파라미터 설정

spec:

template:

spec:

initContainers:

- name: sysctl-tuning

image: busybox:1.36

securityContext:

privileged: true

command:

- sh

- -c

- |

sysctl -w vm.swappiness=1

sysctl -w vm.dirty_background_ratio=5

sysctl -w vm.dirty_ratio=10

sysctl -w vm.overcommit_memory=2

sysctl -w net.core.somaxconn=65535

sysctl -w net.ipv4.tcp_max_syn_backlog=65535

10. 보안

10.1 NetworkPolicy

DB Pod에 대한 네트워크 접근 제한

apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

name: postgres-network-policy

namespace: database

spec:

podSelector:

matchLabels:

app: postgres

policyTypes:

- Ingress

- Egress

ingress:

- from:

- namespaceSelector:

matchLabels:

name: application

podSelector:

matchLabels:

app: backend

- podSelector:

matchLabels:

app: postgres # Pod 간 복제 허용

ports:

- port: 5432

protocol: TCP

egress:

- to:

- podSelector:

matchLabels:

app: postgres

ports:

- port: 5432

protocol: TCP

- to: # DNS 허용

- namespaceSelector: {}

ports:

- port: 53

protocol: UDP

- port: 53

protocol: TCP

10.2 Secrets 관리

External Secrets Operator 사용

apiVersion: external-secrets.io/v1beta1

kind: ExternalSecret

metadata:

name: postgres-secret

namespace: database

spec:

refreshInterval: 1h

secretStoreRef:

name: aws-secrets-manager

kind: ClusterSecretStore

target:

name: postgres-secret

data:

- secretKey: username

remoteRef:

key: prod/database/postgres

property: username

- secretKey: password

remoteRef:

key: prod/database/postgres

property: password

10.3 TLS 설정

cert-manager로 DB 인증서 발급

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

name: postgres-tls

namespace: database

spec:

secretName: postgres-tls-secret

duration: 8760h # 1년

renewBefore: 720h # 30일 전 갱신

issuerRef:

name: internal-ca

kind: ClusterIssuer

dnsNames:

- postgres-primary.database.svc.cluster.local

- postgres-headless.database.svc.cluster.local

- "*.postgres-headless.database.svc.cluster.local"

11. VM에서 K8s로 마이그레이션

11.1 마이그레이션 단계

Phase 1: 준비

1. K8s 클러스터에 DB Operator 설치

2. StorageClass, NetworkPolicy 구성

3. 타겟 DB 클러스터 생성 (빈 상태)

Phase 2: 데이터 마이그레이션

방법 A - 논리적 복제 (최소 다운타임):

1. VM DB에서 논리적 복제(Logical Replication) 설정

2. K8s DB를 Subscriber로 설정

3. 초기 동기화 + 변경분 스트리밍

4. 애플리케이션 전환 (짧은 다운타임)

방법 B - pg_dump/restore:

1. VM DB 백업 (pg_dump)

2. K8s DB로 복원 (pg_restore)

3. 다운타임 동안 전환

Phase 3: 전환

1. 애플리케이션 DB 연결 문자열 변경

2. DNS 변경 또는 Service 엔드포인트 업데이트

3. 이전 VM DB를 읽기 전용으로 전환 (롤백 대비)

Phase 4: 정리

1. 검증 완료 후 이전 VM DB 삭제

2. 모니터링/알림 업데이트

11.2 논리적 복제 설정

-- VM DB (Publisher) 설정

-- postgresql.conf

-- wal_level = logical

-- max_replication_slots = 10

CREATE PUBLICATION myapp_pub FOR ALL TABLES;

-- K8s DB (Subscriber) 설정

CREATE SUBSCRIPTION myapp_sub

CONNECTION 'host=vm-db.example.com port=5432 dbname=myapp user=repl_user password=secret'

PUBLICATION myapp_pub;

12. 프로덕션 체크리스트

K8s DB 프로덕션 체크리스트:

스토리지:

[ ] StorageClass에 reclaimPolicy: Retain 설정

[ ] 볼륨 크기에 20% 이상 여유 공간

[ ] allowVolumeExpansion: true 확인

[ ] IOPS/throughput이 워크로드에 적합한지 확인

고가용성:

[ ] 최소 3개 인스턴스 (1 Primary + 2 Replica)

[ ] PodDisruptionBudget 설정

[ ] Pod Anti-Affinity (다른 노드/존에 분배)

[ ] 자동 Failover 테스트 완료

백업:

[ ] 자동 백업 스케줄 설정 (일 1회 이상)

[ ] WAL 아카이빙 활성화 (PITR 지원)

[ ] 백업 복구 테스트 완료

[ ] 백업 보관 정책 설정 (30일 이상)

보안:

[ ] NetworkPolicy로 접근 제한

[ ] Secrets는 External Secrets Operator로 관리

[ ] TLS 암호화 활성화

[ ] DB 사용자 권한 최소화 (최소 권한 원칙)

모니터링:

[ ] Prometheus + Grafana 대시보드 구성

[ ] 알림 규칙 설정 (복제 지연, 디스크 사용률, 커넥션 수)

[ ] 로그 수집 (Loki/EFK)

[ ] 쿼리 성능 모니터링

성능:

[ ] Resource requests/limits 적절히 설정

[ ] DB 파라미터 튜닝 (shared_buffers, work_mem 등)

[ ] 커널 파라미터 최적화

[ ] 전용 노드 사용 (Taint/Toleration)

13. 실전 퀴즈

**정답:**

StatefulSet은 다음을 보장합니다:

1. **안정적인 네트워크 ID**: 각 Pod에 순차적인 이름이 부여됩니다 (예: db-0, db-1, db-2). Pod가 재생성되어도 같은 이름을 유지합니다.

2. **영구 스토리지 바인딩**: volumeClaimTemplates로 각 Pod에 전용 PVC가 자동 생성됩니다. Pod가 삭제/재생성되어도 같은 PVC에 다시 연결됩니다.

3. **순차적 생성/삭제**: Pod 0이 Ready 상태가 된 후에 Pod 1이 생성됩니다 (OrderedReady 정책).

반면 Deployment는 임의의 Pod 이름, 공유 볼륨, 병렬 생성을 사용하므로 Stateless 앱에 적합합니다.

**정답:**

reclaimPolicy가 Delete(기본값)이면 PVC가 삭제될 때 PV와 실제 스토리지(EBS 볼륨 등)도 함께 삭제됩니다. 이는 다음 상황에서 데이터 손실을 유발합니다:

- 실수로 StatefulSet이나 PVC를 삭제

- 네임스페이스 삭제

- Helm uninstall

Retain으로 설정하면 PVC가 삭제되어도 PV와 실제 스토리지가 유지되어, 데이터 복구가 가능합니다. 프로덕션 DB에서는 반드시 Retain을 사용해야 합니다.

**정답:**

1. CNPG Operator가 모든 인스턴스의 상태를 지속적으로 모니터링합니다.

2. Primary Pod의 liveness probe가 실패하면, Operator가 장애를 감지합니다.

3. Operator는 모든 Replica의 WAL LSN(Log Sequence Number)을 비교하여 가장 최신 데이터를 가진 Replica를 선택합니다.

4. 선택된 Replica가 Primary로 승격되고, pg_promote가 실행됩니다.

5. 나머지 Replica는 새 Primary를 추종하도록 재설정됩니다.

6. Service 엔드포인트가 새 Primary를 가리키도록 자동 업데이트됩니다.

7. 장애 Pod는 재생성되어 새 Replica로 합류합니다.

전체 과정은 일반적으로 30초 이내에 완료됩니다.

**정답:**

CPU limit이 설정되면 Kubernetes는 CFS (Completely Fair Scheduler) throttling을 적용합니다. DB가 순간적으로 높은 CPU를 필요로 할 때 (예: 복잡한 쿼리, VACUUM) throttling이 발생하면 쿼리 레이턴시가 크게 증가합니다.

대신 다음 전략을 권장합니다:

- CPU request만 설정하여 보장된 CPU를 확보

- DB 전용 노드를 사용 (Taint/Toleration)하여 다른 워크로드와 리소스 경합을 방지

- 노드 수준에서 CPU 리소스가 충분한지 확인

Memory limit은 OOM Kill을 방지하기 위해 설정하되, request보다 충분히 여유있게 설정합니다.

**정답:**

논리적 복제(Logical Replication)를 활용합니다:

1. VM DB에서 `wal_level = logical`을 설정하고 Publication을 생성합니다.

2. K8s DB에서 Subscription을 생성하여 VM DB에 연결합니다.

3. 초기 데이터 동기화가 자동으로 진행됩니다.

4. 동기화 완료 후, 변경분이 실시간으로 스트리밍됩니다.

5. 애플리케이션을 잠시 중단(수초~수분)하고, 복제 지연이 0인지 확인합니다.

6. 애플리케이션의 DB 연결 문자열을 K8s DB로 변경합니다.

7. 애플리케이션을 재시작합니다.

이 방법으로 다운타임을 수초~수분으로 최소화할 수 있습니다.

14. 참고 자료

1. [CloudNativePG Documentation](https://cloudnative-pg.io/documentation/)

2. [Percona Operator for MySQL Documentation](https://docs.percona.com/percona-operator-for-mysql/pxc/)

3. [MongoDB Kubernetes Operator](https://github.com/mongodb/mongodb-kubernetes-operator)

4. [Kubernetes StatefulSet Documentation](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)

5. [Kubernetes Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)

6. [Velero - Backup and Restore](https://velero.io/docs/)

7. [External Secrets Operator](https://external-secrets.io/)

8. [Percona Monitoring and Management (PMM)](https://docs.percona.com/percona-monitoring-and-management/)

9. [PostgreSQL Kubernetes Best Practices](https://www.postgresql.org/docs/current/hot-standby.html)

10. [Zalando Postgres Operator](https://postgres-operator.readthedocs.io/)

11. [CrunchyData PGO](https://access.crunchydata.com/documentation/postgres-operator/latest/)

12. [K8ssandra - Cassandra on Kubernetes](https://k8ssandra.io/)

13. [Data on Kubernetes Community](https://dok.community/)

14. [CNCF Storage Landscape](https://landscape.cncf.io/card-mode?category=cloud-native-storage)

현재 단락 (1/838)

- **StatefulSet**: DB처럼 상태가 있는 애플리케이션을 위한 K8s 워크로드 (안정적 네트워크 ID + 영구 스토리지)

작성 글자: 0원문 글자: 19,036작성 단락: 0/838