Files
service-catalog/doc/migrations/dipup-postgresql-ha-cnpg-handoff.md
T
wbsong111 3ee1c1ee23 카탈로그 앱 내장 bitnami postgresql → cnpg-cluster 전환 (1차 5개 차트)
이슈 #9에서 결정된 CloudNativePG 채택을 카탈로그 앱에 확장 적용한다.
airflow/lakekeeper/mlflow/superset/flowise 5개 차트가 내장하던 bitnami
postgresql 서브차트를 끄고 앱 전용 cnpg-cluster 인스턴스를 외부 DB로 쓰도록
전환했다. 5개 모두 dev 클러스터 격리 네임스페이스에서 배포 테스트로 실측 검증했다.

gitea/keycloak/dnsup 는 서브차트가 아니라 공유 postgresql-ha 를 외부 참조하며
paasup/dipup 레포 관리 대상이라 제외했다 — 인수인계 문서만 추가했다.

배포 구조:
- ArgoCD ApplicationSet 으로 DB(syncWave 0) → 앱(syncWave 1) 순서를 보장한다.
  기존 openmetadata/victoria-metrics 관례를 따랐다. cnpg-cluster 차트는 범용
  상태로 유지하고 앱별 값은 manifests/applicationset/<app>/ 에 둔다.

검증 중 발견해 함께 고친 문제:
- lakekeeper: cnpg 의 -ro 는 replica 전용이라 instances:1 에서 엔드포인트가
  0개다. 읽기 연결을 -r(전체 라운드로빈)로 교체했다.
- airflow/superset: ingressClassName 누락 + kong 애노테이션 잔존으로 이
  클러스터(apisix 전용)에서 ingress 접근이 아예 불가능했다. apisix + regex
  path 로 교체했다.
- 배포 테스트가 PV 만 지우고 Longhorn Volume CR 을 남겨 storageScheduled 가
  누적됐다(orphan 112개 ~1TB 로 배포 차단). 두 스크립트의 정리 로직을 고쳤다.

재사용 구조화:
- .claude/skills/chart-to-cnpg/ 신규. 남은 4개 차트(langflow-ide, langfuse,
  litellm, nemo)에 같은 절차를 재사용한다. flowise 에 실제 적용해 검증했다.
- 배포 테스트 공통 절차는 .claude/deploy-test-procedure.md, 환경 함정은
  .claude/pitfalls.md 로 단일화하고 앱별 README 는 참조만 남겼다.

관련: #9, #14

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:17:46 +09:00

6.3 KiB

dipup 인수인계 — postgresql-ha → cnpg-cluster (gitea/keycloak/dnsup)

이 문서는 실행 문서가 아니다. dip-catalog가 아닌 paasup/dipup 레포에서 관리되는 gitea/keycloak/dnsup의 DB 전환 작업을 위해, dip-catalog 쪽에서 파악한 내용을 정리해 넘기는 참고 자료다. dip-catalog는 이 3개 앱과 postgresql-ha를 직접 변경하지 않는다.

배경

카탈로그의 postgresql-ha(bitnami postgresql-repmgr + pgpool, manifests/helm/postgresql-ha/11.9.4/)는 현재 platform 네임스페이스에 배포되어 있고, gitea·keycloak·dnsup 3개 앱이 이 인스턴스의 4개 DB를 쓴다.

DB 소비 앱 owner role
gitea gitea gitea
gitea_session gitea (세션 저장소) gitea
keycloak keycloak keycloak
paasup dnsup paasup

GitHub 이슈 #9에서 이 인스턴스를 CloudNativePG(cnpg-cluster + cloudnative-pg operator)로 대체하기로 이미 결정되었다 (2026-07-31). dip-catalog에는 이미 cloudnative-pg(0.29.0, operator)·cnpg-cluster (1.0.0, Cluster/Database 래퍼 차트)가 추가되어 있고, 이번 작업(카탈로그 앱 airflow/lakekeeper/mlflow/superset 전환)에서 이 두 차트로 4개 앱의 내장 DB를 전용 인스턴스로 옮기는 배포 테스트까지 실측 검증했다.

현재 연결 정보 (변경 지점)

3개 앱은 서브차트가 아니라 외부 호스트를 직접 참조한다. cnpg-cluster로 전환 시 아래 지점만 바뀐다.

파일 현재 값 변경 후 (예: 릴리스명 platform-db)
gitea gitea.config.database.HOST postgresql-postgresql-ha-postgresql:5432 platform-db-rw:5432
gitea gitea.config.session.PROVIDER_CONFIG host=postgresql-postgresql-ha-postgresql host=platform-db-rw
keycloak extraEnv (DB_ADDR) postgresql-postgresql-ha-postgresql platform-db-rw
dnsup env.DB_HOST postgresql-postgresql-ha-postgresql.platform.svc.cluster.local platform-db-rw.platform.svc.cluster.local

