3 minute read

Tags: , , , , , ,

公司有一台機器被灌成全 K8s 環境,同事開了一個 namespace 給我用,我只能透過 kubectl 跟 YAML 操作。 目標:在 namespace 裡起一個 Ollama 服務,跑 qwen2.5:7bqwen2.5vl:7b 兩個模型,讓同網段的 Mac 可以呼叫。


一、Kubernetes 是什麼

一句話:一個幫你管理容器的作業系統控制平台。

你不是直接跟機器說「幫我開 container、開 port、掛硬碟」, 而是用 YAML 跟 Kubernetes 說:我想要的狀態是什麼,然後它負責實現。

這叫做 declarative(宣告式)

  • 你不是一步步下指令
  • 你是描述目標狀態,系統自己去達成

二、核心概念速查

概念 白話解釋
Cluster 整個 K8s 環境,可以是一台或多台機器
Node 一台實際跑容器的機器(實體機或 VM)
Namespace K8s 裡的隔離空間,像資料夾,你的東西放這裡不會跟別人混
Pod K8s 最小的執行單位,容器的外殼,通常裡面跑一個 container
Deployment Pod 的管理器,負責維持你要的 Pod 數量,掛了會自動補
Service 穩定的網路入口,把流量導到後面的 Pod(Pod IP 會變,Service 不會)
PVC PersistentVolumeClaim,向 K8s 申請一塊持久化儲存空間

容易搞混的點

  • Pod 不是 VM:它是一個執行中的應用單位,不是完整主機
  • Service 不是程式:它只轉發流量,不會幫你跑 Ollama
  • Deployment 不是 Pod:Deployment 管 Pod,更新 Deployment 底下 Pod 可能被換掉
  • 容器裡的資料預設不可靠:所以模型要掛 PVC
  • YAML 不是腳本:不是一步步執行,而是描述目標狀態

三、這次的架構

Mac(你的電腦)
  │
  │  HTTP API
  ▼
Node IP : NodePort (31144)
  │
  ▼
Kubernetes Service
  │
  ▼
Ollama Pod (port 11434)
  │
  ▼
/root/.ollama → 掛到 PVC(模型存這裡)

只需要 3 個 K8s 資源:

  1. PVC — 準備硬碟存模型
  2. Deployment — 跑 Ollama 程式
  3. Service (NodePort) — 讓外面打得到

一個 Ollama 服務就能管多個 model,不需要開兩個 Pod。呼叫時指定 model 名稱即可。


四、部署前先跟同事確認

  1. namespace 名稱是什麼
  2. 能不能建 PVC(不能的話每次 Pod 重建都要重抓模型)
  3. 能不能開 NodePort(有些公司會限制,預設範圍 30000-32767)
  4. 哪台 Node IP 你的 Mac 可以 ping 到
  5. 那台機器有沒有 GPU

五、完整 YAML(帶逐行註解)

YOUR_NAMESPACE 換成你的 namespace 名稱後直接用。

# ============================================
# 第一段:PVC — 申請持久化儲存空間
# ============================================
apiVersion: v1                        # K8s API 版本
kind: PersistentVolumeClaim           # 資源類型:持久化儲存申請
metadata:
  name: ollama-models-pvc             # PVC 名稱,等等 Deployment 會引用
  namespace: YOUR_NAMESPACE           # 你的 namespace
spec:
  accessModes:
    - ReadWriteOnce                   # 同一時間給一個 node 讀寫掛載(單 Pod 夠用)
  resources:
    requests:
      storage: 80Gi                   # 申請 80Gi,兩個 7B 模型大概要這麼多
---
# ============================================
# 第二段:Deployment — 跑 Ollama 服務
# ============================================
apiVersion: apps/v1                   # Deployment 用的 API 版本
kind: Deployment                      # 資源類型:部署管理器
metadata:
  name: ollama                        # Deployment 名稱
  namespace: YOUR_NAMESPACE
spec:
  replicas: 1                         # 維持 1 個 Pod(掛了會自動補)
  selector:
    matchLabels:
      app: ollama                     # 用 label 選擇要管理的 Pod
  template:                           # 以下定義 Pod 的模板
    metadata:
      labels:
        app: ollama                   # Pod 的標籤,要跟上面 selector 對上
    spec:
      containers:
        - name: ollama                # container 名稱
          image: ollama/ollama:latest # 使用的 Docker image
          imagePullPolicy: IfNotPresent  # 本機有就不重抓
          ports:
            - containerPort: 11434    # 告訴 K8s 這個 container 用 11434 port
          env:
            - name: OLLAMA_HOST
              value: "0.0.0.0:11434"  # 重要!預設只聽 127.0.0.1,改成 0.0.0.0 才能被外部連到
          volumeMounts:
            - name: ollama-models
              mountPath: /root/.ollama  # Ollama 預設存模型的路徑,掛到 PVC
          resources:
            requests:                 # 最少需要的資源(排程用)
              cpu: "2"
              memory: "8Gi"
            limits:                   # 最多可用的資源(超過會被限制/kill)
              cpu: "4"
              memory: "16Gi"
      volumes:
        - name: ollama-models         # volume 名稱,跟上面 volumeMounts 對應
          persistentVolumeClaim:
            claimName: ollama-models-pvc  # 引用第一段建的 PVC
---
# ============================================
# 第三段:Service — 對外暴露服務
# ============================================
apiVersion: v1
kind: Service
metadata:
  name: ollama-nodeport               # Service 名稱
  namespace: YOUR_NAMESPACE
