Setting up k3s on Hetzner Cloud

Ellie Huxtable

在 Hetzner Cloud 上搭建 k3s

我最近为 Atuin 搭建了一个 HA k3s 集群!

我使用的是 HA etcd,这意味着需要运行奇数个 "server" 节点,而且显然不能只有一个。所以 3 个是最低要求。

这些服务器都部署在一个私有网络中,拥有独立的子网,并且都是 ARM 实例。防火墙配置为几乎禁止所有入站流量和大部分出站流量。

说实话,整个过程简单得让我吃惊。也许有什么地方搞砸了我自己没发现,但几年前我用 kubeadm 的体验可没这么好。k3s 万岁!

参考资料

我读了不少东西。

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

不过 k3s 的文档写得非常好,我基本上就是读那些文档:https://docs.k3s.io/

我没有使用那些(可能很不错的)Hetzner k3s 自动化安装工具。我想确保自己真正理解正在发生的事情,而且我过去也多次在裸机上运行过 Kubernetes。只是用 kubeadm,不是 k3s。

服务器

第一台服务器!注意,k3s 把运行控制平面(control plane)的节点称为 "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. 只有第一台服务器的安装需要 "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 地址,并将已从 Hetzner Cloud 删除的节点从 Kubernetes 中删除。
  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(这也是我想确保 secrets 在静态存储时加密的原因之一):

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 的默认行为实际上是把所有 deployment 都调度到节点上,所以你可能不需要太多 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

你想加多少个都可以:

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 会使用自己的负载均衡器。这其实没什么问题,但我想确保我的 LB 由 hccm 管理。这样我就能得到一个真正的云负载均衡器,其所有目标都由集群自动管理。

让它跑起来花了一点折腾(我犯了一些下面详述的配置错误):

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

然后我创建了 traefik.values.yaml。我不打算把整个文件贴在这里——通常的做法是获取默认 values,保存到文件,然后按需编辑。你也最好按照自己的需求来配置!

我要说的是,这些注解对你的 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 地址加到 LB 上。而我的防火墙拦截了它(不允许公网入站直达节点),所以什么都路由不通。做了这个改动后,一切正常了 😇

我还使用 Cloudflare 来终结 SSL。以后可能会设置 cert-manager,但我尽量让集群保持无状态。而且在边缘终结 SSL 也挺方便省事的。

下一步

现在已经到了可以部署无状态服务并用 Cloudflare 把 DNS 指向它们的阶段!

接下来还有很多想做的事,虽然现在一切已经运转得相当不错了:

  1. 用 Longhorn 设置存储,因为目前还没有存储
  2. 监控配置
  3. 用 cloud init 实现 agent 自动化安装
  4. 便捷的 VPN 访问
  5. 额外的安全加固

原文由 Ellie Huxtable 发布

本文章由 stealth/ox-alpha 进行翻译