CloudNativePG를 통한 오프라인 인플레이스 메이저 업그레이드

작성자: Jonathan Battiato
게시일: 2025년 6월 23일

CloudNativePG(CNPG)는 이제 많은 사용자와 기업에서 채택한 Kubernetes 오퍼레이터입니다. 사용자 피드백을 반영하여 지속적으로 개선되어 왔지만, 아직 구현되지 않은 몇 가지 주요 기능이 있었습니다. 그중에서도 가장 많이 요청되었던 기능 중 하나는 PostgreSQL의 메이저 버전을 선언형으로 업그레이드하는 기능이었습니다. 이제 더 이상 기다릴 필요가 없습니다.

CloudNativePG 1.26 버전에서는 PostgreSQL 기본 도구인 pg_upgrade를 활용한 선언형 오프라인 인플레이스 메이저 업그레이드가 도입되었습니다.

이 블로그에서는 다양한 PostgreSQL 설정을 가진 CNPG 클러스터를 YAML 매니페스트의 태그 한 줄만 변경하여 더 높은 버전으로 쉽게 업그레이드할 수 있는 방법을 소개합니다.


오프라인 인플레이스 메이저 업그레이드란?

PostgreSQL에서 메이저 업그레이드는 많은 시스템 관리자와 DBA에게 숙면을 방해하는 작업입니다. 이는 불가능해서가 아니라, 사전 준비와 테스트에 많은 시간이 들고, 실패 시 수동 롤백이 필요할 수 있기 때문입니다.

왜 까다로울까?

마이너 버전 업그레이드는 PostgreSQL 바이너리 교체만으로도 가능한 경우가 많지만, 메이저 버전 업그레이드는 다릅니다. 메이저 버전 간에는 바이너리 호환성이 없기 때문에 디스크에 저장된 데이터 형식도 달라집니다. 예를 들어 PostgreSQL 17 서버는 PostgreSQL 16 바이너리로 생성된 데이터 디렉토리를 직접 읽을 수 없습니다.

이 문제를 해결하기 위해 일반적으로 두 가지 접근법이 사용됩니다:

  • pg_dumppg_restore를 사용한 논리적 백업/복원
  • pg_upgrade를 통한 바이너리 수준 업그레이드

이러한 방식은 모두 **“오프라인 업그레이드”**에 해당하며, “온라인 업그레이드” 방식과 구분됩니다. 온라인 업그레이드는 논리적 스트리밍 복제를 사용하여 기존 서버와 새로운 서버를 동시에 운영하며 데이터를 마이그레이션하는 방식입니다. 이 경우, 애플리케이션의 전환 시간은 새 서버를 가리키도록 구성만 바꾸면 되므로 매우 짧습니다(일명 블루/그린 배포). 반면, 오프라인 업그레이드는 데이터 마이그레이션 중에 애플리케이션을 완전히 중단해야 합니다.


CNPG의 새로운 기능은 무엇인가요?

논리적 스트리밍 복제는 비교적 최신 기술로 PostgreSQL 메이저 버전 마이그레이션의 부담을 줄여주지만, 한계도 있습니다:

  • 시퀀스를 자동으로 동기화할 수 없습니다.
  • 동기화를 위해 새로운 클러스터가 필요하며, 이는 노드 수와 디스크 사용량을 두 배로 만듭니다.

CloudNativePG는 논리적 복제를 지원하지만, 이번에 도입된 **“오프라인 인플레이스 메이저 업그레이드”**는 말 그대로 기존 클러스터를 “그 자리에서” 새로운 버전으로 교체하는 방식입니다.

이 방식의 장점은 다음과 같습니다:

  • pg_dump/restore보다 훨씬 빠름
  • 디스크 공간 절약 (기존 데이터 디렉토리를 대부분 재사용)
  • YAML 정의 파일의 Postgres 이미지 버전만 바꾸면 되므로 완전히 선언형 방식

어떻게 작동하나요?

CNPG는 pg_upgrade 바이너리를 사용하며, --link 옵션을 활용하여 소스 디렉토리와 대상 디렉토리 간에 하드링크를 생성합니다. 이를 통해 변경이 필요한 파일만 새로 생성하고 나머지는 재사용함으로써 시간과 공간을 절약합니다.

PostgreSQL 버전 관리 방법은 다음과 같습니다:

  • Cluster CRD에서 imageName 필드 사용
  • 또는, ImageCatalog CRD를 Cluster에서 참조

이 중 어느 쪽이든 이미지 버전을 변경하면 Operator가 자동으로 업그레이드를 시작합니다.

업그레이드 절차

  1. 모든 클러스터 파드를 중지하여 데이터 일관성 확보
  2. 새로운 업그레이드 Job 시작:
    • 이미지의 바이너리와 데이터 파일이 메이저 업그레이드에 적합한지 확인
    • 새로운 PGDATA, WAL, tablespace 디렉토리 생성
    • --link 옵션으로 pg_upgrade 수행
    • 성공 시, 기존 디렉토리를 업그레이드된 디렉토리로 교체

실습 예시

PostgreSQL 13 버전의 CNPG 클러스터가 존재한다고 가정

cat cluster-example.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:13.21
instances: 3
storage:
   size: 1Gi
postgresql:
   synchronous:
    method: any
    number: 1
