終於來到最後一天啦!真是辛苦大家了
程式碼也都可從這個 Repo 取得,懶得打就直接 clone 也可以 XD
https://github.com/YIFUNLIN/k8s-30days
原先其實沒有想說要比這個的,小弟目前在服替代役期間 生活乏味,
想耍廢但又深怕自己退伍後忘光光 無法銜接上工作
最後決定還是來比個鐵人賽,這樣才會 push 讓自己每天都要有產出才行 XD
大家的觀看是我繼續的動力!非常感謝 🥹
廢話不多說,讓我們趕快進入正題吧!
30 天前,我們可能還只是在問:
Kubernetes 到底是什麼?
但走到今天,我們已經實際碰過:
FastAPI
Redis
PostgreSQL
Docker
kind
Deployment
Service
ConfigMap
Secret
Probe
PVC
StatefulSet
Scheduler
Affinity
Taint
HPA
Job
CronJob
DaemonSet
Calico
NetworkPolicy
RBAC
Helm
Kustomize
Gateway API
CRD
Operator
kubelet
containerd
Static Pod
etcd
所以 Day 30 不打算再塞一個新的 Kubernetes Resource。
今天反而要做更重要的事情:
把前面 29 天學到的東西真正串起來。
最後再完成兩件事:
第一:把現在的 Kubernetes Project 串上真正的 DevOps Workflow。
第二:建立一套 Kubernetes 出問題時,知道應該從哪裡開始查的 Troubleshooting 方法。
而且今天不只細談所有概念。
也會手把手真的帶你完成一個 Final Lab,並附上實際可能遇到的一些問題和如何除錯:
今日 Lab 大致流程如下:
修改 FastAPI
↓
git push
↓
GitHub Actions
↓
Build Docker Image
↓
Push GHCR
↓
更新 Git 裡的 Kubernetes Desired State
↓
Argo CD 發現變更
↓
Sync 到 kind
↓
Kubernetes 自動部署新版本
把前面學過的:
Docker
GitHub Actions
GHCR
Kustomize
Argo CD
Kubernetes
串成一條完整 Pipeline。
目前每次修改 FastAPI,大概會做:
修改程式碼
↓
docker build
↓
kind load
↓
kubectl apply
學 Kubernetes 時這完全沒有問題。
因為我們就是透過這些手動操作,理解 Image、Deployment、Service 到底在做什麼。
但正式開發環境不會希望工程師每次改一行程式,都手動:
Build
Push
Deploy
因此 DevOps 常見的流程會變成:
git commit
↓
Test
↓
Build
↓
Publish
↓
Deploy
這裡就會出現非常重要的概念:
CI/CD

https://kucw.io/blog/cicd-intro/
CI,Continuous Integration,持續整合,意思是每當程式碼被提交後,自動進行 Test、Build 等工作。
CD,Continuous Delivery / Deployment,持續交付/持續部署,則負責把已經 Build 完的版本繼續送到實際環境。
我們今天就從 CI 開始。

GitHub Actions 是 GitHub 提供的自動化平台。
例如我們可以設定:
當 main branch 有新的 Commit
↓
GitHub 偵測到後,會自動啟動執行環境
↓
Checkout 程式碼
↓
Build Docker Image
↓
Push 到 Container Registry
負責真正執行 Workflow 的機器叫:
Runner
如果使用 GitHub 提供的 Runner,就是:
GitHub-hosted Runner
它是 GitHub 在雲端準備的一個執行環境。

https://kucw.io/blog/github-actions-intro/
以前我們建立:
cka-api:v4
這個 Image 其實只存在自己的 Mac。
所以才需要:
kind load docker-image \
cka-api:v4 \
--name cka-lab
把 Image 搬進 kind Node。
但如果 Kubernetes Cluster 在 AWS、GCP、Azure,Cloud 裡面的 Node 根本看不到我們 Mac 裡面的 Image。
真正的流程會變成:
Source Code
↓
Docker Build
↓
放到 Container Registry
↓
Kubernetes Pull Image
Container Registry 可以理解成:
專門存放 Container Image 的遠端倉庫。
Docker Hub、AWS ECR、Google Artifact Registry 都是 Container Registry。
今天我們直接使用 GitHub 提供的:
GitHub Container Registry
簡稱:
GHCR
網址:
ghcr.io
如果專案目前還只存在自己的 Mac,要先把它放到 GitHub。
進入:
k8s-30days
初始化 Git:
git init
第一次 Commit 前,先建立:
vim .gitignore
.gitignore 的作用是告訴 Git:
哪些檔案不要加入版本控制。
我們之前做 etcd Restore 時,例如會產生:
etcd-restored/
這是將 backup.db restore 成 etcd 可使用的 data directroy (類似解壓縮到另一個新資料夾的概念),這樣 etcd 才可真的使用。
裡面有:
member/
snap/
wal/
其中 WAL 是:
Write-Ahead Log
屬於 etcd Runtime Data,不是 Source Code,而且檔案可能非常大。
因此可以加入:
# macOS
.DS_Store
# Python
__pycache__/
*.pyc
.venv/
venv/
# Secrets
.env
# etcd restore data
etcd-restored/
# etcd snapshot
backup.db
snapshot.db
尤其:
.env
更不應該 Push,因為裡面可能有 Token、Password、API Key 等敏感資料。
建立 Commit:
git add .
git commit -m "initial commit"
把主要 Branch 統一為:
git branch -M main
接著到 GitHub:
GitHub
→ New repository
→ Repository name:k8s-30days
→ Create repository
建立完成後,把本機接到 GitHub:
git remote add origin \
https://github.com/YIFUNLIN/k8s-30days.git
確認:
git remote -v
第一次 Push:
git push -u origin main
之後通常只需要:
git push
即可。
現在關係已經變成:
Mac
↓
Local Git
↓
GitHub Repository
若有先做上面那步,這邊可跳過
一開始我沒有將 etcd-restored/ 加到 .gitignore,直接 Push 時,就遇到:
warning: File etcd-restored/...wal is 61 MB
GH001: Large files detected

