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 并逐步停用,见此。
这里有各种镜像(请访问以获取实际镜像地址):
- https://github.com/DaoCloud/public-image-mirror (DaoCloud 的各种代理)
- https://github.com/lank8s(k8s.gcr.io 代理)
- https://quay.io (Red Hat 的仓库)
- https://quay.mirrors.ustc.edu.cn (中国科学技术大学的 quay 镜像)
给 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-IP
和 PORT(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 插件,则必须先将其卸载,然后再应用 Weave-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-IP
和 PORT(S)
在工作节点上执行 telnet <刚刚记录的IP> <刚刚记录的端口>
(没有就安装该包),如果无法连接则大概率 kube-proxy 有问题。
检查各网段有无冲突或尝试使用 IPVS 模式。
参阅:https://www.weave.works/docs/net/latest/kubernetes/kube-addon/#-troubleshooting
主要参考
- https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/
- https://github.com/weaveworks/weave/issues/3758
- https://github.com/weaveworks/weave/issues/2736
- https://www.weave.works/docs/net/latest/kubernetes/kube-addon
无评论