平時除了維護公司和私人在公有云的 Kubernetes clusters 之外,個人網路環境下還有些需要執行在本地的 workload;比如用於監控本地路由裝置(作 XD)的 Prometheus exporters 和一些新奇玩意兒。為了能夠執行這些應用,我在家組建了一套「邊緣計算叢集」,來看看是怎麼做的吧。
硬體準備
我手頭上目前有一臺 Raspberry Pi 3 B+,我想使用它作為 Master 節點:
和兩塊 Nanopi NEO 2:
搭建叢集
K3s
為了能夠適應較低的計算效能,我選擇了使用 K3s 部署 Kubernetes 叢集。K3s 是一款 Rancher 開源的輕量 Kubernetes 實現,主要目標為物聯網和邊緣計算等場景。
如果你是在搭建測試叢集,不妨試試 Minikube 和 MicroK8s,它們能夠提供更加接近生產環境叢集的體驗。
不同於以上兩款產品,K3s 除了更加輕量外,還支援多節點,因此比較符合我的使用場景。
以上產品的詳細對比可參考 這篇帖子。
K3sup
K3sup 是由 OpenFaaS 的創始人 Alex Ellis 開發的一款小工具,可用於快速部署 K3s 節點,例如部署 master node:
k3sup install --ip "$MASTER" --user pi
執行以上命令,K3sup 將以使用者 pi
的身份透過 SSH 連線 $MASTER
(也就是作為 Master 節點的樹莓派 IP 地址),在下載並安裝 K3s 後,K3sup 會將生成的 Kubeconfig 從遠端拉取到本地的工作目錄中。
接下來部署 worker node:
k3sup join --ip "$WORKER" --server-ip "$MASTER" --user pi
隨後即可透過 kubectl
管理叢集了:
export KUBECONFIG="$(pwd)/kubeconfig"
kubectl get node
具體的安裝過程限於篇幅不再詳述,可參考 Alex Ellis 的這篇部落格。
使用 NodeAffinity 處理不同 CPU 架構問題
根據上圖可以發現,樹莓派的 CPU arch 是 arm
,而 NanoPi 是 arm64
。為了能夠將對應其架構的容器映象排程到正確的節點,使用 NodeAffinity 是解決方案之一。例如部署內網穿透專案 inlets:
apiVersion: apps/v1
kind: Deployment
metadata:
name: inlets-arm64
spec:
selector:
matchLabels:
app: inlets
replicas: 1
template:
metadata:
labels:
app: inlets
spec:
containers:
- name: inlets
image: inlets/inlets:2.6.4-arm64 # 映象為 arm64 版本
args: [server]
affinity:
nodeAffinity:
# 在 Pod Scheduling 時強制要求
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms: # 節點選擇器陣列
- matchExpressions: # 匹配節點 labels
- key: kubernetes.io/arch # label 名稱
operator: In # 要求滿足以下任意值其一
values: [arm64] # 可指定多個值
可以看到我們在 Pod Spec 內定義了 affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms
欄位,值為 NodeSelectorTerm 陣列。我們定義了一條規則為:label kubernetes.io/arch
的值必須存在於陣列 [arm64]
中。本例中只有單個值,因此等效於:必須等於 arm64
。
Docker Manifests
如果你想要部署的應用映象是你自己構建的話,那麼強烈推薦試試看 Docker image manifest v2 的特性 —— 可建立 manifest lists 包含多個不同 platforms 和 architectures 的 image manifests。
Docker Client 也提供了一個實驗性的命令 docker manifest
來建立、推送 manifest lists。我結合例項來說說它的用法。
由於該命令目前是「實驗性」的,首先需要透過環境變數開啟才能夠使用。我們順便配置幾個變數備用:
export DOCKER_CLI_EXPERIMENTAL=enabled # 開啟 Docker CLI 的實驗性功能
export IMAGE_REPO=vendor/app # 映象名稱,請按需填寫
export IMAGE_TAG=v1.0.0 # 映象 tag,請按需填寫
假設你已經分別構建好針對 arm64
和 arm
的映象,接下來先將它們推送到 registry:
docker push "${IMAGE_REPO}:${IMAGE_TAG}-arm64"
docker push "${IMAGE_REPO}:${IMAGE_TAG}-arm"
隨後建立 manifest list 指向多個 image manifests:
docker manifest create --amend \
"${IMAGE_REPO}:${IMAGE_TAG}" \ # manifest list 名稱
"${IMAGE_REPO}:${IMAGE_TAG}-arm64" \ # 針對 arm64 的映象
"${IMAGE_REPO}:${IMAGE_TAG}-arm" # 針對 arm 的映象
最關鍵的一步到了,為它們新增註解。將每個 manifest 繫結至特定的 os
和 arch
:
# 註解 arm64 image manifest
docker manifest annotate \
--os linux \ # 系統為 Linux
--arch arm64 \ # 架構為 arm64
"${IMAGE_REPO}:${IMAGE_TAG}" \ # manifest list 名稱
"${IMAGE_REPO}:${IMAGE_TAG}-arm64" # 被註解的 manifest
# 註解 arm image manifest
docker manifest annotate \
--os linux \ # 系統同為 Linux
--arch arm \ # 架構為 arm
"${IMAGE_REPO}:${IMAGE_TAG}" \ # manifest list 名稱
"${IMAGE_REPO}:${IMAGE_TAG}-arm" # 被註解的 manifest
此時 manifest list 已經建立好了,可使用 docker manifest inspect
命令檢查一下具體資訊。確認無誤後,推送到 registry 即可:
docker manifest push --purge "${IMAGE_REPO}:${IMAGE_TAG}"
需要注意的是,此處的 --purge
引數是必不可少的。因為 Docker CLI 並沒有提供 docker manifest remove
或是 docker manifest purge
之類的命令。如果不隨著推送直接清理,那就只能到本地的 $HOME/.docker/manifests
目錄手動刪除了… 雖然早在 2018 年初就有人針對此問題提出 issue,但截止發稿前仍沒有倉庫 member 回覆。
最後,使用剛剛建立的 manifest list 名稱代替有字尾的 image manifest 名稱即可,甚至可以增加 replicas
的數量,透過 PodAntiAffinity
刻意將多個副本部署在不同 CPU 架構的節點上而無需區分 image
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: inlets
spec:
selector:
matchLabels:
app: inlets
replicas: 2
template:
metadata:
labels:
app: inlets
spec:
containers:
- name: inlets
image: inlets/inlets:2.6.4 # 字尾 `arm64` 已被移除
args: [server]
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/arch
labelSelector:
matchLabels:
app: inlets
完。
本作品採用《CC 協議》,轉載必須註明作者和本文連結