跨越公网的Kubernetes集群:从零构建混合云架构
一个真实的生产案例:如何让内网Kubernetes集群与远程GPU节点协同工作
📖 目录
1. 背景:为什么需要混合集群
1.1 真实场景
想象这样一个场景:
- 你有一个内网Kubernetes集群(172.1.x.x),部署着公司的核心服务
- 公司采购了一台高性能GPU服务器,但只能放在远程机房
- GPU服务器有公网IP (. ..),但与内网集群隔离
- 你希望:
- GPU节点能运行Kubernetes Pod,享受K8s的调度和管理
- 内网服务(MinIO、MongoDB)能被GPU节点访问
- 主集群能管理GPU节点(kubectl logs、exec等)
- 所有通信安全加密
这就是典型的混合云场景:内网主集群 + 公网边缘节点。
1.2 为什么不简单?
如果两个集群在同一内网,一切都很简单。但跨公网后:
❌ 问题1:内网集群无法直接访问GPU节点的私有IP
❌ 问题2:GPU节点无法直接访问内网服务(防火墙/NAT)
❌ 问题3:Kubernetes组件期望节点在同一网络(Kubelet、CNI等)
我们需要一套完整的网络架构来解决这些问题。
2. 架构抽象:理解网络的层次
在深入技术细节前,先建立一个清晰的抽象模型。
2.1 三层网络模型
Kubernetes混合集群的网络可以抽象为三层:
访问
底层支撑
基础设施层 (Infrastructure Layer)
物理网络(公网/内网)
隧道技术(VPN/SSH/Konnectivity)
容器网络(Flannel/Calico)
管理层 (Control Plane)
kubectl命令
API Server ↔ Kubelet通信
日志收集、监控、调度
应用层 (Application Layer)
MinIO对象存储
MongoDB数据库
用户业务Pod
理解这三层很重要:
- 应用层关心"能不能访问服务"
- 管理层关心"能不能管理节点"
- 基础设施层负责"打通网络通道"
2.2 节点拓扑图
我们的实际拓扑:
内网集群 192.168.0.0/16
Master-1
192.168.0.1
• API Server
• Scheduler
• etcd
Worker-1
192.168.0.2
• MinIO
• Ingress
Worker-2
192.168.0.3
• MongoDB
• 业务Pod
内网交换机/路由器
公网网关 NAT
互联网
公网
GPU节点
8.1.2.3
远程机房
• Kubelet
• GPU Pod
• 边缘应用
关键观察:
- 主集群3个节点,都在内网(192.168.0.x)
- GPU节点独立,只有公网IP(80.2.3.x)
- 两侧无法直接通信
3. 核心挑战:跨公网的三大难题
挑战1:双向通信的不对称性
问题描述:
主集群 → GPU节点:❌ 内网无法主动连接公网节点的私有端口
GPU节点 → 主集群:❌ 公网无法访问内网私有IP
技术根源:
- 内网有NAT,外部无法主动发起连接
- 防火墙策略限制入站流量
- 没有VPN或专线
影响:
- kubectl logs/exec 无法工作(需要API Server → Kubelet)
- GPU Pod无法访问MinIO/MongoDB(需要反向访问内网)
挑战2:Kubernetes原生假设被打破
Kubernetes设计时假设所有节点在同一扁平网络:
yaml
# Kubernetes期望的理想状态
node1: 192.168.0.1 ──┐
node2: 192.168.0.2 ──┼─→ 任意节点互相可达
node3: 192.168.0.3 ──┘
但我们的现实:
yaml
# 实际状态
master: 192.168.0.230 ──→ GPU节点不可达
worker: 192.168.0.122 ──→ GPU节点不可达
gpu: 8.1.3.4(公) + 10.x.x.x(私) ──→ 内网不可达
打破的假设:
- Pod IP直接路由(CNI期望)
- 节点IP互通(Kubelet心跳)
- Service网络可达(kube-proxy期望)
挑战3:安全与性能的平衡
安全需求:
- 数据必须加密(公网传输)
- 身份验证(防止中间人攻击)
- 最小权限原则
性能需求:
- 低延迟(AI推理实时性)
- 高带宽(大模型传输)
- 稳定性(避免频繁重连)
传统VPN方案的问题:
- WireGuard: 上级防火墙限制UDP,无法稳定运行
- OpenVPN: TCP-over-TCP性能差
- IPsec: 配置复杂,维护成本高
4. 解决方案:双向隧道设计
我们的核心思路:用不同的隧道技术分别解决单向通信问题
4.1 整体架构图
GPU节点(公网)
主集群(内网)
管理平面
Konnectivity Tunnel
gRPC over TLS
数据平面
SSH Tunnel (tun0)
10.10.0.2 → 10.10.0.1
控制连接
暴露
反向访问
数据连接
⨩ API Server
⨩ Konnectivity Server
⨩ MinIO
⨩ MongoDB
⨩ SSH Server
10.10.0.1
⨨ Kubelet:10250
⨨ Konnectivity Agent
⨨ 应用Pod
(访问内网)
⨨ SSH Client
tun0
互联网
公网、双向TCP
4.2 设计原则
原则1: 单向隧道,双向解决
与其建一个复杂的双向隧道,不如:
- Konnectivity: 专门解决 Master → GPU (管理流量)
- SSH Tunnel: 专门解决 GPU → Master (数据流量)
每个隧道只做一件事,更简单、更可靠。
原则2: 谁需要谁主动
- Master需要管理GPU → Konnectivity由GPU主动连接Master
- GPU需要访问内网服务 → SSH由GPU主动连接Master
原则3: 分层独立
容器网络
Pod IP路由
使用Flannel VXLAN
管理层
kubectl, logs
使用Konnectivity
应用层
MinIO, MongoDB
使用SSH隧道
每层互不干扰,可独立升级。
5. 深入组件:技术栈详解
现在我们深入每个核心组件,理解它们如何工作。
5.1 Konnectivity: Kubernetes官方隧道方案
什么是Konnectivity?
Konnectivity是Kubernetes官方提供的网络代理解决方案,专门用于:
- API Server访问Kubelet(kubectl logs, exec)
- API Server访问Pod(kubectl port-forward)
- API Server访问Service(aggregated API)
核心理念: 反向代理 + 长连接
架构组成
Kubelet (GPU:10250) Konnectivity Agent (GPU) Konnectivity Server API Server 用户/kubectl Kubelet (GPU:10250) Konnectivity Agent (GPU) Konnectivity Server API Server 用户/kubectl 步骤1: Agent主动建立连接 保持心跳 步骤2: 执行 kubectl logs 检测Pod在GPU节点 查看egress-selector配置 查找gpu-ampere01 的Agent连接 全程加密,无需内网端口暴露 gRPC长连接 连接成功 GET /api/pods/gpu-pod-123/log 转发请求(通过UDS) gRPC请求 HTTP: GET /containerLogs/... 日志数据 响应数据 返回响应 显示日志
工作流程详解
假设你执行 kubectl logs gpu-pod-123:
步骤1: kubectl → API Server
bash
GET /api/v1/namespaces/default/pods/gpu-pod-123/log
步骤2: API Server检测到Pod在GPU节点,查看Egress配置
yaml
# /etc/kubernetes/egress-selector-configuration.yaml
egressSelections:
- name: cluster
connection:
proxyProtocol: GRPC
transport:
uds:
udsName: /etc/srv-konnectivity-server/konnectivity-server.socket
步骤3: API Server将请求转发给Konnectivity Server(通过Unix Socket)
步骤4 : Konnectivity Server在已建立的gRPC长连接中找到gpu-ampere01的Agent
步骤5: 通过长连接发送请求到Agent
步骤6 : Agent在GPU节点本地访问 http://localhost:10250/containerLogs/...
步骤7: 响应原路返回
关键优势:
- ✅ Agent主动连接Server,无需内网端口暴露
- ✅ 长连接复用,避免频繁握手
- ✅ TLS加密,安全可靠
- ✅ Kubernetes官方支持,无缝集成
配置要点
Agent配置 (GPU节点):
yaml
args:
- --logtostderr=true
- --ca-cert=/etc/srv-konnectivity-server-ca/ca.crt
- --proxy-server-host=kubernetes # 关键:使用DNS名而非IP
- --proxy-server-port=8132
- --agent-identifiers=host=gpu-ampere01
# 关键技巧:hostAliases解决证书验证
hostAliases:
- ip: "114.1.2.3" # 主集群公网IP
hostnames:
- kubernetes # 匹配证书DNS SAN
为什么用hostAliases?
API Server证书的Subject Alternative Names (SAN)是:
DNS: kubernetes
DNS: kubernetes.default
IP: 192.168.0.230 # 内网IP
没有公网IP 114.1.2.3!如果Agent直接连接IP,TLS验证会失败:
x509: cannot validate certificate for 114.1.2.3 because it doesn't contain any IP SANs
通过hostAliases,我们让Agent通过DNS名kubernetes连接,然后将其解析为公网IP,完美!
5.2 SSH隧道: 数据平面的反向通道
虽然Konnectivity很强大,但它是单向的(Master → GPU)。GPU节点上的应用如何访问内网的MinIO、MongoDB呢?
答案:SSH反向隧道
SSH隧道原理
SSH隧道(SSH Tunneling)是利用SSH协议建立加密通道,转发流量的技术。
正向隧道 (Local Port Forwarding):
bash
# 本地8080 → 远程服务器的3000
ssh -L 8080:localhost:3000 user@remote-server
反向隧道 (Remote Port Forwarding):
bash
# 远程服务器8080 → 本地3000
ssh -R 8080:localhost:3000 user@remote-server
动态隧道 (Dynamic Port Forwarding / SOCKS代理):
bash
# 本地1080作为SOCKS5代理
ssh -D 1080 user@remote-server
我们使用的是 TUN设备隧道,创建虚拟网卡:
我们的SSH Tunnel配置
Master节点 (SSH Server):
bash
# /etc/ssh/sshd_config
PermitRootLogin no
PermitTunnel yes # 允许TUN设备
GPU节点 (SSH Client):
bash
# 建立隧道
ssh -o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-w 0:0 \ # TUN设备编号
root@114.1.2.3
# 创建后的网络配置
ifconfig tun0 10.10.0.2 netmask 255.255.255.0 # GPU侧
ifconfig tun0 10.10.0.1 netmask 255.255.255.0 # Master侧
# 添加路由
ip route add 192.168.0.0/16 via 10.10.0.1 dev tun0 # GPU节点
效果:
GPU节点访问 192.168.0.233:31017 (MongoDB)
↓
查路由表 → 匹配 192.168.0.0/16
↓
经由 tun0 (10.10.0.1)
↓
SSH隧道加密传输
↓
Master节点解密,转发到 192.168.0.233:31017
↓
返回数据原路返回
AutoSSH保活
SSH连接可能断开(网络波动、防火墙),使用AutoSSH自动重连:
bash
# systemd service
[Service]
ExecStart=/usr/bin/autossh -M 0 \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-w 0:0 root@114.1.2.3
Restart=always
RestartSec=10
5.3 Flannel VXLAN: Pod网络的跨公网扩展
Kubernetes Pod之间需要通信,这是由CNI(Container Network Interface)插件负责的。
Flannel基础
Flannel是轻量级的CNI插件,支持多种后端:
- VXLAN: 在UDP上构建虚拟L2网络
- Host-gw: 直接路由(要求同一L2)
- IPSec: 加密的VXLAN
我们使用 VXLAN over 公网。
VXLAN工作原理
VXLAN (Virtual Extensible LAN) 将L2以太网帧封装在UDP包中:
Flannel处理
原始Pod数据包
───────────────
Eth Header | IP Header | Payload
Pod1 → Pod2
Flannel VXLAN 封装
VXLAN封装后的包
────────────────────────────────────────
外层Eth | 外层IP | UDP:8472 | VXLAN | 内层包
Host1 → Host2 | Pod1→Pod2
关键点:
-
外层IP是节点IP(可以是公网IP)
-
UDP端口8472(VXLAN默认)
-
内层IP是Pod IP(10.244.x.x)
关键点:
- 外层IP是节点IP(可以是公网IP)
- UDP端口8472(VXLAN默认)
- 内层IP是Pod IP(10.244.x.x)
跨公网的VXLAN挑战
问题1: 公网IP vs 私有IP
Flannel默认使用节点的内网IP作为VTEP(VXLAN Tunnel Endpoint)。但GPU节点有公网IP!
解决: 使用annotation指定公网IP:
yaml
apiVersion: v1
kind: Node
metadata:
name: gpu-ampere01
annotations:
flannel.alpha.coreos.com/public-ip: "8.1.2.3" # 公网IP
问题2: MTU设置
VXLAN封装增加50字节头部:
原始以太网: 1500 MTU
+ VXLAN封装: 50字节
= 需要: 1550 MTU
但GPU节点通过公网,MTU通常是1500。解决:
yaml
# Flannel ConfigMap
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan",
"VNI": 1,
"MTU": 1320 # 保守值,避免分片
}
}
问题3: FDB(Forwarding Database)同步
Flannel需要知道"Pod IP在哪个节点"。通过Kubernetes API的Node对象和Flannel的backend实现:
bash
# 查看FDB
bridge fdb show dev flannel.1
# 输出示例
00:00:00:00:00:00 dst 8.1.2.3 self permanent # GPU节点
5.4 技术栈总结
现在你应该理解了每个组件的职责:
| 组件 | 作用 | 方向 | 流量类型 | 协议 |
|---|---|---|---|---|
| Konnectivity | API Server访问Kubelet | Master → GPU | 管理流量 | gRPC/TLS |
| SSH Tunnel | GPU访问内网服务 | GPU → Master | 数据流量 | SSH/TCP |
| Flannel VXLAN | Pod间通信 | 双向 | 容器流量 | UDP/8472 |
6. 实战配置:从理论到实践
理论讲完了,现在手把手配置。
6.1 环境准备
主集群节点:
bash
# Master-1
IP: 192.168.0.230 (内网) / 114.1.2.3 (公网)
OS: Ubuntu 22.04
K8s: v1.28.2
# Worker-1
IP: 192.168.0.122 (内网)
# Worker-2
IP: 192.168.0.233 (内网)
GPU节点:
bash
IP: 8.1.2.3 (公网) / 10.x.x.x (内网,不可用)
OS: Ubuntu 22.04
GPU: NVIDIA A100
6.2 步骤1: 部署Konnectivity Server
1. 生成证书(如果需要):
bash
# 在Master节点,使用集群已有的CA
cd /etc/kubernetes/pki
# API Server证书已有kubernetes作为DNS SAN
openssl x509 -in apiserver.crt -text -noout | grep DNS
# 输出: DNS:kubernetes, DNS:kubernetes.default, ...
2. 创建Konnectivity Server Deployment:
yaml
# konnectivity-server.yaml
apiVersion: v1
kind: Pod
metadata:
name: konnectivity-server
namespace: kube-system
spec:
hostNetwork: true
containers:
- name: konnectivity-server-container
image: registry.k8s.io/kas-network-proxy/proxy-server:v0.0.32
command:
- /proxy-server
- --logtostderr=true
- --v=3
- --server-port=0 # 禁用HTTP
- --agent-port=8132 # Agent连接端口
- --health-port=8133
- --admin-port=8134
- --cluster-cert=/etc/kubernetes/pki/ca.crt
- --cluster-key=/etc/kubernetes/pki/ca.key
- --server-cert=/etc/kubernetes/pki/apiserver.crt
- --server-key=/etc/kubernetes/pki/apiserver.key
- --mode=grpc
- --uds-name=/etc/srv-konnectivity-server/konnectivity-server.socket
volumeMounts:
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
- mountPath: /etc/srv-konnectivity-server
name: uds-socket
volumes:
- name: k8s-certs
hostPath:
path: /etc/kubernetes/pki
- name: uds-socket
emptyDir: {}
3. 配置API Server:
yaml
# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
# ... 其他参数 ...
- --egress-selector-config-file=/etc/kubernetes/egress-selector-configuration.yaml
volumeMounts:
- mountPath: /etc/kubernetes/egress-selector-configuration.yaml
name: egress-selector-config
readOnly: true
- mountPath: /etc/srv-konnectivity-server
name: konnectivity-uds
volumes:
- hostPath:
path: /etc/kubernetes/egress-selector-configuration.yaml
type: FileOrCreate
name: egress-selector-config
- hostPath:
path: /etc/srv-konnectivity-server
type: DirectoryOrCreate
name: konnectivity-uds
4. Egress Selector配置:
yaml
# /etc/kubernetes/egress-selector-configuration.yaml
apiVersion: apiserver.k8s.io/v1beta1
kind: EgressSelectorConfiguration
egressSelections:
- name: cluster
connection:
proxyProtocol: GRPC
transport:
uds:
udsName: /etc/srv-konnectivity-server/konnectivity-server.socket
6.3 步骤2: 部署Konnectivity Agent
在GPU节点加入集群后,部署Agent:
yaml
# konnectivity-agent.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: konnectivity-agent
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: konnectivity-agent
template:
metadata:
labels:
k8s-app: konnectivity-agent
spec:
hostAliases:
- ip: "114.1.2.3" # Master公网IP
hostnames:
- kubernetes # 匹配证书DNS SAN
nodeSelector:
kubernetes.io/hostname: gpu-ampere01
containers:
- name: konnectivity-agent
image: registry.k8s.io/kas-network-proxy/proxy-agent:v0.0.32
args:
- --logtostderr=true
- --v=3
- --ca-cert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- --proxy-server-host=kubernetes
- --proxy-server-port=8132
- --agent-identifiers=host=gpu-ampere01
- --service-account-token-path=/var/run/secrets/tokens/konnectivity-agent-token
volumeMounts:
- mountPath: /var/run/secrets/tokens
name: konnectivity-agent-token
serviceAccountName: konnectivity-agent
volumes:
- name: konnectivity-agent-token
projected:
sources:
- serviceAccountToken:
path: konnectivity-agent-token
audience: system:konnectivity-server
创建ServiceAccount和RBAC:
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: konnectivity-agent
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:konnectivity-agent
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: konnectivity-agent
namespace: kube-system
6.4 步骤3: 配置SSH隧道
Master节点:
bash
# 1. 配置SSH Server
vi /etc/ssh/sshd_config
# 添加
PermitTunnel yes
PermitRootLogin禁止-password # 仅密钥登录
systemctl restart sshd
# 2. 配置网络(隧道建立后)
ip addr add 10.10.0.1/24 dev tun0
ip link set tun0 up
GPU节点:
bash
# 1. 生成SSH密钥
ssh-keygen -t ed25519 -C "gpu-tunnel"
# 2. 复制公钥到Master
ssh-copy-id root@114.1.2.3
# 3. 创建systemd service
cat > /etc/systemd/system/tunnel-to-master.service <<EOF
[Unit]
Description=SSH Tunnel to Master Cluster
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/autossh -M 0 \\
-o "ServerAliveInterval=30" \\
-o "ServerAliveCountMax=3" \\
-o "StrictHostKeyChecking=no" \\
-o "ExitOnForwardFailure=yes" \\
-w 0:0 root@114.1.2.3
ExecStartPost=/bin/sleep 2
ExecStartPost=/sbin/ip addr add 10.10.0.2/24 dev tun0
ExecStartPost=/sbin/ip link set tun0 up
ExecStartPost=/sbin/ip route add 192.168.0.0/16 via 10.10.0.1 dev tun0
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# 4. 启动服务
systemctl enable tunnel-to-master
systemctl start tunnel-to-master
# 5. 验证
ping 192.168.0.233 # 应该能通
6.5 步骤4: 配置Flannel
修改Flannel ConfigMap:
bash
kubectl edit cm kube-flannel-cfg -n kube-flannel-system
json
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan",
"VNI": 1,
"Port": 8472,
"MTU": 1320
}
}
GPU节点加annotation:
bash
kubectl annotate node gpu-ampere01 \
flannel.alpha.coreos.com/public-ip=8.1.2.3 \
--overwrite
6.6 验证
1. 验证Konnectivity:
bash
# 查看Agent状态
kubectl get pods -n kube-system -l k8s-app=konnectivity-agent
# 查看Server日志
kubectl logs -n kube-system konnectivity-server | grep "Register backend"
# 应看到: "Register backend for agent" agentID="gpu-ampere01"
# 测试kubectl logs
kubectl logs <gpu-pod-name> # 应该成功
2. 验证SSH隧道:
bash
# GPU节点
ping 192.168.0.233
curl http://192.168.0.233:31017 # MongoDB
3. 验证Flannel:
bash
# 查看FDB
bridge fdb show dev flannel.1 | grep 8.1.2.3
# Pod间通信测试
kubectl run test --image=busybox --command -- sleep 3600
kubectl exec test -- ping <gpu-pod-ip>
7. 运维实践:监控与故障排查
7.1 监控指标
Konnectivity监控:
bash
# Prometheus指标
curl http://localhost:8134/metrics
# 关键指标
konnectivity_network_proxy_server_ready # Server就绪
konnectivity_network_proxy_agent_open_connections # Agent连接数
konnectivity_network_proxy_agent_dial_failure_count # 连接失败
SSH隧道监控:
bash
# 检查隧道状态
systemctl status tunnel-to-master
# 检查tun0接口
ip -s link show tun0 # 查看流量统计
# 持续监控
watch -n 5 'ip addr show tun0; echo "---"; ip route show | grep tun0'
7.2 常见问题排查
问题1: kubectl logs超时
症状:
bash
$ kubectl logs gpu-pod
Error from server: Get "https://192.168.0.122:10250/...": dial tcp 192.168.0.122:10250: i/o timeout
排查:
bash
# 1. 检查Konnectivity Agent状态
kubectl get pods -n kube-system -l k8s-app=konnectivity-agent
# 应该是Running
# 2. 查看Agent日志
kubectl logs -n kube-system konnectivity-agent-xxx
# 查找 "Connected" 或错误信息
# 3. 检查Server日志
kubectl logs -n kube-system konnectivity-server
# 查找 "Register backend"
# 4. 检查UDS socket
ls -lh /etc/srv-konnectivity-server/konnectivity-server.socket
# 应该存在
常见原因:
- UDS socket未创建 → 检查Server配置
- Agent连接失败 → 检查证书、端口、防火墙
- API Server未配置egress-selector → 检查manifest
问题2: GPU Pod无法访问MinIO
症状:
bash
# 在GPU Pod内
curl http://192.168.0.233:30090
# 超时
排查:
bash
# 在GPU节点宿主机
# 1. 检查tun0
ip addr show tun0
# 应有 10.10.0.2
# 2. 检查路由
ip route | grep 192.168
# 应有: 192.168.0.0/16 via 10.10.0.1 dev tun0
# 3. Ping测试
ping 192.168.0.233
# 应该通
# 4. 检查SSH隧道
systemctl status tunnel-to-master
ps aux | grep ssh
常见原因:
- SSH隧道断开 → 重启service
- 路由未生效 → 手动添加路由
- 防火墙拦截 → 检查iptables
问题3: Pod间无法通信
症状:
bash
# Master节点Pod ping GPU节点Pod
ping 10.244.5.10
# 不通
排查:
bash
# 1. 检查Flannel annotation
kubectl get node gpu-ampere01 -o yaml | grep public-ip
# 应该是公网IP
# 2. 检查FDB
bridge fdb show dev flannel.1 | grep 8.1.2.3
# 3. 抓包验证
tcpdump -i eth0 udp port 8472 -nn
# 发送ping时应看到VXLAN包
# 4. 检查MTU
ip link show flannel.1
# MTU应该是1320或更小
8. 经验总结:踩坑与优化
8.1 我们踩过的坑
坑1: 证书SAN不匹配
问题: Agent连接Server时报错:
x509: cannot validate certificate for 114.1.2.3 because it doesn't contain any IP SANs
原因: API Server证书没有公网IP的SAN
解决 : 使用hostAliases将DNS名(kubernetes)映射到公网IP
教训: 理解TLS证书验证机制,灵活运用DNS
坑2: WireGuard UDP被拦截
问题: 最初使用WireGuard,频繁断线
原因: 上级防火墙严格限制UDP流量
解决: 改用基于TCP的SSH隧道
教训: 生产环境的网络策略可能很复杂,方案要适应现实
坑3: MTU分片导致性能下降
问题: 大文件传输速度很慢
原因: VX LAN封装 + SSH隧道导致MTU过小,频繁分片
解决:
- 降低Flannel MTU到1320
- 优化应用层,避免大包
教训: 跨公网注意MTU设置
8.2 性能优化建议
1. 连接复用
Konnectivity使用长连接,避免频繁建立TLS握手:
yaml
# Agent配置
- --keepalive-time=1h
- --sync-interval=1s
2. 监控告警
关键指标告警:
yaml
# Prometheus Rules
- alert: KonnectivityAgentDown
expr: up{job="konnectivity-agent"} == 0
for: 5m
- alert: SSHTunnelDown
expr: node_network_up{device="tun0"} == 0
for: 2m
3. 日志级别
生产环境降低日志级别:
--v=3 # 开发环境
--v=1 # 生产环境(减少IO)
8.3 安全建议
- SSH密钥管理: 使用ed25519密钥,定期轮换
- 证书过期: 监控API Server证书有效期
- 网络隔离: SSH隧道仅允许特定IP段
- 审计日志: 开启Kubernetes审计,记录跨网访问
8.4 架构演进方向
短期(已完成):
- ✅ 基于SSH + Konnectivity的混合方案
- ✅ Flannel VXLAN跨公网
中期(规划中):
- ⏳ Konnectivity UDS配置完成,实现完整管理面
- ⏳ 使用Cilium替换Flannel(eBPF性能优化)
- ⏳ WireGuard over TCP(如新版本支持)
长期(愿景):
- 🎯 服务网格(Istio/Linkerd)统一管理
- 🎯 专线/SD-WAN替代公网隧道
- 🎯 边缘自治(离线也能运行)
总结
我们从零构建了一个跨公网的Kubernetes混合集群,核心要点:
架构精华
┌────────────────────────────────────────────┐
│ 理念: 分层解耦,单向隧道双向互补 │
│ │
│ 管理面: Konnectivity (Master → GPU) │
│ 数据面: SSH Tunnel (GPU → Master) │
│ 容器网络: Flannel VXLAN (跨公网Pod通信) │
└────────────────────────────────────────────┘
关键技术
- Konnectivity: Kubernetes官方方案,gRPC长连接,反向代理
- SSH Tunnel: TUN设备,加密可靠,AutoSSH保活
- Flannel VX LAN: UDP封装,Public IP annotation,MTU优化
- 证书技巧: hostAliases绕过IP SAN限制
适用场景
✅ 适合:
- 边缘计算(GPU/IoT设备)
- 混合云(公有云+私有云)
- 多数据中心互联
- 临时节点扩容
❌ 不适合:
- 延迟敏感应用(<10ms要求)
- 超大规模(>100节点建议专线)
- 合规要求禁止公网传输
学习路线
如果你想深入,建议按此顺序学习:
- 基础: Kubernetes网络模型(Service、Pod网络、DNS)
- 进阶: CNI插件原理(Flannel、Calico)
- 高级: Konnectivity源码、gRPC协议
- 实战: 在测试环境复现本文配置
参考文档
- Kubernetes Network Model
- Konnectivity Service
- Flannel Documentation
- SSH Tunneling Guide
- 集群实际配置:
/Users/wwyz/Documents/zy/oilan/docs/infrastructure/