iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構系列 第 24

[ Day 24 ] 將微服務搬上 K8s - 實戰撰寫 Deployment, Service YAML 檔並部署至 Minikube 叢集!

  • 分享至 

  • xImage
  •  

昨天我們學習了高可用性(High Availability, HA)核心思維,並在本地建立好了 Minikube 測試叢集與 kubectl 命令列工具。

今天,我們將手把手帶你解密 K8s 兩大最核心的資源:Deployment(Pod 副本總管)Service(流量進入點與負載均衡器),並親手寫出符合 Production 規範的 YAML 設定檔,一鍵部署到 Minikube,驗證 K8s 驚人的自動修復(Self-healing)!

核心架構圖解:Pod, Deployment 與 Service 的鐵三角關係

在寫 YAML 之前,我們必須先搞懂這三個元件在 K8s 內部是如何交織協作的:
https://ithelp.ithome.com.tw/upload/images/20260920/20183864ZOdEXGKmHp.png

  1. Pod(K8s 最小原子單位)
    • Pod 是包裹著我們 Docker 容器(my-app:latest)的膠囊。
    • Pod 的生命週期是短暫且隨機的(Ephemeral)。每一次重啟或毀滅重建,Pod 的內部 IP 都會改變(例如從 10.244.0.5 變成 10.244.0.8)。
  2. Deployment(Pod 的自動化總管家)
    • 我們絕不會直接手動建立 Pod,而是建立 Deployment
    • Deployment 負責維護我們宣告的 replicas: 3(3 副本高可用)。如果其中一個 Pod 崩潰,Deployment 會自動補上一個新的,維持 3 個 Pod 永遠在線上。
  3. Service(永不改變的固定門牌)
    • 既然 Pod 的 IP 隨時會變,前端要怎麼存取?這就是 Service 出場的時刻!
    • Service 擁有一個永不改變的虛擬 Cluster IP,並利用 selector 標籤(Labels)自動歸納後方的 3 個 Pod,自動將外部請求以 Round-Robin(輪詢)方式均衡分發給健康的 Pod。

K8s YAML 與 Dockerfile / Docker Compose 的關係

我自己剛開始學k8s的時候常常有疑問,這跟我之前寫的 Dockerfiledocker-compose.yml 有什麼關係?如果我的 Image 名稱叫 my-app 該怎麼改?後來也花了一些時間來釐清這些關聯:

1. 與 Dockerfile 的關係:建材原料 vs 施工藍圖

  • Dockerfile 是用來將專案程式碼打包成「Docker 鏡像(Image)」的藍圖。
  • K8s YAML 裡的 image: my-app:v1,讀取的正是你當初用 Dockerfile 打包出來的這個 Image!如果沒有 Dockerfile 產出 Image,K8s 根本沒有建材可以拉起 Pod。

2. 與 docker-compose.yml 的關係:單機編排 vs 雲端叢集

  • docker-compose.yml 是單機(Single Node)環境下的容器編排工具,裡面宣告的 image, ports, environment 語法概念與 K8s 很像。
  • docker-compose.yml 只能管理單台機器,沒有跨節點調度、沒有自我修復、也沒有 HPA 自動水平擴充。
  • 把微服務從 docker-compose.yml 搬到 K8s,就是把原本在 docker-compose.yml 裡宣告的屬性,拆解並升級到 K8s 的 Deployment(負責 Pod 副本與資源上限)與 Service(負責網路暴露與負載均衡)兩個 YAML 檔案中。

本地實作

撰寫 Deployment YAML 檔 (s3-app-deployment.yaml)

請在專案根目錄建立 k8s/ 資料夾,並新增 s3-app-deployment.yaml 檔案:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: s3-app-deployment
  labels:
    app: s3-service
