跳转到主要内容

Kubernetes 混合云/跨机房部署笔记 —— 基于 Tailscale/Zerotier

本文略过了大部分部署,基础部署请参考另一篇:https://wiki.pha.pub/link/306

默认 root 账户,其他账户请自行 sudo。
默认你已经有了些基本知识。

虽然说是基于 Tailscale/Zerotier 但其他的组网工具也都是可以的,比如 Nebula 和 Kubernetes 专用的 kilo

我个人测试下来,在我的环境中 Tailscale 要比 Zerotier 更快更稳定,但是 Tailscale 不能自定义 IP,如果你不小心重置了 Tailscale 那么更换集群 IP 也是一个不小的工作量。
在我使用 Zerotier 时,经常遇到容器莫名其妙失败,查看日志发现是无法访问 10.96.0.1,所以此类服务 UDP 通讯并非完全可靠,还是要根据具体情况来决定是否使用。更换至 Tailscale 暂时没有遇到此类问题。

本文使用:kubeadm 1.24.3


Kubernetes 网络模型要求:

Pod 可以在没有 NAT 的情况下与任何节点上的任何 Pod 通信;

节点上的代理(例如系统守护进程、kubelet)可以与该节点上的所有 Pod 通信;

在 Pod 直接通讯方面:
每个 Pod 都拥有一个唯一且独立的 IP 地址,只要彼此通信的 Pod 在同一个集群里,那么就可以通过 IP 地址的方式实现 Pod 间的直接访问而无需经过 NAT 等,即使它们不在同一个 Node 节点上。这个通讯由 CNI 插件负责。

在工作节点和控制面通讯方面:
工作节点加入集群时需要访问控制面的 ApiServer,这是直接 HTTPS 通过 IP 访问的,你应该保证工作节点能直接访问此 IP,无论是公网还是内网,由于使用 HTTPS 所以此通讯可以在不安全网络上进行。但从 ApiServer 到 Node、Pod 的连接是纯 HTTP 连接,这不应该在不安全的网络上进行,但同样需要能直接访问的 IP。更多信息。


我们的 Pod 通讯都交给了 CNI 插件解决,我们只需要保证 ApiServer 能够访问,通常集群机器都在同一个局域网下可以互相访问。
在混合云/跨机房·部署时,可以通过 Tailscale 或 Zerotier 等新技术或传统虚拟专用网等组成虚拟局域网。
同时,不推荐将 ApiServer 暴露至公开网络(超过 380,000 个暴露的 Kubernetes API 服务器)。
设置 Apiserver 时使用主机名/负载均衡器地址等以免日后变更。


CNI 插件负责解决了更复杂的 Pod 通讯。而在工作节点和控制面通讯方面,我们借助 Tailscale/Zerotier 同样可以为我们能够降低混合云部署时的通讯复杂度,无论是机器在 NAT 下,还是没有公网 IP 都没有关系,就像在同一个交换机下一样。


当然这个方案是损耗比较大,不仅有加密开销,而且 Tailscale/Zerotier 使用 UDP 进行通讯,跨网时可能会遇到 Qos 限制。
所以并不适合你有较多的大流量跨节点通讯。


Tailscale/Zerotier 请自己参考官网安装并加入网络。


设置前检查

首先请确保各主机间能够正常使用 Tailscale/Zerotier 通讯。

直接 ping <网络中的其他主机> 

如果发现延迟异常,或是 Destination Host Unreachable,你需要检查 rp_filter:
执行 sysctl -a 2>/dev/null | grep "\.rp_filter",各接口值应当为 0 或 2。

复制结果并写入  /etc/sysctl.conf 中,并更改对应接口为你想要的数值,如:

net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.conf.lo.rp_filter = 0
net.ipv4.conf.tailscale0.rp_filter = 0
net.ipv4.conf.zerotier.rp_filter = 0

并执行 sysctl -p
另请参阅 https://access.redhat.com/solutions/53031 以获取更多信息。

Zerotier 特别提示

如果你安装了 Tailscale、Zerotier、Nebula 等多种工具,于是便有了多条通讯路径。
Zerotier 甚至会通过 Cilium 等 Kubernetes 网络插件进行通讯。

请参考此文档 https://docs.zerotier.com/zerotier/zerotier.conf 屏蔽相关地址。

