用 SSH 做隧道:K8s 集群内跨节点访问的进阶解决方案

内容分享2小时前发布
0 0 0

用 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 能否通过隧道访问目标服务

© 版权声明

相关文章

暂无评论

none
暂无评论...