
Kubernetes는 컨테이너화된 애플리케이션의 배포, 확장, 관리 작업을 자동화하는 컨테이너 오케스트레이션 플랫폼으로, 수작업으로 처리하던 롤링 업그레이드나 스케일링 절차를 추상화하여 효율적인 운영을 가능하게 합니다. 클라우드 네이티브 애플리케이션을 구축하다 보면, 애플리케이션이 클러스터 내에서 데이터베이스 기능을 활용할 수 있도록 PostgreSQL과 같은 데이터베이스를 함께 배포해야 할 때가 많습니다.
이 가이드에서는 구성 가능하면서도 확장성 있는 클라우드 네이티브 PostgreSQL 설정을 통해 Kubernetes 클러스터에 PostgreSQL을 배포하는 방법을 안내합니다. 이 튜토리얼은 Google Kubernetes Engine, Amazon EKS, IBM Cloud Kubernetes Service, 또는 Minikube 같은 로컬 환경에도 적용할 수 있습니다. 이제 Kubernetes에서 단일 PostgreSQL 인스턴스를 배포하는 기본 방법과, Helm, Kubernetes Operators, Bitnami PostgreSQL 등 도구를 활용한 고급 배포 방식을 함께 살펴보겠습니다.
사전 준비 사항
이 가이드를 따라 하려면 다음이 필요합니다.
- Kubernetes 클러스터(DigitalOcean, AWS, Google Cloud, IBM Cloud 등의 클라우드 환경 또는 Kind·Minikube 같은 로컬 환경)
- Kubectl 관련 지식 — Kubernetes API를 제어하는 명령줄 도구에 대한 기본 지식
시작하기 전에 Kubernetes에서 단일 PostgreSQL 인스턴스를 배포하는 기본 단계부터 살펴보겠습니다.
간단한 PostgreSQL 배포
PostgreSQL 도커라이징
Kubernetes는 레지스트리에서 도커(Docker) 이미지를 가져와, 설정 파일에 따라 애플리케이션을 배포합니다. 이러한 단계를 거쳐 PostgreSQL 이미지를 직접 빌드할 수도 있고, 공식 도커 허브(Docker Hub)에 공개된 오픈소스 PostgreSQL 이미지를 사용할 수도 있습니다. 이 포스트에서는 최신 PostgreSQL 15.3 이미지를 사용합니다.
연결 구성 및 시크릿(Secret) 생성
데이터베이스 자격 증명과 같은 민감한 정보를 안전하게 저장하기 위해, Kubernetes의 기본 리소스 중 하나인 Secret 구성을 사용합니다.
Kubernetes는 기본적으로 시크릿을 base64 인코딩된 형태로 저장하지만, 이 방식만으로는 충분히 안전하지 않으므로 저장 시 암호화(Encryption at Rest)를 활성화하는 것이 좋습니다.
구성이 완료되면, Kubernetes 시크릿 리소스를 생성하기 위한 설정 파일을 만듭니다. 이번 예시에서는 PostgreSQL 비밀번호를 아래 값으로 사용합니다.
❯ echo -n "postgres" | base64
cG9zdGdyZXMK
이제 시크릿 구성 파일을 만들고 클러스터에 적용합니다.
> cat postgres-secrets.yml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret-config
type: Opaque
data:
password: cG9zdGdyZXMK
여기서 사용한 kind 값은 Secret으로, Kubernetes가 시크릿(Secret) 리소스로 데이터를 관리하도록 지정합니다. postgres-secret-config는 해당 시크릿 리소스의 이름(name)으로, 이 이름 아래에 값들이 저장됩니다. 마지막으로 데이터 섹션에 비밀리에 저장해야 하는 키-값 쌍(key/value pairs)을 정의했습니다.
이제 이 구성을 적용하고, 값이 올바르게 저장되었는지 확인합니다.
❯ kubectl apply -f postgres-secrets.yml
secret/postgres-secret-config created
❯ kubectl get secret postgres-secret-config -o yaml
apiVersion: v1
data:
password: cG9zdGdyZXMK
....
PersistentVolume과 PersistentVolumeClaim 생성하기
이제 데이터베이스의 데이터를 영구적으로 저장할 파일 스토리지를 생성해야 합니다. 기본적으로 도커 인스턴스는 컨테이너가 종료되면 데이터가 유지되지 않습니다. 따라서 데이터베이스 데이터를 보존하려면 별도의 영구 스토리지를 생성해야 합니다.
이를 해결하는 방법은 PostgreSQL 데이터를 저장할 파일 시스템을 마운트하는 것입니다. Kubernetes에서는 이러한 작업을 위한 별도의 구성 형식이 있으므로, 다음 단계를 따릅니다.
- PersistentVolume(PV) 매니페스트를 생성하여 사용할 볼륨 유형을 정의합니다.
- PersistentVolumeClaim(PVC)을 생성하여 동일한 스토리지 클래스(storage class)를 기반으로 해당 PersistentVolume의 사용을 요청합니다.
이번 예제에서는 현재 노드의 파일 시스템을 볼륨으로 사용하지만, 실제 운영 환경에서는 standard, gp2 등 데이터베이스용 표준 스토리지 클래스나 복제 및 IOPS 최적화를 지원하는 클라우드 제공자 전용 동적 프로비저너(dynamic provisioner)를 사용하는 것이 좋습니다.
먼저 PersistentVolume의 구성을 정의합니다.
> cat pv-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
이 구성에서는 클러스터 노드의 /mnt/data 경로에 5GB의 읽기-쓰기(ReadWrite) 스토리지를 예약하도록 지정했습니다.
이제 구성을 적용하고, PersistentVolume이 정상적으로 생성되었는지 확인합니다.
❯ kubectl apply -f pv-volume.yml
persistentvolume/postgres-pv-volume created
❯ kubectl get pv postgres-pv-volume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
postgres-pv-volume 5Gi RWO Retain Available manual 51s
이제 앞서 정의한 PersistentVolume과 일치하는 PersistentVolumeClaim을 구성합니다.
> cat pv-claim.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
이 구성에서는 동일한 스토리지 클래스 이름을 사용하여 1GB의 PersistentVolumeClaim을 요청했습니다. 이 매개변수는 매우 중요합니다. Kubernetes가 전체 5GB 중 1GB를 이 클레임에 할당할 수 있도록 예약하기 때문입니다.
이제 구성을 적용하고, PersistentVolumeClaim이 정상적으로 바인딩되었는지 확인합니다.
❯ kubectl apply -f pv-claim.yml
persistentvolumeclaim/postgres-pv-claim created
❯ kubectl get pvc postgres-pv-claim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-pv-claim Bound postgres-pv-volume 1Gi RWO manual 5m32s
Kubernetes에서 PostgreSQL 배포하기
앞서 생성한 postgres-secret-config 시크릿 이름과 PersistentVolume, PersistentVolumeClaim 구성을 참조해 이제 Kubernetes 인스턴스의 배포(Deployment) 구성을 만들어 보겠습니다.
> cat postgres-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
volumes:
- name: postgres-pv-storage
persistentVolumeClaim:
claimName: postgres-pv-claim
containers:
- name: postgres
image: postgres:11
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret-config
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-pv-storage
이 구성에서는 앞서 정의한 시크릿 설정과 PersistentVolume 마운트를 모두 통합했습니다. 또한 apiVersion: apps/v1 형식의 배포 구성을 사용했는데, 이 형식에서는 셀렉터(selector)와 메타데이터 등의 필드를 명시적으로 지정해야 합니다. 그다음, 컨테이너 이미지 정보와 이미지 풀 정책(imagePullPolicy)을 정의했습니다. 이러한 설정들은 해당 컨테이너가 올바른 볼륨과 시크릿을 참조하도록 보장하기 위해 필요한 것입니다.
이제 배포를 적용하고 정상적으로 사용 가능한지 확인하겠습니다.
❯ kubectl apply -f postgres-deployment.yml
deployment.apps/postgres created
❯ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
postgres 1/1 1 1 28s
PostgreSQL 서버 노출을 위한 서비스 만들기
이제 Kubernetes Service 리소스를 사용해 PostgreSQL Pod를 외부로 노출시켜야 합니다. 이를 위해 포트를 다르게 설정하거나, NodePort 또는 LoadBalancer 타입으로 노출할 수 있습니다. 여기서는 단순성을 고려하여 NodePort를 사용하는 방법을 보여드리겠습니다. NodePort는 클러스터 노드의 IP에서 고정된 포트로 서비스를 노출하는 방식입니다.
아래와 같은 서비스 매니페스트를 사용하실 수 있습니다.
> cat postgres-service.yml
apiVersion: v1
kind: Service
metadata:
name: postgres
labels:
app: postgres
spec:
type: NodePort
ports:
- port: 5432
selector:
app: postgres
이제 서비스의 구성을 적용하고, 포트가 할당되었는지 또는 제대로 사용할 수 있는지 확인하겠습니다.
❯ kubectl apply -f postgres-service.yml
service/postgres created
❯ kubectl get service postgres
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
PostgreSQL 데이터베이스 연결 테스트하기
이제 내부적으로 PostgreSQL 데이터베이스에 연결할 수 있어야 합니다. 아래 명령어를 사용해 확인해 볼 수 있습니다.
❯ kubectl get pods
NAME READY STATUS RESTARTS AGE
postgres-57f4746d96-7z5q8 1/1 Running 0 30m
❯ kubectl exec -it postgres-57f4746d96-7z5q8 -- psql -U postgres
또는 Kubernetes Pod 이름을 변수로 저장해 더 편리하게 사용할 수도 있습니다
POD=$(kubectl get pods -l app=postgres -o jsonpath="{.items[0].metadata.name}")
다른 도커 컨테이너를 이용해 psql 명령으로 연결할 수도 있습니다.
export POSTGRES_PASSWORD=$(kubectl get secret postgres-secret-config -o jsonpath="{.data.password}" | base64 --decode)
❯ kubectl run postgres-client --rm --tty -i --restart='Never' --image postgres:11 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql -h postgres -U postgres
만약 프롬프트가 표시되지 않는다면 Enter 키를 한 번 눌러보세요.
postgres=#
이제 쿼리를 실행할 준비가 되었습니다.
고급 PostgreSQL 배포
앞선 예시에서는 개발 목적으로 단일 PostgreSQL 인스턴스만 배포했습니다. 그러나 실제 프로덕션이나 하이브리드 클라우드 환경에서는 다음과 같은 고급 배포 옵션을 고려할 수 있습니다.
- 비트나미(Bitnami) PostgreSQL 배포: 비트나미는 Helm(커뮤니티에서 관리하는 Kubernetes 패키지 관리자) 등 다양한 형태의 배포를 지원하며, 대규모 환경에 적합한 구성 옵션을 제공합니다.
- 잘란도(Zalando) PostgreSQL 오퍼레이터: 기존 Patroni 기반 클러스터 템플릿을 활용하는 고가용성 Kubernetes 오퍼레이터로, 안정적이고 확장 가능한 PostgreSQL 클러스터 운영을 지원합니다.
이러한 옵션들은 엔터프라이즈급 인프라 구축과 클라우드 네이티브 컴퓨팅 기반의 모범 사례를 따르는 데 도움이 됩니다.
다음 단계
이제 Kubernetes에서 단일 PostgreSQL 인스턴스를 성공적으로 배포했으므로, 다음 단계로 모니터링과 로깅을 통합할 차례입니다. Sumo Logic과 같은 도구는 고급 Kubernetes 모니터링을 지원하며, 클러스터 리소스를 관리하고, Pod 로그를 분석하며, 클라우드 네이티브 애플리케이션의 옵저빌리티를 확보하도록 돕습니다. 기본적으로 클레임이 삭제되면 연결된 볼륨도 함께 삭제될 수 있습니다. 따라서 반환 정책(Reclaim Policy)을 보존(Retain)으로 설정하면 PVC가 제거되더라도 데이터가 유지됩니다.
Sumo Logic을 통한 Kubernetes 모니터링 개선 방법을 알아보세요.



