2.3 컨테이너·Kubernetes
PostgreSQL을 컨테이너로 띄우는 패턴은 크게 두 갈래다 — 단일 Docker 컨테이너(개발·테스트·CI), Kubernetes operator(운영 클러스터). 공식 Docker 이미지 사용법과 두 주요 operator(CloudNativePG, Zalando) 비교를 정리합니다.
공식 Docker 이미지 — postgres
Docker Hub의 postgres는 PostgreSQL Global Development Group이 직접 관리하는 공식 이미지입니다.
docker run -d \
--name pg17 \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_USER=app_user \
-e POSTGRES_DB=app_main \
-p 5432:5432 \
-v pg17_data:/var/lib/postgresql/data \
postgres:17| 환경변수 | 의미 |
|---|---|
POSTGRES_PASSWORD | 슈퍼유저(기본 postgres) 비밀번호. 필수 |
POSTGRES_USER | 슈퍼유저 이름 (기본 postgres) |
POSTGRES_DB | 첫 데이터베이스 이름 (기본 $POSTGRES_USER) |
POSTGRES_INITDB_ARGS | initdb 추가 인자 (예: --data-checksums) |
POSTGRES_HOST_AUTH_METHOD | 외부 접속 인증 방식 (기본 scram-sha-256) |
PGDATA | 데이터 디렉토리 위치 (기본 /var/lib/postgresql/data) |
초기화 스크립트
/docker-entrypoint-initdb.d/에 .sql·.sh·.sql.gz를 마운트하면 첫 기동 시 한 번 실행됩니다. 이미 PGDATA에 데이터가 있으면 건너뜁니다.
# docker-compose.yml
services:
pg:
image: postgres:17
environment:
POSTGRES_PASSWORD: secret
POSTGRES_INITDB_ARGS: "--data-checksums"
volumes:
- ./initdb:/docker-entrypoint-initdb.d:ro
- pg_data:/var/lib/postgresql/data
ports: ["5432:5432"]
volumes:
pg_data:운영용으로 부족한 부분
공식 이미지는 단일 인스턴스 전용입니다. 운영 클러스터에 필요한 다음을 직접 처리해야 합니다.
- 자동 페일오버
- 베이스 백업·WAL 아카이브
- 복제 슬롯 관리
- TLS 인증서 회전
- 모니터링 지표 수집
그래서 운영 환경에서는 operator를 씁니다.
Kubernetes operator 비교
PostgreSQL operator는 여러 개 있지만, 운영에서 자주 보이는 두 개를 비교합니다.
| 항목 | CloudNativePG (CNPG) | Zalando postgres-operator |
|---|---|---|
| 제작 | EDB (오픈소스) | Zalando |
| 라이선스 | Apache 2.0 | MIT |
| 첫 릴리스 | 2022 | 2017 |
| 클러스터 정의 CRD | Cluster | postgresql |
| HA 구현 | 자체 (cluster manager) | Patroni 기반 |
| 베이스 백업 | 내장 (Barman 통합) | 별도 (WAL-E/G) |
| TLS 자동 발급 | cert-manager 통합 | 수동·cert-manager |
| 모니터링 노출 | Prometheus 메트릭 내장 | Prometheus 메트릭 내장 |
| 주요 사용처 | EDB Postgres for Kubernetes 기반, 신규 프로젝트 다수 | Zalando 내부, OpenSearch 시절부터 운영 |
CloudNativePG 최소 예시
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: app-pg
spec:
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:17.0
storage:
size: 50Gi
storageClass: fast-ssd
postgresql:
parameters:
shared_buffers: "2GB"
effective_cache_size: "6GB"
max_wal_size: "4GB"
bootstrap:
initdb:
database: app_main
owner: app_user
dataChecksums: true
backup:
barmanObjectStore:
destinationPath: s3://my-pg-backups
s3Credentials:
accessKeyId: { name: pg-s3-creds, key: ACCESS_KEY_ID }
secretAccessKey: { name: pg-s3-creds, key: SECRET_ACCESS_KEY }CNPG는 Cluster 한 CR로 instances(primary + standby), 백업, TLS, 모니터링을 모두 잡습니다.
Zalando 최소 예시
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: app-pg
spec:
teamId: dba
postgresql:
version: "17"
numberOfInstances: 3
volume:
size: 50Gi
storageClass: fast-ssd
databases:
app_main: app_user
users:
app_user: [superuser, createdb]Zalando는 내부적으로 Patroni를 컨테이너 안에 둬 etcd/k8s configmap을 leader election에 씁니다. 자체 페일오버 검증된 도구라 운영 안정성이 강합니다.
컨테이너 환경에서 주의할 점
flowchart TD
POD["Pod: postgres container"]
STORE["PersistentVolume<br/>(network attached)"]
WAL["WAL archive<br/>(S3·NCP Object Storage)"]
SVC["Service / LoadBalancer"]
CLIENT["client app"]
CLIENT --> SVC --> POD
POD --> STORE
POD --> WAL
classDef pod fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef vol fill:#fed7aa,stroke:#c2410c,color:#7c2d12
classDef wal fill:#d1fae5,stroke:#047857,color:#064e3b
classDef svc fill:#ede9fe,stroke:#6d28d9,color:#3b0764
class POD pod
class STORE vol
class WAL wal
class SVC,CLIENT svc
핵심 관점:
| 항목 | 메모 |
|---|---|
| 스토리지 | 네트워크 스토리지(EBS/CephFS 등)의 fsync 성능이 베어메탈보다 느리다. WAL flush 지연 주의 |
| 메모리 제한 | container memory limit을 너무 빡빡하게 잡으면 OOM Killer가 postgres를 죽인다. work_mem × connections 여유 확보 |
| 종료 신호 | k8s가 SIGTERM → 30초 grace → SIGKILL. PostgreSQL은 fast shutdown 후 안 끝나면 데이터 손상 위험 → terminationGracePeriodSeconds를 60초 이상 |
| 포트 노출 | LoadBalancer로 외부 노출 시 pg_hba.conf와 TLS 필수 |
| 로그 | stdout 출력 (operator 자동). 외부 로그 수집기(Loki, Datadog)로 모음 |
ephemeral volume에 PGDATA 두지 말 것. Pod 재시작 = 데이터 손실입니다. 항상 PersistentVolume + ReclaimPolicy=Retain.
단순 컨테이너 vs operator 선택
| 시나리오 | 권장 |
|---|---|
| 개발·테스트·CI | 공식 postgres:17 이미지 단일 컨테이너 |
| 통합 테스트 환경 | docker-compose, testcontainers |
| 운영 (단일 인스턴스) | k8s에 운영하기보다 일반 VM/베어메탈 + PGDG 추천 |
| 운영 (HA·복제 필요, k8s 인프라 있음) | CloudNativePG 또는 Zalando operator |
| 매니지드 서비스를 못 쓰는 컴플라이언스 환경 | operator |
| 매니지드 가능하면 | RDS/Aurora/NCP Cloud DB (Part XVII) |
“k8s에서 PostgreSQL 운영” 자체가 옳은가는 별개 논쟁입니다. operator가 잘 도와주지만 결국 stateful 워크로드라 운영 부담이 작지 않습니다. 매니지드를 쓸 수 있으면 매니지드가 보통 답입니다.
정리
- 공식 Docker 이미지는 개발·테스트용.
POSTGRES_PASSWORD·볼륨·initdb 스크립트로 시작 - 운영 k8s에서는 CloudNativePG 또는 Zalando operator
- CNPG: 신규·EDB 계열, Apache 2.0, Barman 통합
- Zalando: 더 오래된 프로덕션 검증, Patroni 기반
- ephemeral volume 절대 금지합니다. 네트워크 스토리지 fsync 성능과 종료 시간(
terminationGracePeriodSeconds)에 주의 - k8s 자체 운영보다 매니지드 클라우드가 더 간단한 경우가 많음
다음 절(2.4)에서는 패키지/소스/컨테이너 어떤 경로로 깔았든 공통으로 거치는 initdb 단계를 깊게 봅니다.