카탈로그 앱 내장 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:
@@ -16,12 +16,15 @@ DIP 카탈로그의 Helm 차트에 대한 상세한 리소스 요구사항을
|
||||
| **Workers** | 500m/1Gi → 2000m/2Gi | 1000m/2Gi → 4000m/4Gi | 2000m/4Gi → 8000m/8Gi |
|
||||
| **Webserver** | 200m/256Mi → 500m/512Mi | 500m/512Mi → 1000m/1Gi | 1000m/1Gi → 2000m/2Gi |
|
||||
| **Redis** | 100m/128Mi → 200m/256Mi | 200m/256Mi → 500m/512Mi | 500m/512Mi → 1000m/1Gi |
|
||||
| **PostgreSQL** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
|
||||
> **PostgreSQL**: 내장 서브차트 대신 전용 `cnpg-cluster` 인스턴스를 외부 DB로 사용한다
|
||||
> (`postgresql.enabled: false` + `data.metadataConnection`). 리소스/볼륨 티어는
|
||||
> `manifests/helm/cnpg-cluster/1.0.0/dip-resources-quotas.yaml` ·
|
||||
> `dip-volumes-quotas.yaml` 참고.
|
||||
|
||||
**볼륨 사이즈 권장사항:**
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **PostgreSQL Data** | 10Gi | 50Gi | 200Gi |
|
||||
| **Redis Data** | 1Gi | 5Gi | 20Gi |
|
||||
| **Logs Volume** | 5Gi | 20Gi | 100Gi |
|
||||
| **DAGs Volume** | 1Gi | 5Gi | 20Gi |
|
||||
@@ -261,12 +264,15 @@ resources: {}
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **API Server** | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi | 2000m/4Gi → 4000m/8Gi |
|
||||
| **Database** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
|
||||
> **Database**: 내장 서브차트 대신 전용 `cnpg-cluster` 인스턴스를 외부 DB로 사용한다
|
||||
> (`postgresql.enabled: false` + `externalPostgresql.*`). 리소스/볼륨 티어는
|
||||
> `manifests/helm/cnpg-cluster/1.0.0/dip-resources-quotas.yaml` ·
|
||||
> `dip-volumes-quotas.yaml` 참고.
|
||||
|
||||
**볼륨 사이즈 권장사항:**
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **Database Data** | 5Gi | 20Gi | 100Gi |
|
||||
| **Flow Storage** | 2Gi | 10Gi | 50Gi |
|
||||
|
||||
**권장 사용 시나리오:**
|
||||
@@ -295,20 +301,19 @@ worker:
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# 내장 postgresql 대신 전용 cnpg-cluster 인스턴스를 외부 DB로 사용한다.
|
||||
# flowise는 postgresql.enabled: false 만으로는 부족하고 externalPostgresql.enabled: true 를
|
||||
# 함께 켜야 한다.
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
externalPostgresql:
|
||||
enabled: true
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: "longhorn"
|
||||
size: 5Gi
|
||||
resources: {}
|
||||
# limits:
|
||||
# memory: 512Mi
|
||||
# cpu: 500m
|
||||
# requests:
|
||||
# memory: 256Mi
|
||||
# cpu: 250m
|
||||
host: flowise-db-rw
|
||||
port: 5432
|
||||
username: flowise
|
||||
password: flowise
|
||||
database: flowise
|
||||
```
|
||||
|
||||
---
|
||||
@@ -830,13 +835,16 @@ postgresql:
|
||||
|----------|-------|--------|-------|
|
||||
| **Tracking Server** | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi | 2000m/4Gi → 4000m/8Gi |
|
||||
| **Run Container** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
| **PostgreSQL** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
| **MinIO** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
|
||||
> **PostgreSQL**: 내장 서브차트 대신 전용 `cnpg-cluster` 인스턴스를 외부 DB로 사용한다
|
||||
> (`postgresql.enabled: false` + `externalDatabase.*`). 리소스/볼륨 티어는
|
||||
> `manifests/helm/cnpg-cluster/1.0.0/dip-resources-quotas.yaml` ·
|
||||
> `dip-volumes-quotas.yaml` 참고.
|
||||
|
||||
**볼륨 사이즈 권장사항:**
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **PostgreSQL Data** | 5Gi | 20Gi | 100Gi |
|
||||
| **MinIO Storage** | 20Gi | 200Gi | 1Ti |
|
||||
| **Artifacts** | 10Gi | 100Gi | 500Gi |
|
||||
|
||||
@@ -851,13 +859,6 @@ tracking:
|
||||
resources: {}
|
||||
run:
|
||||
resources: {}
|
||||
|
||||
postgresql:
|
||||
primary:
|
||||
resources: {}
|
||||
persistence:
|
||||
storageClass: ""
|
||||
size: 5Gi
|
||||
```
|
||||
|
||||
|
||||
@@ -1565,13 +1566,16 @@ resources:
|
||||
| **Superset Worker** | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi | 2000m/4Gi → 4000m/8Gi |
|
||||
| **Celery Beat** | 100m/256Mi → 200m/512Mi | 200m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi |
|
||||
| **Celery Flower** | 100m/256Mi → 200m/512Mi | 200m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi |
|
||||
| **PostgreSQL** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
| **Redis** | 200m/256Mi → 500m/512Mi | 500m/512Mi → 1000m/1Gi | 1000m/1Gi → 2000m/2Gi |
|
||||
|
||||
> **PostgreSQL**: 내장 서브차트 대신 전용 `cnpg-cluster` 인스턴스를 외부 DB로 사용한다
|
||||
> (`postgresql.enabled: false` + `supersetNode.connections.db_*`). 리소스/볼륨 티어는
|
||||
> `manifests/helm/cnpg-cluster/1.0.0/dip-resources-quotas.yaml` ·
|
||||
> `dip-volumes-quotas.yaml` 참고.
|
||||
|
||||
**볼륨 사이즈 권장사항:**
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **PostgreSQL Data** | 8Gi | 50Gi | 200Gi |
|
||||
| **Redis Data** | 8Gi | 20Gi | 100Gi |
|
||||
|
||||
**권장 사용 시나리오:**
|
||||
@@ -1974,14 +1978,18 @@ metadata:
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **Lakekeeper Catalog** | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi | 2000m/4Gi → 4000m/8Gi |
|
||||
| **PostgreSQL** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
| **OpenFGA** | 200m/256Mi → 500m/512Mi | 500m/512Mi → 1000m/1Gi | 1000m/1Gi → 2000m/2Gi |
|
||||
| **OpenFGA PostgreSQL** | 250m/512Mi → 500m/1Gi | 500m/1Gi → 1000m/2Gi | 1000m/2Gi → 2000m/4Gi |
|
||||
|
||||
> **Lakekeeper PostgreSQL**: 내장 `groundhog2k/postgres` 서브차트 대신 전용 `cnpg-cluster`
|
||||
> 인스턴스를 외부 DB로 사용한다(`postgresql.enabled: false` + `externalDatabase.*`). 리소스/볼륨
|
||||
> 티어는 `manifests/helm/cnpg-cluster/1.0.0/dip-resources-quotas.yaml` ·
|
||||
> `dip-volumes-quotas.yaml` 참고. **OpenFGA PostgreSQL은 여전히 내장 bitnami 서브차트를
|
||||
> 사용한다** — 이번 전환 범위에 포함되지 않았다.
|
||||
|
||||
**볼륨 사이즈 권장사항:**
|
||||
| 컴포넌트 | Small | Medium | Large |
|
||||
|----------|-------|--------|-------|
|
||||
| **Lakekeeper PostgreSQL** | 5Gi | 20Gi | 100Gi |
|
||||
| **OpenFGA PostgreSQL** | 8Gi | 30Gi | 150Gi |
|
||||
|
||||
**권장 사용 시나리오:**
|
||||
@@ -2000,18 +2008,18 @@ catalog:
|
||||
# cpu: 500m
|
||||
# memory: 1Gi
|
||||
|
||||
# lakekeeper DB
|
||||
# lakekeeper DB — 전용 cnpg-cluster 인스턴스를 외부 DB로 사용한다
|
||||
postgresql:
|
||||
storage:
|
||||
className: ""
|
||||
requestedSize: 5Gi
|
||||
resources: {}
|
||||
# limits:
|
||||
# cpu: 500m
|
||||
# memory: 1Gi
|
||||
# requests:
|
||||
# cpu: 250m
|
||||
# memory: 512Mi
|
||||
enabled: false
|
||||
|
||||
externalDatabase:
|
||||
type: postgres
|
||||
host_read: "lakekeeper-db-r" # instances:1 에서는 -ro가 엔드포인트 0개라 -r을 쓴다
|
||||
host_write: "lakekeeper-db-rw"
|
||||
port: 5432
|
||||
database: lakekeeper
|
||||
user: lakekeeper
|
||||
password: lakekeeper
|
||||
|
||||
|
||||
openfga:
|
||||
|
||||
@@ -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) 서비스에 영향을 주는 운영 작업이라 별도 계획·승인이 필요하다.
|
||||
Reference in New Issue
Block a user