---
# System prepended metadata

title: "\U0001F680 使用 K3s、GitHub 和 ArgoCD 實現 GitOps 自動化部署"
tags: [k3s, GitHub, ArgoCD]

---

# 🚀 使用 K3s、GitHub 和 ArgoCD 實現 GitOps 自動化部署

這份文件將引導你設置一個完整的 CI/CD 流程。當你推送程式碼到 GitHub 時，會自動觸發 GitHub Actions 來建置 Docker 映像檔、推送到 GitHub Container Registry (GHCR)，並更新 Kubernetes (K3s) 的部署清單。最後，ArgoCD 會偵測到部署清單的變更，並自動將應用程式更新到你的 K3s 叢集中。

---

## 步驟 1：📦 安裝 K3s

K3s 是一個輕量級的 Kubernetes 發行版，非常適合在資源有限的環境中運行。

```bash
# 執行此命令來下載並安裝 K3s
curl -sfL https://get.k3s.io | sh - 

# 為了讓 kubectl 可以存取 K3s 叢集，需要修改設定檔的權限
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
```
> **說明：**
> *   `curl` 指令會從官方網站下載 K3s 的安裝腳本。
> *   `sh -` 會直接執行該腳本來進行安裝。
> *   `chmod 644` 讓非 root 用戶也能讀取 K3s 的 kubeconfig 檔案，這樣您才能使用 `kubectl` 指令與叢集互動。

---

## 步驟 2：⛵️ 安裝 ArgoCD

ArgoCD 是一個用於 Kubernetes 的宣告式 GitOps 持續交付工具。

