DEV Community

Jimmy
Jimmy

Posted on

ArgoCD App of Apps 實作:一個 root Application 管理叢集裡所有的 App

一句話結論:把 ArgoCD 裝好之後,只手動 apply 一個 root Application,讓它掃描 Git 裡的某個目錄;之後要新增、修改、移除任何 App,都只要改 Git,不用再碰 kubectl apply。

遇到的問題

GitOps 的第一步,通常是為每個服務建立一個 ArgoCD Application。

服務少的時候沒問題。服務一多,就變成每新增一個,都要手動 kubectl apply 一份 Application 的 YAML。

這等於在 GitOps 裡留了一個沒被 GitOps 管的缺口:Application 本身不在 Git 的管理範圍內。

解法是 App of Apps:用一個 Application,去管理其他所有 Application。

這篇用最小的例子,從安裝 ArgoCD 開始,走完整個流程。

手把手步驟

步驟一:用 Helm 裝 ArgoCD

前提是手邊已經有一個 Kubernetes 叢集,minikube、kind 或雲端的都可以。

helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm upgrade --install argocd argo/argo-cd \
  --namespace argocd --create-namespace \
  --wait --timeout 15m
Enter fullscreen mode Exit fullscreen mode

確認 ArgoCD 起來了:

kubectl -n argocd get pods
kubectl -n argocd rollout status deploy/argocd-server
Enter fullscreen mode Exit fullscreen mode

官方的 Getting Started 是用 kubectl apply 安裝 manifest。這裡改用 Helm,之後想調整參數(資源限制、Ingress 等)比較方便,也方便升級。

步驟二:登入 UI 看一下

最簡單的方式是 port-forward:

kubectl port-forward svc/argocd-server -n argocd 8080:443
Enter fullscreen mode Exit fullscreen mode

然後用瀏覽器打開 https://localhost:8080(憑證是自簽的,瀏覽器會警告)。

帳號是 admin,初始密碼這樣取:

argocd admin initial-password -n argocd
Enter fullscreen mode Exit fullscreen mode

沒有裝 argocd CLI 的話,也可以直接讀 Secret:

kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d; echo
Enter fullscreen mode Exit fullscreen mode

官方文件提醒:登入並改完密碼之後,要把 argocd-initial-admin-secret 刪掉。這個 Secret 只是初始用,留著沒有意義。

步驟三:準備 Git repo 的結構

建一個 Git repo,結構長這樣:

gitops/
├── bootstrap/
│   └── root-app.yaml     # 手動 apply 一次
└── apps/                  # root 會掃描這個目錄
    └── guestbook.yaml     # 一個子 Application
Enter fullscreen mode Exit fullscreen mode

兩個目錄要分開:bootstrap/ 放只需要手動套用一次的東西,apps/ 放 root 要持續掃描的東西。

不要把 root 自己放進 apps/,否則會變成 root 在管理自己,行為難以預期,也很難理解。

步驟四:寫一個子 Application

apps/guestbook.yaml,使用 ArgoCD 官方的範例 repo,大家都能拿到:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: guestbook
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
Enter fullscreen mode Exit fullscreen mode

幾個欄位值得說明一下。

namespace

Application 這個物件本身預設要放在 argocd namespace。

spec.destination.namespace 才是這個 App 的資源要部署到哪裡。兩個很容易搞混。

finalizer

resources-finalizer.argocd.argoproj.io 的作用是:刪除這個 Application 時,連它部署出來的資源也一起刪掉(預設是前景的 cascading deletion)。

沒有加的話,刪掉 Application 只會移除 ArgoCD 的紀錄,資源還留在叢集裡。

syncPolicy

automated 打開自動同步。prune: true 代表 Git 裡移除的資源,叢集裡也會被刪掉。selfHeal: true 代表有人手動改了叢集內容,ArgoCD 會把它改回 Git 的樣子。

CreateNamespace=true 讓 ArgoCD 在目標 namespace 不存在時自動建立。

步驟五:寫 root Application

bootstrap/root-app.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/<你的帳號>/gitops.git
    targetRevision: main
    path: apps
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
Enter fullscreen mode Exit fullscreen mode

這份 YAML 跟子 Application 長得幾乎一樣,差別有兩個。

path: apps 加 directory.recurse: true

path 指向要掃描的目錄。ArgoCD 預設只讀該目錄第一層的檔案,要往下一層一層讀,要設 recurse: true。

所以之後可以在 apps/ 底下用子資料夾整理,root 一樣讀得到。

