Setting up k3s on Hetzner Cloud

Ellie Huxtable

在 Hetzner Cloud 上架設 k3s

我最近為 Atuin 架設了一個 HA(高可用性) k3s 叢集!

我使用的是 HA etcd,這表示我們需要執行奇數個「server」節點,而且顯然不只一個。因此,最少需要 3 個。

所有 server 都架設在 private network(私有網路) 中,位於各自的子網路上,並且都是 ARM 執行個體。firewall(防火牆) 已設定為阻擋幾乎所有的 ingress(傳入流量),以及大部分的 egress(傳出流量)。

老實說,我很驚訝這件事竟然這麼簡單。我可能在不知不覺中搞砸了某些地方,但幾年前我使用 kubeadm 的經驗可有這麼順利。k3s 萬歲!

參考來源

我閱讀了不少資料。

這份指南是個不錯的起點:https://community.hetzner.com/tutorials/k3s-glusterfs-loadbalancer

不過 k3s 的文件真的寫得很好,我主要是閱讀那些文件:https://docs.k3s.io/

我沒有使用那些(可能很優秀的)自動化 Hetzner k3s 架設工具。我想確保自己能確實理解整個運作原理,而且我過去也曾在實體機器上跑過幾次 Kubernetes,只是用的是 kubeadm,而不是 k3s。

伺服器

第一台 server!請注意,k3s 將執行 control plane(控制平面) 的節點稱為「server」,而其他節點則稱為「agent」。在預設情況下,它允許 master 節點同時排程一般的工作負載,這對我的使用情境來說應該沒問題。

我已停用 cloud controller(雲端控制器),因為我們會安裝 Hetzner 專用的版本,同時也停用了 local storage(本地儲存),因為我打算使用 Longhorn。請務必挑選一個夠安全的 token!

另外,由於我在 Hetzner 上啟用了 private networking,而且希望叢集使用它,所以我已將 flannel 指向私有網路介面。

curl -sfL https://get.k3s.io | sh -s - server \
    --cluster-init \
    --disable-cloud-controller \
    --disable local-storage \
    --node-name="$(hostname -f)" \
    --flannel-iface=enp7s0 \
    --kubelet-arg="cloud-provider=external" \
    --secrets-encryption \
    --disable=traefik \
    --token=CHANGE ME

後續的機器執行非常類似的指令

curl -sfL https://get.k3s.io | sh -s - server \
    --server SERVER ADDRESS \
    --disable-cloud-controller \
    --disable local-storage \
    --node-name="$(hostname -f)" \
    --flannel-iface=enp7s0 \
    --kubelet-arg="cloud-provider=external" \
    --secrets-encryption \
    --disable=traefik \
    --token=CHANGE ME

請注意:

  1. 確認網路介面是否正確
  2. 以安全的方式產生 token
  3. 確認是否仍需要 cloud-provider=external。在 Kubernetes v1.29+ 中,可能就不再需要了。
  4. 只有第一台 server 的設定需要「cluster init」。之後,你就已經擁有一個叢集了——不需要再初始化了!

第一次架設時,我一路設定到 Hetzner Cloud Controller,直到看到他們的文件要求必須為每個節點傳入 --kubelet-arg="cloud-provider=external" 這個旗標。

對於大多數旗標,你只需重新執行安裝程式,它就會調整設定並重新啟動節點。但對於這個旗標,如果一開始沒有加上,你就得重新架設整個叢集。HCCM 只會為一開始就正確設定的節點加上標籤,而沒有標籤的節點將無法與你的 LB(負載平衡器) 正常運作。

來補充一些背景。Kubernetes 有許多 CCM(cloud controller managers,雲端控制器管理器),基本上能讓 k8s 與雲端供應商良好地整合。為了安裝外部的 CCM,我們目前需要設定上述的旗標。不過,這個旗標已經被棄用很長一段時間了,原本預計在 v1.24 中移除,但至今尚未實現。

據我了解,目前 kubelet 還綁定了部分 CCM,因此這個旗標能讓你使用非綁定的 CCM。未來的規劃是不再綁定 CCM,讓這個參數變得多餘(因此才會被棄用)。

如果你有興趣深入了解,可以參考這個 Issue:https://github.com/kubernetes/kubernetes/issues/110018

根據所提及的 PR,這項變更可能會包含在 v1.29 中。因此,如果你執行的是 v1.29 以上的版本,或許就不需要再使用 cloud provider 旗標了!

到這個階段,你就可以執行

kubectl get nodes

在任何一台已設定的機器上執行,並會得到類似以下的結果:

NAME       STATUS   ROLES                       AGE    VERSION
server-1   Ready    control-plane,etcd,master   4m4s   v1.27.6+k3s1
server-2   Ready    control-plane,etcd,master   47s    v1.27.6+k3s1
server-3   Ready    control-plane,etcd,master   19s    v1.27.6+k3s1

你也可以透過以下指令檢查 secret encryption(加密機制) 的狀態:

k3s secrets-encrypt status
Encryption Status: Enabled
Current Rotation Stage: start
Server Encryption Hashes: All hashes match

Active  Key Type  Name
------  --------  ----
 *      AES-CBC   aescbckey

太棒了!

存取

在進行更多設定之前,我想先從我的筆電設定 kubectl 的存取權限。直接在節點本機上執行指令感覺不太好。

等設定完全完成後,我會再設定 Tailscale(或 innernet)來進行存取,但目前我只會先用 ssh 埠號轉發。你可以透過以下方式取得 kubeconfig:

cat /etc/rancher/k3s/k3s.yaml

簡單執行

