iT邦幫忙

2025 iThome 鐵人賽

DAY 11
1

在前幾天,我們把 VPC、EKS 與 IRSA 都設定完了,cluster 基礎環境準備好了之後!接下來新的挑戰就會是「怎麼穩定地在這個 cluster 裡部署應用」!

為什麼需要 GitOps?

傳統上,我們可能會寫一堆 YAML,然後用 kubectl apply 或 CI/CD pipeline 把它丟進 cluster。這種模式有一個特徵,被稱作「Push-based」,也就是外部工具(例如 Jenkins、GitLab CI)主動「推送」配置到 cluster。

這個 Push-based 的問題是:

  1. 不一致:Cluster 裡的實際狀態,常常和 Git 裡的 YAML 不一致。
  2. 難追蹤:萬一某個有權限的人在其他人不知情的狀況下執行了 kubectl edit,就會導致第一點不一致的狀況。
  3. 回溯困難:改壞了只能錯哪修哪,沒辦法一鍵 rollback。

GitOps 的核心想法剛好反過來,採取的是「Pull-based」的精神,讓Cluster 內部跑一個控制器(例如 ArgoCD),主動去 Git repo 拉設定檔,並且保證 cluster 狀態與 Git 狀態一致。

這樣有幾個好處:

  • Git repo 會是唯一真實來源(Single Source of Truth)。
  • 有完整的變更紀錄,任何改動都在 commit history 裡。
  • 萬一配置跑壞了,回到舊版本只要 Git revert + sync,或直接使用 ArgoCD 內建的 rollback 指令。

為什麼 ArgoCD 要用 Terraform 部署?

既然 Helm 這麼方便,為什麼我們不直接用 ArgoCD 來部署 ArgoCD 自己呢?因為對我們來說,ArgoCD 本身屬於底層 infra。在我們的分類裡,infra 工具(像是 ArgoCD、External Secrets、CNI plugin)會交給 Terraform 建立;而業務相關或平台元件(像是 LB Controller、Prometheus、Karpenter)才交給 ArgoCD 管理。這樣分層有兩個好處:

  1. Cluster 一開就有基本控制器(例如 External Secrets、ArgoCD 自己)。
  2. 避免雞生蛋問題:因為你沒辦法用 ArgoCD 部署 ArgoCD 本身。

Terraform 部署 ArgoCD

Terraform 提供了 helm_release resource,可以直接安裝 Helm chart。

最重要的是,它可以讀取外部的 values 檔案,讓我們能根據不同環境覆蓋設定。

以下是我們實作的範例:

# https://artifacthub.io/packages/helm/argo/argo-cd/7.8.19
resource "helm_release" "argo_cd" {
  repository        = "https://argoproj.github.io/argo-helm/"
  chart             = "argo-cd"
  version           = "7.8.19"
  namespace         = "argocd"
  name              = "argo-cd"
  create_namespace  = true
  dependency_update = true
  max_history       = 3

  values = [
    templatefile("${path.module}/helm/argo-cd.yaml", {
      roleArn  = aws_iam_role.argo.arn
      argo_url = var.argo_url
    })
  ]

  depends_on = [
    module.eks
  ]
}

argo-cd.yaml 這個 value file 裡,我們可以再額外設定 RBAC、ArgoCD 認得的 git repo ssh extra hosts、SSO 登入設定、還有各個元件的 QoS 都可以寫在 value file 中。

在 Kubernetes 裡,Pod 的 QoS (Quality of Service) 會依照資源設定分成三類:Guaranteed 表示所有容器都設定了 requests 與 limits 且數值相等,享有最高資源保障;Burstable 則是有設定 requests 但不完全等於 limits,能確保基本資源並在有餘裕時額外使用;BestEffort 則完全沒設定 requests/limits,沒有任何保障,叢集壓力大時最容易被驅逐。

而這邊的 request/limit 主要是針對 CPU/Memory 這兩項資源進行設定,細節可以參考官方文件

External Secrets: ArgoCD Dependency

部署 ArgoCD 時,還有一個「先決條件」:External Secrets。因為 ArgoCD 需要連線到 Git repo 或管理 cluster,這些憑證通常存在 AWS Secret Manager。如果 ArgoCD 需要透過 ExternalSecret 從 Secret Manager 讀 repo key,但它自己還沒起來,就會變成 deadlock。因此,ExternalSecrets Controller 必須在 ArgoCD 前安裝,讓 cluster 能自動把 Secret Manager 的內容同步到 Kubernetes Secret。這也是為什麼 ExternalSecrets 和 ArgoCD 一樣,要歸類在「infra 工具」,透過 Terraform 先裝好。

