자체 빌드 이미지 3종(cloudnative-pg/cnpg-postgresql/etcd) + 대응 헬름 차트 도입
security-catalog 프로젝트에서 첫 실사용 자체 빌드 이미지 3종을 포팅한다 — 전부
상위 태그·베이스 OS 교체로 해소 안 되는 CVE(Go 모듈 정적 링크 또는 미수정 CRITICAL/
HIGH)를 자체 빌드(소스 컴파일 또는 SUSE BCI 재설치)로 대응한다:
- images/cloudnative-pg: CNPG operator, release-1.30 소스 컴파일 + bci-micro
- images/cnpg-postgresql: PostgreSQL 18.4, bci-base + zypper 재설치
- images/etcd: etcd v3.7.1, 소스 컴파일(x/text 강제 업그레이드) + bci-micro
함께 추가:
- manifests/helm/{cloudnative-pg,cnpg-cluster,etcd} — 위 이미지를 참조하는 카탈로그 차트
- scripts/deploy-test/*.sh, .claude/deploy-test-procedure.md — CVE 0건과 별개로
"실제로 뜨는가"를 검증하는 배포 스모크 테스트
- .claude/pitfalls.md — 자체 빌드/배포 테스트 중 실측한 함정 모음
검토 중 발견해 반영한 수정:
- cloudnative-pg 차트의 image 블록을 etcd와 동일한 registry/repository/tag 3필드+
따옴표 포맷으로 통일 — 기존 포맷(repository에 registry+repo 결합, 따옴표 없음)은
patch-catalog-tag.py 의 split 패처가 tag만 갱신하고 repository는 그대로 남기는
조용한 부분 치환을 일으켜, 향후 레지스트리 마이그레이션 시 깨진 참조를 만들 수 있었다
- cnpg-cluster 차트의 SLES 커버리지 코멘트를 최신 실측(trivy가 SLES 15.7을 정상
커버함, 2026-07-29 재측정)에 맞게 정정 — 폐기된 "측정 불가/OVAL 우회 필요" 결론이
남아있었다
- CLAUDE.md/MEMORY.md 의 "images/ 디렉토리 없음" 서술을 갱신하고, 레지스트리
마이그레이션(docker.io/wbsong111 → docker.io/paasup)·decisions/analysis 문서 이관·
리소스 프로파일 추가를 다음 작업으로 기록
이 3개 이미지는 아직 dip-catalog 자체 CI(build-image.yml)로 빌드·게이트·push 를
실행해본 적이 없다 — 현재 참조 태그는 security-catalog 쪽에서 이미 검증된 것이다.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# 배포 테스트 절차
|
||||
|
||||
[CLAUDE.md](../CLAUDE.md) 에서 분리했다. 배포 테스트를 실제로 수행할 때만 참고한다.
|
||||
security-catalog 프로젝트에서 포팅했다.
|
||||
|
||||
CVE 0건이어도 동작하지 않는 이미지는 카탈로그에 넣을 수 없다. 게이트 통과와 기능 검증은
|
||||
둘 다 필수다.
|
||||
|
||||
## CNPG (`cnpg-postgresql` 이미지) — 자동화됨
|
||||
|
||||
`build-image.yml` 이 새 이미지로 카탈로그 PR 을 열면, 병합 전에 로컬 kubeconfig 로
|
||||
`scripts/deploy-test/deploy-test-cnpg-cluster.sh` 를 실행해 "실제로 뜨는가"만 빠르게 확인한다(PR 본문에도
|
||||
안내됨).
|
||||
|
||||
```sh
|
||||
IMAGE_NAME=docker.io/wbsong111/cnpg-postgresql:<태그> bash scripts/deploy-test/deploy-test-cnpg-cluster.sh /tmp/deploy-test-out
|
||||
```
|
||||
|
||||
- **Operator(`cloudnative-pg`)는 상시 컴포넌트다** — release `cnpg`, namespace
|
||||
`cnpg-system` 에 1회만 설치되고 스크립트가 재사용한다. cloudnative-pg 차트의 웹훅
|
||||
설정 이름이 릴리스와 무관하게 클러스터 전역 고정이라(`cnpg-validating-webhook-configuration`
|
||||
등) 두 번째 설치가 원천적으로 불가능하다 — 그래서 상시 설치로 전환됐다.
|
||||
- 앱(`cnpg-cluster`)만 고정 네임스페이스(`pg-test-build`)에 매번 재생성되고, 검증은
|
||||
차트 기본값 + `postgresql.imageName` override만으로 이루어진다(`custom-values.yaml`
|
||||
안 씀). 확인 범위는 "psql 로 응답하는가" 수준의 스모크 테스트다.
|
||||
- 정리(uninstall + PVC/PV + 네임스페이스 삭제)는 성공/실패와 무관하게 항상 수행된다.
|
||||
Operator 는 정리 대상이 아니다.
|
||||
- CI(GitHub Actions) 통합은 아직 없다 — 러너에서 dev 클러스터로의 네트워크 경로가 없다.
|
||||
- 백업/PITR, pooler, 동기 복제 등 스모크 테스트 밖의 항목은 아래 수동 절차를 따른다.
|
||||
security-catalog 프로젝트에 상세 배포 테스트 기록(`doc/charts/cnpg/deploy-test.md`)이
|
||||
있으나 dip-catalog 에는 아직 이관되지 않았다.
|
||||
|
||||
## etcd (`etcd` 이미지) — 스크립트 있음, 수동 실행
|
||||
|
||||
`cnpg` 와 달리 상시 오퍼레이터가 없다 — 단일 차트라 매 실행 테스트 네임스페이스
|
||||
(`etcd-test-build`)에 설치하고 끝나면 지운다.
|
||||
|
||||
```sh
|
||||
IMAGE_NAME=quay.io/coreos/etcd:v3.7.1 bash scripts/deploy-test/deploy-test-etcd.sh /tmp/deploy-test-out
|
||||
```
|
||||
|
||||
- 차트 기본값 + `image.*`/`initImage.*`/`replicas` override 만으로 검증한다
|
||||
(`custom-values.yaml` 안 씀 — cnpg 원칙과 동일).
|
||||
- 확인 범위: StatefulSet 롤아웃, 컨테이너 실행 계정(uid 999 기대), 전 멤버 quorum
|
||||
health(`etcdctl endpoint health --cluster`), 쓰기/읽기 왕복(`etcdctl put`/`get`).
|
||||
- 정리(uninstall + PVC/PV + 네임스페이스 삭제)는 성공/실패와 무관하게 항상 수행된다.
|
||||
- TLS·백업 등 스모크 테스트 밖의 항목은 `manifests/helm/etcd/1.1.12/CUSTOM-README.md` 를
|
||||
따른다. security-catalog 프로젝트에 상세 배포 테스트 기록(`doc/charts/etcd/deploy-test.md`)이
|
||||
있으나 dip-catalog 에는 아직 이관되지 않았다.
|
||||
|
||||
## 그 외 차트 — 수동 절차
|
||||
|
||||
1. 전용 네임스페이스를 새로 만든다 (`pg-test-<name>`). 기존 워크로드가 있는 NS 를 쓰지 않는다.
|
||||
2. 오퍼레이터는 가능하면 권한을 축소해 설치한다 (CNPG 는 `config.clusterWide=false`).
|
||||
3. 개발 환경 크기로 축소해 배포한다 (`--set storage.size=2Gi` 등). `custom-values.yaml`
|
||||
자체를 개발용 값으로 바꾸지 않는다.
|
||||
4. 검증 항목은 해당 차트의 `BUILD-README.md` "배포 검증" 절에 정의한다.
|
||||
5. 결과는 실측값으로 기록한다(장소는 `MEMORY.md` 또는 PR 설명 — `doc/charts/` 관례는
|
||||
dip-catalog 에 아직 없다). 통과 항목만 쓰지 말고 발견된 문제와 미검증 항목을 반드시
|
||||
남긴다.
|
||||
6. 정리한다. 정리 명령은 문서에 함께 기록한다.
|
||||
@@ -0,0 +1,53 @@
|
||||
# 이 환경에서 반복적으로 문제가 된 것들
|
||||
|
||||
security-catalog 프로젝트에서 CNPG/etcd 자체 빌드·배포 테스트 작업 중 실제로 겪은 함정을
|
||||
포팅했다. [CLAUDE.md](../CLAUDE.md) 에서 분리했다 — 매 세션 필요한 내용이 아니라 해당
|
||||
작업(자체 빌드 이미지, CNPG/etcd 배포 테스트)을 할 때만 참고한다.
|
||||
|
||||
## 스캐너 결과를 그대로 믿지 말 것
|
||||
|
||||
세 가지 실패 양상을 모두 겪었다. 게이트(`scripts/pipeline/cve-gate.py`)가 이것들을
|
||||
잡도록 만들어져 있다.
|
||||
|
||||
| 양상 | 실측 |
|
||||
| --- | --- |
|
||||
| **벤더 하향 등급** | NVD 9.8 을 Debian 이 LOW(`CVE-2019-1010022`), Ubuntu 가 MEDIUM(`CVE-2026-8376`) 으로 둠 |
|
||||
| **데이터 커버리지 부재** | Debian sid 기반 이미지가 149 패키지에 findings 0건 — 거짓 clean |
|
||||
| **벤더 미평가 은폐** | Ubuntu 의 "Needs evaluation" CVE 는 보고되지 않음. `--detection-priority comprehensive` 로도 안 나옴 |
|
||||
|
||||
> dip-catalog 의 `scan-sbom.sh` 는 두 번째 양상(데이터 커버리지 부재)을 구분하는 자가진단
|
||||
> (`CoverageProbe`)이 아직 없다 — `doc/sbom-pipeline.md` 참고.
|
||||
|
||||
## 이미지 태그의 베이스 OS 를 확인할 것
|
||||
|
||||
같은 앱 버전이라도 태그에 따라 베이스 OS 가 다르고 EOL 이 임박한 것이 섞여 있다.
|
||||
오퍼레이터가 배포판 수명 테이블을 갖고 있으면 기동 로그에 남는다
|
||||
(CNPG: `internal/cmd/manager/instance/run/osdb.go`).
|
||||
|
||||
## `kubectl get cluster` 는 쓰면 안 된다
|
||||
|
||||
dev 클러스터에 `clusters` 단축명을 쓰는 CRD 가 3개 있다(CNPG 배포 테스트 대상 클러스터
|
||||
기준).
|
||||
|
||||
- `clusters.postgresql.cnpg.io` (CloudNativePG)
|
||||
- `clusters.management.cattle.io` (Rancher)
|
||||
- `clusters.cluster.x-k8s.io` (CAPI)
|
||||
|
||||
`kubectl get cluster` 는 CNPG 가 아닌 리소스를 조회해 **빈 결과를 반환한다.**
|
||||
스크립트·런북에서는 항상 FQN 을 쓴다.
|
||||
|
||||
```sh
|
||||
kubectl -n <ns> get clusters.postgresql.cnpg.io <name>
|
||||
```
|
||||
|
||||
## 오퍼레이터 차트는 cluster-scoped 잔여물을 남긴다
|
||||
|
||||
`helm uninstall` 후에도 CRD 와 webhook 설정이 남는다. CNPG 는 webhook `failurePolicy: Fail`
|
||||
이므로 **오퍼레이터를 먼저 지우면 CR 을 삭제할 수 없게 된다.**
|
||||
정리 순서: CR → 오퍼레이터 → webhook 설정 → CRD.
|
||||
|
||||
## 선언적 리소스의 `status` 를 근거로 쓰지 말 것
|
||||
|
||||
CNPG `Database` CRD 는 spec generation 이 바뀔 때만 reconcile 한다. 확장이 DB 에서
|
||||
사라져도 `status.applied` 는 계속 `true` 다. 검증은 항상 실제 DB 에 질의해서 한다
|
||||
(security-catalog 프로젝트에서 실측 — 상세 배포 테스트 기록은 dip-catalog 에 아직 없음).
|
||||
@@ -121,10 +121,13 @@ extract-helm-images.sh → generate-sbom.sh → scan-sbom.sh → cve-gate.py
|
||||
|
||||
- **현재 warn-only**: 게이트가 실패해도 CI/PR 을 막지 않는다. 45+ 개 카탈로그 차트가
|
||||
이 게이트로 트리아지된 적이 없다.
|
||||
- 자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)는
|
||||
도입만 됐고 실사용 이미지가 없다(`images/` 디렉토리 자체가 없음). 게이트가 상위
|
||||
태그·베이스 OS 교체로 해소 안 되는 차단 CVE 를 찾으면 이 프레임워크로 자체 빌드를
|
||||
검토한다 — 절차는 [.claude/image-authoring.md](.claude/image-authoring.md).
|
||||
- 자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)로
|
||||
`images/`에 이미지 3종(`cloudnative-pg`, `cnpg-postgresql`, `etcd`, 전부
|
||||
security-catalog 프로젝트에서 포팅)이 있으나 dip-catalog 자체 CI 로는 한 번도
|
||||
실행된 적이 없다(빌드·게이트·push 모두 미검증 — 현재 참조 태그는 security-catalog
|
||||
쪽에서 이미 빌드된 것). 게이트가 상위 태그·베이스 OS 교체로 해소 안 되는 차단 CVE 를
|
||||
찾으면 이 프레임워크로 자체 빌드를 검토한다 — 절차는
|
||||
[.claude/image-authoring.md](.claude/image-authoring.md).
|
||||
- 상세: [doc/sbom-pipeline.md](doc/sbom-pipeline.md) · 승인 예외: `doc/cve-exceptions.json`
|
||||
· 현재 미결 사항: [MEMORY.md](MEMORY.md)
|
||||
|
||||
|
||||
@@ -28,12 +28,35 @@ dip-catalog 의 `scan-sbom.sh` 는 이 로직이 없다 — 게이트는 finding
|
||||
차단 처리하는 구버전 경로로만 동작한다. 45+ 차트 규모에서 이 판정이 오탐을 얼마나
|
||||
내는지 실측 후 이식 여부를 결정한다.
|
||||
|
||||
**자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)는 도입만
|
||||
했고 실사용 이미지가 없다.** `images/` 디렉토리 자체가 아직 없다. 실제 자체 빌드가
|
||||
필요해지면(게이트가 상위 태그/베이스 OS 교체로 해소 안 되는 차단 CVE 를 찾을 때)
|
||||
[.claude/image-authoring.md](.claude/image-authoring.md) 절차로 `images/<image>/` 를
|
||||
신설한다. 이때 최종 런타임 베이스 OS 정책(security-catalog 는 SUSE BCI 로 고정했으나
|
||||
dip-catalog 는 아직 미결)을 처음으로 정해야 한다.
|
||||
**`images/`에 이미지 3종(`cloudnative-pg`, `cnpg-postgresql`, `etcd`)이 security-catalog
|
||||
프로젝트에서 포팅됐지만 dip-catalog 자체 CI(`build-image.yml`)로는 아직 한 번도
|
||||
빌드·게이트·push 를 실행해본 적이 없다.** 현재 카탈로그 values(`custom-values.yaml`/
|
||||
`dip-values.yaml`)가 참조하는 태그는 전부 security-catalog 쪽에서 이미 빌드·게이트
|
||||
PASS·push 된 실제 이미지(`docker.io/wbsong111/...`)를 그대로 재사용하는 것이다 —
|
||||
dip-catalog 파이프라인으로 재현·재검증된 적은 없다. 최종 런타임 베이스 OS 정책은
|
||||
security-catalog 의 SUSE BCI 고정 결정을 그대로 따랐다(ADR 자체는 미이관, 아래 참고).
|
||||
|
||||
**레지스트리 마이그레이션(`docker.io/wbsong111` → `docker.io/paasup`)이 필요하다.**
|
||||
`build-image.yml` 의 `REGISTRY_HOST` 는 이미 `docker.io/paasup` 로 설정돼 있지만
|
||||
(아래 항목 참고) 위 3개 이미지가 실제로 가리키는 곳은 아직 `docker.io/wbsong111` 다.
|
||||
`REGISTRY=docker.io/paasup` 로 3개 이미지를 재빌드·재게이트·재검증해 PASS 를 확인한
|
||||
뒤에만 6개 values 파일(`manifests/helm/{cloudnative-pg/0.29.0,cnpg-cluster/1.0.0,
|
||||
etcd/1.1.12}/{custom-values,dip-values}.yaml`, cnpg-cluster 는 `dip-values.yaml` 도
|
||||
`imageName` 오버라이드가 있음)의 이미지 참조를 새 태그로 교체한다. push 자격 증명
|
||||
확인은 바로 아래 항목과 동일하다.
|
||||
|
||||
**`doc/decisions/`·`doc/analysis/` 디렉토리 자체가 dip-catalog 에 없다.** 포팅된 3개
|
||||
이미지의 README·values 코멘트가 `doc/decisions/000X-*.md`, `doc/analysis/*.md`,
|
||||
`doc/image-selection.md`, `doc/cve-zero-pipeline.md`, `doc/architecture/build-pipeline.md`
|
||||
를 근거로 계속 인용하지만 이 경로들은 dip-catalog 에 하나도 없다(security-catalog
|
||||
프로젝트에만 있음). 당장 급한 건 아니지만, 이 상태로는 이 레포만 보는 사람이 자체 빌드
|
||||
결정의 CVE 실측·비교 근거를 확인할 방법이 없다 — 각 README 에 이미 요약된 근거(CVE
|
||||
번호·후보 비교표)를 압축한 로컬 stub ADR 작성을 검토한다.
|
||||
|
||||
**`doc/define-chart-resources.md` 에 신규 차트 3종(`cloudnative-pg`, `cnpg-cluster`,
|
||||
`etcd`) 의 Small/Medium/Large 리소스 프로파일이 없다.** CLAUDE.md 의 "신규 차트 추가"
|
||||
규칙(리소스 프로파일 필수)을 아직 못 지켰다 — custom-values.yaml 의 기존
|
||||
requests/limits/storage 값을 근거로 표를 추가하는 별도 작업으로 처리한다.
|
||||
|
||||
**`SBOM_PIPELINE_IMAGE` 재빌드가 보류돼 있다.** 이 마이그레이션으로 빌드 컨텍스트 경로가
|
||||
`doc/scripts/Dockerfile` → `scripts/pipeline/Dockerfile` 로 바뀌었다. Dockerfile 내용
|
||||
@@ -43,7 +66,9 @@ Repo Variable `SBOM_PIPELINE_IMAGE` 갱신은 git 커밋으로 되지 않는 수
|
||||
|
||||
**`build-image.yml` 의 `REGISTRY_HOST` 를 `docker.io/paasup` 로 설정했다.** 실제 이미지를
|
||||
push 하려면 `DOCKERHUB_USER`/`DOCKERHUB_TOKEN` 시크릿이 `paasup` 조직 네임스페이스에
|
||||
push 권한을 가져야 한다 — **미확인**, 실제 첫 자체 빌드 시도 전에 확인 필요.
|
||||
push 권한을 가져야 한다 — **미확인**, 실제 첫 자체 빌드 시도 전에 확인 필요. 이제
|
||||
`images/` 의 3개 이미지가 실사용 후보이므로 위 "레지스트리 마이그레이션" 항목이 이
|
||||
확인을 실제로 필요로 하는 첫 사례다.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# cloudnative-pg — CloudNativePG 오퍼레이터 자체 빌드
|
||||
|
||||
CNPG 오퍼레이터(컨트롤러) 이미지를 업스트림 소스에서 직접 컴파일한다.
|
||||
`manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml`·`dip-values.yaml` 의
|
||||
`image.repository`/`image.tag` 가 이 산출물을 가리킨다.
|
||||
|
||||
security-catalog 프로젝트에서 포팅했다. 목표 정의·CVE 조사 실측·이미지 채택 결정
|
||||
근거(각각 `doc/cve-zero-pipeline.md`, `doc/analysis/cloudnative-pg-operator-cve.md`,
|
||||
`doc/decisions/0005-cloudnative-pg-operator-self-build.md`)는 security-catalog 프로젝트에
|
||||
있고 dip-catalog 에는 아직 이관되지 않았다 — 신규 자체 빌드 이미지 추가 절차 전반은
|
||||
[.claude/image-authoring.md](../../.claude/image-authoring.md) 참고.
|
||||
|
||||
> **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를
|
||||
> 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을
|
||||
> 잃고 재빌드 책임을 지는 선택이다.
|
||||
|
||||
## 왜 자체 빌드하나 — 그리고 왜 `cnpg-postgresql` 과 다른 방법인가
|
||||
|
||||
업스트림 `ghcr.io/cloudnative-pg/cloudnative-pg:1.30.0` 은 실효 HIGH 3건
|
||||
(`CVE-2026-39822` stdlib, `CVE-2026-56852` `golang.org/x/text`, `GHSA-hrxh-6v49-42gf`
|
||||
`google.golang.org/grpc`)으로 차단된다. 상위 태그가 없다(2026-07-30 확인, 최신 릴리스가
|
||||
여전히 `v1.30.0`). 업스트림 배포 이미지 베이스가 `gcr.io/distroless/static-debian13`
|
||||
(OS 패키지 사실상 0개)라 **베이스 OS 를 바꾸는 것만으로는 고쳐지지 않는다** — CVE 는
|
||||
바이너리에 정적 링크된 Go 모듈 버전이 원인이다. 자체 빌드(소스 컴파일)만 유효한 대응이다.
|
||||
|
||||
`cnpg-postgresql`(OS 베이스 교체 + zypper 패치)과 성격이 다르지만, **오케스트레이션은
|
||||
동일한 [scripts/build/build-hardened-image.sh](../../scripts/build/build-hardened-image.sh)
|
||||
하나를 공유한다.** 이 이미지가 그 스크립트의 계약(`build.env` 가 `DOCKERFILE`·`TARGET`·
|
||||
`BUILD_ARGS`·`APP_VERSION` 선언, `verify.sh` 가 `VERIFY-OK` 로 종료)만 지키면, 이미지
|
||||
종류(OS 패키지 설치형 vs 소스 컴파일형)는 스크립트가 몰라도 된다 — 근거는
|
||||
[.claude/image-authoring.md](../../.claude/image-authoring.md) 참고.
|
||||
|
||||
## 소스·버전 관리
|
||||
|
||||
| 항목 | 값 |
|
||||
| --- | --- |
|
||||
| 소스 | `https://github.com/cloudnative-pg/cloudnative-pg.git` |
|
||||
| pinned commit | `source.build.env` 의 `SOURCE_COMMIT` (release-1.30 브랜치) |
|
||||
| 빌더 | 공식 `golang` 이미지 (`source.build.env` 의 `GO_BUILDER_TAG`, go.mod 요구 버전과 일치) |
|
||||
| 최종 베이스 | `registry.suse.com/bci/bci-micro:15.7` — security-catalog 는 SUSE BCI 하나만 쓰기로 결정했다(ADR 미이관); 업스트림의 distroless 대신 여기 적용 |
|
||||
|
||||
빌더 스테이지(Go 컴파일)는 공식 `golang` 이미지를 그대로 쓴다 — 최종 이미지에 남는 것이
|
||||
아니라 컴파일 산출물만 최종 스테이지로 넘어오므로 스캔·정책 대상이 아니다. `bci-micro`
|
||||
는 SUSE BCI 중 가장 가벼운 변종이지만 `bci-base` 와 달리 `nonroot`(uid 65532) 계정이
|
||||
미리 없어 `source.Dockerfile` 이 직접 만든다(`/etc/passwd`·`/etc/group` 에 추가).
|
||||
|
||||
`SOURCE_COMMIT` 은 **자동 추적하지 않는다.** `cnpg-postgresql` 의 PGDG 버전처럼 사람이
|
||||
업스트림 `release-1.30` 브랜치(또는 그다음 패치 릴리스가 나오면 그 태그)를 보고
|
||||
`source.build.env` 를 고쳐 PR 을 여는 것 자체가 갱신 트리거다.
|
||||
|
||||
**권장 점검 주기**: 카탈로그 게이트가 이 이미지의 차단 CVE 를 다시 보고할 때, 또는
|
||||
업스트림이 `v1.30.1`(혹은 그 이상) 을 릴리스했을 때 — 릴리스가 나오면 그쪽으로 갈아타는
|
||||
것(대응 우선순위 a)이 이 자체 빌드를 유지하는 것보다 항상 우선이다.
|
||||
|
||||
## 빌드
|
||||
|
||||
```sh
|
||||
# 로컬 빌드 (push 없음)
|
||||
IMAGE=cloudnative-pg BASE_OS=source bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
|
||||
# 레지스트리에 push 까지 (현재 실제 배포 이미지도 이 네임스페이스에 있다)
|
||||
IMAGE=cloudnative-pg BASE_OS=source REGISTRY=docker.io/wbsong111 \
|
||||
bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
```
|
||||
|
||||
수행 순서: **빌드 → 기능 검증(`verify.sh`) → SBOM → 전 심각도 스캔 → 게이트 판정.**
|
||||
dip-catalog 의 `scan-sbom.sh` 는 커버리지 자가진단(`CoverageProbe`)이 없다는 점에
|
||||
유의한다 — `doc/sbom-pipeline.md` 참고. `verify.sh` 는 `manager version` 출력에 pinned
|
||||
commit 이 실제로 반영됐는지(ldflags 주입 확인), 이미지 `Config.User` 가
|
||||
`65532:65532`(nonroot)인지, `--help` 가 정상 종료하는지를 확인한다 — 실제 컨트롤러
|
||||
기동(k8s API 필요)은 이 스모크 테스트 범위 밖이며, dev 클러스터 배포 테스트
|
||||
(`.claude/deploy-test-procedure.md`)가 담당한다.
|
||||
|
||||
### 파일 구성
|
||||
|
||||
| 파일 | 역할 |
|
||||
| --- | --- |
|
||||
| `source.Dockerfile` | 빌드 정의 — 소스 컴파일(builder 스테이지) + SUSE BCI(`bci-micro`) 패키징(final 스테이지) |
|
||||
| `source.build.env` | pinned commit·버전·빌더 이미지 태그. `BUILD_ARGS` 에 나열한 이름만 `--build-arg` 로 전달된다 |
|
||||
| `verify.sh` | 기능 검증. 호스트에서 bash 로 실행되며 직접 `docker run --entrypoint /manager` 를 호출한다(게스트 스크립트 주입 방식보다 상위 호환 — 최종 이미지에 셸이 없는 경우에도 그대로 동작한다) |
|
||||
|
||||
베이스 변종이 하나뿐이라 파일명이 `source.*` 로 고정돼 있다 — `cnpg-postgresql` 처럼
|
||||
변종이 늘면 `<variant>.Dockerfile`/`<variant>.build.env` 로 분기한다.
|
||||
|
||||
### 태그
|
||||
|
||||
```
|
||||
docker.io/wbsong111/cloudnative-pg:1.30.0-security-hardened-20260730
|
||||
└ app ─┘└ 슬러그 ┘└ 하드닝 ┘└ 빌드일 ┘
|
||||
```
|
||||
|
||||
이 태그는 security-catalog 프로젝트에서 이미 빌드·게이트 PASS·push 된 실제 이미지다.
|
||||
dip-catalog 는 이를 그대로 재사용한다 — 재빌드·재푸시 여부는 `MEMORY.md` 참고.
|
||||
@@ -0,0 +1,11 @@
|
||||
# build-image.yml 이 읽는 카탈로그 반영 메타데이터. 빌드 정의(<variant>.build.env)와는
|
||||
# 별개다 — 이건 "이 이미지가 어느 차트의 어느 필드를 가리키는가" 만 담는다.
|
||||
|
||||
CHART_DIRS="manifests/helm/cloudnative-pg/0.29.0"
|
||||
|
||||
# 태그 표기 스타일 — imageName(단일 필드 문자열) | split(registry/repository/tag 분리)
|
||||
TAG_STYLE=split
|
||||
TAG_BLOCK=image
|
||||
|
||||
# base_os 입력을 안 주면 쓸 기본 변종 (images/<image>/<DEFAULT_BASE_OS>.build.env)
|
||||
DEFAULT_BASE_OS=source
|
||||
@@ -0,0 +1,80 @@
|
||||
# CloudNativePG 오퍼레이터 — 업스트림 소스를 pinned commit 으로 직접 컴파일한다.
|
||||
#
|
||||
# 업스트림(https://github.com/cloudnative-pg/cloudnative-pg)의 Dockerfile 은 이미 goreleaser
|
||||
# 로 빌드된 바이너리(`dist/manager/manager_<arch>`)를 COPY 만 한다 — 컴파일 자체는 이 Dockerfile
|
||||
# 밖(Makefile `docker-build` → goreleaser)에서 일어난다. 우리는 그 컴파일 단계를 Dockerfile
|
||||
# 안으로 가져와 `go build` 로 직접 재현한다. `cnpg-postgresql/suse.Dockerfile` 이 "업스트림
|
||||
# Dockerfile 을 다른 배포판으로 이식"한 것이라면, 이건 "업스트림이 Dockerfile 밖에서 하던
|
||||
# 빌드를 Dockerfile 안으로 흡수"한 것이다.
|
||||
#
|
||||
# 왜 자체 빌드인가 — cloudnative-pg:1.30.0 이 게이트에서 차단하는 CVE 3건(stdlib·x/text·grpc)
|
||||
# 은 OS 패키지가 아니라 바이너리에 정적 링크된 Go 모듈 버전이 원인이다. 배포 이미지 베이스가
|
||||
# distroless(OS 패키지 사실상 0개)라 베이스 OS 교체로는 고칠 수 없다 — 소스를 다시 컴파일해야
|
||||
# 한다. 세 CVE 모두 업스트림 release-1.30 브랜치에 이미 백포트돼 있다(SOURCE_COMMIT 이 그
|
||||
# 커밋을 가리킨다) — 근거: doc/analysis/cloudnative-pg-operator-cve.md
|
||||
#
|
||||
# 업스트림과의 대응 관계 (release-1.30 브랜치 Dockerfile 기준)
|
||||
# go build (Makefile build-manager) → 동일 (ldflags 까지 그대로)
|
||||
# goreleaser 멀티아치(manager_amd64/arm64) → 단일 아키텍처(linux/amd64)만 직접 COPY.
|
||||
# 심볼릭 링크 대신 같은 바이너리를 두 경로에 COPY (아래 "실측으로 드러난 필수 조건" 참고)
|
||||
# distroless base(gcr.io/distroless/static-debian13:nonroot) → SUSE BCI(bci-micro)로 교체.
|
||||
# 카탈로그는 SUSE BCI 하나만 쓴다(decisions/0001) — 최종 런타임 이미지에도 동일하게 적용한다.
|
||||
# builder 스테이지(Go 컴파일)는 공식 golang 이미지를 그대로 쓴다 — 컴파일 결과물에는
|
||||
# 영향이 없고, 최종 이미지에만 남는 게 무엇인지가 스캔·정책 대상이다
|
||||
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_BUILDER_TAG=1.26.5-trixie
|
||||
# FROM 에서 쓰는 ARG 는 반드시 첫 FROM 이전(전역 스코프)에 선언해야 한다 — 스테이지 안에서
|
||||
# 선언하면(예: 이전엔 builder 스테이지 RUN 다음에 둠) 그 스테이지 지역 변수가 되어 다음
|
||||
# FROM 의 이미지명 해석에 쓰이지 않는다(실측: "FROM argument 'RUNTIME_BASE' is not
|
||||
# declared" 경고와 함께 빈 이미지명 에러 발생).
|
||||
ARG RUNTIME_BASE=registry.suse.com/bci/bci-micro:15.7
|
||||
|
||||
# 호스트 네이티브 아키텍처로 빌더를 띄운다. Go 크로스컴파일은 에뮬레이션이 필요 없으므로
|
||||
# --platform=$BUILDPLATFORM 로 고정해 에뮬레이션 오버헤드를 피한다(로컬 arm64 Docker 데스크톱
|
||||
# 에서 linux/amd64 결과물을 만들 때 특히 중요).
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_BUILDER_TAG} AS builder
|
||||
ARG TARGETARCH
|
||||
ARG SOURCE_COMMIT
|
||||
ARG APP_VERSION
|
||||
WORKDIR /src
|
||||
|
||||
# BuildKit 의 git context 지원 — tarball+checksum 관리 없이 git 자체가 커밋 무결성을 보장한다.
|
||||
ADD https://github.com/cloudnative-pg/cloudnative-pg.git#${SOURCE_COMMIT} /src
|
||||
|
||||
# 업스트림 Makefile 의 LDFLAGS·.goreleaser.yml 의 build 설정을 그대로 재현한다.
|
||||
# (release-1.30 기준 go.mod: go 1.26.5, google.golang.org/grpc v1.82.1, golang.org/x/text v0.39.0)
|
||||
RUN --mount=type=cache,target=/root/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
set -eux; \
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -trimpath \
|
||||
-ldflags "-s -w \
|
||||
-X github.com/cloudnative-pg/cloudnative-pg/pkg/versions.buildVersion=${APP_VERSION} \
|
||||
-X github.com/cloudnative-pg/cloudnative-pg/pkg/versions.buildCommit=${SOURCE_COMMIT} \
|
||||
-X github.com/cloudnative-pg/cloudnative-pg/pkg/versions.buildDate=$(date -u +%Y-%m-%d)" \
|
||||
-o /out/manager ./cmd/manager
|
||||
|
||||
FROM ${RUNTIME_BASE} AS final
|
||||
WORKDIR /
|
||||
|
||||
# bci-micro 는 root 만 있고 65532 사용자가 없다(distroless 의 nonroot 변종과 달리 nonroot
|
||||
# 계정을 미리 만들어두지 않는다) — 직접 만든다. bci-micro 에 bash·coreutils 는 있다
|
||||
# (zypper·rpm 은 없음 — "micro" 는 패키지 매니저가 빠진 것이지 셸까지 없는 건 아니다).
|
||||
RUN set -eux; \
|
||||
echo 'nonroot:x:65532:65532:nonroot:/home/nonroot:/bin/false' >> /etc/passwd; \
|
||||
echo 'nonroot:x:65532:' >> /etc/group; \
|
||||
mkdir -p /home/nonroot; \
|
||||
chown 65532:65532 /home/nonroot
|
||||
|
||||
# 실측으로 드러난 필수 조건: 오퍼레이터는 `operator/manager_<GOARCH>` 를 런타임에 glob 해서
|
||||
# "가용 아키텍처" 목록을 만든다(pkg/utils/discovery.go DetectAvailableArchitectures) — 이
|
||||
# 목록이 비어 있으면 Cluster 리컨실이 "invalid architecture: amd64" 로 실패한다(배포
|
||||
# 테스트 2026-07-30 에서 재현). 업스트림은 멀티아치 심볼릭 링크로 이걸 만들지만, 우리는
|
||||
# 단일 아키텍처만 다루므로 같은 바이너리를 두 경로에 COPY 해 같은 효과를 낸다.
|
||||
COPY --from=builder /out/manager /manager
|
||||
COPY --from=builder /out/manager /operator/manager_amd64
|
||||
COPY --from=builder /src/licenses /licenses
|
||||
COPY --from=builder /src/LICENSE /licenses/LICENSE
|
||||
USER 65532:65532
|
||||
ENTRYPOINT ["/manager"]
|
||||
@@ -0,0 +1,32 @@
|
||||
# CloudNativePG 오퍼레이터 — 소스 컴파일형 자체 빌드 (유일한 변종)
|
||||
#
|
||||
# build-hardened-image.sh 가 source 한다. BUILD_ARGS 에 나열한 이름만 --build-arg 로 넘어간다.
|
||||
# 이 이미지는 소스를 직접 컴파일하므로 "베이스 OS" 선택지가 없다 — BASE_OS=source 로 호출한다:
|
||||
# IMAGE=cloudnative-pg BASE_OS=source bash scripts/build/build-hardened-image.sh <OUT_DIR>
|
||||
#
|
||||
# 왜 자체 빌드하는가·CVE 근거 → doc/analysis/cloudnative-pg-operator-cve.md
|
||||
# 결정 → doc/decisions/0004-cloudnative-pg-operator-self-build.md
|
||||
|
||||
DOCKERFILE=source.Dockerfile
|
||||
TARGET=final
|
||||
TAG_SLUG=security
|
||||
|
||||
# build-hardened-image.sh 가 태그·verify.sh 전달용으로 요구하는 범용 필수값.
|
||||
# 스톡 1.30.0 과는 태그 슬러그(TAG_SLUG=security)로 구분되므로 버전 문자열 자체는 그대로 둔다.
|
||||
APP_VERSION=1.30.0
|
||||
|
||||
# release-1.30 브랜치 HEAD, 2026-07-30 확인 — CVE-2026-39822(stdlib)·CVE-2026-56852(x/text)·
|
||||
# GHSA-hrxh-6v49-42gf(grpc) 가 모두 이 커밋에 백포트돼 있다. 갱신할 때는 release-1.30 의
|
||||
# 최신 커밋으로 사람이 다시 고른다(자동 추적하지 않음 — cnpg-postgresql 의 PGDG 버전처럼
|
||||
# 사람이 트리거해야 하는 갱신 축).
|
||||
SOURCE_COMMIT=4463551204bc5cdb5af05b2c60a2d6b58ce9ff6a
|
||||
|
||||
# go.mod 의 `go 1.26.5` 요구를 만족하는 공식 golang 이미지 태그. 이 이미지는 builder
|
||||
# 스테이지에서만 쓰이고 최종 이미지에는 남지 않는다.
|
||||
GO_BUILDER_TAG=1.26.5-trixie
|
||||
|
||||
# 최종 런타임 베이스 — 카탈로그는 SUSE BCI 하나만 쓴다(decisions/0001). bci-base 대신
|
||||
# 가장 가벼운 bci-micro 를 쓴다(패키지 매니저 없음, bash·coreutils 는 있음).
|
||||
RUNTIME_BASE=registry.suse.com/bci/bci-micro:15.7
|
||||
|
||||
BUILD_ARGS="SOURCE_COMMIT GO_BUILDER_TAG APP_VERSION RUNTIME_BASE"
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# cloudnative-pg 오퍼레이터 이미지 기능 검증 — 호스트에서 bash 로 실행된다
|
||||
# (build-hardened-image.sh 가 `env TAG=... PLATFORM=... SOURCE_COMMIT=... bash verify.sh`
|
||||
# 형태로 호출한다).
|
||||
#
|
||||
# cnpg-postgresql/verify.sh 와 달리 게스트 셸을 쓰지 않는다 — 최종 이미지 베이스가
|
||||
# gcr.io/distroless/static-debian13:nonroot 라 셸이 아예 없다(`/bin/sh` 없음, 업스트림
|
||||
# Dockerfile 도 이 사실을 자기 빌더 스테이지 주석에 명시한다). 대신 `--entrypoint /manager`
|
||||
# 로 바이너리를 직접 실행해 검증한다 — 실제 컨트롤러 기동(k8s API 필요)은 이 스모크
|
||||
# 테스트 범위 밖이며, dev 클러스터 배포 테스트가 담당한다.
|
||||
#
|
||||
# 마지막 줄에 VERIFY-OK 를 출력하면 통과다. build-hardened-image.sh 가 그것으로 판정한다.
|
||||
set -e
|
||||
|
||||
TAG="${TAG:?TAG 환경변수가 필요하다}"
|
||||
PLATFORM="${PLATFORM:-linux/amd64}"
|
||||
SOURCE_COMMIT="${SOURCE_COMMIT:?SOURCE_COMMIT 환경변수가 필요하다 (build-hardened-image.sh 가 build.env 에서 전달)}"
|
||||
SHORT_COMMIT="${SOURCE_COMMIT:0:8}"
|
||||
|
||||
echo "== manager version =="
|
||||
OUT="$(docker run --rm --platform "$PLATFORM" --entrypoint /manager "$TAG" version)"
|
||||
echo " $OUT"
|
||||
case "$OUT" in
|
||||
*"$SHORT_COMMIT"*) ;;
|
||||
*) echo "FAIL: version 출력에 pinned commit($SHORT_COMMIT) 이 없다 — ldflags 주입 확인 필요"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "== 실행 사용자 (nonroot, 이미지 메타데이터) =="
|
||||
# 셸이 없어 컨테이너 안에서 id 를 실행할 수 없다 — 이미지가 선언한 기본 User 를 확인한다.
|
||||
# 위 version 실행 자체가 이 기본 사용자로 이미 성공했다(권한 문제였다면 여기까지 오지 못한다).
|
||||
USER_CFG="$(docker inspect --format '{{.Config.User}}' "$TAG")"
|
||||
[ "$USER_CFG" = "65532:65532" ] || { echo "FAIL: 이미지 Config.User 가 65532:65532 가 아니다 (실제: $USER_CFG)"; exit 1; }
|
||||
echo " Config.User=$USER_CFG"
|
||||
|
||||
echo "== --help 스모크 (k8s API 없이 도는 유일한 확인 범위) =="
|
||||
docker run --rm --platform "$PLATFORM" --entrypoint /manager "$TAG" --help >/dev/null
|
||||
echo " /manager --help 종료 코드 0"
|
||||
|
||||
echo "VERIFY-OK"
|
||||
@@ -0,0 +1,132 @@
|
||||
# cnpg-postgresql — CloudNativePG PostgreSQL 자체 빌드
|
||||
|
||||
CNPG 오퍼레이터가 관리하는 PostgreSQL 인스턴스 이미지를 직접 빌드한다.
|
||||
`manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml` 의 `imageName` 이 이 산출물을 가리킨다.
|
||||
|
||||
security-catalog 프로젝트에서 포팅했다. 목표 정의·이미지 선택 규칙·후보 비교 실측·이미지
|
||||
채택 결정·빌드 자동화(CI) 문서(`doc/cve-zero-pipeline.md`, `doc/image-selection.md`,
|
||||
`doc/analysis/cnpg-image-vuln-comparison.md`, `doc/decisions/0001-cnpg-postgresql-image.md`,
|
||||
`doc/architecture/build-pipeline.md`)는 security-catalog 프로젝트에 있고 dip-catalog 에는
|
||||
아직 이관되지 않았다.
|
||||
|
||||
> **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를
|
||||
> 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을
|
||||
> 잃고 재빌드 책임을 지는 선택이다.
|
||||
|
||||
---
|
||||
|
||||
## 왜 자체 빌드하나
|
||||
|
||||
업스트림 `ghcr.io/cloudnative-pg/postgresql:18.4-standard-trixie` 는 실효 CRITICAL/HIGH
|
||||
23건을 갖고 있고, **전부 수정 버전이 없다**(Debian `unimportant` 9 / `no-dsa` 7 /
|
||||
`postponed` 3 / 미분류 4). SUSE 는 같은 CVE 들을 이미 백포트했다 — 베이스 OS 를 바꾸면
|
||||
벤더 판정이 달라진다.
|
||||
|
||||
**단, 수치가 낮아진 것이 실제 개선인지 벤더 미평가인지 반드시 구분해야 한다.**
|
||||
`cve-gate.py` 의 교차 검증(`--crossref`)이 이를 돕는다. security-catalog 의
|
||||
`scan-sbom.sh` 가 제공하던 커버리지 자가진단(`CoverageProbe`)은 dip-catalog 의
|
||||
`scan-sbom.sh` 에는 아직 없다(`doc/sbom-pipeline.md` 참고).
|
||||
|
||||
## 베이스 OS
|
||||
|
||||
이 이미지는 **SUSE BCI 15.7** 로 빌드됐다(security-catalog 프로젝트 ADR, 미이관).
|
||||
|
||||
| 파일 | 베이스 | 패키지 관리자 | 측정 가능성 |
|
||||
| --- | --- | --- | --- |
|
||||
| `suse.Dockerfile` | `registry.suse.com/bci/bci-base:15.7` | zypper + PGDG rpm | ✅ trivy 로 완전 측정 |
|
||||
|
||||
`ubuntu:24.04` 기반 빌드(후보 B)는 security-catalog 프로젝트에서 검토 후 제거됐다 —
|
||||
카탈로그가 채택하지 않아 재빌드되지 않은 채 낡아가고 있었다(빌드 하루 만에 findings
|
||||
38 → 46). 관련 실측은 이관되지 않았다.
|
||||
|
||||
### 업스트림과의 차이
|
||||
|
||||
**원칙: 업스트림 Dockerfile 과의 차이를 최소로 유지한다.** 패키지 구성·uid·확장 목록을
|
||||
임의로 줄이면 오퍼레이터가 이미지에 기대하는 조건이 깨진다.
|
||||
|
||||
`suse.Dockerfile` 은 업스트림 CNPG(Debian/apt/PGDG-deb)를 SUSE(zypper/PGDG-rpm)로
|
||||
이식한 것이라 저장소 설정·패키지명·경로가 전부 다르다. 대응 관계는 파일 상단 주석 참고.
|
||||
|
||||
`patched` 단계의 `zypper update -y` 가 실제로 기여한 몫: 베이스 이미지는 주기적으로만
|
||||
재빌드되므로 배포판 아카이브보다 뒤처져 있다. 이 단계가 없으면 그 시점의 미패치 취약점이
|
||||
그대로 남는다 — 자동 재빌드가 필요한 이유가 이것이다.
|
||||
|
||||
### 업스트림 빌드 레시피 변경 점검 (수동, 자동화 대상 아님)
|
||||
|
||||
`cloudnative-pg/postgres-containers` 저장소가 자체 Dockerfile 구조를 바꾸면(새 확장,
|
||||
새 하드닝 단계 등) 이 포트도 따라가야 한다. **CI 로 자동 감지하지 않는다** — 배포판이
|
||||
달라(Debian vs SUSE) 의미 있는 diff 가 안 나온다.
|
||||
|
||||
**권장 점검 주기: CNPG 마이너 릴리스마다.** 업스트림 Dockerfile 을 훑어보고 구조가
|
||||
바뀌었으면 `suse.Dockerfile` 을 손으로 다시 이식한다.
|
||||
|
||||
---
|
||||
|
||||
## 빌드
|
||||
|
||||
```sh
|
||||
# 카탈로그가 쓰는 베이스 OS (기본값)
|
||||
IMAGE=cnpg-postgresql BASE_OS=suse bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
|
||||
# 레지스트리에 push 까지 (현재 실제 배포 이미지도 이 네임스페이스에 있다)
|
||||
IMAGE=cnpg-postgresql BASE_OS=suse REGISTRY=docker.io/wbsong111 \
|
||||
bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
|
||||
# 교차 검증을 함께 (사각지대 후보를 뽑는다. CI 에는 아직 연결되지 않았다)
|
||||
CROSSREF=/tmp/ref/standard-trixie.json IMAGE=cnpg-postgresql BASE_OS=suse \
|
||||
bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
```
|
||||
|
||||
수행 순서: **빌드 → 기능 검증(`verify.sh`) → SBOM → 전 심각도 스캔 → 게이트 판정.**
|
||||
기능 검증을 통과하지 못하면 스캔으로 넘어가지 않는다.
|
||||
CVE 0건이어도 동작하지 않는 이미지는 카탈로그에 넣을 수 없다.
|
||||
|
||||
### 파일 구성
|
||||
|
||||
| 파일 | 역할 |
|
||||
| --- | --- |
|
||||
| `<base-os>.Dockerfile` | 빌드 정의 |
|
||||
| `<base-os>.build.env` | 베이스·앱 버전·확장 목록. `BUILD_ARGS` 에 나열한 이름만 `--build-arg` 로 전달된다 |
|
||||
| `verify.sh` | 기능 검증. **베이스 OS 공통** — CNPG 요구사항은 베이스와 무관하다 |
|
||||
|
||||
베이스 OS 가 늘어도 이 구조는 그대로다 — `build-hardened-image.sh` 는 `BASE_OS` 환경변수로
|
||||
`<base-os>.build.env` 를 고른다.
|
||||
|
||||
### 태그에 빌드일을 넣는다
|
||||
|
||||
```
|
||||
docker.io/wbsong111/cnpg-postgresql:18.4-bci15.7-hardened-20260729
|
||||
└ 앱 ─┘└ 베이스 ─┘└ 하드닝 ┘└ 빌드일 ┘
|
||||
```
|
||||
|
||||
같은 앱 버전이라도 `zypper update` 결과가 시점마다 다르다. 롤링 태그를 피하고
|
||||
자체 이미지에도 같은 실수를 하지 않는다(빌드일 포함 고정 태그).
|
||||
|
||||
이 태그는 security-catalog 프로젝트에서 이미 빌드·게이트 PASS·push 된 실제 이미지다.
|
||||
dip-catalog 는 이를 그대로 재사용한다 — 재빌드·재푸시 여부는 `MEMORY.md` 참고.
|
||||
|
||||
---
|
||||
|
||||
## 실측 (security-catalog, 2026-07-29 / trivy 0.72.0 기준)
|
||||
|
||||
| | `bci15.7-hardened-20260728` |
|
||||
| --- | --- |
|
||||
| 커버리지 자가진단 | ✅ `ok` |
|
||||
| 전 심각도 findings | 0 |
|
||||
| 실효 고유 C/H | **0 / 0** |
|
||||
| 게이트 | **PASS** |
|
||||
|
||||
### 채택 당시 비교 (후보 A/B/C, security-catalog 기준)
|
||||
|
||||
| | 업스트림 `standard-trixie` (A) | Ubuntu 24.04 자체빌드 (B, 제거됨) | **SUSE BCI 15.7 (C, 채택)** |
|
||||
| --- | --- | --- | --- |
|
||||
| 패키지 수 | 148 | 147 | 182 |
|
||||
| trivy findings (전 심각도) | 316 | 46 | 0 |
|
||||
| 실효 고유 C/H | 23 | 0 | 0 |
|
||||
| 미조사 사각지대 | 0 | 18건 | 18건 |
|
||||
| 서명·attestation | ✅ | ❌ | ❌ |
|
||||
| 배포 검증 | — | ✅ failover 2초 | ✅ failover 3초 |
|
||||
| gid | `postgres` | ⚠️ `tape` | `postgres` |
|
||||
|
||||
이 수치는 dip-catalog 에서 재측정한 것이 아니라 security-catalog 프로젝트에서 이관한
|
||||
기록이다 — dip-catalog 자체 게이트로 재확인은 아직 하지 않았다(`MEMORY.md` 참고).
|
||||
@@ -0,0 +1,11 @@
|
||||
# build-image.yml 이 읽는 카탈로그 반영 메타데이터. 빌드 정의(<variant>.build.env)와는
|
||||
# 별개다 — 이건 "이 이미지가 어느 차트의 어느 필드를 가리키는가" 만 담는다.
|
||||
|
||||
# 이 이미지의 태그를 참조하는 차트 버전 디렉토리 (공백 구분, 여러 개 가능)
|
||||
CHART_DIRS="manifests/helm/cnpg-cluster/1.0.0"
|
||||
|
||||
# 태그 표기 스타일 — imageName(단일 필드 문자열) | split(registry/repository/tag 분리)
|
||||
TAG_STYLE=imageName
|
||||
|
||||
# base_os 입력을 안 주면 쓸 기본 변종 (images/<image>/<DEFAULT_BASE_OS>.build.env)
|
||||
DEFAULT_BASE_OS=suse
|
||||
@@ -0,0 +1,83 @@
|
||||
# CloudNativePG PostgreSQL — SUSE BCI 기반 자체 빌드
|
||||
#
|
||||
# 업스트림 CNPG Dockerfile(Debian/apt/PGDG-deb)을 SUSE(zypper/PGDG-rpm)로 이식한 것이다.
|
||||
# ARG BASE 만 바꾸는 수준이 아니라 패키지 관리자·저장소 설정·패키지명·경로가 모두 다르다.
|
||||
#
|
||||
# 업스트림과의 대응 관계
|
||||
# postgresql-common + apt.postgresql.org.sh → rpm --import + zypper addrepo (PGDG zypp)
|
||||
# postgresql-18 → postgresql18-server
|
||||
# postgresql-18-pgaudit / -pgvector → pgaudit_18 / pgvector_18
|
||||
# locales-all → glibc-locale
|
||||
# usermod -u 26 postgres → 불필요 (PGDG RPM 이 uid 26 으로 생성)
|
||||
# /usr/lib/postgresql/18/bin → /usr/pgsql-18/bin
|
||||
#
|
||||
# CNPG 호환성 근거 (실측)
|
||||
# - uid 26: PGDG RPM 이 postgres 를 uid/gid 26 으로 만든다 (RPM 계열 관례)
|
||||
# - 바이너리 탐색: CNPG 는 경로를 하드코딩하지 않고 PATH 로 찾는다.
|
||||
# cloudnative-pg 소스에서 "usr/lib/postgresql" 는 테스트 픽스처에만 등장한다.
|
||||
# 그래도 하드코딩 경로를 쓰는 코드가 생길 경우를 대비해 심볼릭 링크를 함께 둔다.
|
||||
#
|
||||
# 측정 가능성 — trivy 는 SLES 15.7 을 정상 커버한다.
|
||||
# 2026-07-29 이전에는 "trivy 의 SUSE 데이터가 15.7 을 커버하지 않는다" 로 판단했으나
|
||||
# 양성 대조(SBOM 사본에 취약 버전 주입 후 재스캔)로 재측정한 결과 오판이었다 — trivy 는
|
||||
# 13건을 잡았다(SUSE-SU ID 포함). 0건은 이 이미지가 실제로 최신이라서다.
|
||||
# scan-sbom.sh 의 커버리지 자가진단이 매 스캔마다 이를 확인한다.
|
||||
# 경위: doc/analysis/sles-oval-measurement.md
|
||||
|
||||
ARG BASE=registry.suse.com/bci/bci-base:15.7
|
||||
FROM $BASE AS minimal
|
||||
|
||||
ARG PG_MAJOR=18
|
||||
ARG PG_VERSION=18.4-4200001PGDG.sles15.7
|
||||
ARG SLE_REPO=sles-15.7-x86_64
|
||||
ARG PGDG_KEY=PGDG-RPM-GPG-KEY-SLES15
|
||||
|
||||
ENV PATH=$PATH:/usr/pgsql-${PG_MAJOR}/bin
|
||||
|
||||
# PGDG 저장소는 GPG 키를 미리 import 해야 한다. addrepo 만 하면
|
||||
# "Signature verification failed for repomd.xml" 로 저장소가 스킵된다.
|
||||
RUN set -eux; \
|
||||
rpm --import "https://download.postgresql.org/pub/repos/zypp/keys/${PGDG_KEY}"; \
|
||||
zypper --non-interactive addrepo --refresh \
|
||||
"https://download.postgresql.org/pub/repos/zypp/${PG_MAJOR}/suse/${SLE_REPO}/" pgdg; \
|
||||
zypper --non-interactive refresh; \
|
||||
zypper --non-interactive install -y --no-recommends \
|
||||
"postgresql${PG_MAJOR}-server=${PG_VERSION}"; \
|
||||
zypper clean --all; \
|
||||
rm -rf /var/log/zypp /var/cache/zypp
|
||||
|
||||
# CNPG 는 PATH 로 바이너리를 찾지만, Debian 레이아웃을 가정하는 코드가 생길 경우를 대비한다.
|
||||
RUN set -eux; \
|
||||
mkdir -p /usr/lib/postgresql; \
|
||||
ln -sfn "/usr/pgsql-${PG_MAJOR}" "/usr/lib/postgresql/${PG_MAJOR}"; \
|
||||
id postgres | grep -q 'uid=26(' || { echo "postgres uid 가 26 이 아니다"; exit 1; }
|
||||
|
||||
USER 26
|
||||
|
||||
|
||||
FROM minimal AS standard
|
||||
ARG PG_MAJOR=18
|
||||
# 업스트림 standard 와 동등한 구성: pgaudit, pgvector, contrib(pg_stat_statements), 전 로케일.
|
||||
# pg-failover-slots 는 PGDG zypp 저장소에 없어 제외한다 (업스트림 Debian standard 와의 차이).
|
||||
ARG EXTENSIONS="pgaudit_${PG_MAJOR} pgvector_${PG_MAJOR}"
|
||||
USER 0
|
||||
RUN set -eux; \
|
||||
zypper --non-interactive install -y --no-recommends \
|
||||
glibc-locale \
|
||||
"postgresql${PG_MAJOR}-contrib" \
|
||||
${EXTENSIONS}; \
|
||||
zypper clean --all; \
|
||||
rm -rf /var/log/zypp /var/cache/zypp
|
||||
USER 26
|
||||
|
||||
|
||||
# 베이스 이미지에 남은 구버전 패키지를 보안 업데이트로 올린다.
|
||||
# Debian 판의 `apt-get upgrade` 단계에 대응한다 (그쪽에서 NVD 기준 HIGH 5건을 해소했다).
|
||||
FROM standard AS patched
|
||||
USER 0
|
||||
RUN set -eux; \
|
||||
zypper --non-interactive refresh; \
|
||||
zypper --non-interactive update -y --no-recommends; \
|
||||
zypper clean --all; \
|
||||
rm -rf /var/log/zypp /var/cache/zypp
|
||||
USER 26
|
||||
@@ -0,0 +1,28 @@
|
||||
# CNPG PostgreSQL — SUSE BCI / zypper 기반 자체 빌드 (카탈로그 채택 베이스 OS)
|
||||
#
|
||||
# build-hardened-image.sh 가 source 한다. BUILD_ARGS 에 나열한 이름만 --build-arg 로 넘어간다.
|
||||
# BASE_OS 기본값이 이 파일(suse)이다 — 카탈로그의 imageName 이 실제로 이 정의로 빌드된다.
|
||||
#
|
||||
# 측정 가능성 — trivy 는 SLES 15.7 을 정상 커버한다 (2026-07-29 재측정, 이전 판단 정정).
|
||||
# 상세: doc/analysis/sles-oval-measurement.md
|
||||
|
||||
DOCKERFILE=suse.Dockerfile
|
||||
TARGET=patched
|
||||
TAG_SLUG=bci15.7
|
||||
|
||||
# build-hardened-image.sh 가 태그·verify.sh 전달용으로 요구하는 범용 필수값.
|
||||
# PG_VERSION(아래, PGDG 패키지의 정확한 EVR)에서 메이저.마이너만 뽑은 값과 같다 —
|
||||
# 예전엔 스크립트가 PG_VERSION 을 파싱해 유도했지만, 이제는 이미지가 명시적으로 선언한다.
|
||||
APP_VERSION=18.4
|
||||
|
||||
BASE=registry.suse.com/bci/bci-base:15.7
|
||||
PG_MAJOR=18
|
||||
# PGDG zypp 저장소의 정확한 EVR. apt 쪽과 형식이 다르다.
|
||||
PG_VERSION=18.4-4200001PGDG.sles15.7
|
||||
SLE_REPO=sles-15.7-x86_64
|
||||
PGDG_KEY=PGDG-RPM-GPG-KEY-SLES15
|
||||
|
||||
# pg-failover-slots 는 PGDG zypp 저장소에 없어 제외한다 (apt 변종과의 차이).
|
||||
EXTENSIONS="pgaudit_18 pgvector_18"
|
||||
|
||||
BUILD_ARGS="BASE PG_MAJOR PG_VERSION SLE_REPO PGDG_KEY EXTENSIONS"
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# CNPG PostgreSQL 이미지 기능 검증 — 호스트에서 bash 로 실행된다(build-hardened-image.sh 가
|
||||
# `env TAG=... PLATFORM=... PG_MAJOR=... bash verify.sh` 형태로 호출한다). 이 이미지는 셸
|
||||
# (`/bin/sh`)이 있으므로, 실제 점검은 컨테이너 안에서 게스트 셸 스크립트로 수행한다.
|
||||
#
|
||||
# 마지막 줄에 VERIFY-OK 를 출력하면 통과다. build-hardened-image.sh 가 그것으로 판정한다.
|
||||
#
|
||||
# 왜 이미지 디렉토리에 두나: 검증 항목은 이미지마다 다르다. 빌드 스크립트에 하드코딩하면
|
||||
# 이미지가 둘 이상이 될 때 깨진다. 실제로 apt·suse 두 변종이 생겨 분리했다.
|
||||
#
|
||||
# CNPG 가 이미지에 요구하는 것(오퍼레이터 소스 기준)
|
||||
# - uid 26 — 다르면 오퍼레이터가 관리하는 볼륨 권한이 깨진다
|
||||
# - PATH 에서 initdb/pg_ctl/postgres 를 찾을 수 있어야 한다 (경로 하드코딩 아님)
|
||||
# - 차트가 선언한 확장이 실제로 생성되어야 한다
|
||||
set -e
|
||||
|
||||
TAG="${TAG:?TAG 환경변수가 필요하다}"
|
||||
PLATFORM="${PLATFORM:-linux/amd64}"
|
||||
PG_MAJOR="${PG_MAJOR:?PG_MAJOR 환경변수가 필요하다 (build-hardened-image.sh 가 build.env 에서 전달)}"
|
||||
|
||||
docker run --rm -i --platform "$PLATFORM" -e PG_MAJOR="$PG_MAJOR" --entrypoint sh "$TAG" <<'GUEST'
|
||||
set -e
|
||||
|
||||
export PGDATA=/tmp/_verify
|
||||
export PATH="/usr/lib/postgresql/${PG_MAJOR}/bin:/usr/pgsql-${PG_MAJOR}/bin:$PATH"
|
||||
|
||||
echo "== 실행 사용자 =="
|
||||
uid=$(id -u)
|
||||
gid=$(id -g)
|
||||
gname=$(id -gn 2>/dev/null || echo '?')
|
||||
echo " uid=$uid gid=$gid($gname)"
|
||||
[ "$uid" = "26" ] || { echo "FAIL: uid=$uid — CNPG 는 26 을 요구한다"; exit 1; }
|
||||
|
||||
# gid 는 실패시키지 않고 경고만 한다. 실측 사례: Ubuntu 베이스에서 gid 26 이 `tape`
|
||||
# 그룹에 선점되어 있어 postgres 가 tape 그룹으로 동작했다. 기능은 동작하지만 위생 문제다.
|
||||
if [ "$gname" != "postgres" ]; then
|
||||
echo " WARN: gid $gid 의 그룹명이 '$gname' 이다 (postgres 가 아님)"
|
||||
fi
|
||||
|
||||
echo "== 바이너리 탐색 (PATH 기준) =="
|
||||
for b in initdb pg_ctl postgres psql; do
|
||||
p=$(command -v "$b") || { echo "FAIL: $b 를 PATH 에서 찾을 수 없다"; exit 1; }
|
||||
echo " $b → $p"
|
||||
done
|
||||
|
||||
echo "== initdb + 기동 =="
|
||||
initdb -D "$PGDATA" -A trust >/dev/null
|
||||
pg_ctl -D "$PGDATA" -w start >/dev/null \
|
||||
-o "-c shared_preload_libraries=pgaudit,pg_stat_statements -c unix_socket_directories=/tmp"
|
||||
psql -h /tmp -U postgres -Atc "SELECT version();" | sed 's/^/ /'
|
||||
|
||||
echo "== 확장 생성 =="
|
||||
# 차트(cnpg-cluster)가 Database CRD 로 선언하는 것과 같은 목록이다.
|
||||
psql -h /tmp -U postgres -q -c \
|
||||
"CREATE EXTENSION pgaudit; CREATE EXTENSION vector; CREATE EXTENSION pg_stat_statements;"
|
||||
psql -h /tmp -U postgres -Atc \
|
||||
"SELECT extname||' '||extversion FROM pg_extension ORDER BY 1;" | sed 's/^/ /'
|
||||
|
||||
echo "== libxml2 링크 (XML 함수) =="
|
||||
psql -h /tmp -U postgres -Atc \
|
||||
"SELECT xml_is_well_formed_document('<a>x</a>');" | sed 's/^/ /'
|
||||
|
||||
echo "== 로케일 =="
|
||||
# 업스트림 standard 는 전 로케일을 포함한다. 줄어들면 정렬·비교 동작이 달라진다.
|
||||
n=$(psql -h /tmp -U postgres -Atc "SELECT count(*) FROM pg_collation;")
|
||||
echo " pg_collation: ${n}개"
|
||||
[ "$n" -gt 100 ] || echo " WARN: collation 이 ${n}개다 — 로케일 패키지 누락 의심"
|
||||
|
||||
pg_ctl -D "$PGDATA" -w stop >/dev/null
|
||||
echo "VERIFY-OK"
|
||||
GUEST
|
||||
@@ -0,0 +1,95 @@
|
||||
# etcd — 자체 빌드
|
||||
|
||||
etcd 서버/`etcdctl`/`etcdutl` 바이너리를 업스트림 소스에서 직접 컴파일한다.
|
||||
`manifests/helm/etcd/1.1.12/custom-values.yaml` 의 `image.repository`/`image.tag` 가
|
||||
이 산출물을 가리킨다.
|
||||
|
||||
security-catalog 프로젝트에서 포팅했다. 목표 정의·CVE 조사 실측·이미지 채택 결정
|
||||
근거(각각 `doc/cve-zero-pipeline.md`, `doc/analysis/etcd-cve.md`,
|
||||
`doc/decisions/0007-etcd-image-self-build.md`)는 security-catalog 프로젝트에 있고
|
||||
dip-catalog 에는 아직 이관되지 않았다 — 신규 자체 빌드 이미지 추가 절차 전반은
|
||||
[.claude/image-authoring.md](../../.claude/image-authoring.md) 참고.
|
||||
|
||||
> **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를
|
||||
> 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을
|
||||
> 잃고 재빌드 책임을 지는 선택이다.
|
||||
|
||||
## 왜 자체 빌드하나 — 그리고 왜 `cloudnative-pg` 와 같은 방법인가
|
||||
|
||||
업스트림 `quay.io/coreos/etcd:v3.7.1` 은 실효 HIGH 1건(`CVE-2026-56852`,
|
||||
`golang.org/x/text` — 깨진 UTF-8 입력에 대한 `norm.Iter` 무한루프 DoS)으로 차단된다.
|
||||
상위 태그가 없다(2026-07-31 확인, 최신 릴리스가 여전히 `v3.7.1`이고 `release-3.7`
|
||||
브랜치도 아직 이 CVE 를 백포트하지 않았다). 업스트림 배포 이미지 베이스가
|
||||
`gcr.io/distroless/static-debian12`(OS 패키지 사실상 0개)라 **베이스 OS 를 바꾸는 것만
|
||||
으로는 고쳐지지 않는다** — CVE 는 바이너리에 정적 링크된 Go 모듈 버전이 원인이다.
|
||||
자체 빌드(소스 컴파일)만 유효한 대응이다.
|
||||
|
||||
이는 `cloudnative-pg` 오퍼레이터 자체 빌드와 **완전히 같은 유형의 문제**(같은
|
||||
CVE-2026-56852)이고 대응도 같다 — **오케스트레이션은 동일한
|
||||
[scripts/build/build-hardened-image.sh](../../scripts/build/build-hardened-image.sh)
|
||||
하나를 공유한다.**
|
||||
|
||||
## 소스·버전 관리
|
||||
|
||||
| 항목 | 값 |
|
||||
| --- | --- |
|
||||
| 소스 | `https://github.com/etcd-io/etcd.git` |
|
||||
| pinned commit | `source.build.env` 의 `SOURCE_COMMIT` — `v3.7.1` 태그가 가리키는 실제 커밋 |
|
||||
| 빌더 | 공식 `golang` 이미지 (`source.build.env` 의 `GO_BUILDER_TAG`, go.mod 요구 버전과 일치) |
|
||||
| 최종 베이스 | `registry.suse.com/bci/bci-micro:15.7` — security-catalog 는 SUSE BCI 하나만 쓰기로 결정했다(ADR 미이관); 업스트림의 distroless 대신 여기 적용 |
|
||||
|
||||
빌더 스테이지(Go 컴파일)는 공식 `golang` 이미지를 그대로 쓴다 — 최종 이미지에 남는 것이
|
||||
아니라 컴파일 산출물만 최종 스테이지로 넘어오므로 스캔·정책 대상이 아니다.
|
||||
|
||||
**업스트림과 다른 부분은 딱 하나다** — `golang.org/x/text` 를 워크스페이스 전역
|
||||
`go.work` `replace` 한 줄로 `0.39.0` 이상으로 강제 업그레이드한다(각 서브모듈의
|
||||
`go.mod` 를 개별로 고치지 않는다). 그 외 빌드 절차(`server`/`etcdutl`/`etcdctl` 세
|
||||
바이너리를 각각 컴파일, `CGO_ENABLED=0` 정적 링크)는 업스트림
|
||||
`scripts/build_lib.sh` 의 `etcd_build()` 를 그대로 재현한다.
|
||||
|
||||
`SOURCE_COMMIT` 은 **자동 추적하지 않는다.** `cloudnative-pg` 와 동일하게 사람이
|
||||
업스트림 `release-3.7` 브랜치(또는 그다음 패치 릴리스가 나오면 그 태그)를 보고
|
||||
`source.build.env` 를 고쳐 PR 을 여는 것 자체가 갱신 트리거다.
|
||||
|
||||
**권장 점검 주기**: 카탈로그 게이트가 이 이미지의 차단 CVE 를 다시 보고할 때, 또는
|
||||
업스트림이 `v3.7.2`(혹은 그 이상, `x/text` 백포트 포함)를 릴리스했을 때 — 릴리스가
|
||||
나오면 그쪽으로 갈아타는 것(대응 우선순위 a)이 이 자체 빌드를 유지하는 것보다 항상
|
||||
우선이다.
|
||||
|
||||
## 빌드
|
||||
|
||||
```sh
|
||||
# 로컬 빌드 (push 없음)
|
||||
IMAGE=etcd BASE_OS=source bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
|
||||
# 레지스트리에 push 까지 (현재 실제 배포 이미지도 이 네임스페이스에 있다)
|
||||
IMAGE=etcd BASE_OS=source REGISTRY=docker.io/wbsong111 \
|
||||
bash scripts/build/build-hardened-image.sh /tmp/out
|
||||
```
|
||||
|
||||
수행 순서: **빌드 → 기능 검증(`verify.sh`) → SBOM → 전 심각도 스캔 → 게이트 판정.**
|
||||
`verify.sh` 는 `etcd`/`etcdctl`/`etcdutl` 이 PATH 에 있는지, `etcd --version` 출력에
|
||||
pinned commit 이 실제로 반영됐는지(ldflags 주입 확인), 단일 노드로 기동해 `etcdctl`
|
||||
put/get 왕복이 실제로 동작하는지를 확인한다 — 다중 노드 쿼럼·TLS 등은 이 스모크 테스트
|
||||
범위 밖이며, dev 클러스터 배포 테스트(`.claude/deploy-test-procedure.md`,
|
||||
`scripts/deploy-test/deploy-test-etcd.sh`)가 담당한다.
|
||||
|
||||
### 파일 구성
|
||||
|
||||
| 파일 | 역할 |
|
||||
| --- | --- |
|
||||
| `source.Dockerfile` | 빌드 정의 — 소스 컴파일(builder 스테이지) + SUSE BCI(`bci-micro`) 패키징(final 스테이지) |
|
||||
| `source.build.env` | pinned commit·버전·빌더 이미지 태그. `BUILD_ARGS` 에 나열한 이름만 `--build-arg` 로 전달된다 |
|
||||
| `verify.sh` | 기능 검증. 호스트에서 bash 로 실행되며 `docker run --entrypoint sh` 로 게스트 셸 스크립트를 주입한다(`bci-micro` 는 bash·coreutils 가 있다 — `cnpg-postgresql/verify.sh` 와 같은 패턴, `cloudnative-pg` 의 셸 없는 distroless 와는 다르다) |
|
||||
|
||||
베이스 변종이 하나뿐이라 파일명이 `source.*` 로 고정돼 있다.
|
||||
|
||||
### 태그
|
||||
|
||||
```
|
||||
docker.io/wbsong111/etcd:3.7.1-security-hardened-20260731
|
||||
└ app ┘└ 슬러그 ┘└ 하드닝 ┘└ 빌드일 ┘
|
||||
```
|
||||
|
||||
이 태그는 security-catalog 프로젝트에서 이미 빌드·게이트 PASS·push 된 실제 이미지다.
|
||||
dip-catalog 는 이를 그대로 재사용한다 — 재빌드·재푸시 여부는 `MEMORY.md` 참고.
|
||||
@@ -0,0 +1,11 @@
|
||||
# build-image.yml 이 읽는 카탈로그 반영 메타데이터. 빌드 정의(<variant>.build.env)와는
|
||||
# 별개다 — 이건 "이 이미지가 어느 차트의 어느 필드를 가리키는가" 만 담는다.
|
||||
|
||||
CHART_DIRS="manifests/helm/etcd/1.1.12"
|
||||
|
||||
# 태그 표기 스타일 — imageName(단일 필드 문자열) | split(registry/repository/tag 분리)
|
||||
TAG_STYLE=split
|
||||
TAG_BLOCK=image
|
||||
|
||||
# base_os 입력을 안 주면 쓸 기본 변종 (images/<image>/<DEFAULT_BASE_OS>.build.env)
|
||||
DEFAULT_BASE_OS=source
|
||||
@@ -0,0 +1,83 @@
|
||||
# etcd — 업스트림 소스를 pinned commit 으로 직접 컴파일한다.
|
||||
#
|
||||
# 업스트림(https://github.com/etcd-io/etcd) 의 루트 Dockerfile 은 이미 빌드된 바이너리
|
||||
# (`etcd`/`etcdctl`/`etcdutl`)를 ADD 만 한다 — 컴파일 자체는 `scripts/build.sh` →
|
||||
# `scripts/build_lib.sh` 의 `etcd_build()` 가 Dockerfile 밖에서 수행한다. 우리는 그 빌드
|
||||
# 단계를 Dockerfile 안으로 가져와 `go build` 로 직접 재현한다(cloudnative-pg 자체 빌드와
|
||||
# 동일한 패턴 — doc/decisions/0005 참고).
|
||||
#
|
||||
# 왜 자체 빌드인가 — quay.io/coreos/etcd:v3.7.1 이 게이트에서 차단하는 HIGH 1건
|
||||
# (CVE-2026-56852, golang.org/x/text)은 OS 패키지가 아니라 바이너리에 정적 링크된 Go
|
||||
# 모듈 버전이 원인이다. etcd v3.7.1/release-3.7 브랜치는 go.mod 에 x/text v0.37.0(취약)을
|
||||
# 고정하고 있고 상위 패치 릴리스가 아직 없다(2026-07-31 확인) — 베이스 OS 교체로는
|
||||
# 고쳐지지 않으므로 자체 빌드가 유일한 대응이다. 근거: doc/analysis/etcd-cve.md
|
||||
#
|
||||
# 업스트림과의 대응 관계 (v3.7.1 태그, scripts/build_lib.sh 의 etcd_build() 기준)
|
||||
# cd server && go build ... → 동일 (ldflags 는 GitSHA 대신 pinned commit 을 직접 주입 — 아래 참고)
|
||||
# cd etcdutl && go build ... → 동일
|
||||
# cd etcdctl && go build ... → 동일
|
||||
# distroless/static-debian12 → SUSE BCI(bci-micro)로 교체. 카탈로그는 SUSE BCI 하나만
|
||||
# 쓴다(decisions/0001) — builder 스테이지(Go 컴파일)는 공식 golang 이미지를 그대로 쓴다
|
||||
#
|
||||
# 업스트림과 다르게 하는 부분 — x/text 강제 업그레이드
|
||||
# etcd 는 Go 워크스페이스(go.work)로 서버/etcdctl/etcdutl 모듈을 함께 관리한다.
|
||||
# 각 go.mod 를 개별로 고치는 대신 go.work 에 워크스페이스 전역 replace 한 줄만 추가해
|
||||
# golang.org/x/text 를 강제 업그레이드한다 — 업스트림과의 차이를 최소로 유지하기 위함
|
||||
# (doc/image-selection.md 7절 "빌드 원칙: 업스트림과의 차이를 최소로 유지한다").
|
||||
#
|
||||
# ldflags — GitSHA 대신 pinned commit 을 직접 주입하는 이유
|
||||
# 업스트림은 `git rev-parse --short HEAD` 로 얻은 값을 버전 문자열에 심는다. 우리는
|
||||
# BuildKit 의 git context(`ADD ...#${SOURCE_COMMIT}`)로 체크아웃하므로 커밋을 이미 알고
|
||||
# 있다 — 호스트의 verify.sh 가 굳이 컨테이너 안에서 git 을 실행하지 않고도 버전 문자열에
|
||||
# pinned commit 이 반영됐는지 확인할 수 있도록, 알고 있는 전체 커밋 해시를 그대로 심는다
|
||||
# (cloudnative-pg/source.Dockerfile 과 동일한 이유).
|
||||
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_BUILDER_TAG=1.26.5-trixie
|
||||
# FROM 에서 쓰는 ARG 는 반드시 첫 FROM 이전(전역 스코프)에 선언해야 한다 — .claude/image-authoring.md
|
||||
# 3번 항목, cloudnative-pg/source.Dockerfile 에서 실제로 겪은 버그.
|
||||
ARG RUNTIME_BASE=registry.suse.com/bci/bci-micro:15.7
|
||||
|
||||
# 호스트 네이티브 아키텍처로 빌더를 띄운다. Go 크로스컴파일은 에뮬레이션이 필요 없다.
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_BUILDER_TAG} AS builder
|
||||
ARG TARGETARCH
|
||||
ARG SOURCE_COMMIT
|
||||
ARG APP_VERSION
|
||||
ARG XTEXT_FIX_VERSION
|
||||
WORKDIR /src
|
||||
|
||||
# BuildKit 의 git context 지원 — tarball+checksum 관리 없이 git 자체가 커밋 무결성을 보장한다.
|
||||
ADD https://github.com/etcd-io/etcd.git#${SOURCE_COMMIT} /src
|
||||
|
||||
# CVE-2026-56852 대응: golang.org/x/text 를 워크스페이스 전역으로 강제 업그레이드한다.
|
||||
# `go work sync` 가 이 replace 를 각 모듈(server/etcdctl/etcdutl 등)의 go.sum 에 반영한다.
|
||||
RUN echo "replace golang.org/x/text => golang.org/x/text v${XTEXT_FIX_VERSION}" >> go.work \
|
||||
&& go work sync
|
||||
|
||||
# 업스트림 scripts/build_lib.sh 의 etcd_build() 를 그대로 재현한다.
|
||||
# (GOARCH 는 TARGETARCH 로 대체, GitSHA 대신 pinned commit 전체 해시를 심는다 — 위 설명 참고)
|
||||
RUN --mount=type=cache,target=/root/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
set -eux; \
|
||||
mkdir -p /out; \
|
||||
LDFLAGS="-X=go.etcd.io/etcd/api/v3/version.GitSHA=${SOURCE_COMMIT}"; \
|
||||
( cd server && CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -trimpath -installsuffix=cgo -ldflags="${LDFLAGS}" -o /out/etcd . ); \
|
||||
( cd etcdutl && CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -trimpath -installsuffix=cgo -ldflags="${LDFLAGS}" -o /out/etcdutl . ); \
|
||||
( cd etcdctl && CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -trimpath -installsuffix=cgo -ldflags="${LDFLAGS}" -o /out/etcdctl . )
|
||||
|
||||
FROM ${RUNTIME_BASE} AS final
|
||||
|
||||
# 업스트림 루트 Dockerfile 과 동일한 경로·구조 — chart(groundhog2k/etcd) 의 startup/liveness/
|
||||
# readiness probe 가 `/usr/local/bin/etcdctl` 을 하드코딩해 호출하므로 경로를 바꾸면 안 된다.
|
||||
COPY --from=builder /out/etcd /usr/local/bin/etcd
|
||||
COPY --from=builder /out/etcdctl /usr/local/bin/etcdctl
|
||||
COPY --from=builder /out/etcdutl /usr/local/bin/etcdutl
|
||||
COPY --from=builder /src/LICENSE /licenses/LICENSE
|
||||
|
||||
WORKDIR /var/etcd/
|
||||
WORKDIR /var/lib/etcd/
|
||||
|
||||
EXPOSE 2379 2380
|
||||
|
||||
CMD ["/usr/local/bin/etcd"]
|
||||
@@ -0,0 +1,37 @@
|
||||
# etcd — 소스 컴파일형 자체 빌드 (유일한 변종)
|
||||
#
|
||||
# build-hardened-image.sh 가 source 한다. BUILD_ARGS 에 나열한 이름만 --build-arg 로 넘어간다.
|
||||
# 이 이미지는 소스를 직접 컴파일하므로 "베이스 OS" 선택지가 없다 — BASE_OS=source 로 호출한다:
|
||||
# IMAGE=etcd BASE_OS=source bash scripts/build/build-hardened-image.sh <OUT_DIR>
|
||||
#
|
||||
# 왜 자체 빌드하는가·CVE 근거 → doc/analysis/etcd-cve.md
|
||||
# 결정 → doc/decisions/0007-etcd-image-self-build.md
|
||||
|
||||
DOCKERFILE=source.Dockerfile
|
||||
TARGET=final
|
||||
TAG_SLUG=security
|
||||
|
||||
# build-hardened-image.sh 가 태그·verify.sh 전달용으로 요구하는 범용 필수값.
|
||||
# 업스트림 stock 이미지(quay.io/coreos/etcd:v3.7.1)와는 태그 슬러그(TAG_SLUG=security)로
|
||||
# 구분되므로 버전 문자열 자체는 그대로 둔다.
|
||||
APP_VERSION=3.7.1
|
||||
|
||||
# v3.7.1 태그가 가리키는 실제 커밋(태그 객체가 아니라 peeled commit), 2026-07-31 확인:
|
||||
# git ls-remote --tags https://github.com/etcd-io/etcd.git v3.7.1 v3.7.1^{}
|
||||
SOURCE_COMMIT=5e7fd0de9a57db03ecc11794dc40403a734c07bb
|
||||
|
||||
# go.mod 의 `go 1.26`/`toolchain go1.26.5` 요구를 만족하는 공식 golang 이미지 태그.
|
||||
# 이 이미지는 builder 스테이지에서만 쓰이고 최종 이미지에는 남지 않는다.
|
||||
GO_BUILDER_TAG=1.26.5-trixie
|
||||
|
||||
# 최종 런타임 베이스 — 카탈로그는 SUSE BCI 하나만 쓴다(decisions/0001). 정적 링크
|
||||
# 바이너리(CGO_ENABLED=0)라 패키지 매니저가 없는 가장 가벼운 bci-micro 로 충분하다.
|
||||
RUNTIME_BASE=registry.suse.com/bci/bci-micro:15.7
|
||||
|
||||
# CVE-2026-56852 대응 — golang.org/x/text 를 이 버전 이상으로 강제 업그레이드한다.
|
||||
# v3.7.1/release-3.7 브랜치는 아직 이 CVE 를 백포트하지 않았다(go.mod 실측 x/text
|
||||
# v0.37.0). etcd 메인테이너가 release-3.7 브랜치에 패치를 백포트하면(그리고 v3.7.2 가
|
||||
# 나오면) 이 자체 빌드는 걷어내고 상위 태그로 돌아간다(대응 우선순위 a 가 c 보다 우선).
|
||||
XTEXT_FIX_VERSION=0.39.0
|
||||
|
||||
BUILD_ARGS="SOURCE_COMMIT GO_BUILDER_TAG APP_VERSION RUNTIME_BASE XTEXT_FIX_VERSION"
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# etcd 이미지 기능 검증 — 호스트에서 bash 로 실행된다(build-hardened-image.sh 가
|
||||
# `env TAG=... PLATFORM=... SOURCE_COMMIT=... XTEXT_FIX_VERSION=... bash verify.sh`
|
||||
# 형태로 호출한다). 최종 베이스가 bci-micro 라 bash/coreutils 가 있으므로(패키지
|
||||
# 매니저만 없음 — .claude/image-authoring.md), cnpg-postgresql/verify.sh 와 같은
|
||||
# 게스트 셸 패턴을 쓴다(cloudnative-pg 처럼 셸 없는 distroless 가 아니다).
|
||||
#
|
||||
# 마지막 줄에 VERIFY-OK 를 출력하면 통과다. build-hardened-image.sh 가 그것으로 판정한다.
|
||||
#
|
||||
# 이 이미지에 요구하는 것
|
||||
# - /usr/local/bin/etcd·etcdctl·etcdutl 이 PATH 에 있어야 한다 (chart 의 startup/
|
||||
# liveness/readiness probe 가 /usr/local/bin/etcdctl 을 하드코딩해 호출한다)
|
||||
# - 단일 노드로 기동해 put/get 왕복이 실제로 동작해야 한다 (게이트 0건이어도 못 쓰는
|
||||
# 이미지는 무의미하다 — .claude/image-authoring.md 5번)
|
||||
set -e
|
||||
|
||||
TAG="${TAG:?TAG 환경변수가 필요하다}"
|
||||
PLATFORM="${PLATFORM:-linux/amd64}"
|
||||
SOURCE_COMMIT="${SOURCE_COMMIT:?SOURCE_COMMIT 환경변수가 필요하다 (build-hardened-image.sh 가 build.env 에서 전달)}"
|
||||
|
||||
docker run --rm -i --platform "$PLATFORM" -e SOURCE_COMMIT="$SOURCE_COMMIT" --entrypoint sh "$TAG" <<'GUEST'
|
||||
set -e
|
||||
|
||||
echo "== 바이너리 탐색 (PATH 기준) =="
|
||||
for b in etcd etcdctl etcdutl; do
|
||||
p=$(command -v "$b") || { echo "FAIL: $b 를 PATH 에서 찾을 수 없다"; exit 1; }
|
||||
echo " $b -> $p"
|
||||
done
|
||||
|
||||
echo "== 버전 (pinned commit 반영 확인) =="
|
||||
# sed 가 없다(bci-micro 는 bash·coreutils 는 있지만 sed 는 별도 패키지라 빠져 있다 —
|
||||
# 2026-07-31 CI 에서 실측: "sh: sed: command not found"). 순수 셸 루프로 들여쓴다.
|
||||
OUT="$(etcd --version)"
|
||||
while IFS= read -r line; do echo " $line"; done <<EOF
|
||||
$OUT
|
||||
EOF
|
||||
case "$OUT" in
|
||||
*"$SOURCE_COMMIT"*) ;;
|
||||
*) echo "FAIL: 버전 출력에 pinned commit($SOURCE_COMMIT) 이 없다 — ldflags 주입 확인 필요"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "== 단일 노드 기동 =="
|
||||
export ETCD_DATA_DIR=/tmp/verify-data
|
||||
export ETCD_LISTEN_CLIENT_URLS=http://127.0.0.1:2379
|
||||
export ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2379
|
||||
export ETCD_LISTEN_PEER_URLS=http://127.0.0.1:12380
|
||||
export ETCD_INITIAL_ADVERTISE_PEER_URLS=http://127.0.0.1:12380
|
||||
export ETCD_INITIAL_CLUSTER=default=http://127.0.0.1:12380
|
||||
export ETCD_NAME=default
|
||||
etcd >/tmp/etcd-verify.log 2>&1 &
|
||||
ETCD_PID=$!
|
||||
|
||||
trap 'kill $ETCD_PID 2>/dev/null || true' EXIT
|
||||
|
||||
echo "== health 대기 (최대 30s) =="
|
||||
ok=0
|
||||
for i in $(seq 1 30); do
|
||||
if etcdctl endpoint health >/dev/null 2>&1; then ok=1; break; fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$ok" != "1" ]; then
|
||||
echo "FAIL: etcd 가 30초 내에 healthy 상태가 되지 못했다"
|
||||
cat /tmp/etcd-verify.log
|
||||
exit 1
|
||||
fi
|
||||
HEALTH="$(etcdctl endpoint health)"
|
||||
while IFS= read -r line; do echo " $line"; done <<EOF
|
||||
$HEALTH
|
||||
EOF
|
||||
|
||||
echo "== put/get 왕복 =="
|
||||
etcdctl put smoke-test ok >/dev/null
|
||||
answer="$(etcdctl get smoke-test --print-value-only)"
|
||||
[ "$answer" = "ok" ] || { echo "FAIL: get 응답 이상 (got: '$answer')"; exit 1; }
|
||||
echo " get smoke-test => '$answer'"
|
||||
|
||||
kill "$ETCD_PID" 2>/dev/null || true
|
||||
trap - EXIT
|
||||
|
||||
echo "VERIFY-OK"
|
||||
GUEST
|
||||
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,102 @@
|
||||
# cloudnative-pg 버전 갱신 가이드
|
||||
|
||||
## 1. git 작업 환경 구성
|
||||
|
||||
```sh
|
||||
git clone https://github.com/paasup/dip-catalog.git
|
||||
cd dip-catalog
|
||||
git checkout -b update-cloudnative-pg/<신규버전>
|
||||
```
|
||||
|
||||
## 2. helm chart 업데이트
|
||||
|
||||
### 1) 신규 버전 확인
|
||||
|
||||
```sh
|
||||
helm repo add cnpg https://cloudnative-pg.github.io/charts
|
||||
helm repo update cnpg
|
||||
helm search repo cnpg/cloudnative-pg --versions | head
|
||||
```
|
||||
|
||||
차트 버전과 operator 버전(appVersion)은 다르다. 대응 관계를 반드시 확인한다.
|
||||
|
||||
| 차트 버전 | operator(appVersion) |
|
||||
| --- | --- |
|
||||
| 0.29.0 | 1.30.0 |
|
||||
| 0.28.3 | 1.29.1 |
|
||||
| 0.27.1 | 1.28.1 |
|
||||
|
||||
### 2) 신규 버전 디렉토리 생성
|
||||
|
||||
버전별 독립 디렉토리다. 기존 디렉토리를 수정하지 않고 새로 만든다.
|
||||
|
||||
```sh
|
||||
NEW=0.30.0
|
||||
OLD=0.29.0
|
||||
cd manifests/helm/cloudnative-pg
|
||||
|
||||
# 업스트림 차트를 신규 버전 디렉토리로 내려받는다
|
||||
helm pull cnpg/cloudnative-pg --version "$NEW" --untar --untardir /tmp/cnpg-pull
|
||||
mkdir -p "$NEW"
|
||||
cp -R /tmp/cnpg-pull/cloudnative-pg/. "$NEW"/
|
||||
|
||||
# PaaSup 관리 파일을 이전 버전에서 승계한다
|
||||
for f in custom-values.yaml dip-values.yaml dip-resources-quotas.yaml \
|
||||
CUSTOM-README.md BUILD-README.md; do
|
||||
cp "$OLD/$f" "$NEW/$f"
|
||||
done
|
||||
```
|
||||
|
||||
### 3) diff 확인
|
||||
|
||||
업스트림 `values.yaml` 변경으로 `custom-values.yaml` 의 키가 사라졌는지 확인한다.
|
||||
이게 Breaking Change 판단의 핵심이다.
|
||||
|
||||
```sh
|
||||
diff -u "$OLD/values.yaml" "$NEW/values.yaml" | less
|
||||
|
||||
# custom-values.yaml 의 각 키가 신규 values.yaml 에 존재하는지 검증
|
||||
helm template test "$NEW" -f "$NEW/custom-values.yaml" >/dev/null && echo "렌더링 OK"
|
||||
```
|
||||
|
||||
`values.schema.json` 이 있으므로 없는 키를 넘기면 렌더링이 실패한다. 위 명령이 통과해야 한다.
|
||||
|
||||
### 4) 렌더링 결과 비교
|
||||
|
||||
```sh
|
||||
helm template cnpg "$OLD" -f "$OLD/custom-values.yaml" -n cnpg-system > /tmp/old.yaml
|
||||
helm template cnpg "$NEW" -f "$NEW/custom-values.yaml" -n cnpg-system > /tmp/new.yaml
|
||||
diff -u /tmp/old.yaml /tmp/new.yaml
|
||||
```
|
||||
|
||||
RBAC 규칙 추가/삭제, webhook 경로 변경, CRD 필드 변경을 특히 주의해서 본다.
|
||||
|
||||
## 3. 문서 갱신
|
||||
|
||||
| 파일 | 갱신 내용 |
|
||||
| --- | --- |
|
||||
| `CUSTOM-README.md` | 차트/operator 버전 번호, 업그레이드 명령의 CRD URL, 변경된 values 키 |
|
||||
| `BUILD-README.md` | 위 버전 대응 표에 신규 행 추가 |
|
||||
| `doc/charts/cnpg/deploy-test.md` | 신규 버전으로 배포 테스트 재실행 후 결과 갱신 |
|
||||
| `manifests/helm/cnpg-cluster/*/CUSTOM-README.md` | operator 버전 호환성 명시 부분 |
|
||||
|
||||
## 4. 배포 검증
|
||||
|
||||
`doc/charts/cnpg/deploy-test.md` 의 검증 절차를 신규 버전으로 재실행한다.
|
||||
최소한 다음 4개는 통과해야 한다.
|
||||
|
||||
1. operator Pod `Running`, CRD 11개 생성
|
||||
2. `cnpg-cluster` 차트로 3-instance Cluster 배포 → `readyInstances=3/3`
|
||||
3. `-rw` / `-ro` 서비스 라우팅 (primary / replica 분리)
|
||||
4. primary Pod 삭제 → failover 후 쓰기 복구, 데이터 정합성 유지
|
||||
|
||||
## 5. PR
|
||||
|
||||
```sh
|
||||
git add manifests/helm/cloudnative-pg/<신규버전> doc/
|
||||
git commit -m "cloudnative-pg <신규버전> 추가"
|
||||
git push -u origin update-cloudnative-pg/<신규버전>
|
||||
```
|
||||
|
||||
PR 생성 시 `helm-catalog-sbom` 워크플로가 변경 차트에 대해 SBOM·취약점 스캔을 수행한다.
|
||||
CRITICAL 취약점이 있으면 내용을 확인하고 PR 본문에 판단 근거를 남긴다.
|
||||
@@ -0,0 +1,169 @@
|
||||
# CloudNativePG Operator 배포
|
||||
|
||||
차트 버전 `0.29.0` / operator 버전 `1.30.0`
|
||||
|
||||
CloudNativePG 는 PostgreSQL 을 Kubernetes 오퍼레이터로 운영하는 CNCF 프로젝트다.
|
||||
이 차트는 **오퍼레이터만** 설치한다. 실제 DB 클러스터는 `cnpg-cluster` 차트로 배포한다.
|
||||
|
||||
```
|
||||
cloudnative-pg (operator) → CRD + controller + webhook
|
||||
↓ 감시
|
||||
cnpg-cluster (Cluster CR) → PostgreSQL primary/replica Pod, PVC, Service
|
||||
```
|
||||
|
||||
## 1. 배포 방법
|
||||
|
||||
### 1) 배포 시 주의 사항
|
||||
|
||||
- **CRD 와 webhook 은 cluster-scoped 리소스다.** 네임스페이스를 분리해도 클러스터 전체에 하나만 존재한다.
|
||||
여러 팀이 각자 오퍼레이터를 설치하면 CRD 버전이 충돌한다. 클러스터당 오퍼레이터는 1개만 둔다.
|
||||
- **webhook 의 `failurePolicy` 가 `Fail` 이다.** 오퍼레이터 Pod 가 없는 상태에서는 `Cluster` 리소스의
|
||||
생성·수정·삭제가 모두 거부된다. 오퍼레이터를 제거할 때는 webhook 설정을 반드시 함께 삭제해야 한다
|
||||
(아래 [4. 제거](#4-제거) 참고).
|
||||
- **`helm upgrade` 는 CRD 를 갱신하지 않는다.** Helm 의 CRD 처리 방식 때문에 버전 업그레이드 시
|
||||
CRD 를 수동 apply 해야 한다.
|
||||
- Kubernetes 1.25+ 필요. 검증 환경은 RKE2 v1.34.1 이다.
|
||||
|
||||
### 2) 배포
|
||||
|
||||
```sh
|
||||
git clone https://github.com/paasup/dip-catalog.git
|
||||
cd dip-catalog/manifests/helm/cloudnative-pg/0.29.0
|
||||
helm upgrade cnpg ./ -f custom-values.yaml --install -n cnpg-system --create-namespace --wait
|
||||
```
|
||||
|
||||
### 3) 확인
|
||||
|
||||
```sh
|
||||
kubectl -n cnpg-system get pods
|
||||
kubectl get crd | grep cnpg # 11개
|
||||
```
|
||||
|
||||
> `kubectl get cluster` 는 쓰지 않는다. Rancher(`clusters.management.cattle.io`) 와
|
||||
> CAPI(`clusters.cluster.x-k8s.io`) 가 같은 단축명을 쓰기 때문에 엉뚱한 리소스가 조회된다.
|
||||
> 반드시 `kubectl get clusters.postgresql.cnpg.io` 로 FQN 을 쓴다.
|
||||
|
||||
## 2. custom-values.yaml 설명
|
||||
|
||||
### 1) 이미지 설정
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `image.repository` | 오퍼레이터 이미지. 오프라인 환경에서는 사내 미러 경로로 변경 | `ghcr.io/cloudnative-pg/cloudnative-pg` |
|
||||
| `image.tag` | 미설정 시 차트 `appVersion`(1.30.0) 사용. 버전 변경은 차트 교체를 우선한다 | `""` |
|
||||
| `imagePullSecrets` | 사설 레지스트리 인증 시크릿 | `[]` |
|
||||
|
||||
### 2) 감시 범위 (RBAC 영향)
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `config.clusterWide` | `true` = ClusterRole 로 전체 네임스페이스 감시. `false` = 설치 네임스페이스만 감시하고 RBAC 이 Role 로 축소됨 | `true` |
|
||||
| `config.data.WATCH_NAMESPACE` | `clusterWide: true` 상태에서 감시 대상을 특정 네임스페이스로 한정 (쉼표 구분) | 미설정 |
|
||||
| `config.maxConcurrentReconciles` | 동시 reconcile 수 | `10` |
|
||||
|
||||
#### 보안 관점 — 실측 RBAC 비교 (operator 1.30.0)
|
||||
|
||||
`clusterWide` 는 감시 범위와 **RBAC 범위를 함께** 바꾼다. 실제로 렌더링해 측정한 결과다.
|
||||
|
||||
| | `clusterWide: true` | `clusterWide: false` |
|
||||
| --- | --- | --- |
|
||||
| ClusterRole 규칙 수 | 다수 (전 리소스) | **3개** |
|
||||
| ClusterRole 이 다루는 리소스 | `secrets`, `pods`, `pods/exec`, `serviceaccounts`, `roles`, `rolebindings`, `deployments`, `configmaps`, PVC, webhook 설정, `nodes` … | `nodes`(RO), `clusterimagecatalogs`(RO), webhook 설정(get/patch) |
|
||||
| 네임스페이스 Role | 없음 | 생성됨 (20 규칙, 설치 NS 한정) |
|
||||
| ClusterRoleBinding | 생성됨 | 생성됨 (축소된 ClusterRole 에 바인딩) |
|
||||
|
||||
**`clusterWide: true` 의 실제 위험도:** 오퍼레이터 ServiceAccount 가 클러스터 전체에 대해
|
||||
다음을 갖는다.
|
||||
|
||||
- `secrets` 전체 CRUD → 모든 네임스페이스의 모든 시크릿 열람 (Harbor·Keycloak·Infisical 토큰 포함)
|
||||
- `pods/exec` → 임의 네임스페이스의 임의 Pod 에 exec
|
||||
- `roles` / `rolebindings` 생성 → **권한 상승 경로**
|
||||
- `serviceaccounts`, `deployments` 조작
|
||||
- `mutatingwebhookconfigurations` / `validatingwebhookconfigurations` patch → 어드미션 제어 변경
|
||||
|
||||
이 조합은 실질적으로 **cluster-admin 에 준한다.** 오퍼레이터 Pod 가 침해되면 클러스터 전체가
|
||||
침해된다고 봐야 한다.
|
||||
|
||||
**`WATCH_NAMESPACE` 는 보안 경계가 아니다.** 이 값은 오퍼레이터가 *reconcile 할 대상*만
|
||||
좁힌다. RBAC 은 그대로 cluster-wide 로 남으므로 토큰의 권한은 줄어들지 않는다.
|
||||
심층 방어(defense-in-depth) 수단일 뿐, 권한 축소로 오해하면 안 된다.
|
||||
|
||||
**권고**
|
||||
|
||||
- **보안이 우선이면 `clusterWide: false`** — 테넌트 네임스페이스마다 오퍼레이터를 따로 설치한다.
|
||||
다만 CRD 와 webhook 설정은 cluster-scoped 싱글턴이므로 **모든 오퍼레이터의 버전이 같아야 하고**,
|
||||
각 설치가 동일한 webhook 설정을 patch 하려고 경쟁한다. 운영 복잡도가 크게 올라간다.
|
||||
- **운영 편의가 우선이면 `clusterWide: true`** — 단, 위 권한을 감수하는 결정임을 명시하고
|
||||
다음 보완책을 함께 적용한다.
|
||||
- 오퍼레이터 네임스페이스에 접근 가능한 주체를 최소화 (`cnpg-system` 을 별도 관리)
|
||||
- 오퍼레이터 Pod 의 exec/attach 를 Kyverno 등으로 차단
|
||||
- 감사 로그에서 오퍼레이터 SA 의 `secrets` 접근을 모니터링
|
||||
- `clusterimagecatalogs` 로 허용 이미지를 고정해 임의 이미지 기동을 막는다
|
||||
|
||||
배포 테스트에서는 격리를 위해 `clusterWide: false` 를 사용했다.
|
||||
`custom-values.yaml` 기본값은 업스트림과 동일한 `true` 이므로, 도입 시 이 결정을 반드시
|
||||
검토해야 한다.
|
||||
|
||||
### 3) Webhook
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `webhook.port` | webhook 서비스 포트 | `9443` |
|
||||
| `webhook.mutating.create` | mutating webhook 생성 여부 | `true` |
|
||||
| `webhook.validating.create` | validating webhook 생성 여부 | `true` |
|
||||
| `webhook.*.failurePolicy` | `Fail` 유지 권장. `Ignore` 로 바꾸면 검증 없이 잘못된 Cluster 스펙이 통과된다 | `Fail` |
|
||||
|
||||
### 4) 모니터링
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `monitoring.podMonitorEnabled` | Prometheus Operator CRD 필요. 없으면 배포 실패 | `false` |
|
||||
| `monitoring.grafanaDashboard.create` | Grafana 대시보드 ConfigMap 생성 | `false` |
|
||||
|
||||
### 5) 리소스
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `resources` | 오퍼레이터 Pod 의 cpu/memory. 티어별 값은 `dip-resources-quotas.yaml` 참고 | 업스트림은 `{}` (무제한) |
|
||||
| `replicaCount` | leader election 기반이라 2 이상은 가용성 목적 (reconcile 은 리더 1개가 수행) | `1` |
|
||||
|
||||
## 3. 업그레이드
|
||||
|
||||
```sh
|
||||
# 1) CRD 를 먼저 수동 갱신 (helm upgrade 는 CRD 를 건드리지 않음)
|
||||
kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/v1.30.0/releases/cnpg-1.30.0.yaml \
|
||||
--dry-run=server # 먼저 dry-run 으로 영향 확인
|
||||
|
||||
# 2) 차트 업그레이드
|
||||
helm upgrade cnpg ./ -f custom-values.yaml -n cnpg-system --wait
|
||||
```
|
||||
|
||||
오퍼레이터 업그레이드는 실행 중인 `Cluster` 의 인스턴스를 롤링 재시작시킨다.
|
||||
`primaryUpdateStrategy` 설정에 따라 primary 전환이 발생하므로 서비스 영향 시간을 고려해야 한다.
|
||||
|
||||
## 4. 제거
|
||||
|
||||
**순서가 중요하다.** webhook 이 `failurePolicy: Fail` 이므로 오퍼레이터를 먼저 지우면
|
||||
`Cluster` 리소스를 삭제할 수 없게 된다.
|
||||
|
||||
```sh
|
||||
# 1) 먼저 모든 Cluster 리소스 삭제
|
||||
kubectl get clusters.postgresql.cnpg.io -A
|
||||
kubectl -n <ns> delete clusters.postgresql.cnpg.io <name>
|
||||
|
||||
# 2) 오퍼레이터 제거
|
||||
helm uninstall cnpg -n cnpg-system
|
||||
|
||||
# 3) cluster-scoped 잔여물 제거 (helm uninstall 로 남는다)
|
||||
kubectl delete validatingwebhookconfiguration cnpg-validating-webhook-configuration --ignore-not-found
|
||||
kubectl delete mutatingwebhookconfiguration cnpg-mutating-webhook-configuration --ignore-not-found
|
||||
kubectl get crd -o name | grep cnpg.io | xargs -r kubectl delete
|
||||
```
|
||||
|
||||
> CRD 삭제는 해당 CRD 의 모든 리소스를 삭제한다. PVC 는 남지만 `Cluster` 정의는 사라지므로
|
||||
> 운영 클러스터에서는 3단계를 실행하기 전에 반드시 백업을 확인한다.
|
||||
|
||||
## 5. 검증 이력
|
||||
|
||||
`doc/charts/cnpg/deploy-test.md` 참고. RKE2 v1.34.1 / Longhorn 환경에서 3-instance 구성,
|
||||
failover 3초, 데이터 정합성 유지를 확인했다.
|
||||
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
- name: cluster
|
||||
repository: https://cloudnative-pg.github.io/grafana-dashboards
|
||||
version: 0.0.5
|
||||
digest: sha256:92acaa7742cad61339d69da604eda609e3d5e02f05efa224f5a58f2b845cd2b4
|
||||
generated: "2026-01-19T21:05:18.955160552+02:00"
|
||||
@@ -0,0 +1,26 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.30.0
|
||||
dependencies:
|
||||
- alias: monitoring
|
||||
condition: monitoring.grafanaDashboard.create
|
||||
name: cluster
|
||||
repository: https://cloudnative-pg.github.io/grafana-dashboards
|
||||
version: "0.0"
|
||||
description: CloudNativePG Operator Helm Chart
|
||||
home: https://cloudnative-pg.io
|
||||
icon: https://raw.githubusercontent.com/cloudnative-pg/artwork/main/cloudnativepg-logo.svg
|
||||
keywords:
|
||||
- operator
|
||||
- controller
|
||||
- postgresql
|
||||
- postgres
|
||||
- database
|
||||
kubeVersion: '>=1.29.0-0'
|
||||
maintainers:
|
||||
- email: p.scorsolini@gmail.com
|
||||
name: phisco
|
||||
name: cloudnative-pg
|
||||
sources:
|
||||
- https://github.com/cloudnative-pg/charts
|
||||
type: application
|
||||
version: 0.29.0
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,139 @@
|
||||
{{ template "chart.header" . }}
|
||||
|
||||
|
||||
{{ template "chart.deprecationWarning" . }}
|
||||
|
||||
|
||||
{{ template "chart.badgesSection" . }}
|
||||
|
||||
|
||||
{{ template "chart.description" . }}
|
||||
|
||||
|
||||
{{ template "chart.homepageLine" . }}
|
||||
|
||||
|
||||
About this chart
|
||||
----------------
|
||||
|
||||
Helm chart to install the [CloudNativePG operator](https://cloudnative-pg.io), originally created and sponsored
|
||||
by [EDB](https://www.enterprisedb.com/) to manage PostgreSQL workloads on any supported Kubernetes cluster
|
||||
running in private, public, or hybrid cloud environments.
|
||||
|
||||
**NOTE**: this chart supports only the latest point release of the CloudNativePG operator.
|
||||
|
||||
The chart installs only the operator (controller manager, webhooks, RBAC and CRDs). To provision a PostgreSQL
|
||||
`Cluster` resource, use the companion [`cluster`](https://github.com/cloudnative-pg/charts/tree/main/charts/cluster) chart
|
||||
(see the [Cluster chart README](https://github.com/cloudnative-pg/charts/blob/main/charts/cluster/README.md) for details)
|
||||
or apply your own `Cluster` manifest.
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
### Add the chart repository
|
||||
|
||||
```console
|
||||
helm repo add cnpg https://cloudnative-pg.github.io/charts
|
||||
helm repo update
|
||||
```
|
||||
|
||||
### Install the operator
|
||||
|
||||
```console
|
||||
helm upgrade --install cnpg \
|
||||
--namespace cnpg-system \
|
||||
--create-namespace \
|
||||
cnpg/cloudnative-pg
|
||||
```
|
||||
|
||||
### Install with custom parameters
|
||||
|
||||
You can override individual chart values from the command line with `--set`.
|
||||
For example, to enable the Prometheus `PodMonitor`:
|
||||
|
||||
```console
|
||||
helm upgrade --install cnpg \
|
||||
--namespace cnpg-system \
|
||||
--create-namespace \
|
||||
--set monitoring.podMonitorEnabled=true \
|
||||
cnpg/cloudnative-pg
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Enabling the `PodMonitor` requires the Prometheus Operator CRDs to be installed in the cluster.
|
||||
> Without them the install fails with `no matches for kind "PodMonitor"`.
|
||||
|
||||
See the [Values](#values) section below for the full list of configurable parameters.
|
||||
|
||||
### Verify the installation
|
||||
|
||||
```console
|
||||
kubectl -n cnpg-system get deploy
|
||||
kubectl -n cnpg-system rollout status deploy/cnpg-cloudnative-pg
|
||||
```
|
||||
|
||||
|
||||
Single namespace installation
|
||||
-----------------------------
|
||||
|
||||
It is possible to limit the operator's capabilities to solely the namespace in which it has been installed.
|
||||
With this restriction, the cluster-level permissions required by the operator will be substantially reduced,
|
||||
and the security profile of the installation will be enhanced.
|
||||
|
||||
You can install the operator in single-namespace mode by setting the `config.clusterWide` flag to `false`,
|
||||
as in the following example:
|
||||
|
||||
```console
|
||||
helm upgrade --install cnpg \
|
||||
--namespace cnpg-system \
|
||||
--create-namespace \
|
||||
--set config.clusterWide=false \
|
||||
cnpg/cloudnative-pg
|
||||
```
|
||||
|
||||
**IMPORTANT**: the single-namespace installation mode can't coexist with the cluster-wide operator. Otherwise
|
||||
there would be collisions when managing the resources in the namespace watched by the single-namespace
|
||||
operator. It is up to the user to ensure there is no collision between operators.
|
||||
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
```console
|
||||
helm uninstall cnpg --namespace cnpg-system
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
> Uninstalling the chart does not remove the CRDs. Deleting them cascade-deletes every `Cluster` (and other
|
||||
> CloudNativePG) resource across the whole cluster, together with the PostgreSQL data stored in their PVCs.
|
||||
> This is irreversible, so only delete the CRDs if you intend to permanently remove all managed databases.
|
||||
|
||||
|
||||
{{ template "chart.sourcesSection" . }}
|
||||
|
||||
|
||||
{{ template "chart.requirementsSection" . }}
|
||||
|
||||
|
||||
{{ template "chart.valuesSection" . }}
|
||||
|
||||
|
||||
{{ template "chart.maintainersSection" . }}
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Please read the [code of conduct](https://github.com/cloudnative-pg/charts/blob/main/CODE-OF-CONDUCT.md) and the
|
||||
[guidelines](https://github.com/cloudnative-pg/charts/blob/main/CONTRIBUTING.md) to contribute to the project.
|
||||
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
Helm charts for CloudNativePG are distributed under [Apache License 2.0](./LICENSE).
|
||||
|
||||
|
||||
{{- if not .SkipVersionFooter }}
|
||||
{{ template "helm-docs.versionFooter" . }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.16.0
|
||||
description: CloudNativePG Grafana Cluster Dashboard.
|
||||
name: cluster
|
||||
type: application
|
||||
version: 0.0.5
|
||||
@@ -0,0 +1,59 @@
|
||||
<!-- THIS FILE IS AUTOMATICALLY GENERATED. Make changes to README.md.gotmpl instead. -->
|
||||
|
||||
# cluster
|
||||
|
||||
  
|
||||
|
||||

|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
_**Note,** this dashboard is already included in the [CloudNativePG Operator Helm Chart][operator]._
|
||||
|
||||
There are 4 ways to use the CloudNativePG Grafana Cluster Dashboard:
|
||||
|
||||
0. Install the [CloudNativePG Operator Helm Chart][operator]
|
||||
|
||||
1. Install manually via [Grafana.com](https://grafana.com/grafana/dashboards/20417-cloudnativepg/).
|
||||
|
||||
2. Install manually via the [Grafana JSON](https://github.com/cloudnative-pg/grafana-dashboards/blob/main/charts/cluster/grafana-dashboard.json):
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/cloudnative-pg/grafana-dashboards/main/charts/cluster/grafana-dashboard.json
|
||||
```
|
||||
|
||||
3. Install directly in your cluster as a Helm Chart:
|
||||
|
||||
```bash
|
||||
helm repo add cnpg-grafana https://cloudnative-pg.github.io/grafana-dashboards
|
||||
helm upgrade
|
||||
--install \
|
||||
--namespace monitoring \
|
||||
cnpg-grafana-cluster cnpg-grafana/cluster
|
||||
```
|
||||
|
||||
2. As as a dependency to an existing chart:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- name: cluster
|
||||
alias: cnpg-grafana-cluster-dashboard
|
||||
version: "0.0"
|
||||
repository: https://cloudnative-pg.github.io/grafana-dashboards
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| fullnameOverride | string | `""` | |
|
||||
| grafanaDashboard.annotations | object | `{}` | Annotations that ConfigMaps can have to get configured in Grafana. |
|
||||
| grafanaDashboard.configMapName | string | `"cnpg-grafana-dashboard"` | The name of the ConfigMap containing the dashboard. |
|
||||
| grafanaDashboard.labels | object | `{}` | Labels that ConfigMaps should have to get configured in Grafana. |
|
||||
| grafanaDashboard.namespace | string | `""` | Allows overriding the namespace where the ConfigMap will be created, defaulting to the same one as the Release. |
|
||||
| grafanaDashboard.sidecarLabel | string | `"grafana_dashboard"` | Label that ConfigMaps should have to be loaded as dashboards. DEPRECATED: Use labels instead. |
|
||||
| grafanaDashboard.sidecarLabelValue | string | `"1"` | Label value that ConfigMaps should have to be loaded as dashboards. DEPRECATED: Use labels instead. |
|
||||
| nameOverride | string | `""` | |
|
||||
|
||||
[operator]: https://github.com/cloudnative-pg/charts/tree/main/charts/cloudnative-pg
|
||||
@@ -0,0 +1,59 @@
|
||||
<!-- THIS FILE IS AUTOMATICALLY GENERATED. Make changes to README.md.gotmpl instead. -->
|
||||
|
||||
{{ template "chart.header" . }}
|
||||
|
||||
{{ template "chart.deprecationWarning" . }}
|
||||
|
||||
{{ template "chart.badgesSection" . }}
|
||||
|
||||

|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
_**Note,** this dashboard is already included in the [CloudNativePG Operator Helm Chart][operator]._
|
||||
|
||||
There are 4 ways to use the CloudNativePG Grafana Cluster Dashboard:
|
||||
|
||||
0. Install the [CloudNativePG Operator Helm Chart][operator]
|
||||
|
||||
1. Install manually via [Grafana.com](https://grafana.com/grafana/dashboards/20417-cloudnativepg/).
|
||||
|
||||
2. Install manually via the [Grafana JSON](https://github.com/cloudnative-pg/grafana-dashboards/blob/main/charts/cluster/grafana-dashboard.json):
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/cloudnative-pg/grafana-dashboards/main/charts/cluster/grafana-dashboard.json
|
||||
```
|
||||
|
||||
3. Install directly in your cluster as a Helm Chart:
|
||||
|
||||
```bash
|
||||
helm repo add cnpg-grafana https://cloudnative-pg.github.io/grafana-dashboards
|
||||
helm upgrade
|
||||
--install \
|
||||
--namespace monitoring \
|
||||
cnpg-grafana-cluster cnpg-grafana/cluster
|
||||
```
|
||||
|
||||
2. As as a dependency to an existing chart:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- name: cluster
|
||||
alias: cnpg-grafana-cluster-dashboard
|
||||
version: "0.0"
|
||||
repository: https://cloudnative-pg.github.io/grafana-dashboards
|
||||
```
|
||||
|
||||
|
||||
{{ template "chart.requirementsSection" . }}
|
||||
|
||||
|
||||
{{ template "chart.valuesSection" . }}
|
||||
|
||||
|
||||
{{ template "chart.maintainersSection" . }}
|
||||
|
||||
{{ template "helm-docs.versionFooter" . }}
|
||||
|
||||
[operator]: https://github.com/cloudnative-pg/charts/tree/main/charts/cloudnative-pg
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
CloudNativePG Grafana Dashboard installed successfully.
|
||||
|
||||
{{- if (or .Values.grafanaDashboard.sidecarLabel .Values.grafanaDashboard.sidecarLabelValue) }}
|
||||
DEPRECATION NOTICE: The grafanaDashboard.sidecarLabel is deprecated and will be removed in a future release. Use the grafanaDashboard.labels instead.
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.grafanaDashboard.configMapName }}
|
||||
namespace: {{ default .Release.Namespace .Values.grafanaDashboard.namespace }}
|
||||
{{- if (or .Values.grafanaDashboard.labels .Values.grafanaDashboard.sidecarLabel) }}
|
||||
labels:
|
||||
{{- if .Values.grafanaDashboard.sidecarLabel }}
|
||||
{{ .Values.grafanaDashboard.sidecarLabel }}: {{ .Values.grafanaDashboard.sidecarLabelValue | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.grafanaDashboard.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.grafanaDashboard.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
cnp.json: |-
|
||||
{{ .Files.Get "grafana-dashboard.json" | indent 6 }}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fullnameOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"grafanaDashboard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"configMapName": {
|
||||
"type": "string"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string"
|
||||
},
|
||||
"sidecarLabel": {
|
||||
"type": "string"
|
||||
},
|
||||
"sidecarLabelValue": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nameOverride": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# Default values for cluster.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
grafanaDashboard:
|
||||
# -- Allows overriding the namespace where the ConfigMap will be created, defaulting to the same one as the Release.
|
||||
namespace: ""
|
||||
# -- The name of the ConfigMap containing the dashboard.
|
||||
configMapName: "cnpg-grafana-dashboard"
|
||||
# -- Label that ConfigMaps should have to be loaded as dashboards. DEPRECATED: Use labels instead.
|
||||
sidecarLabel: "grafana_dashboard"
|
||||
# -- Label value that ConfigMaps should have to be loaded as dashboards. DEPRECATED: Use labels instead.
|
||||
sidecarLabelValue: "1"
|
||||
# -- Labels that ConfigMaps should have to get configured in Grafana.
|
||||
labels: {}
|
||||
# -- Annotations that ConfigMaps can have to get configured in Grafana.
|
||||
annotations: {}
|
||||
@@ -0,0 +1,66 @@
|
||||
# CloudNativePG Operator — PaaSup 오버라이드
|
||||
# 업스트림 기본값은 values.yaml 참고. 여기에는 변경이 필요한 항목만 둔다.
|
||||
|
||||
image:
|
||||
# 자체 빌드(대응 우선순위 c) — 업스트림 1.30.0 이 게이트 차단 HIGH 3건(stdlib·x/text·grpc,
|
||||
# Go 모듈 정적 링크라 베이스 OS 교체로 해소 불가)으로 막혀 release-1.30 브랜치를 직접
|
||||
# 컴파일했다. 근거: doc/analysis/cloudnative-pg-operator-cve.md, 결정: doc/decisions/0005.
|
||||
# 빌드 정의: images/cloudnative-pg/. 상위 태그가 나오면(대응 우선순위 a) 되돌리는 것이 우선.
|
||||
registry: "docker.io/wbsong111"
|
||||
repository: "cloudnative-pg"
|
||||
tag: "1.30.0-security-hardened-20260730c"
|
||||
|
||||
# 오프라인/사설 레지스트리 환경에서 미러 사용 시 지정.
|
||||
# imagePullSecrets:
|
||||
# - name: paasup-registry
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
crds:
|
||||
# operator 차트가 CRD 를 함께 설치한다. helm upgrade 로는 CRD 가 갱신되지 않으므로
|
||||
# 버전 업그레이드 시 CRD 를 수동 apply 해야 한다 (CUSTOM-README.md 참고).
|
||||
create: true
|
||||
|
||||
config:
|
||||
# true(업스트림 기본): 전체 네임스페이스 감시.
|
||||
# → ClusterRole 에 secrets 전체 CRUD, pods/exec, roles/rolebindings 생성 권한이
|
||||
# 클러스터 범위로 부여된다. 실질적으로 cluster-admin 급이다.
|
||||
# false: 설치 네임스페이스만 감시. ClusterRole 이 3개 규칙(nodes RO,
|
||||
# clusterimagecatalogs RO, webhook 설정 get/patch)으로 축소되고 나머지 권한은
|
||||
# 설치 네임스페이스 한정 Role 로 내려간다.
|
||||
# 상세 비교는 CUSTOM-README.md 의 "보안 관점 — 실측 RBAC 비교" 참고.
|
||||
clusterWide: true
|
||||
data: {}
|
||||
# 감시 대상 네임스페이스 한정 (clusterWide: true 와 함께 사용).
|
||||
# 주의: reconcile 범위만 좁힌다. RBAC 은 여전히 cluster-wide 이므로
|
||||
# 보안 경계가 아니다 — 심층 방어 수단으로만 취급한다.
|
||||
# WATCH_NAMESPACE: "platform,defense-llm"
|
||||
|
||||
webhook:
|
||||
port: 9443
|
||||
# failurePolicy: Fail — operator 가 죽으면 Cluster 리소스 조작이 막힌다.
|
||||
# operator 를 제거할 때 webhook 설정도 반드시 함께 삭제해야 한다.
|
||||
mutating:
|
||||
create: true
|
||||
failurePolicy: Fail
|
||||
validating:
|
||||
create: true
|
||||
failurePolicy: Fail
|
||||
|
||||
monitoring:
|
||||
# rancher-monitoring(Prometheus Operator) 설치 환경에서만 true 로 둔다.
|
||||
podMonitorEnabled: false
|
||||
grafanaDashboard:
|
||||
create: false
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 500Mi
|
||||
|
||||
tolerations: []
|
||||
|
||||
nodeSelector: {}
|
||||
@@ -0,0 +1,35 @@
|
||||
# CloudNativePG Operator 리소스 티어.
|
||||
# operator 는 컨트롤 플레인 컴포넌트로, 관리하는 Cluster 수에 따라 부하가 늘어난다.
|
||||
# 실제 DB 리소스는 cnpg-cluster 차트의 dip-resources-quotas.yaml 에서 정의한다.
|
||||
|
||||
small:
|
||||
replicaCount: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 500Mi
|
||||
|
||||
medium:
|
||||
replicaCount: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 500Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
large:
|
||||
# 다수 Cluster 를 관리하는 환경. leader election 기반이라 replica 를 늘려도
|
||||
# 실제 reconcile 은 리더 1개가 수행한다(가용성 목적).
|
||||
replicaCount: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
@@ -0,0 +1,51 @@
|
||||
# CloudNativePG Operator — DIP 플랫폼 기본 배포값
|
||||
# custom-values.yaml 과 달리 플랫폼 운영 환경(멀티 테넌트) 기준값이다.
|
||||
|
||||
image:
|
||||
# 자체 빌드(대응 우선순위 c) — custom-values.yaml 상단 주석·doc/decisions/0005 참고.
|
||||
registry: "docker.io/wbsong111"
|
||||
repository: "cloudnative-pg"
|
||||
tag: "1.30.0-security-hardened-20260730c"
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
crds:
|
||||
create: true
|
||||
|
||||
config:
|
||||
# 플랫폼 운영은 테넌트 네임스페이스를 모두 감시해야 하므로 cluster-wide 로 둔다.
|
||||
# 이는 오퍼레이터 SA 에 cluster-admin 급 권한(전 네임스페이스 secrets CRUD,
|
||||
# pods/exec, roles/rolebindings 생성)을 부여하는 결정이다. CUSTOM-README.md 의
|
||||
# "보안 관점 — 실측 RBAC 비교" 에 정리된 보완책을 반드시 함께 적용한다.
|
||||
clusterWide: true
|
||||
# reconcile 대상을 테넌트 네임스페이스로 한정한다(RBAC 축소 효과는 없음).
|
||||
data: {}
|
||||
# WATCH_NAMESPACE: "platform,defense-llm"
|
||||
|
||||
webhook:
|
||||
mutating:
|
||||
create: true
|
||||
failurePolicy: Fail
|
||||
validating:
|
||||
create: true
|
||||
failurePolicy: Fail
|
||||
|
||||
monitoring:
|
||||
# rancher-monitoring 이 설치된 플랫폼에서는 활성화한다.
|
||||
podMonitorEnabled: true
|
||||
grafanaDashboard:
|
||||
create: true
|
||||
sidecarLabel: grafana_dashboard
|
||||
sidecarLabelValue: "1"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 500Mi
|
||||
|
||||
tolerations: []
|
||||
|
||||
nodeSelector: {}
|
||||
@@ -0,0 +1,3 @@
|
||||
The JSON file has been moved to a dedicated repository for CloudNativePG dashboards located at:
|
||||
|
||||
https://github.com/cloudnative-pg/grafana-dashboards/blob/main/charts/cluster/grafana-dashboard.json
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
CloudNativePG operator should be installed in namespace "{{ include "cloudnative-pg.namespace" . }}".
|
||||
You can now create a PostgreSQL cluster with 3 nodes as follows:
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
# Example of PostgreSQL cluster
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: cluster-example
|
||||
{{if not .Values.config.clusterWide -}}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
{{- end }}
|
||||
spec:
|
||||
instances: 3
|
||||
storage:
|
||||
size: 1Gi
|
||||
EOF
|
||||
|
||||
kubectl get -A cluster
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
{{/*
|
||||
Allow the release namespace to be overridden for multi-namespace deployments in combined charts
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.namespace" -}}
|
||||
{{- if .Values.namespaceOverride -}}
|
||||
{{- .Values.namespaceOverride -}}
|
||||
{{- else -}}
|
||||
{{- .Release.Namespace -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.labels" -}}
|
||||
helm.sh/chart: {{ include "cloudnative-pg.chart" . }}
|
||||
{{ include "cloudnative-pg.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "cloudnative-pg.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "cloudnative-pg.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Define the common set of rules that can be applied either with
|
||||
namespace scope or clusterwide
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.commonRules" }}
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps/status
|
||||
- secrets/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- persistentvolumeclaims
|
||||
- pods
|
||||
- pods/exec
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/status
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- discovery.k8s.io
|
||||
resources:
|
||||
- endpointslices
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- podmonitors
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- poddisruptionbudgets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- backups
|
||||
- clusters
|
||||
- databaseroles
|
||||
- databases
|
||||
- poolers
|
||||
- publications
|
||||
- scheduledbackups
|
||||
- subscriptions
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- failoverquorums
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- backups/status
|
||||
- databases/status
|
||||
- publications/status
|
||||
- scheduledbackups/status
|
||||
- subscriptions/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- imagecatalogs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- clusters/finalizers
|
||||
- databaseroles/finalizers
|
||||
- poolers/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- clusters/status
|
||||
- databaseroles/status
|
||||
- poolers/status
|
||||
- failoverquorums/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- rolebindings
|
||||
- roles
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- snapshot.storage.k8s.io
|
||||
resources:
|
||||
- volumesnapshots
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Define the set of rules that must be applied clusterwide
|
||||
*/}}
|
||||
{{- define "cloudnative-pg.clusterwideRules" }}
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- admissionregistration.k8s.io
|
||||
resources:
|
||||
- mutatingwebhookconfigurations
|
||||
- validatingwebhookconfigurations
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- clusterimagecatalogs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
{{- end }}
|
||||
@@ -0,0 +1,61 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
{{- if .Values.config.create }}
|
||||
{{- if not .Values.config.secret }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.config.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
{{- if .Values.config.clusterWide -}}
|
||||
{{- toYaml .Values.config.data | nindent 2 }}
|
||||
{{- else -}}
|
||||
{{- $watchNamespaceMap := dict "WATCH_NAMESPACE" .Release.Namespace -}}
|
||||
{{- $fullConfiguration := merge .Values.config.data $watchNamespaceMap -}}
|
||||
{{- toYaml $fullConfiguration | nindent 2 }}
|
||||
{{- end -}}
|
||||
{{- else }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
metadata:
|
||||
name: {{ .Values.config.name }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
stringData:
|
||||
{{- if .Values.config.clusterWide -}}
|
||||
{{- toYaml .Values.config.data | nindent 2 }}
|
||||
{{- else -}}
|
||||
{{- $watchNamespaceMap := dict "WATCH_NAMESPACE" .Release.Namespace -}}
|
||||
{{- $fullConfiguration := merge .Values.config.data $watchNamespaceMap -}}
|
||||
{{- toYaml $fullConfiguration | nindent 2 }}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "cloudnative-pg.selectorLabels" . | nindent 6 }}
|
||||
{{- if .Values.updateStrategy }}
|
||||
strategy:
|
||||
{{- toYaml .Values.updateStrategy | nindent 4 }}
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/rbac: {{ include (print $.Template.BasePath "/rbac.yaml") . | sha256sum }}
|
||||
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
|
||||
checksum/monitoring-config: {{ include (print $.Template.BasePath "/monitoring-configmap.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.selectorLabels" . | nindent 8 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.hostNetwork }}
|
||||
hostNetwork: {{ .Values.hostNetwork }}
|
||||
{{- end }}
|
||||
{{- if .Values.dnsPolicy }}
|
||||
dnsPolicy: {{ .Values.dnsPolicy }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- args:
|
||||
- controller
|
||||
- --leader-elect
|
||||
- --max-concurrent-reconciles={{ .Values.config.maxConcurrentReconciles }}
|
||||
{{- if .Values.config.name }}
|
||||
{{- if not .Values.config.secret }}
|
||||
- --config-map-name={{ .Values.config.name }}
|
||||
{{- else }}
|
||||
- --secret-name={{ .Values.config.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
- --webhook-port={{ .Values.webhook.port }}
|
||||
{{- range .Values.additionalArgs }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
command:
|
||||
- /manager
|
||||
env:
|
||||
- name: OPERATOR_IMAGE_NAME
|
||||
value: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
- name: OPERATOR_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: MONITORING_QUERIES_CONFIGMAP
|
||||
value: "{{ .Values.monitoringQueriesConfigMap.name }}"
|
||||
{{- if not .Values.config.clusterWide }}
|
||||
- name: WATCH_NAMESPACE
|
||||
value: "{{ include "cloudnative-pg.namespace" . }}"
|
||||
{{- end }}
|
||||
{{- if .Values.additionalEnv }}
|
||||
{{- tpl (.Values.additionalEnv | toYaml) . | nindent 8 }}
|
||||
{{- end }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: webhook-server
|
||||
scheme: HTTPS
|
||||
{{- if .Values.webhook.livenessProbe.initialDelaySeconds }}
|
||||
initialDelaySeconds: {{ .Values.webhook.livenessProbe.initialDelaySeconds }}
|
||||
{{- end }}
|
||||
name: manager
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
- containerPort: {{ .Values.webhook.port }}
|
||||
name: webhook-server
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: webhook-server
|
||||
scheme: HTTPS
|
||||
{{- if .Values.webhook.readinessProbe.initialDelaySeconds }}
|
||||
initialDelaySeconds: {{ .Values.webhook.readinessProbe.initialDelaySeconds }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 10 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.containerSecurityContext | nindent 10 }}
|
||||
startupProbe:
|
||||
{{- if .Values.webhook.startupProbe.failureThreshold }}
|
||||
failureThreshold: {{ .Values.webhook.startupProbe.failureThreshold }}
|
||||
{{- end }}
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: webhook-server
|
||||
scheme: HTTPS
|
||||
{{- if .Values.webhook.startupProbe.periodSeconds }}
|
||||
periodSeconds: {{ .Values.webhook.startupProbe.periodSeconds }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- mountPath: /controller
|
||||
name: scratch-data
|
||||
- mountPath: /run/secrets/cnpg.io/webhook
|
||||
name: webhook-certificates
|
||||
{{- if .Values.priorityClassName }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
serviceAccountName: {{ include "cloudnative-pg.serviceAccountName" . }}
|
||||
terminationGracePeriodSeconds: 10
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.topologySpreadConstraints }}
|
||||
topologySpreadConstraints:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
name: scratch-data
|
||||
- name: webhook-certificates
|
||||
secret:
|
||||
defaultMode: 420
|
||||
optional: true
|
||||
secretName: cnpg-webhook-cert
|
||||
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.monitoringQueriesConfigMap.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
cnpg.io/reload: ""
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
data:
|
||||
queries: {{- toYaml .Values.monitoringQueriesConfigMap.queries | nindent 4 }}
|
||||
@@ -0,0 +1,116 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
{{- if .Values.webhook.mutating.create }}
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
name: cnpg-mutating-webhook-configuration
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /mutate-postgresql-cnpg-io-v1-backup
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.mutating.failurePolicy }}
|
||||
name: mbackup.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- backups
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /mutate-postgresql-cnpg-io-v1-cluster
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.mutating.failurePolicy }}
|
||||
name: mcluster.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- clusters
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /mutate-postgresql-cnpg-io-v1-database
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.mutating.failurePolicy }}
|
||||
name: mdatabase.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- databases
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /mutate-postgresql-cnpg-io-v1-scheduledbackup
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.mutating.failurePolicy }}
|
||||
name: mscheduledbackup.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- scheduledbackups
|
||||
sideEffects: None
|
||||
{{- end }}
|
||||
@@ -0,0 +1,48 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
{{- if .Values.monitoring.podMonitorEnabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.monitoring.podMonitorAdditionalLabels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end}}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "cloudnative-pg.selectorLabels" . | nindent 6 }}
|
||||
podMetricsEndpoints:
|
||||
- port: metrics
|
||||
{{- with .Values.monitoring.podMonitorMetricRelabelings }}
|
||||
metricRelabelings:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .Values.monitoring.podMonitorRelabelings }}
|
||||
relabelings:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,182 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.serviceAccountName" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.rbac.create }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- include "cloudnative-pg.clusterwideRules" . }}
|
||||
{{/*
|
||||
If we're doing a clusterWide installation (default)
|
||||
we add ALL the necessary rules for the operator to the ClusterRole
|
||||
*/}}
|
||||
{{- if .Values.config.clusterWide }}
|
||||
{{- include "cloudnative-pg.commonRules" . }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "cloudnative-pg.serviceAccountName" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
{{/*
|
||||
If we're doing a single-namespace installation
|
||||
we create a Role with the common rules for the operator,
|
||||
and a RoleBinding. We already created the ClusterRole above with the
|
||||
required cluster-wide rules
|
||||
*/}}
|
||||
{{- if eq .Values.config.clusterWide false }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- include "cloudnative-pg.commonRules" . }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "cloudnative-pg.fullname" . }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "cloudnative-pg.serviceAccountName" . }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}-view
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- if .Values.rbac.aggregateClusterRoles }}
|
||||
rbac.authorization.k8s.io/aggregate-to-view: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
||||
{{- end }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- backups
|
||||
- clusters
|
||||
- clusters/status
|
||||
- databaseroles
|
||||
- databases
|
||||
- failoverquorums
|
||||
- poolers
|
||||
- publications
|
||||
- scheduledbackups
|
||||
- imagecatalogs
|
||||
- clusterimagecatalogs
|
||||
- subscriptions
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "cloudnative-pg.fullname" . }}-edit
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- if .Values.rbac.aggregateClusterRoles }}
|
||||
rbac.authorization.k8s.io/aggregate-to-edit: "true"
|
||||
rbac.authorization.k8s.io/aggregate-to-admin: "true"
|
||||
{{- end }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
resources:
|
||||
- backups
|
||||
- clusters
|
||||
- clusters/status
|
||||
- databaseroles
|
||||
- databases
|
||||
- failoverquorums
|
||||
- poolers
|
||||
- publications
|
||||
- scheduledbackups
|
||||
- imagecatalogs
|
||||
- clusterimagecatalogs
|
||||
- subscriptions
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- patch
|
||||
- update
|
||||
---
|
||||
{{- end }}
|
||||
@@ -0,0 +1,44 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.commonAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
{{- if .Values.service.ipFamilyPolicy }}
|
||||
ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }}
|
||||
{{- end }}
|
||||
{{- if .Values.service.ipFamilies }}
|
||||
ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: webhook-server
|
||||
name: webhook-server
|
||||
selector:
|
||||
{{- include "cloudnative-pg.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,137 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
{{- if .Values.webhook.validating.create }}
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
name: cnpg-validating-webhook-configuration
|
||||
labels:
|
||||
{{- include "cloudnative-pg.labels" . | nindent 4 }}
|
||||
{{- with .Values.rbac.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /validate-postgresql-cnpg-io-v1-backup
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.validating.failurePolicy }}
|
||||
name: vbackup.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- backups
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /validate-postgresql-cnpg-io-v1-cluster
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.validating.failurePolicy }}
|
||||
name: vcluster.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- clusters
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /validate-postgresql-cnpg-io-v1-scheduledbackup
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.validating.failurePolicy }}
|
||||
name: vscheduledbackup.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- scheduledbackups
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /validate-postgresql-cnpg-io-v1-database
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.validating.failurePolicy }}
|
||||
name: vdatabase.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- databases
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ include "cloudnative-pg.namespace" . }}
|
||||
path: /validate-postgresql-cnpg-io-v1-pooler
|
||||
port: {{ .Values.service.port }}
|
||||
failurePolicy: {{ .Values.webhook.validating.failurePolicy }}
|
||||
name: vpooler.cnpg.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- postgresql.cnpg.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- poolers
|
||||
sideEffects: None
|
||||
{{- end }}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,696 @@
|
||||
#
|
||||
# Copyright © contributors to CloudNativePG, established as
|
||||
# CloudNativePG a Series of LF Projects, LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Default values for CloudNativePG.
|
||||
# This is a YAML-formatted file.
|
||||
# Please declare variables to be passed to your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: ghcr.io/cloudnative-pg/cloudnative-pg
|
||||
pullPolicy: IfNotPresent
|
||||
# -- Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
namespaceOverride: ""
|
||||
|
||||
hostNetwork: false
|
||||
dnsPolicy: ""
|
||||
|
||||
# -- Update strategy for the operator.
|
||||
# ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy
|
||||
updateStrategy: {}
|
||||
# For example:
|
||||
# type: RollingUpdate
|
||||
# rollingUpdate:
|
||||
# maxSurge: 25%
|
||||
# maxUnavailable: 25%
|
||||
|
||||
crds:
|
||||
# -- Specifies whether the CRDs should be created when installing the chart.
|
||||
create: true
|
||||
|
||||
# -- The webhook configuration.
|
||||
webhook:
|
||||
port: 9443
|
||||
mutating:
|
||||
create: true
|
||||
failurePolicy: Fail
|
||||
validating:
|
||||
create: true
|
||||
failurePolicy: Fail
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 3
|
||||
readinessProbe:
|
||||
initialDelaySeconds: 3
|
||||
startupProbe:
|
||||
failureThreshold: 6
|
||||
periodSeconds: 5
|
||||
|
||||
# Operator configuration.
|
||||
config:
|
||||
# -- Specifies whether the secret should be created.
|
||||
create: true
|
||||
# -- The name of the configmap/secret to use.
|
||||
name: cnpg-controller-manager-config
|
||||
# -- Specifies whether it should be stored in a secret, instead of a configmap.
|
||||
secret: false
|
||||
# -- This option determines if the operator is responsible for observing
|
||||
# events across the entire Kubernetes cluster or if its focus should be
|
||||
# narrowed down to the specific namespace within which it has been deployed.
|
||||
clusterWide: true
|
||||
# -- The content of the configmap/secret, see
|
||||
# https://cloudnative-pg.io/documentation/current/operator_conf/#available-options
|
||||
# for all the available options.
|
||||
data: {}
|
||||
# INHERITED_ANNOTATIONS: categories
|
||||
# INHERITED_LABELS: environment, workload, app
|
||||
# WATCH_NAMESPACE: namespace-a,namespace-b
|
||||
|
||||
# -- The maximum number of concurrent reconciles. Defaults to 10.
|
||||
maxConcurrentReconciles: 10
|
||||
|
||||
# -- Additional arguments to be added to the operator's args list.
|
||||
additionalArgs: []
|
||||
|
||||
# -- Array containing extra environment variables which can be templated.
|
||||
additionalEnv: []
|
||||
# For example:
|
||||
# - name: RELEASE_NAME
|
||||
# value: "{{ .Release.Name }}"
|
||||
# - name: MY_VAR
|
||||
# value: "mySpecialKey"
|
||||
|
||||
serviceAccount:
|
||||
# -- Specifies whether the service account should be created.
|
||||
create: true
|
||||
# -- The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template.
|
||||
name: ""
|
||||
|
||||
rbac:
|
||||
# -- Specifies whether ClusterRole and ClusterRoleBinding should be created.
|
||||
create: true
|
||||
# -- Aggregate ClusterRoles to Kubernetes default user-facing roles.
|
||||
# Ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles
|
||||
aggregateClusterRoles: false
|
||||
|
||||
# -- Annotations to be added to all other resources.
|
||||
commonAnnotations: {}
|
||||
# -- Annotations to be added to the pod.
|
||||
podAnnotations: {}
|
||||
# -- Labels to be added to the pod.
|
||||
podLabels: {}
|
||||
|
||||
# -- Container Security Context.
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsUser: 10001
|
||||
runAsGroup: 10001
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
capabilities:
|
||||
drop:
|
||||
- "ALL"
|
||||
|
||||
# -- Security Context for the whole pod.
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
# fsGroup: 2000
|
||||
|
||||
# -- Priority indicates the importance of a Pod relative to other Pods.
|
||||
priorityClassName: ""
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
# -- The name of the Webhook Service.
|
||||
name: cnpg-webhook-service
|
||||
# DO NOT CHANGE THE SERVICE NAME as it is currently used to generate the certificate
|
||||
# and can not be configured
|
||||
|
||||
port: 443
|
||||
# -- Set the ip family policy to configure dual-stack see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services)
|
||||
ipFamilyPolicy: ""
|
||||
# -- Sets the families that should be supported and the order in which they should be applied to ClusterIP as well. Can be IPv4 and/or IPv6.
|
||||
ipFamilies: []
|
||||
|
||||
resources: {}
|
||||
# If you want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
#
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 200Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 100Mi
|
||||
|
||||
# -- Nodeselector for the operator to be installed.
|
||||
nodeSelector: {}
|
||||
|
||||
# -- Topology Spread Constraints for the operator to be installed.
|
||||
topologySpreadConstraints: []
|
||||
|
||||
# -- Tolerations for the operator to be installed.
|
||||
tolerations: []
|
||||
|
||||
# -- Affinity for the operator to be installed.
|
||||
affinity: {}
|
||||
|
||||
monitoring:
|
||||
|
||||
# -- Specifies whether the monitoring should be enabled. Requires Prometheus Operator CRDs.
|
||||
podMonitorEnabled: false
|
||||
# -- Metrics relabel configurations to apply to samples before ingestion.
|
||||
podMonitorMetricRelabelings: []
|
||||
# -- Relabel configurations to apply to samples before scraping.
|
||||
podMonitorRelabelings: []
|
||||
# -- Additional labels for the podMonitor
|
||||
podMonitorAdditionalLabels: {}
|
||||
|
||||
grafanaDashboard:
|
||||
create: false
|
||||
# -- Allows overriding the namespace where the ConfigMap will be created, defaulting to the same one as the Release.
|
||||
namespace: ""
|
||||
# -- The name of the ConfigMap containing the dashboard.
|
||||
configMapName: "cnpg-grafana-dashboard"
|
||||
# -- Label that ConfigMaps should have to be loaded as dashboards. DEPRECATED: Use labels instead.
|
||||
sidecarLabel: "grafana_dashboard"
|
||||
# -- Label value that ConfigMaps should have to be loaded as dashboards. DEPRECATED: Use labels instead.
|
||||
sidecarLabelValue: "1"
|
||||
# -- Labels that ConfigMaps should have to get configured in Grafana.
|
||||
labels: {}
|
||||
# -- Annotations that ConfigMaps can have to get configured in Grafana.
|
||||
annotations: {}
|
||||
|
||||
# Default monitoring queries
|
||||
monitoringQueriesConfigMap:
|
||||
# -- The name of the default monitoring configmap.
|
||||
name: cnpg-default-monitoring
|
||||
# -- A string representation of a YAML defining monitoring queries.
|
||||
queries: |
|
||||
backends:
|
||||
query: |
|
||||
SELECT sa.datname
|
||||
, sa.usename
|
||||
, sa.application_name
|
||||
, states.state
|
||||
, COALESCE(sa.count, 0) AS total
|
||||
, COALESCE(sa.max_tx_secs, 0) AS max_tx_duration_seconds
|
||||
FROM ( VALUES ('active')
|
||||
, ('idle')
|
||||
, ('idle in transaction')
|
||||
, ('idle in transaction (aborted)')
|
||||
, ('fastpath function call')
|
||||
, ('disabled')
|
||||
) AS states(state)
|
||||
LEFT JOIN (
|
||||
SELECT datname
|
||||
, state
|
||||
, usename
|
||||
, COALESCE(application_name, '') AS application_name
|
||||
, pg_catalog.count(*)
|
||||
, COALESCE(EXTRACT (EPOCH FROM (pg_catalog.max(pg_catalog.now() OPERATOR(pg_catalog.-) xact_start))), 0) AS max_tx_secs
|
||||
FROM pg_catalog.pg_stat_activity
|
||||
GROUP BY datname, state, usename, application_name
|
||||
) sa ON states.state OPERATOR(pg_catalog.=) sa.state
|
||||
WHERE sa.usename IS NOT NULL
|
||||
metrics:
|
||||
- datname:
|
||||
usage: "LABEL"
|
||||
description: "Name of the database"
|
||||
- usename:
|
||||
usage: "LABEL"
|
||||
description: "Name of the user"
|
||||
- application_name:
|
||||
usage: "LABEL"
|
||||
description: "Name of the application"
|
||||
- state:
|
||||
usage: "LABEL"
|
||||
description: "State of the backend"
|
||||
- total:
|
||||
usage: "GAUGE"
|
||||
description: "Number of backends"
|
||||
- max_tx_duration_seconds:
|
||||
usage: "GAUGE"
|
||||
description: "Maximum duration of a transaction in seconds"
|
||||
|
||||
backends_waiting:
|
||||
query: |
|
||||
SELECT pg_catalog.count(*) AS total
|
||||
FROM pg_catalog.pg_locks blocked_locks
|
||||
JOIN pg_catalog.pg_locks blocking_locks
|
||||
ON blocking_locks.locktype OPERATOR(pg_catalog.=) blocked_locks.locktype
|
||||
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
|
||||
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
|
||||
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
|
||||
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
|
||||
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
|
||||
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
|
||||
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
|
||||
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
|
||||
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
|
||||
AND blocking_locks.pid OPERATOR(pg_catalog.<>) blocked_locks.pid
|
||||
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid OPERATOR(pg_catalog.=) blocking_locks.pid
|
||||
WHERE NOT blocked_locks.granted
|
||||
metrics:
|
||||
- total:
|
||||
usage: "GAUGE"
|
||||
description: "Total number of backends that are currently waiting on other queries"
|
||||
|
||||
pg_database:
|
||||
query: |
|
||||
SELECT datname
|
||||
, pg_catalog.pg_database_size(datname) AS size_bytes
|
||||
, pg_catalog.age(datfrozenxid) AS xid_age
|
||||
, pg_catalog.mxid_age(datminmxid) AS mxid_age
|
||||
FROM pg_catalog.pg_database
|
||||
WHERE datallowconn
|
||||
metrics:
|
||||
- datname:
|
||||
usage: "LABEL"
|
||||
description: "Name of the database"
|
||||
- size_bytes:
|
||||
usage: "GAUGE"
|
||||
description: "Disk space used by the database"
|
||||
- xid_age:
|
||||
usage: "GAUGE"
|
||||
description: "Number of transactions from the frozen XID to the current one"
|
||||
- mxid_age:
|
||||
usage: "GAUGE"
|
||||
description: "Number of multiple transactions (Multixact) from the frozen XID to the current one"
|
||||
|
||||
pg_postmaster:
|
||||
query: |
|
||||
SELECT EXTRACT(EPOCH FROM pg_postmaster_start_time) AS start_time
|
||||
FROM pg_catalog.pg_postmaster_start_time()
|
||||
metrics:
|
||||
- start_time:
|
||||
usage: "GAUGE"
|
||||
description: "Time at which postgres started (based on epoch)"
|
||||
|
||||
pg_replication:
|
||||
query: |
|
||||
SELECT CASE WHEN (
|
||||
NOT pg_catalog.pg_is_in_recovery()
|
||||
OR pg_catalog.pg_last_wal_receive_lsn() OPERATOR(pg_catalog.=) pg_catalog.pg_last_wal_replay_lsn())
|
||||
THEN 0
|
||||
ELSE GREATEST (0,
|
||||
EXTRACT(EPOCH FROM (pg_catalog.now() OPERATOR(pg_catalog.-) pg_catalog.pg_last_xact_replay_timestamp())))
|
||||
END AS lag,
|
||||
pg_catalog.pg_is_in_recovery() AS in_recovery,
|
||||
EXISTS (TABLE pg_catalog.pg_stat_wal_receiver) AS is_wal_receiver_up,
|
||||
(SELECT pg_catalog.count(*) FROM pg_catalog.pg_stat_replication) AS streaming_replicas
|
||||
metrics:
|
||||
- lag:
|
||||
usage: "GAUGE"
|
||||
description: "Replication lag behind primary in seconds"
|
||||
- in_recovery:
|
||||
usage: "GAUGE"
|
||||
description: "Whether the instance is in recovery"
|
||||
- is_wal_receiver_up:
|
||||
usage: "GAUGE"
|
||||
description: "Whether the instance wal_receiver is up"
|
||||
- streaming_replicas:
|
||||
usage: "GAUGE"
|
||||
description: "Number of streaming replicas connected to the instance"
|
||||
|
||||
pg_replication_slots:
|
||||
query: |
|
||||
SELECT slot_name,
|
||||
slot_type,
|
||||
database,
|
||||
active,
|
||||
(CASE pg_catalog.pg_is_in_recovery()
|
||||
WHEN TRUE THEN pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_last_wal_receive_lsn(), restart_lsn)
|
||||
ELSE pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), restart_lsn)
|
||||
END) as pg_wal_lsn_diff
|
||||
FROM pg_catalog.pg_replication_slots
|
||||
WHERE NOT temporary
|
||||
metrics:
|
||||
- slot_name:
|
||||
usage: "LABEL"
|
||||
description: "Name of the replication slot"
|
||||
- slot_type:
|
||||
usage: "LABEL"
|
||||
description: "Type of the replication slot"
|
||||
- database:
|
||||
usage: "LABEL"
|
||||
description: "Name of the database"
|
||||
- active:
|
||||
usage: "GAUGE"
|
||||
description: "Flag indicating whether the slot is active"
|
||||
- pg_wal_lsn_diff:
|
||||
usage: "GAUGE"
|
||||
description: "Replication lag in bytes"
|
||||
|
||||
pg_stat_archiver:
|
||||
query: |
|
||||
SELECT archived_count
|
||||
, failed_count
|
||||
, COALESCE(EXTRACT(EPOCH FROM (pg_catalog.now() OPERATOR(pg_catalog.-) last_archived_time)), -1) AS seconds_since_last_archival
|
||||
, COALESCE(EXTRACT(EPOCH FROM (pg_catalog.now() OPERATOR(pg_catalog.-) last_failed_time)), -1) AS seconds_since_last_failure
|
||||
, COALESCE(EXTRACT(EPOCH FROM last_archived_time), -1) AS last_archived_time
|
||||
, COALESCE(EXTRACT(EPOCH FROM last_failed_time), -1) AS last_failed_time
|
||||
, COALESCE(CAST(CAST('x' OPERATOR(pg_catalog.||) pg_catalog.right(pg_catalog.split_part(last_archived_wal, '.', 1), 16) AS pg_catalog.bit(64)) AS pg_catalog.int8), -1) AS last_archived_wal_start_lsn
|
||||
, COALESCE(CAST(CAST('x' OPERATOR(pg_catalog.||) pg_catalog.right(pg_catalog.split_part(last_failed_wal, '.', 1), 16) AS pg_catalog.bit(64)) AS pg_catalog.int8), -1) AS last_failed_wal_start_lsn
|
||||
, EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time
|
||||
FROM pg_catalog.pg_stat_archiver
|
||||
predicate_query: |
|
||||
SELECT NOT pg_catalog.pg_is_in_recovery()
|
||||
OR pg_catalog.current_setting('archive_mode') OPERATOR(pg_catalog.=) 'always'
|
||||
metrics:
|
||||
- archived_count:
|
||||
usage: "COUNTER"
|
||||
description: "Number of WAL files that have been successfully archived"
|
||||
- failed_count:
|
||||
usage: "COUNTER"
|
||||
description: "Number of failed attempts for archiving WAL files"
|
||||
- seconds_since_last_archival:
|
||||
usage: "GAUGE"
|
||||
description: "Seconds since the last successful archival operation"
|
||||
- seconds_since_last_failure:
|
||||
usage: "GAUGE"
|
||||
description: "Seconds since the last failed archival operation"
|
||||
- last_archived_time:
|
||||
usage: "GAUGE"
|
||||
description: "Epoch of the last time WAL archiving succeeded"
|
||||
- last_failed_time:
|
||||
usage: "GAUGE"
|
||||
description: "Epoch of the last time WAL archiving failed"
|
||||
- last_archived_wal_start_lsn:
|
||||
usage: "GAUGE"
|
||||
description: "Archived WAL start LSN"
|
||||
- last_failed_wal_start_lsn:
|
||||
usage: "GAUGE"
|
||||
description: "Last failed WAL LSN"
|
||||
- stats_reset_time:
|
||||
usage: "GAUGE"
|
||||
description: "Time at which these statistics were last reset"
|
||||
|
||||
pg_stat_bgwriter:
|
||||
runonserver: "<17.0.0"
|
||||
query: |
|
||||
SELECT checkpoints_timed
|
||||
, checkpoints_req
|
||||
, checkpoint_write_time
|
||||
, checkpoint_sync_time
|
||||
, buffers_checkpoint
|
||||
, buffers_clean
|
||||
, maxwritten_clean
|
||||
, buffers_backend
|
||||
, buffers_backend_fsync
|
||||
, buffers_alloc
|
||||
FROM pg_catalog.pg_stat_bgwriter
|
||||
metrics:
|
||||
- checkpoints_timed:
|
||||
usage: "COUNTER"
|
||||
description: "Number of scheduled checkpoints that have been performed"
|
||||
- checkpoints_req:
|
||||
usage: "COUNTER"
|
||||
description: "Number of requested checkpoints that have been performed"
|
||||
- checkpoint_write_time:
|
||||
usage: "COUNTER"
|
||||
description: "Total amount of time that has been spent in the portion of checkpoint processing where files are written to disk, in milliseconds"
|
||||
- checkpoint_sync_time:
|
||||
usage: "COUNTER"
|
||||
description: "Total amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk, in milliseconds"
|
||||
- buffers_checkpoint:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers written during checkpoints"
|
||||
- buffers_clean:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers written by the background writer"
|
||||
- maxwritten_clean:
|
||||
usage: "COUNTER"
|
||||
description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers"
|
||||
- buffers_backend:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers written directly by a backend"
|
||||
- buffers_backend_fsync:
|
||||
usage: "COUNTER"
|
||||
description: "Number of times a backend had to execute its own fsync call (normally the background writer handles those even when the backend does its own write)"
|
||||
- buffers_alloc:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers allocated"
|
||||
|
||||
pg_stat_bgwriter_17:
|
||||
runonserver: ">=17.0.0"
|
||||
name: pg_stat_bgwriter
|
||||
query: |
|
||||
SELECT buffers_clean
|
||||
, maxwritten_clean
|
||||
, buffers_alloc
|
||||
, EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time
|
||||
FROM pg_catalog.pg_stat_bgwriter
|
||||
metrics:
|
||||
- buffers_clean:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers written by the background writer"
|
||||
- maxwritten_clean:
|
||||
usage: "COUNTER"
|
||||
description: "Number of times the background writer stopped a cleaning scan because it had written too many buffers"
|
||||
- buffers_alloc:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers allocated"
|
||||
- stats_reset_time:
|
||||
usage: "GAUGE"
|
||||
description: "Time at which these statistics were last reset"
|
||||
|
||||
pg_stat_checkpointer:
|
||||
runonserver: ">=17.0.0"
|
||||
query: |
|
||||
SELECT num_timed AS checkpoints_timed
|
||||
, num_requested AS checkpoints_req
|
||||
, restartpoints_timed
|
||||
, restartpoints_req
|
||||
, restartpoints_done
|
||||
, write_time
|
||||
, sync_time
|
||||
, buffers_written
|
||||
, EXTRACT(EPOCH FROM stats_reset) AS stats_reset_time
|
||||
FROM pg_catalog.pg_stat_checkpointer
|
||||
metrics:
|
||||
- checkpoints_timed:
|
||||
usage: "COUNTER"
|
||||
description: "Number of scheduled checkpoints that have been performed"
|
||||
- checkpoints_req:
|
||||
usage: "COUNTER"
|
||||
description: "Number of requested checkpoints that have been performed"
|
||||
- restartpoints_timed:
|
||||
usage: "COUNTER"
|
||||
description: "Number of scheduled restartpoints due to timeout or after a failed attempt to perform it"
|
||||
- restartpoints_req:
|
||||
usage: "COUNTER"
|
||||
description: "Number of requested restartpoints that have been performed"
|
||||
- restartpoints_done:
|
||||
usage: "COUNTER"
|
||||
description: "Number of restartpoints that have been performed"
|
||||
- write_time:
|
||||
usage: "COUNTER"
|
||||
description: "Total amount of time that has been spent in the portion of processing checkpoints and restartpoints where files are written to disk, in milliseconds"
|
||||
- sync_time:
|
||||
usage: "COUNTER"
|
||||
description: "Total amount of time that has been spent in the portion of processing checkpoints and restartpoints where files are synchronized to disk, in milliseconds"
|
||||
- buffers_written:
|
||||
usage: "COUNTER"
|
||||
description: "Number of buffers written during checkpoints and restartpoints"
|
||||
- stats_reset_time:
|
||||
usage: "GAUGE"
|
||||
description: "Time at which these statistics were last reset"
|
||||
|
||||
pg_stat_database:
|
||||
query: |
|
||||
SELECT datname
|
||||
, xact_commit
|
||||
, xact_rollback
|
||||
, blks_read
|
||||
, blks_hit
|
||||
, tup_returned
|
||||
, tup_fetched
|
||||
, tup_inserted
|
||||
, tup_updated
|
||||
, tup_deleted
|
||||
, conflicts
|
||||
, temp_files
|
||||
, temp_bytes
|
||||
, deadlocks
|
||||
, blk_read_time
|
||||
, blk_write_time
|
||||
FROM pg_catalog.pg_stat_database
|
||||
metrics:
|
||||
- datname:
|
||||
usage: "LABEL"
|
||||
description: "Name of this database"
|
||||
- xact_commit:
|
||||
usage: "COUNTER"
|
||||
description: "Number of transactions in this database that have been committed"
|
||||
- xact_rollback:
|
||||
usage: "COUNTER"
|
||||
description: "Number of transactions in this database that have been rolled back"
|
||||
- blks_read:
|
||||
usage: "COUNTER"
|
||||
description: "Number of disk blocks read in this database"
|
||||
- blks_hit:
|
||||
usage: "COUNTER"
|
||||
description: "Number of times disk blocks were found already in the buffer cache, so that a read was not necessary (this only includes hits in the PostgreSQL buffer cache, not the operating system's file system cache)"
|
||||
- tup_returned:
|
||||
usage: "COUNTER"
|
||||
description: "Number of rows returned by queries in this database"
|
||||
- tup_fetched:
|
||||
usage: "COUNTER"
|
||||
description: "Number of rows fetched by queries in this database"
|
||||
- tup_inserted:
|
||||
usage: "COUNTER"
|
||||
description: "Number of rows inserted by queries in this database"
|
||||
- tup_updated:
|
||||
usage: "COUNTER"
|
||||
description: "Number of rows updated by queries in this database"
|
||||
- tup_deleted:
|
||||
usage: "COUNTER"
|
||||
description: "Number of rows deleted by queries in this database"
|
||||
- conflicts:
|
||||
usage: "COUNTER"
|
||||
description: "Number of queries canceled due to conflicts with recovery in this database"
|
||||
- temp_files:
|
||||
usage: "COUNTER"
|
||||
description: "Number of temporary files created by queries in this database"
|
||||
- temp_bytes:
|
||||
usage: "COUNTER"
|
||||
description: "Total amount of data written to temporary files by queries in this database"
|
||||
- deadlocks:
|
||||
usage: "COUNTER"
|
||||
description: "Number of deadlocks detected in this database"
|
||||
- blk_read_time:
|
||||
usage: "COUNTER"
|
||||
description: "Time spent reading data file blocks by backends in this database, in milliseconds"
|
||||
- blk_write_time:
|
||||
usage: "COUNTER"
|
||||
description: "Time spent writing data file blocks by backends in this database, in milliseconds"
|
||||
|
||||
pg_stat_replication:
|
||||
primary: true
|
||||
query: |
|
||||
SELECT usename
|
||||
, COALESCE(application_name, '') AS application_name
|
||||
, COALESCE(client_addr::text, '') AS client_addr
|
||||
, COALESCE(client_port::text, '') AS client_port
|
||||
, EXTRACT(EPOCH FROM backend_start) AS backend_start
|
||||
, COALESCE(pg_catalog.age(backend_xmin), 0) AS backend_xmin_age
|
||||
, pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), sent_lsn) AS sent_diff_bytes
|
||||
, pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), write_lsn) AS write_diff_bytes
|
||||
, pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), flush_lsn) AS flush_diff_bytes
|
||||
, COALESCE(pg_catalog.pg_wal_lsn_diff(pg_catalog.pg_current_wal_lsn(), replay_lsn),0) AS replay_diff_bytes
|
||||
, COALESCE((EXTRACT(EPOCH FROM write_lag)),0)::float AS write_lag_seconds
|
||||
, COALESCE((EXTRACT(EPOCH FROM flush_lag)),0)::float AS flush_lag_seconds
|
||||
, COALESCE((EXTRACT(EPOCH FROM replay_lag)),0)::float AS replay_lag_seconds
|
||||
FROM pg_catalog.pg_stat_replication
|
||||
metrics:
|
||||
- usename:
|
||||
usage: "LABEL"
|
||||
description: "Name of the replication user"
|
||||
- application_name:
|
||||
usage: "LABEL"
|
||||
description: "Name of the application"
|
||||
- client_addr:
|
||||
usage: "LABEL"
|
||||
description: "Client IP address"
|
||||
- client_port:
|
||||
usage: "LABEL"
|
||||
description: "Client TCP port"
|
||||
- backend_start:
|
||||
usage: "COUNTER"
|
||||
description: "Time when this process was started"
|
||||
- backend_xmin_age:
|
||||
usage: "COUNTER"
|
||||
description: "The age of this standby's xmin horizon"
|
||||
- sent_diff_bytes:
|
||||
usage: "GAUGE"
|
||||
description: "Difference in bytes from the last write-ahead log location sent on this connection"
|
||||
- write_diff_bytes:
|
||||
usage: "GAUGE"
|
||||
description: "Difference in bytes from the last write-ahead log location written to disk by this standby server"
|
||||
- flush_diff_bytes:
|
||||
usage: "GAUGE"
|
||||
description: "Difference in bytes from the last write-ahead log location flushed to disk by this standby server"
|
||||
- replay_diff_bytes:
|
||||
usage: "GAUGE"
|
||||
description: "Difference in bytes from the last write-ahead log location replayed into the database on this standby server"
|
||||
- write_lag_seconds:
|
||||
usage: "GAUGE"
|
||||
description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written it"
|
||||
- flush_lag_seconds:
|
||||
usage: "GAUGE"
|
||||
description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it"
|
||||
- replay_lag_seconds:
|
||||
usage: "GAUGE"
|
||||
description: "Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it"
|
||||
|
||||
pg_settings:
|
||||
query: |
|
||||
SELECT name,
|
||||
CASE setting WHEN 'on' THEN '1' WHEN 'off' THEN '0' ELSE setting END AS setting
|
||||
FROM pg_catalog.pg_settings
|
||||
WHERE vartype IN ('integer', 'real', 'bool')
|
||||
ORDER BY 1
|
||||
metrics:
|
||||
- name:
|
||||
usage: "LABEL"
|
||||
description: "Name of the setting"
|
||||
- setting:
|
||||
usage: "GAUGE"
|
||||
description: "Setting value"
|
||||
|
||||
pg_extensions:
|
||||
query: |
|
||||
SELECT
|
||||
pg_catalog.current_database() as datname,
|
||||
name as extname,
|
||||
default_version,
|
||||
installed_version,
|
||||
CASE
|
||||
WHEN default_version OPERATOR(pg_catalog.=) installed_version THEN 0
|
||||
ELSE 1
|
||||
END AS update_available
|
||||
FROM pg_catalog.pg_available_extensions
|
||||
WHERE installed_version IS NOT NULL
|
||||
metrics:
|
||||
- datname:
|
||||
usage: "LABEL"
|
||||
description: "Name of the database"
|
||||
- extname:
|
||||
usage: "LABEL"
|
||||
description: "Extension name"
|
||||
- default_version:
|
||||
usage: "LABEL"
|
||||
description: "Default version"
|
||||
- installed_version:
|
||||
usage: "LABEL"
|
||||
description: "Installed version"
|
||||
- update_available:
|
||||
usage: "GAUGE"
|
||||
description: "An update is available"
|
||||
target_databases:
|
||||
- '*'
|
||||
@@ -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,269 @@
|
||||
# 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` 로 한다.
|
||||
|
||||
### 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.0.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/wbsong111/cnpg-postgresql:18.4-bci15.7-hardened-20260729"
|
||||
#
|
||||
# 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/wbsong111/cnpg-postgresql:18.4-bci15.7-hardened-20260729"
|
||||
|
||||
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,122 @@
|
||||
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 }}
|
||||
|
||||
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,167 @@
|
||||
# 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;
|
||||
|
||||
# 선언적 데이터베이스·확장·스키마 관리 (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: {}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,109 @@
|
||||
# etcd 버전 갱신 가이드
|
||||
|
||||
## 1. git 작업 환경 구성
|
||||
|
||||
```sh
|
||||
git clone https://github.com/paasup/dip-catalog.git
|
||||
cd dip-catalog
|
||||
git checkout -b update-etcd/<신규버전>
|
||||
```
|
||||
|
||||
## 2. helm chart 업데이트
|
||||
|
||||
### 1) 신규 버전 확인
|
||||
|
||||
```sh
|
||||
helm repo add groundhog2k https://groundhog2k.github.io/helm-charts/
|
||||
helm repo update groundhog2k
|
||||
helm search repo groundhog2k/etcd --versions | head
|
||||
```
|
||||
|
||||
차트 버전과 etcd 버전(appVersion)은 다르다. 대응 관계를 반드시 확인한다.
|
||||
|
||||
| 차트 버전 | etcd(appVersion) |
|
||||
| --- | --- |
|
||||
| 1.1.12 | v3.7.1 |
|
||||
|
||||
### 2) 이미지 레지스트리 재확인 (매 버전 갱신 시 필수)
|
||||
|
||||
`CUSTOM-README.md` 에 적어둔 대로 etcd 프로젝트는 `gcr.io/etcd-development`,
|
||||
`quay.io/coreos` 를 **3.8부터 폐지**하고 `registry.k8s.io/etcd` 로 이전한다
|
||||
([etcd-io/etcd#20928](https://github.com/etcd-io/etcd/issues/20928)). 버전을 올릴 때마다
|
||||
아래를 확인해 어느 레지스트리로 고정할지 다시 판단한다.
|
||||
|
||||
```sh
|
||||
NEW_VER=v3.8.0 # 예시
|
||||
# registry.k8s.io 에 신규 버전이 이미 올라와 있는지 확인
|
||||
curl -sL "https://registry.k8s.io/v2/etcd/tags/list" | grep "\"$NEW_VER\""
|
||||
# 아직 없다면 과도기 레지스트리(quay.io/coreos, gcr.io/etcd-development)로 폴백
|
||||
```
|
||||
|
||||
### 3) 신규 버전 디렉토리 생성
|
||||
|
||||
버전별 독립 디렉토리다. 기존 디렉토리를 수정하지 않고 새로 만든다.
|
||||
|
||||
```sh
|
||||
NEW=1.2.0
|
||||
OLD=1.1.12
|
||||
cd manifests/helm/etcd
|
||||
|
||||
helm pull groundhog2k/etcd --version "$NEW" --untar --untardir /tmp/etcd-pull
|
||||
mkdir -p "$NEW"
|
||||
cp -R /tmp/etcd-pull/etcd/. "$NEW"/
|
||||
|
||||
# PaaSup 관리 파일을 이전 버전에서 승계한다
|
||||
for f in custom-values.yaml dip-values.yaml dip-resources-quotas.yaml \
|
||||
dip-volumes-quotas.yaml CUSTOM-README.md BUILD-README.md; do
|
||||
cp "$OLD/$f" "$NEW/$f"
|
||||
done
|
||||
```
|
||||
|
||||
### 4) diff 확인
|
||||
|
||||
```sh
|
||||
diff -u "$OLD/values.yaml" "$NEW/values.yaml" | less
|
||||
helm template test "$NEW" -f "$NEW/custom-values.yaml" >/dev/null && echo "렌더링 OK"
|
||||
```
|
||||
|
||||
`custom-values.yaml` 의 `image.tag`/`initImage.tag` 를 신규 appVersion·busybox 최신
|
||||
고정 태그로 갱신한다(위 2번 결과 반영).
|
||||
|
||||
### 5) 렌더링 결과 비교
|
||||
|
||||
```sh
|
||||
helm template etcd "$OLD" -f "$OLD/custom-values.yaml" -n etcd-system > /tmp/old.yaml
|
||||
helm template etcd "$NEW" -f "$NEW/custom-values.yaml" -n etcd-system > /tmp/new.yaml
|
||||
diff -u /tmp/old.yaml /tmp/new.yaml
|
||||
```
|
||||
|
||||
## 3. 문서 갱신
|
||||
|
||||
| 파일 | 갱신 내용 |
|
||||
| --- | --- |
|
||||
| `CUSTOM-README.md` | 차트/etcd 버전 번호, 이미지 레지스트리 판단 결과 |
|
||||
| `BUILD-README.md` | 위 버전 대응 표에 신규 행 추가 |
|
||||
| `doc/charts/etcd/deploy-test.md` | 신규 버전으로 배포 테스트 재실행 후 결과 갱신 |
|
||||
| `doc/image-selection.md` | 현재 카탈로그 상태 표의 etcd 행 갱신 |
|
||||
|
||||
## 4. 배포 검증
|
||||
|
||||
```sh
|
||||
IMAGE_NAME=<registry>/<repository>:<tag> bash scripts/deploy-test/deploy-test-etcd.sh /tmp/etcd-deploy-test-out
|
||||
```
|
||||
|
||||
최소한 다음을 통과해야 한다.
|
||||
|
||||
1. 3-replica StatefulSet 이 모두 `Running`
|
||||
2. `etcdctl endpoint health --cluster` 전 멤버 healthy
|
||||
3. `etcdctl put`/`get` 쓰기·읽기 왕복 확인
|
||||
|
||||
## 5. PR
|
||||
|
||||
```sh
|
||||
git add manifests/helm/etcd/<신규버전> doc/
|
||||
git commit -m "etcd <신규버전> 추가"
|
||||
git push -u origin update-etcd/<신규버전>
|
||||
```
|
||||
|
||||
PR 생성 시 `helm-catalog-sbom` 워크플로가 변경 차트에 대해 SBOM·취약점 스캔을 수행한다.
|
||||
CRITICAL 취약점이 있으면 내용을 확인하고 PR 본문에 판단 근거를 남긴다.
|
||||
@@ -0,0 +1,106 @@
|
||||
# etcd 배포
|
||||
|
||||
차트 버전 `1.1.12` (`groundhog2k/etcd`) / etcd 버전 `v3.7.1`
|
||||
|
||||
etcd 는 분산 key-value 저장소다. 이 차트는 오퍼레이터 없이 StatefulSet 으로 직접
|
||||
멀티노드 클러스터를 구성한다(cnpg 처럼 operator + CR 구조가 아니다).
|
||||
|
||||
## 1. 배포 방법
|
||||
|
||||
### 1) 배포 시 주의 사항
|
||||
|
||||
- **`replicas` 는 최초 배포 후 변경할 수 없다.** 업스트림 차트가 명시하는 제약이다
|
||||
(`values.yaml` 주석 — "Automatic scaling or manually scaling the etcd cluster after
|
||||
first deployment is not supported"). 클러스터 크기를 바꾸려면 재배포(새 클러스터 생성
|
||||
+ 데이터 마이그레이션)가 필요하다. 배포 전에 티어(`dip-resources-quotas.yaml`)를 반드시
|
||||
확정한다.
|
||||
- **자동 백업이 없다.** cnpg 의 `barmanObjectStore`/`scheduledBackup` 같은 내장 기능이
|
||||
없다. 스냅샷은 운영자가 수동으로 실행한다.
|
||||
```sh
|
||||
kubectl -n <ns> exec <pod-0> -- etcdctl snapshot save /tmp/snapshot.db
|
||||
kubectl -n <ns> cp <pod-0>:/tmp/snapshot.db ./snapshot.db
|
||||
```
|
||||
정기 백업이 필요하면 별도 CronJob 을 구성해야 한다 — 이 차트 범위 밖이다.
|
||||
- **TLS 는 기본 비활성이다.** `custom-values.yaml` 은 업스트림 기본값(평문 클라이언트/피어
|
||||
통신)을 그대로 쓴다. 운영 배포는 `dip-values.yaml` 로 `settings.https.autoTls: true` 를
|
||||
켠다(자체 서명 인증서 자동 생성). CA 발급 인증서로 교체하려면 `extraSecrets` 로 마운트하고
|
||||
`settings.https.autoTls` 를 `false` 로 내린 뒤 인증서 경로를 직접 지정해야 한다
|
||||
(차트 `values.yaml` 의 `settings.https` 주석 참고).
|
||||
- init 컨테이너(`busybox`)가 데이터 디렉토리(`/data/etcd`)를 `chmod 700` 으로 생성한다.
|
||||
`securityContext`(uid/gid 999, non-root, RO rootfs)와 함께 이 권한이 맞물려야 정상
|
||||
기동한다 — 배포 테스트에서 이미지가 실제로 uid 999 로 뜨는지 확인 완료
|
||||
(`doc/charts/etcd/deploy-test.md`).
|
||||
|
||||
### 2) 배포
|
||||
|
||||
```sh
|
||||
git clone https://github.com/paasup/dip-catalog.git
|
||||
cd dip-catalog/manifests/helm/etcd/1.1.12
|
||||
helm upgrade etcd ./ -f custom-values.yaml --install -n etcd-system --create-namespace --wait
|
||||
```
|
||||
|
||||
### 3) 확인
|
||||
|
||||
```sh
|
||||
kubectl -n etcd-system get pods -l app.kubernetes.io/name=etcd
|
||||
kubectl -n etcd-system exec etcd-0 -- etcdctl endpoint health --cluster
|
||||
```
|
||||
|
||||
## 2. custom-values.yaml 설명
|
||||
|
||||
### 1) 이미지 설정
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `image.registry` / `image.repository` / `image.tag` | etcd 본체 이미지. **자체 빌드**(`docker.io/wbsong111/etcd:3.7.1-security-hardened-20260731`) — 업스트림 `quay.io/coreos/etcd:v3.7.1` 이 게이트 차단 HIGH 1건(`CVE-2026-56852`, `golang.org/x/text`)으로 막혀 대체했다. 근거·빌드 정의는 [images/etcd/README.md](../../../images/etcd/README.md), 채택 결정 배경은 security-catalog 프로젝트 decisions/0007(dip-catalog 미이관). 상위 태그가 나오거나 `release-3.7` 에 백포트되면 업스트림으로 되돌리는 것이 우선(대응 우선순위 a) | 업스트림 `quay.io/coreos/etcd` (태그 미지정) |
|
||||
| `initImage.registry` / `initImage.repository` / `initImage.tag` | 데이터 디렉토리 초기화용 init 컨테이너. 업스트림 기본값(`busybox:stable`)은 롤링 태그라 `busybox:1.38.0-uclibc` 로 고정 | 업스트림 `busybox:stable` |
|
||||
|
||||
### 2) 클러스터 크기
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `replicas` | 최초 배포 후 변경 불가(위 주의사항). 이 카탈로그 항목은 단일 APISIX 인스턴스의 설정 저장소 용도라 쿼럼 HA 대신 단일 노드로 둔다(결정 배경: security-catalog 프로젝트 decisions/0006, dip-catalog 미이관) — 노드 장애 시 단일 장애점이 되는 트레이드오프를 받아들인 것. 쿼럼 HA가 필요하면 `dip-values.yaml`(3, 홀수 권장) 사용 | 1 |
|
||||
|
||||
### 3) 스토리지
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `storage.requestedSize` | PVC 크기. 티어별 값은 `dip-volumes-quotas.yaml` 참고 | `5Gi` |
|
||||
| `storage.className` | `longhorn` | `longhorn` |
|
||||
|
||||
etcd 는 권장 데이터 크기 상한이 있다(기본 `--quota-backend-bytes` 2GB, 업스트림도 8GB 초과를
|
||||
권장하지 않음). 볼륨을 과하게 키우는 것이 능사가 아니다.
|
||||
|
||||
### 4) TLS
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `settings.https.enabled` | 클라이언트/피어 통신 TLS 사용 여부 | `false`(custom-values) / `true`(dip-values) |
|
||||
| `settings.https.autoTls` | etcd 자체 서명 인증서 자동 생성 | `false`(custom-values) / `true`(dip-values) |
|
||||
|
||||
### 5) 리소스
|
||||
|
||||
| Name | 설명 | 기본값 |
|
||||
| --- | --- | --- |
|
||||
| `resources` | 티어별 값은 `dip-resources-quotas.yaml` 참고 | 업스트림은 `{}`(무제한) |
|
||||
|
||||
## 3. 업그레이드
|
||||
|
||||
버전 갱신 절차는 `BUILD-README.md` 참고. etcd 는 마이너 버전 간 데이터 포맷 호환성을
|
||||
공식 문서에서 보장하는 범위 내에서만 순차 업그레이드해야 한다(예: 3.6→3.7). 여러 마이너
|
||||
버전을 건너뛰지 않는다.
|
||||
|
||||
## 4. 제거
|
||||
|
||||
```sh
|
||||
helm uninstall etcd -n etcd-system
|
||||
kubectl -n etcd-system delete pvc -l app.kubernetes.io/name=etcd
|
||||
```
|
||||
|
||||
PVC 는 helm uninstall 로 지워지지 않는다(StatefulSet 볼륨클레임 기본 동작). 데이터를
|
||||
보존할 필요가 없다면 위처럼 명시적으로 삭제한다. `longhorn` 은 `reclaimPolicy: Retain` 이므로
|
||||
PV 자체가 남을 수 있다 — `.claude/pitfalls.md` 참고.
|
||||
|
||||
## 5. 검증 이력
|
||||
|
||||
`doc/charts/etcd/deploy-test.md` 참고.
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v2
|
||||
appVersion: v3.7.1
|
||||
description: A Helm chart for etcd on Kubernetes
|
||||
icon: https://etcd.io/etcd-horizontal-white.png
|
||||
keywords:
|
||||
- database
|
||||
- etcd
|
||||
maintainers:
|
||||
- name: groundhog2k
|
||||
url: https://github.com/groundhog2k/helm-charts
|
||||
name: etcd
|
||||
type: application
|
||||
version: 1.1.12
|
||||
@@ -0,0 +1,176 @@
|
||||
# Etcd
|
||||
|
||||
  
|
||||
|
||||
## Changelog
|
||||
|
||||
see [RELEASENOTES.md](RELEASENOTES.md)
|
||||
|
||||
A Helm chart for a Etcd HA-cluster on Kubernetes
|
||||
|
||||
## TL;DR
|
||||
|
||||
```bash
|
||||
helm repo add groundhog2k https://groundhog2k.github.io/helm-charts/
|
||||
helm install my-release groundhog2k/etcd
|
||||
```
|
||||
|
||||
## Introduction
|
||||
|
||||
This chart uses the original [Etcd image from Quay.io](https://quay.io/repository/coreos/etcd) to deploy a stateful Etcd cluster in Kubernetes.
|
||||
|
||||
It fully supports deployment of the multi-architecture docker image.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes 1.12+
|
||||
- Helm 3.x
|
||||
- PV provisioner support in the underlying infrastructure
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart with the release name `my-release`:
|
||||
|
||||
```bash
|
||||
helm install my-release groundhog2k/etcd
|
||||
```
|
||||
|
||||
## Uninstalling the Chart
|
||||
|
||||
To uninstall/delete the `my-release` deployment:
|
||||
|
||||
```bash
|
||||
helm uninstall my-release
|
||||
```
|
||||
|
||||
## Common parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| fullnameOverride | string | `""` | Fully override the deployment name |
|
||||
| nameOverride | string | `""` | Partially override the deployment name |
|
||||
|
||||
## Deployment parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
||||
| image.registry | string | `"quay.io/coreos"` | Image registry |
|
||||
| image.repository | string | `"etcd"` | Image name |
|
||||
| image.tag | string | `""` | Image tag |
|
||||
| initImage.pullPolicy | string | `"IfNotPresent"` | Init image pull policy |
|
||||
| initImage.registry | string | `"docker.io"` | Image registry |
|
||||
| initImage.repository | string | `"busybox"` | Init image name |
|
||||
| initImage.tag | string | `"stable"` | Init image tag |
|
||||
| imagePullSecrets | list | `[]` | Image pull secrets |
|
||||
| extraInitContainers | list | `[]` | Extra init containers |
|
||||
| extaContainers | list | `[]` | Extra containers for usage as sidecars |
|
||||
| startupProbe | object | `see values.yaml` | Startup probe configuration |
|
||||
| livenessProbe | object | `see values.yaml` | Liveness probe configuration |
|
||||
| readinessProbe | object | `see values.yaml` | Readiness probe configuration |
|
||||
| customStartupProbe | object | `{}` | Custom startup probe (overwrites default startup probe configuration) |
|
||||
| customLivenessProbe | object | `{}` | Custom liveness probe (overwrites default liveness probe configuration) |
|
||||
| customReadinessProbe | object | `{}` | Custom readiness probe (overwrites default readiness probe configuration) |
|
||||
| resources | object | `{}` | Resource limits and requests |
|
||||
| priorityClassName | string | `""` | Deployment priority class name |
|
||||
| nodeSelector | object | `{}` | Deployment node selector |
|
||||
| customLabels | object | `{}` | Additional labels for Deployment or StatefulSet |
|
||||
| customAnnotations | object | `{}` | Additional annotations for Deployment or StatefulSet |
|
||||
| podAnnotations | object | `{}` | Additional pod annotations |
|
||||
| podLabels | object | `{}` | Additional pod labels |
|
||||
| podSecurityContext | object | `see values.yaml` | Pod security context |
|
||||
| securityContext | object | `see values.yaml` | Container security context |
|
||||
| env | list | `[]` | Additional container environmment variables |
|
||||
| args | list | `[]` | Additional container command arguments |
|
||||
| rbac.create | bool | `true` | Enable creation of RBAC |
|
||||
| serviceAccount.annotations | object | `{}` | Additional service account annotations |
|
||||
| serviceAccount.create | bool | `true` | Enable service account creation |
|
||||
| serviceAccount.name | string | `""` | Optional name of the service account |
|
||||
| serviceAccount.automountServiceAccountToken | bool | `true` | Specifies whether a service account token should be automatically mounted |
|
||||
| affinity | object | `{}` | Affinity for pod assignment |
|
||||
| tolerations | list | `[]` | Tolerations for pod assignment |
|
||||
| topologySpreadConstraints | object | `{}` | Topology spread constraints for pods |
|
||||
| podManagementPolicy | string | `"Parallel"` | Pod management policy |
|
||||
| updateStrategyType | string | `"RollingUpdate"` | Pod update strategy |
|
||||
| replicas | int | `1` | Number of replicas (Due to the nature of etcd cluster initialization this value must be set before deploying the cluster) |
|
||||
| revisionHistoryLimit | int | `nil` | Maximum number of revisions maintained in revision history |
|
||||
| podDisruptionBudget | object | `{}` | Pod disruption budget |
|
||||
| podDisruptionBudget.minAvailable | int | `nil` | Minimum number of pods that must be available after eviction |
|
||||
| podDisruptionBudget.maxUnavailable | int | `nil` | Maximum number of pods that can be unavailable after eviction |
|
||||
| clusterDomain | string | `"cluster.local"` | Kubernetes cluster domain (DNS) suffix |
|
||||
|
||||
## Service parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| service.type | string | `"ClusterIP"` | Service type |
|
||||
| service.clusterIP | string | `nil` | The cluster ip address (only relevant for type LoadBalancer or NodePort) |
|
||||
| service.loadBalancerIP | string | `nil` | The load balancer ip address (only relevant for type LoadBalancer) |
|
||||
| service.loadBalancerSourceRanges | list | `[]` | The list of IP CIDR ranges that are allowed to access the load balancer (only relevent for type LoadBalancer) |
|
||||
| service.client.port | int | `2379` | Client service port |
|
||||
| service.client.nodePort | int | `nil` | Service node port (only relevant for type LoadBalancer or NodePort) |
|
||||
| service.peer.port | int | `2380` | Peer service port |
|
||||
| service.peer.nodePort | int | `nil` | Service node port (only relevant for type LoadBalancer or NodePort) |
|
||||
| service.annotations | object | `{}` | Additional service annotations |
|
||||
| service.labels | object | `{}` | Additional service labels |
|
||||
|
||||
## Service monitor parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| serviceMonitor.enabled | bool | `false` | Enable service monitor |
|
||||
| serviceMonitor.additionalLabels | object | `{}` | Additional labels for the service monitor object |
|
||||
| serviceMonitor.annotations | object | `{}` | Annotations for the service monitor object |
|
||||
| serviceMonitor.interval | Duration | `nil` | Scrape interval for prometheus |
|
||||
| serviceMonitor.scrapeTimeout | Duration | `nil` | Scrape timeout value |
|
||||
| serviceMonitor.extraEndpointParameters | object | `nil` | Extra parameters rendered to the [service monitor endpoint](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#endpoint) |
|
||||
| serviceMonitor.extraParameters | object | `nil` | Extra parameters rendered to the [service monitor object](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#servicemonitorspec) |
|
||||
|
||||
## Network policies
|
||||
|
||||
Allows to define optional network policies for [ingress and egress](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
|
||||
The policyTypes will be automatically set
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| networkPolicy.ingress | object | `{}` | Ingress network policies |
|
||||
| networkPolicy.egress | object | `{}` | Egress network policies |
|
||||
|
||||
## Storage parameters
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| storage.accessModes[0] | string | `"ReadWriteOnce"` | Storage access mode |
|
||||
| storage.volumeName | string | `"etcd-data"` | Internal volume name and prefix of a created PVC |
|
||||
| storage.persistentVolumeClaimName | string | `nil` | PVC name when existing storage volume should be used |
|
||||
| storage.requestedSize | string | `nil` | Size for new PVC, when no existing PVC is used |
|
||||
| storage.className | string | `nil` | Storage class name |
|
||||
| storage.annotations | object | `{}` | Additional storage annotations |
|
||||
| storage.labels | object | `{}` | Additional storage labels |
|
||||
| extraStorage | list | `[]` | A list of additional existing PVC that will be mounted into the container |
|
||||
| extraStorage[].name | string | `nil` | Internal name of the volume |
|
||||
| extraStorage[].pvcName | string | `nil` | Name of the existing PVC |
|
||||
| extraStorage[].mountPath | string | `nil` | Mount path where the PVC should be mounted into the container |
|
||||
|
||||
## Etcd settings
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| settings.clusterToken | bool | `"etcd-cluster-0"` | Unique cluser token |
|
||||
| settings.https.enabled | bool | `false` | Enable HTTPS |
|
||||
| settings.https.autoTls | bool | `false` | Automatic TLS mode of etcd (TLS certs. created automaically) |
|
||||
| settings.shutdownDelay | int | `3` | Delay after termination request to give etcd process time for graceful shutdown |
|
||||
|
||||
## Etcd secrets and configuration
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| extraSecrets | list | `[]` | A list of additional existing secrets that will be mounted into the container |
|
||||
| extraSecrets[].name | string | `nil` | Name of the existing K8s secret |
|
||||
| extraSecrets[].defaultMode | int | `0440` | Mount default access mode |
|
||||
| extraSecrets[].mountPath | string | `nil` | Mount path where the secret should be mounted into the container (f.e. /mysecretfolder) |
|
||||
| extraConfigs | list | `[]` | A list of additional existing configMaps that will be mounted into the container |
|
||||
| extraConfigs[].name | string | `nil` | Name of the existing K8s configMap |
|
||||
| extraConfigs[].defaultMode | int | `0440` | Mount default access mode |
|
||||
| extraConfigs[].mountPath | string | `nil` | Mount path where the configMap should be mounted into the container (f.e. /myconfigfolder) |
|
||||
| extraEnvSecrets | list | `[]` | A list of existing secrets that will be mounted into the container as environment variables |
|
||||
@@ -0,0 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
| Chart version | App version | Change description |
|
||||
| :------------ | :---------- | :----------------- |
|
||||
| 0.1.0 | v3.5.6 | Initial version |
|
||||
| 0.1.1 | v3.5.7 | Upgraded etcd to v3.5.7 |
|
||||
| 0.1.2 | v3.5.7 | Updated default security context |
|
||||
| 0.1.3 | v3.5.7 | Fixed "nil" syntax error for health checks (thx @omegazeng) |
|
||||
| 0.1.4 | v3.5.8 | Upgraded etcd to v3.5.8 |
|
||||
| 0.1.5 | v3.5.9 | Upgraded etcd to v3.5.9 |
|
||||
| 0.1.6 | v3.5.9 | Added support for network policies and additional labels and annotations |
|
||||
| 0.1.7 | v3.5.10 | Upgraded etcd to v3.5.10 |
|
||||
| 0.1.8 | v3.5.11 | Upgraded etcd to v3.5.11 |
|
||||
| 0.1.9 | v3.5.12 | Upgraded etcd to v3.5.12 |
|
||||
| 0.1.10 | v3.5.13 | Upgraded etcd to v3.5.13 |
|
||||
| 0.1.11 | v3.5.13 | Fixed build pipeline issue |
|
||||
| 1.0.0 | v3.5.13 | Final version with configuration secret, extra config and extra volume support |
|
||||
| 1.0.1 | v3.5.15 | Upgraded etcd to v3.5.15 |
|
||||
| 1.0.2 | v3.5.16 | Upgraded etcd to v3.5.16 |
|
||||
| 1.0.3 | v3.5.17 | Upgraded etcd to v3.5.17 |
|
||||
| 1.0.4 | v3.5.18 | Upgraded etcd to v3.5.18 |
|
||||
| 1.0.6 | v3.5.20 | Upgraded etcd to v3.5.20 |
|
||||
| 1.0.7 | v3.5.21 | Upgraded etcd to v3.5.21 |
|
||||
| 1.0.8 | v3.5.21 | Added support for loadBalancerSourceRanges |
|
||||
| 1.1.0 | v3.6.4 | Upgraded etcd to v3.6.4 |
|
||||
| 1.1.1 | v3.6.4 | Changed to busybox:stable containter image |
|
||||
| 1.1.2 | v3.6.4 | Added ability to set automountServiceAccountToken - thx @krizzpiBiGdirekt |
|
||||
| 1.1.3 | v3.6.4 | Added priorityClassName - thx @JimCronqvist |
|
||||
| 1.1.4 | v3.6.6 | Upgraded etcd to v3.6.6 |
|
||||
| 1.1.5 | v3.6.7 | Upgraded etcd to v3.6.7 |
|
||||
| 1.1.6 | v3.6.9 | Upgraded etcd to v3.6.9 |
|
||||
| 1.1.7 | v3.6.9 | Fixed insecure parameters - thx @oleksiialeksieiev |
|
||||
| 1.1.8 | v3.6.11 | Upgraded etcd to v3.6.11 |
|
||||
| 1.1.9 | v3.6.13 | Upgraded etcd to v3.6.13, fixed PVC definition - thx @trandbert37 |
|
||||
| 1.1.10 | v3.7.0 | Upgraded etcd to v3.7.0 |
|
||||
| 1.1.11 | v3.7.0 | Fixed README.md markdown formatting |
|
||||
| 1.1.12 | v3.7.1 | Upgraded etcd to v3.7.1 |
|
||||
| | | |
|
||||
@@ -0,0 +1,50 @@
|
||||
# etcd — PaaSup 오버라이드
|
||||
# 업스트림 기본값은 values.yaml 참고. 여기에는 변경이 필요한 항목만 둔다.
|
||||
|
||||
image:
|
||||
# 자체 빌드(대응 우선순위 c) — 업스트림 quay.io/coreos/etcd:v3.7.1 이 게이트 차단
|
||||
# HIGH 1건(golang.org/x/text, CVE-2026-56852, 바이너리 정적 링크라 베이스 OS 교체로
|
||||
# 해소 불가)으로 막혀 v3.7.1 태그가 가리키는 commit 을 그대로 컴파일했다. x/text 만
|
||||
# go.work 워크스페이스 전역 replace 로 0.39.0 이상으로 강제. 근거: doc/analysis/etcd-cve.md,
|
||||
# 결정: doc/decisions/0007-etcd-image-self-build.md. 빌드 정의: images/etcd/.
|
||||
# 상위 태그가 나오거나 release-3.7 에 백포트되면(대응 우선순위 a) 되돌리는 것이 우선.
|
||||
#
|
||||
# 이전 값: quay.io/coreos/etcd:v3.7.1 (업스트림). etcd 프로젝트는 3.8부터
|
||||
# gcr.io/etcd-development·quay.io/coreos 를 폐지하고 registry.k8s.io/etcd 로 이전
|
||||
# 예정이다(etcd-io/etcd#20928) — 상위 태그로 돌아갈 때 이것도 함께 재검토한다.
|
||||
registry: "docker.io/wbsong111"
|
||||
repository: "etcd"
|
||||
tag: "3.7.1-security-hardened-20260731"
|
||||
|
||||
initImage:
|
||||
# 업스트림 기본값 "stable" 은 롤링 태그다 (doc/image-selection.md 2번 규칙 위반).
|
||||
# 이 init 컨테이너도 extract-helm-images.sh 에 잡혀 게이트 대상이 되므로 고정한다.
|
||||
registry: "docker.io"
|
||||
repository: "busybox"
|
||||
tag: "1.38.0-uclibc"
|
||||
|
||||
# 클러스터 초기화 특성상 최초 배포 전에 정해야 하며, 이후 자동/수동 스케일링을
|
||||
# 지원하지 않는다(업스트림 values.yaml 주석).
|
||||
#
|
||||
# 이 카탈로그 항목의 용도는 단일 APISIX 인스턴스의 설정 저장소다 — APISIX 자체가
|
||||
# 단일 인스턴스라 etcd 쪽에서 쿼럼 HA를 가져갈 이유가 없고, 오퍼레이터 없이 가벼운
|
||||
# 차트 하나로 충분하다는 것이 이 결정의 핵심이다(doc/decisions/0006-etcd-chart-selection.md).
|
||||
# 노드 장애 시 단일 장애점이 된다는 것을 받아들인 트레이드오프다. 쿼럼 HA가 필요한
|
||||
# 다른 소비자가 생기면 dip-values.yaml(replicas: 3)을 쓰거나 이 값을 재검토한다.
|
||||
replicas: 1
|
||||
|
||||
storage:
|
||||
requestedSize: 5Gi
|
||||
className: "longhorn"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
# TLS — 업스트림 기본값(평문 클라이언트/피어 통신)을 그대로 둔다. 운영 배포는
|
||||
# dip-values.yaml 에서 autoTls: true 로 켠다. 여기서는 CVE 스캔 기준값 + 최소 동작
|
||||
# 확인용이라 단순하게 유지한다.
|
||||
@@ -0,0 +1,33 @@
|
||||
# etcd 는 데이터셋이 작고(대개 수백 MB~수 GB) CPU/메모리 요구량도 낮다.
|
||||
# 티어는 주로 replica 수(가용성)로 구분한다 — replica 수는 최초 배포 후 변경 불가
|
||||
# (업스트림 values.yaml 주석)하므로 반드시 배포 전에 티어를 정해야 한다.
|
||||
|
||||
small:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
medium:
|
||||
replicas: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
|
||||
large:
|
||||
replicas: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: 4Gi
|
||||
@@ -0,0 +1,32 @@
|
||||
# etcd — DIP 플랫폼 기본 배포값 (운영 기준)
|
||||
|
||||
replicas: 3
|
||||
|
||||
settings:
|
||||
clusterToken: "etcd-cluster-0"
|
||||
https:
|
||||
# 평문 클라이언트/피어 통신은 운영 기본값으로 부적절하다.
|
||||
# autoTls 는 etcd 가 자체 서명 인증서를 생성한다 — CA 발급 인증서로
|
||||
# 교체하려면 extraSecrets 로 마운트하고 이 값을 다시 false 로 내려야 한다.
|
||||
enabled: true
|
||||
autoTls: true
|
||||
|
||||
serviceMonitor:
|
||||
# rancher-monitoring(Prometheus Operator) 설치 환경에서만 true
|
||||
enabled: true
|
||||
|
||||
storage:
|
||||
requestedSize: 20Gi
|
||||
className: "longhorn"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
|
||||
# 이 차트는 자동 백업이 없다. 스냅샷은 운영자가 수동으로 실행한다
|
||||
# kubectl exec <pod> -- etcdctl snapshot save /tmp/snapshot.db
|
||||
# 정기 백업이 필요하면 별도 CronJob 을 구성해야 한다(이번 범위 밖).
|
||||
@@ -0,0 +1,17 @@
|
||||
# etcd 는 권장 데이터 크기 상한이 있다(기본 quota-backend-bytes 2GB, 상향 시에도
|
||||
# 8GB 를 넘기지 않는 것을 업스트림이 권장). 볼륨은 여유를 두되 과하게 키우지 않는다.
|
||||
|
||||
small:
|
||||
storage:
|
||||
requestedSize: 5Gi
|
||||
className: "longhorn"
|
||||
|
||||
medium:
|
||||
storage:
|
||||
requestedSize: 20Gi
|
||||
className: "longhorn"
|
||||
|
||||
large:
|
||||
storage:
|
||||
requestedSize: 50Gi
|
||||
className: "longhorn"
|
||||
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "etcd.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "etcd.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "etcd.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "etcd.labels" -}}
|
||||
helm.sh/chart: {{ include "etcd.chart" . }}
|
||||
{{ include "etcd.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "etcd.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "etcd.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "etcd.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "etcd.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,29 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ include "etcd.fullname" . }}
|
||||
labels:
|
||||
{{- include "etcd.labels" . | nindent 4 }}
|
||||
data:
|
||||
{{- $replicaCount := int .Values.replicas }}
|
||||
{{- $initialCluster := list }}
|
||||
{{- $etcdFullname := include "etcd.fullname" . }}
|
||||
{{- $etcdInternalServiceName := printf "%s-internal" $etcdFullname }}
|
||||
{{- $protocol := (or .Values.settings.https.enabled .Values.settings.https.autoTls) | ternary "https" "http" }}
|
||||
{{- $servicefqdn := printf "%s.%s.svc.%s" $etcdInternalServiceName .Release.Namespace .Values.clusterDomain }}
|
||||
ETCD_DATA_DIR: "/data/etcd"
|
||||
ETCD_INITIAL_CLUSTER_TOKEN: "{{ .Values.settings.clusterToken }}"
|
||||
ETCD_INITIAL_CLUSTER_STATE: "new"
|
||||
ETCD_LISTEN_CLIENT_URLS: "{{ $protocol }}://0.0.0.0:2379"
|
||||
ETCD_LISTEN_PEER_URLS: "{{ $protocol }}://0.0.0.0:2380"
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
ETCD_LISTEN_METRICS_URLS: "http://0.0.0.0:12379"
|
||||
{{- end }}
|
||||
{{- range $e, $i := until $replicaCount }}
|
||||
{{- $initialCluster = append $initialCluster (printf "%s-%d=%s://%s-%d.%s:%d" $etcdFullname $i $protocol $etcdFullname $i $servicefqdn 2380) }}
|
||||
{{- end }}
|
||||
ETCD_INITIAL_CLUSTER: {{ join "," $initialCluster | quote }}
|
||||
{{- if .Values.settings.https.autoTls }}
|
||||
ETCD_AUTO_TLS: "true"
|
||||
ETCD_PEER_AUTO_TLS: "true"
|
||||
{{- end }}
|
||||
@@ -0,0 +1,25 @@
|
||||
{{- with .Values.networkPolicy }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ include "etcd.fullname" $ }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "etcd.selectorLabels" $ | nindent 6 }}
|
||||
policyTypes:
|
||||
{{- if .ingress }}
|
||||
- Ingress
|
||||
{{- end }}
|
||||
{{- if .egress }}
|
||||
- Egress
|
||||
{{- end }}
|
||||
{{- with .ingress }}
|
||||
ingress:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .egress }}
|
||||
egress:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "etcd.fullname" . }}-internal
|
||||
labels:
|
||||
{{- include "etcd.labels" . | nindent 4 }}
|
||||
spec:
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- port: {{ .Values.service.client.port }}
|
||||
targetPort: client
|
||||
name: client
|
||||
- port: {{ .Values.service.peer.port }}
|
||||
targetPort: peer
|
||||
name: peer
|
||||
selector:
|
||||
{{- include "etcd.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,47 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "etcd.fullname" . }}
|
||||
labels:
|
||||
{{- include "etcd.labels" . | nindent 4 }}
|
||||
{{- with .Values.service.labels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.client.port }}
|
||||
targetPort: client
|
||||
name: client
|
||||
{{- if and ( or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) (.Values.service.client.nodePort) }}
|
||||
nodePort: {{ .Values.service.client.nodePort }}
|
||||
{{- end }}
|
||||
- port: {{ .Values.service.peer.port }}
|
||||
targetPort: peer
|
||||
name: peer
|
||||
{{- if and ( or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) (.Values.service.peer.nodePort) }}
|
||||
nodePort: {{ .Values.service.peer.nodePort }}
|
||||
{{- end }}
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
- port: {{ .Values.service.prometheus.port }}
|
||||
targetPort: prometheus
|
||||
name: prometheus
|
||||
{{- if and ( or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") ) (.Values.service.prometheus.nodePort) }}
|
||||
nodePort: {{ .Values.service.prometheus.nodePort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.service.type "LoadBalancer") (.Values.service.loadBalancerIP) }}
|
||||
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.service.type "LoadBalancer") (.Values.service.loadBalancerSourceRanges) }}
|
||||
loadBalancerSourceRanges: {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- if .Values.service.clusterIP }}
|
||||
clusterIP: {{ .Values.service.clusterIP }}
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "etcd.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "etcd.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "etcd.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,34 @@
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ include "etcd.fullname" . }}
|
||||
labels:
|
||||
{{- include "etcd.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceMonitor.additionalLabels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.serviceMonitor.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
endpoints:
|
||||
- port: "prometheus"
|
||||
path: "/metrics"
|
||||
{{- if .Values.serviceMonitor.interval }}
|
||||
interval: {{ .Values.serviceMonitor.interval }}
|
||||
{{- end }}
|
||||
{{- if .Values.serviceMonitor.scrapeTimeout }}
|
||||
scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }}
|
||||
{{- end }}
|
||||
{{- with .Values.serviceMonitor.extraEndpointParameters }}
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .Values.serviceMonitor.extraParameters }}
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "etcd.selectorLabels" . | nindent 6 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,280 @@
|
||||
{{- $fullname := include "etcd.fullname" . }}
|
||||
{{- $etcdInternalServiceName := printf "%s-internal" $fullname }}
|
||||
{{- $protocol := (or .Values.settings.https.enabled .Values.settings.https.autoTls) | ternary "https" "http" }}
|
||||
{{- $servicefqdn := printf "%s.%s.svc.%s" $etcdInternalServiceName .Release.Namespace .Values.clusterDomain }}
|
||||
{{- $createPvc := and (empty .Values.storage.persistentVolumeClaimName) (.Values.storage.requestedSize) }}
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ $fullname }}
|
||||
labels:
|
||||
{{- include "etcd.labels" . | nindent 4 }}
|
||||
{{- with .Values.customLabels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.customAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicas }}
|
||||
{{- if .Values.revisionHistoryLimit }}
|
||||
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
|
||||
{{- end }}
|
||||
serviceName: {{ $fullname }}-internal
|
||||
podManagementPolicy: {{ .Values.podManagementPolicy }}
|
||||
updateStrategy:
|
||||
type: {{ .Values.updateStrategyType }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "etcd.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/etcdconfig: {{ include (print $.Template.BasePath "/etcdconfig.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "etcd.selectorLabels" . | nindent 8 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.priorityClassName }}
|
||||
priorityClassName: {{ .Values.priorityClassName }}
|
||||
{{- end }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "etcd.serviceAccountName" . }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
initContainers:
|
||||
- name: {{ .Chart.Name }}-init
|
||||
{{- with .Values.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
image: "{{ .Values.initImage.registry }}/{{ .Values.initImage.repository }}:{{ .Values.initImage.tag }}"
|
||||
imagePullPolicy: {{ .Values.initImage.pullPolicy }}
|
||||
volumeMounts:
|
||||
- name: {{ .Values.storage.volumeName }}
|
||||
mountPath: /data
|
||||
command: ["/bin/sh"]
|
||||
args: ["-c", "mkdir -p /data/etcd && chmod 700 /data/etcd"]
|
||||
{{- with .Values.extraInitContainers }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: client
|
||||
containerPort: 2379
|
||||
- name: peer
|
||||
containerPort: 2380
|
||||
{{- if .Values.serviceMonitor.enabled }}
|
||||
- name: prometheus
|
||||
containerPort: 12379
|
||||
{{- end }}
|
||||
{{- if .Values.customStartupProbe }}
|
||||
startupProbe:
|
||||
{{- toYaml .Values.customStartupProbe | nindent 12 }}
|
||||
{{- else }}
|
||||
{{- if .Values.startupProbe.enabled }}
|
||||
startupProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/local/bin/etcdctl
|
||||
- endpoint
|
||||
- health
|
||||
{{- if (or .Values.settings.https.enabled .Values.settings.https.autoTls) }}
|
||||
- --insecure-skip-tls-verify=true
|
||||
- --insecure-transport=false
|
||||
{{- end }}
|
||||
{{- with .Values.startupProbe }}
|
||||
initialDelaySeconds: {{ .initialDelaySeconds }}
|
||||
timeoutSeconds: {{ .timeoutSeconds }}
|
||||
failureThreshold: {{ .failureThreshold }}
|
||||
successThreshold: {{ .successThreshold }}
|
||||
periodSeconds: {{ .periodSeconds }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.customLivenessProbe }}
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.customLivenessProbe | nindent 12 }}
|
||||
{{- else }}
|
||||
{{- if .Values.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/local/bin/etcdctl
|
||||
- endpoint
|
||||
- health
|
||||
{{- if (or .Values.settings.https.enabled .Values.settings.https.autoTls) }}
|
||||
- --insecure-skip-tls-verify=true
|
||||
- --insecure-transport=false
|
||||
{{- end }}
|
||||
{{- with .Values.livenessProbe }}
|
||||
initialDelaySeconds: {{ .initialDelaySeconds }}
|
||||
timeoutSeconds: {{ .timeoutSeconds }}
|
||||
failureThreshold: {{ .failureThreshold }}
|
||||
successThreshold: {{ .successThreshold }}
|
||||
periodSeconds: {{ .periodSeconds }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.customReadinessProbe }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.customReadinessProbe | nindent 12 }}
|
||||
{{- else }}
|
||||
{{- if .Values.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /usr/local/bin/etcdctl
|
||||
- endpoint
|
||||
- health
|
||||
{{- if (or .Values.settings.https.enabled .Values.settings.https.autoTls) }}
|
||||
- --insecure-skip-tls-verify=true
|
||||
- --insecure-transport=false
|
||||
{{- end }}
|
||||
{{- with .Values.readinessProbe }}
|
||||
initialDelaySeconds: {{ .initialDelaySeconds }}
|
||||
timeoutSeconds: {{ .timeoutSeconds }}
|
||||
failureThreshold: {{ .failureThreshold }}
|
||||
successThreshold: {{ .successThreshold }}
|
||||
periodSeconds: {{ .periodSeconds }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.args }}
|
||||
args:
|
||||
{{- range .Values.args }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
env:
|
||||
- name: NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: ETCD_NAME
|
||||
value: $(NODE_NAME)
|
||||
- name: ETCD_ADVERTISE_CLIENT_URLS
|
||||
value: "{{ $protocol }}://$(NODE_NAME).{{ $servicefqdn }}:2379"
|
||||
- name: ETCD_INITIAL_ADVERTISE_PEER_URLS
|
||||
value: "{{ $protocol }}://$(NODE_NAME).{{ $servicefqdn }}:2380"
|
||||
{{- with .Values.env }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ $fullname }}
|
||||
{{- range .Values.extraEnvSecrets }}
|
||||
- secretRef:
|
||||
name: {{ . }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: {{ .Values.storage.volumeName }}
|
||||
mountPath: /data
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- range $secret := .Values.extraSecrets }}
|
||||
- name: {{ $secret.name }}
|
||||
mountPath: {{ $secret.mountPath }}
|
||||
{{- end }}
|
||||
{{- range $config := .Values.extraConfigs }}
|
||||
- name: {{ $config.name }}
|
||||
mountPath: {{ $config.mountPath }}
|
||||
{{- end }}
|
||||
{{- range $storage := .Values.extraStorage }}
|
||||
- name: {{ $storage.name }}
|
||||
mountPath: {{ $storage.mountPath }}
|
||||
{{- end }}
|
||||
{{- with .Values.extraContainers }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.topologySpreadConstraints }}
|
||||
topologySpreadConstraints:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
{{- range $secret := .Values.extraSecrets }}
|
||||
- name: {{ $secret.name }}
|
||||
secret:
|
||||
secretName: {{ $secret.name }}
|
||||
defaultMode: {{ $secret.defaultMode | default 0440 }}
|
||||
{{- end }}
|
||||
{{- range $config := .Values.extraConfigs }}
|
||||
- name: {{ $config.name }}
|
||||
configMap:
|
||||
name: {{ $config.name }}
|
||||
defaultMode: {{ $config.defaultMode | default 0440 }}
|
||||
{{- end }}
|
||||
{{- range $storage := .Values.extraStorage }}
|
||||
- name: {{ $storage.name }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ $storage.pvcName }}
|
||||
{{- end }}
|
||||
{{- with .Values.storage }}
|
||||
{{- if not $createPvc }}
|
||||
- name: {{ .volumeName }}
|
||||
{{- if .persistentVolumeClaimName }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .persistentVolumeClaimName }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
volumeClaimTemplates:
|
||||
- apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ .volumeName }}
|
||||
{{- with .labels }}
|
||||
labels:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .accessModes }}
|
||||
accessModes:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- if .className }}
|
||||
storageClassName: {{ .className }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .requestedSize }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,273 @@
|
||||
# Default values for Etcd deployment
|
||||
|
||||
## Etcd container image
|
||||
image:
|
||||
registry: "quay.io/coreos"
|
||||
repository: "etcd"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: ""
|
||||
|
||||
# Default Init container image
|
||||
initImage:
|
||||
registry: "docker.io"
|
||||
repository: "busybox"
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "stable"
|
||||
|
||||
## Pull secrets and name override options
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
## Additional labels for Deployment or StatefulSet
|
||||
customLabels: {}
|
||||
|
||||
## Additional annotations for Deployment or StatefulSet
|
||||
customAnnotations: {}
|
||||
|
||||
## Number of etcd replicas in the cluster
|
||||
## Due to the nature of etcd cluster initialization this value must be set before deploying the cluster
|
||||
## Automatic scaling or manually scaling the etcd cluster after first deployment is not supported
|
||||
replicas: 1
|
||||
|
||||
## Optional service account
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: false
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
# Specifies whether a service account token should be automatically mounted
|
||||
automountServiceAccountToken: true
|
||||
|
||||
## Additional pod annotations
|
||||
podAnnotations: {}
|
||||
|
||||
## Additional pod labels
|
||||
podLabels: {}
|
||||
|
||||
## Pod management policy
|
||||
podManagementPolicy: Parallel
|
||||
|
||||
## Pod update strategy
|
||||
updateStrategyType: RollingUpdate
|
||||
|
||||
## Pod security context uses file system group 999 (postgres)
|
||||
podSecurityContext:
|
||||
fsGroup: 999
|
||||
supplementalGroups:
|
||||
- 999
|
||||
|
||||
## Default security options to run PostgreSQL as non-root (postgres user), read only container without privilege escalation
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsGroup: 999
|
||||
runAsUser: 999
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
## Etcd service ports (default: Client port 2379, Peer port 2380)
|
||||
service:
|
||||
type: ClusterIP
|
||||
## Client service port
|
||||
client:
|
||||
port: 2379
|
||||
## The node port (only relevant for type LoadBalancer or NodePort)
|
||||
nodePort:
|
||||
## Peer service port
|
||||
peer:
|
||||
port: 2380
|
||||
## The node port (only relevant for type LoadBalancer or NodePort)
|
||||
nodePort:
|
||||
## Prometheus service port
|
||||
prometheus:
|
||||
port: 12379
|
||||
## The node port (only relevant for type LoadBalancer or NodePort)
|
||||
nodePort:
|
||||
## The cluster ip address (only relevant for type LoadBalancer or NodePort)
|
||||
clusterIP:
|
||||
## The loadbalancer ip address (only relevant for type LoadBalancer)
|
||||
loadBalancerIP:
|
||||
## The list of IP CIDR ranges that are allowed to access the load balancer (only relevent for type LoadBalancer)
|
||||
loadBalancerSourceRanges: []
|
||||
## Annotations to add to the service
|
||||
annotations: {}
|
||||
## Labels to add to the service
|
||||
labels: {}
|
||||
|
||||
## Service monitor configuration for Prometheus metrics
|
||||
serviceMonitor:
|
||||
## Enable service monitor
|
||||
enabled: false
|
||||
## Additional labels for the service monitor object
|
||||
additionalLabels: {}
|
||||
## Annotations for the service monitor object
|
||||
annotations: {}
|
||||
## The scrape interval for prometheus
|
||||
# interval:
|
||||
## The scrape timeout value
|
||||
# scrapeTimeout:
|
||||
## Extra parameters rendered to the service monitor endpoint
|
||||
extraEndpointParameters: {}
|
||||
## Extra parameters rendered to the service monitor
|
||||
extraParameters: {}
|
||||
|
||||
resources: {}
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
## Pod priority class name
|
||||
priorityClassName: ""
|
||||
|
||||
## Additional node selector
|
||||
nodeSelector: {}
|
||||
|
||||
## Tolerations for pod assignment
|
||||
tolerations: []
|
||||
|
||||
## Affinity for pod assignment
|
||||
affinity: {}
|
||||
|
||||
## Topology spread constraints for pods
|
||||
topologySpreadConstraints: {}
|
||||
|
||||
## Maximum number of revisions maintained in revision history
|
||||
revisionHistoryLimit:
|
||||
|
||||
## Custom startup probe (overwrites default startup probe)
|
||||
customStartupProbe: {}
|
||||
|
||||
## Default startup check
|
||||
startupProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
successThreshold: 1
|
||||
periodSeconds: 10
|
||||
|
||||
## Custom liveness probe (overwrites default liveness probe)
|
||||
customLivenessProbe: {}
|
||||
|
||||
## Default health check
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
successThreshold: 1
|
||||
periodSeconds: 10
|
||||
|
||||
## Custom readiness probe (overwrites default readiness probe)
|
||||
customReadinessProbe: {}
|
||||
|
||||
## Default readiness probe
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
successThreshold: 1
|
||||
periodSeconds: 10
|
||||
|
||||
## Extra init containers
|
||||
extraInitContainers: []
|
||||
|
||||
## Extra containers for usage as sidecars
|
||||
extraContainers: []
|
||||
|
||||
## Additional environment variables
|
||||
env: []
|
||||
|
||||
## Arguments for the container entrypoint process
|
||||
args: []
|
||||
|
||||
## A list of existing secrets that will be mounted into the container as environment variables
|
||||
extraEnvSecrets: []
|
||||
|
||||
## A list of additional existing secrets that will be mounted into the container
|
||||
## The mounted files of the secrets can be used for advanced configuration (see settings.https.enabled)
|
||||
extraSecrets: []
|
||||
## Name of the existing K8s secret
|
||||
# - name:
|
||||
## Mount default mode (0440 if parameter is omitted)
|
||||
# defaultMode: 0440
|
||||
## Mount path where the secret should be mounted into the container (f.e. /mysecretfolder)
|
||||
# mountPath:
|
||||
|
||||
## A list of additional existing configMaps that will be mounted into the container
|
||||
extraConfigs: []
|
||||
## Name of the existing K8s configMap
|
||||
# - name:
|
||||
## Mount default mode (0440 if parameter is omitted)
|
||||
# defaultMode: 0440
|
||||
## Mount path where the configMap should be mounted into the container (f.e. /mysecretfolder)
|
||||
# mountPath:
|
||||
|
||||
## Default Kubernetes cluster domain
|
||||
clusterDomain: cluster.local
|
||||
|
||||
## Etcd specific settings
|
||||
settings:
|
||||
## Unique cluser token
|
||||
clusterToken: "etcd-cluster-0"
|
||||
## Configure secure transport
|
||||
## Certificates must be mounted into the container using `extraSecrets:` or generated automatically using autoTls: true
|
||||
## Other tls options have to be added manually using environment variables or args: (see https://etcd.io/docs/v3.5/op-guide/clustering/#tls and https://etcd.io/docs/v3.5/op-guide/configuration/)
|
||||
https:
|
||||
## Enable HTTPS
|
||||
enabled: false
|
||||
## Automatic TLS mode of etcd (TLS certs. created automaically)
|
||||
autoTls: false
|
||||
|
||||
## Delay after termination request to give etcd process time for graceful shutdown
|
||||
shutdownDelay: 3
|
||||
|
||||
## Storage parameters
|
||||
storage:
|
||||
## Set persistentVolumenClaimName to reference an existing PVC
|
||||
persistentVolumeClaimName:
|
||||
|
||||
## Internal volume name and prefix of a created PVC
|
||||
volumeName: "etcd-data"
|
||||
|
||||
## Alternative set requestedSize to define a size for a dynmaically created PVC
|
||||
requestedSize:
|
||||
|
||||
## the storage class name
|
||||
className:
|
||||
|
||||
## Default access mode (ReadWriteOnce)
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
|
||||
## Additional storage annotations
|
||||
annotations: {}
|
||||
|
||||
## Additional storage labels
|
||||
labels: {}
|
||||
|
||||
## Mount existing extra PVC
|
||||
extraStorage: {}
|
||||
## Internal volume name
|
||||
# - name:
|
||||
## Container mount path
|
||||
# mountPath:
|
||||
## Name of existing PVC
|
||||
# pvcName:
|
||||
|
||||
## Network policies
|
||||
networkPolicy: {}
|
||||
## Ingress and Egress policies
|
||||
# ingress: {}
|
||||
# egress: {}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# deploy-test-cnpg-cluster.sh
|
||||
# cnpg-postgresql 자체 빌드 이미지가 실제로 CNPG operator 위에서 동작하는지
|
||||
# Kubernetes 배포 수준에서 확인한다. build-image.yml 이 카탈로그 PR 을 연 뒤,
|
||||
# 병합 전에 사람이 로컬 kubeconfig 로 이 스크립트를 돌려 확인한다.
|
||||
#
|
||||
# CVE 게이트(scripts/pipeline/cve-gate.py)는 "취약점이 없는가" 만 본다. 이 스크립트는
|
||||
# "0건이어도 실제로 뜨는가" 를 본다 — .claude/deploy-test-procedure.md 의 원칙
|
||||
# ("Zero CVE 로는 부족하다. 동작하지 않는 이미지는 카탈로그에 올릴 수 없다").
|
||||
#
|
||||
# Operator(cloudnative-pg)는 상시 컴포넌트로 취급한다 — 없으면 1회 설치하고
|
||||
# 있으면 그대로 재사용한다. 매번 새로 설치하지 않는 이유:
|
||||
# cloudnative-pg 차트의 ValidatingWebhookConfiguration/MutatingWebhookConfiguration
|
||||
# 이름이 릴리스와 무관하게 클러스터 전역으로 하드코딩돼 있어(cnpg-*-webhook-configuration,
|
||||
# manifests/helm/cloudnative-pg/0.29.0/templates/validatingwebhookconfiguration.yaml)
|
||||
# 같은 클러스터에 operator 를 두 번 설치할 수 없다.
|
||||
#
|
||||
# Postgres 앱(cnpg-cluster)은 매 실행 고정 이름으로 지우고 새로 만든다 — 반복 실행
|
||||
# 가능해야 "배포 검증"이 의미가 있다. 정리는 성공/실패 무관 항상 수행한다(trap).
|
||||
#
|
||||
# 배포는 각 차트의 **기본값(values.yaml)** 만 쓴다. custom-values.yaml 은 쓰지 않는다 —
|
||||
# 운영 설정(백업·확장·사이징)까지 검증 대상에 섞이면 "이 이미지가 도는가" 라는 질문과
|
||||
# 무관한 변수가 늘어난다. 유일한 override 는 검증 대상인 이미지 자체뿐이다.
|
||||
#
|
||||
# 사용:
|
||||
# IMAGE_NAME=docker.io/wbsong111/cnpg-postgresql:<tag> bash scripts/deploy-test/deploy-test-cnpg-cluster.sh <OUT_DIR>
|
||||
#
|
||||
# 환경변수:
|
||||
# IMAGE_NAME (필수) 검증할 postgresql 이미지 전체 경로:태그
|
||||
# OPERATOR_CHART_DIR 기본 manifests/helm/cloudnative-pg/0.29.0
|
||||
# APP_CHART_DIR 기본 manifests/helm/cnpg-cluster/1.0.0
|
||||
# OPERATOR_NAMESPACE 기본 cnpg-system (상시 — 이 스크립트가 지우지 않는다)
|
||||
# OPERATOR_RELEASE 기본 cnpg
|
||||
# TEST_NAMESPACE 기본 pg-test-build (매 실행 재생성)
|
||||
# APP_RELEASE 기본 pg-build
|
||||
# WAIT_TIMEOUT 기본 600 (초) — Cluster healthy 대기 한도
|
||||
#
|
||||
# 산출물 (OUT_DIR):
|
||||
# deploy-test.log 전체 실행 로그
|
||||
#
|
||||
# 종료 코드: 0 = 기능 확인(psql SELECT 1) 통과, 그 외 = 실패.
|
||||
# 정리는 종료 코드와 무관하게 항상 수행된다.
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
OUT_DIR="${1:?사용법: deploy-test-cnpg-cluster.sh <OUT_DIR>}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
LOG="$OUT_DIR/deploy-test.log"
|
||||
: > "$LOG"
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME:?IMAGE_NAME 환경변수가 필요하다 (검증할 postgresql 이미지 전체 경로:태그)}"
|
||||
OPERATOR_CHART_DIR="${OPERATOR_CHART_DIR:-$REPO_ROOT/manifests/helm/cloudnative-pg/0.29.0}"
|
||||
APP_CHART_DIR="${APP_CHART_DIR:-$REPO_ROOT/manifests/helm/cnpg-cluster/1.0.0}"
|
||||
OPERATOR_NAMESPACE="${OPERATOR_NAMESPACE:-cnpg-system}"
|
||||
OPERATOR_RELEASE="${OPERATOR_RELEASE:-cnpg}"
|
||||
TEST_NAMESPACE="${TEST_NAMESPACE:-pg-test-build}"
|
||||
APP_RELEASE="${APP_RELEASE:-pg-build}"
|
||||
WAIT_TIMEOUT="${WAIT_TIMEOUT:-600}"
|
||||
|
||||
log() { echo "$(date -u +%H:%M:%S) $*" | tee -a "$LOG"; }
|
||||
|
||||
log "== 사전 확인 =="
|
||||
for t in kubectl helm python3; do
|
||||
command -v "$t" >/dev/null || { log "::error:: $t 없음"; exit 2; }
|
||||
done
|
||||
[ -d "$OPERATOR_CHART_DIR" ] || { log "::error:: operator 차트 없음: $OPERATOR_CHART_DIR"; exit 2; }
|
||||
[ -d "$APP_CHART_DIR" ] || { log "::error:: app 차트 없음: $APP_CHART_DIR"; exit 2; }
|
||||
if ! kubectl get ns >/dev/null 2>>"$LOG"; then
|
||||
log "::error:: 클러스터 연결 실패 — kubeconfig/네트워크 확인"
|
||||
log "::error:: (dev 클러스터는 간헐적 타임아웃이 있을 수 있다 — 재시도해볼 것)"
|
||||
exit 2
|
||||
fi
|
||||
log " OK (kubectl context: $(kubectl config current-context 2>/dev/null))"
|
||||
|
||||
# --- operator: 상시 컴포넌트. 없으면 1회 설치, 있으면 재사용 --------------------
|
||||
ensure_operator() {
|
||||
if helm status "$OPERATOR_RELEASE" -n "$OPERATOR_NAMESPACE" >/dev/null 2>>"$LOG"; then
|
||||
log "== operator: 기존 릴리스 재사용 ($OPERATOR_RELEASE/$OPERATOR_NAMESPACE) =="
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 우리가 모르는 operator 가 이미 떠 있으면(웹훅 이름이 클러스터 전역 고정이라) 충돌한다.
|
||||
# 자동으로 지우지 않고 사람이 정리하게 한다.
|
||||
if kubectl get validatingwebhookconfiguration cnpg-validating-webhook-configuration >/dev/null 2>&1; then
|
||||
owner_ns="$(kubectl get validatingwebhookconfiguration cnpg-validating-webhook-configuration \
|
||||
-o jsonpath='{.metadata.annotations.meta\.helm\.sh/release-namespace}' 2>/dev/null)"
|
||||
owner_rel="$(kubectl get validatingwebhookconfiguration cnpg-validating-webhook-configuration \
|
||||
-o jsonpath='{.metadata.annotations.meta\.helm\.sh/release-name}' 2>/dev/null)"
|
||||
if [ "$owner_ns" != "$OPERATOR_NAMESPACE" ] || [ "$owner_rel" != "$OPERATOR_RELEASE" ]; then
|
||||
log "::error:: 다른 CNPG operator 가 이미 클러스터에 있다 (release=$owner_rel ns=$owner_ns)."
|
||||
log "::error:: cloudnative-pg 웹훅 설정은 클러스터 전역 고정 이름이라 두 번째 설치가 불가능하다."
|
||||
log "::error:: 그 operator 를 먼저 정리하거나 OPERATOR_NAMESPACE/OPERATOR_RELEASE 를 그 값에 맞춰라."
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
|
||||
log "== operator: 신규 설치 ($OPERATOR_RELEASE/$OPERATOR_NAMESPACE, 차트 기본값) =="
|
||||
helm install "$OPERATOR_RELEASE" "$OPERATOR_CHART_DIR" \
|
||||
-n "$OPERATOR_NAMESPACE" --create-namespace --wait --timeout "${WAIT_TIMEOUT}s" \
|
||||
>>"$LOG" 2>&1 || { log "::error:: operator 설치 실패 — $LOG 확인"; exit 1; }
|
||||
log " OK"
|
||||
}
|
||||
|
||||
# --- 테스트 네임스페이스 정리 (선-정리·후-정리 공용) --------------------------
|
||||
cleanup_test_ns() {
|
||||
kubectl get ns "$TEST_NAMESPACE" >/dev/null 2>&1 || return 0
|
||||
|
||||
log "== 정리: $TEST_NAMESPACE =="
|
||||
if helm status "$APP_RELEASE" -n "$TEST_NAMESPACE" >/dev/null 2>&1; then
|
||||
helm uninstall "$APP_RELEASE" -n "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
fi
|
||||
kubectl -n "$TEST_NAMESPACE" delete pvc --all --wait --timeout=120s >>"$LOG" 2>&1
|
||||
|
||||
# storageClass longhorn 은 reclaimPolicy Retain 이라 PVC 삭제만으로는 볼륨이 안
|
||||
# 지워진다(.claude/pitfalls.md 의 기존 함정). 이 네임스페이스가 남긴 PV 만 골라 지운다.
|
||||
local pv
|
||||
for pv in $(kubectl get pv -o json 2>/dev/null | python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
for p in d.get('items', []):
|
||||
cr = p['spec'].get('claimRef') or {}
|
||||
if cr.get('namespace') == '$TEST_NAMESPACE':
|
||||
print(p['metadata']['name'])
|
||||
" 2>/dev/null); do
|
||||
kubectl delete pv "$pv" --ignore-not-found >>"$LOG" 2>&1 \
|
||||
|| log "::warning:: PV $pv 삭제 실패 — 수동 확인 필요"
|
||||
done
|
||||
|
||||
kubectl delete ns "$TEST_NAMESPACE" --wait --timeout=120s >>"$LOG" 2>&1
|
||||
log " 완료"
|
||||
}
|
||||
|
||||
RESULT=1
|
||||
trap 'cleanup_test_ns' EXIT
|
||||
|
||||
ensure_operator
|
||||
cleanup_test_ns # 직전 실행이 비정상 종료했을 경우를 대비한 선-정리
|
||||
|
||||
log "== app: 설치 ($APP_RELEASE/$TEST_NAMESPACE, 기본값 + imageName override) =="
|
||||
kubectl create ns "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
if ! helm install "$APP_RELEASE" "$APP_CHART_DIR" -n "$TEST_NAMESPACE" \
|
||||
--set postgresql.imageName="$IMAGE_NAME" --wait --timeout "${WAIT_TIMEOUT}s" \
|
||||
>>"$LOG" 2>&1; then
|
||||
log "::error:: app 차트 설치 실패 — $LOG 확인"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== Cluster healthy 대기 (최대 ${WAIT_TIMEOUT}s) =="
|
||||
instances="$(kubectl -n "$TEST_NAMESPACE" get clusters.postgresql.cnpg.io "$APP_RELEASE" \
|
||||
-o jsonpath='{.spec.instances}' 2>/dev/null)"
|
||||
deadline=$((SECONDS + WAIT_TIMEOUT))
|
||||
phase=""
|
||||
ready=0
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
phase="$(kubectl -n "$TEST_NAMESPACE" get clusters.postgresql.cnpg.io "$APP_RELEASE" \
|
||||
-o jsonpath='{.status.phase}' 2>/dev/null)"
|
||||
ready="$(kubectl -n "$TEST_NAMESPACE" get clusters.postgresql.cnpg.io "$APP_RELEASE" \
|
||||
-o jsonpath='{.status.readyInstances}' 2>/dev/null)"
|
||||
log " phase=$phase ready=${ready:-0}/$instances"
|
||||
[ "$phase" = "Cluster in healthy state" ] && [ "${ready:-0}" = "$instances" ] && break
|
||||
sleep 10
|
||||
done
|
||||
if [ "$phase" != "Cluster in healthy state" ] || [ "${ready:-0}" != "$instances" ]; then
|
||||
log "::error:: 타임아웃 — Cluster 가 healthy 상태에 도달하지 못함"
|
||||
kubectl -n "$TEST_NAMESPACE" get pods -l "cnpg.io/cluster=$APP_RELEASE" >>"$LOG" 2>&1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== 기능 확인: psql SELECT 1 (appdb) =="
|
||||
# 항상 clusters.postgresql.cnpg.io FQN 을 쓴다 — bare `cluster` 는 Rancher/CAPI 의
|
||||
# 동명 CRD 와 충돌한다(.claude/pitfalls.md).
|
||||
primary_pod="$(kubectl -n "$TEST_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=$APP_RELEASE,cnpg.io/instanceRole=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)"
|
||||
if [ -z "$primary_pod" ]; then
|
||||
log "::error:: primary pod 를 찾지 못함"
|
||||
exit 1
|
||||
fi
|
||||
log " primary=$primary_pod"
|
||||
|
||||
version="$(kubectl -n "$TEST_NAMESPACE" exec "$primary_pod" -c postgres -- \
|
||||
psql -U postgres -d appdb -Atc "SELECT version();" 2>>"$LOG")"
|
||||
log " version: $version"
|
||||
|
||||
answer="$(kubectl -n "$TEST_NAMESPACE" exec "$primary_pod" -c postgres -- \
|
||||
psql -U postgres -d appdb -Atc "SELECT 1;" 2>>"$LOG")"
|
||||
if [ "$answer" = "1" ]; then
|
||||
log "== 결과: PASS =="
|
||||
RESULT=0
|
||||
else
|
||||
log "::error:: psql 응답 이상 (got: '$answer')"
|
||||
RESULT=1
|
||||
fi
|
||||
|
||||
exit "$RESULT"
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# deploy-test-etcd.sh
|
||||
# etcd 차트가 지정한 이미지로 실제 Kubernetes 배포 수준에서 정상 동작하는지
|
||||
# 확인한다. CVE 게이트(scripts/pipeline/cve-gate.py)는 "취약점이 없는가"만
|
||||
# 본다. 이 스크립트는 "0건이어도 실제로 뜨는가"를 본다 —
|
||||
# .claude/deploy-test-procedure.md 의 원칙("Zero CVE 로는 부족하다. 동작하지
|
||||
# 않는 이미지는 카탈로그에 올릴 수 없다").
|
||||
#
|
||||
# cnpg 배포 테스트(deploy-test-cnpg-cluster.sh)와 달리 etcd 차트는 오퍼레이터가
|
||||
# 없는 단일 차트다 — 상시 컴포넌트 설치 단계가 없고, 매 실행 테스트 네임스페이스에
|
||||
# 차트를 설치하고 끝나면 지운다. 정리는 성공/실패 무관 항상 수행한다(trap).
|
||||
#
|
||||
# 배포는 차트 **기본값(values.yaml)** 만 쓴다. custom-values.yaml 은 쓰지 않는다 —
|
||||
# 유일한 override 는 검증 대상 이미지와 replica 수뿐이다(cnpg 원칙과 동일).
|
||||
#
|
||||
# 사용:
|
||||
# IMAGE_NAME=quay.io/coreos/etcd:v3.7.1 bash scripts/deploy-test/deploy-test-etcd.sh <OUT_DIR>
|
||||
#
|
||||
# 환경변수:
|
||||
# IMAGE_NAME (필수) 검증할 etcd 이미지 전체 경로:태그 (registry/repository:tag)
|
||||
# INIT_IMAGE init 컨테이너 이미지 전체 경로:태그 기본 docker.io/busybox:1.38.0-uclibc
|
||||
# CHART_DIR 기본 manifests/helm/etcd/1.1.12
|
||||
# TEST_NAMESPACE 기본 etcd-test-build (매 실행 재생성)
|
||||
# RELEASE 기본 etcd-build
|
||||
# REPLICAS 기본 3 (쿼럼 확인을 위해 홀수 권장)
|
||||
# WAIT_TIMEOUT 기본 300 (초) — StatefulSet 롤아웃 대기 한도
|
||||
#
|
||||
# 산출물 (OUT_DIR):
|
||||
# deploy-test.log 전체 실행 로그
|
||||
#
|
||||
# 종료 코드: 0 = 기능 확인(quorum health + put/get) 통과, 그 외 = 실패.
|
||||
# 정리는 종료 코드와 무관하게 항상 수행된다.
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
OUT_DIR="${1:?사용법: deploy-test-etcd.sh <OUT_DIR>}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
LOG="$OUT_DIR/deploy-test.log"
|
||||
: > "$LOG"
|
||||
|
||||
IMAGE_NAME="${IMAGE_NAME:?IMAGE_NAME 환경변수가 필요하다 (검증할 etcd 이미지 전체 경로:태그)}"
|
||||
INIT_IMAGE="${INIT_IMAGE:-docker.io/busybox:1.38.0-uclibc}"
|
||||
CHART_DIR="${CHART_DIR:-$REPO_ROOT/manifests/helm/etcd/1.1.12}"
|
||||
TEST_NAMESPACE="${TEST_NAMESPACE:-etcd-test-build}"
|
||||
RELEASE="${RELEASE:-etcd-build}"
|
||||
REPLICAS="${REPLICAS:-3}"
|
||||
WAIT_TIMEOUT="${WAIT_TIMEOUT:-300}"
|
||||
|
||||
log() { echo "$(date -u +%H:%M:%S) $*" | tee -a "$LOG"; }
|
||||
|
||||
# IMAGE_NAME="registry[/path]/repository:tag" → registry / repository / tag 분해.
|
||||
# 차트가 imageName 단일 필드가 아니라 3분할 필드(image.registry/repository/tag)를
|
||||
# 쓰기 때문에 필요하다.
|
||||
split_image() {
|
||||
local full="$1" tag repo_full registry repository
|
||||
tag="${full##*:}"
|
||||
repo_full="${full%:*}"
|
||||
registry="${repo_full%/*}"
|
||||
repository="${repo_full##*/}"
|
||||
echo "$registry" "$repository" "$tag"
|
||||
}
|
||||
|
||||
read -r IMAGE_REGISTRY IMAGE_REPOSITORY IMAGE_TAG <<< "$(split_image "$IMAGE_NAME")"
|
||||
read -r INIT_REGISTRY INIT_REPOSITORY INIT_TAG <<< "$(split_image "$INIT_IMAGE")"
|
||||
|
||||
log "== 사전 확인 =="
|
||||
for t in kubectl helm python3; do
|
||||
command -v "$t" >/dev/null || { log "::error:: $t 없음"; exit 2; }
|
||||
done
|
||||
[ -d "$CHART_DIR" ] || { log "::error:: 차트 없음: $CHART_DIR"; exit 2; }
|
||||
if ! kubectl get ns >/dev/null 2>>"$LOG"; then
|
||||
log "::error:: 클러스터 연결 실패 — kubeconfig/네트워크 확인"
|
||||
exit 2
|
||||
fi
|
||||
log " OK (kubectl context: $(kubectl config current-context 2>/dev/null))"
|
||||
log " image=$IMAGE_REGISTRY/$IMAGE_REPOSITORY:$IMAGE_TAG"
|
||||
log " initImage=$INIT_REGISTRY/$INIT_REPOSITORY:$INIT_TAG"
|
||||
log " replicas=$REPLICAS"
|
||||
|
||||
# --- 테스트 네임스페이스 정리 (선-정리·후-정리 공용) --------------------------
|
||||
cleanup_test_ns() {
|
||||
kubectl get ns "$TEST_NAMESPACE" >/dev/null 2>&1 || return 0
|
||||
|
||||
log "== 정리: $TEST_NAMESPACE =="
|
||||
if helm status "$RELEASE" -n "$TEST_NAMESPACE" >/dev/null 2>&1; then
|
||||
helm uninstall "$RELEASE" -n "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
fi
|
||||
kubectl -n "$TEST_NAMESPACE" delete pvc --all --wait --timeout=120s >>"$LOG" 2>&1
|
||||
|
||||
# storageClass longhorn 은 reclaimPolicy Retain 이라 PVC 삭제만으로는 볼륨이 안
|
||||
# 지워진다(.claude/pitfalls.md). 이 네임스페이스가 남긴 PV 만 골라 지운다.
|
||||
local pv
|
||||
for pv in $(kubectl get pv -o json 2>/dev/null | python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
for p in d.get('items', []):
|
||||
cr = p['spec'].get('claimRef') or {}
|
||||
if cr.get('namespace') == '$TEST_NAMESPACE':
|
||||
print(p['metadata']['name'])
|
||||
" 2>/dev/null); do
|
||||
kubectl delete pv "$pv" --ignore-not-found >>"$LOG" 2>&1 \
|
||||
|| log "::warning:: PV $pv 삭제 실패 — 수동 확인 필요"
|
||||
done
|
||||
|
||||
kubectl delete ns "$TEST_NAMESPACE" --wait --timeout=120s >>"$LOG" 2>&1
|
||||
log " 완료"
|
||||
}
|
||||
|
||||
RESULT=1
|
||||
trap 'cleanup_test_ns' EXIT
|
||||
|
||||
cleanup_test_ns # 직전 실행이 비정상 종료했을 경우를 대비한 선-정리
|
||||
|
||||
log "== etcd: 설치 ($RELEASE/$TEST_NAMESPACE, 차트 기본값 + 이미지·replicas override) =="
|
||||
kubectl create ns "$TEST_NAMESPACE" >>"$LOG" 2>&1
|
||||
if ! helm install "$RELEASE" "$CHART_DIR" -n "$TEST_NAMESPACE" \
|
||||
--set image.registry="$IMAGE_REGISTRY" \
|
||||
--set image.repository="$IMAGE_REPOSITORY" \
|
||||
--set image.tag="$IMAGE_TAG" \
|
||||
--set initImage.registry="$INIT_REGISTRY" \
|
||||
--set initImage.repository="$INIT_REPOSITORY" \
|
||||
--set initImage.tag="$INIT_TAG" \
|
||||
--set replicas="$REPLICAS" \
|
||||
--wait --timeout "${WAIT_TIMEOUT}s" \
|
||||
>>"$LOG" 2>&1; then
|
||||
log "::error:: 차트 설치 실패 — $LOG 확인"
|
||||
kubectl -n "$TEST_NAMESPACE" get pods >>"$LOG" 2>&1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STS_NAME="$(kubectl -n "$TEST_NAMESPACE" get statefulset -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)"
|
||||
if [ -z "$STS_NAME" ]; then
|
||||
log "::error:: StatefulSet 을 찾지 못함"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== StatefulSet 롤아웃 대기 (최대 ${WAIT_TIMEOUT}s): $STS_NAME =="
|
||||
if ! kubectl -n "$TEST_NAMESPACE" rollout status "statefulset/$STS_NAME" --timeout="${WAIT_TIMEOUT}s" >>"$LOG" 2>&1; then
|
||||
log "::error:: 타임아웃 — StatefulSet 이 준비되지 못함"
|
||||
kubectl -n "$TEST_NAMESPACE" get pods -o wide >>"$LOG" 2>&1
|
||||
kubectl -n "$TEST_NAMESPACE" describe pods >>"$LOG" 2>&1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pod0="${STS_NAME}-0"
|
||||
log "== 컨테이너 실행 계정 확인 (non-root uid 999 기대) =="
|
||||
# quay.io/coreos/etcd 는 etcd/etcdctl 바이너리만 든 최소 이미지라 id/cat/sh 조차
|
||||
# 없다(실측: `id` exec 시 "executable file not found in \$PATH"). uid 는 직접 확인할
|
||||
# 수 없으므로, 아래 quorum health·put/get 이 성공한다는 것 자체를 간접 증거로 삼는다
|
||||
# — securityContext(runAsUser 999, RO rootfs)가 거부됐다면 kubelet 이 애초에
|
||||
# 컨테이너를 못 띄웠거나 /data 쓰기가 실패했을 것이다.
|
||||
if uid="$(kubectl -n "$TEST_NAMESPACE" exec "$pod0" -- id -u 2>>"$LOG")" && [ -n "$uid" ]; then
|
||||
log " pod0 uid=$uid"
|
||||
[ "$uid" = "999" ] || log "::warning:: 기대한 uid(999) 와 다름 — CUSTOM-README.md 의 securityContext 가정 재검토 필요"
|
||||
else
|
||||
log " id 바이너리 없음(최소 이미지) — 직접 확인 불가. 아래 quorum health/put-get 성공 여부로 간접 확인"
|
||||
fi
|
||||
|
||||
log "== 기능 확인: quorum health =="
|
||||
health_out="$(kubectl -n "$TEST_NAMESPACE" exec "$pod0" -- etcdctl endpoint health --cluster 2>>"$LOG")"
|
||||
log " $health_out"
|
||||
healthy_count="$(echo "$health_out" | grep -c "is healthy" || true)"
|
||||
if [ "$healthy_count" != "$REPLICAS" ]; then
|
||||
log "::error:: quorum health 이상 (healthy=$healthy_count / replicas=$REPLICAS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "== 기능 확인: put/get 왕복 =="
|
||||
if ! kubectl -n "$TEST_NAMESPACE" exec "$pod0" -- etcdctl put smoke-test ok >>"$LOG" 2>&1; then
|
||||
log "::error:: etcdctl put 실패"
|
||||
exit 1
|
||||
fi
|
||||
answer="$(kubectl -n "$TEST_NAMESPACE" exec "$pod0" -- etcdctl get smoke-test --print-value-only 2>>"$LOG")"
|
||||
log " get smoke-test => '$answer'"
|
||||
if [ "$answer" = "ok" ]; then
|
||||
log "== 결과: PASS =="
|
||||
RESULT=0
|
||||
else
|
||||
log "::error:: get 응답 이상 (got: '$answer')"
|
||||
RESULT=1
|
||||
fi
|
||||
|
||||
exit "$RESULT"
|
||||
Reference in New Issue
Block a user