四处捡破烂,搭了个 k3s 集群用用。
资产清单
- Oracle Always Free 凑了 4 台主机
- node1
- node2
- node3
- node4
- Google Cloud Always Free 凑了 1 台主机
- node5
- Racknerd 凑了 1 台传家宝主机「这个花钱了,但是超值」
- node6
搭建过程
下面记录搭建过程
网络环境构建
在安装 k3s 之前,需要先确保网络通畅。可以使用的 OpenVPN Access Server,可免费创建 2 个连接,刚好够用了。首先在 node1 上部署 OpenVPN Access Server,对默认配置做下修改,在 VPN 设置中添加静态 IP 段。

使用路由模式访问内网段其他主机,像下图中这样,允许配置的内网段可以访问到 VPN 客户端和对应的子网,同时开启 Gateway 模式,

在网络设置中关闭 multi-daemon 模式,这里我选择仅开启 TCP 协议,

之后在 node5 和 node6 安装 OpenVPN 客户端,
apt update
apt install openvpnOpenVPN Access Server 创建两个账户,分别配置静态 IP 并导出配置文件,内容拷贝至 node5 (10.3.0.2) 和 node6(10.3.0.3)的 /etc/openvpn/client/client.conf 文件中并重启服务,
systemctl enable openvpn-client@client
systemctl restart openvpn-client@client服务启动后会自动创建一个 Tunnel 设备,

为了保证能够访问到 Oracle 子网中的其他主机,还需在 Oracle Cloud 中为子网配置路由,

之后从 10.3.0.2 尝试 ping Oracle 子网内的任意主机,确认网络可达。

安装 k3s Server
推荐直接使用命令行进行安装,配置文件在服务卸载之后就没了。
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - --token <token> --tls-san <node1_vpn_ip> --tls-san <node1_ip> --tls-san <domain> --tls-san <public_ip> --write-kubeconfig-mode 644 --advertise-address <node1_ip> --node-external-ip <public_ip>Info
有一点需要注意,由于此处借助 VPN 构建链路,防火墙没有放通 k3s 的 api server 端口。所以在配置
--node-external-ip时需要同时指定--advertise-address,否则会导致节点间部分网络故障。
安装 k3s Agent
参考如下命令并执行即可
curl -sfL https://get.k3s.io | K3S_URL=https://<internal_ip>:6443 K3S_TOKEN=<token> sh -s - --node-external-ip <public_ip>
这里有一点比较麻烦,由于这个 k3s 集群涉及到的主机环境太复杂,所以未安装云服务厂商的 cloud controller 而是使用 k3s 的默认 controller。为了后续的服务发布,需手动给这些节点逐个配置 --node-external-ip。
注意事项
对于安装 OpenVPN 的两个节点 node5 和 node6,需要使用 --flannel-iface 选项指定使用 OpenVPN 创建的虚拟网卡(一般为 tun 开头)
curl -sfL https://get.k3s.io | K3S_URL=https://<internal_ip>:6443 K3S_TOKEN=yViMiGUyoCpF6J sh -s - --node-ip <internal_ip> --node-external-ip <public_ip> --flannel-iface tun0
原因请参考 故障排查。
本地访问
安装完成之后将 k3s 文件下载到本地,并放置在 ~/.kube/config 文件中
export SERVER_IP=<server-ip>
scp user@$SERVER_IP:/etc/rancher/k3s/k3s.yaml ~/.kube/config && \
sed -i "s/127.0.0.1/$SERVER_IP/g" ~/.kube/config && \
export KUBECONFIG=~/.kube/config之后可通过 kubectl 命令进行交互
kubectl get nodes
NAME STATUS ROLES AGE VERSION
node2 Ready <none> 129m v1.31.6+k3s1
node5 Ready <none> 126m v1.31.6+k3s1
node1 Ready control-plane,master 139m v1.31.6+k3s1
node3 Ready <none> 129m v1.31.6+k3s1
node4 Ready <none> 130m v1.31.6+k3s1
node6 Ready <none> 127m v1.31.6+k3s1服务部署
尝试创建一个 nginx 服务,并通过 NodePort 模式对外暴露
kubectl create deployment nginx-test --image=nginx:latest
kubectl expose deployment nginx-test --port 80 --type NodePort
确认防火墙放通 30000-32767 端口,之后通过任意节点的公网 IP+ 端口即可访问该服务
# Get the NodePort number
NODE_PORT=$(kubectl get svc nginx-test -o jsonpath='{.spec.ports[0].nodePort}')
# Access using any node's external IP
curl http://<public_ip>:$NODE_PORT
故障排查
DNS 服务异常
在 node6 上运行 busybox 时发现,使用 nslookup 无法正常解析 DNS 请求,
/ # nslookup mysql-service
;; connection timed out; no servers could be reacheping CoreDNS 的 Services IP 也不通
/ # ping 10.43.0.10
PING 10.43.0.10 (10.43.0.10): 56 data bytes
^C
--- 10.43.0.10 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss在 node6 的 tun0 网卡上进行抓包
sudo tcpdump 'not port 6443' -i tun0 -vn -w 3.pcap
可以看到 DNS 请求正常发送

请求实际发送至运行 CoreDNS 的 Pod 节点(10.42.0.6)

分析发现该数据包为 flannel 封装后的内容,但源 IP 却为地址 node6 的公网 IP,根据路由规则该数据包能够发送至 VPN 对应的虚拟网卡,但由于源 IP 不对导致对端 VPN 虚拟网卡不会处理该数据包。为了解决该问题,可通过 --flannel-iface 选项指定网络接口。