```bash
# 首先，為 ArgoCD 建立一個專屬的命名空間
kubectl create namespace argocd

# 接著，從 ArgoCD 的官方 GitHub 儲存庫應用其安裝清單
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
> **說明：**
> *   `kubectl create namespace argocd`：在您的 K3s 叢集中建立一個名為 `argocd` 的隔離區，所有 ArgoCD 相關的資源都將部署在這裡。
> *   `kubectl apply -f ...`：這個指令會下載 ArgoCD 官方提供的 YAML 清單檔案，並根據該檔案在 `argocd` 命名空間中建立所有必要的資源，例如 Deployments、Services、CRDs 等。

---

## 步驟 3：🐙 建立應用程式儲存庫 (python-app-repo)

這個私有儲存庫將存放您的應用程式原始碼和 GitHub Actions 工作流程。

1.  在 GitHub 上建立一個**私有 (Private)** 儲存庫，命名為 `python-app-repo`。
2.  在儲存庫中建立以下檔案：

    *   **`app.py` (一個簡單的 Flask 應用)**
        ```python
        from flask import Flask
        app = Flask(__name__)

        @app.route('/')
        def hello_world():
            return 'Hello from k3s, ArgoCD, and GHCR!'

        if __name__ == '__main__':
            app.run(host='0.0.0.0', port=5000)
        ```

    *   **`Dockerfile` (用於建置應用程式映像)**
        ```dockerfile
        FROM python:3.12-slim
        WORKDIR /app
        RUN pip install Flask
        COPY app.py .
        EXPOSE 5000
        CMD ["python", "app.py"]
        ```

    *   **`.github/workflows/build-push-update.yml` (GitHub Actions 工作流程)**
        ```yaml
        name: Build, Push to GHCR, and Update Manifests

        on:
          push:
            branches:
              - main # 只在 main 分支 push 時觸發

        jobs:
          build_and_update:
            runs-on: ubuntu-latest
            permissions:
              contents: read
              packages: write # 允許推送到 GHCR

            steps:
              - name: Checkout App Repo
                uses: actions/checkout@v3

              - name: Log in to GHCR
                uses: docker/login-action@v2
                with:
                  registry: ghcr.io
                  username: ${{ github.actor }}
                  password: ${{ secrets.GITHUB_TOKEN }}

              - name: Get Image Tag
                id: meta
                run: echo "TAG=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT

              - name: Build and Push to GHCR
                uses: docker/build-push-action@v4
                with:
                  context: .
                  push: true
                  tags: ghcr.io/${{ github.repository_owner }}/python-app:${{ steps.meta.outputs.TAG }}

              - name: Checkout Manifests Repo
                uses: actions/checkout@v3
                with:
                  repository: <GITHUB_USERNAME>/k8s-manifests-repo # 替換成您的部署儲存庫
                  path: k8s-manifests-repo
                  token: ${{ secrets.DEPLOY_PAT }} # 需要設定 PAT
                  ref: main # 推送指定分支
              - name: Update Deployment Image
                run: |
                  OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
                  sed -i "s|image: ghcr.io/${OWNER_LC}/python-app:.*|image: ghcr.io/${OWNER_LC}/python-app:${{ steps.meta.outputs.TAG }}|g" k8s-manifests-repo/deployment.yaml

              - name: Commit and Push Manifest Changes
                run: |
                  cd k8s-manifests-repo
                  git config --global user.name 'github-actions[bot]'
                  git config --global user.email 'github-actions[bot]@users.noreply.github.com'
                  git commit -am "Update image to ${{ steps.meta.outputs.TAG }}" || echo "No changes to commit"
                  git push
        ```

3.  **設定 `DEPLOY_PAT` Secret**

    > 此工作流程需要一個 Personal Access Token (PAT) 才能推送到另一個儲存庫 (`k8s-manifests-repo`)。

    *   **產生 PAT**：
        1.  前往 GitHub **Settings > Developer settings > Personal access tokens > Tokens (classic)**。
        2.  點擊 **"Generate new token" (classic)**。
        3.  最重要的：勾選 **`repo`** 權限。這將允許它存取並推送到您的所有儲存庫。
        4.  產生 Token 並立即複製它 (離開頁面後將無法再次看到)。
    *   **在 `python-app-repo` 中儲存 Secret**：
        1.  前往 `python-app-repo` 儲存庫。
        2.  點擊 **Settings > Secrets and variables > Actions**。
        3.  在 "Repository secrets" 區域，點擊 **"New repository secret"**。
        4.  **Name**: `DEPLOY_PAT`
        5.  **Value**: 貼上剛剛複製的 PAT (它看起來像 `ghp_...`)。
        6.  點擊 **"Add secret"**。

---

## 步驟 4：📝 建立部署清單儲存庫 (k8s-manifests-repo)

這個公開儲存庫將存放 Kubernetes 部署清單，ArgoCD 會監控此儲存庫的變更。

1.  在 GitHub 上建立一個**公開 (Public)** 儲存庫，命名為 `k8s-manifests-repo`。
2.  在儲存庫中建立以下 YAML 檔案：

    *   **`namespace.yaml`**
        ```yaml
        apiVersion: v1
        kind: Namespace
        metadata:
          name: python-app-ns
        ```

    *   **`deployment.yaml`**
        ```yaml
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: python-app
          namespace: python-app-ns
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: python-app
          template:
            metadata:
              labels:
                app: python-app
            spec:
              containers:
              - name: web
                # 注意：請將 <GITHUB_USERNAME> 替換成 GitHub 用戶名 (全小寫)
                image: ghcr.io/<github_username>/python-app:initial-tag
                ports:
                - containerPort: 5000
              imagePullSecrets:
              - name: ghcr-creds # 關鍵：k3s 需要這個 Secret 來拉取私有映像
        ```
        > **注意：** `image` 欄位中的 `initial-tag` 是一個佔位符，當 GitHub Actions 第一次成功運行後，它會被自動更新。

    *   **`service.yaml`**
        ```yaml
        apiVersion: v1
        kind: Service
        metadata:
          name: python-app-svc
          namespace: python-app-ns
        spec:
          selector:
            app: python-app
          ports:
          - protocol: TCP
            port: 80
            targetPort: 5000
          type: NodePort
        ```

---

## 步驟 5：🔑 建立用於拉取映像的 PAT

K3s 需要一個 Token 才能從私有的 GitHub Container Registry (GHCR) 拉取 Docker 映像。

1.  前往 GitHub **Settings > Developer settings > Personal access tokens > Tokens (classic)**。
2.  點擊 **"Generate new token" (classic)**。
3.  勾選 **`read:packages`** 權限。
4.  點擊 **"Generate token"** 並複製它。

---

## 步驟 6：🔒 在 K3s 中建立映像拉取 Secret

將上一步產生的 Token 提供給 K3s，讓它可以通過身份驗證。

在 Linux 主機上執行以下指令：
```bash
kubectl create secret docker-registry ghcr-creds \
  --namespace=python-app-ns \
  --docker-server=ghcr.io \
  --docker-username=<GITHUB_USERNAME> \
  --docker-password=<步驟5產生的TOKEN>
