[Kubernetes] K3s 환경에서 PostgreSQL Master-Replica HA 구성하기 (Helm + hostPath PV 완벽 가이드)
jay
2026년 1월 12일 23:01
목차
최근 미니피씨에 K3s를 구축하고 서비스를 올리는 작업을 진행 중이다. DB는 가장 익숙한 PostgreSQL을 선택했는데, 단순히 파드 하나 띄우는 것(Standalone) 말고, 읽기 부하 분산과 가용성을 위해 Master-Replica(Primary-ReadReplica) 구조를 잡고 싶었다.
Cloud 환경이었다면 EBS나 블록 스토리지로 뚝딱했겠지만, 맨땅에 헤딩하는 온프레미스 환경이라 스토리지부터 Helm 설정까지 꽤나 삽질을 했다. 그 과정과 최종 설정을 기록해둔다.
🏗️ 목표 아키텍처
- Cluster: K3s (Single Node ‘jay’)
- Install Tool: Helm (bitnami/postgresql)
- Architecture:
- Primary (Write): 고정된
hostPath경로(/srv/k3s/data/postgresql/master)를 사용하여 데이터 영속성 보장. - Read Replicas (Read): HPA를 통해 부하에 따라 자동 스케일링(Scale-out). 스토리지는 K3s 기본
local-path를 통한 동적 프로비저닝 사용.
- Primary (Write): 고정된

