1

FRP内网穿透

·31 分钟

实验室GPU服务器远程访问系统:从手工配置到自动化运维

FRP内网穿透 + SSH双因素认证 + Ansible批量部署的完整实践
作者:Zeqi Li
完成日期:2026年1月
服务规模:5台GPU服务器,10-20名用户


一、背景:实验室的远程访问难题

我们实验室有5台深度学习服务器,分散在不同的网段和位置:

服务器位置GPU配置用途
2080Ti办公室1×RTX 2080Ti轻量级训练 + 控制平面
5070Ti实验室1×RTX 5070TiSquid代理 + 监控中心
3090机房4×RTX 3090重型训练主力
4070Ti-001实验室1×RTX 4070Ti个人训练
4070Ti-002实验室1×RTX 4070Ti个人训练

核心矛盾

问题1:校园网物理隔离

  • WiFi网段(10.176.0.0/16)和有线网段(10.177.0.0/16)无法互通
  • 在宿舍连WiFi时无法访问实验室服务器

问题2:外网访问需求

  • 用户需要在家里、出差时提交作业、查看训练进度
  • 所有服务器都在内网,没有公网IP

问题3:用户体验要求

  • 希望访问方式尽可能简单,最好"像在校内一样用SSH"

为什么不用VPN?

在确定方案前,我们对比了主流的远程访问方案:

方案用户体验实施难度维护成本适用场景
VPN需要先连VPN再SSH高(证书管理、客户端配置)高(用户端故障支持)大型企业(>50用户)
FRP直接SSH,对用户透明低(一次配置)低(无客户端)中小团队(<20用户)
SSH隧道需要手动建立隧道中(用户需理解原理)个人使用

最终选择FRP的核心原因:对用户透明。

用户不需要知道什么是内网穿透,也不需要安装任何客户端,就像在校内一样用SSH连接。这对实验室场景至关重要——我们不想花时间教大家配置VPN客户端。


二、架构设计:控制平面集中化

2.1 最初的想法(❌ 放弃)

一开始的直觉是:每台服务器独立部署frpc客户端。

阿里云 frps
    ↓
  ┌─┴─┬─────┬─────┬─────┐
  ↓   ↓     ↓     ↓     ↓
frpc frpc  frpc  frpc  frpc
(5台独立配置)

问题:

  • 5台服务器 = 5份frpc配置文件(重复劳动)
  • IP地址变化需要逐台修改
  • FRP Token分散存储在5个地方(泄露风险高)
  • 配置不一致容易出错

2.2 集中控制平面(✅ 采用)

最终采用的架构:用一台服务器作为统一的FRP控制平面

┌─────────────────────────────────────────────┐
│          外网用户                            │
└───────────────┬─────────────────────────────┘
                ↓
┌───────────────────────────────────────────┐
│   阿里云 frps (8.162.3.156)               │
│   公网IP + FRP服务端                       │
└───────────────┬───────────────────────────┘
                ↓ FRP隧道
┌───────────────────────────────────────────┐
│   2080Ti (控制平面) 10.177.19.137         │
│   统一管理5个FRP代理:                     │
│   ├─ :12080 → 127.0.0.1:22022 (本机)      │
│   ├─ :15070 → 10.177.19.58:22022 (5070Ti) │
│   ├─ :13090 → 10.177.11.62:22022 (3090)   │
│   ├─ :24070 → 10.177.18.132:22022 (4070-2)│
│   └─ :14070 → 10.177.18.221:52223 (4070-1)│
└───────────────┬───────────────────────────┘
                ↓ SSH连接
        ┌───────┴───────┬───────┬───────┐
        ↓               ↓       ↓       ↓
    [5台目标服务器 - 业务平面]

核心设计理念:控制平面与业务平面分离

  • 控制平面(2080Ti): 专职管理FRP隧道,转发流量
  • 业务平面(5台服务器): 专注计算任务,无需关心FRP细节

2.3 为什么选2080Ti作为控制平面?

考虑因素2080Ti之前的kirara VM
稳定性✅ 机房服务器,24小时运行❌ 个人PC虚拟机,可能关机
网络✅ 直连校园有线网10.177网段⚠️ 在私人路由器内部,多一步路由转发配置
电源✅ 常开❌ 过年会断电

