Skip to main content

7 posts tagged with "k8s"

View All Tags

Wei Ji

K8s 不是一個完整解決方案,K8s 只是框架。

驚不驚喜?意不意外?

當你安裝了 Docker 之後就可以開始打包 image 並運行程式,甚至可以使用 Dokcer Compose 指令開始進行容器編排 (Orchestration)。

反觀,對於一個 K8s 使用者而言,生態系內有很多「口味」供你選擇:Minikube、MicroK8s、K3s、GKE、EKS...但是當你選擇原味 (Vanilla)時,你可能會發現歷經一番波折安裝完成之後,你還是什麼都不能幹:不能正常建立網路連線、不能正常的掛載持久化實體 (Volume)...

為什麼?因為 K8s 本身不是開箱即用解決方案,它只是一種框架。

不是開箱即用的網路

info

這個段落會多次提及 OSI 的 L4 和 L7,對兩者差異不清楚的讀者建議先去查點資料,不然閱讀起來可能會有點吃力。

我在前一篇文章中介紹了 Service,以及內網模式 ClusterIP 與節點開埠 NodePort,即便是 NodePort 也不像是標準佈署使用的模式,因為它只能開奇怪的埠號。這是因為 Service 其實還有兩個生產環境在使用的模式:LoadBalancer 模式或是加掛一個 Ingress。

以 ServiceLB (LoadBalancer) 為例,K8s 只提供一個 L4 的抽象層,具體的實做依然需要別人來完成:

info

LB (LoadBalancer) Controller 是一個方便讀者理解的稱呼,實際情況比較複雜,有其他專門的術語用來稱呼這個東西。

在雲端環境(如 Google, AWS)這是由雲端供應商實作的,可能是一團閉源商業軟體和硬體級 LB 整合而成;在 K3s 中這是由 klipper-lb 透過 hostPort 實作的。

Ingress 也是相同的狀況,K8s 只提供一個 L7 的抽象層,具體的實做依然需要別人來完成:

實作可以是 Nginx 也可以是 Traefik。

不是開箱即用的持久化儲存

K8s 在設計上就是一個分散式運行的框架,當 Pod 可能分散在多個不同的主機(節點)時,我們就不能像 Docker 那樣直接用某個路徑當作持久化實體。

在 K8s 的世界中,Volume 通常是一個 SDS (Software-defined storage); 在 K8s 的世界中,網路線就是 SATA 線。

細節我不在此解釋,簡單來說在 K8s 要幫 Container 掛載 Volume 需要經過層層抽象,並且最後實際的儲存實體並不在 K8s 內實作:

info

你也可以在 K8s 設定 storageClassName: local-storage 的 Volume,但是這樣你可能需要額外配置讓 Pod 只能佈署在特定的 Node 上。

info

Storage Backend 同樣是一個方便讀者理解的稱呼,並不是 K8s 內的標準術語。

Wei Ji

Longhorn 可以用很多方式安裝,包含最基本的 kubectl 指令:

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.10.1/deploy/longhorn.yaml

不過等等!不要急著下指令!讓我們瞄一眼裡面有些什麼:

wget https://raw.githubusercontent.com/longhorn/longhorn/v1.10.1/deploy/longhorn.yaml

是的,這是一個有四千多行的 YAML,善於偷懶的聰明開發者們當然不會想直接跟這團東西打交道。

info

這個數字可以當作一個參考,一個完整的雲原生軟體佈署到 K8s 需要的聲明的資訊大概會達到這個量體。

Helm

就像 Longhorn 需要四千多行的 YAML 一樣,還有許許多多的雲原生應用軟體實際都是由大量的 K8s 資源或實體交錯編排而成,於是就有了 Helm 這個工具的誕生:一個 K8s 世界的 APT (Advanced Packaging Tool)。

在 Helm 的世界,這一堆聲明的 YAML 被包裝成一個稱作 Chart 的東西,「安裝」(佈署應用程式到 K8s)大概像這樣:

helm repo add longhorn https://charts.longhorn.io
helm repo update
helm install longhorn longhorn/longhorn \
--namespace longhorn-system \
--create-namespace \
--version 1.10.1