ssh -L 6443:localhost:6443 root@a server ip

就能在你的本機裝置上使用 kubectl 了。不過還是建議設定一個更穩固的方式喔 😊

Hetzner Cloud Controller Manager

試著快速唸三次看看。總之,hccm 能將我們的叢集與 Hetzner Cloud API 整合,這表示我們可以(以下摘自 README):

  1. 將伺服器類型加入 node.kubernetes.io/instance-type 標籤,設定外部 IPv4 與 IPv6 位址,並刪除 Hetzner Cloud 上已被刪除的節點在 Kubernetes 中的對應項目。
  2. 透過在節點上設定 topology.kubernetes.io/regiontopology.kubernetes.io/zone 標籤,讓 Kubernetes 感知伺服器所屬的 failure domain(故障網域)。
  3. 允許你為 Pod 流量使用 Hetzner Cloud Private Networks(私有網路)。
  4. 允許你將 Hetzner Cloud Load Balancer(負載平衡器) 與 Kubernetes Service 結合使用。

Hetzner 的部落格文章建議直接套用 manifest, hccm 的文件則建議使用 Helm chart(Helm 套件)。我在設定時已啟用 private networking(我不知道為什麼有人會想在 public network 上跑這個,或許還是別這麼做吧?)

helm repo add hcloud https://charts.hetzner.cloud
helm repo update hcloud

接著,你需要建立一個包含 Hetzner Cloud API token 與網路名稱的 k8s secret(這也是我之所以想確保 secret 有在靜態加密的原因之一):

kubectl -n kube-system create secret generic hcloud --from-literal=token=SOME SECRET --from-literal=network=NETWORK NAME
helm install hccm hcloud/hcloud-cloud-controller-manager -n kube-system --set networking.enabled=true --set networking.clusterCIDR=10.42.0.0/16

請留意 clusterCIDR 的設定。如果你沒有更動過 k3s 預設值,那麼 10.42.0.0/16 就適用。

kubectl logs -n kube-system deployment/hcloud-cloud-controller-manager

現在應該會看到一些輸出,接著:

kubectl describe node agent-1

應該會顯示一些額外的資訊註解:

node.kubernetes.io/instance-type=cax21
topology.kubernetes.io/region=fsn1
topology.kubernetes.io/zone=fsn1-dc14

Agent

k3s 的預設行為是會在所有節點上排程部署,因此你可能不需要太多這類節點(如果你可以接受這種行為的話)。

設定方式與 server 非常相似!只是設定更少。你同樣會需要先前使用的 token。

curl -sfL https://get.k3s.io | sh -s - agent \
    --server SERVER ADDRESS \
    --node-name="$(hostname -f)" \
    --flannel-iface=enp7s0 \
    --kubelet-arg="cloud-provider=external" \
    --token=CHANGE ME

你可以依需求設定任意數量的這類節點

kubectl get nodes
NAME       STATUS   ROLES                       AGE    VERSION
agent-1    Ready    <none>                      9s     v1.27.6+k3s1
server-1   Ready    control-plane,etcd,master   100m   v1.27.6+k3s1
server-2   Ready    control-plane,etcd,master   96m    v1.27.6+k3s1
server-3   Ready    control-plane,etcd,master   96m    v1.27.6+k3s1

使用 Traefik 設定 Ingress

稍早,我在設定節點時已停用 Traefik,但現在又要來設定它?

基本上,k3s 預設的 Traefik 會使用它自己的 Load Balancer。這其實沒什麼問題,但我希望能讓 hccm 來管理我的 LB。這樣我就能獲得一個真正的雲端 LB,而所有目標都會由叢集自動管理。

花了一番功夫才讓它正常運作(我在下方會詳述我犯的一些設定錯誤)

helm repo add traefik https://traefik.github.io/charts
helm repo update

接著我建立了 traefik.values.yaml。我不會在這裡貼上完整內容——通常我會先取得預設值,存成檔案後再依需求修改。你最好也依照自己的需求來設定!

我要說的是,service 上的這些 annotation 非常重要:

service:
  enabled: true
  ## -- Single service is using `MixedProtocolLBService` feature gate.
  ## -- When set to false, it will create two Service, one for TCP and one for UDP.
  single: true
  type: LoadBalancer
  # -- Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config)
  annotations:
    load-balancer.hetzner.cloud/location: fsn1
    load-balancer.hetzner.cloud/name: lb
    load-balancer.hetzner.cloud/use-private-ip: "true"

首先,我們設定 LB 的位置,給它一個名稱,然後告訴它使用 private IP。我一開始沒有這個選項,結果 Load Balancer 根本無法運作!所有目標都是不健康的狀態。

在預設情況下,hccm 只會將 public IP 位址加入到 LB 中。我的 firewall 擋住了它(不允許 public ingress 直接連到節點),所以沒有任何流量能正常路由。做了這個變更後,一切就正常了 😇

我同時也使用 Cloudflare 來終止 SSL。之後我可能會再設定 cert-manager,但我正盡量讓我的叢集保持無狀態。而且在邊緣終止 SSL 也相當方便、簡單。

後續步驟

到這個階段,我已經可以部署無狀態服務,並透過 Cloudflare 將 DNS 指向它們了!

雖然目前運作得相當順利,但接下來還有不少想做的事

  1. 使用 Longhorn 設定儲存空間,因為目前還沒有儲存功能
  2. 設定監控
  3. 使用 cloud-init 自動化 agent 的設定
  4. 簡易的 VPN 存取
  5. 額外的安全性強化

原文由 Ellie Huxtable 發布

本文章由 muse-spark-1.2-contributor 進行翻譯