用 SSH 做隧道:K8s 集群内跨节点访问的进阶解决方案
前言:K8s 跨节点访问的 “痛点” 与 SSH 隧道的价值
你是否在 K8s 运维中遇到过这些场景?
节点 B 上运行着一个本地调试服务(如 10.0.0.11:8080),未暴露为 K8s Service,节点 A 的 Pod 因网络策略限制无法直接访问;本地开发机需调试节点 C 上的私有 Pod(无 ExternalIP、无 Ingress 暴露),只能通过集群节点间接访问;跨命名空间的 Service 因网络策略仅允许节点 D 访问,其他节点的 Pod 需临时访问该 Service,修改网络策略周期长。
K8s 原生网络方案(如 Service、Ingress、NetworkPolicy)虽能解决多数访问问题,但在 临时调试、私有服务访问、快速验证 场景下,存在 “配置复杂、周期长、侵入性强” 的问题。而 SSH 隧道 作为轻量解决方案,具备三大优势:
无侵入性:无需修改 K8s 网络配置(如 NetworkPolicy、Service),仅依赖节点 SSH 服务;加密安全:所有跨节点流量经 SSH 加密传输,避免集群内明文数据泄露;快速部署:几分钟内即可建立隧道,适合临时访问、紧急调试场景。
本文从 “场景→原理→实战→优化” 全流程讲解,带你掌握 SSH 隧道在 K8s 跨节点访问中的核心用法。
一、核心认知:K8s 集群内 SSH 隧道的适用场景与原理
在动手前,先明确 SSH 隧道的定位的工作逻辑,避免与 K8s 原生方案混淆:
1. 适用场景(什么时候用 SSH 隧道?)
|
场景类型 |
具体需求 |
为什么选 SSH 隧道? |
|
节点本地服务访问 |
节点运行非 K8s 管理的服务(如调试脚本、本地数据库),Pod 需跨节点访问 |
无需暴露为 Service,避免集群内服务冗余 |
|
本地调试跨节点 Pod |
本地开发机需访问集群内无外部暴露的 Pod(如私有测试 Pod) |
无需配置 Ingress/NodePort,快速建立调试通道 |
|
网络策略受限访问 |
跨节点访问受 NetworkPolicy 拦截,需临时绕过验证 |
无需修改 NetworkPolicy(避免影响其他业务) |
|
跨节点 Pod 直连 |
节点 A 的 Pod 需直接访问节点 B 的 Pod(无 Service 转发) |
跳过 K8s 网络插件转发,减少延迟(适合调试) |
2. 核心原理:SSH 隧道如何实现 K8s 跨节点转发?
K8s 集群内的 SSH 隧道,本质是通过 集群内某节点(跳板节点) 建立加密通道,将跨节点流量转发到目标地址,核心分为两种模式:
(1)本地转发(Local Forwarding):Pod / 本地 → 跳板节点 → 目标节点
|
节点A的Pod(10.244.0.5)→ 节点A本地端口(127.0.0.1:9000)→ SSH隧道 → 节点B(10.0.0.11)→ 节点B的目标服务(127.0.0.1:8080) |
核心用途:Pod 访问其他节点的本地服务(未暴露为 Service),或本地开发机通过跳板节点访问目标节点服务。
(2)远程转发(Remote Forwarding):目标节点 → 跳板节点 → Pod / 本地
|
节点B的Pod(10.244.1.6:3306)→ SSH隧道 → 节点A(10.0.0.10)的本地端口(0.0.0.0:3307)→ 节点A的Pod访问(10.0.0.10:3307) |
核心用途:目标节点的 Pod 需主动暴露服务给其他节点访问(如临时共享调试服务)。
3. 前置条件(集群需满足的基础环境)
节点 SSH 服务正常:所有涉及的 K8s 节点需安装并启动 SSH 服务(多数 Linux 发行版默认安装,可通过 systemctl status sshd 验证);节点间 SSH 免密登录:隧道建立需无交互执行,需配置跳板节点与目标节点间的 SSH 免密登录(下文详细步骤);端口权限:隧道使用的本地端口(如 9000、3307)需未被占用,且节点防火墙允许该端口访问(如 firewall-cmd –add-port=9000/tcp –permanent)。
二、基础准备:K8s 节点间 SSH 免密登录配置(核心前提)
SSH 隧道需无交互执行,因此必须先配置 跳板节点与目标节点 间的免密登录,以 “节点 A(跳板)→ 节点 B(目标)” 为例:
步骤 1:在跳板节点(节点 A)生成 SSH 密钥
登录节点 A(可通过 kubectl debug node/节点A名称 -it –image=ubuntu 进入节点,或直接通过节点 IP 登录),执行:
|
# 生成 ED25519 密钥(无密码,避免交互) ssh-keygen -t ed25519 -a 200 -f ~/.ssh/k8s_ssh_tunnel -C “k8s-cross-node-tunnel” # 执行后按 3 次回车(无需设置 Passphrase,否则隧道建立需输密码) |
生成两个文件:~/.ssh/k8s_ssh_tunnel(私钥)、~/.ssh/k8s_ssh_tunnel.pub(公钥)。
步骤 2:将公钥分发到目标节点(节点 B)
在节点 A 执行,将公钥上传到节点 B,实现免密登录:
|
# 格式:ssh-copy-id -i 公钥路径 目标节点用户名@目标节点IP ssh-copy-id -i ~/.ssh/k8s_ssh_tunnel.pub root@10.0.0.11 # 节点B IP为10.0.0.11,用户为root(根据实际修改) # 首次执行需输入节点B的root密码,后续登录无需密码 |
步骤 3:验证节点间免密登录
在节点 A 执行,验证是否能无交互登录节点 B:
|
# 格式:ssh -i 私钥路径 目标节点用户名@目标节点IP “echo '免密登录成功'” ssh -i ~/.ssh/k8s_ssh_tunnel root@10.0.0.11 “echo '免密登录成功'” # 输出“免密登录成功”即配置完成,可建立 SSH 隧道 |
三、实战场景:K8s 集群内 SSH 隧道的 3 大高频用法
以下场景均基于 “K8s 集群节点 IP 段:10.0.0.0/24,Pod IP 段:10.244.0.0/16”,所有命令均在集群节点或本地开发机执行,可根据实际环境修改参数。
场景 1:节点 A 的 Pod 访问节点 B 的本地服务(未暴露为 Service)
需求描述
节点 B(IP:10.0.0.11)运行着一个本地调试服务(127.0.0.1:8080,非 K8s 管理,仅节点 B 本地可访问);节点 A(IP:10.0.0.10)的 Pod(IP:10.244.0.5)需访问该服务,因 NetworkPolicy 限制,Pod 无法直接 ping 通节点 B。
实现步骤(本地转发隧道)
步骤 1:在跳板节点(节点 A)建立 SSH 本地隧道
登录节点 A,执行隧道命令,将节点 A 的 127.0.0.1:9000 端口转发到节点 B 的 127.0.0.1:8080:
|
# 格式:ssh -L 跳板节点本地IP:跳板端口:目标服务IP:目标端口 目标节点用户@目标节点IP -i 私钥路径 -fN ssh -L 0.0.0.0:9000:127.0.0.1:8080 root@10.0.0.11 -i ~/.ssh/k8s_ssh_tunnel -fN |
参数解析:
0.0.0.0:9000:节点 A 的所有网卡监听 9000 端口(允许 Pod 访问,若填 127.0.0.1:9000 则仅节点 A 本地可访问);127.0.0.1:8080:节点 B 的目标服务地址(本地服务,仅节点 B 可访问);-fN:后台运行(-f)、不执行远程命令(-N),仅建立隧道。
步骤 2:验证节点 A 的隧道端口监听
在节点 A 执行,确认 9000 端口已监听:
|
netstat -tuln | grep 9000 # 输出示例:tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN |
步骤 3:Pod 访问隧道端口(穿透到节点 B 服务)
进入节点 A 的 Pod(10.244.0.5),执行访问命令:
|
# 1. 进入 Pod kubectl exec -it Pod名称 -n 命名空间 — /bin/bash # 2. 访问节点 A 的 9000 端口(即转发到节点 B 的 8080 服务) curl http://10.0.0.10:9000 # 10.0.0.10 是节点 A 的 IP |
效果:若返回节点 B 服务的响应(如 HTML 页面、JSON 数据),说明隧道生效,Pod 成功跨节点访问服务。
步骤 4:隧道管理(停止 / 重启)
|
# 停止隧道(先查进程号) ps aux | grep “ssh -L 0.0.0.0:9000” | grep -v grep kill -9 进程号 # 替换为实际进程号 # 重启隧道(重新执行步骤1的命令) |
场景 2:本地开发机访问 K8s 节点 C 的私有 Pod(无外部暴露)
需求描述
节点 C(IP:10.0.0.12)的 Pod(IP:10.244.1.7,端口 3306,MySQL 服务)未暴露为 Service,仅节点 C 可访问该 Pod;本地开发机需连接该 MySQL 服务,进行数据调试。
实现步骤(本地→跳板节点→目标 Pod 隧道)
步骤 1:选择跳板节点(如节点 A,IP:10.0.0.10,本地可访问)
确保本地开发机已配置到节点 A 的 SSH 免密登录(步骤同 “节点间免密”,本地生成密钥并上传到节点 A)。
步骤 2:本地开发机建立 SSH 本地隧道
在本地终端执行,将本地 127.0.0.1:3307 端口转发到节点 C 的 Pod(10.244.1.7:3306),通过节点 A 跳板:
|
# 格式:ssh -L 本地端口:目标PodIP:Pod端口 跳板节点用户@跳板节点IP -i 本地私钥路径 -fN ssh -L 127.0.0.1:3307:10.244.1.7:3306 root@10.0.0.10 -i ~/.ssh/local_to_k8s -fN |
核心逻辑:本地流量先转发到节点 A,节点 A 因处于集群内,可直接访问节点 C 的 Pod(K8s 节点默认可访问集群内所有 Pod),无需额外配置。
步骤 3:本地连接 MySQL 服务(穿透到 Pod)
使用本地 MySQL 客户端(如 Navicat、命令行)连接:
|
# 本地终端执行 mysql -h 127.0.0.1 -P 3307 -u root -p # 输入 Pod 中 MySQL 的 root 密码,若能成功连接并查看数据库,说明隧道生效 |
场景 3:跨节点访问受 NetworkPolicy 限制的 Service
需求描述
K8s 集群内有一个 Service(test-svc,端口 80),仅允许节点 D(IP:10.0.0.13)访问(NetworkPolicy 规则限制);节点 E(IP:10.0.0.14)的 Pod 需临时访问该 Service,修改 NetworkPolicy 需审批,周期长。
实现步骤(远程转发隧道)
步骤 1:在跳板节点(节点 D)建立 SSH 远程隧道
登录节点 D(Service 允许访问的节点),执行隧道命令,将节点 D 的 0.0.0.0:8080 端口转发到 test-svc 的地址(test-svc.test-namespace.svc.cluster.local:80):
|
# 格式:ssh -R 跳板节点监听IP:跳板端口:Service地址:Service端口 目标节点用户@目标节点IP -i 私钥路径 -fN # 注:此处“目标节点”为节点 E,隧道方向:节点 D 的 Service 流量 → 节点 E 的端口 ssh -R 0.0.0.0:8080:test-svc.test-namespace.svc.cluster.local:80 root@10.0.0.14 -i ~/.ssh/k8s_ssh_tunnel -fN |
参数解析:
-R:远程转发标识,将跳板节点(D)的服务转发到目标节点(E)的端口;0.0.0.0:8080:节点 E 的所有网卡监听 8080 端口(允许节点 E 的 Pod 访问);test-svc.test-namespace.svc.cluster.local:80:Service 的集群内 DNS 地址(节点 D 可访问)。
步骤 2:节点 E 的 Pod 访问本地隧道端口
进入节点 E 的 Pod,访问节点 E 的 8080 端口(即转发到 test-svc):
|
# 进入 Pod 后执行 curl http://10.0.0.14:8080 # 10.0.0.14 是节点 E 的 IP # 若返回 Service 的响应,说明隧道生效,成功绕过 NetworkPolicy 限制 |
四、进阶优化:提升 K8s 内 SSH 隧道的稳定性与安全性
1. 隧道自动重连(避免网络波动断开)
默认 SSH 隧道在网络闪断后会断开,需手动重启。用 autossh 工具可实现自动重连,步骤如下:
步骤 1:在跳板节点安装 autossh
|
# CentOS/RHEL sudo yum install autossh -y # Ubuntu/Debian sudo apt install autossh -y |
步骤 2:用 autossh 建立隧道(以场景 1 为例)
|
# 格式:autossh -M 心跳端口 -L 转发配置 其他参数 autossh -M 20000 -L 0.0.0.0:9000:127.0.0.1:8080 root@10.0.0.11 -i ~/.ssh/k8s_ssh_tunnel -fN |
-M 20000:指定心跳检测端口(20000),autossh 通过该端口监控隧道状态,断开后自动重连。
2. 安全加固(避免隧道被滥用)
(1)限制隧道端口的访问来源
仅允许指定 Pod / 节点访问隧道端口,通过 iptables 配置(以节点 A 为例,仅允许 10.244.0.0/16 网段的 Pod 访问 9000 端口):
|
# 在节点 A 执行 sudo iptables -A INPUT -p tcp –dport 9000 -s 10.244.0.0/16 -j ACCEPT # 允许 Pod 网段 sudo iptables -A INPUT -p tcp –dport 9000 -j REJECT # 拒绝其他来源 |
(2)使用专用 SSH 用户(避免 root 权限滥用)
在节点间创建专用用户(如 k8s_tunnel),仅授予 SSH 登录权限,无其他系统权限:
|
# 在目标节点(节点 B)创建用户 sudo useradd -m k8s_tunnel # 创建用户并生成家目录 sudo passwd k8s_tunnel # 设置密码(仅首次使用,后续用密钥) sudo mkdir -p /home/k8s_tunnel/.ssh sudo cp ~root/.ssh/authorized_keys /home/k8s_tunnel/.ssh/ # 复制公钥 sudo chown -R k8s_tunnel:k8s_tunnel /home/k8s_tunnel/.ssh |
后续隧道命令改用 k8s_tunnel 用户:ssh -L … k8s_tunnel@10.0.0.11 …。
(3)定期轮换 SSH 密钥
避免密钥长期使用导致泄露风险,每 1-3 个月轮换一次节点间的 SSH 密钥:
|
# 1. 在节点 A 重新生成密钥 ssh-keygen -t ed25519 -a 200 -f ~/.ssh/k8s_ssh_tunnel_new -C “k8s-tunnel-new” # 2. 分发新公钥到目标节点 ssh-copy-id -i ~/.ssh/k8s_ssh_tunnel_new.pub k8s_tunnel@10.0.0.11 # 3. 验证新密钥登录 ssh -i ~/.ssh/k8s_ssh_tunnel_new k8s_tunnel@10.0.0.11 “echo '新密钥登录成功'” # 4. 删除旧密钥(目标节点) ssh k8s_tunnel@10.0.0.11 “sed -i '/k8s-cross-node-tunnel/d' ~/.ssh/authorized_keys” # 5. 替换旧密钥文件 mv ~/.ssh/k8s_tunnel_new ~/.ssh/k8s_ssh_tunnel mv ~/.ssh/k8s_tunnel_new.pub ~/.ssh/k8s_ssh_tunnel.pub |
3. 结合 K8s 资源管理隧道(可选)
若需长期使用隧道,可将隧道命令封装为 K8s DaemonSet 或 Deployment,实现隧道的容器化管理(如自动重启、资源限制),示例 DaemonSet 配置(仅在节点 A 运行):
|
# ssh-tunnel-daemonset.yaml apiVersion: apps/v1 kind: DaemonSet metadata: name: ssh-tunnel namespace: kube-system spec: selector: matchLabels: app: ssh-tunnel template: metadata: labels: app: ssh-tunnel spec: nodeName: node-a # 仅在节点 A 运行 containers: – name: ssh-tunnel image: ubuntu:22.04 command: [“/bin/bash”, “-c”] args: – apt update && apt install -y openssh-client autossh; mkdir -p /root/.ssh; echo “$SSH_PRIVATE_KEY” > /root/.ssh/k8s_ssh_tunnel; chmod 600 /root/.ssh/k8s_ssh_tunnel; autossh -M 20000 -L 0.0.0.0:9000:127.0.0.1:8080 root@10.0.0.11 -i /root/.ssh/k8s_ssh_tunnel -fN; sleep infinity; env: – name: SSH_PRIVATE_KEY valueFrom: secretKeyRef: name: ssh-tunnel-secret key: private_key ports: – containerPort: 9000 hostPort: 9000 # 映射到节点 A 的 9000 端口 |
说明:需先创建 Secret 存储 SSH 私钥(kubectl create secret generic ssh-tunnel-secret –from-file=private_key=~/.ssh/k8s_ssh_tunnel),容器化隧道可利用 K8s 原生能力实现高可用。
五、常见问题排查(K8s 隧道踩坑指南)
1. 隧道建立失败:“ssh: connect to host 10.0.0.11 port 22: Connection timed out”
原因:
节点 A 与节点 B 间网络不通(如防火墙拦截 22 端口、节点间无路由);K8s 节点的 SSH 服务未监听集群内 IP(仅监听 127.0.0.1)。
解决:
在节点 A 测试节点 B 的 22 端口:telnet 10.0.0.11 22,若不通需开放节点防火墙 22 端口(firewall-cmd –add-port=22/tcp –permanent);检查节点 B 的 SSH 监听地址:netstat -tuln | grep 22,确保输出 0.0.0.0:22(监听所有网卡),若仅 127.0.0.1:22,需修改 /etc/ssh/sshd_config 中的 ListenAddress 0.0.0.0,重启 SSH 服务。
2. Pod 无法访问隧道端口:“curl: (7) Failed to connect to 10.0.0.10 port 9000: Connection refused”
原因:
隧道端口仅监听节点本地(127.0.0.1:9000),未监听集群内 IP;节点防火墙拒绝 Pod 网段访问隧道端口。
解决:
重建隧道时指定 0.0.0.0:9000(如 ssh -L 0.0.0.0:9000:…);配置 iptables 允许 Pod 网段访问:iptables -A INPUT -p tcp –dport 9000 -s 10.244.0.0/16 -j ACCEPT。
3. 隧道频繁断开:“autossh: ssh exited with status 255; restarting ssh”
原因:
节点间网络不稳定,SSH 心跳未配置;autossh 心跳端口被占用。
解决:
在 SSH 命令中添加心跳参数(-o ServerAliveInterval=30 -o ServerAliveCountMax=3),确保隧道保活;更换 autossh 心跳端口(如 -M 20001),避免端口冲突。
六、总结:SSH 隧道 vs K8s 原生方案的选择
|
方案类型 |
优势 |
劣势 |
适用场景 |
|
SSH 隧道 |
轻量、无侵入、快速部署、加密安全 |
不适合长期使用、需手动管理、依赖 SSH 服务 |
临时调试、私有服务访问、紧急跨节点访问 |
|
K8s Service(NodePort/ClusterIP) |
集群原生支持、长期稳定、自动负载均衡 |
需暴露服务、可能增加集群服务冗余 |
长期跨节点访问、服务共享 |
|
K8s Ingress |
外部访问统一入口、支持域名 / SSL |
配置复杂、需 Ingress Controller |
外部访问集群内服务 |
|
NetworkPolicy 修改 |
集群原生安全控制、长期生效 |
配置周期长、可能影响其他业务 |
长期跨节点访问权限调整 |
结论:SSH 隧道是 K8s 跨节点访问的 “临时解决方案”,适合调试、紧急验证、私有服务访问等场景,无需修改集群网络配置,几分钟内即可生效;若需长期稳定的跨节点访问,仍建议使用 K8s 原生方案(如 Service、Ingress)。
通过本文的实战步骤与优化技巧,你可轻松应对 K8s 集群内跨节点访问的各类临时需求,兼顾效率与安全性。若在操作中遇到其他问题,欢迎在评论区留言讨论!
附:K8s 内 SSH 隧道常用命令速查表
|
操作需求 |
命令示例 |
说明 |
|
节点间免密登录配置 |
ssh-keygen -t ed25519 -f ~/.ssh/k8s_tunnel; ssh-copy-id -i ~/.ssh/k8s_tunnel.pub root@10.0.0.11 |
生成密钥并分发到目标节点 |
|
本地转发隧道(节点 A→B) |
ssh -L 0.0.0.0:9000:127.0.0.1:8080 root@10.0.0.11 -i ~/.ssh/k8s_tunnel -fN |
节点 A 9000→节点 B 8080 |
|
远程转发隧道(节点 D→E) |
ssh -R 0.0.0.0:8080:test-svc:80 root@10.0.0.14 -i ~/.ssh/k8s_tunnel -fN |
节点 D Service→节点 E 8080 |
|
自动重连隧道(autossh) |
autossh -M 20000 -L 0.0.0.0:9000:127.0.0.1:8080 root@10.0.0.11 -i ~/.ssh/k8s_tunnel -fN |
心跳检测 + 自动重连 |
|
隧道进程管理 |
`ps aux |
grep “ssh -L 0.0.0.0:9000”; kill -9 进程号 ` |
|
端口监听验证 |
`netstat -tuln |
grep 9000或ss -tuln |
|
Pod 访问隧道测试 |
kubectl exec -it Pod名称 — curl http://节点IP:隧道端口 |
验证 Pod 能否通过隧道访问目标服务 |



