diff --git a/.claude/skills/chart-to-cnpg/SKILL.md b/.claude/skills/chart-to-cnpg/SKILL.md index f8fab3c..45f5f5f 100644 --- a/.claude/skills/chart-to-cnpg/SKILL.md +++ b/.claude/skills/chart-to-cnpg/SKILL.md @@ -1,6 +1,6 @@ --- name: chart-to-cnpg -description: 카탈로그 차트가 내장한 bitnami postgresql 서브차트를 전용 cnpg-cluster 인스턴스로 전환할 때 사용한다. "postgresql 서브차트를 cnpg로 바꿔줘", "이 차트도 cnpg-cluster 쓰게 해줘", "bitnami postgresql 제거" 같은 요청, 또는 manifests/helm//의 custom-values.yaml에서 postgresql 서브차트를 끄고 외부 DB로 돌리는 작업에 해당한다. 남은 대상은 flowise, langflow-ide, langfuse, litellm, nemo다. +description: 카탈로그 차트가 내장한 bitnami postgresql 서브차트를 전용 cnpg-cluster 인스턴스로 전환할 때 사용한다. "postgresql 서브차트를 cnpg로 바꿔줘", "이 차트도 cnpg-cluster 쓰게 해줘", "bitnami postgresql 제거" 같은 요청, 또는 manifests/helm//의 custom-values.yaml에서 postgresql 서브차트를 끄고 외부 DB로 돌리는 작업에 해당한다. 남은 대상은 langflow-ide, langfuse, litellm, nemo, infisical-standalone다(flowise는 2026-08 전환 완료). --- # 카탈로그 앱 → cnpg-cluster 전환 diff --git a/.claude/skills/self-build-image/SKILL.md b/.claude/skills/self-build-image/SKILL.md index 5936ea1..06bf552 100644 --- a/.claude/skills/self-build-image/SKILL.md +++ b/.claude/skills/self-build-image/SKILL.md @@ -1,6 +1,6 @@ --- name: self-build-image -description: 자체 빌드 하드닝 이미지를 추가하거나 기존 빌드 정의를 변경할 때 사용한다. "이미지 자체 빌드해줘", "하드닝 이미지 추가", "차단 CVE를 자체 빌드로 해소", "build-hardened-image.sh 실행", "images/ 아래 새 이미지" 같은 요청이 해당한다. 현재 cloudnative-pg, cnpg-postgresql, etcd 3종이 있다. +description: 자체 빌드 하드닝 이미지를 추가하거나 기존 빌드 정의를 변경할 때 사용한다. "이미지 자체 빌드해줘", "하드닝 이미지 추가", "차단 CVE를 자체 빌드로 해소", "build-hardened-image.sh 실행", "images/ 아래 새 이미지" 같은 요청이 해당한다. 현재 adc, apisix, apisix-ingress-controller, cloudnative-pg, cnpg-postgresql, etcd, keycloak 7종이 있다(정확한 목록은 images/ 디렉토리가 단일 출처). --- # 자체 빌드 하드닝 이미지 diff --git a/CLAUDE.md b/CLAUDE.md index fc86cd5..6bfb586 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ DIP Catalog는 세 개의 독립 서브시스템으로 구성된다. | 서브시스템 | 설명 | 경로 | |-----------|------|------| -| **정적 카탈로그** | 45+ 엔터프라이즈 Helm 차트 버전 보관소 (Kafka, Airflow, MLflow, KServe, OpenMetadata 등) | `manifests/helm/` | +| **정적 카탈로그** | 엔터프라이즈 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` | @@ -32,10 +32,16 @@ dip-catalog/ │ └── status.md # 구현 현황 ├── scripts/ │ ├── pipeline/ # SBOM 생성 + CVE 스캔 + 게이트 판정 (sbom.yml/cve-edge-post.yml 이 쓴다) -│ └── build/ # 자체 빌드 이미지 프레임워크 (build-image.yml 이 쓴다 — 아직 실사용 이미지 없음) -└── doc/ # 차트 리소스 프로파일(CPU/Memory/Storage) + CVE/SBOM 파이프라인 문서 +│ ├── build/ # 자체 빌드 이미지 프레임워크 (build-image.yml 이 쓴다) +│ └── deploy-test/ # 배포 검증 스크립트 + fixtures (helm/kubectl 실행 전담) +├── images// # 자체 빌드 하드닝 이미지 정의 (목록은 이 디렉토리가 단일 출처) +├── MEMORY.md # 현재 상태·미결 결정 (작업 이어받을 때 여기서 시작) +└── doc/ # 차트 리소스 프로파일 + 스택 분류 + CVE/SBOM 파이프라인 + 운영 가이드 ├── sbom-pipeline.md # SBOM 생성·스캔·게이트 메커니즘 - └── cve-exceptions.json # 게이트 승인 예외 목록 + ├── cve-exceptions.json # 게이트 승인 예외 목록 + ├── catalog-stack-classification.md # 차트 스택 분류·우선순위(P0~P2) + ├── define-chart-resources.md # 차트별 Small/Medium/Large 리소스 프로파일 + └── migrations/ # 레포 간 이관 핸드오프 문서 ``` --- @@ -54,6 +60,11 @@ dip-catalog/ | `CUSTOM-README.md` | 업그레이드 주의사항 (자동 생성 + 수동 추가 가능) | | `BUILD-README.md` | 업스트림 README (버전 정보 파싱에 사용됨, 변경 금지) | +실제 카탈로그는 이 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///` 디렉토리를 생성하고 위 5개 파일을 포함시킨다. diff --git a/manifests/applicationset/airflow/1.16.0/dip-questions.yaml b/manifests/applicationset/airflow/1.16.0/dip-questions.yaml new file mode 100644 index 0000000..ff63f46 --- /dev/null +++ b/manifests/applicationset/airflow/1.16.0/dip-questions.yaml @@ -0,0 +1,57 @@ +questions: +# --- DAG git-sync --- +- variable: "$dags.gitSync.repo" + label: Git Repo URL + type: string + required: true + default: "https://git-url/org/repo.git" +- variable: infisical.GIT_SYNC_USERNAME + label: Git username + type: string + required: true + default: "" +- variable: infisical.GIT_SYNC_PASSWORD + label: Git password + type: password + required: true + default: "" +# git-sync v4 는 GITSYNC_* 를, 차트는 두 이름 모두를 secretKeyRef 로 참조한다. +# 네 키가 모두 없으면 scheduler/webserver 파드가 기동되지 않는다. +- variable: infisical.GITSYNC_USERNAME + label: Git username + type: hidden + default: "${infisical.GIT_SYNC_USERNAME}" +- variable: infisical.GITSYNC_PASSWORD + label: Git password + type: hidden + default: "${infisical.GIT_SYNC_PASSWORD}" + +# --- Web admin --- +- variable: "$webserver.defaultUser.password" + label: Web admin password + type: password + required: true + default: "" + +# --- cnpg-cluster(airflow-db) 자격증명 --- +# bootstrap.initdb.secretName 이 참조하는 시크릿. cnpg 는 username/password 키를 요구한다 +# (kubernetes.io/basic-auth). 값은 doc1 의 bootstrap.initdb.owner 와 일치해야 한다. +- variable: infisical.username + label: DB Username + type: hidden + default: "airflow" +- variable: infisical.password + label: DB Password + type: hidden + default: "" +# data.metadataSecretName 이 참조하는 같은 시크릿의 connection 키 — 전체 DSN. +# 차트는 이 문자열을 AIRFLOW__DATABASE__SQL_ALCHEMY_CONN 에 그대로 주입한다. +# dip-values.yaml 의 data.metadataConnection 은 무시되므로 여기가 유일한 실효 값이다. +# +# 호스트에 $CATALOG_NAME 접두사가 반드시 있어야 한다. dip 배포는 cnpg 릴리스명에 +# 테넌트 접두사를 붙여 나가므로(관측: instance=demo01-air-airflow-db) 실제 서비스는 +# airflow-db-rw 가 아니라 $CATALOG_NAME-airflow-db-rw 다. +- variable: infisical.connection + label: postgres connection + type: hidden + default: "postgresql://airflow:${infisical.password}@$CATALOG_NAME-airflow-db-rw.$CATALOG_NAME:5432/airflow?sslmode=disable" diff --git a/manifests/applicationset/airflow/1.16.0/dip-values.yaml b/manifests/applicationset/airflow/1.16.0/dip-values.yaml new file mode 100644 index 0000000..a7bdc14 --- /dev/null +++ b/manifests/applicationset/airflow/1.16.0/dip-values.yaml @@ -0,0 +1,187 @@ +instances: 1 + +postgresql: + imageName: "docker.io/paasup/cnpg-postgresql:18.4-bci15.7-hardened-20260803" + + 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 + + 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: 1000m + memory: 2Gi + +bootstrap: + initdb: + database: airflow + owner: airflow + secretName: "$INFISICAL_SECRET" + encoding: UTF8 + postInitApplicationSQL: [] + +databases: + - name: airflow + owner: airflow + ensure: present + reclaimPolicy: retain + extensions: + - name: pg_stat_statements + - name: pgaudit + +enableSuperuserAccess: false + +affinity: + enablePodAntiAffinity: true + podAntiAffinityType: preferred + topologyKey: kubernetes.io/hostname + nodeSelector: {} + tolerations: [] + +monitoring: + enablePodMonitor: false + +backup: + enabled: false + retentionPolicy: 30d + barmanObjectStore: + destinationPath: "" + endpointURL: "" + s3Credentials: + accessKeyId: + name: "" + key: ACCESS_KEY_ID + secretAccessKey: + name: "" + key: ACCESS_SECRET_KEY + +scheduledBackup: + enabled: false + schedule: "0 0 2 * * *" + +pooler: + enabled: false + instances: 2 + type: rw + poolMode: transaction +--- +ingress: + web: + enabled: true + annotations: + cert-manager.io/cluster-issuer: "root-ca-issuer" + cert-manager.io/duration: 8760h + cert-manager.io/renew-before: 720h + k8s.apisix.apache.org/plugin-config-name: oidc-keycloak-authz + ingressClassName: "apisix" + pathType: "Prefix" + hosts: + - name: "{{ .Name }}.{{ .Domain }}" + tls: + enabled: true + secretName: "{{ .Name }}-tls-secret" + +executor: "KubernetesExecutor" + +config: + core: + executor: KubernetesExecutor + default_timezone: kst + logging: + colored_console_log: "False" + logging_level: "INFO" + webserver: + enable_proxy_fix: "True" + rbac: "True" + default_ui_timezone: kst + +scheduler: + replicas: 1 + +dags: + persistence: + enabled: true + size: 5Gi + storageClassName: longhorn + accessMode: ReadWriteMany + gitSync: + enabled: true + repo: "$dags.gitSync.repo" + branch: master + rev: HEAD + depth: 1 + subPath: "" + credentialsSecret: "$INFISICAL_SECRET" + env: + - name: GIT_SSL_NO_VERIFY + value: "true" + +webserver: + defaultUser: + enabled: true + password: "$webserver.defaultUser.password" + livenessProbe: + initialDelaySeconds: 120 + readinessProbe: + initialDelaySeconds: 120 + startupProbe: + initialDelaySeconds: 30 + webserverConfig: | + AUTH_ROLE_PUBLIC = 'User' + +logs: + persistence: + enabled: true + size: 5Gi + storageClassName: longhorn + +statsd: + enabled: false + +# 내장 bitnami postgresql 서브차트 대신 syncWave 0 의 cnpg-cluster(airflow-db)를 쓴다. +postgresql: + enabled: false + +migrateDatabaseJob: + useHelmHooks: false + +data: + # 시크릿의 connection 키(전체 DSN)를 그대로 쓴다 — 이 값이 설정되면 차트는 + # -metadata 시크릿을 만들지 않는다. + metadataSecretName: "$INFISICAL_SECRET" + # metadataSecretName 이 있으면 접속에는 쓰이지 않지만, pgbouncer/keda 경로와 + # NOTES 출력이 참조하므로 cnpg 서비스에 맞춰 둔다. + # 호스트 접두사는 flowise/6.0.0/dip-values.yaml 의 externalPostgresql.host 와 같은 규칙이다 + # — dip 배포에서 cnpg 릴리스명이 테넌트 접두사를 달고 나가므로 {{ .Name }} 가 필요하다. + metadataConnection: + user: airflow + protocol: postgresql + host: "{{ .Name }}-airflow-db-rw" + port: 5432 + db: airflow + sslmode: disable +