kubectl get cluster
NAME     AGE    INSTANCES READY STATUS    PRIMARY
cluster-example 15m 3    3    Cluster in healthy state cluster-example-1
kubectl get cluster cluster-example -o jsonpath='{.spec.imageName}'
ghcr.io/cloudnative-pg/postgresql:13.21

테이블을 생성하고 데이터를 삽입해봅시다:

kubectl cnpg psql cluster-example -- app
psql (13.21 (Debian 13.21-1.pgdg110+1))
Type "help" for help.
app=# create table numbers(x int);
CREATE TABLE
app=# insert into numbers (select generate_series(1,10000));
INSERT 0 10000
app=# select count(*) from numbers ;
count
-------
10000
(1 row)

클러스터의 imageName을 PostgreSQL 17.5로 변경하고, 다른 터미널에서 리소스를 관찰해봅시다.

kubectl patch cluster cluster-example \
--type='json' \
-p='[{"op":"replace", "path":"/spec/imageName", "value":"ghcr.io/cloudnative-pg/postgresql:17.5"}]'
kubectl get pods -w
NAME READY   STATUS     RESTARTS        AGE
[...]
cluster-example-1 1/1    Terminating 0          34m
cluster-example-2 1/1    Terminating 0          33m
cluster-example-3 1/1    Terminating 0          33m
cluster-example-1-major-upgrade-vg9hc 0/1 Pending    0    0s
cluster-example-1-major-upgrade-vg9hc 0/1 Init:1/2     0    1s
cluster-example-1-major-upgrade-vg9hc 0/1 PodInitializing 0    2s
cluster-example-1-major-upgrade-vg9hc 1/1 Running    0    99s
cluster-example-1-major-upgrade-vg9hc 0/1 Completed    0    2m6s
cluster-example-1     0/1 Pending    0    0s
cluster-example-1    0/1 Init:0/1     0    0s
cluster-example-1    0/1 PodInitializing 0    1s
cluster-example-1     0/1 Running    0    2s
cluster-example-1    1/1 Running    0    11s
[...]

기본 인스턴스만 업그레이드되었으며, 이를 기반으로 숫자가 증가된 이름을 가진 새로운 레플리카가 생성되었습니다.

kubectl get pods
NAME READY STATUS RESTARTS AGE
cluster-example-1 1/1 Running 0 5m25s
cluster-example-4 1/1 Running 0 4m56s
cluster-example-5 1/1 Running 0 4m24s
kubectl cnpg status cluster-example-custom
Cluster Summary
Name default/cluster-example-custom
System ID: 7512075037961891879
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:17.5 # New image
Primary instance: cluster-example-custom-1
Primary start time: 2025-06-04 12:12:02 +0000 UTC (uptime 23h50m54s)
Status: Cluster in healthy state
Instances: 3
Ready instances: 3
[...]

데이터가 잘 유지되고 있는지 확인해보겠습니다.

kubectl cnpg psql cluster-example -- app
psql (17.5 (Debian 17.5-1.pgdg110+1))
Type "help" for help.
app=# select count(*) from numbers ;
count
-------
10000
(1 row)

이제 데이터베이스에서 통계 정보를 재작성해봅시다.

kubectl cnpg psql cluster-example -- app
psql (17.5 (Debian 17.5-1.pgdg110+1))
Type "help" for help.
app=# VACUUM ANALYZE;

완료되었습니다!

유의사항 및 한계

  • 업그레이드 완료 후, 기존 레플리카는 삭제되고 새로 생성됨 (대용량일 경우 시간 소요)
  • 오프라인 방식이므로 다운타임 발생
  • 업그레이드 실패 시 수동 조치 필요

중요 참고사항

VACUUM ANALYZE 필수

업그레이드 후 PostgreSQL의 쿼리 성능 최적화를 위해 VACUUM ANALYZE를 반드시 실행해야 합니다. 현재 CNPG는 이 명령을 자동 실행하지 않습니다.

PostgreSQL 베이스 이미지 변경 시 주의

예: PostgreSQL 17(Debian Bullseye 기반)에서 PostgreSQL 18(Debian Bookworm 기반)으로 업그레이드하려면, 먼저 Bookworm 기반의 PostgreSQL 17 이미지로 전환한 후 PostgreSQL 18로 업그레이드해야 합니다.


결론

CloudNativePG는 PostgreSQL 클러스터의 전체 라이프사이클을 관리하는 강력한 Kubernetes 오퍼레이터입니다. 이번에 도입된 오프라인 인플레이스 메이저 업그레이드 기능은 자동화된 방식으로 더 빠르고 안정적인 PostgreSQL 업그레이드를 가능하게 하여 DBA의 수고를 덜고 다운타임도 최소화합니다.

위 실습 예시처럼, CNPG 오퍼레이터는 업그레이드에 필요한 모든 단계를 자동으로 처리합니다. 단일 인스턴스든 다중 인스턴스든, 동기 복제를 사용하든 관계없이 안정적으로 업그레이드를 수행합니다.

개발, 테스트, 운영 환경의 자동화를 가속화하고 싶다면, CNPG를 CI/CD 파이프라인에 통합해 보세요.

CloudNativePG는 CNCF Sandbox 프로젝트이며, EDB가 주도적으로 개발에 참여하고 있는 완전한 오픈소스입니다. Kubernetes 기반 데이터베이스 마이그레이션을 가속화하고 싶다면 언제든지 EDB에 문의하세요.



문의 메일: salesinquiry@enterprisedb.com