决策: 用稳定的机房服务器做控制平面,而不是个人PC虚拟机。

2.4 零权限设计

控制平面的关键安全设计:不存储任何目标服务器的认证凭据

2080Ti上只有frpc配置文件:

[[proxies]]
name = "3090-ssh"
type = "tcp"
localIP = "10.177.11.62"  # 只知道IP
localPort = 22022          # 只知道端口
remotePort = 13090

没有:

  • ❌ SSH私钥
  • ❌ 用户密码
  • ❌ 任何认证信息

安全价值:

  • 即使2080Ti被攻破,攻击者也无法直接访问其他服务器
  • 只能建立FRP隧道,仍需通过SSH双因素认证
  • 限制横向移动的可能性

三、安全设计:SSH双因素认证是核心

3.1 诚实的安全评估

技术文章经常会说"我们部署了六层防护",听起来很厉害。但实际上,不同防护层的贡献度是不均等的

防护层级              真实贡献度
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SSH双因素认证         ████████████████ 80%  ← 主力防线
UFW最小权限           ███ 15%               ← 访问控制  
fail2ban              █ 3%                  ← 辅助防护
FRP Token             █ 2%                  ← 准入门槛
iptables限速          边缘作用
systemd资源限制       边缘作用

核心认知:

  • 如果SSH双因素被破解,其他层也挡不了多久
  • 安全设计的重点是SSH双因素认证,最大的保障其实来源于秘钥,其他防护是辅助

3.2 SSH Match LocalPort:核心技术亮点 ⭐⭐⭐

这是整个系统最有价值的设计:同一个SSH服务,根据访问端口不同,应用不同的认证策略。

# /etc/ssh/sshd_config