spec:
  type: NodePort                      # 在每台 node 開一個固定 port 對外
  selector:
    app: ollama                       # 把流量導給 label 是 app=ollama 的 Pod
  ports:
    - name: http
      port: 11434                     # Service 自己的 port(cluster 內用)
      targetPort: 11434               # 轉發到 Pod/container 的 port
      nodePort: 31144                 # 對外開在 node 上的 port(Mac 打這個)

Port 三層關係

Mac 打 → NodeIP:31144 (nodePort)
           ↓
      Service:11434 (port,cluster 內用)
           ↓
      Pod:11434 (targetPort,container 真正聽的)

六、部署步驟

Step 1:套用 YAML

kubectl apply -f ollama.yaml

Step 2:確認資源都起來了

# 看 Pod 狀態(要等到 Running)
kubectl get pod -n YOUR_NAMESPACE -o wide

# 看 Service
kubectl get svc -n YOUR_NAMESPACE

# 看 PVC(狀態要是 Bound)
kubectl get pvc -n YOUR_NAMESPACE

Step 3:看 log 確認 Ollama 正常啟動

kubectl logs -n YOUR_NAMESPACE deploy/ollama -f

Step 4:進 Pod 拉模型

kubectl exec -it -n YOUR_NAMESPACE deploy/ollama -- sh

進去後:

ollama pull qwen2.5:7b
ollama pull qwen2.5vl:7b
ollama list              # 確認兩個模型都在

Step 5:找 Node IP

kubectl get nodes -o wide

找到 node 的 INTERNAL-IP,例如 192.168.1.50

Step 6:從 Mac 測試

# 確認服務活著
curl http://192.168.1.50:31144/api/tags

# 呼叫純文字模型
curl http://192.168.1.50:31144/api/generate -d '{
  "model": "qwen2.5:7b",
  "prompt": "你好",
  "stream": false
}'

# 用 chat 格式
curl http://192.168.1.50:31144/api/chat -d '{
  "model": "qwen2.5:7b",
  "messages": [
    { "role": "user", "content": "幫我列出三個導入地端 LLM 的注意事項" }
  ],
  "stream": false
}'

七、如果不能開 NodePort

port-forward 替代(開發測試用,不是正式方案):

kubectl port-forward -n YOUR_NAMESPACE svc/ollama-nodeport 11434:11434 --address 0.0.0.0

然後 Mac 打 http://127.0.0.1:11434/api/tags


八、如果有 GPU

需要叢集已裝好 NVIDIA device plugin,然後在 Deployment 的 resources 加:

resources:
  limits:
    nvidia.com/gpu: "1"

沒裝 device plugin 的話加了也沒用,先跟同事確認。


九、常用 kubectl 指令速查

# 看 namespace 裡所有資源
kubectl get all -n YOUR_NAMESPACE

# 看 Pod 詳細狀態(排錯用)
kubectl describe pod <pod-name> -n YOUR_NAMESPACE

# 看 Service 詳細
kubectl describe svc ollama-nodeport -n YOUR_NAMESPACE

# 看 PVC 詳細
kubectl describe pvc ollama-models-pvc -n YOUR_NAMESPACE

# 看 log
kubectl logs -n YOUR_NAMESPACE deploy/ollama -f

# 進 Pod
kubectl exec -it -n YOUR_NAMESPACE deploy/ollama -- sh

十、常見問題排查

問題 可能原因
Pod 一直 Pending CPU/memory request 太高、namespace quota 不夠、沒有可用 node、PVC 沒綁成功
Pod 起來又掛掉 記憶體不足(OOMKilled)、image 啟動失敗、OLLAMA_HOST 沒設對
Mac 打不到 NodePort 被防火牆擋、打錯 node IP、Service selector 沒對到 Pod、Pod 沒 ready
模型不見了 沒掛 PVC、掛載路徑錯、Pod 重建用到新的空 volume

十一、第一階段驗收標準

完成以下 4 件事就算成功:

  • kubectl get pod 顯示 ollama 狀態是 Running
  • kubectl exec 進 Pod 後 ollama list 看到 qwen2.5:7bqwen2.5vl:7b
  • Mac 執行 curl http://<node-ip>:31144/api/tags 有回應
  • Mac 呼叫 qwen2.5:7b 的 generate API 能拿到回覆

十二、注意事項

  • Ollama 模型是按需載入ollama list 有模型不代表常駐記憶體,用到時才載入,反而省資源
  • qwen2.5vl:7b(視覺模型)比純文字更吃記憶體:RAM 不夠的話兩個模型可能無法同時順暢跑
  • NodePort 通了不代表所有 node 都能通:要看 Pod 排到哪台 node、防火牆規則
  • PVC 很重要:沒掛的話 Pod 重建就要重抓模型,浪費時間

十三、Kubernetes vs Docker 對照

Docker(你自己手動) Kubernetes(交給平台)
docker run Deployment
-v mount volume PVC + volumeMounts
-p publish port Service (NodePort)
--restart=always Deployment replicas
手動管理 宣告式,K8s 自動維護

十四、第一階段先懂這五個就夠

  1. Pod — 最小執行單位
  2. Deployment — Pod 的管理器
  3. Service — 穩定的網路入口
  4. PVC — 持久化儲存
  5. Namespace — 隔離空間

其他(Ingress、ConfigMap、Secret、StatefulSet、Helm、HPA…)之後再學。