#29 는 webserverSecretKey 에 placeholder 를 두고 콘솔이 배포 시점에 난수로 치환하게 했다.
신규 배포는 고정됐지만 재배포 경로에서 되살아났다 — 콘솔의 재배포 분기가 이미 치환된
values 가 아니라 placeholder 가 남아 있는 default_values_yaml 을 다시 parse 해서 매번 새
난수를 만든다. 실측에서 재배포 3회에 webserver ReplicaSet 이 3개 생겼고, airflow-config ·
metadata-secret · webserver-config 등 다른 체크섬은 전부 같은데
checksum/webserver-secret-key 하나만 달랐다.
fernet key(#38, PR #45)가 이미 쓰는 "이름을 넘긴다" 방식으로 바꾼다. 이쪽이 값을 고정하는
것보다 강하다 — webserverSecretKeySecretName 이 설정되면 Secret 템플릿이 `if not` 가드로
비고 checksum/ 애노테이션이 해싱하는 대상이 상수가 된다. Deployment spec 이 애초에 키 값에
의존하지 않게 되므로 앞으로 어떤 이유로 키가 바뀌어도 롤링이 발생하지 않는다. 키가 tenant
레포(git)에 평문으로 남지도 않는다.
값 이름은 webserverSecretKeyName 이 아니라 webserverSecretKeySecretName 이다
(values.yaml:500). 차트가 읽는 Secret 의 키 이름은 webserver-secret-key 로 하드코딩돼
있다(_helpers.yaml:99).
CUSTOM-README 5절은 두 비밀값을 같은 방식으로 설명하도록 고치고, helm 직접 배포 절차에
webserver Secret 생성을 추가했다. 마이그레이션 시 ArgoCD prune 주의사항도 적었다 —
차트가 만들던 Secret 은 tracking-id 가 붙은 추적 대상이라 콘솔이 인수할 때 그 애노테이션을
벗겨내야 한다.
세 파일 모두 service-catalog(ac5ae54, 배포 반영 완료)와 바이트 동일하다.
Closes#46
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
차트가 fernet key Secret 을 직접 만들면 helm.sh/hook: pre-install +
hook-delete-policy: before-hook-creation 이 붙는다. ArgoCD 는 훅을 "삭제 후 재생성" 하는데,
변경이 없는 sync 에서는 삭제 직후 operation 이 종료되어 재생성되지 않는다. 컨트롤러 로그로
확인했다.
변경 있는 sync waiting for deletion ... -> serverside-applied -> Succeeded 'all tasks run'
no-op sync waiting for deletion ... -> Succeeded (같은 초) 'no more tasks'
파드는 이미 주입된 env 로 계속 돌아 증상이 즉시 드러나지 않지만, 재시작하는 순간
CreateContainerConfigError 로 기동하지 못한다. refresh·selfHeal 로 흔히 발생하는 상황이다.
fernetKeySecretName 을 주면 차트가 Secret 을 아예 만들지 않으므로
(templates/secrets/fernetkey-secret.yaml:23) 훅 자체가 사라지고, 모든 워크로드가 그 이름을
secretKeyRef 로 참조한다(templates/_helpers.yaml:395-397).
$FERNET_KEY_SECRET 은 placeholder 다. dip-console-api 가 <릴리스명>-fernet-key Secret 을
만들고(없을 때만) 이 자리를 그 이름으로 치환한다. 콘솔이 만든 Secret 은 ArgoCD 추적 대상이
아니라 sync 의 영향을 받지 않는다.
값이 아니라 이름을 넘기는 이유가 하나 더 있다 — fernet key 는 메타DB 의
Connection·Variable 암호화에 쓰이므로 tenant 레포(git)에 평문으로 남기지 않는다.
업스트림도 can only be set during install, not upgrade 라고 못박고 있어, 이미 배포된
테넌트는 재배포 시 현재 Secret 을 그대로 인수한다.
CUSTOM-README 5절은 이 문서의 성격(배포 방법 · custom-values.yaml 키 설명)에 맞게 키
테이블과 배포 절차 중심으로 다시 썼다(69줄 -> 48줄). 상세 근거는 이슈로 넘긴다.
redis 훅 Secret 2종은 CeleryExecutor 전환 예정이 없어 조치하지 않는다. 참조 워크로드가
0개인 것을 클러스터에서 확인했다. 전환 시 처리 방법만 문서에 남긴다.
검증
service-catalog 에 선반영해 테스트 클러스터에서 확인했다. 재배포 후 sync 를 걸었을 때 훅인
redis Secret 2종은 또 삭제됐고 콘솔이 관리하는 fernet Secret 만 살아남았다(resourceVersion
미동, 값 동일). 이 레포 기준으로도 helm template 2회 렌더에서 차트가 fernet Secret 을 만들지
않고 webserver 체크섬이 동일함을 확인했다.
관련: paasup/dip-catalog#38, paasup/dip-console-api#100
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`demo01-air` 에서 webserver 가 반복 롤링되고 migrations job 이 5분 주기로 재실행되던
것을 추적한 결과, 원인이 서로 다른 두 개였고 둘 다 카탈로그 기본값에서 비롯됐다 —
모든 airflow 테넌트가 해당된다.
원인 1 — webserverSecretKey 가 렌더할 때마다 바뀐다
값을 주지 않으면 차트가 템플릿에서 직접 난수를 만들고(`randAlphaNum 32`), 그 Secret 이
webserver·worker Deployment 의 `checksum/webserver-secret-key` 애노테이션에 물려 있어
렌더할 때마다 Deployment spec 이 바뀐다. 캐시된 매니페스트를 쓰는 reconcile 로는
일어나지 않지만 일반 refresh 만으로도 일어난다(실측).
차트 안에서는 못 고친다. `lookup` 재사용은 ArgoCD repo-server 가 클러스터 접근 없이
클라이언트 렌더를 하므로 항상 빈 값이다(클러스터에 Secret 이 실재하는데도 빈 값인 것을
확인). 릴리스명·네임스페이스 기반 결정적 파생은 렌더는 안정되지만 파생 입력이 전부 공개
정보라 세션 서명 키를 예측할 수 있어 부적합하다. 값이 차트 바깥에서 와야 한다.
그래서 `$WEBSERVER_SECRET_KEY` placeholder 를 두고, 배포 시점에 dip-console-api 가
난수로 치환한다(`catalogs/catalogtype/catalog_airflow.go` 의 `DeployPreInstall`).
치환된 values 가 tenant 레포에 저장되므로 이후 렌더는 그 고정값을 쓴다. 공용
`catalog_type_base.go` 가 아니라 airflow 전용 훅에 둔다.
원인 2 — migrations job 이 TTL 삭제 ↔ selfHeal 재생성 루프에 빠진다
`useHelmHooks: false`(업스트림이 ArgoCD 환경에 권고) 로 Job 이 ArgoCD 추적 리소스가
됐는데 차트 기본값 `ttlSecondsAfterFinished: 300` 이 그대로 남아 있었다. 완료 300초 뒤
TTL 컨트롤러가 Job 을 지우면 ArgoCD 가 OutOfSync 로 보고 selfHeal 이 다시 만든다.
템플릿에서 TTL 은 `useHelmHooks` 와 무관하게 항상 렌더되고 `kindIs "invalid"` 일 때만
빠지므로, `~` 로 꺼서 필드 자체를 없앤다. 대신 완료된 Job 객체가 남는다.
반영 위치가 2곳이다 — helm 카탈로그와 applicationset(`---` 뒤 두 번째 문서) 양쪽.
한쪽만 고치면 배포 경로에 따라 증상이 남는다.
주의 — dip-console-api 와 짝이다. 한쪽만 머지되면 placeholder 문자열이 그대로 키가
되거나(카탈로그만) 아무 일도 일어나지 않는다(console 만). 이미 배포된 테넌트는
재배포해야 적용된다.
fernet key 는 같은 구조지만 체크섬에 물려 있지 않아 증상·마이그레이션 경로가 다르다 —
별건(#38)으로 둔다.
Refs #29
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-ingress-unauth 는 kubectl get ingress 목록에서 "인증 없는 입구"를
그대로 광고한다. 리소스가 무엇인지가 아니라 무엇이 없는지로 이름을
지은 것이기도 해서, 경로가 /static 외로 늘어나면 이름이 뜻을 잃는다.
파일명(webserver-ingress-static.yaml)과 일치시키고 용도를 그대로
드러내는 -ingress-static 으로 바꾼다. 값 키 dip.unauthenticatedPaths
는 values 파일 안에만 있고 의도를 정확히 서술하므로 그대로 둔다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
세션이 없는 상태로 접속하면 Airflow UI 한 페이지가 /static 자원을 수십 개
동시에 요청하고, 각 요청이 저마다 APISIX openid-connect 로그인 플로우를
시작한다. 세션은 state를 하나만 보관하므로 콜백이 동시에 돌아오면 마지막
하나를 뺀 전부가 state 검증에 실패해 500이 난다.
templates/webserver/webserver-ingress-static.yaml 은 업스트림 Apache Airflow
차트에 없는 PaaSup 추가 템플릿이다. dip.unauthenticatedPaths 가 있을 때만
애노테이션 없는 Ingress를 하나 더 렌더해 해당 경로를 인증 없이 통과시킨다.
애노테이션을 전부 비우는 것은 의도된 것으로, plugin-config-name 을 빼는 게
목적이고 cert-manager.io/* 까지 빼는 이유는 웹 Ingress와 같은 TLS Secret 을
두고 Certificate 를 중복 생성하지 않게 하기 위해서다.
값을 ingress.web 아래가 아니라 dip 아래에 두는 이유는 values.schema.json 의
ingress.web 이 additionalProperties: false 라 그 아래 새 키를 넣으면 스키마
검증에서 배포가 실패하기 때문이다. qdrant·litellm 의 dip.mainPath 와 같은
자리를 쓴다.
webserver-ingress.yaml 과 동일한 조건으로 렌더하므로 Airflow 3.0
(webserver → api-server) 전환 시 함께 재작업이 필요하다. 업스트림 파일은
수정하지 않았고 fork 델타는 신규 템플릿 1개뿐이다.
이슈 #24 는 dip-console 이 차트 밖에서 생성하는 방안(B안)을 권고했으나,
실제로 증상이 재현되는 서비스가 airflow 하나로 좁혀져 차트 템플릿(A안)으로
갔다. dip-console 로 옮길 때는 dip.unauthenticatedPaths 를 비우면 된다.
redirect_uri 고정은 여전히 dip-console 몫으로 남는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
이슈 #9에서 결정된 CloudNativePG 채택을 카탈로그 앱에 확장 적용한다.
airflow/lakekeeper/mlflow/superset/flowise 5개 차트가 내장하던 bitnami
postgresql 서브차트를 끄고 앱 전용 cnpg-cluster 인스턴스를 외부 DB로 쓰도록
전환했다. 5개 모두 dev 클러스터 격리 네임스페이스에서 배포 테스트로 실측 검증했다.
gitea/keycloak/dnsup 는 서브차트가 아니라 공유 postgresql-ha 를 외부 참조하며
paasup/dipup 레포 관리 대상이라 제외했다 — 인수인계 문서만 추가했다.
배포 구조:
- ArgoCD ApplicationSet 으로 DB(syncWave 0) → 앱(syncWave 1) 순서를 보장한다.
기존 openmetadata/victoria-metrics 관례를 따랐다. cnpg-cluster 차트는 범용
상태로 유지하고 앱별 값은 manifests/applicationset/<app>/ 에 둔다.
검증 중 발견해 함께 고친 문제:
- lakekeeper: cnpg 의 -ro 는 replica 전용이라 instances:1 에서 엔드포인트가
0개다. 읽기 연결을 -r(전체 라운드로빈)로 교체했다.
- airflow/superset: ingressClassName 누락 + kong 애노테이션 잔존으로 이
클러스터(apisix 전용)에서 ingress 접근이 아예 불가능했다. apisix + regex
path 로 교체했다.
- 배포 테스트가 PV 만 지우고 Longhorn Volume CR 을 남겨 storageScheduled 가
누적됐다(orphan 112개 ~1TB 로 배포 차단). 두 스크립트의 정리 로직을 고쳤다.
재사용 구조화:
- .claude/skills/chart-to-cnpg/ 신규. 남은 4개 차트(langflow-ide, langfuse,
litellm, nemo)에 같은 절차를 재사용한다. flowise 에 실제 적용해 검증했다.
- 배포 테스트 공통 절차는 .claude/deploy-test-procedure.md, 환경 함정은
.claude/pitfalls.md 로 단일화하고 앱별 README 는 참조만 남겼다.
관련: #9, #14
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>