cloud

以 GitOps 管理的 values 在 Kubernetes 執行 Airflow

在家用 Kubernetes 叢集上,以 Argo CD、Helm、Vault 與 External Secrets 部署 Airflow。

English繁中
以 GitOps 管理的 values 在 Kubernetes 執行 Airflow

我的家用 Kubernetes 叢集將 Apache Airflow 作為以 GitOps 管理的平台服務。它負責編排定時執行的資料工作與維運流程;Kubernetes 則為 scheduler、API server、Celery worker、triggerer 與支援服務提供隔離的執行環境。

部署分成兩部分:可公開審查的 Helm values 放在 Git,憑證與產生的金鑰則放在 Vault。Argo CD 負責依正確順序同步這兩部分。

部署結構

這個 repository 採用 app-of-apps 模式。根 Argo CD Application 從 clusters/apps/ 同步子 Application;Airflow 的資源則分成以下幾個部分:

clusters/apps/airflow-secrets.yaml  ->  apps/airflow/ 的 ExternalSecrets
clusters/apps/airflow.yaml          ->  Apache Airflow Helm chart 與 Git values
infra/airflow/                      ->  Ingress 與 KubernetesPodOperator RBAC

airflow-secrets 在 sync wave 5 同步,會建立 airflow namespace、由 Vault 支援的 ClusterSecretStore,以及 ExternalSecret 物件。Helm Application 則在 sync wave 10 同步,表達 chart 應排在這些 secret resources 之後;wave 編號本身不能保證 controller 已建立所需的 Secret,所以仍須確認 ExternalSecret 的 readiness,才能把它當成 deployment gate。

# airflow Application 的結構
spec:
  sources:
    - repoURL: https://airflow.apache.org
      chart: airflow
      targetRevision: 1.22.0
      helm:
        releaseName: airflow
        valueFiles:
          - $values/apps/airflow/airflow-values.yaml
    - repoURL: <private Git repository>
      targetRevision: main
      ref: values
  destination:
    namespace: airflow
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

multi-source Application 讓上游 chart 與叢集設定能各自清楚檢視。我會固定 chart 版本,而不是在 Argo CD refresh 時默默套用當下最新版本。

放在 Git 的執行設定

非敏感的 values 選用 CeleryExecutor、關閉範例 DAG、設定 Asia/Taipei 為 UI 時區,並限制 worker 的資源預算。Logs、triggerer state 與 Celery worker state 都使用持久化磁碟,避免 Pod 被重新排程時遺失維運資料。

executor: CeleryExecutor

config:
  celery:
    worker_concurrency: 4

env:
  - name: AIRFLOW__CORE__LOAD_EXAMPLES
    value: "FALSE"
  - name: AIRFLOW__WEBSERVER__DEFAULT_UI_TIMEZONE
    value: Asia/Taipei

data:
  metadataSecretName: airflow-metadata
  brokerUrlSecretName: airflow-broker-url

fernetKeySecretName: airflow-fernet-key
apiSecretKeySecretName: airflow-api-secret-key
jwtSecretName: airflow-jwt-secret
webserverSecretKeySecretName: airflow-webserver-secret-key

values 只引用 Kubernetes Secret 的「名稱」,不會保存其內容。如此 chart 仍維持宣告式設定,同時不把資料庫連線字串、簽章金鑰或密碼提交到 Git。

由 Vault 提供 Secrets

External Secrets Operator 透過自己的 Kubernetes service account 向 Vault 驗證。名為 vault-airflowClusterSecretStore 讀取 secret/airflow/airflow-override 內的一份 Vault KV v2 payload;各個 ExternalSecret 再將其轉換為 chart 要使用的 Kubernetes Secrets。

產生的 Secrets 包含:

  • Airflow fernet、API、JWT 與 webserver 簽章金鑰
  • metadata database 與 Celery broker 的連線字串
  • Redis 密碼
  • 選用的預設使用者帳密
  • DAG Git sync 使用的 SSH 私鑰

相關設定值集中在同一份 Vault payload,再由 ESO 產生個別的 Kubernetes Secrets,讓每一項 Helm value 引用所需的 Secret。Vault policy 僅允許 External Secrets role 唯讀 Airflow 的路徑。

DAG 發布

DAG 透過 chart 的 gitSync sidecar 從私有 Git repository 同步。repository 位址、branch 與已驗證的 SSH host key 屬於一般 values;私鑰則由 airflow-ssh-secret 提供。

dags:
  gitSync:
    enabled: true
    repo: ssh://git@<git-host>:<port>/data/airflow-dag.git
    branch: main
    sshKeySecret: airflow-ssh-secret
    knownHosts: |
      <verified Git SSH host key>

我會維持 host-key verification。若 Git server 的 key 改變,必須先驗證新的 fingerprint 才更新 knownHosts;為了讓部署先通過而關閉驗證,會讓原本的小問題變成供應鏈風險。

KubernetesPodOperator 權限

部分 DAG 會透過 KubernetesPodOperator 建立短暫的 Pod。Airflow worker service account 在 airflow namespace 內有一個 Role,可處理 Pods、Pod logs、Pod status 與 Events。跨 namespace 的工作採 opt-in:可重用的 ClusterRole 定義權限,但每個目標 namespace 都必須為 airflow-worker 建立專屬的 RoleBinding

因此新的 DAG 目標不會自動取得全叢集權限。我會在預定的 namespace 新增一個 RoleBinding、完成檢視後才套用,其餘 namespaces 維持不可存取。

資料庫 migration 與存取

migration Job 使用 Sync hook,讓 Argo CD 在 Application 同步過程中顯示執行結果。chart 也已設定可使用預設使用者 Secret;不過目前 user-creation Job 是停用的,帳密仍會保留在 Vault。

NGINX Ingress 將 Airflow hostname 路由至 chart 的 API server Service。Ingress 與 RBAC 設定位於 infra/airflow/,讓 Helm release 的 values 可以專注於應用程式本身。

驗證

部署有變更時,我會先確認 Secret 已同步完成,再檢查 chart workload 與 migration Job:

kubectl -n airflow get externalsecret
kubectl -n airflow get secret \
  airflow-fernet-key \
  airflow-api-secret-key \
  airflow-jwt-secret \
  airflow-metadata \
  airflow-broker-url \
  airflow-redis-password \
  airflow-webserver-secret-key \
  airflow-ssh-secret

kubectl -n airflow get pods,jobs,ingress
kubectl -n airflow logs job/airflow-run-airflow-migrations --tail=120

Airflow 的 clear_old_logs DAG 概覽,顯示排程執行成功

Airflow 概覽顯示由 Git 同步的 clear_old_logs DAG 已啟用;在選定期間的近期執行皆成功,沒有 failed task 或 failed run。

Airflow log-cleanup task 成功執行與其日誌

Task 詳細頁確認 BashOperator 已成功完成;執行日誌記錄 cleanup 流程,且 command 以零退出碼結束。

將手動安裝的 Helm release 遷移至 Argo CD 前,最重要的是先還原既有 Secret 的值。若在遷移時更換 fernet 或 webserver key,已加密的 variables、sessions 與既有設定都可能失效。