背景

在内网环境中,经常需要将本地服务暴露到远程服务器上,常见场景如:

  • 本地开发环境需要被外部访问
  • 内网服务需要通过跳板机访问
  • Kubernetes 集群服务需要临时暴露到外部网络

使用 SSH 反向隧道(-R 参数)可以将本地端口映射到远程服务器端口,而 autossh 则能自动保持 SSH 连接稳定,在断线时自动重连。

本文将从 Docker 容器部署开始,逐步过渡到 Kubernetes 集群部署方案。

什么是 SSH 反向隧道

SSH 反向隧道(Remote Port Forwarding)允许你将本地机器的端口暴露到远程机器上。当远程服务器上的某个端口收到流量时,它会通过 SSH 连接将其转发到你的本地服务。

远程服务器:6001 → SSH隧道 → 本地服务:3067

方案一:Docker 容器部署

准备 SSH 密钥

首先创建专用的 SSH 密钥用于隧道认证:

1ssh-keygen -t rsa -b 4096 -f ~/.ssh/autossh_id_rsa -N ""

将公钥添加到远程服务器的 ~/.ssh/authorized_keys 中:

1ssh-copy-id -i ~/.ssh/autossh_id_rsa.pub user@10.0.0.1

Docker 启动命令

 1docker run -d \
 2  --name=ssh-tunnel \
 3  --restart=always \
 4  --network host \
 5  -v ~/.ssh/autossh_id_rsa:/app/autossh_id_rsa:ro \
 6  -v ~/.ssh/known_hosts:/app/known_hosts:ro \
 7  -e SSH_REMOTE_USER=user \
 8  -e SSH_REMOTE_HOST=10.0.0.1 \
 9  -e SSH_REMOTE_PORT=22 \
10  -e SSH_MODE=-R \
11  -e SSH_TUNNEL_PORT=6001 \
12  -e SSH_TARGET_HOST=127.0.0.1 \
13  -e SSH_TARGET_PORT=3067 \
14  -e SSH_BIND_IP=127.0.0.1 \
15  -e SSH_KEY_FILE=/app/autossh_id_rsa \
16  -e SSH_KNOWN_HOSTS_FILE=/app/known_hosts \
17  jnovack/autossh

环境变量说明

变量 说明 示例
SSH_REMOTE_USER 远程服务器用户名 user
SSH_REMOTE_HOST 远程服务器地址 10.0.0.1
SSH_REMOTE_PORT SSH 端口 22
SSH_MODE 隧道模式,-R 为反向隧道 -R
SSH_TUNNEL_PORT 远程服务器监听端口 6001
SSH_TARGET_HOST 本地目标地址 127.0.0.1
SSH_TARGET_PORT 本地目标端口 3067
SSH_BIND_IP 远程绑定地址 127.0.0.1
SSH_KEY_FILE SSH 私钥路径(容器内) /app/autossh_id_rsa
SSH_KNOWN_HOSTS_FILE known_hosts 路径(容器内) /app/known_hosts

映射关系:远程服务器 127.0.0.1:6001 ↔ 本地 127.0.0.1:3067

为什么使用 --network host

使用宿主机网络模式的原因:

  • SSH 隧道本质是网络层的连接,不需要 Docker 的网络隔离
  • 避免端口映射的额外开销
  • 确保 127.0.0.1 绑定正确指向宿主机回环地址

常见问题:密钥路径错误

jnovack/autossh 镜像默认查找密钥路径为 /id_rsa(容器根目录),如果挂载到其他路径,需要通过 SSH_KEY_FILE 环境变量显式指定。如果使用 SSH_EXTRA_ARGS="-i /app/autossh_id_rsa" 会报错,因为镜像的密钥检测逻辑先于 SSH 参数生效:

1[FATAL] No SSH Key file found

正确做法是使用 SSH_KEY_FILE=/app/autossh_id_rsa 环境变量指定路径。

验证隧道

1# 查看容器日志
2docker logs ssh-tunnel
3
4# 在远程服务器上测试连接
5ssh user@10.0.0.1
6curl http://127.0.0.1:6001

方案二:Kubernetes 集群部署

当需要将 SSH 隧道纳入 Kubernetes 集群管理时,可以使用 Deployment + Secret + ConfigMap 的方式部署。

架构说明

在 K8s 中部署 autossh 需要使用 hostNetwork: true 模式(对应 Docker 的 --network host),因为 SSH 隧道需要在宿主机网络层面工作。

完整 YAML

 1---
 2apiVersion: v1
 3kind: Namespace
 4metadata:
 5  name: ssh-tunnel
 6---
 7apiVersion: v1
 8kind: Secret
 9metadata:
10  name: ssh-key
11  namespace: ssh-tunnel
12type: Opaque
13data:
14  # 使用 base64 编码的 SSH 私钥
15  # 生成方式: base64 -w0 ~/.ssh/autossh_id_rsa
16  autossh_id_rsa: <BASE64_ENCODED_KEY>
17---
18apiVersion: v1
19kind: ConfigMap
20metadata:
21  name: ssh-known-hosts
22  namespace: ssh-tunnel
23data:
24  known_hosts: |
25    # 此处填写远程服务器的 SSH 公钥指纹
26    # 从本地 ~/.ssh/known_hosts 中提取对应条目
27---
28apiVersion: apps/v1
29kind: Deployment
30metadata:
31  name: ssh-tunnel
32  namespace: ssh-tunnel
33  labels:
34    app: ssh-tunnel
35spec:
36  replicas: 1
37  selector:
38    matchLabels:
39      app: ssh-tunnel
40  template:
41    metadata:
42      labels:
43        app: ssh-tunnel
44    spec:
45      hostNetwork: true
46      restartPolicy: Always
47      volumes:
48        - name: ssh-key
49          secret:
50            secretName: ssh-key
51            defaultMode: 0400
52        - name: known-hosts
53          configMap:
54            name: ssh-known-hosts
55            defaultMode: 0444
56      containers:
57        - name: autossh
58          image: jnovack/autossh
59          imagePullPolicy: IfNotPresent
60          env:
61            - name: SSH_REMOTE_USER
62              value: "user"
63            - name: SSH_REMOTE_HOST
64              value: "10.0.0.1"
65            - name: SSH_REMOTE_PORT
66              value: "22"
67            - name: SSH_MODE
68              value: "-R"
69            - name: SSH_TUNNEL_PORT
70              value: "6001"
71            - name: SSH_TARGET_HOST
72              value: "127.0.0.1"
73            - name: SSH_TARGET_PORT
74              value: "3067"
75            - name: SSH_BIND_IP
76              value: "127.0.0.1"
77            - name: SSH_KEY_FILE
78              value: "/etc/ssh-key/autossh_id_rsa"
79            - name: SSH_KNOWN_HOSTS_FILE
80              value: "/etc/ssh-known-hosts/known_hosts"
81          volumeMounts:
82            - name: ssh-key
83              mountPath: /etc/ssh-key
84              readOnly: true
85            - name: known-hosts
86              mountPath: /etc/ssh-known-hosts
87              readOnly: true

K8s 部署步骤

 1# 1. 生成 SSH 私钥 Secret
 2kubectl create secret generic ssh-key \
 3  --namespace=ssh-tunnel \
 4  --from-file=autossh_id_rsa=~/.ssh/autossh_id_rsa \
 5  --dry-run=client -o yaml > ssh-key-secret.yaml
 6
 7# 2. 生成 known_hosts ConfigMap
 8kubectl create configmap ssh-known-hosts \
 9  --namespace=ssh-tunnel \
10  --from-file=known_hosts=~/.ssh/known_hosts \
11  --dry-run=client -o yaml > ssh-known-hosts-cm.yaml
12
13# 3. 部署
14kubectl apply -f ssh-tunnel.yaml

Kuboard 一键部署

在 Kuboard 中,可以直接将完整 YAML 粘贴到"导入 YAML"中一键部署。需要注意:

  1. 将 SSH 私钥通过 base64 -w0 ~/.ssh/autossh_id_rsa 编码后填入 Secret 的 data.autossh_id_rsa 字段
  2. 从本地 ~/.ssh/known_hosts 中提取远程服务器的公钥指纹,填入 ConfigMap
  3. 确保目标节点能直接访问远程服务器的 SSH 端口(22)

K8s 部署注意事项

  • hostNetwork 要求:Pod 使用宿主机网络,确保能正常发起 SSH 连接
  • 节点选择:建议使用 nodeSelector 将 Pod 调度到特定节点,方便网络策略管理
  • 密钥权限:Secret 挂载时设置 defaultMode: 0400,确保私钥文件权限正确(SSH 要求私钥权限为 600 或 400)
  • 高可用:反向隧道是点对点连接,建议 replicas: 1,避免多个 Pod 同时绑定同一端口导致冲突

Docker vs K8s 部署对比

维度 Docker Kubernetes
部署复杂度 低,一条命令 中,需要 YAML + 密钥管理
密钥管理 宿主机直接挂载 Secret 资源管理
自愈能力 Docker restart policy K8s 自动重启 + 健康检查
网络模式 --network host hostNetwork: true
适用场景 单机、临时隧道 集群化、需要统一管理
日志管理 docker logs kubectl logs + 日志聚合

总结

SSH 反向隧道是解决内网服务暴露问题的经典方案。通过 autossh 可以保证隧道连接的稳定性,而 Docker 和 Kubernetes 则提供了不同的部署选择:

  • Docker 适合快速启动和临时隧道场景,一条命令即可完成部署
  • Kubernetes 适合生产环境,通过声明式配置和密钥管理提升安全性和可维护性

选择哪种方式取决于你的基础设施和管理需求。

参考