最近接触到一个走公网组 K8S 集群的需求,做了一些技术调研,这里做下总结和记录。

实现方案是:先用 Wireguard VPN 软件把节点的网络打通,再正常安装 K8S 集群。

这种做的优点是 Wireguard 组网之后,节点间的通讯就是加密的了,而且性能也不差。
还有就是如果多个节点都拥有公网 IP,可以组成网状 VPN,可用性和稳定性方面都有优势。

"wireguard performance"

方案的要求:

  1. 必须有至少一个公网 IP 才行(用于 VPN 组网和通讯)。
  2. CNI 我用的是 Cilium,其他方案没测试过。但是理论上应该是没问题的。
  3. 其他的基本要求
# 0. 系统 Ubuntu 22.04+ (因为 CGroupV2 要求内核是 5.10+)
# 1. 关闭 swap
sudo swapoff -a
# vi /etc/fstab 注释掉 swap 的行。

# 2. 设置网络功能
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system

# 3. 安装 Containerd
sudo apt-get install -y containerd

# crictl https://github.com/kubernetes-sigs/cri-tools/tags
VERSION="v1.30.1"
curl -L https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-${VERSION}-linux-amd64.tar.gz --output crictl-${VERSION}-linux-amd64.tar.gz
sudo tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin
rm -f crictl-$VERSION-linux-amd64.tar.gz
echo "runtime-endpoint: unix:///run/containerd/containerd.sock" > /etc/crictl.yaml

# 4. 开启 CGroupV2
sudo mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
cat /etc/containerd/config.toml | grep SystemdCgroup
systemctl restart containerd

# 5. Ubuntu, Debian 安装 kubelet, kubeadm v1.28
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

Wireguard VPN

wireguard 是一个开源的 VPN 协议,性能和安全都是顶尖的。

安装很简单 apt install wireguard 即可。配置也比较简单,只是要提前规划好节点 IP 地址就行。

这里提一下,国外有很多多设备互联的平台,都是基于 Wireguard 做的。所以 Wireguard 除了可以组建跨云的 K8S 集群之外,还可以把本地家庭网络里的移动设备、电脑、NAS 等设备和云上网络互联互通。

# Wireguard 常用命令
wg show # 查看配置和状态
wg genkey | tee privatekey | wg pubkey > publickey # 生成公私钥对

wg-quick up k8s_wg0 # 启动服务
wg-quick down k8s_wg0 # 停止服务
systemctl enable wg-quick@k8s_wg0 # 开机自启
systemctl status wg-quick@k8s_wg0 # 查看状态
wg syncconf k8s_wg0 <(wg-quick strip k8s_wg0) # 热加载配置

配置单点结构

# 这里选择使用 10.89.21.0/21 网段,总共有 2048 个IP,可以搭建一个 2000 个节点的集群,一般情况足够了。

# MTU 设置一般不需要,遇到性能问题,可以再调整,参考:https://gist.github.com/nitred/f16850ca48c48c79bf422e90ee5b9d95

# 有公网IP的机器作为服务端,需要知道其他全部节点的信息。
# node-01 的配置:
cat <<EOF > /etc/wireguard/k8s_wg0.conf
[Interface]
PrivateKey = xxxx
Address = 10.89.21.1/21
ListenPort = 51870
MTU = 1420

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.2/32

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.3/32

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.4/32

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.5/32
EOF

# 没有公网IP的机器作为客户端,只需要配置上服务端的信息即可
# node-02,03,04,05 的配置类似,改下自己的 Address IP 即可:
cat <<EOF > /etc/wireguard/k8s_wg0.conf
[Interface]
PrivateKey = xxxx
Address = 10.89.21.2/32

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.1/32
Endpoint = <公网IP>:51870 # 这里也可以使用域名
PersistentKeepalive = 25
EOF

配置网状结构

网状配置略微复杂,每个服务端都需要配置全部的其他客户端 peer 信息。下面的例子是 node-01,02,03 有公网 IP 作为服务端,node-04,05 没有公网 IP 作为客户端。