cnpg-cluster의 서비스 명명 규칙은 <릴리스명>-rw(쓰기/기본 연결)이다 — 자세한 내용은 manifests/helm/cnpg-cluster/1.0.0/CUSTOM-README.md §3 참고.

선결 과제 — 공유 인스턴스 하나로 4개 DB를 만들 수 없다

cnpg-cluster 차트의 bootstrap.initdbDB 1개 + owner role 1개만 최초 생성한다. databases[](Database CRD)로 DB를 추가할 수는 있지만 owner role은 미리 존재해야 한다 — role 자체를 새로 만들지 못한다. postgresql-hainitdbScripts처럼 gitea/keycloak/paasup 3개 role을 한 번에 만들 방법이 지금 차트에는 없다.

이번 dip-catalog 작업(airflow/lakekeeper/mlflow/superset)은 각 앱이 자기 전용 인스턴스를 쓰는 구조라 이 문제를 만나지 않았다(인스턴스마다 role 1개만 필요). gitea/keycloak/dnsup는 원래 인스턴스 1개를 공유하던 구조라 선택이 필요하다.

옵션 A — 공유 인스턴스 유지 (기존 토폴로지와 동일)

bootstrap.initdb.postInitSQL(CNPG 업스트림 API에 있는 필드 — superuser로 최초 1회, DB 생성 전 단계에 실행되는 SQL. 기존 postInitApplicationSQL과 달리 CREATE ROLE/ CREATE DATABASE에 적합)을 manifests/helm/cnpg-cluster/1.0.0/templates/cluster.yaml· values.yaml에 추가해야 한다. 이 필드로 keycloak/paasup role을 만들고(gitea role은 bootstrap.initdb.owner로 자동 생성), databases[]로 gitea/gitea_session/keycloak/ paasup 4개 DB를 선언하는 방식이다. 이 작업은 dip-catalog 쪽 차트 변경이 선행되어야 하므로, dipup에서 이 옵션을 원하면 dip-catalog에 먼저 요청이 필요하다.

옵션 B — 앱별 전용 인스턴스 3개 (이번 작업에서 검증된 패턴 그대로)

gitea용, keycloak용, dnsup(paasup)용 cnpg-cluster 릴리스를 각각 배포한다. 각 인스턴스는 bootstrap.initdb.database/owner만 채우면 되므로 차트 수정이 필요 없다manifests/applicationset/airflow/1.16.0/airflow-db-values.yaml 등과 동일한 패턴의 값 오버레이 3개(dipup 쪽에 gitea-db-values.yaml, keycloak-db-values.yaml, dnsup-db-values.yaml 격)만 있으면 된다. 이 값들은 ArgoCD ApplicationSet의 valuesPath로 연결하는 것을 권장한다 — manifests/applicationset/{airflow,lakekeeper,mlflow,superset}/가 그 구조의 실제 예시다(DB 차트 syncWave 0 → 앱 차트 syncWave 1). gitea의 gitea_session DB는 같은 인스턴스 안에서 databases[]owner: gitea로 추가하면 된다(role은 이미 bootstrap로 생성됨).

단점은 postgres 파드 수가 늘어나는 것(인스턴스 3개 × instances 설정값)이다 — 리소스는 manifests/helm/cnpg-cluster/1.0.0/dip-resources-quotas.yamlsmall(instances:1) 기준을 참고한다.

권장: 별도 사유가 없다면 옵션 B — dip-catalog 차트 변경 없이 바로 시작할 수 있고, 이번 4개 앱 전환에서 동일 패턴이 실측 검증되었다.

이번 작업에서 실측된 주의사항 (그대로 적용됨)

  • cnpg-cluster의 -ro(replica 전용) 서비스는 instances:1이면 엔드포인트가 0개다. 읽기/쓰기를 분리해서 연결하는 앱이 있다면 읽기는 -ro가 아니라 -r(전체 인스턴스 라운드로빈)을 쓴다 — lakekeeper/0.11.0/custom-values.yaml에서 실측.
  • cloudnative-pg operator(namespace cnpg-system, release cnpg)는 클러스터에 이미 1회 설치되어 있으므로 재사용하면 된다 — 새로 설치할 필요 없다.
  • DB owner 계정의 비밀번호는 bootstrap.initdb.secretName으로 지정한 시크릿(키: username/password)에서 온다. 앱 쪽 값(예: keycloak의 DB_PASSWORD)과 반드시 일치해야 한다.

이번 작업 범위에 포함되지 않은 것

  • gitea/keycloak/dnsup의 custom-values.yaml 실제 수정 (dipup 관리 대상)
  • postgresql-ha 카탈로그 항목 제거 여부 판단
  • 실 데이터 마이그레이션(pg_dump/pg_restore) 및 컷오버 절차, 다운타임 계획, 롤백 계획 — SSO(keycloak)·git(gitea) 서비스에 영향을 주는 운영 작업이라 별도 계획·승인이 필요하다.