即在 /var/lib/zerotier-one 创建 local.conf 并以 Json 格式书写。(Json 格式检查
Zerotier 会自动检测此文件,编写完后保存并重启 Zerotier,使用 zerotier-cli info -j 检查以确保正确配置。
可参考这篇:https://wiki.pha.pub/link/317

检查节点间通信

Tailscale 

使用 tailscale status 确保节点间是 direct 连接而不是 relay "foo"。
然后使用 tailscale ping <节点名> 在节点间测试通信,确保没有经过中继服务器。
如果无法建立直接连接,请参考此文章 https://tailscale.com/kb/1082/firewall-ports/
无直接连接时性能会严重降低。


Zerotier

使用 zerotier-cli peers 确保节点间是 DIRECT 连接而不是 RELAY。
如果无法建立直接连接,请参考此文章 https://docs.zerotier.com/zerotier/troubleshooting
无直接连接时性能会严重降低。

设置 hosts 或 MagicDNS

使用 Tailscale 的 MagicDNS 或类似服务或是在 hosts 中设置主机名。

设置主机名:
hostnamectl set-hostname <主机名>

然后在每一台主机的 /etc/hosts 中添加 <本机Tailscale或Zerotier的IP> <主机名>
如:

10.1.1.1  Gen1
10.1.1.2  Gen2
10.1.1.3  Gen3
为节点指定正确 IP

如果你直接使用 kubeadm join 那么它是找不到正确的 IP 的。

你需要在 /etc/sysconfig/kubelet 中

KUBELET_EXTRA_ARGS="--node-ip=<本机Tailscale或Zerotier的IP> [--可能有的其他参数]"
IPVS 模式

默认状态下 kube-proxy 使用 iptables 进行负载均衡/转发等,但 iptables 不是为此设计的,所以使用 iptables 规则复杂且性能低。
而 IPVS 是一个工作在内核态的高性能 4 层负载均衡,使用 IPVS 可以避免出现大量 iptables 规则,有时还能解决一些奇怪的问题。
如我有一台节点 iptables 模式死活无法访问 10.96.0.1:443 但切换到 IPVS 模式后,它神奇的好了。

安装包
首先确保已安装 ipset ipvsadm 包:yum install ipset ipvsadm

确保已加载对应内核模块
lsmod | grep -e ip_vs  有输出则已加载。
如果没有输出则需要加载内核模块:

modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack


引导集群

镜像

Kubernetes v1.25 之前默认镜像源为 k8s.gcr.io,v1.25 及之后版本为 registry.k8s.io。
且在 1.22, 1.23, 1.24 版本的 2022 年 12 月补丁中也会将默认镜像源改为 registry.k8s.io。
在 2023 年 4 月 3 日以后镜像不再发布到 k8s.gcr.io,只发布到 registry.k8s.io。

很明显,中国大陆无法访问 k8s.gcr.io,因为它指向了 googlecode.l.googleusercontent.com。
而 registry.k8s.io 有多个供应商,但目前在无法识别流量来源时也会指向 GCP,所以大概率还是无法访问。

注意:k8s.gcr.io 将在 2023 年 3 月 20 日起逐步迁移到 registry.k8s.io 并逐步停用,见此

这里有各种镜像(请访问以获取实际镜像地址):

给 kubeadm 使用:

kubeadm config images pull --image-repository=<镜像地址>

这里使用 DaoCloud:

kubeadm config images pull --image-repository=k8s.m.daocloud.io

注意:你需要先启动容器运行时,如 systemctl restart containerd 

允许通过防火墙

请根据你的防火墙实现配置。

控制面:

协议 方向 端口范围 用途 使用者
TCP 入站 6443 Kubernetes API 服务器 全部
TCP 入站 2379-2380 etcd 服务器客户端 API kube-apiserver、etcd
TCP 入站 10250 Kubelet API 它自己、控制面
TCP 入站 10259 kube-scheduler 它自己
TCP 入站 10257 kube-controller-manager 它自己

工作节点:

协议 方向 端口范围 用途 使用者
TCP 入站 10250 Kubelet API 它自己、控制面
TCP 入站 30000-32767 NodePort 服务* 全部

*默认范围,参阅:https://kubernetes.io/docs/concepts/services-networking/service/

启动 kubelet

启动并开机自启:
systemctl enable --now kubelet
一直崩溃重启是正常的,因为它在等待 kubeadm 告诉它该怎么做。

初始化控制面节点

#仅控制面执行
导出配置文件

kubeadm config print init-defaults > kubeadm.yaml

修改配置文件:
详见:https://kubernetes.io/zh-cn/docs/reference/config-api/kubeadm-config.v1beta2/

apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
  - system:bootstrappers:kubeadm:default-node-token
  #token: abcdef.0123456789abcdef            #加入节点要用的 tocken,可以注释掉
  ttl: 24h0m0s
  usages:
  - signing
  - authentication
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 1.2.3.4    #API 服务器地址,设置为本机 Tailscale 或 Zerotier 的 IP
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///var/run/containerd/containerd.sock
  imagePullPolicy: IfNotPresent
  name: node            #节点名称
  taints: null
---
apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns:
  imageRepository: k8s.m.daocloud.io/coredns          #如果你更换了镜像,那么这里需要特殊设置,见 https://github.com/kubernetes/kubeadm/issues/2714,此处为 Daocloud 镜像
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: k8s.gcr.io      #设置为镜像地址,比如 Daocloud 镜像 k8s-gcr.m.daocloud.io
kind: ClusterConfiguration
kubernetesVersion: 1.24.0         #kubernetes 版本
controlPlaneEndpoint: cpendpoint    #控制面地址,建议设置为自定义 DNS 名称,默认不存在
networking:
  dnsDomain: cluster.local
  podSubnet: 10.32.0.0/12		#指定 Pod 的 IP 地址范围,不应该与主机或其他 IP 段冲突。(此选项会影响 kube-proxy 的 --cluster-cidr 设置)
  serviceSubnet: 10.96.0.0/14     #指定 VIP(虚拟 IP)服务的 IP 地址范围,不应该与主机或其他 IP 段冲突(默认为"10.96.0.0/12")
scheduler: {}
---   #IPVS 配置分块
apiVersion: kubeproxy.config.k8s.io/v1alpha1 # IPVS 配置
kind: KubeProxyConfiguration  # IPVS 配置
mode: ipvs  # IPVS 配置

使用配置文件引导集群:
kubeadm init --config kubeadm.yaml

这会运行一系列检查以确保机器可以运行 Kubernetes,解决完错误后重新运行。
你应该会看到 Your Kubernetes control-plane has initialized successfully!

如果失败了,你需要解决问题,然后执行 kubeadm reset 并再来一遍。

导出配置文件

普通用户请执行(以普通用户身份执行):

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

root 用户请执行:

export KUBECONFIG=/etc/kubernetes/admin.conf

或持久化

mkdir -p /root/.kube
sudo cp -i /etc/kubernetes/admin.conf /root/.kube/config
sudo chown root:root /root/.kube/config

启用 kubelet 自动补全:

echo "source <(kubectl completion bash)" >> ~/.bashrc



警告:
admin.conf 中的 Subject: O = system:masters, CN = kubernetes-admin. system:masters 是一个例外的、超级用户组,可以绕过鉴权层(例如 RBAC)。不要将 admin.conf 文件与任何人共享,应该使用 kubeadm kubeconfig user 命令为其他用户生成 kubeconfig 文件,完成对他们的定制授权。 请参阅:https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-certs#kubeconfig-additional-users

令牌

记住最后输出的 kubeadm join <控制面自定义 DNS 名称>:6443 --token <令牌> --discovery-token-ca-cert-hash sha256:<校验值>
令牌用于控制平面节点和加入节点之间的相互认证。保持安全,因为任何拥有此令牌的人都可以将节点添加到你的集群中。
如果 tocken 过期了,可以在控制面使用 kubeadm token create 创建新的 tocken,默认 24 小时过期。
如果你不知道校验值,可以在控制面使用 openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //' 来获取校验值。

其实也可以用 kubeadm token create --print-join-command 直接生成一条新的加入命令。

控制面节点隔离

默认情况下,你的集群不会在控制面节点上调度(运行) Pod,如果你希望在控制面上调度 Pod,比如单机集群。
执行:kubectl taint nodes --all node-role.kubernetes.io/control-plane- node-role.kubernetes.io/master-
这会移除所有节点上的node-role.kubernetes.io/control-plane 和 node-role.kubernetes.io/master  标记(污点)。
说明: node-role.kubernetes.io/master 污点已被废弃(控制面以前称为 master),kubeadm 将在 1.25 版本中停止使用它。

检查节点 IP

使用 kubectl get nodes -owide 检查各节点 INTERNAL-IP 是否正确

检查 kube-proxy

在控制面上执行 kubectl get svc kubernetes ,记录 CLUSTER-IPPORT(S)
在工作节点上执行 telnet <刚刚记录的IP> <刚刚记录的端口> (没有就安装该包),如果无法连接则大概率 kube-proxy 有问题。
检查各网段有无冲突或尝试使用 IPVS 模式。

检查 IPVS

查看所有 kube-proxy pod:kubectl get pods -n kube-system|grep kube-proxy 
检查日志:kubectl logs <kube-proxy节点名称> -n kube-system
日志中应该有:Using ipvs Proxier.
ipvsadm -Ln 中应当出现一些转发规则。


加入工作节点

在工作节点机器上执行刚刚的 kubeadm join <控制面自定义 DNS 名称>:6443 --token <令牌> --discovery-token-ca-cert-hash sha256:<校验值>  命令即可。

若要设置高可用控制面 你需要加上 --apiserver-advertise-address=<本机Tailscale或Zerotier的IP> ,就像这样
kubeadm join <控制面自定义 DNS 名称>:6443 --token <令牌> --discovery-token-ca-cert-hash sha256:<校验值> --control-plane --certificate-key <证书秘钥> --apiserver-advertise-address=<本机Tailscale或Zerotier的IP>


同样,如果失败了,你需要解决问题,然后执行 kubeadm reset 并再来一遍。

注意:由于集群节点通常是按顺序初始化的,CoreDNS Pod 很可能都在第一个控制平面节点上运行。为了提供更高的可用性,请在加入至少一个新节点后重新平衡 CoreDNS Pod,在控制面执行 kubectl -n kube-system rollout restart deployment coredns





集群网络

注意:仅控制面执行。
Kubernetes 并没有自己实现网络模型,要么你自己实现一个,要么从 https://kubernetes.io/docs/concepts/cluster-administration/networking/ 找一个合适的。
这里有一个来自 https://kubevious.io 的表格可以帮你在一定程度上选择。

名称 Flannel Calico Cilium Weavenet Canal
部署方式 DaemonSet DaemonSet DaemonSet DaemonSet DaemonSet
封装和路由 VxLAN IPinIP,BGP,eBPF VxLAN,eBPF VxLAN VxLAN
网络策略支持
数据储存 Etcd Etcd Etcd Etcd
加密
Ingress 支持
企业级支持

Flannel 被定位为简单的选择;而 Calico 以其性能、灵活性和性能而闻名;Weave 则使用网状网络,容易设置且不是很复杂;Cilium 使用 Linux 内核 eBPF 技术, 高效且灵活,但需要较新的内核。

集群网络非常复杂,你应该进一步阅读他们的官方文档。

这里我们使用 Weavenet。
我同样测试了 Cilium ,也能够正常运行,可以参考另一篇

防火墙规则

请根据你的防火墙实现配置。

协议 方向 端口范围/代码 描述
TCP 入站 6783 Weavenet 控制
UDP 入站 6783-6784 Weavenet 数据端口
镜像

官方脚本会从 gcr.io 下载镜像,很明显,国内无法访问。
根据你的容器运行时修改镜像。
containerd 可参考:https://wiki.pha.pub/link/306

安装

#仅控制面执行
官方一键:
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"

重要提示:默认情况下,此配置不会启用加密。如果您的数据平面流量不安全,可能会允许恶意行为者访问您的 pod 网络。

注意:如果在集群中使用之前完整安装 Weave Net 的Weave CNI 插件,则必须先将其卸载,然后再应用 Wea​​ve-kube 插件。

参阅:https://www.weave.works/docs/net/latest/kubernetes/kube-addon

检查

查看所有 Pod:kubectl get pods --all-namespaces -owide

所有 weave-net pod 都应该是  2/2   Running。
如果是 1/2,我遇到的很多问题都是与 kube-proxy 有关的。
在控制面上执行 kubectl get svc kubernetes ,记录 CLUSTER-IPPORT(S)
在工作节点上执行 telnet <刚刚记录的IP> <刚刚记录的端口> (没有就安装该包),如果无法连接则大概率 kube-proxy 有问题。
检查各网段有无冲突或尝试使用 IPVS 模式。

参阅:https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#-troubleshooting




主要参考