From d3816a14caefb8ffeb900a3a0ac773e086720fa7 Mon Sep 17 00:00:00 2001 From: wbsong111 Date: Fri, 21 Aug 2026 13:56:36 +0900 Subject: [PATCH] =?UTF-8?q?airflow=20fernet=20key=20=EB=A5=BC=20=EC=BD=98?= =?UTF-8?q?=EC=86=94=EC=9D=B4=20=EB=A7=8C=EB=93=A0=20Secret=20=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=84=98=EA=B8=B4=EB=8B=A4=20(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 차트가 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) --- .../airflow/1.16.0/dip-values.yaml | 8 ++ .../helm/airflow/1.16.0/CUSTOM-README.md | 101 +++++++----------- manifests/helm/airflow/1.16.0/dip-values.yaml | 8 ++ 3 files changed, 56 insertions(+), 61 deletions(-) diff --git a/manifests/applicationset/airflow/1.16.0/dip-values.yaml b/manifests/applicationset/airflow/1.16.0/dip-values.yaml index 3b84a44..e56369b 100644 --- a/manifests/applicationset/airflow/1.16.0/dip-values.yaml +++ b/manifests/applicationset/airflow/1.16.0/dip-values.yaml @@ -163,6 +163,14 @@ dags: # 차트 안에서는 못 고친다(ArgoCD repo-server 는 클러스터 접근이 없어 helm lookup 이 빈 값). webserverSecretKey: "$WEBSERVER_SECRET_KEY" +# fernet key 는 값이 아니라 Secret 이름만 넘긴다 — dip-console-api 가 배포 시점에 +# <릴리스명>-fernet-key Secret 을 만들고 이 자리를 그 이름으로 치환한다. +# 비워두면 차트가 Secret 을 pre-install 훅으로 만드는데, ArgoCD 는 훅을 "삭제 후 재생성" +# 하므로 변경이 없는 sync 에서는 삭제 직후 operation 이 끝나 재생성되지 않는다. 그러면 +# 파드 재시작 시 기동에 실패한다 (paasup/dip-catalog#38). +# 이름만 넘기므로 키가 tenant 레포(git)에 평문으로 남지 않는다. +fernetKeySecretName: "$FERNET_KEY_SECRET" + webserver: defaultUser: enabled: true diff --git a/manifests/helm/airflow/1.16.0/CUSTOM-README.md b/manifests/helm/airflow/1.16.0/CUSTOM-README.md index 5061fbe..800216d 100644 --- a/manifests/helm/airflow/1.16.0/CUSTOM-README.md +++ b/manifests/helm/airflow/1.16.0/CUSTOM-README.md @@ -201,70 +201,49 @@ Airflow 웹서버는 Flask-AppBuilder 구조라 번들링 없이 CSS/JS를 개 ### 5) 반복 재배포 방지 설정 -`dip-values.yaml`에 있는 두 항목은 airflow가 반복 재배포되는 것을 막기 위한 것이다. 원인이 -서로 다르므로 따로 설명한다 (issue #29). +ArgoCD 배포에서 airflow가 반복 재배포되거나 Secret이 사라지는 것을 막기 위한 설정이다. +배경과 실측 근거는 paasup/dip-catalog#29 · #38 참고. -#### 5.1) `webserverSecretKey` — 렌더 결정성 +| Name | 설명 | 기본값 | +| ---- | ---- | ------ | +| `webserverSecretKey` | 세션 서명 키. 비우면 차트가 렌더할 때마다 난수를 만들어 webserver가 계속 롤링되고 웹 세션이 끊긴다 | `~` | +| `fernetKeySecretName` | fernet key Secret 이름. 비우면 차트가 `pre-install` 훅으로 Secret을 만드는데, 변경이 없는 sync에서 삭제만 되고 재생성되지 않는다 | `~` | +| `migrateDatabaseJob.ttlSecondsAfterFinished` | `useHelmHooks: false`와 같이 두면 TTL 삭제 ↔ selfHeal 재생성 루프가 생긴다. `~`로 끈다 | `300` | -``` yaml -webserverSecretKey: "$WEBSERVER_SECRET_KEY" -``` +- dip-values.yaml + ``` yaml + webserverSecretKey: "$WEBSERVER_SECRET_KEY" + fernetKeySecretName: "$FERNET_KEY_SECRET" -- 이 값을 주지 않으면 차트가 템플릿 안에서 직접 난수를 만든다 - (`templates/secrets/webserver-secret-key-secret.yaml`의 `randAlphaNum 32`). -- 그 Secret은 webserver·worker Deployment의 `checksum/webserver-secret-key` 애노테이션에 - 물려 있어서, **매니페스트를 다시 렌더할 때마다 Deployment spec이 바뀌고 롤링이 걸린다.** - 실측에서 webserver가 반복 재배포되고 그때마다 웹 세션이 끊겼다. 캐시된 매니페스트를 - 그대로 쓰는 reconcile로는 일어나지 않지만, **일반 refresh만으로도 일어난다.** -- **차트 안에서는 못 고친다.** `lookup`으로 기존 Secret을 재사용하는 방법은 ArgoCD - repo-server가 클러스터 접근 없이 클라이언트 렌더를 하므로 항상 빈 값이 된다(클러스터에 - Secret이 실제로 있는데도 빈 값인 것을 실측 확인). 릴리스명·네임스페이스 기반 결정적 - 파생은 렌더가 안정되는 대신 파생 입력이 전부 공개 정보라 **세션 서명 키를 예측할 수 있어** - 부적합하다. 값이 차트 바깥에서 와야 한다. -- 그래서 `$WEBSERVER_SECRET_KEY`는 **placeholder**다. 배포 시점에 dip-console-api가 난수로 - 치환한다 — `catalogs/catalogtype/catalog_airflow.go`의 `DeployPreInstall` - (crypto/rand 32바이트, URL-safe base64). 치환된 values가 tenant 레포에 저장되므로 이후 - 렌더는 그 고정값을 쓴다. -- **dip-console-api와 짝이다.** 한쪽만 반영되면 placeholder 문자열이 그대로 키가 되거나 - (카탈로그만) 아무 일도 일어나지 않는다(console만). -- **helm으로 직접 배포할 때는** dip-console-api를 거치지 않으므로 치환이 일어나지 않는다. - `custom-values.yaml`에 실제 값을 직접 넣는다. + migrateDatabaseJob: + useHelmHooks: false + ttlSecondsAfterFinished: ~ + ``` +- `$WEBSERVER_SECRET_KEY`와 `$FERNET_KEY_SECRET`는 placeholder다. dip-console-api가 배포 + 시점에 각각 난수 키와 Secret 이름으로 치환한다 + (`catalogs/catalogtype/catalog_airflow.go`의 `DeployPreInstall`). + **카탈로그와 dip-console-api를 함께 반영해야 한다** — 한쪽만 반영하면 placeholder 문자열이 + 그대로 값이 되거나 아무 일도 일어나지 않는다. +- 같은 내용을 `manifests/applicationset/airflow/1.16.0/dip-values.yaml`(`---` 뒤 두 번째 + YAML 문서)에도 반영한다. +- 이미 배포된 테넌트는 **재배포해야 적용된다.** fernet key는 재배포해도 기존 Secret을 그대로 + 인수하므로 Connection·Variable은 유지된다. webserver 세션은 끊겨 다시 로그인해야 한다. + +- **helm으로 직접 배포할 때**는 dip-console-api를 거치지 않으므로 두 값을 직접 넣는다. ``` sh - # 값 생성 + # fernet key Secret 생성 (키 이름은 반드시 fernet-key) + kubectl create secret generic airflow-fernet-key -n airflow \ + --from-literal=fernet-key="$(python3 -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())')" + + # webserver secret key 값 생성 python3 -c "import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())" ``` -- **재배포하면 새 키가 생성되어 기존 웹 세션은 무효화된다** — 사용자는 다시 로그인해야 한다. - -#### 5.2) `migrateDatabaseJob.ttlSecondsAfterFinished` — TTL ↔ selfHeal 루프 - -``` yaml -migrateDatabaseJob: - useHelmHooks: false - ttlSecondsAfterFinished: ~ -``` - -- `useHelmHooks: false`로 두면 migrations Job이 helm hook이 아니라 **ArgoCD가 추적하는 일반 - 리소스**가 된다. ArgoCD 환경에서 hook을 끄라는 것은 업스트림 권고다 - (`values.yaml` 주석: `Disable this if you are using ArgoCD for example`). -- 그런데 차트 기본값 `ttlSecondsAfterFinished: 300`이 그대로 남아 있으면, 완료 300초 뒤 TTL - 컨트롤러가 Job을 지우고 ArgoCD는 그것을 OutOfSync로 보고 selfHeal로 다시 만든다. - **migrations job이 5분 주기로 재실행되는 루프**가 된다. -- 둘의 조합이 문제다. 하나만 있으면 발생하지 않는다. -- 리소스 수명은 ArgoCD가 관리하므로 **TTL을 끈다.** `~`(null)이면 템플릿의 - `kindIs "invalid"` 가드에 걸려 `ttlSecondsAfterFinished` 필드 자체가 렌더되지 않는다 - (`templates/jobs/migrate-database-job.yaml`). 대신 완료된 Job 객체가 네임스페이스에 남는다. -- `createUserJob`도 `ttlSecondsAfterFinished: 300` + `useHelmHooks: true`로 구조가 같지만, - hook이 살아 있어 ArgoCD 추적 대상이 아니므로 지금은 문제가 없다. `useHelmHooks`를 끄게 - 되면 TTL도 같이 꺼야 한다. - -#### 5.3) 반영 위치가 2곳이다 - -같은 내용을 아래 두 파일에 모두 반영해야 한다. 한쪽만 고치면 배포 경로에 따라 증상이 남는다. - -- `manifests/helm/airflow/1.16.0/dip-values.yaml` -- `manifests/applicationset/airflow/1.16.0/dip-values.yaml` (`---` 뒤 두 번째 YAML 문서) - -#### 5.4) 이미 배포된 테넌트 - -tenant 레포에 저장된 values에는 이 설정이 없으므로, **재배포해야 적용된다.** 그전까지 기존 -테넌트의 롤링·job 재실행 루프는 계속된다. \ No newline at end of file + ``` yaml + webserverSecretKey: "<위에서 생성한 값>" + fernetKeySecretName: "airflow-fernet-key" + ``` +- 참고: `<릴리스명>-redis-password` · `<릴리스명>-broker-url`도 같은 `pre-install` 훅이라 + sync에서 사라진다. `executor: "KubernetesExecutor"`인 현재는 참조하는 워크로드가 없어 + 무해하다(다음 배포 때 자동으로 다시 생긴다). CeleryExecutor로 바꾼다면 차트가 제공하는 + `redis.passwordSecretName` · `data.brokerUrlSecretName`으로 위 5.2와 같게 처리한다 — + `broker-url`은 redis 비밀번호를 DSN에 포함하므로 두 Secret을 함께 생성해야 한다. diff --git a/manifests/helm/airflow/1.16.0/dip-values.yaml b/manifests/helm/airflow/1.16.0/dip-values.yaml index ad7cec5..1a69a70 100644 --- a/manifests/helm/airflow/1.16.0/dip-values.yaml +++ b/manifests/helm/airflow/1.16.0/dip-values.yaml @@ -72,6 +72,14 @@ dags: # 차트 안에서는 못 고친다(ArgoCD repo-server 는 클러스터 접근이 없어 helm lookup 이 빈 값). webserverSecretKey: "$WEBSERVER_SECRET_KEY" +# fernet key 는 값이 아니라 Secret 이름만 넘긴다 — dip-console-api 가 배포 시점에 +# <릴리스명>-fernet-key Secret 을 만들고 이 자리를 그 이름으로 치환한다. +# 비워두면 차트가 Secret 을 pre-install 훅으로 만드는데, ArgoCD 는 훅을 "삭제 후 재생성" +# 하므로 변경이 없는 sync 에서는 삭제 직후 operation 이 끝나 재생성되지 않는다. 그러면 +# 파드 재시작 시 기동에 실패한다 (paasup/dip-catalog#38). +# 이름만 넘기므로 키가 tenant 레포(git)에 평문으로 남지 않는다. +fernetKeySecretName: "$FERNET_KEY_SECRET" + webserver: defaultUser: enabled: true