```
> **說明：**
> *   `ghcr-creds`：這是 Secret 的名稱，必須與 `deployment.yaml` 中的 `imagePullSecrets` 名稱一致。
> *   `--namespace=python-app-ns`：確保這個 Secret 建立在您的應用程式所在的命名空間中。
> *   `--docker-username`：您的 GitHub 用戶名。
> *   `--docker-password`：貼上您在步驟 5 中產生的 `read:packages` Token。

---

## 步驟 7：🤖 建立 ArgoCD 應用程式

現在，我們需要告訴 ArgoCD 要監控哪個 Git 儲存庫以及要將它部署到哪裡。

1.  在 Linux 主機上，執行以下指令來建立 `argocd-app.yaml` 檔案。
    > **注意：** 請記得將 **`<GITHUB_USERNAME>`** 替換成您自己的 GitHub 用戶名。

    ```yaml
    # argocd-app.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      # 1. 應用程式的名稱
      name: python-app
      # 2. ArgoCD Application CRD 必須建立在 ArgoCD 所在的命名空間
      namespace: argocd
    spec:
      # 3. ArgoCD 專案，使用預設的 'default' 即可
      project: default

      # 4. 來源 (Source) - 您的 Git 儲存庫
      source:
        # 5. 替換成您的 "公開" k8s-manifests-repo URL
        repoURL: https://github.com/**`<GITHUB_USERNAME>`**/k8s-manifests-repo.git

        # 6. 要監控的 Git 分支
        targetRevision: main

        # 7. YAML 檔案所在的儲存庫路徑 ( '.' 代表根目錄)
        path: .

      # 8. 目的地 (Destination) - 您的 k3s 叢集
      destination:
        # 9. 'https://kubernetes.default.svc' 是 ArgoCD 內部的 API 伺服器地址
        server: https://kubernetes.default.svc

        # 10. 您希望將應用程式部署到 k3s 上的哪個命名空間
        #     這必須與 k8s-manifests-repo/deployment.yaml 中的 namespace 一致
        namespace: python-app-ns

      # 11. 同步策略 (Sync Policy)
      syncPolicy:
        # 12. 啟用自動同步
        automated:
          # (推薦) 當 Git 中的資源被刪除時，也自動從 k3s 中刪除
          prune: true
          # (推薦) 自動修復叢集狀態與 Git 狀態的偏差
          selfHeal: true

        # (可選) 確保 ArgoCD 在部署前建立 'python-app-ns' 命名空間
        syncOptions:
        - CreateNamespace=true
    ```

---

## 步驟 8：✅ 應用 ArgoCD 應用程式清單

執行此指令後，ArgoCD 將開始同步到 `k8s-manifests-repo` 儲存庫。

```bash
kubectl apply -f argocd-app.yaml
```

---

## 步驟 9：🔍 驗證部署

現在，您可以檢查應用程式的狀態。

```bash
# 查看 Pod 的啟動狀態，確認它們是否為 "Running"
kubectl get pods -n python-app-ns

# 查看 Service 的 Port，找到 NodePort 對應的外部端口
# 例如：80:31234/TCP，其中 31234 就是外部端口
kubectl get service python-app-svc -n python-app-ns

# 查看節點 (Node) 的外部 IP
kubectl get nodes -o wide
```

> **如何存取應用程式？**
>
> 在瀏覽器中輸入 `http://<節點的外部IP>:<NodePort端口>` 即可看到 "Hello from k3s, ArgoCD, and GHCR!" 的訊息。