spec:
  # 💡 1. 宣告副本數:開 3 個 Pod 實現單機多節點高可用 (HA)
  replicas: 3
  # 💡 2. 標籤選擇器:Deployment 透過貼紙 (Label) 管理 Pod
  selector:
    matchLabels:
      app: my-app  # your_image
  # 💡 3. Pod 範本 (Template):定義每個 Pod 裡面要裝什麼容器
  template:
    metadata:
      labels:
        app: my-app  # your_image
    spec:
      containers:
      - name: s3-service-container
        image: my-app:latest  # your_image
        # 💡 強制使用本地 Minikube 鏡像 (不從 Docker Hub 拉取)
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: http
        # 💡 4. 環境變數注入 (AWS S3 憑證與設定)
        env:
        - name: AWS_ACCESS_KEY_ID
          value: "{ypur_ak}"
        - name: AWS_SECRET_ACCESS_KEY
          value: "{ypur_sk}"
        # 💡 5. 資源配額硬體限制 (Resource Limits)
        resources:
          requests:
            cpu: "250m"       # 保留 0.25 核 CPU
            memory: "256Mi"   # 保留 256MB 記憶體
          limits:
            cpu: "500m"       # 上限 0.5 核 CPU
            memory: "512Mi"   # 上限 512MB 記憶體
  • 將一些變數填入你自己的image_name,以及aws的key,才能正常執行喔
  • replicas: 3:實現3 節點負載分流!任何一個 Pod 壞掉,其餘 2 個 Pod 繼續接單,系統達到 preliminary 零中斷。
  • matchLabels: app: s3-service:只要 Pod 貼有 app: s3-service 的標籤,就會被這個 Deployment 納入管轄。
  • resources.limits:防止某個 Pod 發生記憶體洩漏(Memory Leak)吞噬整台宿主機,精確將 Pod 限制在 0.5 核 / 512MB 內。

撰寫 Service YAML 檔 (s3-app-service.yaml)

接著建立 k8s/s3-app-service.yaml 檔案,為這 3 個 Pod 建立對外門牌:

apiVersion: v1
kind: Service
metadata:
  name: s3-app-service
spec:
  # 💡 1. Service 類型:NodePort 允許我們從本地端存取 Minikube 叢集
  type: NodePort

  # 💡 2. 標籤選擇器:將流量路由給帶有 app: my-app 貼紙的 Pods
  selector:
    app: my-app # your_image

  ports:
  - name: http
    port: 8080        # Service 內部的 ClusterPort
    targetPort: 8080  # 映射到 Pod 容器內部的 Port
    nodePort: 30080   # 暴露給宿主機本地端存取的對外 Port (30000-32767)

  • type: NodePort:K8s 將宿主機(Minikube)的 30080 Port 開放,並自動綁定到 Service,讓本地瀏覽器與 JMeter 能打入叢集內部。
  • selector: app: my-app:Service 透過這個標籤,自動在背景維護一個名為 Endpoints 的動態 IP 列表(包含 3 個 Pod 的 IP),並自動執行負載均衡分流。

一鍵部署至 Minikube

現在我們打開 Terminal,執行這套標準的 K8s 部署流程:

步驟 1:將 Docker 鏡像載入 Minikube 內部

因為 Minikube 是運行在獨立的虛擬機/容器內,我們要先把本地 build 好的 my-app:latest 鏡像傳進 Minikube:

minikube image load my-app:latest

步驟 2:一鍵套用 YAML 檔宣告裝備!

使用 kubectl apply -f 指令,套用整個 k8s/ 資料夾下的所有 YAML 檔:

kubectl apply -f k8s/
  • 畫面顯示 deployment.apps/s3-app-deployment createdservice/s3-app-service created

步驟 3:觀察 Pods 與 Service 部署狀態

K8s 在短短 12 秒內自動建好了 3 個獨立且各具 Pod IP 的微服務容器,並且全部處於 Running 狀態!

kubectl get pods -o wide

步驟 4:測試 Service 負載均衡與 API 呼叫

輸入以下指令取得 Minikube 的服務對外存取 URL:

minikube service s3-app-service --url

https://ithelp.ithome.com.tw/upload/images/20260920/20183864RmNU6u07LE.png

測試產生的url,列出api,發現回應成功!就代表服務部署成功啦!
https://ithelp.ithome.com.tw/upload/images/20260920/20183864hvW2WUjWhm.png