Helm 還有另外一個重要的功能:對 Chart 傳入變數,也是今天我要使用 Helm 的主要原因。

用 Helm 安裝 Longhorn

info

這個段落的內容主要參考 Longhorn 的官方文件1

首先下載參數檔:

curl -Lo values.yaml https://raw.githubusercontent.com/longhorn/longhorn/refs/tags/v1.10.1/chart/values.yaml

根據需求修改 values.yaml,以我目前的需求為例,分別是:

  • 持久化須儲存到外部 DAS 的掛載點。
  • 目前只有一個工作節點,所以副本只有一份。
defaultSettings:
defaultDataPath: /mnt/das-storage
defaultReplicaCount: 1

安裝 Longhorn 到 K8s:

helm install longhorn longhorn/longhorn \
--namespace longhorn-system \
--create-namespace \
--version 1.10.1 \
--values values.yaml

拉取 Image 需要時間,過程中可以使用指令檢查是不是所有 Pod 已經就位:

$ kubectl -n longhorn-system get pod
NAME READY STATUS RESTARTS AGE
engine-image-ei-3154f3aa-tsq6p 0/1 ContainerCreating 0 23s
longhorn-driver-deployer-58768fb7fd-ktpq8 1/1 Running 0 2m58s
longhorn-manager-bsqms 2/2 Running 2 (44s ago) 2m58s
longhorn-ui-7b9c99fd9-j8bxb 1/1 Running 0 2m58s
longhorn-ui-7b9c99fd9-w2l6b 1/1 Running 0 2m58s

執行 Tunnel 連線到 Longhorn 的 Dashboard2

kubectl port-forward \
service/longhorn-frontend \
-n longhorn-system 3002:80 \
--address 0.0.0.0

Footnotes

  1. Longhorn | Documentation. Retrieved 2026-01-05, from https://longhorn.io/docs/1.10.1/advanced-resources/deploy/customizing-default-settings/

  2. Longhorn 部署筆記 | 翠鳥圖書館 Project Halcyon Library. Retrieved 2026-01-05, from https://wiki.pha.pub/books/109-TAs/page/longhorn

Wei Ji

接續我在前一篇貼文指出的:K8s 本身只是框架,並沒有包含 SDS (Software-defined storage) 的具體實作:

Longhorn 則是其中一種 SDS 實作:

Longhorn 巧妙的運用 K8s 作為基礎設施,將 SDS 運行在 Cluster 內部,並且利用 K8s 的多節點特性運行多個實例,並在多個實例上建立資料冗餘實現分散式副本:

Wei Ji

安裝

懶人安裝法:

curl -sfL https://get.k3s.io | sh - 

檢查一下有沒有正常:

sudo k3s kubectl get node 

設定

從安裝 K3s 的機器複製 /etc/rancher/k3s/k3s.yaml 到想要遠端連線的機器(Client 端)的 ~/.kube/config1

info

要留意 K8s 的憑證會過期這件事。

Footnotes

  1. Cluster Access | K3s. Retrieved 2026-01-02, from https://docs.k3s.io/cluster-access

Wei Ji

Pod

聲明一個 Pod:

apiVersion: v1
kind: Pod
metadata:
name: awesome-pod
labels:
app: awesomeApp
spec:
containers:
- name: awesome-container
image: mendhak/http-https-echo:38
ports:
- containerPort: 8080

根據聲明建立資源(在這個例子中是建立 Pod):

kubectl create -f sample.yaml

接著使用這個指令建立隧道 (tunnel):

kubectl port-forward awesome-pod 3001:8080

接著就可以透過這個連結訪問剛剛建立的 Pod 了: http://localhost:3001

這個通道是臨時的,中止指令就會消滅通道,使用過 ngrok 的人或許會對這種感覺不陌生,它就像是「反向的 ngrok」,暫時將 Cluster 內特定的資源暴露到本機上。

info

玩耍結束後不要忘記銷毀資源:

kubectl delete -f sample.yaml

Service