# 有公网 IP 的机器作为 服务端,需要知道其他 全部 节点的信息。
# node-01,02,03 的配置类似
cat <<EOF > /etc/wireguard/k8s_wg0.conf
[Interface]
PrivateKey = xxxx
Address = 10.89.21.1/21
ListenPort = 51870
MTU = 1420

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.2/32
Endpoint = <公网IP>:51870
PersistentKeepalive = 25

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.3/32
Endpoint = <公网IP>:51870
PersistentKeepalive = 25

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.4/32

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.5/32
EOF

# 客户端节点,只需要配置全部的服务端节点就行
# node04,05 的配置类似
cat <<EOF > /etc/wireguard/k8s_wg0.conf
[Interface]
PrivateKey = xxxx
Address = 10.89.21.4/32

[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.1/21
Endpoint = <公网IP>:51870
PersistentKeepalive = 25
[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.2/21
Endpoint = <公网IP>:51870
PersistentKeepalive = 25
[Peer]
PublicKey = xxxx
AllowedIPs = 10.89.21.3/21
Endpoint = <公网IP>:51870
PersistentKeepalive = 25
EOF

Setup K8S

部署 K8S 就比较简单了,使用 Kubeadm 就可以了。只是要注意几个地方:

  1. 设置 kubelet 启动参数,指定 node ip
# 修改 /etc/default/kubelet,配置上当前节点分配的 IP 地址
KUBELET_EXTRA_ARGS="--node-ip=10.89.21.1"
  1. 设置 kubeadm 启动参数,指定 node ip
kubeadm config images pull --kubernetes-version=v1.28.12

cat <<EOF | sudo tee kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
skipPhases:
- addon/kube-proxy
localAPIEndpoint:
advertiseAddress: "10.89.21.1" # 这里要指定 k8s 主节点的 IP
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
serviceSubnet: "10.90.0.0/16" # svc 网段也可以提前规划好,避免冲突
podSubnet: "10.91.0.0/16" # POD 网段也可以提前规划好,避免冲突
kubernetesVersion: v1.28.12 # 指定安装的 K8S 版本
controlPlaneEndpoint: "10.89.21.1:6443" # 这里要指定 k8s 主节点的 IP
apiServer:
certSANs:
- "35.35.35.35" # 如果要对外提供 k8s api-server 访问,这里可以填公网IP 或者 对外的域名,做到 k8s 证书里。
- "abck8s-api-server.somedomain.com"
certificatesDir: "/etc/kubernetes/pki"
imageRepository: "registry.k8s.io"
clusterName: "abck8s"
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd # 这里要指定使用 CgroupV2
EOF

kubeadm init --config=kubeadm-config.yaml
  1. 设置 Cilium 参数
cat <<EOF > cilium.yaml
k8sServiceHost: "10.89.21.1"
k8sServicePort: "6443"
cluster:
name: abck8s
id: 35
operator:
replicas: 1
ipam:
mode: "kubernetes"
operator:
clusterPoolIPv4PodCIDRList: ["10.91.0.0/16"]
clusterPoolIPv4MaskSize: 16
hubble:
enabled: true
nodePort:
enabled: true
range: "30000,32767"
hostServices:
enabled: true
autoDirectNodeRoutes: false # node 节点的子网可能不同,这里要设置为 false
tunnelProtocol: "vxlan" # tunnel 模式要开启,默认就是开启的
routingMode: "tunnel" # tunnel 模式要开启,默认就是开启的
ipv4NativeRoutingCIDR: "10.90.0.0/16"
kubeProxyReplacement: "true"
# 这里看情况要不要开启3层加密:node, node2node, pod2pod
# encryption:
# enabled: true
# type: wireguard
# nodeEncryption: true
# strictMode:
# enabled: true
# cidr: "10.91.0.0/16"
# allowRemoteNodeIdentities: true
socketLB:
enabled: true
hostNamespaceOnly: true
bpf:
lbExternalClusterIP: true
daemon:
devices: ["k8s_wg0"] # 这里要指定使用 wireguard 的网卡
EOF

# 部署
helm upgrade --install cilium cilium/cilium --version 1.15.7 -f cilium.yaml --namespace kube-system

# 检查状态
kubectl -n kube-system exec ds/cilium -- cilium-dbg status --verbose

测试和验证

最后验证下,部署一个 nginx pod,然后访问一下,看看是否能访问到。

cat <<EOF > nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
nodePort: 30080
EOF

kubectl apply -f nginx-deploy.yaml
kubectl rollout restart deploy nginx-deploy