destination 的 namespace 是 argocd

因為 root 部署出來的東西就是「Application 這種物件」,而它們預設要在 argocd namespace。

把這個 repo 推到 Git,repoURL 換成自己的。如果是私有 repo,要先在 ArgoCD 設定 repo 的存取憑證。

步驟六:套用 root,看它長出子 App

kubectl apply -f bootstrap/root-app.yaml
Enter fullscreen mode Exit fullscreen mode

這是唯一一次要手動 apply 的東西。

看一下結果:

kubectl get applications -n argocd
Enter fullscreen mode Exit fullscreen mode

預期會看到 root 與 guestbook 兩個,狀態慢慢變成 Synced 與 Healthy:

NAME        SYNC STATUS   HEALTH STATUS
guestbook   Synced        Healthy
root        Synced        Healthy
Enter fullscreen mode Exit fullscreen mode

ArgoCD 預設大約每 3 分鐘輪詢一次 Git,所以 push 之後可能要等一下。急的話,在 UI 上按 Refresh 就會立刻檢查。

步驟七:新增、移除 App,只改 Git

要新增一個 App,在 apps/ 加一個 Application 的 YAML,commit、push。

等下一輪輪詢(或按 Refresh),root 就會偵測到新檔案,建立對應的 Application,新的 App 再各自同步。

要移除,把檔案刪掉再 push。因為 root 有 prune: true,那個 Application 會被刪除,加上子 Application 的 finalizer,它部署的資源也會一起清掉。

全程不需要再對叢集下 kubectl apply。

踩到的坑

1. 刪掉 root,會連帶刪光所有子 App 和它們的資源

root 有加 finalizer,所以刪除它會級聯往下刪:先刪子 Application,子 Application 因為也有 finalizer,再刪它們部署的資源。

換句話說,一個 kubectl delete application root,就能把整個叢集的服務清空。

官方的 Cluster Bootstrapping 文件有專門一節在講這件事。

如果不想要這種風險,可以考慮不給 root 加 finalizer,或是關掉 root 的 prune。取捨是:以後刪除 root 或移除子 Application 時,要自己處理殘留的資源。

2. 子 Application 本身的設定,是由 root 負責同步的

這個坑很隱蔽。假設改了某個子 Application 的設定(例如 plugin 的環境變數、syncOptions),push 之後,如果 root 還沒同步這個改動,子 Application 會繼續用舊的設定渲染,狀態還顯示 Synced,但新開的資源就是沒有出現。

我就是這樣被騙過:看起來一切正常,卻怎麼等都沒有東西。

處理方式是:先確認 root 已經是 Synced,再對子 Application 做一次 hard refresh:

kubectl -n argocd annotate application <名稱> \
  argocd.argoproj.io/refresh=hard --overwrite
Enter fullscreen mode Exit fullscreen mode

hard 的意思是連 manifest 快取都清掉再重新評估。

3. recurse: true 會讀到目錄裡所有的 YAML

root 掃描的目錄裡,只應該放 Application 的 manifest。

如果裡面混了其他 YAML(文件、範例、值檔),ArgoCD 會把它們都當成要部署的資源,可能直接報錯,或是意外建立不該存在的東西。

把「手動套用一次的東西」和「持續掃描的東西」分在不同目錄,就是為了避免這個問題。

為什麼這樣選

官方的 Cluster Bootstrapping 文件,推薦的其實是 ApplicationSet,App of Apps 被列為替代方案。

ApplicationSet 適合「很多個長得很像的 App」或「要部署到很多個叢集」,用 generator 一次產生一堆 Application,不用寫一堆 YAML。

我選 App of Apps 的原因很單純:

  • 一個 App 一個檔案,看檔案就知道它的設定,容易讀。
  • 每個 App 的設定可以各自不同(有的自動同步,有的手動)。
  • 概念簡單,不需要先學 generator 和模板語法。

單一叢集、App 數量不多、每個差異不小的情況,這樣就夠了。要管很多叢集或很多相似的 App,可以直接看 ApplicationSet。

另外有一個官方的安全警告值得記住:App of Apps 是管理員專用的工具。

能在任意 Project 底下建立 Application,等於是管理員等級的權限。所以只有管理員能對 root 所指向的 repo 推送,改動也應該經過審核。

延伸閱讀

這篇用的是 ArgoCD 的 stable 文件與 argo-helm 的 argo-cd chart。版本不同的話,記得對一下欄位名稱。

Top comments (0)