cnpg-cluster 1.1.0 — postInitApplicationSQLRefs 노출
앱별 부트스트랩 SQL 을 ConfigMap/Secret 참조로 실행할 수 있게 한다. CNPG CRD 에는 이미 있는 필드(operator 0.29.0/CRD 확인됨)인데 차트가 렌더하지 않아 values 로 쓸 수 없었다. 용도: 스키마 덤프처럼 큰 SQL 을 values 에 인라인하지 않고 ConfigMap 으로 넘기는 경우. postInitApplicationSQL(인라인)과 시점·권한이 동일(클러스터 생성 직후 1회, 앱 DB 안에서 superuser) 하고 SQL 출처만 다르다. 1회성이라 비멱등 SQL(CREATE TABLE 등)을 그대로 넣어도 재실행되지 않는다. - values.yaml: bootstrap.initdb.postInitApplicationSQLRefs 추가(configMapRefs/secretRefs, 기본 빈 배열) - templates/cluster.yaml: 값이 있을 때만 렌더(있으면 렌더, 없으면 필드 자체가 안 나옴 — 기존 클러스터에 영향 없음을 렌더 테스트로 확인) - CUSTOM-README.md: postInitApplicationSQL 과의 차이·참조 처리 순서(Secret 전체 → ConfigMap 전체) 문서화 카탈로그 관례 파일(BUILD-README·CUSTOM-README 본문 대부분·dip-*.yaml·custom-values.yaml)은 1.0.0 에서 그대로 승계했다(diff 로 확인). 검증: 빈 값일 때 필드 미노출, configMapRefs 지정 시 정상 렌더, 기존 1.0.0 custom-values.yaml 로 1.1.0 렌더해도 문제없음(하위 호환) 확인. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
|||||||
|
# cnpg-cluster 버전 갱신 가이드
|
||||||
|
|
||||||
|
이 차트는 **PaaSup 자체 제작**이다. 업스트림 차트를 내려받는 `cloudnative-pg` 와 달리
|
||||||
|
`helm pull` 로 갱신하지 않는다. 갱신 사유는 두 가지다.
|
||||||
|
|
||||||
|
1. PostgreSQL major 버전 상향 (예: 18 → 19)
|
||||||
|
2. CNPG operator 버전 상향으로 CRD 필드가 바뀐 경우
|
||||||
|
|
||||||
|
## 1. git 작업 환경 구성
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/paasup/dip-catalog.git
|
||||||
|
cd dip-catalog
|
||||||
|
git checkout -b update-cnpg-cluster/<신규버전>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 신규 버전 디렉토리 생성
|
||||||
|
|
||||||
|
```sh
|
||||||
|
NEW=1.1.0
|
||||||
|
OLD=1.0.0
|
||||||
|
cd manifests/helm/cnpg-cluster
|
||||||
|
cp -R "$OLD" "$NEW"
|
||||||
|
# Chart.yaml 의 version / appVersion 갱신
|
||||||
|
```
|
||||||
|
|
||||||
|
| 필드 | 의미 |
|
||||||
|
| --- | --- |
|
||||||
|
| `version` | 이 차트의 버전 (디렉토리명과 일치시킨다) |
|
||||||
|
| `appVersion` | 배포되는 PostgreSQL 버전 |
|
||||||
|
|
||||||
|
## 3. CRD 스키마 대조
|
||||||
|
|
||||||
|
operator 를 올렸다면 `Cluster` / `Database` / `Pooler` / `ScheduledBackup` CRD 스키마가
|
||||||
|
바뀌었을 수 있다. 템플릿이 쓰는 필드가 아직 존재하는지 확인한다.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 템플릿이 참조하는 spec 필드 목록 확인
|
||||||
|
kubectl get crd clusters.postgresql.cnpg.io -o json | python3 -c "
|
||||||
|
import json,sys
|
||||||
|
d=json.load(sys.stdin)
|
||||||
|
print(sorted(d['spec']['versions'][0]['schema']['openAPIV3Schema']
|
||||||
|
['properties']['spec']['properties'].keys()))
|
||||||
|
"
|
||||||
|
|
||||||
|
kubectl get crd databases.postgresql.cnpg.io -o json | python3 -c "
|
||||||
|
import json,sys
|
||||||
|
d=json.load(sys.stdin)
|
||||||
|
print(sorted(d['spec']['versions'][0]['schema']['openAPIV3Schema']
|
||||||
|
['properties']['spec']['properties'].keys()))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 렌더링·검증
|
||||||
|
|
||||||
|
```sh
|
||||||
|
NEW=1.1.0
|
||||||
|
cd manifests/helm/cnpg-cluster/$NEW
|
||||||
|
|
||||||
|
# 1) lint
|
||||||
|
helm lint . -f custom-values.yaml
|
||||||
|
|
||||||
|
# 2) 기본 경로 렌더링
|
||||||
|
helm template pg-cnpg . -f custom-values.yaml -n test >/dev/null && echo OK
|
||||||
|
|
||||||
|
# 3) 옵션 경로(backup/pooler/scheduledBackup) 렌더링 — 기본값이 false 이므로 별도 확인 필요
|
||||||
|
helm template pg-cnpg . -f custom-values.yaml -n test \
|
||||||
|
--set backup.enabled=true \
|
||||||
|
--set backup.barmanObjectStore.destinationPath=s3://x/y \
|
||||||
|
--set backup.barmanObjectStore.s3Credentials.accessKeyId.name=s \
|
||||||
|
--set backup.barmanObjectStore.s3Credentials.secretAccessKey.name=s \
|
||||||
|
--set scheduledBackup.enabled=true \
|
||||||
|
--set pooler.enabled=true >/dev/null && echo "옵션 경로 OK"
|
||||||
|
|
||||||
|
# 4) 실제 API 서버 + CNPG webhook 검증 (스키마 위반을 여기서 잡는다)
|
||||||
|
helm template pg-dryrun . -f custom-values.yaml -n <기존ns> \
|
||||||
|
| kubectl apply --dry-run=server -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
4단계가 가장 중요하다. `helm template` 은 CRD 스키마를 검사하지 않으므로 렌더링이 통과해도
|
||||||
|
실제 apply 에서 거부될 수 있다.
|
||||||
|
|
||||||
|
## 5. 렌더링 결과 비교
|
||||||
|
|
||||||
|
```sh
|
||||||
|
helm template pg . "$OLD" -f "$OLD/custom-values.yaml" -n test > /tmp/old.yaml 2>/dev/null || \
|
||||||
|
helm template pg "$OLD" -f "$OLD/custom-values.yaml" -n test > /tmp/old.yaml
|
||||||
|
helm template pg "$NEW" -f "$NEW/custom-values.yaml" -n test > /tmp/new.yaml
|
||||||
|
diff -u /tmp/old.yaml /tmp/new.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 배포 검증
|
||||||
|
|
||||||
|
개발 클러스터에 실제 배포해 `doc/charts/cnpg/deploy-test.md` 의 검증 항목을 재실행한다.
|
||||||
|
최소 통과 기준:
|
||||||
|
|
||||||
|
1. `readyInstances = instances`, `phase = Cluster in healthy state`
|
||||||
|
2. `-rw` / `-ro` 서비스 라우팅 분리 (`pg_is_in_recovery()` 가 `f` / `t`)
|
||||||
|
3. `-ro` 로 쓰기 시도 시 `read-only transaction` 오류
|
||||||
|
4. `databases[].extensions` 가 primary·전체 replica 에 모두 생성됨
|
||||||
|
5. primary Pod 삭제 → failover 후 쓰기 복구, failover 전후 데이터 보존
|
||||||
|
6. `postgresql.parameters` 가 `SHOW` 로 실제 반영 확인
|
||||||
|
|
||||||
|
### PostgreSQL major 버전 상향 시 추가 확인
|
||||||
|
|
||||||
|
- 확장 호환성: `pgaudit`, `pg_stat_statements` 의 신규 major 대응 버전 존재 여부
|
||||||
|
- `postgresql.parameters` 중 제거·개명된 GUC 가 있는지
|
||||||
|
- major 업그레이드는 in-place 가 아니다. CNPG 는 논리 복제(`import`) 또는
|
||||||
|
새 클러스터 생성 후 데이터 이관 방식을 쓴다. `imageName` 만 바꾸면 기동에 실패한다.
|
||||||
|
|
||||||
|
## 7. 문서 갱신
|
||||||
|
|
||||||
|
| 파일 | 갱신 내용 |
|
||||||
|
| --- | --- |
|
||||||
|
| `CUSTOM-README.md` | 차트/PostgreSQL 버전, 변경된 values 키, operator 호환 버전 |
|
||||||
|
| `BUILD-README.md` | 이 문서의 절차에 변경이 있으면 반영 |
|
||||||
|
| `dip-values.yaml` / `dip-*-quotas.yaml` | 파라미터 이름이 바뀌었으면 반영 |
|
||||||
|
| `doc/charts/cnpg/deploy-test.md` | 신규 버전 검증 결과 추가 |
|
||||||
|
|
||||||
|
## 8. PR
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git add manifests/helm/cnpg-cluster/<신규버전> doc/
|
||||||
|
git commit -m "cnpg-cluster <신규버전> 추가 (PostgreSQL <버전>)"
|
||||||
|
git push -u origin update-cnpg-cluster/<신규버전>
|
||||||
|
```
|
||||||
|
|
||||||
|
`helm-catalog-sbom` 워크플로가 PR 에서 변경 차트의 SBOM·취약점 스캔을 수행한다.
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
# cnpg-cluster 배포
|
||||||
|
|
||||||
|
차트 버전 `1.0.0` / PostgreSQL `18`
|
||||||
|
|
||||||
|
CloudNativePG 의 `Cluster` / `Database` / `Pooler` / `ScheduledBackup` 커스텀 리소스를
|
||||||
|
Helm 으로 감싼 PaaSup 자체 제작 차트다. 업스트림 차트가 아니다.
|
||||||
|
|
||||||
|
**사전 조건:** `cloudnative-pg` 오퍼레이터(차트 `0.29.0` / operator `1.30.0`)가 먼저 설치되어
|
||||||
|
있어야 한다. 오퍼레이터가 없으면 webhook 부재로 `Cluster` 생성 자체가 거부된다.
|
||||||
|
|
||||||
|
## 1. 배포 방법
|
||||||
|
|
||||||
|
### 1) 배포 시 주의 사항
|
||||||
|
|
||||||
|
- **`instances` 와 노드 수를 맞춘다.** `affinity.podAntiAffinityType: required` 상태에서
|
||||||
|
노드 수가 `instances` 보다 적으면 Pod 가 `Pending` 에 머문다. 단일 노드 개발 환경은
|
||||||
|
`preferred` 로 내려야 한다.
|
||||||
|
- **`backup.enabled: false` 면 PITR 이 불가능하다.** 또한 WAL 이 오브젝트 스토리지로
|
||||||
|
아카이브되지 않아 `walStorage` 볼륨에 계속 쌓인다. 운영 배포는 반드시 백업을 켠다.
|
||||||
|
- **확장(extension)은 `databases` 로 선언한다.** `bootstrap.initdb.postInitApplicationSQL` 에
|
||||||
|
`CREATE EXTENSION` 을 넣으면 **오류 없이 무시된다** (operator 1.30.0 에서 확인,
|
||||||
|
`doc/charts/cnpg/deploy-test.md` 검증 기록 참고).
|
||||||
|
- **`bootstrap` 은 최초 1회만 적용된다.** 이미 생성된 클러스터의 `bootstrap.initdb.database`
|
||||||
|
를 바꿔도 아무 일도 일어나지 않는다. DB 추가는 `databases` 로 한다.
|
||||||
|
- **`postInitApplicationSQL` vs `postInitApplicationSQLRefs`.** 둘 다 같은 시점(클러스터
|
||||||
|
생성 직후 1회)·같은 권한(앱 DB 안에서 superuser)으로 실행된다. 차이는 SQL 을 어디서
|
||||||
|
가져오느냐뿐이다 — 전자는 values 에 인라인, 후자는 ConfigMap/Secret 참조. 스키마 덤프처럼
|
||||||
|
큰 SQL 은 후자를 쓴다(values 에 수백 KB를 넣지 않아도 됨). 1회성이므로 **비멱등 SQL**
|
||||||
|
(`CREATE TABLE` 등, `IF NOT EXISTS` 없이)을 그대로 넣어도 된다 — 재실행되지 않는다.
|
||||||
|
참조 순서는 Secret 전체 → ConfigMap 전체, 각 그룹 안에서는 배열 순서.
|
||||||
|
|
||||||
|
### 2) 배포
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/paasup/dip-catalog.git
|
||||||
|
cd dip-catalog/manifests/helm/cnpg-cluster/1.0.0
|
||||||
|
helm upgrade pg-cnpg ./ -f custom-values.yaml --install -n <namespace> --create-namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) 확인
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# FQN 필수 — kubectl get cluster 는 Rancher/CAPI 리소스와 충돌한다
|
||||||
|
kubectl -n <ns> get clusters.postgresql.cnpg.io pg-cnpg
|
||||||
|
kubectl -n <ns> get pods -l cnpg.io/cluster=pg-cnpg -L cnpg.io/instanceRole
|
||||||
|
kubectl -n <ns> get databases.postgresql.cnpg.io
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. custom-values.yaml 설명
|
||||||
|
|
||||||
|
### 1) 클러스터 규모
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `instances` | PostgreSQL 인스턴스 수. 1 = 단독(failover 불가), 3 = primary 1 + replica 2 | `3` |
|
||||||
|
| `primaryUpdateStrategy` | `unsupervised` = operator 가 자동 switchover 후 업데이트. `supervised` = 운영자 수동 승격 | `unsupervised` |
|
||||||
|
| `primaryUpdateMethod` | `switchover` = 정상 전환. `restart` = 제자리 재시작(다운타임 발생) | `switchover` |
|
||||||
|
|
||||||
|
### 2) 이미지
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `postgresql.image.repository` | PostgreSQL 이미지 저장소 | `ghcr.io/cloudnative-pg/postgresql` |
|
||||||
|
| `postgresql.image.tag` | **베이스 OS 를 포함한 태그를 써야 한다** (아래 경고 참고) | `18.4-system-trixie` |
|
||||||
|
| `postgresql.imageName` | 전체 이미지 경로 직접 지정 (오프라인 미러). 지정 시 위 두 값은 무시된다 | `""` |
|
||||||
|
|
||||||
|
#### 이미지 타입 — `system` 은 deprecated 다
|
||||||
|
|
||||||
|
CNPG 는 세 가지 타입을 발행한다.
|
||||||
|
([업스트림 README](https://github.com/cloudnative-pg/postgres-containers#image-types))
|
||||||
|
|
||||||
|
| 타입 | 내용 | 상태 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `minimal` | PostgreSQL 본체. **PG18+ 는 JIT 없음, pgaudit·pgvector 없음** | 현행 |
|
||||||
|
| `standard` | + pgaudit, pgvector, pg-failover-slots, JIT, 전 로케일 | **현행 · 권장** |
|
||||||
|
| `system` | `standard` + barman-cloud 바이너리 | **deprecated** |
|
||||||
|
|
||||||
|
업스트림은 `standard` 가 `system` 과 **기능 동등**하며, barman-cloud 는
|
||||||
|
[Barman Cloud Plugin](https://github.com/cloudnative-pg/plugin-barman-cloud) 으로 대체하라고
|
||||||
|
명시한다. `minimal`/`standard` 는 애초에 백업 플러그인과 함께 쓰도록 설계된 이미지다.
|
||||||
|
|
||||||
|
**현재 `custom-values.yaml` 은 `18.4-system-trixie`(deprecated)를 쓰고 있다.**
|
||||||
|
`standard-trixie` 로 전환하면 차단 CVE 가 32 → 23 건으로 줄어든다(barman 의 Python 스택 제거).
|
||||||
|
전환 전 백업 경로를 정해야 한다 — 아래 [8. 백업](#8-백업) 참고.
|
||||||
|
|
||||||
|
**경고 — 맨 major 태그(`:18`)를 쓰지 말 것.** 실측 결과다.
|
||||||
|
|
||||||
|
| 태그 | 베이스 OS | 지원 종료 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ghcr.io/cloudnative-pg/postgresql:18` | Debian 11 (bullseye) | **2026-08-31** |
|
||||||
|
| `ghcr.io/cloudnative-pg/postgresql:18.4-system-trixie` | Debian 13 (trixie) | 2030-06-30 |
|
||||||
|
|
||||||
|
두 태그 모두 PostgreSQL 18.4 지만 베이스 OS 가 다르다. `:18` 로 배포하면 instance-manager 가
|
||||||
|
`OS distribution is deprecated` 를 로그로 남긴다. 보안 카탈로그 관점에서 EOL 베이스 이미지는
|
||||||
|
패치되지 않는 OS 패키지 CVE 를 그대로 안고 가는 것이므로 사용하지 않는다.
|
||||||
|
|
||||||
|
`18.4-system-trixie` 에 포함된 확장(실측): `pgaudit 18.0`, `pg_stat_statements 1.12`,
|
||||||
|
`pgcrypto 1.4`, `pg_trgm 1.6`, `vector 0.8.5`.
|
||||||
|
|
||||||
|
참고로 `imageName` 을 아예 비우면 operator 1.30.0 이 `18.4-system-trixie` 를 기본값으로
|
||||||
|
채워준다. 다만 SBOM·재현성을 위해 카탈로그에서는 태그를 명시적으로 고정한다.
|
||||||
|
|
||||||
|
### 3) PostgreSQL 설정
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `postgresql.parameters` | postgresql.conf 파라미터 map. 값은 전부 문자열로 렌더링된다 | custom-values.yaml 참고 |
|
||||||
|
| `postgresql.sharedPreloadLibraries` | preload 라이브러리 목록. operator 가 자체 항목과 병합한다 | `[pgaudit, pg_stat_statements]` |
|
||||||
|
| `postgresql.pg_hba` | pg_hba.conf 추가 규칙. CNPG 기본값은 TLS + scram-sha-256 | `[]` |
|
||||||
|
| `postgresql.synchronous` | 동기 복제. 미설정 시 비동기(async). 운영은 `{method: any, number: 1}` 권장 | 미설정 |
|
||||||
|
|
||||||
|
`sharedPreloadLibraries` 에 올리는 것과 `databases[].extensions` 로 `CREATE EXTENSION` 하는 것은
|
||||||
|
**별개**다. `pg_stat_statements` 는 둘 다 필요하다 — preload 만 하면 뷰가 없고,
|
||||||
|
extension 만 만들면 데이터가 수집되지 않는다.
|
||||||
|
|
||||||
|
### 4) 스토리지
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `storage.size` / `storage.storageClass` | 데이터 볼륨 | `10Gi` / `longhorn` |
|
||||||
|
| `walStorage.enabled` | WAL 을 별도 볼륨으로 분리. I/O 경합 감소 + WAL 폭증이 데이터 볼륨을 채우는 것을 방지 | `true` |
|
||||||
|
| `walStorage.size` | WAL 볼륨. 대략 데이터의 50%. 백업 미설정 시 더 크게 잡는다 | `5Gi` |
|
||||||
|
|
||||||
|
볼륨 크기는 축소할 수 없다. 티어별 값은 `dip-volumes-quotas.yaml` 참고.
|
||||||
|
|
||||||
|
### 5) 데이터베이스·확장 (Database CRD)
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `databases[].name` | DB 이름. `bootstrap.initdb.database` 와 같은 이름을 쓰면 그 DB 를 관리 대상으로 잡는다 | - |
|
||||||
|
| `databases[].owner` | 소유자. 생략 시 `bootstrap.initdb.owner` | - |
|
||||||
|
| `databases[].ensure` | `present` / `absent` | `present` |
|
||||||
|
| `databases[].reclaimPolicy` | `retain` = Database 리소스를 지워도 실제 DB 보존. `delete` = 함께 삭제 | `retain` |
|
||||||
|
| `databases[].extensions` | `[{name, ensure, version, schema}]` | `[]` |
|
||||||
|
| `databases[].schemas` | `[{name, ensure, owner}]` | `[]` |
|
||||||
|
|
||||||
|
#### 중요 — 확장이 조용히 사라지는 문제 (operator 1.30.0 실측)
|
||||||
|
|
||||||
|
**증상.** primary switchover(failover, 롤링 이미지 업데이트) 이후 `Database` 로 선언한 확장이
|
||||||
|
전체 인스턴스에서 사라진다. 그런데 `Database.status` 는 계속 `applied: true` 로 남는다.
|
||||||
|
**조용한 실패이므로 status 만 보면 정상으로 보인다.**
|
||||||
|
|
||||||
|
배포 테스트에서 2회 모두 재현되었다 (failover 후 1회, 롤링 이미지 업데이트 후 1회).
|
||||||
|
데이터(테이블·레코드)는 정상 보존되며 확장만 유실된다.
|
||||||
|
|
||||||
|
**원인.** `Database` reconciler 는 **spec generation 이 바뀔 때만** 동작한다. 지속적으로
|
||||||
|
수렴(converge)시키지 않는다. 검증 내용:
|
||||||
|
|
||||||
|
- DB 에서 수동으로 `DROP EXTENSION` → 2분간 관찰, operator 는 복구하지 않음.
|
||||||
|
`status.extensions[].applied` 는 계속 `true`.
|
||||||
|
- `helm upgrade` 로 동일한 Database 매니페스트 재적용 → spec 이 같으므로 generation 불변
|
||||||
|
→ **reconcile 이 돌지 않아 복구되지 않음**.
|
||||||
|
- `kubectl annotate` → generation 을 바꾸지 않으므로 효과 없음.
|
||||||
|
|
||||||
|
**점검 방법.** status 를 믿지 말고 DB 에 직접 질의한다.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl -n <ns> exec <primary-pod> -c postgres -- \
|
||||||
|
psql -U postgres -d appdb -Atc "SELECT extname, extversion FROM pg_extension ORDER BY 1;"
|
||||||
|
```
|
||||||
|
|
||||||
|
**복구 방법 (검증됨).** `Database` 리소스를 삭제하고 재생성해 generation 을 초기화한다.
|
||||||
|
`reclaimPolicy: retain` 이면 실제 DB 와 데이터는 보존된다(테스트에서 레코드 수 유지 확인).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl -n <ns> delete databases.postgresql.cnpg.io <release>-<dbname>
|
||||||
|
helm upgrade <release> ./ -f custom-values.yaml -n <ns> # 재생성 → reconcile 실행
|
||||||
|
```
|
||||||
|
|
||||||
|
`reclaimPolicy: delete` 로 설정한 상태에서 이 절차를 쓰면 **실제 DB 가 삭제된다.**
|
||||||
|
반드시 `retain` 인지 먼저 확인한다.
|
||||||
|
|
||||||
|
**운영 권고**
|
||||||
|
|
||||||
|
- switchover·업그레이드 후에는 확장 존재 여부를 점검 항목에 넣는다.
|
||||||
|
- 확장 유무에 기능이 의존하는 서비스(감사 로깅 등)는 확장 존재를 애플리케이션 레벨에서
|
||||||
|
헬스체크하거나, 위 점검을 모니터링으로 자동화한다.
|
||||||
|
- `pgaudit` 처럼 보안 요건에 해당하는 확장이 조용히 사라지면 **감사 로그가 중단된다.**
|
||||||
|
`shared_preload_libraries` 는 `Cluster` spec 이라 유지되지만, `CREATE EXTENSION` 이
|
||||||
|
풀리면 pgaudit 의 세션 감사 기능이 동작하지 않는다.
|
||||||
|
|
||||||
|
### 6) 인증·보안
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `enableSuperuserAccess` | `false` 면 postgres superuser 시크릿이 아예 생성되지 않는다. 보안상 `false` 권장 | `false` |
|
||||||
|
| `bootstrap.initdb.secretName` | 앱 계정 비밀번호를 담은 기존 시크릿. 미지정 시 operator 가 `<release>-app` 에 무작위 생성 | `""` |
|
||||||
|
|
||||||
|
operator 가 클러스터 CA(`<release>-ca`), 서버 인증서(`<release>-server`),
|
||||||
|
복제 인증서(`<release>-replication`)를 자동 발급한다. 인스턴스 간 통신은 mTLS 다.
|
||||||
|
컨테이너는 항상 non-root(uid 26)로 실행되며 차트에서 재정의할 값이 없다.
|
||||||
|
|
||||||
|
### 7) 배치
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `affinity.podAntiAffinityType` | `required` = 노드당 1개 강제(운영). `preferred` = 부족해도 스케줄(단일 노드 개발) | `preferred` |
|
||||||
|
| `affinity.topologyKey` | 분산 기준. 멀티 AZ 는 `topology.kubernetes.io/zone` | `kubernetes.io/hostname` |
|
||||||
|
|
||||||
|
### 8) 백업
|
||||||
|
|
||||||
|
CNPG 1.30 은 세 가지 백업 경로를 제공한다. 이 차트는 현재 첫 번째만 구현하고 있다.
|
||||||
|
|
||||||
|
| 경로 | CRD 필드 | 이미지 내장 barman | 이 차트 지원 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 오브젝트 스토리지 (in-core) | `spec.backup.barmanObjectStore` | **필요** (`system` 전용) | ✅ (`backup.*`) |
|
||||||
|
| CSI 볼륨 스냅샷 | `spec.backup.volumeSnapshot` | 불필요 | ❌ 미구현 |
|
||||||
|
| Barman Cloud Plugin | `spec.plugins[]` + `isWALArchiver` | 불필요 | ❌ 미구현 |
|
||||||
|
|
||||||
|
**in-core barman 은 phase out 예정이다.** `standard`/`minimal` 이미지로 전환하려면 아래 둘 중
|
||||||
|
하나를 구현해야 한다.
|
||||||
|
|
||||||
|
- **플러그인**: barman 이 사이드카 이미지에 있어 PostgreSQL 이미지와 분리된다. 업스트림 권장
|
||||||
|
- **CSI 스냅샷**: `VolumeSnapshotClass` 가 선행 필요하다. 이 dev 클러스터는 VolumeSnapshot CRD 와
|
||||||
|
Longhorn CSI 드라이버는 있으나 **클래스가 정의되어 있지 않다.** 또한 스냅샷은 베이스 백업이므로
|
||||||
|
스냅샷 시점 사이로 복구(PITR)하려면 WAL 아카이빙이 별도로 필요하다
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `backup.enabled` | S3 호환 스토리지로 WAL 아카이브 + base backup (`system` 이미지 필요) | `false` |
|
||||||
|
| `backup.retentionPolicy` | 보존 기간 | `30d` |
|
||||||
|
| `backup.barmanObjectStore.destinationPath` | 예: `s3://pg-backup/cnpg` | `""` |
|
||||||
|
| `backup.barmanObjectStore.endpointURL` | MinIO/RustFS 등 사설 S3 엔드포인트 | `""` |
|
||||||
|
| `scheduledBackup.enabled` | 정기 백업 활성화 (`backup.enabled: true` 필요) | `false` |
|
||||||
|
| `scheduledBackup.schedule` | **6필드 cron** (초 분 시 일 월 요일). 표준 5필드가 아니다 | `"0 0 2 * * *"` |
|
||||||
|
|
||||||
|
### 9) 커넥션 풀러
|
||||||
|
|
||||||
|
| Name | 설명 | 기본값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `pooler.enabled` | PgBouncer 배포 | `false` |
|
||||||
|
| `pooler.type` | `rw` (primary) / `ro` (replica) | `rw` |
|
||||||
|
| `pooler.poolMode` | `transaction` 권장. `session` 은 풀링 효과가 낮다 | `transaction` |
|
||||||
|
|
||||||
|
## 3. 접속
|
||||||
|
|
||||||
|
| 서비스 | 대상 |
|
||||||
|
| --- | --- |
|
||||||
|
| `<release>-rw` | primary — 읽기/쓰기 |
|
||||||
|
| `<release>-ro` | replica 만 — 읽기 전용 |
|
||||||
|
| `<release>-r` | 전체 인스턴스 — 읽기 라운드로빈 |
|
||||||
|
| `<release>-pooler-rw` | PgBouncer 경유 (pooler 활성화 시) |
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl -n <ns> get secret <release>-app -o jsonpath='{.data.password}' | base64 -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 운영
|
||||||
|
|
||||||
|
### failover
|
||||||
|
|
||||||
|
primary Pod 손실 시 operator 가 자동으로 replica 를 승격한다. 실측 2~3초.
|
||||||
|
구 primary 는 재기동 후 replica 로 자동 재합류한다.
|
||||||
|
|
||||||
|
수동 switchover 는 `Cluster` 의 `status.targetPrimary` 를 직접 바꾸지 않고
|
||||||
|
`kubectl cnpg promote` 플러그인을 쓴다. 플러그인이 없으면 primary Pod 를 삭제하는
|
||||||
|
방식으로 대체할 수 있다(계획된 전환에는 권장하지 않음).
|
||||||
|
|
||||||
|
### 시퀀스 주의
|
||||||
|
|
||||||
|
failover 후 `serial`/`identity` 시퀀스 값이 점프한다(실측 1 → 34). WAL 에 기록되지 않은
|
||||||
|
시퀀스 캐시 블록이 유실되는 PostgreSQL 표준 동작이다. 시퀀스 연속성을 가정하는 애플리케이션은
|
||||||
|
영향을 받는다.
|
||||||
|
|
||||||
|
### 제거
|
||||||
|
|
||||||
|
```sh
|
||||||
|
helm uninstall <release> -n <ns>
|
||||||
|
# PVC 는 남는다. 데이터까지 지우려면 명시적으로 삭제한다.
|
||||||
|
kubectl -n <ns> get pvc -l cnpg.io/cluster=<release>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 검증 이력
|
||||||
|
|
||||||
|
`doc/charts/cnpg/deploy-test.md` 참고.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: cnpg-cluster
|
||||||
|
description: A Helm chart for PostgreSQL cluster with CloudNativePG operator
|
||||||
|
type: application
|
||||||
|
version: 1.1.0
|
||||||
|
appVersion: "18.4"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# cnpg-cluster
|
||||||
|
|
||||||
|
CloudNativePG 커스텀 리소스를 Helm 으로 감싼 PostgreSQL 클러스터 차트.
|
||||||
|
|
||||||
|
- 차트 버전: `1.0.0`
|
||||||
|
- PostgreSQL: `18`
|
||||||
|
- 필요 operator: `cloudnative-pg` 차트 `0.29.0` (operator `1.30.0`) 이상
|
||||||
|
|
||||||
|
## 생성되는 리소스
|
||||||
|
|
||||||
|
| 리소스 | 조건 | 파일 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Cluster` | 항상 | `templates/cluster.yaml` |
|
||||||
|
| `Database` | `databases` 가 비어있지 않을 때 | `templates/database.yaml` |
|
||||||
|
| `Pooler` | `pooler.enabled: true` | `templates/pooler.yaml` |
|
||||||
|
| `ScheduledBackup` | `backup.enabled` + `scheduledBackup.enabled` | `templates/scheduled-backup.yaml` |
|
||||||
|
|
||||||
|
operator 가 위 리소스를 받아 StatefulSet 없이 개별 Pod, PVC, Service(`-rw`/`-ro`/`-r`),
|
||||||
|
TLS 시크릿을 생성한다.
|
||||||
|
|
||||||
|
## 빠른 시작
|
||||||
|
|
||||||
|
```sh
|
||||||
|
helm upgrade pg-cnpg ./ -f custom-values.yaml --install -n <namespace> --create-namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
배포 옵션과 주의사항은 [CUSTOM-README.md](CUSTOM-README.md), 버전 갱신 절차는
|
||||||
|
[BUILD-README.md](BUILD-README.md) 를 참고한다.
|
||||||
|
|
||||||
|
## 값 파일
|
||||||
|
|
||||||
|
| 파일 | 용도 |
|
||||||
|
| --- | --- |
|
||||||
|
| `values.yaml` | 차트 기본값 (전체 키 문서화) |
|
||||||
|
| `custom-values.yaml` | PaaSup 표준 오버라이드 |
|
||||||
|
| `dip-values.yaml` | DIP 플랫폼 운영 배포값 (백업·pooler·동기복제 활성) |
|
||||||
|
| `dip-resources-quotas.yaml` | Small/Medium/Large CPU·메모리 티어 |
|
||||||
|
| `dip-volumes-quotas.yaml` | Small/Medium/Large 볼륨 티어 |
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
# cnpg-cluster — PaaSup 오버라이드
|
||||||
|
# 사전 조건: cloudnative-pg operator(차트 0.29.0 / operator 1.30.0)가 설치되어 있어야 한다.
|
||||||
|
|
||||||
|
instances: 3
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
# SUSE BCI 15.7 기반 자체 하드닝 빌드로 교체했다 (2026-07-28).
|
||||||
|
# 결정 근거·받아들인 비용 → doc/decisions/0001-cnpg-postgresql-image.md
|
||||||
|
# 빌드 정의 → images/cnpg-postgresql/suse.Dockerfile + suse.build.env
|
||||||
|
#
|
||||||
|
# 태그에 빌드일을 포함한다. 같은 앱 버전이라도 베이스 업데이트 결과가 시점마다 다르므로
|
||||||
|
# 롤링 태그를 쓰지 않는다 (doc/image-selection.md 2번).
|
||||||
|
imageName: "docker.io/paasup/cnpg-postgresql:18.4-bci15.7-hardened-20260803"
|
||||||
|
#
|
||||||
|
# trivy 는 SLES 15.7 을 정상 커버한다(2026-07-29 재측정, 양성 대조로 13건 실측 —
|
||||||
|
# doc/analysis/sles-oval-measurement.md). 2026-07-28 시점에는 "trivy 가 SLES 15.7
|
||||||
|
# 데이터를 커버하지 않아 0건이 측정 불가다"로 판단해 OVAL 직접 평가로 우회했으나
|
||||||
|
# 이는 오판이었던 것으로 정정됐다 — 실효 C/H 0/0 은 실제 결과다. 게이트 PASS.
|
||||||
|
#
|
||||||
|
# ⚠️ pg-failover-slots 확장이 없다 — PGDG zypp 저장소에 패키지가 없다.
|
||||||
|
# 이 차트는 쓰지 않지만 상위 구성에서 요구하면 확인해야 한다.
|
||||||
|
#
|
||||||
|
# 이전 값: ghcr.io/cloudnative-pg/postgresql:18.4-system-trixie
|
||||||
|
# 업스트림 deprecated 타입이었고 실효 CRITICAL/HIGH 6/26 (차단 32건) 이었다.
|
||||||
|
# 그 32건은 전부 수정 버전이 없어 어떤 조치로도 해소되지 않는다.
|
||||||
|
# image:
|
||||||
|
# repository: ghcr.io/cloudnative-pg/postgresql
|
||||||
|
# tag: "18.4-standard-trixie"
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
max_connections: "200"
|
||||||
|
shared_buffers: 256MB
|
||||||
|
work_mem: 8MB
|
||||||
|
maintenance_work_mem: 128MB
|
||||||
|
effective_cache_size: 1GB
|
||||||
|
log_timezone: Asia/Seoul
|
||||||
|
timezone: Asia/Seoul
|
||||||
|
|
||||||
|
# 감사 로깅. CNPG 기본 이미지에 pgaudit 가 포함되어 있다.
|
||||||
|
sharedPreloadLibraries:
|
||||||
|
- pgaudit
|
||||||
|
- pg_stat_statements
|
||||||
|
|
||||||
|
primaryUpdateStrategy: unsupervised
|
||||||
|
primaryUpdateMethod: switchover
|
||||||
|
|
||||||
|
storage:
|
||||||
|
size: 20Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
|
||||||
|
walStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: appdb
|
||||||
|
owner: appuser
|
||||||
|
# 비밀번호를 직접 관리할 때 지정. 미지정 시 operator 가 <release>-app 시크릿에 자동 생성한다.
|
||||||
|
# secretName: "$INFISICAL_SECRET"
|
||||||
|
encoding: UTF8
|
||||||
|
# CREATE EXTENSION 은 여기에 넣지 않는다 (CNPG 1.30.0 에서 무시됨). 아래 databases 로 선언한다.
|
||||||
|
postInitApplicationSQL: []
|
||||||
|
|
||||||
|
# 확장은 Database CRD 로 선언한다. sharedPreloadLibraries 에 올린 것과 짝을 맞춘다.
|
||||||
|
databases:
|
||||||
|
- name: appdb
|
||||||
|
owner: appuser
|
||||||
|
ensure: present
|
||||||
|
reclaimPolicy: retain
|
||||||
|
extensions:
|
||||||
|
- name: pg_stat_statements
|
||||||
|
- name: pgaudit
|
||||||
|
|
||||||
|
# postgres superuser 직접 접속 차단. 시크릿 자체가 생성되지 않는다.
|
||||||
|
enableSuperuserAccess: false
|
||||||
|
|
||||||
|
affinity:
|
||||||
|
enablePodAntiAffinity: true
|
||||||
|
# 단일 노드 dev 환경에서는 preferred 여야 3 인스턴스가 스케줄된다.
|
||||||
|
# 노드 수 >= instances 인 운영 환경에서는 required 로 변경한다.
|
||||||
|
podAntiAffinityType: preferred
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
monitoring:
|
||||||
|
# rancher-monitoring(Prometheus Operator) 설치 환경에서만 true
|
||||||
|
enablePodMonitor: false
|
||||||
|
|
||||||
|
# 백업 — 운영 배포 시 반드시 활성화한다. 미설정이면 PITR 불가.
|
||||||
|
backup:
|
||||||
|
enabled: false
|
||||||
|
retentionPolicy: 30d
|
||||||
|
barmanObjectStore:
|
||||||
|
destinationPath: "" # 예: s3://pg-backup/cnpg
|
||||||
|
endpointURL: "" # 예: http://rustfs.defense-llm.svc.cluster.local:9000
|
||||||
|
s3Credentials:
|
||||||
|
accessKeyId:
|
||||||
|
name: "" # 예: pg-backup-s3
|
||||||
|
key: ACCESS_KEY_ID
|
||||||
|
secretAccessKey:
|
||||||
|
name: ""
|
||||||
|
key: ACCESS_SECRET_KEY
|
||||||
|
|
||||||
|
scheduledBackup:
|
||||||
|
enabled: false
|
||||||
|
schedule: "0 0 2 * * *" # 6필드 cron — 매일 02:00
|
||||||
|
|
||||||
|
pooler:
|
||||||
|
enabled: false
|
||||||
|
instances: 2
|
||||||
|
type: rw
|
||||||
|
poolMode: transaction
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
small:
|
||||||
|
instances: 1
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 2Gi
|
||||||
|
postgresql:
|
||||||
|
parameters:
|
||||||
|
max_connections: "100"
|
||||||
|
shared_buffers: 256MB
|
||||||
|
effective_cache_size: 1GB
|
||||||
|
pooler:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
medium:
|
||||||
|
instances: 3
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 4Gi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 8Gi
|
||||||
|
postgresql:
|
||||||
|
parameters:
|
||||||
|
max_connections: "200"
|
||||||
|
shared_buffers: 2GB
|
||||||
|
effective_cache_size: 6GB
|
||||||
|
pooler:
|
||||||
|
enabled: true
|
||||||
|
instances: 2
|
||||||
|
|
||||||
|
large:
|
||||||
|
instances: 3
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "4"
|
||||||
|
memory: 16Gi
|
||||||
|
limits:
|
||||||
|
cpu: "8"
|
||||||
|
memory: 32Gi
|
||||||
|
postgresql:
|
||||||
|
parameters:
|
||||||
|
max_connections: "500"
|
||||||
|
shared_buffers: 8GB
|
||||||
|
effective_cache_size: 24GB
|
||||||
|
pooler:
|
||||||
|
enabled: true
|
||||||
|
instances: 3
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# cnpg-cluster — DIP 플랫폼 기본 배포값 (멀티 테넌트 운영 기준)
|
||||||
|
|
||||||
|
instances: 3
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
# custom-values.yaml 과 동일하게 SUSE BCI 15.7 자체 빌드를 쓴다.
|
||||||
|
# 근거·비용 → doc/decisions/0001-cnpg-postgresql-image.md
|
||||||
|
# trivy 는 SLES 15.7 을 정상 커버한다(2026-07-29 재측정 — doc/analysis/sles-oval-measurement.md).
|
||||||
|
# 실효 C/H 0/0, 게이트 PASS.
|
||||||
|
imageName: "docker.io/paasup/cnpg-postgresql:18.4-bci15.7-hardened-20260803"
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
max_connections: "200"
|
||||||
|
shared_buffers: 512MB
|
||||||
|
work_mem: 16MB
|
||||||
|
maintenance_work_mem: 256MB
|
||||||
|
effective_cache_size: 3GB
|
||||||
|
log_timezone: Asia/Seoul
|
||||||
|
timezone: Asia/Seoul
|
||||||
|
# 감사 로깅 — pgaudit 대상 지정
|
||||||
|
pgaudit.log: "ddl, role, write"
|
||||||
|
pgaudit.log_catalog: "off"
|
||||||
|
pgaudit.log_parameter: "on"
|
||||||
|
|
||||||
|
sharedPreloadLibraries:
|
||||||
|
- pgaudit
|
||||||
|
- pg_stat_statements
|
||||||
|
|
||||||
|
# 운영 환경은 최소 1개 동기 복제본을 요구한다.
|
||||||
|
synchronous:
|
||||||
|
method: any
|
||||||
|
number: 1
|
||||||
|
|
||||||
|
primaryUpdateStrategy: unsupervised
|
||||||
|
primaryUpdateMethod: switchover
|
||||||
|
|
||||||
|
storage:
|
||||||
|
size: 100Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
|
||||||
|
walStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 50Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 4Gi
|
||||||
|
limits:
|
||||||
|
cpu: "4"
|
||||||
|
memory: 8Gi
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: appdb
|
||||||
|
owner: appuser
|
||||||
|
# 플랫폼은 Infisical 로 자격증명을 주입한다.
|
||||||
|
secretName: "$INFISICAL_SECRET"
|
||||||
|
encoding: UTF8
|
||||||
|
postInitApplicationSQL: []
|
||||||
|
|
||||||
|
databases:
|
||||||
|
- name: appdb
|
||||||
|
owner: appuser
|
||||||
|
ensure: present
|
||||||
|
reclaimPolicy: retain
|
||||||
|
extensions:
|
||||||
|
- name: pg_stat_statements
|
||||||
|
- name: pgaudit
|
||||||
|
|
||||||
|
enableSuperuserAccess: false
|
||||||
|
|
||||||
|
affinity:
|
||||||
|
enablePodAntiAffinity: true
|
||||||
|
# 운영 클러스터는 노드 수 >= instances 이므로 required 로 노드 분산을 강제한다.
|
||||||
|
podAntiAffinityType: required
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
monitoring:
|
||||||
|
enablePodMonitor: true
|
||||||
|
|
||||||
|
backup:
|
||||||
|
enabled: true
|
||||||
|
retentionPolicy: 30d
|
||||||
|
barmanObjectStore:
|
||||||
|
destinationPath: "s3://dip-pg-backup/cnpg"
|
||||||
|
endpointURL: ""
|
||||||
|
s3Credentials:
|
||||||
|
accessKeyId:
|
||||||
|
name: pg-backup-s3
|
||||||
|
key: ACCESS_KEY_ID
|
||||||
|
secretAccessKey:
|
||||||
|
name: pg-backup-s3
|
||||||
|
key: ACCESS_SECRET_KEY
|
||||||
|
|
||||||
|
scheduledBackup:
|
||||||
|
enabled: true
|
||||||
|
schedule: "0 0 2 * * *"
|
||||||
|
|
||||||
|
pooler:
|
||||||
|
enabled: true
|
||||||
|
instances: 2
|
||||||
|
type: rw
|
||||||
|
poolMode: transaction
|
||||||
|
parameters:
|
||||||
|
max_client_conn: "1000"
|
||||||
|
default_pool_size: "25"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# data 와 WAL 을 별도 볼륨으로 분리한다.
|
||||||
|
# WAL 볼륨은 대략 data 의 50% 를 잡는다. 백업(barmanObjectStore) 미설정 시 WAL 이
|
||||||
|
# 정리되지 않고 쌓이므로 WAL 볼륨을 더 크게 잡아야 한다.
|
||||||
|
|
||||||
|
small:
|
||||||
|
storage:
|
||||||
|
size: 20Gi
|
||||||
|
storageClass: "longhorn"
|
||||||
|
walStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: "longhorn"
|
||||||
|
|
||||||
|
medium:
|
||||||
|
storage:
|
||||||
|
size: 100Gi
|
||||||
|
storageClass: "longhorn"
|
||||||
|
walStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 50Gi
|
||||||
|
storageClass: "longhorn"
|
||||||
|
|
||||||
|
large:
|
||||||
|
storage:
|
||||||
|
size: 500Gi
|
||||||
|
storageClass: "longhorn"
|
||||||
|
walStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 200Gi
|
||||||
|
storageClass: "longhorn"
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
PostgreSQL 클러스터 "{{ include "cnpg-cluster.fullname" . }}" 를 배포했습니다.
|
||||||
|
인스턴스 {{ .Values.instances }}개 / 이미지 {{ include "cnpg-cluster.image" . }}
|
||||||
|
|
||||||
|
1. 상태 확인 (반드시 FQN 을 사용하세요 — kubectl get cluster 는 Rancher/CAPI 리소스와 충돌합니다)
|
||||||
|
|
||||||
|
kubectl -n {{ include "cnpg-cluster.namespace" . }} get clusters.postgresql.cnpg.io {{ include "cnpg-cluster.fullname" . }}
|
||||||
|
kubectl -n {{ include "cnpg-cluster.namespace" . }} get pods -l cnpg.io/cluster={{ include "cnpg-cluster.fullname" . }} -L cnpg.io/instanceRole
|
||||||
|
|
||||||
|
2. 접속 엔드포인트
|
||||||
|
|
||||||
|
{{ include "cnpg-cluster.fullname" . }}-rw : primary (읽기/쓰기)
|
||||||
|
{{ include "cnpg-cluster.fullname" . }}-ro : replica 만 (읽기 전용)
|
||||||
|
{{ include "cnpg-cluster.fullname" . }}-r : 전체 인스턴스 (읽기 라운드로빈)
|
||||||
|
{{- if .Values.pooler.enabled }}
|
||||||
|
{{ include "cnpg-cluster.fullname" . }}-pooler-{{ .Values.pooler.type }} : PgBouncer ({{ .Values.pooler.poolMode }} 모드)
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
3. 자격증명
|
||||||
|
|
||||||
|
DB : {{ .Values.bootstrap.initdb.database }}
|
||||||
|
User : {{ .Values.bootstrap.initdb.owner }}
|
||||||
|
{{- if .Values.bootstrap.initdb.secretName }}
|
||||||
|
Secret : {{ .Values.bootstrap.initdb.secretName }} (사용자 지정)
|
||||||
|
{{- else }}
|
||||||
|
Secret : {{ include "cnpg-cluster.fullname" . }}-app (operator 자동 생성)
|
||||||
|
|
||||||
|
kubectl -n {{ include "cnpg-cluster.namespace" . }} get secret {{ include "cnpg-cluster.fullname" . }}-app \
|
||||||
|
-o jsonpath='{.data.password}' | base64 -d
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.enableSuperuserAccess }}
|
||||||
|
|
||||||
|
superuser(postgres) 직접 접속은 비활성화되어 있습니다 (enableSuperuserAccess: false).
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
4. psql 접속 예시
|
||||||
|
|
||||||
|
kubectl -n {{ include "cnpg-cluster.namespace" . }} exec -it {{ include "cnpg-cluster.fullname" . }}-1 -c postgres -- \
|
||||||
|
psql -U postgres -d {{ .Values.bootstrap.initdb.database }}
|
||||||
|
|
||||||
|
{{- if not .Values.backup.enabled }}
|
||||||
|
|
||||||
|
경고: 백업이 비활성화되어 있습니다(backup.enabled: false). 운영 환경에서는
|
||||||
|
barmanObjectStore 를 설정하고 scheduledBackup 을 활성화하세요.
|
||||||
|
백업 없이는 PITR(Point-In-Time Recovery)이 불가능합니다.
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq (int .Values.instances) 1 }}
|
||||||
|
|
||||||
|
경고: instances=1 입니다. replica 가 없어 failover 가 불가능합니다.
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq .Values.affinity.podAntiAffinityType "preferred" }}
|
||||||
|
|
||||||
|
참고: podAntiAffinityType 이 "preferred" 입니다. 여러 인스턴스가 같은 노드에
|
||||||
|
배치될 수 있어 노드 장애 시 동시 손실 위험이 있습니다.
|
||||||
|
노드 수가 instances 이상인 운영 환경에서는 "required" 를 사용하세요.
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.fullname" -}}
|
||||||
|
{{- if .Values.nameOverride }}
|
||||||
|
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- .Release.Name | default "cnpg-cluster" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "cnpg-cluster.chart" . }}
|
||||||
|
{{ include "cnpg-cluster.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "cnpg-cluster.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the namespace to use
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.namespace" -}}
|
||||||
|
{{- if .Values.namespaceOverride }}
|
||||||
|
{{- .Values.namespaceOverride }}
|
||||||
|
{{- else }}
|
||||||
|
{{- .Release.Namespace }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
PostgreSQL 이미지. imageName 이 지정되면 그것을 그대로 쓰고, 아니면 repository:tag 조합.
|
||||||
|
|
||||||
|
주의: 맨 major 태그(`:18`)를 쓰면 안 된다. 그 태그는 Debian 11(bullseye) 기반으로
|
||||||
|
2026-08-31 에 지원이 종료된다. 반드시 베이스 OS 를 포함한 태그를 지정한다
|
||||||
|
(예: 18.4-system-trixie → Debian 13, 2030-06-30 까지 지원).
|
||||||
|
*/}}
|
||||||
|
{{- define "cnpg-cluster.image" -}}
|
||||||
|
{{- if .Values.postgresql.imageName }}
|
||||||
|
{{- .Values.postgresql.imageName }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s:%s" .Values.postgresql.image.repository (.Values.postgresql.image.tag | toString) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" . }}
|
||||||
|
namespace: {{ include "cnpg-cluster.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cnpg-cluster.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.extraLabels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.extraAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
instances: {{ .Values.instances }}
|
||||||
|
imageName: {{ include "cnpg-cluster.image" . }}
|
||||||
|
|
||||||
|
primaryUpdateStrategy: {{ .Values.primaryUpdateStrategy }}
|
||||||
|
primaryUpdateMethod: {{ .Values.primaryUpdateMethod }}
|
||||||
|
|
||||||
|
enableSuperuserAccess: {{ .Values.enableSuperuserAccess }}
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
{{- with .Values.postgresql.parameters }}
|
||||||
|
parameters:
|
||||||
|
{{- range $k, $v := . }}
|
||||||
|
{{ $k }}: {{ $v | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.postgresql.sharedPreloadLibraries }}
|
||||||
|
shared_preload_libraries:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.postgresql.pg_hba }}
|
||||||
|
pg_hba:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.postgresql.synchronous }}
|
||||||
|
synchronous:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: {{ .Values.bootstrap.initdb.database }}
|
||||||
|
owner: {{ .Values.bootstrap.initdb.owner }}
|
||||||
|
{{- with .Values.bootstrap.initdb.secretName }}
|
||||||
|
secret:
|
||||||
|
name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.bootstrap.initdb.encoding }}
|
||||||
|
encoding: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.bootstrap.initdb.localeCollate }}
|
||||||
|
localeCollate: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.bootstrap.initdb.localeCType }}
|
||||||
|
localeCType: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.bootstrap.initdb.postInitApplicationSQL }}
|
||||||
|
postInitApplicationSQL:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.bootstrap.initdb.postInitApplicationSQLRefs }}
|
||||||
|
{{- if or .configMapRefs .secretRefs }}
|
||||||
|
postInitApplicationSQLRefs:
|
||||||
|
{{- with .secretRefs }}
|
||||||
|
secretRefs:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .configMapRefs }}
|
||||||
|
configMapRefs:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
storage:
|
||||||
|
size: {{ .Values.storage.size }}
|
||||||
|
{{- with .Values.storage.storageClass }}
|
||||||
|
storageClass: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if .Values.walStorage.enabled }}
|
||||||
|
walStorage:
|
||||||
|
size: {{ .Values.walStorage.size }}
|
||||||
|
{{- with .Values.walStorage.storageClass }}
|
||||||
|
storageClass: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 4 }}
|
||||||
|
|
||||||
|
affinity:
|
||||||
|
enablePodAntiAffinity: {{ .Values.affinity.enablePodAntiAffinity }}
|
||||||
|
podAntiAffinityType: {{ .Values.affinity.podAntiAffinityType }}
|
||||||
|
topologyKey: {{ .Values.affinity.topologyKey }}
|
||||||
|
{{- with .Values.affinity.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
monitoring:
|
||||||
|
enablePodMonitor: {{ .Values.monitoring.enablePodMonitor }}
|
||||||
|
{{- with .Values.monitoring.customQueriesConfigMap }}
|
||||||
|
customQueriesConfigMap:
|
||||||
|
{{- toYaml . | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if .Values.backup.enabled }}
|
||||||
|
backup:
|
||||||
|
retentionPolicy: {{ .Values.backup.retentionPolicy }}
|
||||||
|
barmanObjectStore:
|
||||||
|
destinationPath: {{ required "backup.enabled=true 이면 backup.barmanObjectStore.destinationPath 가 필요하다" .Values.backup.barmanObjectStore.destinationPath }}
|
||||||
|
{{- with .Values.backup.barmanObjectStore.endpointURL }}
|
||||||
|
endpointURL: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
s3Credentials:
|
||||||
|
accessKeyId:
|
||||||
|
name: {{ required "backup.enabled=true 이면 s3Credentials.accessKeyId.name 이 필요하다" .Values.backup.barmanObjectStore.s3Credentials.accessKeyId.name }}
|
||||||
|
key: {{ .Values.backup.barmanObjectStore.s3Credentials.accessKeyId.key }}
|
||||||
|
secretAccessKey:
|
||||||
|
name: {{ required "backup.enabled=true 이면 s3Credentials.secretAccessKey.name 이 필요하다" .Values.backup.barmanObjectStore.s3Credentials.secretAccessKey.name }}
|
||||||
|
key: {{ .Values.backup.barmanObjectStore.s3Credentials.secretAccessKey.key }}
|
||||||
|
wal:
|
||||||
|
{{- toYaml .Values.backup.barmanObjectStore.wal | nindent 8 }}
|
||||||
|
data:
|
||||||
|
{{- toYaml .Values.backup.barmanObjectStore.data | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{{- /*
|
||||||
|
선언적 데이터베이스/확장 관리 (CNPG 1.26+ Database CRD).
|
||||||
|
|
||||||
|
중요: bootstrap.initdb.postInitApplicationSQL 안의 CREATE EXTENSION 은 CNPG 1.30.0 에서
|
||||||
|
반영되지 않는다(오류도 나지 않고 조용히 무시됨 — doc/deploy-test-cnpg.md 검증 기록 참고).
|
||||||
|
확장은 반드시 이 Database CRD 로 선언해야 한다.
|
||||||
|
*/}}
|
||||||
|
{{- range .Values.databases }}
|
||||||
|
---
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Database
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" $ }}-{{ .name }}
|
||||||
|
namespace: {{ include "cnpg-cluster.namespace" $ }}
|
||||||
|
labels:
|
||||||
|
{{- include "cnpg-cluster.labels" $ | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
cluster:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" $ }}
|
||||||
|
name: {{ .name }}
|
||||||
|
owner: {{ .owner | default $.Values.bootstrap.initdb.owner }}
|
||||||
|
ensure: {{ .ensure | default "present" }}
|
||||||
|
# retain = Database 리소스를 지워도 실제 DB 는 남는다 (운영 기본값)
|
||||||
|
databaseReclaimPolicy: {{ .reclaimPolicy | default "retain" }}
|
||||||
|
{{- with .extensions }}
|
||||||
|
extensions:
|
||||||
|
{{- range . }}
|
||||||
|
- name: {{ .name }}
|
||||||
|
ensure: {{ .ensure | default "present" }}
|
||||||
|
{{- with .version }}
|
||||||
|
version: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .schema }}
|
||||||
|
schema: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .schemas }}
|
||||||
|
schemas:
|
||||||
|
{{- range . }}
|
||||||
|
- name: {{ .name }}
|
||||||
|
ensure: {{ .ensure | default "present" }}
|
||||||
|
{{- with .owner }}
|
||||||
|
owner: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{{- if .Values.pooler.enabled }}
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Pooler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" . }}-pooler-{{ .Values.pooler.type }}
|
||||||
|
namespace: {{ include "cnpg-cluster.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cnpg-cluster.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
cluster:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" . }}
|
||||||
|
instances: {{ .Values.pooler.instances }}
|
||||||
|
type: {{ .Values.pooler.type }}
|
||||||
|
pgbouncer:
|
||||||
|
poolMode: {{ .Values.pooler.poolMode }}
|
||||||
|
{{- with .Values.pooler.parameters }}
|
||||||
|
parameters:
|
||||||
|
{{- range $k, $v := . }}
|
||||||
|
{{ $k }}: {{ $v | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: pgbouncer
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.pooler.resources | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{{- if and .Values.backup.enabled .Values.scheduledBackup.enabled }}
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: ScheduledBackup
|
||||||
|
metadata:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" . }}
|
||||||
|
namespace: {{ include "cnpg-cluster.namespace" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "cnpg-cluster.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
# CNPG 의 schedule 은 6필드다 (초 분 시 일 월 요일) — 표준 5필드 cron 이 아니다.
|
||||||
|
schedule: {{ .Values.scheduledBackup.schedule | quote }}
|
||||||
|
backupOwnerReference: {{ .Values.scheduledBackup.backupOwnerReference }}
|
||||||
|
immediate: {{ .Values.scheduledBackup.immediate }}
|
||||||
|
cluster:
|
||||||
|
name: {{ include "cnpg-cluster.fullname" . }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# Default values for cnpg-cluster
|
||||||
|
# CloudNativePG operator(cloudnative-pg 차트)가 먼저 설치되어 있어야 한다.
|
||||||
|
nameOverride: ""
|
||||||
|
namespaceOverride: ""
|
||||||
|
|
||||||
|
# PostgreSQL 인스턴스 수. 1 = 단독, 3 이상 = primary 1 + replica N-1
|
||||||
|
instances: 3
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/cloudnative-pg/postgresql
|
||||||
|
# 반드시 베이스 OS 를 포함한 태그를 쓴다.
|
||||||
|
# 금지: "18" — Debian 11(bullseye) 기반이며 2026-08-31 지원 종료
|
||||||
|
# 권장: "18.4-system-trixie" — Debian 13, 2030-06-30 까지 지원
|
||||||
|
# (operator 1.30.0 이 imageName 미지정 시 선택하는 기본값과 동일)
|
||||||
|
# 포함 확장: pgaudit 18.0, pg_stat_statements 1.12, pgcrypto 1.4, pg_trgm 1.6, vector 0.8.5
|
||||||
|
tag: "18.4-system-trixie"
|
||||||
|
# 전체 이미지 경로를 직접 지정할 때 사용 (오프라인 미러 등). 지정 시 image.* 는 무시된다.
|
||||||
|
imageName: ""
|
||||||
|
|
||||||
|
# postgresql.conf 파라미터
|
||||||
|
parameters:
|
||||||
|
max_connections: "100"
|
||||||
|
shared_buffers: 128MB
|
||||||
|
# 감사 로깅용. pgaudit 는 CNPG 기본 이미지에 포함되어 있다.
|
||||||
|
# shared_preload_libraries 는 CNPG 가 자동 관리하므로 직접 넣지 않는다.
|
||||||
|
|
||||||
|
# 확장 기능 (pgaudit 등). shared_preload_libraries 는 operator 가 계산한다.
|
||||||
|
sharedPreloadLibraries: []
|
||||||
|
|
||||||
|
# pg_hba.conf 추가 규칙. CNPG 기본값은 TLS 강제 + scram-sha-256 이다.
|
||||||
|
pg_hba: []
|
||||||
|
# - hostssl appdb appuser 10.42.0.0/16 scram-sha-256
|
||||||
|
|
||||||
|
# 동기 복제 설정. 미설정 시 비동기(async) 복제다.
|
||||||
|
# synchronous:
|
||||||
|
# method: any
|
||||||
|
# number: 1
|
||||||
|
|
||||||
|
# primary 업데이트 전략
|
||||||
|
# unsupervised = operator 가 자동으로 switchover 후 업데이트 (기본)
|
||||||
|
# supervised = 운영자가 수동 승격해야 진행
|
||||||
|
primaryUpdateStrategy: unsupervised
|
||||||
|
primaryUpdateMethod: switchover
|
||||||
|
|
||||||
|
storage:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
|
||||||
|
# WAL 을 별도 볼륨으로 분리한다. I/O 경합을 줄이고 WAL 폭증이 데이터 볼륨을 채우는 것을 막는다.
|
||||||
|
walStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 5Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
|
# 초기 DB/사용자 생성
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: appdb
|
||||||
|
owner: appuser
|
||||||
|
# 비밀번호를 담은 기존 시크릿 이름. 미지정 시 operator 가 무작위 생성해
|
||||||
|
# <release>-app 시크릿에 저장한다.
|
||||||
|
secretName: ""
|
||||||
|
encoding: UTF8
|
||||||
|
localeCollate: ""
|
||||||
|
localeCType: ""
|
||||||
|
# 초기화 SQL. 최초 bootstrap 시 1회만 superuser 로 실행된다.
|
||||||
|
# 주의: CREATE EXTENSION 은 여기에 넣으면 안 된다. CNPG 1.30.0 에서 오류 없이
|
||||||
|
# 무시된다(검증 기록: doc/deploy-test-cnpg.md). 확장은 아래 databases 로 선언한다.
|
||||||
|
postInitApplicationSQL: []
|
||||||
|
# - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;
|
||||||
|
|
||||||
|
# 초기화 SQL 을 ConfigMap/Secret 에서 읽어 실행한다. postInitApplicationSQL 과 같은
|
||||||
|
# 시점·같은 권한(앱 DB, superuser)이지만 SQL 을 values 에 인라인하지 않아도 된다 —
|
||||||
|
# 스키마 덤프처럼 큰 SQL 을 넣을 때 쓴다.
|
||||||
|
# 처리 순서: Secret 전체 → ConfigMap 전체, 각 그룹 안에서는 배열 순서대로.
|
||||||
|
# bootstrap 시 1회만 실행되므로 비멱등 덤프(CREATE TABLE …)를 그대로 넣어도 된다.
|
||||||
|
postInitApplicationSQLRefs:
|
||||||
|
configMapRefs: []
|
||||||
|
# - name: db-setting-configmap
|
||||||
|
# key: database-schema.sql
|
||||||
|
secretRefs: []
|
||||||
|
|
||||||
|
# 선언적 데이터베이스·확장·스키마 관리 (CNPG 1.26+ Database CRD).
|
||||||
|
# 확장 설치의 정식 경로다. bootstrap 으로 만든 DB 도 여기서 관리 대상으로 선언할 수 있다.
|
||||||
|
databases: []
|
||||||
|
# - name: appdb # bootstrap.initdb.database 와 같은 이름을 쓰면 그 DB 를 관리한다
|
||||||
|
# owner: appuser # 생략 시 bootstrap.initdb.owner
|
||||||
|
# ensure: present # present | absent
|
||||||
|
# reclaimPolicy: retain # retain = Database 리소스를 지워도 실제 DB 는 보존
|
||||||
|
# extensions:
|
||||||
|
# - name: pg_stat_statements
|
||||||
|
# - name: pgaudit
|
||||||
|
# schemas:
|
||||||
|
# - name: app
|
||||||
|
# owner: appuser
|
||||||
|
|
||||||
|
# postgres(superuser) 계정으로의 직접 접속 허용 여부.
|
||||||
|
# false 면 superuser 시크릿이 아예 생성되지 않는다. 보안상 false 를 권장한다.
|
||||||
|
enableSuperuserAccess: false
|
||||||
|
|
||||||
|
# Pod 분산 배치
|
||||||
|
affinity:
|
||||||
|
enablePodAntiAffinity: true
|
||||||
|
# preferred = 노드가 부족해도 스케줄됨 (단일 노드 dev 환경 필수)
|
||||||
|
# required = 노드당 1개 강제 (운영 권장, 노드 수 >= instances 필요)
|
||||||
|
podAntiAffinityType: preferred
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
# 참고: 컨테이너 보안 컨텍스트는 operator 가 관리한다. CNPG 는 항상 non-root(uid 26)
|
||||||
|
# runAsNonRoot / readOnlyRootFilesystem 로 Pod 를 만들며, 차트에서 재정의할 값이 없다.
|
||||||
|
|
||||||
|
monitoring:
|
||||||
|
# Prometheus Operator CRD 필요
|
||||||
|
enablePodMonitor: false
|
||||||
|
# 커스텀 메트릭 쿼리 ConfigMap
|
||||||
|
customQueriesConfigMap: []
|
||||||
|
|
||||||
|
# 백업 — S3 호환 오브젝트 스토리지 (Barman Cloud)
|
||||||
|
backup:
|
||||||
|
enabled: false
|
||||||
|
# 보존 기간
|
||||||
|
retentionPolicy: 30d
|
||||||
|
barmanObjectStore:
|
||||||
|
destinationPath: "" # 예: s3://pg-backup/cnpg
|
||||||
|
endpointURL: "" # 예: http://rustfs.defense-llm.svc:9000
|
||||||
|
s3Credentials:
|
||||||
|
accessKeyId:
|
||||||
|
name: "" # 시크릿 이름
|
||||||
|
key: ACCESS_KEY_ID
|
||||||
|
secretAccessKey:
|
||||||
|
name: ""
|
||||||
|
key: ACCESS_SECRET_KEY
|
||||||
|
wal:
|
||||||
|
compression: gzip
|
||||||
|
maxParallel: 2
|
||||||
|
data:
|
||||||
|
compression: gzip
|
||||||
|
jobs: 2
|
||||||
|
|
||||||
|
# 정기 백업 스케줄 (backup.enabled: true 일 때만 생성)
|
||||||
|
scheduledBackup:
|
||||||
|
enabled: false
|
||||||
|
# CNPG 의 cron 은 6필드다 (초 분 시 일 월 요일)
|
||||||
|
schedule: "0 0 2 * * *"
|
||||||
|
backupOwnerReference: self
|
||||||
|
immediate: false
|
||||||
|
|
||||||
|
# PgBouncer 커넥션 풀러
|
||||||
|
pooler:
|
||||||
|
enabled: false
|
||||||
|
instances: 2
|
||||||
|
type: rw # rw | ro
|
||||||
|
poolMode: transaction # session | transaction | statement
|
||||||
|
parameters:
|
||||||
|
max_client_conn: "1000"
|
||||||
|
default_pool_size: "25"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
|
||||||
|
# 추가 라벨/어노테이션
|
||||||
|
extraLabels: {}
|
||||||
|
extraAnnotations: {}
|
||||||
Reference in New Issue
Block a user