Include /etc/ssh/sshd_config.d/*.conf

Port 22       # 校内直连端口
Port 22022    # 外网FRP访问端口

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes

# ... 其他配置 ...

# ========================================
# 差异化认证策略(配置文件末尾)
# ========================================

# 外网访问(22022端口):密钥 AND 密码
Match LocalPort 22022
    AuthenticationMethods publickey,password
    # 逗号 = AND关系,必须同时满足

# 校内访问(22端口):密钥 OR 密码
Match LocalPort 22
    AuthenticationMethods publickey password
    # 空格 = OR关系,满足其一即可

设计思想:

访问场景端口威胁模型认证策略理由
外网FRP22022面向全球,24小时扫描密钥 AND 密码双因素强制,即使密钥泄露也需要密码
校内直连22仅校园网可达密钥 OR 密码灵活便捷,有密钥可无密码登录

为什么这样设计?

外网必须双因素:

  • 公网端口暴露在互联网,面临全球自动化扫描攻击
  • 即使SSH密钥泄露(笔记本丢失、U盘泄露),攻击者仍需账户密码
  • 双因素认证的安全性是指数级提升

校内允许单因素:

  • 校内只有校园网能访问(10.177.0.0/16, 10.176.0.0/16),相对可信
  • 研究生日常使用频繁,如果每次都要双因素会影响效率
  • 配置了密钥的用户可以无密码登录(便利性)
  • 没配置密钥的用户用密码也能登录(灵活性)

用户无感知:

  • 用户不需要知道这个设计存在
  • SSH客户端会自动适应(有密钥就用密钥,需要密码就提示)
  • 从校内访问:ssh user@10.177.19.58(有密钥就免密码)
  • 从外网访问:ssh -p 15070 user@8.162.3.156(必须输入密码)

这是整个安全设计的核心。

3.3 UFW:最小权限原则

防火墙规则遵循"最小权限原则":只开放必要的访问,拒绝其他一切。

# 校内直连(22端口):允许校园网
ufw allow from 10.177.0.0/16 to any port 22 comment 'Campus Wired'
ufw allow from 10.176.0.0/16 to any port 22 comment 'Campus WiFi'

# 外网FRP(22022端口):只允许控制平面
ufw allow from 10.177.19.137 to any port 22022 comment 'FRP from 2080Ti'

纵深防御体现:

即使外网攻击者绕过了FRP,直接尝试访问22022端口:

  1. UFW会检查源IP
  2. 发现不是2080Ti(10.177.19.137)
  3. 拒绝连接

这是第二层防线。但如果攻击者能绕过FRP直接访问,说明已经攻破了某些环节,这时候UFW的作用有限。

UFW的实际价值:

  • ✅ 减少攻击面(关闭不必要的端口)
  • ✅ 防止配置错误(只允许特定IP访问22022)
  • ⚠️ 但不是主要防护手段

3.4 fail2ban:锦上添花,非雪中送炭

# /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 3
bantime = 3600
findtime = 600

fail2ban的作用:

场景fail2ban效果
低级暴力破解(字典攻击)✅ 有效阻止
降低日志噪音✅ 减少无意义的攻击尝试
有组织的攻击❌ 无效(攻击者可以很慢地尝试)
只有密码认证的系统❌ 只能延缓,无法根治

讨论:fail2ban是否忽略控制平面IP?

忽略的理由:

  • 所有外网流量都来自2080Ti,封禁它会影响所有用户
  • 有SSH Match规则的强制双因素认证作为主防护

结论: fail2ban其实这里感觉有点鸡肋,也不能说完全没有作用,但真的用处不大,等效于访问频率限制,哈哈。

3.5 FRP Token:准入门槛

# frps.toml (阿里云)
[auth]
method = "token"
token = "256位随机字符串"

FRP Token的作用:

  • 防止未授权的frpc接入frps(准入控制)
  • 类似于"大门钥匙",没有token无法建立隧道

FRP Token的局限:

  • 不是加密传输(FRP本身不加密,依赖SSH的加密)
  • 泄露后,攻击者只能建立FRP隧道,仍需通过SSH双因素认证
  • 真正的安全防护还是在SSH层

Token管理:

  • 集中存储在2080Ti的frpc.toml中
  • 服务器端存储在阿里云frps.toml中
  • 只有这两处存储,降低泄露风险

四、部署实践:踩过的坑

4.1 systemd socket陷阱(Ubuntu 24.04)

现象:

/etc/ssh/sshd_config中明确配置了Port 22022,重启SSH后,端口却没有监听。

$ sudo ss -tlnp | grep sshd
LISTEN 0 4096 [::]:22   # 只有22端口,22022不见了!

排查过程:

# 1. 检查配置语法(无错误)
$ sudo sshd -t

# 2. 检查SSH是如何启动的
$ sudo systemctl status ssh.service | grep "TriggeredBy"
TriggeredBy: ● ssh.socket  # ← 发现是socket activation启动

原因:

Ubuntu 24.04的SSH默认由systemd的ssh.socket管理,而不是传统的sshd直接启动。这种情况下,sshd_config中的Port配置会被socket的ListenStream覆盖。

解决方案:

创建systemd socket override配置:

sudo mkdir -p /etc/systemd/system/ssh.socket.d
sudo nano /etc/systemd/system/ssh.socket.d/override.conf
# /etc/systemd/system/ssh.socket.d/override.conf
[Socket]
ListenStream=
ListenStream=0.0.0.0:22
ListenStream=[::]:22
ListenStream=0.0.0.0:22022
ListenStream=[::]:22022

关键点:

  1. 第一行的空ListenStream=必须存在(清除默认值)
  2. 必须明确指定IPv4(0.0.0.0)和IPv6(::)
  3. 不指定IPv4会导致只监听IPv6,IPv4连接被拒绝

应用配置:

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl restart ssh.service

# 验证(应该看到4行)
sudo ss -tlnp | grep sshd
# 0.0.0.0:22
# 0.0.0.0:22022
# [::]:22
# [::]:22022

重要:并非所有服务器都需要这个配置!

# 检测方法
sudo systemctl status ssh.service | grep "TriggeredBy"

# 如果输出 "TriggeredBy: ● ssh.socket" → 需要override
# 如果无输出 → 使用原生SSH,只需修改sshd_config

在我们的5台服务器中:

  • 2080Ti, 5070Ti: 需要override(Ubuntu 24.04)
  • 3090, 4070Ti-002: 不需要(原生SSH)

教训:

  • 这不是"设计创新",纯粹是踩坑记录,但对遇到同样问题的人有参考价值
  • 系统版本升级可能改变默认行为,需要关注

4.2 Ansible密码配置的单引号陷阱

问题:

在Ansible inventory中配置sudo密码时,认证失败。

# ❌ 错误示例
server1  ansible_become_pass=Pass@2024!
fatal: [server1]: FAILED! => {"msg": "Incorrect sudo password"}

原因:

Ansible解析配置文件时,特殊字符会被解释:

  • ! - 某些shell中触发历史替换
  • $ - 变量开始符号
  • ` - 命令替换
  • @ - 在某些上下文有特殊含义

解决方案:用单引号

# ✅ 正确示例
server1  ansible_become_pass='Pass@2024!'

单引号 vs 双引号:

引号类型字符解释适用场景
单引号 '...'所有字符都是字面量密码、特殊字符
双引号 "..."解释 $, !, `, \需要变量替换
无引号依赖解析器,不安全简单字母数字

如果密码包含单引号怎么办?

# 密码是 it's-a-password
ansible_become_pass='it'\''s-a-password'
#                      ↑转义单引号的技巧

最佳实践:

  • 密码值使用单引号

4.3 UFW规则的grep匹配问题

问题:

Ansible playbook检查UFW规则是否存在时,grep匹配失败。

UFW输出格式:

$ sudo ufw status numbered
[3] 22022    ALLOW IN    10.177.19.137
    ↑端口           ↑IP

错误的grep:

shell: ufw status numbered | grep "10.177.19.137.*22022"
# 这是在找:IP...端口
# 但实际是:端口...IP
# 所以匹配不到!

正确的做法:

shell: ufw status numbered | grep "22022" | grep "10.177.19.137"
# 先匹配端口,再匹配IP

经验:

  • 先观察实际输出格式,再写匹配规则
  • 复杂匹配拆成多个简单grep(更清晰、更可靠)

4.4 幂等性设计:可重复运行的Playbook

Ansible最重要的特性是幂等性:重复运行应该安全,不产生副作用。

UFW规则的幂等性处理:

- name: 检查UFW规则是否存在
  shell: ufw status numbered | grep "22022" | grep "{{ control_plane_ip }}"
  register: rule_check
  ignore_errors: yes
  changed_when: false

- name: 只在规则不存在时添加
  ufw:
    rule: allow
    from_ip: "{{ control_plane_ip }}"
    to_port: 22022
    comment: 'SSH FRP from 2080Ti'
  when: rule_check.rc != 0  # ← 关键:只在不存在时执行

关键设计:

  • register: rule_check - 保存检查结果
  • when: rule_check.rc != 0 - 只在规则不存在时添加
  • changed_when: false - 检查操作不算"改变"

验证幂等性:

# 第一次运行
$ ansible-playbook update-frp-ufw.yml
TASK [添加UFW规则] ***
changed: [server1]  # ← 添加了规则

# 第二次运行
$ ansible-playbook update-frp-ufw.yml
TASK [检查UFW规则] ***
ok: [server1]
TASK [添加UFW规则] ***
skipping: [server1]  # ← 规则已存在,跳过

经验:

  • 自动化脚本必须考虑幂等性,"先检查,再操作"

五、自动化演进:从手工到批量

5.1 三个阶段的演进

Phase 1: 纯手工时代(2025.11)

# 每台服务器都要登录,逐一操作
ssh server1
sudo nano /etc/ssh/sshd_config  # 手工复制粘贴
sudo nano /etc/systemd/system/ssh.socket.d/override.conf
sudo ufw allow from ... to any port 22022
sudo systemctl daemon-reload
sudo systemctl restart ssh
exit

# 重复5次...

痛点:

  • ⏱️ 每台服务器30分钟,5台 = 2.5小时
  • 😵 重复劳动,容易出错
  • 🤔 配置不一致(有的服务器忘了某步)
  • 📝 需要记录哪台配过哪台没配

Phase 2: Ansible批量配置(2025.12)

# update-frp-ufw.yml
- hosts: gpu_servers
  become: yes
  vars:
    control_plane_ip: "10.177.19.137"
    frp_port: 22022
  
  tasks:
    - name: 添加UFW规则
      ufw:
        rule: allow
        from_ip: "{{ control_plane_ip }}"
        to_port: "{{ frp_port }}"
        comment: 'SSH FRP from 2080Ti'
# 一条命令更新所有服务器
ansible-playbook -i inventory.ini update-frp-ufw.yml

收益:

  • ⏱️ 5台服务器5分钟搞定
  • ✅ 配置一致性保证
  • 🔄 幂等性,可重复运行
  • 📊 清晰的执行报告

Phase 3: 用户体验优化(2026.01)

不仅自动化了管理员操作,还优化了用户端体验。

用户侧:Python脚本自动生成密钥

#!/usr/bin/env python3
# generate_ssh_key.py

import os
import subprocess

def main():
    print("=== SSH密钥生成工具 ===\n")
    
    username = input("用户名: ")
    hostname = input("服务器名(如 3090, 5070ti): ")
    
    key_name = f"{username}@{hostname}"
    key_path = os.path.expanduser(f"~/.ssh/{key_name}")
    
    # 生成密钥
    cmd = [
        'ssh-keygen',
        '-t', 'ed25519',
        '-f', key_path,
        '-C', key_name
    ]
    
    subprocess.run(cmd)
    
    # 显示公钥
    print("\n" + "="*60)
    print("✅ 密钥生成成功!")
    print("="*60)
    print("\n📋 公钥内容(发送给管理员):\n")
    
    with open(f"{key_path}.pub") as f:
        print(f.read())
    
    print("📁 私钥位置:", key_path)
    print("🔐 请妥善保管私钥文件!\n")

if __name__ == "__main__":
    main()

管理员侧:Ansible批量分发密钥

# add-user-key.yml
- name: 为用户添加SSH公钥
  hosts: "{{ target_host }}"
  become: yes
  
  tasks:
    - name: 确保.ssh目录存在
      file:
        path: "/home/{{ target_user }}/.ssh"
        state: directory
        owner: "{{ target_user }}"
        mode: '0700'
    
    - name: 添加公钥
      authorized_key:
        user: "{{ target_user }}"
        key: "{{ user_public_key }}"
        state: present
# 一键添加用户密钥到所有GPU服务器
ansible-playbook add-user-key.yml \
  -e "target_user=zhangsan" \
  -e "user_public_key='ssh-ed25519 AAAA...'" \
  -e "target_host=gpu_servers"

用户体验对比:

阶段用户操作管理员操作总耗时体验
Phase 1手工生成密钥
复制粘贴给管理员
逐台SSH登录
手工添加
~30min😫
Phase 2手工生成密钥
复制粘贴给管理员
Ansible批量添加~10min🙂
Phase 3运行脚本自动生成Ansible批量添加~5min😄

5.2 什么值得自动化,什么不值得

值得自动化的:

任务重复频率出错风险自动化收益
✅ UFW规则批量配置高(IP变化、新增服务器)高(容易遗漏)⭐⭐⭐⭐⭐
✅ 用户密钥批量分发高(新用户入组)中(权限错误)⭐⭐⭐⭐⭐
✅ 配置一致性检查中(定期巡检)高(配置漂移)⭐⭐⭐⭐
✅ IP变化后批量更新低但关键高(容易遗漏服务器)⭐⭐⭐⭐

不值得自动化的:

任务理由
❌ SSH基础配置每台只做一次,手工操作更灵活(踩坑时方便调试)
❌ 从零搭建整套系统步骤复杂、变数多,自动化脚本维护成本爆炸
❌ 过度抽象的配置模板为了"通用性"引入过多变量和条件,反而难以理解

务实的自动化思路:

"在一定基础上运维" - 接受手工配置一遍基础环境,然后在此基础上自动化重复性工作。

这不是偷懒,而是工程判断:

  • 手工配置一遍,可以理解每个步骤的作用
  • 找到重复性高、易出错的部分
  • 然后针对性自动化

反面教材:

有些"全自动部署脚本"为了处理各种边界情况,变得极其复杂:

# 过度设计的"全自动部署"(伪代码)
if is_ubuntu_24_04 && systemd_socket_enabled; then
  create_socket_override
elif is_ubuntu_22_04 && systemd_socket_enabled; then
  use_different_syntax
elif is_ubuntu_20_04; then
  use_sysvinit_style
elif is_debian; then
  check_debian_version
    if version > 11; then
      ...
    fi
elif is_centos; then
  ...
fi

# 500行后...

这种脚本的维护成本远超手工操作。好的自动化是简化工作,不是制造复杂性。

5.3 实例:IP变化的自动化处理

场景: 2080Ti的IP从10.177.19.137变成10.177.19.200

手工时代(Phase 1):

# 需要逐台操作,容易遗漏
ssh server1
sudo ufw delete [规则编号]
sudo ufw allow from 10.177.19.200 to any port 22022
exit

ssh server2
# ... 重复
# 5台服务器 × 5分钟 = 25分钟

自动化时代(Phase 3):

# 1. 修改inventory.ini的变量(10秒)
vim ansible-infra/group_vars/all.yml
# control_plane_ip: "10.177.19.200"

# 2. 运行playbook(1分钟)
ansible-playbook update-frp-ufw.yml

# 完成!所有5台服务器的UFW规则已更新

节省时间: 25分钟 → 1分钟

更重要的是: 不会遗漏任何一台服务器,配置一致性有保证。

5.4 未来改进方向

目前还有一处手工操作:修改frpc.toml中的IP地址。

当前做法:

# 手工编辑frpc.toml
vim /path/to/frpc.toml

[[proxies]]
localIP = "10.177.19.58"  # ← 手工改这里

未来改进: 用Jinja2模板化

# frpc.toml.j2 (模板)
{% for server in gpu_servers %}
[[proxies]]
name = "{{ server.name }}-ssh"
type = "tcp"
localIP = "{{ server.ansible_host }}"
localPort = 22022
remotePort = {{ server.external_port }}
{% endfor %}
# group_vars/gpu_servers.yml
gpu_servers:
  - name: 2080ti
    ansible_host: "{{ hostvars['2080ti']['ansible_host'] }}"
    external_port: 12080
  - name: 5070ti
    ansible_host: "{{ hostvars['5070ti']['ansible_host'] }}"
    external_port: 15070
  # ...

这样修改inventory.ini后,一条命令就能重新生成frpc.toml并重启服务。

目标: 真正的"改一处,全局生效"。


六、总结与反思

完成了什么

历时三个月(2025.11 - 2026.01),完成了5台GPU服务器的远程访问系统:

✅ 核心成果:

  • 5台服务器全部实现外网穿透,双因素认证
  • 用户体验透明(直接SSH,无需VPN客户端)
  • 运维成本低(Ansible批量管理)
  • 安全性可控(SSH Match LocalPort + UFW + fail2ban)
  • 易于扩展(新增服务器只需改配置+运行playbook)

📊 效率提升:

  • 添加用户:30分钟 → 5分钟
  • IP变化更新:25分钟 → 1分钟
  • 新增服务器:2小时 → 30分钟
  • 故障排查:有清晰的检查清单和脚本

核心发现与思考

1. 方案选择:场景适配比技术复杂度重要

选FRP而不是VPN,核心原因是"对用户透明"。对于10-20人的实验室,用户体验优先于技术炫技。

2. 安全设计:诚实评估每层的真实贡献

SSH双因素认证贡献80%,其他都是辅助。承认哪里是核心、哪里是辅助,比堆砌防护层数更有意义。

3. 自动化边界:在一定基础上运维

不追求"从零全自动",而是手工打好基础,然后自动化重复性工作。好的自动化简化工作,不制造复杂性。

4. 架构思想:控制/业务分离的价值

用2080Ti统一管理FRP,而不是每台服务器独立配置,体现了清晰的职责分离。这不仅简化配置,也降低了安全风险。

建议与启发

如果你也在做类似的FRP内网穿透系统:

  1. 优先考虑用户体验 - 技术方案要让用户"无感知",而不是展示技术复杂度
  2. 诚实评估技术含量 - 承认哪些是核心设计,哪些是常规操作,不夸大
  3. 准确评估安全机制 - SSH双因素认证是主力,其他是辅助
  4. 自动化要有边界 - 不是所有事情都值得自动化,找到重复性高、易出错的部分
  5. 渐进式改进 - 先手工做一遍理解流程,再自动化,不要一开始就追求完美
  6. 文档和代码同样重要 - 踩过的坑记录下来,未来的自己会感谢现在的你

附录:配置参考

A. frpc配置模板(控制平面)

# /path/to/frp/frpc.toml
# FRP客户端配置 - 2080Ti控制平面

serverAddr = "你的公网IP"
serverPort = 7000

[auth]
method = "token"
token = "你的256位随机Token"  # openssl rand -hex 32

# 本机SSH穿透
[[proxies]]
name = "2080ti-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22022
remotePort = 12080

# 其他服务器示例
[[proxies]]
name = "3090-ssh"
type = "tcp"
localIP = "10.177.11.62"
localPort = 22022
remotePort = 13090

# ... 其他服务器配置

B. sshd_config模板(目标服务器)

# /etc/ssh/sshd_config
# SSH服务器配置 - 双端口 + 差异化认证

Include /etc/ssh/sshd_config.d/*.conf

Port 22       # 校内直连
Port 22022    # 外网FRP

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

# 性能优化
Compression yes
ClientAliveInterval 60
UseDNS no

# ========================================
# 差异化认证策略(文件末尾)
# ========================================

# 外网:密钥 AND 密码
Match LocalPort 22022
    AuthenticationMethods publickey,password

# 校内:密钥 OR 密码
Match LocalPort 22
    AuthenticationMethods publickey password

C. systemd socket override(如需要)

# /etc/systemd/system/ssh.socket.d/override.conf
# 仅在使用socket activation时需要

[Socket]
ListenStream=
ListenStream=0.0.0.0:22
ListenStream=[::]:22
ListenStream=0.0.0.0:22022
ListenStream=[::]:22022

应用配置:

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl restart ssh.service

D. UFW配置脚本

#!/bin/bash
# ufw-setup.sh - UFW规则配置

CONTROL_PLANE_IP="10.177.19.137"

# 校内访问(22端口)
ufw allow from 10.177.0.0/16 to any port 22 comment 'Campus Wired'
ufw allow from 10.176.0.0/16 to any port 22 comment 'Campus WiFi'

# 外网FRP(22022端口)
ufw allow from $CONTROL_PLANE_IP to any port 22022 comment 'FRP from 2080Ti'

# 启用UFW
ufw --force enable

# 显示规则
ufw status numbered

E. Ansible inventory示例

# inventory.ini
# ⚠️ 包含密码,务必使用 chmod 600 保护

[gpu_servers]
2080ti     ansible_host=10.177.19.137  ansible_user=user1  ansible_become_pass='pass1'
5070ti     ansible_host=10.177.19.58   ansible_user=user2  ansible_become_pass='pass2'
3090       ansible_host=10.177.11.62   ansible_user=user3  ansible_become_pass='pass3'
4070ti-002 ansible_host=10.177.18.132  ansible_user=user4  ansible_become_pass='pass4'
4070ti-001 ansible_host=192.168.31.98  ansible_user=user5  ansible_become_pass='pass5'

[cloud]
astra      ansible_host=公网IP         ansible_user=root   ansible_become_pass='pass'

[all:vars]
ansible_port=22
ansible_ssh_private_key_file=~/.ssh/id_ed25519
control_plane_ip=10.177.19.137

F. 常用运维命令

# ========================================
# FRP相关
# ========================================

# 查看frpc状态
systemctl status frpc

# 查看frpc日志
journalctl -u frpc -n 50

# 实时日志
journalctl -u frpc -f

# 验证隧道建立
journalctl -u frpc | grep "start proxy success"

# 重启frpc
sudo systemctl restart frpc

# ========================================
# SSH相关
# ========================================

# 检查端口监听
ss -tlnp | grep sshd

# 测试配置语法
sshd -t

# 重启SSH
systemctl restart ssh.service

# 查看当前连接
who | grep pts

# ========================================
# UFW相关
# ========================================

# 查看规则
ufw status numbered

# 删除规则
ufw delete [编号]

# 重新加载
ufw reload

# ========================================
# Ansible相关
# ========================================

# 测试连通性
ansible all -i inventory.ini -m ping

# 批量执行命令
ansible gpu_servers -i inventory.ini -m shell -a "ufw status"

# 批量检查SSH端口
ansible gpu_servers -i inventory.ini -m shell -a "ss -tlnp | grep sshd"

# 运行playbook
ansible-playbook -i inventory.ini playbook.yml

# ========================================
# 故障排查
# ========================================

# 测试外网穿透
ssh -v -p 外网端口 用户@公网IP

# 检查防火墙
telnet 公网IP 端口

# 查看认证日志
tail -f /var/log/auth.log

项目完成时间: 2026年1月
文档版本: v3.0
作者: Zeqi Li

希望这篇实践记录对你有帮助!如有问题欢迎交流。