太大啦,不能推上去
所以我們現在要把它從 Git Tracking 移除,但保留 Mac 上的檔案:
git rm -r --cached etcd-restored
如果 Repository 才剛建立,可以直接修改上一個 Commit:
git add .gitignore
git commit --amend --no-edit
再:
git push --force-with-lease origin main
最後變成:
Mac
└── etcd-restored/ ← 還存在
Git
└── 不追蹤
GitHub
└── 不保存 Runtime Data
這也是一個很實際的 DevOps 習慣:
Source Code、Config 可以進 Git;Secret、Log、Backup、Runtime Data 通常不應該直接進 Git。
建立:
mkdir -p .github/workflows
記得是 workflows,有 s !!!
不然 github action 會讀不到哦
再建立:
vim .github/workflows/build.yaml
目錄會變成:
.github/
└── workflows/
└── build.yaml
GitHub 會自動辨識:
.github/workflows/
裡面的 Workflow YAML。
先建立 build.yaml
vim .github/workflows/build.yaml
裡面:
name: Build API
on:
push:
branches:
- main
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" \
| docker login ghcr.io \
-u "${{ github.actor }}" \
--password-stdin
- name: Build
run: |
OWNER=$(
echo "${GITHUB_REPOSITORY_OWNER}" \
| tr '[:upper:]' '[:lower:]'
)
docker build \
-t ghcr.io/${OWNER}/cka-api:${GITHUB_SHA} \
./app
- name: Push
run: |
OWNER=$(
echo "${GITHUB_REPOSITORY_OWNER}" \
| tr '[:upper:]' '[:lower:]'
)
docker push \
ghcr.io/${OWNER}/cka-api:${GITHUB_SHA}
這份 YAML 可以把它理解成「告訴 GitHub 什麼時候要做事,以及要做哪些事」。
name: Build API 這個 Workflow 顯示在 Actions 頁面的名稱;on.push.branches: main 則代表只要有人 Push 到 main Branch,就觸發這套流程。permissions 是設定這次 Workflow 拿到的 GITHUB_TOKEN 有哪些權限,其中 contents: read 讓它讀取 Repository,packages: write 則讓它可以把 Docker Image Push 到 GHCR。jobs。這裡只有一個叫 build 的 Job,runs-on: ubuntu-latest 代表 GitHub 會準備一台 Ubuntu Runner。
steps 就是這台 Runner 依序做的事情:先用 actions/checkout 把程式碼抓下來,再登入 GHCRdocker build 建立 Imagedocker push 上傳。Image Tag 使用 ${GITHUB_SHA},也就是觸發這次 Workflow 的 Git Commit SHA,因此之後可以從 Image 反查到 Source Code。換句話說,這份 YAML 可以先讀成:
Push main
→ 開 Ubuntu Runner
→ 抓程式碼
→ 登入 GHCR
→ Build Image
→ Push Image 到 hub上
建立好 build.yaml 後,就來 Push 啦:
git add .
git commit -m "add github actions workflow"
git push
接著到:
GitHub Repository
→ Actions
就可以看到 Workflow。
現在:
git push
↓
GitHub Actions
↓
Docker Build
↓
GHCR
已經自動化了。
這裡我們沒有自己建立 Password。
GitHub Actions 執行 Workflow 時,GitHub 會自動提供:
GITHUB_TOKEN
它是一個暫時性的 Token。
例如:
permissions:
contents: read
packages: write
代表:
可以讀 Repository
+
可以寫 GitHub Packages / GHCR
因此 Workflow 才有權限:
docker push ghcr.io/...
假設 Production 現在跑:
cka-api:latest
凌晨出問題。
你第一個問題可能就是:
這到底是哪一版程式?
所以我們改成:
cka-api:<Git SHA>
Git SHA 是 Git Commit 的唯一識別碼。
因此可以一路追:
Running Image
↓
Image Tag
↓
Git SHA
↓
Git Commit
↓
Source Code
這種能力叫:
Traceability
也就是:
可追溯性。
我們現在的 kind Cluster 跑在 Apple Silicon Mac。
因此 Node 通常是:
arm64
GitHub-hosted Runner Build 出來的 Image,則可能是:
linux/amd64
所以更完整的做法是建立:
Multi-Architecture Image
讓同一個 Image 同時支援:
linux/amd64
linux/arm64
等等 Final Lab 我們就直接把這件事一起處理掉。
這是一個非常重要的觀念。
我們的:
kind Cluster
存在 Mac。
GitHub Actions Runner 卻存在:
GitHub Cloud
所以:
GitHub Runner
│
│ Internet
│
✕
│
Mac localhost
│
▼
kind Cluster
它們不是同一台機器。
即使把:
kubeconfig
放進 GitHub Secret,Runner 看到的:
localhost
127.0.0.1
也是 Runner 自己。
不是我們的 Mac。
因此我們不讓 GitHub Actions 直接 Push Deployment 到本機 kind。
這時就輪到:
GitOps
登場。

