"베이스 OS 교체"를 독립 레버로 두던 기존 표현을 없앤다 — 이 저장소의 베이스 교체 이력을 전부 찾아보면 예외 없이 hardened-containers 자체 빌드 커밋이다(argocd· keycloak·cnpg-postgresql/etcd·apisix). 다른 배포판의 backport를 실제로 쓰려면 그 배포판 위에 앱을 다시 얹어야 하므로 그 자체가 이미 자체 빌드다. 빌드 없이 끝나는 진짜 무료 치환은 태그 교체와, 관리가 끊긴 벤더 이미지(bitnamilegacy 등)를 활성 벤더의 기존 이미지로 바꾸는 것뿐이다. dip-catalog는 이제 이미지를 전혀 빌드하지 않는다(자체 빌드는 hardened-containers로 완전히 이관됨) — 이 사실을 CLAUDE.md·관련 skill에 명시한다. 유일한 예외인 apisix keycloak-authz는 빌드가 아니라 이미 private으로 빌드된 이미지의 태그 반영이라 원칙에 위배되지 않는다. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
17 KiB
DIP Catalog — 하네스 엔지니어링 가이드
프로젝트 개요
DIP Catalog는 세 개의 독립 서브시스템으로 구성된다.
| 서브시스템 | 설명 | 경로 |
|---|---|---|
| 정적 카탈로그 | 엔터프라이즈 Helm 차트 버전 보관소 (2026-08-14 기준 차트 59개 / 버전 디렉토리 70개 — Kafka, Airflow, MLflow, KServe, OpenMetadata, APISIX, VictoriaMetrics 등) | manifests/helm/ |
| 자동화 에이전트 | Helm 차트 신규 버전 감지 → Diff 분석 → 문서 생성 → GitHub PR 자동화 | agent/update_catalog/ |
| CVE/SBOM 게이트 | 카탈로그 이미지 SBOM·취약점 스캔 + 게이트 판정 (현재 warn-only) | scripts/pipeline/, scripts/build/, doc/sbom-pipeline.md |
카탈로그는 운영 클러스터 직접 변경과 무관하다. 모든 변경은 Git PR을 통해서만 이루어진다.
디렉토리 구조
dip-catalog/
├── manifests/
│ ├── helm/<chart>/<version>/ # Helm 차트 정적 카탈로그 (각 버전 독립 디렉토리)
│ ├── applicationset/ # ArgoCD ApplicationSet 매니페스트
│ └── kustomize/ # Kustomize 오버레이 (Kubeflow, Model Registry)
├── agent/
│ └── update_catalog/
│ ├── skills/ # 자동화 Skills (Python, 각 Skill 독립 실행 가능)
│ ├── scripts/run_flow.sh # 전체 파이프라인 로컬 실행 스크립트
│ └── docs/ # 아키텍처·설계 문서 (한국어)
│ ├── design/ # 컴포넌트별 상세 설계 (00~05)
│ ├── decisions/ # ADR (아키텍처 결정 기록)
│ └── status.md # 구현 현황
├── scripts/
│ ├── pipeline/ # SBOM 생성 + CVE 스캔 + 게이트 판정 (sbom.yml/cve-edge-post.yml 이 쓴다)
│ ├── build/ # 자체 빌드 이미지 축의 카탈로그 쪽 절반 — 발행 태그 반영
│ │ # (apply-published-tags.py). CVE 스캔은 sbom.yml 이 다른
│ │ # 카탈로그 이미지와 동일하게 하고, 빌드 자체는 hardened-containers 레포
│ └── deploy-test/ # 배포 검증 스크립트 + fixtures (helm/kubectl 실행 전담)
├── catalog/
│ └── image-map/<image>.env # 자체 빌드 이미지 → 차트·필드 매핑 (목록은 이 디렉토리가 단일 출처)
├── MEMORY.md # 현재 상태·미결 (작업 이어받을 때 여기서 시작 — 유지 규칙은 파일 안에)
└── doc/ # 차트 리소스 프로파일 + 스택 분류 + CVE/SBOM 파이프라인 + 운영 가이드
├── sbom-pipeline.md # SBOM 생성·스캔·게이트 메커니즘
├── cve-exceptions.json # 게이트 승인 예외 목록
├── catalog-stack-classification.md # 차트 스택 분류·우선순위(P0~P2)
├── define-chart-resources.md # 차트별 Small/Medium/Large 리소스 프로파일
├── decisions/ # ADR — 재측정으로 복원되지 않는 차트 선택 근거
│ # (이미지 자체 빌드 ADR은 hardened-containers 레포로 이관됨)
└── migrations/ # 레포 간 이관 핸드오프 문서
정적 카탈로그 작업
차트 디렉토리 구조
각 manifests/helm/<chart>/<version>/에 다음 파일이 있어야 한다.
| 파일 | 역할 |
|---|---|
Chart.yaml |
Helm 차트 메타데이터 |
values.yaml |
업스트림 기본값 |
custom-values.yaml |
PaaSup 전용 오버라이드 (Breaking Change 판단 기준) |
CUSTOM-README.md |
업그레이드 주의사항 (자동 생성 + 수동 추가 가능) |
BUILD-README.md |
차트 갱신 작업 가이드 (버전 정보 파싱에 사용됨 — 아래 주의) |
BUILD-README.md는 업스트림 원문이 아니라 레포가 직접 쓰는 한국어 갱신 가이드다.chart_version_detector가 이 파일에서helm repo add <repo> <url>한 줄을 정규식으로 뽑아 업스트림 레포를 정한다 — 이 줄이 없으면 신규 버전 자동 감지가 조용히 실패한다 (repo: null→latest_version: null). 그래서 "변경 금지"가 아니라 이 줄을 지우거나 형식을 바꾸지 않는 선에서 갱신 가능이 정확한 규정이다. 2026-08-19 argo-cd에서 실제로 이 줄이 없어 감지가 실패해 추가했다.
실제 카탈로그는 이 5개를 전부 갖추지 않은 디렉토리가 있다(2026-08-14 기준 70개 중
custom-values.yaml 65 · CUSTOM-README.md 66 · BUILD-README.md 61). 배포 파라미터화용
dip-values.yaml(36) · dip-questions.yaml(23) · dip-resources-quotas.yaml(27) 계열은
차트에 따라 선택적으로 둔다.
신규 차트 추가
manifests/helm/<chart>/<version>/ 디렉토리를 생성하고 위 5개 파일을 포함시킨다.
custom-values.yaml은 PaaSup 환경에 필요한 오버라이드만 작성한다. 기본값 전체를 복사하지 않는다.
리소스 프로파일
차트별 CPU/Memory/Storage 요구사항은 doc/define-chart-resources.md에 Small/Medium/Large 티어로 정의되어 있다. 신규 차트 추가 시 이 파일에도 리소스 정의를 추가한다.
자동화 Skills 작업
파이프라인 순서
chart_version_detector → chart_updater → helm_diff →
breaking_change_check → generate_upgrade_doc → update_docs_file → create_pr
각 Skill은 독립 실행 가능하다. JSON을 stdout으로 출력하며, 다음 Skill의 입력으로 전달된다.
로컬 실행
# 전체 파이프라인 실행 (필수 환경변수 오버라이드)
CATALOG_ROOT=/path/to/dip-catalog CHART=airflow \
bash agent/update_catalog/scripts/run_flow.sh
# 개별 Skill 실행 예시
python3 agent/update_catalog/skills/chart_version_detector/scripts/run.py \
--catalog-root /path/to/dip-catalog --chart airflow
python3 agent/update_catalog/skills/helm_diff/scripts/run.py \
--chart airflow --repo bitnami \
--chart-path /path/to/dip-catalog/manifests/helm/airflow/1.15.0 \
--from-version 1.15.0 --to-version 1.16.0
주요 환경변수
| 변수 | 설명 | 기본값 |
|---|---|---|
CATALOG_ROOT |
dip-catalog 레포 절대 경로 | 하드코딩된 로컬 경로 (반드시 오버라이드) |
CHART |
대상 차트명 | airflow |
USE_CLAUDE_CLI=1 |
LLM 호출 활성화 (breaking=true 시 상세 가이드 생성) | 미설정 시 템플릿 기반 생성 |
run_flow.sh의CATALOG_ROOT기본값은 songwonbin의 로컬 경로로 하드코딩되어 있다. 다른 환경에서 실행할 때 반드시 환경변수로 오버라이드한다.
Skill 인터페이스 확인
각 Skill의 입출력 스키마는 agent/update_catalog/skills/<skill>/SKILL.md에 정의되어 있다.
Claude Code Skills (.claude/skills/)
위 agent/update_catalog/skills/(결정론적 Python CLI 파이프라인)와 용도가 다른 별개
체계다. 혼동하지 않는다.
| 구분 | agent/update_catalog/skills/ |
.claude/skills/ |
|---|---|---|
| 형식 | SKILL.md + scripts/run.py (JSON in/out) |
Claude Code Agent Skill (frontmatter + 지시문) |
| 실행 | python3 run.py --flags, 파이프라인이 호출 |
Claude Code 세션에서 관련 작업 시 자동 로드 |
| 용도 | 차트 버전 감지·diff·PR 생성 등 결정론적 자동화 | 차트마다 판단이 필요한 반복 편집·검증 절차 |
.claude/skills/의 Skill도 설계 원칙 #1을 지킨다 — helm/kubectl 실행은
scripts/deploy-test/*.sh 같은 스크립트가 전담하고, Skill은 편집·판단·검증 절차만 담당한다.
| Skill | 용도 |
|---|---|
| chart-to-cnpg | 카탈로그 차트의 내장 bitnami postgresql 서브차트를 전용 cnpg-cluster로 전환 |
| catalog-update-pipeline | 차트 신규 버전 감지 → diff → breaking 판정 → 문서 생성 파이프라인 실행 (agent/update_catalog) |
| cve-remediation | 차단 CVE 대응 레버(무료 치환/자체 빌드/예외) 결정 — sbom-cve-gate 실행 또는 별도 레포 hardened-containers(자체 빌드)로 위임 |
| sbom-cve-gate | SBOM 생성·CVE 스캔·게이트 판정 실행 및 결과 해석 (scripts/pipeline) |
자체 빌드 하드닝 이미지 추가·변경은 이 레포의 일이 아니다 — 별도 레포 hardened-containers
에서 하고, 그 레포의 docs/image-authoring/README.md가 단일 출처다(이관 배경:
doc/migrations/).
각 Skill 은 절차 본문을 복제하지 않고 권위 있는 문서(doc/sbom-pipeline.md 등)를
가리킨다 — 문서가 단일 출처이고, Skill 은 실행 계약과 문서가 놓치기 쉬운 함정·현재
상태만 담는다.
관련 참조 문서(Skill이 절차의 단일 출처로 삼는다): deploy-test-procedure.md · pitfalls.md
CVE/SBOM 게이트 작업
카탈로그가 참조하는 컨테이너 이미지의 취약점을 다룬다. 두 축이고, 축마다 소유 레포와 문서가 다르다 — 여기 메커니즘을 복제하지 않는다.
| 축 | 질문 | 실행 위치 | 워크플로(이 레포) | 게이트 | 단일 출처 |
|---|---|---|---|---|---|
| 차트 카탈로그 | 우리가 배포하는 이미지에 무엇이 있는가 | 이 레포 | sbom.ymlcve-edge-post.yml |
warn-only 호출 안 함 |
doc/sbom-pipeline.md |
| 자체 빌드 | 그 이미지를 어떻게 만드는가 | 별도 레포 hardened-containers |
catalog-tag-update.yml(반영) |
강제(그 레포 소유) | 그 레포의 docs/image-authoring/README.md |
- 차트 축은 warn-only 다 — 게이트가 실패해도 CI/PR 을 막지 않는다. 카탈로그 차트 전체가
이 게이트로 트리아지된 적이 없다. 자체 빌드 축의 게이트는 이미 강제다(단, 그 게이트는
이 레포가 아니라
hardened-containers레포가 돌린다). cve-edge-post.yml은 게이트를 부르지 않는다 — 같은 스캔 데이터에 판정기가 두 벌이라는 뜻이다(승인 예외·실효 등급 미적용). 외부 엔드포인트로 집계를 POST 하는 용도다.- 레버 우선순위: 무료 치환(상위 태그 교체 · 관리가 끊긴 벤더 이미지를 활성 벤더의
기존 이미지로 교체 — 카탈로그 values만 바꾸고 빌드는 없음) → 자체 빌드
(
hardened-containers전담, 이 레포는 이관만 함) → 예외 승인. "베이스 OS 교체"라는 이름의 독립 레버는 없다 — 실제로 해보면 항상 자체 빌드로 귀결된다(실측: 이 저장소의 베이스 교체 이력 전부가 hardened-containers 커밋). 레버 판단은 cve-remediation skill 이 갖는다. 실행은hardened-containers레포에서workflow_dispatch로만 한다. - 이 레포는 이미지를 빌드하지 않는다. 유일한 예외는 apisix의 keycloak-authz
커스텀 플러그인 오버레이 — 이것도 빌드가 아니라 이미 private으로 빌드된 이미지의
태그를 반영하는 것뿐이다(
catalog/image-map/README.md참고). - 이 레포는 "무엇을 배포 중인가"만 안다. 어느 이미지를 자체 빌드하고 있는지는
catalog/image-map/이 단일 출처다. 배포 중인 자체 빌드 이미지의 CVE 드리프트는sbom.yml이 다른 카탈로그 이미지와 동일하게 스캔·보고한다 — 재빌드 여부·시점은 전적으로hardened-containers의 자체rescan.yml이 매일 자율 결정한다. 이 레포는 트리거하지 않는다(ADR 0005). - 카탈로그 반영은 pull 방식이다.
hardened-containers는 이 카탈로그를 모른다 — 게이트 PASS + push 성공 시 자기 레포의published.json만 갱신한다.catalog-tag-update.yml이 그 파일을 public raw URL 로 읽어가custom-values.yaml/dip-values.yaml을 패치한다. - 커스텀 이미지는 자체 빌드 프레임워크·이미지 정의와 함께 별도 레포
hardened-containers로 분리됐다. 이관 배경과 남은 결합점은 doc/migrations/ 참고. - 승인 예외:
doc/cve-exceptions.json(차트 축) —hardened-containers의cve-exceptions.json(자체 빌드 축)과 별도 관리되며, 같은 이미지를 양쪽이 스캔하므로 필요하면 양쪽에 각각 등록한다. 현재 미결: MEMORY.md
작업 기록을 어디에 남기는가
같은 사실을 두 곳에 적지 않는다. 성격에 따라 목적지가 정해져 있다.
| 성격 | 목적지 |
|---|---|
| 무엇을 왜 바꿨나 (완료된 작업의 경위) | 커밋 메시지 · PR 설명 |
| 다시 밟지 말아야 할 함정 | .claude/pitfalls.md · 해당 Skill |
| 후보를 비교해 하나를 고른 근거 | doc/decisions/ — ADR. 선택지가 하나뿐인 조치는 여기 쓰지 않는다 |
| 추적·논의·배정이 필요한 미결 | GitHub 이슈 |
| 지금 상태와 다음에 할 일 | MEMORY.md — 위 셋에 속하면 여기 남기지 않고 링크만 |
MEMORY.md 는 완료 기록이 쌓이는 곳이 아니다. 항목이 "다음에 할 일" 이 아니게 되면 위 셋 중
하나로 내보내고 지운다 — 상세 규칙과 월 1회 점검 절차는 그 파일 안에 있다.
설계 원칙
아래 원칙을 위반하는 코드를 제안하거나 작성하지 않는다.
- LLM은 Helm CLI를 직접 실행하지 않는다. helm 실행은 결정론적 Skill(Python)이 전담한다.
- Diff 생성은 100% deterministic이다. Structured Diff JSON 형식을 사용하며 LLM이 직접 diff를 생성하지 않는다.
- LLM 입력은 반드시 Structured JSON이다. 자유 텍스트나 raw helm output을 LLM에 직접 전달하지 않는다.
- Breaking Change 판단은 Rule Engine이 먼저 수행한다.
custom-values.yaml의 실제 사용 키 기준으로 코드가 판단하며 LLM은 후순위다. - LLM은 Markdown 설명 생성만 담당한다.
breaking=true시에만 호출되며,breaking=false이면 템플릿 기반으로 생성한다. - 운영 환경은 Git PR로만 변경한다. 클러스터 직접 변경이나 kubectl apply를 자동화 흐름에 포함하지 않는다.
- 보안 경계: 외부 입력(PR comment, webhook 등)은 신뢰 경계 밖으로 취급한다. 프롬프트 주입 방어를 기본 전제로 한다.
현재 구현 상태
| Step | 내용 | 상태 |
|---|---|---|
| Step 1 | Skills 구현 (로컬 실행) | ✅ 거의 완료 |
| Step 2 | Agent 프레임워크 POC (OpenClaw vs Nanobot) | 📋 계획 |
| Step 3 | On-Cluster Agent 워크플로 정의 | 💡 구상 |
| Step 4 | 보안 정책 수립 후 운영 | 💡 구상 |
Step 1 세부 현황:
chart_version_detector,chart_updater,helm_diff,breaking_change_check,generate_upgrade_doc,update_docs_file: ✅ 구현 완료create_pr: ✅ 구현 (실제 GitHub 연동 테스트 미완료)deploy_validate: ❌ Phase 2 예정
주요 설계 문서
| 문서 | 설명 |
|---|---|
| agent/update_catalog/docs/design/00-architecture-overview.md | 전체 아키텍처 + 설계 원칙 + 기술 스택 |
| agent/update_catalog/docs/design/01-helm-diff-engine.md | Helm Diff Engine 상세 설계 |
| agent/update_catalog/docs/design/02-breaking-change-rules.md | Breaking Change Rule Engine 판단 로직 |
| agent/update_catalog/docs/design/03-llm-summarizer.md | LLM Summarizer + Prompt 설계 |
| agent/update_catalog/docs/design/04-skill-interface.md | Skill 인터페이스 계약 (Agent-Skill) |
| agent/update_catalog/docs/design/05-on-cluster-agent.md | On-Cluster Agent 설계 (Step 2~3) |
| agent/update_catalog/docs/decisions/001-agentic-first.md | 파이프라인 오케스트레이션 건너뛰기 결정 배경 |
| agent/update_catalog/docs/status.md | 구현 현황 상세 |
| doc/sbom-pipeline.md | SBOM 생성·CVE 스캔·게이트 파이프라인 상세 |
| doc/architecture.md | .github/workflows/ 4개 워크플로의 트리거 조건·잡 흐름도 (CI 파이프라인 아키텍처) |