Pod 在 K8s 的世界裡其實是雜魚、消耗品,可能會故障然後被清除掉之後被新建的 Pod 取代,又或是為了負載需求而被複製成一堆一樣參數的 Pod,Pod 的 IP 也因此是浮動的,實際使用你不會直接連線到特定的 Pod,而是透過一層抽象找到「在運行這種服務的 Pod」,那個抽象就是 Service。這個抽象不只是用於外部訪問,也包含 Pod 對 Pod 的內部連線。

ClusterIP

一個 Service 大概長得像這樣:

apiVersion: v1
kind: Service
metadata:
name: awesome-service
spec:
selector:
app: awesomeApp
type: ClusterIP
ports:
- protocol: TCP
port: 3001
targetPort: 8080
完整的檔案如下: sample.yaml
apiVersion: v1
kind: Pod
metadata:
name: awesome-pod
labels:
app: awesomeApp
spec:
containers:
- name: awesome-container
image: mendhak/http-https-echo:38
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: awesome-service
spec:
selector:
app: awesomeApp
type: ClusterIP
ports:
- protocol: TCP
port: 3001
targetPort: 8080
# 用一樣的指令把服務帶起來:
kubectl create -f sample.yaml

# 開 tunnel
kubectl port-forward service/awesome-service 3002:3001

只是這次我們連線的目標不是 Pod 而是 Service: http://localhost:3002

ClusterIP 尚未真正對外暴露我們的服務,這個模式主要給 Pod 內部訪問用(路徑3→2)1

NodePort

apiVersion: v1
kind: Pod
metadata:
name: awesome-pod
labels:
app: awesomeApp
spec:
containers:
- name: awesome-container
image: mendhak/http-https-echo:38
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: awesome-service
spec:
selector:
app: awesomeApp
type: NodePort
ports:
- protocol: TCP
port: 3001
targetPort: 8080
nodePort: 30390

接著用一樣的指令把服務帶起來:

kubectl create -f sample.yaml