https://picluster.ricsanfre.com/docs/argocd/
傳統 Deployment 比較像:
CI
↓
kubectl apply
↓
Cluster
CI 主動把東西 Push 進 Kubernetes。
GitOps 則反過來:
Git Repository
▲
│ Compare
│
Argo CD
│
▼
Kubernetes Cluster
Argo CD 是 GitOps Controller。
它直接跑在 Kubernetes 裡,不斷比較:
Git Desired State
與:
Cluster Actual State
如果不同,就:
Sync
讓 Cluster 回到 Git 描述的狀態。
是不是非常熟悉?
因為 Kubernetes 從 Day 1 就一直在做:
Desired State
vs
Actual State
接下來不只停留在概念。
我們真的把目前的:
FastAPI
+
GitHub Actions
+
GHCR
+
Kustomize
+
Argo CD
+
kind
全部串起來。
最終希望完成:
Developer
│
│ git push
▼
GitHub
│
▼
GitHub Actions
│
├── Build Image
│
└── Push GHCR
│
▼
更新 Kustomize newTag
│
▼
Git Repository
▲
│ Watch
│
Argo CD
│
▼
kind Kubernetes
│
▼
cka-api 新版本
這就是今天真正的 Final Lab。
我們前面已經有:
deploy/
├── base/
│ ├── api.yaml
│ └── kustomization.yaml
│
└── overlays/
├── local/
│ └── kustomization.yaml
└── production/
└── kustomization.yaml
今天先讓 Argo CD 管理:
deploy/overlays/local
也就是我們現在的 kind Environment。
修改:
vim deploy/overlays/local/kustomization.yaml
例如:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
replicas:
- name: api
count: 1
images:
- name: cka-api
newName: ghcr.io/yifunlin/cka-api
newTag: bootstrapi
這份 YAML 不負責建立一套全新的 Deployment,而是在修改 base。
apiVersion 和 kind: Kustomization 是告訴 Kustomize「這是一份 Kustomize 設定」resources: ../../base 則表示先把 deploy/base 裡原本的 Kubernetes Resource 拿進來,再針對 Local 環境做調整。replicas 表示 Local 環境的 api Deployment 只需要 1 個 Pod;images 則是在改 Image。
name: cka-api 是要尋找 Base 裡原本使用的 cka-api,找到之後把它改成 ghcr.io/yifunlin/cka-api,再由 newTag 決定實際版本。也就是 Base 可以繼續保持通用設定,而 Local Overlay 只負責描述「這個環境和 Base 有哪些不同」。所以這份設定真正表達的是:
先使用 Base
↓
Local 環境 replicas 改成 1
↓
Image 改從 GHCR 取得
↓
使用指定的 newTag
目前寫的是 newTag: bootstrapi;如果這只是初始化用的 Placeholder,要注意 GHCR 必須真的有這個 Tag,否則在 GitHub Actions 更新成真正的 Git SHA 之前,Kubernetes 會 Pull 不到它。
這裡非常重要。
假設 Base Deployment 原本:
containers:
- name: api
image: cka-api:v4
Kustomize 會找到:
cka-api
並把它換成:
ghcr.io/yifunlin/cka-api:<新的 Git SHA>
所以我們不用每一次 Build 都直接修改:
deployment.yaml
只需要更新:
newTag
build.yaml:把 CI 和 GitOps 接起來前面的 Workflow 只有:
Build
↓
Push
但 GitOps 還缺一件事情:
Git 裡面的 Kubernetes Desired State 也要知道新的 Image Version。
所以把 build.yaml 的 Workflow 再改成:
name: Build API
on:
push:
branches:
- main
paths:
- "app/**"
- ".github/workflows/build.yaml"
permissions:
contents: write
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set image name
id: vars
run: |
OWNER=$(
echo "${GITHUB_REPOSITORY_OWNER}" \
| tr '[:upper:]' '[:lower:]'
)
echo "image=ghcr.io/${OWNER}/cka-api" \
>> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v6
with:
context: ./app
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ steps.vars.outputs.image }}:${{ github.sha }}
- name: Update Kustomize Image Tag
run: |
sed -i \
"s/newTag: .*/newTag: ${GITHUB_SHA}/" \
deploy/overlays/local/kustomization.yaml
- name: Commit GitOps Change
run: |
git config user.name "github-actions[bot]"
git config user.email \
"41898282+github-actions[bot]@users.noreply.github.com"
git add deploy/overlays/local/kustomization.yaml
git diff --cached --quiet \
&& exit 0
git commit \
-m "chore: deploy ${GITHUB_SHA}"
git push
build.yaml:把 CI 和 GitOps 接起來第二份 GitHub Actions YAML 是前面簡單版本的升級版。最大的不同,是它不再只是「Build Image → Push GHCR」,而是 Build 完之後還會去修改 Kustomize,所以整條 Pipeline 才真正能和 Argo CD 接起來。
你的 paths 限制 Workflow 主要只有在 app/** 或 Workflow 本身變動時才觸發,避免 GitHub Actions 自己修改 kustomization.yaml 後,又再次觸發 Build,形成循環。因為這次還要 Commit 回 Repository,所以 contents 也從 read 改成了 write。
Set image name 這一步先組出 ghcr.io/.../cka-api,並透過 $GITHUB_OUTPUT 保存給後面的 Step 使用。接著 QEMU 和 Docker Buildx 是為了建立 Multi-Architecture Image,因此同時 Build linux/amd64 和 linux/arm64,你的 Apple Silicon kind 才能使用同一份 Image。Build and Push 則把這個 Image 直接 Push 到 GHCR,而且 Tag 使用 ${{ github.sha }}。
後面才是 GitOps 最重要的部分。sed 會把:
newTag: 舊版本
直接改成這次新的 ${GITHUB_SHA}。接著 GitHub Actions 用 github-actions[bot] 的身份 Commit 這個變更,再 git push 回 GitHub。於是 Git Repository 裡描述的 Desired State 從「舊 Image」變成「新 Image」,Argo CD 才有東西可以偵測。
所以不要把這份 YAML 看成一大堆指令,它本質上只有:
Code 改變
↓
Build 新 Image
↓
Push GHCR
↓
Kustomize newTag 改成新 SHA
↓
Commit 回 Git
↓
等待 Argo CD 接手
這次多做了三件重要的事情。
第一:
Build AMD64 + ARM64
因此我們的 Apple Silicon kind 可以正常 Pull。
第二:
Image Tag = Git SHA
保有 Traceability。
第三,也是 GitOps 最重要的地方:
Build 完 Image
↓
修改 Kustomize newTag
↓
Commit 回 GitHub
因此 Git Repository 裡面的 Desired State 真的跟著版本一起變了。
我們現在設定:
paths:
- "app/**"
- ".github/workflows/build.yaml"
所以主要只有:
Application Source Code
改變時才 Build 新 Image。
GitHub Actions 後面自己修改:
deploy/overlays/local/kustomization.yaml
不需要再次 Build Image。
否則很容易變成:
Workflow 改 Git
↓
Git 又觸發 Workflow
↓
Workflow 又改 Git
↓
...
把 Trigger 範圍縮小,可以讓 Pipeline 的責任更清楚。
Push:
git add .
git commit -m "setup gitops pipeline"
git push
到:
GitHub Repository
→ Actions
應該可以看到:
Build API

會長得像這樣!有東西在跑 (名稱跟我不同沒關係!.筆者實作時有小修正commit一版)
開始執行。

成功後,到 GitHub 的:
Packages

應該會看到:
cka-api
裡面的版本 Tag 會是一串:
Git SHA

第一次建立 GHCR Package 時,它可能不是公開狀態。
為了讓這個 Lab 簡單一點,可以去 package management setting 把:
cka-api
Container Package 設定為:
Public


這樣 kind 就可以直接:
docker pull
而不需要額外建立 Registry Credential。
如果之後正式環境使用 Private Registry,再進一步使用:
imagePullSecrets
即可。
如果 Day 27 已經安裝,可跳過這兩個指令(建立 namespace 和 install ArgoCD)。
先建立 Namespace:
kubectl create namespace argocd
安裝 Argo CD:
kubectl apply \
-n argocd \
--server-side \
--force-conflicts \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
觀察:
kubectl get pods \
-n argocd \
-w

等主要 Component 逐漸:
Running
代表 Argo CD 已經起來。
而因為 Argo CD Server 預設沒有直接暴露到你的 Mac 外面,所以如果你想開:
https://localhost:8080 去看 Argo CD Web UI,
還是需要 Port Forward。這也是官方提供的存取方式之一。
Port Forward:
kubectl port-forward \
svc/argocd-server \
-n argocd \
8080:443
瀏覽器進:
https://localhost:8080
登入帳號:
admin
Password 就是剛才取得的 Initial Password。
如果你從來沒改過 Argo CD 的 admin 密碼,可以直接從 Kubernetes 裡的初始密碼 Secret 取出來。
執行:
kubectl -n argocd \
get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" \
| base64 -d
echo
會直接印出初始密碼。
然後登入:
Username: admin
Password: 剛剛輸出的密碼
就成功進去啦

現在要告訴 Argo CD:
我要你監控哪一個 Git Repository 的哪一個資料夾?
先建立:
mkdir -p argocd
建立:
vim argocd/cka-api-app.yaml
內容:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cka-api
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YIFUNLIN/k8s-30days.git
targetRevision: main
path: deploy/overlays/local
destination:
server: https://kubernetes.default.svc
namespace: cka-lab
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
這份 YAML 和前面兩份最大的差別,是它不是 GitHub Actions 設定,也不是 Kustomize 設定,而是 Argo CD 的 Application Resource。
apiVersion: argoproj.io/v1alpha1 和 kind: Application 表示這是一個 Argo CD 安裝 CRD 後才認得的 Custom Resource。metadata 裡則把這個 Application 命名成 cka-api,並建立在 argocd Namespace。source 描述的是 Desired State 從哪裡來。這裡告訴 Argo CD 去 YIFUNLIN/k8s-30days Repository,看 main Branch 裡的 deploy/overlays/local。Argo CD 會 Render 這個 Kustomize 目錄,算出最後應該存在的 Deployment、Service 等 Kubernetes Resource。destination 則回答相反的問題:算完之後要部署去哪裡? https://kubernetes.default.svc 看起來很像一般網址,但它其實不是 Internet 網址。它是 Kubernetes Cluster 內部的 Service DNS 名稱。代表 Argo CD 自己所在的這個 Kubernetes Cluster,而 namespace: cka-lab 代表最後把 Application 部署到 cka-lab Namespace。syncPolicy 決定同步策略。automated 代表不用每次手動按 Sync;prune: true 表示 Git 已經刪掉的 Resource,也可以從 Cluster 移除;selfHeal: true 則表示如果有人手動把 Cluster 改到和 Git 不同,Argo CD 可以把它修回 Git 描述的狀態。CreateNamespace=true 則是在目標 Namespace 不存在時允許 Argo CD 建立它。因此整份 Argo CD YAML 其實只是在回答:
我是誰?
cka-api
Desired State 在哪?
GitHub → main → deploy/overlays/local
要部署去哪?
目前這個 Cluster → cka-lab
不同步怎麼辦?
自動 Sync + Self Heal
我們設定:
automated:
prune: true
selfHeal: true
Automated Sync 代表:
Git 變了,Argo CD 可以自動同步。
Prune 代表:
Git 裡已經刪掉的 Resource,也可以從 Cluster 移除。
Self Heal 則更加符合 GitOps 精神。
假設有人偷偷:
kubectl scale deployment api \
-n cka-lab \
--replicas=10
但 Git 寫的是:
replicas: 2
那就是:
Git Desired State = 2
Cluster Actual State = 10
Argo CD 發現 Drift 後,可以把它修回:
2
這就是:
Self Healing
前面我們只是建立了:
argocd/cka-api-app.yaml
這個檔案目前還只是:
Git Repository 裡的一份 YAML 設定檔。
也就是說:
有 YAML 檔案
≠
Kubernetes 裡已經真的有這個 Resource
所以接下來要把它真正送進 Kubernetes。
先確認目前位於專案根目錄:
cd ~/k8s-30days
然後執行:
kubectl apply \
-f argocd/cka-api-app.yaml
這一步會把 YAML 裡定義的:
kind: Application
建立到:
argocd Namespace
裡。
因為我們前面安裝 Argo CD 時,不只是安裝一些 Pod。
Argo CD 也會替 Kubernetes 加入自己的:
CRD
也就是:
Custom Resource Definition
其中就包含:
Application
所以原本 Kubernetes 不認識:
kind: Application
但是安裝 Argo CD 後,Kubernetes API 就開始認得這種 Resource。
整個關係可以理解成:
Argo CD Installation
↓
安裝 Application CRD
↓
Kubernetes 開始認識 kind: Application
↓
kubectl apply cka-api-app.yaml
↓
建立 Application Resource
我們的:
argocd/cka-api-app.yaml
其實是在告訴 Argo CD 三件事:
第一:去哪裡找 Desired State?
第二:要監控哪個資料夾?
第三:要部署到哪個 Kubernetes Cluster / Namespace?
例如:
source:
repoURL: https://github.com/YIFUNLIN/k8s-30days.git
targetRevision: main
path: deploy/overlays/local
代表:
Git Repository:
https://github.com/YIFUNLIN/k8s-30days.git
Branch:
main
真正要監控的 Kubernetes 設定:
deploy/overlays/local
而:
destination:
server: https://kubernetes.default.svc
namespace: cka-lab
代表:
把這些 Resource 部署到 Argo CD 自己所在的 Kubernetes Cluster,目標 Namespace 是
cka-lab。
所以整體關係是:
GitHub Repository
│
│
▼
deploy/overlays/local
│
│ Desired State
▼
Argo CD
│
▼
cka-lab Namespace
這個:
https://kubernetes.default.svc
是 Kubernetes Cluster 內部用來存取:
Kubernetes API Server
的 Service DNS。
因為 Argo CD 本身就跑在這個 kind Cluster 裡,所以它可以直接透過:
kubernetes.default.svc
管理:
自己所在的這一個 Cluster。
因此這個 Lab 不需要另外設定一台遠端 Cluster。
先:
kubectl get applications \
-n argocd
應該會看到:
NAME SYNC STATUS HEALTH STATUS
cka-api ...

也可以看更詳細資訊:
kubectl describe application cka-api \
-n argocd
現在 Kubernetes 裡就真的存在:
Application: cka-api
一旦 Application 建立完成,Argo CD 的:
application-controller
就會開始持續做:
讀 Git
↓
Render Kustomize
↓
取得 Desired State
↓
讀 Kubernetes Cluster
↓
取得 Actual State
↓
比較兩者
↓
如果不同就 Sync
所以它的核心其實還是在做:
Desired State
vs
Actual State
只是這次:
Desired State
不是直接來自:
kubectl apply deployment.yaml
而是來自:
Git Repository
我們前面設定:
syncPolicy:
automated:
prune: true
selfHeal: true
所以 Argo CD 發現 Git 和 Cluster 不一致時,可以自動:
Sync
例如:
Git:
replicas = 1
Cluster:
replicas = 3
如果開:
selfHeal
Argo CD 就會嘗試把 Cluster 修回:
replicas = 1
這就是 GitOps 很重要的概念:
Git 才是 Desired State 的 Source of Truth。
查看:
kubectl get applications \
-n argocd
也可以打開 Argo CD Web UI,看:
Sync Status
Health Status
Git Revision
Resources
如果 Application 顯示:
Synced
Healthy
代表:
Git Desired State
↓
Argo CD
↓
Kubernetes Actual State
目前是一致的。
接著再查看:
kubectl get pods \
-n cka-lab
以及目前 Deployment 使用的 Image:
kubectl get deployment api \
-n cka-lab \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

如果看到:
ghcr.io/yifunlin/cka-api:<Git SHA>

代表:
Argo CD 已經開始根據 Git 裡的 Kustomize 設定管理這個 Deployment。
現在流程才真正完成:
Application YAML
↓
kubectl apply
↓
建立 Argo CD Application Resource
↓
Argo CD 開始 Watch Git
↓
同步 deploy/overlays/local
↓
管理 cka-lab
現在你只要在 127.0.0.1:8080 登入後,
就可以成功進來看到這個視覺化後的 Applications 了!

接下來我們會先詳細解釋相關原理:
前面執行:
kubectl apply \
-f argocd/cka-api-app.yaml
之後,我們不只是把一份 YAML 丟進 Kubernetes。
真正發生的是:
cka-api-app.yaml
↓
建立 Argo CD Application
↓
Argo CD 讀取 Git Repository
↓
讀取 deploy/overlays/local
↓
Render Kustomize
↓
取得完整 Kubernetes Desired State
↓
與 Cluster Actual State 比較
↓
Sync
↓
Web UI 建立 Resource Tree
所以現在打開 Argo CD Web UI,就可以看到剛才建立的:
cka-api
Application,以及它底下實際管理的 Kubernetes Resource。
這裡有一個很重要的觀念:
Argo CD 並不是把 Cluster 裡所有 Resource 全部列給你看,而是顯示這個 Application 所管理的 Resource。
我們的 Application 設定:
source:
repoURL: https://github.com/YIFUNLIN/k8s-30days.git
targetRevision: main
path: deploy/overlays/local
所以 Argo CD 會從:
deploy/overlays/local
開始 Render Kustomize。
如果這個 Overlay 最後產生的是:
Service api
Deployment api
Argo CD 的 cka-api Application 就會管理這些 Resource。
因此現在畫面裡看到:
cka-api
│
├── Service api
│
└── Deployment api
│
├── ReplicaSet
├── ReplicaSet
├── ReplicaSet
└── ReplicaSet
│
└── Pod
不是 Argo CD 自己額外建立了一套 Kubernetes Resource。
而是:
Argo CD 把 Kubernetes 原本的 Resource 關係視覺化了。
這張圖裡一個很容易讓人疑惑的地方,就是:
Deployment api
下面怎麼會有好幾個:
ReplicaSet
例如畫面中可以看到:
api-855d7cff4
api-584697869f
api-98458988
api-7d96c7cfbf
這其實正好是前面學 Deployment 時提過的:
Deployment
↓
ReplicaSet
↓
Pod
當 Deployment 的 Pod Template 發生改變,例如:
Image Tag 改變
Kubernetes 不會直接修改舊 ReplicaSet。
而是建立一個:
新的 ReplicaSet

例如:
Revision 1
↓
ReplicaSet A
Revision 2
↓
ReplicaSet B
Revision 3
↓
ReplicaSet C
Revision 4
↓
ReplicaSet D
所以經過幾次:
Image 更新
Rolling Update
之後,就會留下多個 ReplicaSet。
舊 ReplicaSet 通常會被 Scale 到:
0 Pod
但仍然保留一段時間,讓 Deployment 可以:
Rollback
而目前最新的 ReplicaSet 才會真正維持正在執行的 Pod。
所以這張 Argo CD Resource Tree,其實把 Deployment 的關係畫得非常直觀:
Deployment api
│
├── 舊 ReplicaSet rev1
├── 舊 ReplicaSet rev2
├── 舊 ReplicaSet rev3
│
└── Current ReplicaSet rev4
│
▼
Pod
這也是為什麼 Argo CD UI 很適合拿來理解 Kubernetes Resource 之間的關係。
畫面左上角可以看到兩個非常重要的狀態:
APP HEALTH
Healthy
以及:
SYNC STATUS
Synced
這兩個看起來很像,但代表完全不同的事情。
Synced 回答的是:
Git 裡面描述的 Desired State,和 Cluster 現在的 Actual State 一不一樣?
例如 Git 寫:
replicas: 1
Cluster 也是:
replicas = 1
而 Image 也完全相同,那就是:
Synced
如果有人直接執行:
kubectl scale deployment api \
-n cka-lab \
--replicas=3
但是 Git 還是:
replicas: 1
Argo CD 就可能暫時看到:
OutOfSync
因為:
Git Desired State
≠
Cluster Actual State
Healthy 則是在回答另外一件事:
這些 Resource 本身目前運作得健不健康?
例如 Deployment:
replicas = 1
而且:
1 個 Pod Ready
那 Deployment 就可能顯示:
Healthy
所以:
Synced
是在看:
Git vs Cluster
而:
Healthy
是在看:
Resource 本身有沒有正常運作
這兩件事情不能混在一起。
甚至可能出現:
Synced
但
Degraded
代表:
Git 和 Cluster 設定完全一致,但 Application 本身其實跑壞了。
例如 Git 裡本來就寫了一個不存在的 Image:
image: ghcr.io/example/api:v999999
Argo CD 很忠實地把它部署出去,所以:
Desired State = Actual State
→ Synced
但是 Pod:
ImagePullBackOff
因此 Application:
不 Healthy
這個差別非常重要。
畫面中可以看到:
Synced to main (ef10f26)

這表示 Argo CD 目前同步的是:
main Branch
中的某一個 Git Commit。
例如:
ef10f26
就是 Git SHA 的縮寫。
這代表我們現在已經可以從:
Kubernetes Running Resource
一路追到:
Argo CD
↓
Git Revision
↓
Git Commit
↓
Source Code / Manifest
這正是前面提過的:
Traceability
也就是可追溯性。
而畫面中甚至可以看到:
Author:
github-actions[bot]
Comment:
chore: deploy ...

這代表目前這次 Deployment 的 Desired State,正是我們前面的:
GitHub Actions
自動修改:
Kustomize newTag
之後 Commit 回 GitHub 的結果。
也就是整條 Chain 已經真的串起來:
GitHub Actions
↓
更新 Kustomize
↓
github-actions[bot] Commit
↓
Argo CD 發現 Git Revision 改變
↓
Sync
↓
Kubernetes
不是只有流程圖而已。
右邊還可以看到:
LAST SYNC
Sync OK
這是在告訴我們:
最近一次 Argo CD 執行 Sync 的結果。
例如:
Sync OK
代表最近一次同步成功。
下面還能看到:
時間
Author
Commit Message
Git Revision
所以當 Production 發生:
「剛剛部署完之後 API 就掛了」
Argo CD UI 可以很快幫你回答:
剛剛到底部署了哪個 Git Commit?
誰產生這次變更?
什麼時間 Sync?
Sync 成功還是失敗?
這也是 GitOps 在 Troubleshooting 時非常有價值的地方。
畫面最主要的區域就是:
Application Resource Tree
我們目前可以看到:
cka-api
│
├── svc/api
│
└── deploy/api
│
├── rs
├── rs
├── rs
└── rs
│
└── pod
這些縮寫分別是:
svc
→ Service
deploy
→ Deployment
rs
→ ReplicaSet
pod
→ Pod
也就是把 Kubernetes 原本:
Service
Deployment
↓
ReplicaSet
↓
Pod
的關係直接視覺化。
點擊其中一個 Resource,例如:
Deployment api

通常可以進一步查看:
Manifest
Live Manifest
Events
Health
Resource Details
點擊 Pod,也可以進一步查看 Pod 狀態,甚至從 Argo CD 介面進一步觀察 Log。

所以 Argo CD 不只是:
Deployment Tool
它也可以變成一個很好用的:
Kubernetes Application View
上方可以看到:
DETAILS
DIFF
SYNC
SYNC STATUS
HISTORY AND ROLLBACK
DELETE
REFRESH
其中最值得先理解的是:
DIFF
它會比較:
Git Desired State
和:
Cluster Actual State
到底差在哪裡。
例如有人手動修改 Deployment,DIFF 就能很直觀地看到:
Git:
replicas: 1
Cluster:
replicas: 3
這對 Troubleshooting 非常有用。
SYNC 則代表:

手動要求 Argo CD 把 Git Desired State 套用到 Cluster。
我們現在已經設定:
syncPolicy:
automated:
所以通常不需要一直自己按:
SYNC
Git 變動之後,Argo CD 可以自己處理。
但如果沒有開 Automated Sync,就可以從這裡手動 Sync。
HISTORY AND ROLLBACK

則可以查看:
過去同步過哪些 Revision
以及在適當情況下回到前面的版本。
這也是 GitOps 的另一個優點:
每次 Deployment
↓
都有 Git Revision
↓
有歷史
↓
可以追蹤
REFRESH
則是要求 Argo CD:
再重新取得 Git 與 Cluster 狀態並比較。
如果剛剛 Push Git,Web UI 看起來還沒更新,就可以:
Refresh
讓狀態重新整理。
至於:
DELETE
就不要把它當成普通的重新整理按鈕亂按。
它是刪除這個 Argo CD Application 的操作,而且依刪除方式與設定不同,可能連被 Application 管理的 Resource 都一起處理,所以實驗時也要看清楚再操作。
最後再把整件事情串一次。
不是:
kubectl port-forward
讓 Service 和 Deployment 出現在 UI。
Port Forward 只是:
你的 Browser
↓
localhost:8080
↓
argocd-server
讓我們可以開 Argo CD 網頁而已。
真正關鍵的是 Step 7:
kubectl apply \
-f argocd/cka-api-app.yaml
它建立:
Argo CD Application
然後:
Application
↓
source.repoURL
↓
main
↓
deploy/overlays/local
↓
Kustomize Render
↓
得到 Service + Deployment
↓
Argo CD Sync
↓
Kubernetes
最後 Argo CD 再從 Kubernetes API 取得 Resource 之間的關係,因此 Web UI 才能畫出:
cka-api
│
┌────────┴────────┐
▼ ▼
Service api Deployment api
│
▼
ReplicaSet
│
▼
Pod
所以這張 UI 其實就是前面所有概念的視覺化版本:
Git
↓
Desired State
↓
Argo CD
↓
Kubernetes Resource
↓
Actual State
而
接下來 Step 8 才要做真正的 End-to-End 測試:
修改 main.py
↓
git push
↓
GitHub Actions
↓
Build / Push GHCR
↓
更新 Kustomize newTag
↓
Argo CD 偵測變化
↓
自動 Sync
↓
Deployment Rolling Update
這樣我們才可以驗證:
整條 CI + GitOps Pipeline 真的有串起來。
前面我們已經完成:
GitHub Actions
↓
GHCR
↓
Kustomize
↓
Argo CD
↓
kind Kubernetes
而且 Argo CD 的 Application 已經顯示:
Synced
Healthy
現在才要做今天最重要的實驗:
只修改 Application Source Code,後面全部交給 Pipeline 自己完成。
這一次我們不再手動:
docker build
kind load
kubectl apply Deployment
而是看看:
git push
之後,整套系統能不能自己把新版本部署到 Kubernetes。
這一步之後會變得很重要。
因為我們的 GitHub Actions 不只是 Build Image,它還會:
修改 Kustomize newTag
↓
git commit
↓
git push 回 GitHub
所以 GitHub Repository 有可能比我們 Mac 上的 Local Repository 更新。
所以當你想要Push 時,就會失敗
下面的<補充> 會進行詳細說明,為何會突然本地落後於 repo
因此以後在開始修改前,都先做:
git pull --rebase origin main
rebase 在這裡可以簡單理解成:
先把 GitHub 上最新的 Commit 拉回來,再把自己的工作接在最新版本後面。
這樣可以避免等等 Push 時發現:
fetch first
打開:
vim app/main.py
我們原本首頁是:
@app.get('/')
def root():
visits = redis_client.incr("visits")
return {
"message":"Hello from k8s",
"visits": visits
}
只需要把:
Hello from k8s
改成:
Hello from GitOps!
變成:
@app.get('/')
def root():
visits = redis_client.incr("visits")
return {
"message":"Hello from GitOps!",
"visits": visits
}
其他 Endpoint 不需要刪。
例如之前為了 HPA Load Test 建立的:
@app.get('/work')
def work():
end_time = time.time() + 0.2
counter = 0
while time.time() < end_time:
counter += 1
return {
"iterations": counter
}
可以繼續保留。
它只有在有人 Request:
/work
時才會故意消耗一些 CPU,用來測試 HPA,平常不會自己一直執行。
接著:
git add app/main.py
建立 Commit:
git commit -m "update api message"
然後:
git push
這時我們只做了一件事:
把新的 Source Code Push 到 GitHub。
接下來不要再:
docker build
kind load
kubectl apply
而是開始讓 Pipeline 自己跑。
有時可能看到:
! [rejected] main -> main (fetch first)
Updates were rejected because the remote contains work
that you do not have locally.
這不一定代表出錯。
在我們現在的架構裡,很可能是因為:
GitHub Actions
↓
Build Image
↓
更新 Kustomize newTag
↓
Commit
↓
Push 回 GitHub
所以:
GitHub main
已經比:
Local main
多了一個 GitHub Actions Bot 的 Commit。
這時執行:
git pull --rebase origin main
它會把:
Remote Commit
+
你的 Local Commit
重新接成一條線:
原本:
┌─ GitHub Actions Commit
────────┤
└─ 你的 Commit
rebase 後:
──────── GitHub Actions Commit
│
▼
你的 Commit
完成後再:
git push
即可。
如果想先確認 GitHub 到底多了哪些 Commit,可以:
git fetch origin
再:
git log HEAD..origin/main \
--oneline
如果看到:
chore: deploy <Git SHA>
就代表那正是 GitHub Actions 更新 Kustomize Image Version 產生的 Commit。
這次真正要理解的是後面的整條 Chain。
第一步:
git push
GitHub 發現:
app/**
有改變。
因此觸發:
GitHub Actions
接著 Runner:
Checkout Source Code
↓
Build linux/amd64 + linux/arm64 Image
↓
Push Image 到 GHCR
新 Image 會類似:
ghcr.io/yifunlin/cka-api:<Git SHA>
接下來 GitHub Actions 修改:
deploy/overlays/local/kustomization.yaml
原本可能是:
images:
- name: cka-api
newName: ghcr.io/yifunlin/cka-api
newTag: 舊的-Git-SHA
改成:
images:
- name: cka-api
newName: ghcr.io/yifunlin/cka-api
newTag: 新的-Git-SHA
然後 GitHub Actions 再:
git commit
↓
git push
把新的 Desired State 寫回 GitHub。
現在 Git 裡的:
deploy/overlays/local
改變了。
而我們前面建立的:
Application: cka-api
正好在監控:
GitHub
↓
main
↓
deploy/overlays/local
所以 Argo CD 會比較:
Git Desired State
ghcr.io/yifunlin/cka-api:<新 SHA>
和:
Cluster Actual State
ghcr.io/yifunlin/cka-api:<舊 SHA>
發現:
Desired State
≠
Actual State
於是 Automated Sync 開始作用。
Argo CD 更新 Deployment 後,Kubernetes 發現:
Pod Template
裡面的 Image 改了。
例如:
舊:
ghcr.io/yifunlin/cka-api:abc123
新:
ghcr.io/yifunlin/cka-api:def456
Deployment Controller 就會開始:
Rolling Update
也就是:
建立新 Pod
↓
新 Pod Pull 新 Image
↓
新 Pod Ready
↓
移除舊 Pod
因此我們可以直接觀察:
kubectl get pods \
-n cka-lab \
-w
可能會看到:
舊 api Pod Running
新 api Pod ContainerCreating
新 api Pod Running
舊 api Pod Terminating

這就是 Rolling Update 真正在發生。
另外開一個 Terminal:
kubectl rollout status \
deployment/api \
-n cka-lab
成功後會看到類似:
deployment "api" successfully rolled out

接著確認目前 Kubernetes 真正使用的 Image:
kubectl get deployment api \
-n cka-lab \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

應該會看到:
ghcr.io/yifunlin/cka-api:<新的 Git SHA>
也可以查看 Argo CD:
kubectl get applications \
-n argocd

應該最後再次回到:
Synced
Healthy
整個狀態就變成:
Git newTag
=
Deployment Image
=
Pod Image
如果目前 api Service Port 是:
80
先:
kubectl port-forward \
svc/api \
-n cka-lab \
8000:80
再開另一個 Terminal:
curl http://127.0.0.1:8000/
原本可能得到:
{
"message": "Hello from k8s",
"visits": 10
}
現在應該變成:
{
"message": "Hello from GitOps!",
"visits": 1
}

注意:
visits
還會繼續增加。
因為它存在:
Redis
裡,而不是存在 API Pod 本身。
所以即使 API Deployment Rolling Update、Pod 被換掉:
API Pod
舊 → 新
Redis 裡的資料仍然存在。
這也順便再次驗證了我們前面學到的:
Stateless Application
+
External State
概念。
我們這次真正做的只有:
修改 main.py
↓
git commit
↓
git push
後面全部變成:
Developer
│
│ git push
▼
GitHub Repository
│
▼
GitHub Actions
│
├── Build Multi-Arch Image
│
└── Push GHCR
│
▼
更新 Kustomize newTag
│
▼
Git Commit
│
▼
Argo CD
│
│ Compare
▼
Desired State ≠ Actual State
│
▼
Sync
│
▼
Deployment
│
▼
Rolling Update
│
▼
New Pod
│
▼
Hello from GitOps!
而我們完全沒有手動:
docker build
kind load
kubectl apply Deployment
這就是今天最重要的成果。
以前只有我們自己修改 Repository:
Mac
↓
GitHub
但現在多了一個角色:
GitHub Actions Bot
它也會修改 Repository:
Mac Developer
│
▼
GitHub
▲
│
GitHub Actions Bot
所以 GitHub 已經不再只是:
被動接收我們的 Push
它自己也可能產生新的 Commit。
因此之後開始工作前,可以養成:
git pull --rebase origin main
的習慣。
確保:
Local main
先跟:
GitHub main
同步。
在我們這個學習 Project 裡,Application Code 與 GitOps Config 放在同一個 Repository,這種情況尤其容易遇到。
真正大型 Production 環境也常進一步把:
Application Repository
與:
GitOps Repository
拆開。
例如:
cka-api
└── Source Code
cka-gitops
└── Kubernetes Manifest
這樣 CI 更新 Deployment Version 時,就不會一直改 Application Source Repository。
但目前 Day 30 的 Lab 使用:
Single Repository
反而比較容易一次看懂:
Code
→ CI
→ Image
→ Git Desired State
→ Argo CD
→ Kubernetes
整條 GitOps Pipeline 是怎麼串起來的。
GitHub Actions 負責:
Source Code
↓
Build
↓
Container Image
↓
Publish GHCR
↓
更新 Image Version
Argo CD 負責:
Watch Git
↓
Compare Desired / Actual State
↓
Sync
↓
Reconcile
Kubernetes 則負責:
Deployment
↓
Scheduler
↓
kubelet
↓
containerd
↓
Pod
三者不是互相取代。
而是一層接著一層。
前面我們已經把 CI、GitOps 與 Kubernetes 串起來了。最後要練的,其實也是實務上最重要的能力之一:Troubleshooting。
真實世界通常不會直接告訴你「Service Selector 寫錯」或「Image Tag 不存在」,你看到的往往只有一句:
API DOWN
所以排錯最重要的不是看到錯誤就開始亂下指令,而是先回答:
問題到底發生在哪一層?
可以先記住這條主線:
症狀
↓
判斷 Layer
↓
找 Evidence
↓
定位原因
↓
再處理
只要 kubectl 還能正常使用,我通常會先看:
kubectl get pods -n cka-lab -o wide
kubectl get events \
-n cka-lab \
--sort-by=.lastTimestamp
第一步不是急著修,而是先看 Pod 到底是 Pending、ImagePullBackOff、CrashLoopBackOff,還是看起來 Running 卻沒有 Ready。
如果是 Pending,通常代表 Container 還沒真正開始跑,這時應該先查 Scheduling:
kubectl describe pod POD_NAME -n cka-lab
重點看最下面的 Events。像是 CPU / Memory 不足、NodeSelector、Affinity、Taint,甚至 PVC 尚未 Bound,都可能讓 Pod 一直卡在 Pending。
如果看到:
ImagePullBackOff
代表 kubelet 連 Image 都還沒成功拉下來。
這時查:
kubectl describe pod POD_NAME -n cka-lab
優先確認 Image Name、Tag、Registry 權限、CPU Architecture,以及 Image 是否真的存在。
如果看到:
CrashLoopBackOff
情況就不同了。
它代表 Container 有成功啟動,但是程式一直 Crash,又被 Kubernetes 重啟。
這時才適合開始看 Log:
kubectl logs POD_NAME -n cka-lab
kubectl logs POD_NAME \
-n cka-lab \
--previous
--previous 特別重要,因為目前的 Container 可能已經重新啟動,真正 Crash 前的錯誤反而存在上一個 Container Instance 裡。
有時候 Pod 顯示:
Running
但可能是:
0/1
這通常代表 Container 還活著,但 Readiness Probe 沒有通過。
這時就要開始檢查:
Probe
ConfigMap
Secret
Environment Variable
Application Log
所以:
Running ≠ Healthy
Kubernetes 不只在意 Process 有沒有活著,也在意它是不是真的可以提供服務。
假設 Pod 已經:
Running 1/1
API 還是連不到,就往下一層看:
kubectl get svc -n cka-lab
kubectl get endpointslice -n cka-lab
這裡最常遇到的問題就是:
Service Selector
≠
Pod Label
結果 Service 雖然存在,背後卻根本沒有 Endpoint。
如果 Endpoint 正常,再繼續往:
DNS
NetworkPolicy
Gateway
HTTPRoute
檢查。
也就是從最內層一路往外查:
Pod
↓
Service
↓
EndpointSlice
↓
NetworkPolicy / DNS
↓
Gateway / HTTPRoute
↓
External Client
這樣就不需要一開始看到 API 掛掉,就同時懷疑十幾種東西。
如果指令直接看到:
Forbidden
通常就應該先想到:
RBAC
可以利用:
kubectl auth can-i ...
確認目前的 User 或 ServiceAccount 到底有沒有對應權限。
如果看到:
Node NotReady
問題就已經不只是 Application 了,而要繼續往:
Node
↓
kubelet
↓
Container Runtime
↓
CNI
檢查。
最後,如果連:
kubectl get nodes
都無法正常連到 Cluster,那才要把焦點移到 Control Plane,例如 API Server、Static Pod、憑證與 etcd。
所以整套 Troubleshooting 思路其實可以濃縮成:
kubectl 能不能用?
↓
Pod 正常嗎?
↓
Container 正常嗎?
↓
Service 有 Endpoint 嗎?
↓
Network 正常嗎?
↓
External Traffic 正常嗎?
一層一層往下查,比背幾十個 kubectl 指令重要得多。
回頭看 Day 1,我們可能只是知道:
Kubernetes 可以管理 Container
但走到 Day 30,看到:
Deployment
Service
ConfigMap
Secret
PVC
StatefulSet
Scheduler
HPA
NetworkPolicy
RBAC
Helm
Kustomize
Gateway API
CRD
Operator
kubelet
containerd
etcd
Argo CD
已經不再只是彼此獨立的名詞。
我們現在可以把它們真正串起來:
Developer
↓
Git Push
↓
GitHub Actions
↓
Build Image
↓
GHCR
↓
更新 Git Desired State
↓
Argo CD
↓
Kubernetes
↓
Scheduler
↓
kubelet
↓
containerd
↓
Pod
而當中任何一層出問題,我們也開始知道應該從哪裡找 Evidence。
這其實才是這 30 天最重要的成果。
不是背完所有 Kubernetes Resource,而是開始理解:
**一個 Application 從程式碼,到 Container,到 Kubernetes,再到真正提供服務,中間到底發生