在 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请注意:
- 确认网络接口是否正确
- 安全地生成 token
- 确认 cloud-provider=external 是否仍然必要。在 Kubernetes v1.29+ 中可能不再需要。
- 只有第一台服务器的安装需要 "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 statusEncryption 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):
- 将服务器类型添加到
node.kubernetes.io/instance-type标签,设置外部 ipv4 和 ipv6 地址,并将已从 Hetzner Cloud 删除的节点从 Kubernetes 中删除。 - 通过在节点上设置
topology.kubernetes.io/region和topology.kubernetes.io/zone标签,让 Kubernetes 感知服务器的故障域。 - 允许 Pod 流量使用 Hetzner Cloud 私有网络。
- 允许将 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 NAMEhelm 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-dc14Agent 节点
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 nodesNAME 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 指向它们的阶段!
接下来还有很多想做的事,虽然现在一切已经运转得相当不错了:
- 用 Longhorn 设置存储,因为目前还没有存储
- 监控配置
- 用 cloud init 实现 agent 自动化安装
- 便捷的 VPN 访问
- 额外的安全加固
随机一篇博客