1. Primary용 정적 PV/PVC 생성 (고정 경로 사용)
Primary DB는 가장 중요하므로, 관리가 용이한 특정 경로에 데이터를 저장하기 위해 hostPath를 이용한 PV를 수동으로 생성하고 바인딩했다.
file: postgresql-master-pv-pvc.yaml
# PV: 실제 서버의 경로와 매핑
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgresql-dev-master-pv
labels:
type: local
spec:
storageClassName: postgresql-dev-pv # PVC와 매핑될 키값
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/srv/k3s/data/postgresql/master" # 실제 데이터 저장 경로
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- jay # 노드 호스트네임 (kubectl get nodes로 확인)
---
# PVC: Helm 차트가 사용할 요청서
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgresql-dev-master-pvc
spec:
storageClassName: postgresql-dev-pv # PV와 동일한 이름 사용
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
selector:
matchLabels:
type: local # PV의 라벨과 일치해야 함
2. Helm Values 설정 (dev-values.yaml)
Bitnami 차트를 커스터마이징 했다. Primary는 정적 PVC를 쓰고, Replica는 동적 프로비저닝 + HPA를 쓰는 하이브리드 구성이다. (HPA는 설정이 제대로 먹지 않았는데,, 그 과정이 2편에서 계속된다.)
file: dev-values.yaml
## Bitnami PostgreSQL Helm Chart Values
# [중요] 이거 안 쓰면 Replica 안 뜬다. (기본값이 standalone임)
architecture: replication
global:
defaultStorageClass: "local-path"
auth:
existingSecret: "postgresql-secret"
secretKeys:
adminPasswordKey: "postgres-password"
userPasswordKey: "password"
replicationPasswordKey: "replication-password"
database: "firebat"
username: "devdb"
primary:
name: "primary"
persistence:
enabled: true
existingClaim: "postgresql-dev-master-pvc" # 위에서 만든 정적 PVC 연결
volumeName: "data"
size: 5Gi
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
readReplicas:
autoscaling:
enabled: true
minReplicas: 1 # [주의] 0으로 설정 시 트래픽 발생하면 장애로 이어짐. 최소 1 유지.
maxReplicas: 3
targetCPUUtilizationPercentage: 80
persistence:
enabled: true
existingClaim: "" # 동적 할당을 위해 비워둠
storageClass: "local-path" # K3s 기본 StorageClass 사용
size: 5Gi
accessModes:
- ReadWriteOnce
volumePermissions:
enabled: true
containerSecurityContext:
runAsUser: 0
privileged: true
🚨 트러블 슈팅 (삽질 로그)
Helm 배포 과정에서 겪었던 주요 이슈들과 해결 방법이다.
Issue 1. “Replica 파드가 아예 안 뜸”
- 현상:
helm install을 했는데 Primary 파드 하나만 뜨고 Replica는 감감무소식. - 원인: Bitnami 차트의 기본 모드는
standalone이다. Replica 설정을 아무리 적어도architecture가 지정되지 않으면 무시된다. - 해결:
values.yaml최상단에architecture: replication추가.
Issue 2. FATAL: password authentication failed for user "repl_user"
- 현상: Primary는 정상인데 Replica 파드가 무한 재시작되며 로그에 인증 실패가 뜸. 비밀번호를 바꾼 적이 없음.
- 원인: “기존 데이터 잔존 문제”. 처음에 Standalone 모드로 띄웠을 때 PVC에 데이터가 생성되었는데, 이때는 복제가 필요 없어
repl_user가 생성되지 않음. 이후 설정을 바꿔 재배포했지만 데이터가 남아있어 초기화 스크립트가 스킵됨. - 해결: 개발 환경이므로 Primary용 PVC를 삭제하고 재배포하여 초기화 스크립트가 다시 돌게 함. (운영 환경이라면 Primary DB에 접속해
CREATE ROLE repl_user ...로 수동 생성 필요)
Issue 3. Replica Scale-out 시 ProvisioningFailed
- 현상: Replica 개수를 늘렸는데 파드가
Pending상태.storageclass.storage.k8s.io "postgresql-dev-pv" not found에러 발생. - 원인: Primary는 내가 만든 정적 PV를 쓰지만, Replica는 HPA로 늘어날 때마다 새로운 PV가 자동으로 생성되어야 한다. 그런데 존재하지 않는 StorageClass 이름을 적었거나,
selector를 남겨둬서 자동 생성을 막고 있었음. - 해결:
readReplicas.persistence.storageClass를 클러스터에 실제 존재하는"local-path"로 변경하고selector삭제.
Issue 4. “StatefulSet is invalid … Forbidden”
- 현상: 설정을 수정하고
helm upgrade를 쳤는데 업데이트 실패. - 원인: StatefulSet의
volumeClaimTemplates(스토리지 설정)은 Immutable(변경 불가) 필드다. 한 번 생성되면 StorageClass 등을 바꿀 수 없다. - 해결: 기존 Replica용 StatefulSet을 삭제하고 다시 배포.
🚀 배포 및 확인
모든 설정을 마치고 배포를 진행한다.
확인 1: 파드 상태
> kubectl get pods
---------------------------------------------------------------
NAME READY STATUS RESTARTS AGE
postgresql-dev-primary-0 1/1 Running 0 4h49m
postgresql-dev-read-0 1/1 Running 0 4h49m
확인 2: PV/PVC 연결 상태
- Primary는 내가 지정한
master-pv와 연결됨. - Replica는
pvc-xxxxx처럼 랜덤한 이름으로local-path를 통해 자동 생성됨.
구성 요약:
- Primary:
postgresql-dev-master-pvc를 통해/srv/k3s/data/postgresql/master경로에 영구 저장. - Replica:
local-pathStorageClass를 통해/srv/k3s/data/pvc-xxx경로에 자동 할당. - HPA: CPU 사용량에 따라 Replica 파드가 1~3개로 자동 조절됨.
📝 마무리
온프레미스 K3s 환경이라 스토리지가 제일 골치였는데, Primary는 정적 할당(안전성), Replica는 동적 할당(유연성/HPA)으로 혼합해서 구성하는 것이 정답이었다.
이제 부하 테스트를 걸어서 HPA가 작동하며 Replica 파드가 늘어나는지(그리고 PV가 자동으로 생기는지) 구경하러 가야겠다. 끝!
You might also like
Comments (0)
Leave a Reply
Or login to comment with your account.
No comments yet. Be the first to share your thoughts!