Pod IP (10.244.0.10) 功用

在這個kubectl get pods -o wide階段會發現每個pod其實都有一個自己的pod id,那又為什麼還要執行 minikube service --url 產生 URL?

原因很簡單 10.244.0.x 是 K8s 叢集內部的私人分機 IP 我們的本地電腦在實體網路上根本直接連不到它。反觀 http://127.0.0.1:53048 是外部跨界專線 (SSH Tunnel),因為 Docker Desktop 跑在獨立的虛擬網路區段,Minikube 在本地 127.0.0.1:53048 開了一條管道,直通 K8s 內部 Service,並自動做負載平衡將請求均衡分發給 .10.11.12 這 3 個 Pod!

https://ithelp.ithome.com.tw/upload/images/20260920/20183864TuYEajkRlA.png
直接在本地端用curl打pod ip是一定連不到的喔

宣告式 API 與 K8s 的自我復原實測

為了管理成百上千台伺服器與容器,Kubernetes 引入了雲端原生最偉大的設計思想 : 宣告式 API (Declarative API)。

  • 傳統指令式 (Imperative) : 手動執行 docker run -d -p 8080:8080 my-app ➔ 萬一容器掛了,服務不會理你,除非你再敲一次命令。
  • K8s 宣告式 (Declarative) : 訂ymal合約 replicas: 3 我期望 (Desired State) 隨時保持 3 個健康的 my-app 容器在運作 ➔ K8s Control Plane 的 Controller Loop 會 7x24 小時不斷對照 「當前狀態 (Current State) vs 期望狀態 (Desired State)」 若發現少了一個 Pod,立刻自動長出一個補充!

現在,我們來驗證宣告式 API所講的親手殺掉一個 Pod 時,K8s 會發生什麼事?

  1. 開 Terminal 視窗 1,實時監控 Pod 狀態:
kubectl get pods -w  
  1. 開 Terminal 視窗 2,手動殺掉 Pod 1:
kubectl delete pod s3-app-deployment-756d49c74-6kdhc
  1. 觀察 Terminal 視窗 1 的即時演變:

https://ithelp.ithome.com.tw/upload/images/20260920/20183864yf2vzNpVR8.png

  • 第 0 秒 (偵測狀態不合):當 6kdhc 收到刪除指令進入 Terminating 狀態時,K8s 大腦(kube-controller-manager)的調和迴圈(Reconciliation Loop)在第 0 秒立刻監測到「當前狀態 (2 個 Pod) != YAML 宣告的期望狀態 (3 個 Pod)」。
  • 第 0 秒 ~ 第 1 秒 (自動補建與拉起):大腦 0 秒內自動發起救援,立刻調度建立全新 Pod j4dzb(經歷 Pending -> ContainerCreating)。
  • 第 1 秒 (無縫接管):全新 Pod j4dzb 在 1 秒內成功啟動並進入 Running 狀態!舊 Pod 6kdhc 平靜退場清空,整個自癒過程在 1 秒內由 K8s 宣告式 API 全自動完成,外部請求完全由其餘健康 Pod 無感接管!

總結

今天我們正式告別了單機手動時代,完成了一次極其壯烈的微服務架構升級:

  • 從Docker單點容器 ➔ 3 副本高可用叢集:不再害怕單一 Pod 崩潰或記憶體爆炸。
  • 從動態 IP 混亂 ➔ Service 固定門牌與負載均衡:實現自動化動態服務發現(Service Discovery)。
  • 從人工維運 ➔ 1秒極速修復 (Self-Healing):宣告式 API 確保系統狀態永遠符合預期。

這正是 Kubernetes 能夠統治雲端世界的核心魅力所在!而明天將進一步說明如果在修復的過程中一直有流量寫入,又該怎麼面對,以及之前在 Day 17 用 Docker Compose 架好的 Prometheus 與 Grafana 監控,該如何跨網路無縫對接 K8s 叢集?請大家拭目以待了!


上一篇
[ Day 23 ] Docker單機節點極限:為什麼我們必須走向 K8s?高可用性 (HA) 核心概念與思維轉換
系列文
學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言