이슈 #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>
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.initdb는 DB 1개 + owner role 1개만 최초 생성한다.
databases[](Database CRD)로 DB를 추가할 수는 있지만 owner role은 미리 존재해야
한다 — role 자체를 새로 만들지 못한다. postgresql-ha의 initdbScripts처럼
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.yaml의 small(instances:1) 기준을
참고한다.
권장: 별도 사유가 없다면 옵션 B — dip-catalog 차트 변경 없이 바로 시작할 수 있고, 이번 4개 앱 전환에서 동일 패턴이 실측 검증되었다.
이번 작업에서 실측된 주의사항 (그대로 적용됨)
- cnpg-cluster의
-ro(replica 전용) 서비스는instances:1이면 엔드포인트가 0개다. 읽기/쓰기를 분리해서 연결하는 앱이 있다면 읽기는-ro가 아니라-r(전체 인스턴스 라운드로빈)을 쓴다 —lakekeeper/0.11.0/custom-values.yaml에서 실측. cloudnative-pgoperator(namespacecnpg-system, releasecnpg)는 클러스터에 이미 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) 서비스에 영향을 주는 운영 작업이라 별도 계획·승인이 필요하다.