這次從 Node 的 IP 訪問(例如: http://192.168.0.123:30390

info

Service 除了 ClusterIP 和 NodePort 以外,還有其他種類,不過在此不做過多的解釋,因為不是本文重點。

Deployment

Pod 在 K8s 的世界裡其實是雜魚、消耗品

是的,在 K8s 我們一般不會直接佈署 Pod,而是透過 Deployment 組件(或是其他類似功能的組件)來間接的佈署 Pod:

apiVersion: apps/v1
kind: Deployment
metadata:
name: awesome-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: awesomeApp
spec:
containers:
- name: kubernetes-demo-container
image: mendhak/http-https-echo:38
ports:
- containerPort: 8080
selector:
matchLabels:
app: awesomeApp
---
apiVersion: v1
kind: Service
metadata:
name: awesome-service
spec:
selector:
app: awesomeApp
type: NodePort
ports:
- protocol: TCP
port: 3001
targetPort: 8080
nodePort: 30390

你可以用瀏覽器打開它(例如: http://192.168.0.123:30390), 不過你可能只會看到相同的 os.hostname,試試多執行幾次 curl

curl http://192.168.0.123:30390/ | jq .os.hostname

Footnotes

  1. Kubernetes Service 概念詳解 | Kubernetes. Retrieved 2026-01-01, from https://tachingchen.com/tw/blog/kubernetes-service/

Wei Ji

  • Node
    • 一個安裝 K8s 的實體機器或虛擬機器。
  • Cluster
    • 多個 Node 構成的叢集。
  • Pod
    • K8s 的最小佈署單位。
    • 通常包含一個 Container。
    • 視情況可以包含多個 Container。
  • Container
    • OCI (Open Container Initiative) 容器。

Namespace

在同一個 Cluster 下可以再設定一個 Namespace 來隔離資源,知道有這個東西就好,初學者可以先不管它的存在。

Wei Ji

最近想學 K8s 然後陸續把 Homelab 的服務從 Docker Swarm 移過去,不過一直靜不下心好好讀它,思緒糊成一團,於是想說先把一些已經知道的事情整理一下。

動機

info

因為我實際上還未實際使用 K8s,所以以下可以當成「對 K8s 的美好幻想」。

簡單紀錄一下幾個讓我想學 K8s 的動機。

Image Volume

前一陣子在鼓搗 Keyclaok,並且外掛了兩個組件:

Keycloak 的插件機制是將 *.jar 檔案放到 /opt/keycloak/providers/ 下,待啟動時被載入。

在 OCI (Open Container Initiative) 佈署下處理這種這整合有兩種方法:

  • 建置階段:直接在 Dockerfile 用 COPY 完成組裝。
  • 佈署階段:透過 Volume 將 jar 檔案掛載進容器。

前者缺乏彈性;後者 Docker 不支援,但是 K8s 支援1

info

話說回來這幾天在讀 Docker Compose 的 spec 看到這東西(image):

// service.volumes.type
"type": {
"type": "string",
"enum": ["bind", "volume", "tmpfs", "cluster", "npipe", "image"],
"description": "The mount type: bind for mounting host directories, volume for named volumes, tmpfs for temporary filesystems, cluster for cluster volumes, npipe for named pipes, or image for mounting from an image."
},

也許 Docker 也支援了說不定。

depends_on

Docker Compose 使用 depends_on 來延遲一些容器的啟動,避免仰賴其他容器的服務過早的啟動,例如:後端嘗試對還沒準備完成的資料庫進行連線。

Docker Swarm 則不支援這個功能,它的邏輯是透過 health check:反正有仰賴的容器連線失敗會 health check 失敗,直接重啟容器直到成功為止。但是這樣的設計無法運行相對複雜的服務,例如:我嘗試在 Swarm 模式下運行 GVM (OpenVas) 並沒有成功過。

而 K8s 則是透過 initContainers 的機制來處理這類需求。

Serverless

工作時使用雲端的 Serverless 的服務 (GCP Cloud Run),因此我對於自架 (selfhosted) 方案很感興趣,也有找到一些開源方案,不過它們多基於 K8s,例如:

info

我這裡是指基於 OCI 的 Serverless,非 OCI 的 "Function based" Serverless 方案通常都有緊支援特定程式語言的限制。

K3s 與遠端訪問

前幾天 (2025-12-23) 安裝完 K3s 設定遠端訪問時看到這個:

$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://127.0.0.1:6443
name: default
contexts:
- context:
cluster: default
user: default
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
user:
client-certificate-data: DATA+OMITTED
client-key-data: DATA+OMITTED

雖然知道 certificate-authority-data 大概是為了 HTTPS 的加密機制而準備的,不過我其實不知所以然。

K8s 架構圖

查資料的時候看到這張架構圖:

我注意到 K8s 的組件之間通訊都是加密的,這讓我想到之前透過「Kubernetes The Hard Way」學習 K8s 的經驗。

以及某篇文章,有個老兄抱怨 K8s 難用,伺服器癱瘓的原因似乎是憑證過期,並且它們最後在 AWS 找到了歸屬。

Kubernetes The Hard Way

Kubernetes The Hard Way 是一個架設 K8s 的教學,但是不使用現成的安裝精靈,而是手動安裝與配置每一個組件。

去年(2024 年 1 月)我其實試著依照這份指南的步驟嘗試在一台 x86 32 位元筆電上建立 K8s,雖然最後沒有持續下去,但是我隱約記得過程中一直在生成金鑰跟簽署憑證。

info

我當時有試著將執行過得步驟寫成 Make/Ansible 腳本:

https://github.com/FlySkyPie/k8s-builder-and-installer-for-x86-single-node

正確打開 K8s 的方式...難道是密碼學?

作為一個以開發前端業務邏輯為重心的開發者,密碼學相關的基礎概念可以說是很容易被輕忽的部份。或許我該好好的梳理以下對於加密技術的基礎概念作為。

當然,如果我只是要作為 K8s 的使用者,這些知識似乎沒有這麼重要,不過我同時還是要在 Homelab 架設 K8s 的維護者,我不認為紮紮時時的把基礎打好有什麼壞處。

Footnotes

  1. Use an Image Volume With a Pod | Kubernetes. https://kubernetes.io/docs/tasks/configure-pod-container/image-volumes/