Terraform 範例:

resource "helm_release" "external_secrets" {
  repository        = "https://charts.external-secrets.io"
  chart             = "external-secrets"
  version           = "0.9.5"
  namespace         = "kube-system"
  name              = "external-secrets"
  dependency_update = true
  max_history       = 3

  depends_on = [
    module.eks
  ]

  values = [
    templatefile("${path.module}/helm/external-secrets.yaml", {})
  ]
}

接著我們會採用 ClusterSecretStore 來作為所有 Secret 的來源,並且處理 IRSA 的設置:

module "external_secrets_irsa_role" {
  source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  #checkov:skip=CKV_TF_1: versioning is easier to manage
  version = "5.30.0"

	# 這邊官方一樣有提供已經寫好的 policy 可以用,真是貼心 <3
  attach_external_secrets_policy        = true
  role_name_prefix                      = "external-secrets"
  external_secrets_ssm_parameter_arns   = ["arn:aws:ssm:*:*:parameter/*"]
  external_secrets_secrets_manager_arns = ["arn:aws:secretsmanager:*:*:secret:*"]
  assume_role_condition_test            = "StringLike"

  oidc_providers = {
    main = {
      provider_arn = module.eks.oidc_provider_arn
      namespace_service_accounts = [
        "*:*",
      ]
    }
  }
}

然後會需要設置 Service Account,並且綁定上面所建立的 IRSA role

resource "kubernetes_service_account_v1" "external_secrets" {
  metadata {
    name      = "external-secrets-irsa"
    namespace = "kube-system"
    annotations = {
      "eks.amazonaws.com/role-arn" = module.external_secrets_irsa_role.iam_role_arn
    }
  }
}

最後是建立 ClusterSecretStore 作為所有 Secret 的來源

resource "kubernetes_manifest" "cluster_secret_store" {
  manifest = yamldecode(<<-YAML
    apiVersion: external-secrets.io/v1beta1
    kind: ClusterSecretStore
    metadata:
      name: default
    spec:
      provider:
        aws:
          service: SecretsManager
          region: ap-northeast-1
          auth:
            jwt:
              serviceAccountRef:
                name: ${kubernetes_service_account_v1.external_secrets.metadata[0].name}
                namespace: ${kubernetes_service_account_v1.external_secrets.metadata[0].namespace}
  YAML
  )
}

驗證

  1. 確認 ArgoCD Pod 是否起來:

    kubectl get pods -n argocd
    

    應該要看到 argocd-serverargocd-repo-server 等 Pod Running。

    https://ithelp.ithome.com.tw/upload/images/20250911/20119667HYotaZqjfU.png

  2. Port-forward ArgoCD UI from Freelens:

    https://ithelp.ithome.com.tw/upload/images/20250911/20119667rBgsA29kfQ.png

  3. 找到 ArgoCD UI Admin user 的 password

    kubectl -n argocd get secret argocd-initial-admin-secret \
      -o jsonpath="{.data.password}" | base64 -d
    
  4. 打開 https://localhost:8080 登入(密碼是上一步找回的結果),就能看到熟悉的 Dashboard。

    https://ithelp.ithome.com.tw/upload/images/20250911/20119667kl2SXns0Xd.png

    https://ithelp.ithome.com.tw/upload/images/20250911/20119667TrpErYo7OJ.png

    理論上會看到全空的畫面(如圖),因為現在還沒有部署任何 Application(但因為現在 ArgoCD 裡面有一些其他專案的 Apps 所以我暫時先切到 Unknown 狀態了、剛裝好的介面和現在的截圖會長一樣)

結語

到這裡,我們已經完成了 GitOps 核心工具 ArgoCD 的部署。

  • 前半段,我們了解了 GitOps 與 Push-based / Pull-based 的差異,還有 Helm Chart 解決的痛點。
  • 後半段,透過 Terraform 把 ArgoCD 與 External Secrets 部署進 cluster,幫後續的 GitOps 工作打好基礎。

接下來的文章,我會介紹該如何正確地把 repo secret 和 cluster secret 建立到 cluster 內,讓 ArgoCD 可以正確的向 git repo 連線,並且一次管理多個 cluster 中的 menifests。


上一篇
[Day 10] Helm Chart 概念講解(+偷渡和 CRD & Addon 的比較)
下一篇
[Day 12] Cross Cluster GitOps:Argo CD Repo/Cluster Secret 的安全實作
系列文
EKS in Practice:IaC × GitOps 實戰 30 天14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言