Files
service-catalog/manifests/helm/cloudnative-pg/0.29.0/CUSTOM-README.md
T
wbsong111 6878b61efa 자체 빌드 이미지 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>
2026-08-03 12:03:37 +09:00

8.3 KiB

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 의 failurePolicyFail 이다. 오퍼레이터 Pod 가 없는 상태에서는 Cluster 리소스의 생성·수정·삭제가 모두 거부된다. 오퍼레이터를 제거할 때는 webhook 설정을 반드시 함께 삭제해야 한다 (아래 4. 제거 참고).
  • helm upgrade 는 CRD 를 갱신하지 않는다. Helm 의 CRD 처리 방식 때문에 버전 업그레이드 시 CRD 를 수동 apply 해야 한다.
  • Kubernetes 1.25+ 필요. 검증 환경은 RKE2 v1.34.1 이다.

2) 배포

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) 확인

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. 업그레이드

# 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 리소스를 삭제할 수 없게 된다.

# 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초, 데이터 정합성 유지를 확인했다.