카탈로그 앱 내장 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>
This commit is contained in:
wbsong111
2026-08-05 15:17:46 +09:00
parent 35ce2bb80d
commit 3ee1c1ee23
42 changed files with 1650 additions and 146 deletions
@@ -0,0 +1,97 @@
# 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](https://github.com/paasup/dip-catalog/issues/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-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) 서비스에 영향을 주는 운영 작업이라 별도 계획·승인이 필요하다.