在 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请注意:
- 检查网络接口是否正确
- 安全地生成 token
- 确认是否仍需 cloud-provider=external。在 Kubernetes v1.29+ 中可能不再需要。
- 只有第一台 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 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 地址,并自动从 Kubernetes 中删除已在 Hetzner Cloud 上删除的节点。 - 通过在节点上设置
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(这也是我为什么要确保 Secret 在静态时加密的原因之一)
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 默认会在所有节点上调度部署,所以如果你能接受这种行为,可能并不需要太多 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 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 会使用它自己的负载均衡器,这本身没什么问题,但我希望由 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 指向它们了!
虽然现在已经跑得挺好了,但接下来还有不少想做的事
- 使用 Longhorn 配置存储,因为目前还没有存储
- 配置监控
- 通过 cloud-init 实现 agent 自动化部署
- 便捷的 VPN 访问
- 进一步的安全加固
随机一篇博客
评论
登录后参与讨论