Kubernetes 部署 SSH 跳板机:用 autossh 打通内网访问
背景
在 Kubernetes 集群中,很多时候服务没有对外暴露的网络入口,排查问题或日常运维都需要进入集群内部。通常的做法是:
- 在集群内部署一个 SSH 服务作为跳板机
- 通过 SSH 反向隧道将跳板机暴露到外部可访问的服务器上
- 开发者通过隧道连接跳板机,再访问集群内部服务
本文介绍如何在 K8s 中用一个 Pod 两个容器实现 SSH 跳板机 + autossh 自动隧道,实现稳定的内网穿透。
架构
用户 --> 跳板机服务器:6022 --> autossh 隧道 --> sshd 容器:22 --> K8s 内部网络
整个方案由两个容器组成,共享同一个 Pod 网络:
- sshd:
hermsi/alpine-sshd镜像,提供 SSH 登录服务,作为进入集群的入口 - autossh:
jnovack/autossh镜像,建立到外部服务器的反向隧道,保持连接稳定
两个容器通过 127.0.0.1:22 通信,无需额外的 Service。
前置准备
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
容器镜像
- sshd:
hermsi/alpine-sshd— 轻量级 Alpine SSH 服务,支持通过环境变量创建用户 - autossh:
jnovack/autossh— 封装了 autossh 的 Docker 镜像,通过环境变量配置隧道
完整 YAML
将以下内容保存为 ssh-tunnel.yaml:
1---
2apiVersion: v1
3kind: Namespace
4metadata:
5 name: devops-prd
6---
7apiVersion: v1
8kind: Secret
9metadata:
10 name: ssh-key
11 namespace: devops-prd
12type: Opaque
13data:
14 # SSH 私钥(base64 编码),用于 autossh 连接跳板机服务器
15 # 生成: base64 -w0 ~/.ssh/autossh_id_rsa
16 autossh_id_rsa: <BASE64_ENCODED_PRIVATE_KEY>
17 # SSH 公钥,用于 xu 用户的密钥认证登录
18 # 生成: base64 -w0 ~/.ssh/autossh_id_rsa.pub
19 autossh_id_rsa.pub: <BASE64_ENCODED_PUBLIC_KEY>
20---
21apiVersion: apps/v1
22kind: Deployment
23metadata:
24 name: ssh-tunnel
25 namespace: devops-prd
26 labels:
27 app: ssh-tunnel
28spec:
29 replicas: 1
30 selector:
31 matchLabels:
32 app: ssh-tunnel
33 template:
34 metadata:
35 labels:
36 app: ssh-tunnel
37 spec:
38 restartPolicy: Always
39 volumes:
40 - name: ssh-key
41 secret:
42 secretName: ssh-key
43 defaultMode: 0400
44 containers:
45 # ---- 容器1: SSH 跳板机 ----
46 - name: sshd
47 image: hermsi/alpine-sshd
48 imagePullPolicy: IfNotPresent
49 ports:
50 - containerPort: 22
51 env:
52 - name: ROOT_LOGIN_UNLOCKED
53 value: "false"
54 - name: SSH_USERS
55 value: "xu:1000:1000"
56 volumeMounts:
57 - name: ssh-key
58 subPath: autossh_id_rsa.pub
59 mountPath: /conf.d/authorized_keys/xu
60 readOnly: true
61
62 # ---- 容器2: autossh 隧道 ----
63 - name: autossh
64 image: jnovack/autossh
65 imagePullPolicy: IfNotPresent
66 env:
67 - name: SSH_REMOTE_USER
68 value: "user"
69 - name: SSH_REMOTE_HOST
70 value: "10.0.0.1"
71 - name: SSH_REMOTE_PORT
72 value: "22"
73 - name: SSH_MODE
74 value: "-R"
75 - name: SSH_TUNNEL_PORT
76 value: "6022"
77 - name: SSH_TARGET_HOST
78 value: "127.0.0.1"
79 - name: SSH_TARGET_PORT
80 value: "22"
81 - name: SSH_BIND_IP
82 value: "127.0.0.1"
83 - name: SSH_KEY_FILE
84 value: "/etc/ssh-key/autossh_id_rsa"
85 - name: SSH_OPTIONS
86 value: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
87 volumeMounts:
88 - name: ssh-key
89 mountPath: /etc/ssh-key
90 readOnly: true
部署步骤
1. 准备密钥
将 SSH 密钥对用 base64 编码后填入 Secret:
1# 私钥给 autossh 用
2base64 -w0 ~/.ssh/autossh_id_rsa
3
4# 公钥给 sshd 做用户认证
5base64 -w0 ~/.ssh/autossh_id_rsa.pub
2. 部署
1kubectl apply -f ssh-tunnel.yaml
3. 验证
部署后,通过跳板机服务器连接:
1ssh xu@10.0.0.1 -p 6022 -i ~/.ssh/autossh_id_rsa
登录成功后,可以在跳板机容器内访问集群内部服务:
1# 访问集群内的其他 Pod
2curl http://some-service.devops-prd.svc.cluster.local:8080
3
4# 或者通过 kubectl 进入其他容器
5kubectl exec -it some-pod -- /bin/sh
关键问题与解决方案
问题 1:SSH 密钥路径错误
jnovack/autossh 镜像默认查找密钥路径为 /id_rsa,需要通过 SSH_KEY_FILE 环境变量显式指定:
1- name: SSH_KEY_FILE
2 value: "/etc/ssh-key/autossh_id_rsa"
问题 2:主机密钥检查失败
首次连接时,远程服务器的主机密钥不在 known_hosts 中会导致连接失败。通过 SSH_OPTIONS 跳过检查:
1- name: SSH_OPTIONS
2 value: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
问题 3:公钥文件挂载为目录
hermsi/alpine-sshd 要求每个用户的公钥文件放在 /conf.d/authorized_keys/<用户名>。直接用 volume 挂载会变成目录,需要用 subPath 挂载为文件:
1volumeMounts:
2 - name: ssh-key
3 subPath: autossh_id_rsa.pub
4 mountPath: /conf.d/authorized_keys/xu
5 readOnly: true
为什么用双容器而不是单容器
将 sshd 和 autossh 放在两个容器中有以下好处:
| 维度 | 双容器 | 单容器 |
|---|---|---|
| 关注点分离 | sshd 和 autossh 各司其职 | 需要进程管理,脚本复杂 |
| 镜像维护 | 各自使用专注的镜像 | 需要自定义镜像或安装依赖 |
| 更新灵活 | 单独更新任一容器 | 整体更新,耦合度高 |
| 可观测性 | 各自有独立日志 | 日志混在一起 |
两个容器在同一个 Pod 中共享网络命名空间,127.0.0.1:22 就能访问 sshd,和单容器体验一致。
环境变量说明
autossh 环境变量
| 变量 | 说明 | 示例 |
|---|---|---|
SSH_REMOTE_USER |
跳板机服务器用户名 | user |
SSH_REMOTE_HOST |
跳板机服务器地址 | 10.0.0.1 |
SSH_MODE |
-R 为反向隧道 |
-R |
SSH_TUNNEL_PORT |
跳板机监听端口 | 6022 |
SSH_TARGET_HOST |
本地目标地址(sshd 容器) | 127.0.0.1 |
SSH_TARGET_PORT |
本地目标端口 | 22 |
SSH_KEY_FILE |
私钥路径 | /etc/ssh-key/autossh_id_rsa |
SSH_OPTIONS |
额外的 SSH 参数 | -o StrictHostKeyChecking=no |
sshd 环境变量
| 变量 | 说明 | 示例 |
|---|---|---|
ROOT_LOGIN_UNLOCKED |
是否允许 root 登录 | false |
SSH_USERS |
创建附加用户(用户名:UID:GID) | xu:1000:1000 |
安全建议
- 不要硬编码密钥:示例中的 Secret 为了方便展示使用了 base64,生产环境建议用 External Secrets Operator 或 Vault 管理
- 限制跳板机访问:在跳板机服务器上通过
iptables或安全组限制6022端口的来源 IP - 定期轮换密钥:autossh 的 SSH 密钥应定期更换
- 审计日志:启用 sshd 的详细日志,并接入日志收集系统
总结
通过一个 Pod 两个容器的方式,可以在 Kubernetes 集群中快速部署 SSH 跳板机 + autossh 反向隧道,实现稳定的内网穿透。这个方案的好处是:
- 一个 YAML 文件搞定,不需要额外配置
- 双容器分离关注点,维护简单
- 利用 Pod 共享网络,通信零开销
- autossh 自动重连,隧道稳定可靠
