Setting up k3s on Hetzner Cloud

Ellie Huxtable

在 Hetzner Cloud 上搭建 k3s

原文由 Ellie Huxtable 发布,订阅该博客

最近我为 Atuin 搭建了一个高可用的 k3s 集群!

我用的是高可用 etcd,这意味着需要运行奇数个“server”节点,而且显然不能只有一个,所以最少需要 3 个。

所有 server 都部署在私有网络的独立子网里,用的是 ARM 实例。防火墙已配置为几乎禁止所有入站流量,并限制了大部分出站流量。

说实话,整个过程之简单让我有点吃惊。也许有什么地方被我弄错了但还没发现,不过几年前用 kubeadm 的体验可没这么顺畅。为 k3s 欢呼!

参考资料

我参考了不少资料。

这篇指南是个不错的起点:https://community.hetzner.com/tutorials/k3s-glusterfs-loadbalancer

不过 k3s 的官方文档写得非常好,我主要看的就是它:https://docs.k3s.io/

我没有用那些(可能很好用的)Hetzner k3s 自动化部署工具。我想确保自己真正理解整个流程,而且之前也曾在裸机上跑过几次 Kubernetes,不过用的是 kubeadm,不是 k3s。

Server 节点

先从第一台 server 开始!注意,k3s 把运行控制平面的节点称为“server”,其他节点则称为“agent”。默认情况下,master 节点也会被用来调度常规工作负载,这对我的场景来说应该没问题。

我禁用了自带的 cloud controller,因为我们要安装 Hetzner 专用的版本,同时也禁用了本地存储,因为我打算使用 Longhorn。记得选一个足够安全的 token!

另外,由于我在 Hetzner 上启用了私有网络,并希望集群走私有网络,所以把 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 只会给一开始就正确配置的节点打标签,未打标签的节点无法与负载均衡器正常协同工作。

补充一点背景。Kubernetes 有一堆 CCM(cloud controller manager,云控制器管理器),用于让 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 加密状态

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 地址,并自动从 Kubernetes 中删除已在 Hetzner Cloud 上删除的节点。
  2. 通过在节点上设置 topology.kubernetes.io/regiontopology.kubernetes.io/zone 标签,让 Kubernetes 感知服务器所在的故障域。
  3. 允许为 Pod 流量使用 Hetzner Cloud 私有网络。
  4. 允许将 Hetzner Cloud 负载均衡器与 Kubernetes Service 结合使用

Hetzner 的博客文章建议直接应用一个 manifest, hccm 的官方文档推荐使用 Helm Chart。我启用了私有网络来部署(说实话我也不明白为什么要在公网上跑这个,也许还是别这么做吧?)

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 默认会在所有节点上调度部署,所以如果你能接受这种行为,可能并不需要太多 agent 节点

配置方式和 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

你可以根据需要创建任意数量的 agent

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 会使用它自己的负载均衡器,这本身没什么问题,但我希望由 hccm 来管理我的负载均衡器。这样我就能得到一个真正的云负载均衡器,所有后端目标都会由集群自动管理。

为了让它跑起来我折腾了一会儿(下面会详细说我犯过的配置错误)

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

然后我创建了 traefik.values.yaml。这里就不贴完整内容了——我一般是先获取默认值保存到文件,再按需修改。你也最好根据自己的需求来配置!

不过,Service 上的这几个注解非常重要:

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"

首先设置负载均衡器的位置,给它起个名字,然后让它使用私有 IP。我一开始没加这个选项,结果负载均衡器根本无法工作!所有目标都是不健康状态。

默认情况下,hccm 只会把公网 IP 添加到负载均衡器。我的防火墙拦截了这些流量(不允许公网直接入站到节点),所以路由完全不通。改成私有 IP 后,一切就正常了 😇

我还在用 Cloudflare 来终止 SSL。以后也许会配置 cert-manager,但我正尽量让集群保持无状态。而且在边缘终止 SSL 也更方便。

下一步

到现在,我已经可以部署无状态服务,并通过 Cloudflare 将 DNS 指向它们了!

虽然现在已经跑得挺好了,但接下来还有不少想做的事

  1. 使用 Longhorn 配置存储,因为目前还没有存储
  2. 配置监控
  3. 通过 cloud-init 实现 agent 自动化部署
  4. 便捷的 VPN 访问
  5. 进一步的安全加固

本文章由 muse-spark-1.2-contributor 进行翻译

评论