ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

VS Code Remote-SSH:本地编辑远程运行的高效开发指南

VS Code Remote-SSH:本地编辑远程运行的高效开发指南

1. 痛点与原理:为什么"本地编辑+远程运行"是更合理的开发方式

如果你也经历过这样的场景——手里是一台本地笔记本,真正要干的活却在一台没有显示器的Linux服务器上,改代码全靠终端里开着vim,上下翻页找函数,改完还要担心哪行缩进出了问题——那今天这套方案应该能帮到你:用SSH远程连接,把VS Code直接接到远端那台机器上。我第一次完整跑通这个流程之后,基本就回不到"本地改文件、scp传服务器"的老节奏里了。

先说我自己的一段糟心事。项目跑在一台Linux开发机上,代码全靠scp传上去。白天在本地改一个参数,存好,上传,再去服务器上跑一遍看结果。问题是你永远记不清本地和远端哪个版本是新的,尤其是两个窗口同时打开的时候,改错版本、覆盖回滚这种事,发生过太多次。后来也试过直接在服务器上vim,但用惯了编辑器的人再回到终端里写Python,最大的感受就是"变量在哪儿定义的"这种问题要来回翻文件。装补全插件、配LSP又等于额外维护一套开发环境。不能说不行,但面对一个真正的项目目录,效率确实不高。

SSH远程连接VS Code的思路,简单说就是:**编辑器界面留在本地,真正的文件系统、命令执行、语言服务、调试进程全都在远程机器上。**你本地的VS Code窗口相当于一个遥控面板,画什么界面、响应什么快捷键都发生在本地,而背后干活的是远端那台机器的CPU和内存。这项工作由VS Code官方提供的Remote - SSH扩展完成。发起连接之后,远程主机会自动安装一个轻量级的VS Code Server,位置一般在~/.vscode-server。本地界面和远端Server之间,走的就是一条SSH加密通道。

这和远程桌面完全是两码事。远程桌面是把屏幕画面压缩传回来,画质、延迟、带宽消耗都很难受。Remote - SSH传的是编辑指令和文件内容,绝大部分计算在远端完成,所以哪怕本地只是一台普通轻薄本,只要网络稳定,打开一个大项目的体验也和本地开发非常接近。代码索引、搜索、格式化、调试,都是在服务器上对着真实目录操作,然后再把结果返回到你的编辑器窗口里。

这套方案适合谁?适合那些代码必须跑在特定机器上的开发者:比如跑模型训练、连公司内部服务、操作测试环境、在树莓派上做嵌入式开发。前置条件也不复杂:目标机能通过SSH登录,能容纳VS Code Server运行,本地能访问到它的22端口。满足这三条,接下来就是配置的事了。

2. 第一次SSH连接:环境准备、密钥登录与VS Code接入

2.1 服务端和客户端的基本检查

开始之前先确认两头都齐了没有,免得配到一半卡住。

服务端这边,Linux机器需要装好OpenSSH Server。Debian/Ubuntu系可以用sudo apt install openssh-server,装完看一下服务状态:

systemctl status ssh ss -tlnp | grep 22

只要能看到sshd在监听22端口,服务端这块就算过了。如果你用的是云主机,还要确认安全组或防火墙有没有放行22端口,这个一会儿单独讲排查方法。

客户端这边,好消息是现在大部分系统都自带OpenSSH。Windows 10/11在“可选功能”里通常已经启用了OpenSSH客户端,macOS和Linux更是自带。不确定就打开终端跑一句ssh -V,能输出版本号就没问题。VS Code里只需要装一个扩展:在扩展面板搜索“Remote - SSH”,选官方发布的那一个即可。也可以直接装“Remote Development”扩展包,把Remote - SSH、Remote - Containers这些都一起装好,后面用Docker开发也用得上。

2.2 密钥登录:十分钟换掉天天输密码的烦恼

我第一次用Remote - SSH连接时还在用密码登录,能用,但每次连上都要输一遍密码,如果服务器要求强密码,那串字符能把人烦死。密钥登录是更推荐的方式,也是后续做安全加固的基础。

生成本地密钥对,终端里执行:

ssh-keygen -t ed25519 -C "laptop-key"

ed25519是现在比较推荐的算法,密钥短、生成快、安全性也够;如果目标Linux发行版太老,再用-t rsa -b 4096兜底。生成过程中会问保存路径和passphrase,路径直接回车用默认的~/.ssh/id_ed25519。passphrase建议设置一个,作用是私钥被人拿走也打不开,这个后面专门说。

然后把公钥传到服务器上:

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip

如果你的机器上没有ssh-copy-id,那就用一条手动的命令,把公钥追加到远端的authorized_keys里:

cat ~/.ssh/id_ed25519.pub | ssh user@server-ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

这里有个细节:远端~/.ssh目录必须是700权限,authorized_keys必须是600权限,否则很多sshd配置会直接拒绝使用这个密钥。传完之后验证一下ssh user@server-ip,不再提示输密码就说明通了。第一次连接时终端会问是否信任这台主机的fingerprint,输入yes回车,把它记到known_hosts里,这是防止中间人攻击的正常机制,别直接跳过。

2.3 VS Code添加SSH主机与首次连接

密钥验证通过后,回到VS Code。按Ctrl+Shift+P调出命令面板,输入“Remote-SSH: Connect to Host”,第一次可以手动输入user@server-ip。但强烈建议走配置文件这条路,后面多台机器会轻松很多。

同样在命令面板里输入“Remote-SSH: Open SSH Configuration File...”,选~/.ssh/config(Windows下通常是C:\Users\你的用户名\.ssh\config),把常用机器写进去:

Host mydev HostName 192.168.1.20 User devuser Port 22 IdentityFile ~/.ssh/id_ed25519

保存后再次执行“Remote-SSH: Connect to Host”,列表里就会出现mydev这个别名,选择它即可。首次连接时,VS Code会在远端自动下载并启动VS Code Server,这个过程会花一点时间。连接成功后左下角会出现一个“SSH: mydev”的绿色标记,这时你打开的每一个文件、每一个终端,都已经在远端机器上工作了。

有一类环境比较特殊:目标机器在内网,完全没有外网访问权限,VS Code Server自动下载会失败。这种时候需要离线安装,先看本地VS Code的Commit版本号,再到官方下载路径拿对应的server-linux-x64压缩包,scp到服务器上解压到~/.vscode-server/bin/对应版本号目录里,重新连接就能跳过下载。我第一次遇到这种情况时还以为是网络问题,折腾了半天才发现是版本匹配的问题,建议先查这个。

3. 连接不稳、慢、反复要密码:常见问题的完整排查链路

3.1 连接慢的先查这三处:DNS、GSSAPI、空闲保活

Remote - SSH体验好不好,很大程度取决于连接质量本身。我自己遇到过最典型的问题就是:第一次连接等十几秒才弹出来,“转圈”半天。排查慢连接,不要瞎猜,先用ssh -vvv user@host看详细握手日志,看卡在哪一步。常见的元凶有三个:

第一是反向DNS解析。sshd默认可能对连接来源IP做反查,有些内网DNS响应慢,就会白白等上好几秒。解决办法是在服务器端的/etc/ssh/sshd_config里加一行:

UseDNS no

改完记得sudo systemctl reload ssh。如果你改不了服务端配置,至少可以在客户端config里关掉GSSAPI认证,这也能省下不少尝试时间:

Host mydev HostName 192.168.1.20 User devuser GSSAPIAuthentication no ServerAliveInterval 60 ServerAliveCountMax 3

第二是GSSAPI认证尝试,尤其在公司内网环境,SSH客户端默认会尝试Kerberos这种认证方式,目标机器不响应或响应慢,连接就会卡住。第三是空闲断连,开发机任务跑一半,窗口放个十分钟,再敲键盘发现已经断了。加上ServerAliveInterval 60会让客户端每60秒发一个保活包,服务器同样可以配置ClientAliveInterval,这条尤其适合放在远程终端里跑长任务的人。

3.2 配置了密钥还反复要密码:权限与known_hosts的坑

明明按上面流程把公钥传上去了,连接时还是要密码,问题多半出在远端权限或者密钥路径上。先在本地终端手动执行:

ssh -v user@host

仔细看输出里有没有Offering public key片段,以及最后是Authenticated还是Permission denied。如果压根没发送密钥,检查config里的IdentityFile路径是否写对,或者本地有没有指定要用那把钥匙。如果发送了密钥但被拒绝,八成是远端~/.ssh或authorized_keys的权限有问题,把那两个权限修一下再试。

还有一种情况更恼火:之前一直能连,某天突然弹出一句:

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

这说明远端主机的host key变了,常见于服务器重装系统、虚拟机被重建、或者两台机器的IP被互换。解决办法是删掉本地known_hosts里对应的旧记录,让SSH重新记录:

ssh-keygen -R 192.168.1.20

千万不要图省事把整个known_hosts文件删掉,那样会丢掉很多其它机器的指纹记录,以后连别的机器也会重新弹确认。

3.3 跳板机和多主机:一份config打通所有开发机

在很多公司或高校环境里,开发机不允许直连,必须先登录一台跳板机,再从跳板机跳进去。用Remote - SSH连跳板场景,不需要每一步手动操作,OpenSSH的ProxyJump指令可以直接串起来。

在~/.ssh/config里这样写:

Host bastion HostName jump.example.com User jumpuser Host dev01 HostName 10.0.0.11 User devuser ProxyJump bastion IdentityFile ~/.ssh/id_ed25519

保存之后再连接dev01,VS Code会自动先连跳板机,再通过它接管到内网开发机。整个过程对编辑器窗口来说是无感的,体验跟直连差不多。OpenSSH版本比较老的机器不支持ProxyJump,退而求其次用ProxyCommand ssh -W %h:%p bastion效果也一样。

多台机器管理起来,建议每台都给一个能看懂的别名,比如mydev、gpu-box、prod-server,不要每次临时输入root@xxx这种长字符串。同一个用户和钥匙重复出现时,还可以用参数继承减少重复:

Host dev01 dev02 gpu-box User devuser IdentityFile ~/.ssh/id_ed25519

后面每个主机名下只写自己独有的HostName和端口。

3.4 端口不通怎么办:从ping到nc的逐层排查

连接最让人崩溃的是Connection timed out,这通常是网络层面的问题,端口根本没被访问到。排查思路从外向里,一层层收窄。

先用ping看主机能不能通,但要注意:很多环境禁ping,不通不代表端口不通。接着测TCP层:

nc -vz 192.168.1.20 22 telnet 192.168.1.20 22

能连上但黑屏,按Ctrl+]然后输quit退出。如果nc显示Connected,说明网络通、端口也在监听,问题多半在SSH认证;如果显示Connection refused,说明TCP到了但22端口没有服务监听,检查服务端sshd是否启动、是否监听的别的端口;如果一直Timed out,多半是被防火墙或安全组拦住了,去云控制台或者本机防火墙放行22端口。

公司内网如果要求先接入办公网络才能访问开发机,别忘了你现在的网络环境是否在允许范围内。之前有一次我在家里连不上公司机器,排查半天发现是网段不对,换个网络环境立刻通了,这类问题排查链路里排在很前面。

4. 连上之后把体验拉满:扩展、终端与端口转发

4.1 扩展分两端安装,别装错地方

连接上远端只是开始,真正让开发体验接近本地的是扩展配置。VS Code的扩展分两类:一类是纯UI扩展,比如主题、图标、快捷键优化、括号配对,这些只需要装本地;另一类是语言服务和调试器,比如Python、Pylance、ESLint、Prettier、Java、Go之类,它们需要直接在远端分析代码、跑进程,所以必须装在SSH那一边。

操作方法是:连接远端之后,打开扩展面板,左上角会有一个下拉选择框,切换到“SSH: mydev”这一侧,然后再点安装。装完之后扩展列表里会区分“本地已安装”和“远程已安装”。常见问题是:本地装了一大堆Python相关扩展,连上服务器却发现补全没用,就是因为本地的Python扩展压根够不到远端的解释器。

初次连接后,VS Code右下角有时会弹提示“发现此文件夹有推荐的扩展”,点一下就可以把这台机器需要的语言服务批量装到远端。选解释器也一样,连上远端后按Ctrl+Shift+P,执行“Python: Select Interpreter”,列表里出现的是服务器上的conda环境或者venv,选对之后整个编辑器都会用远端解释器做代码分析,跳转定义、查看类型、自动补全全部就会变得和本地开发一样顺滑。

4.2 远程终端的正确打开方式

按下Ctrl+\``打开的终端,默认就是远端Shell,这一点很容易被忽略。它和本地终端完全隔离,你在里面执行npm install、pip install、git log`,用的都是远端机器的资源和文件系统。

多开几个终端也方便:一个窗口跑训练日志,一个窗口敲git命令,还有一个临时改配置。右键终端标题可以重命名,方便区分各自用途。习惯用putty的人刚转过来最不适应的是粘贴快捷键,VS Code的终端里粘贴默认是Ctrl+Shift+V,Windows下有时候Ctrl+V也能用,但别太依赖它。

终端跑长任务时注意一件事:虽然VS Code窗口还开着,但如果本地网络掉线,远端那个终端进程可能还在跑。真要保证断连不丢进程,建议在远端用tmux包一层,而不是直接裸跑python train.py。这个习惯我在后面第5章还会详细说。

4.3 端口转发:把远程服务"搬到"本地浏览器

开发中最常见的幸福时刻,是远端服务跑起来了,本地浏览器直接就能访问。Remote - SSH里内置了端口转发面板,连上远端后切到“端口”标签页,点“添加端口”,输入比如8000,VS Code就会自动建一条SSH隧道,然后本地打开http://localhost:8000就能看到远端服务。

这条隧道走的是SSH通道,外部网络无法直接访问,这也是我推荐的方式——服务本身可以只监听在127.0.0.1上,不暴露到公网。对于TensorBoard、Jupyter这类工具特别实用:GPU服务器上启动tensorboard --logdir runs,默认监听6006端口,在端口面板里加上6006,本地浏览器就能实时看训练曲线。

除了在VS Code面板里转发,也可以在SSH config里写死,这样即使不打开VS Code,用命令行ssh mydev时也会自动转发:

Host mydev HostName 192.168.1.20 User devuser LocalForward 6006 localhost:6006

或者用命令行临时转发,ssh -L 6006:localhost:6006 user@host,效果一样。端口转发拼起来之后,整个开发流就完整了:编辑在本地VS Code,运行在远端服务器,调试信息、可视化页面全在本地浏览器,两边优势都能吃到。

5. 一次完整的远程开发复盘:改代码、跑任务、转发服务

5.1 一个常见场景:GPU服务器上的长任务调试

拿一个具体项目走一遍完整流程。假设A同学有一台GPU服务器,训练代码放在/opt/projects/my-train里,之前的工作方式是:本地改好文件,scp到服务器,再SSH进去执行nohup python train.py > train.log 2>&1 &,用一堆命令看日志找问题。

痛点很真实:改一个超参数就得重新传整个文件,偶尔还会本地远端两头版本不一致;想看一下loss曲线要手动开TensorBoard再转发端口;代码报错时只能靠print,想断点调试根本不可能。接到Remote - SSH之后,这套流程变成了下面这样。

5.2 从打开目录到跑通调试的完整路径

先通过“Remote-SSH: Connect to Host”连上别名mygpu,然后在菜单里执行File > Open Folder,地址栏直接输入/opt/projects/my-train,回车。VS Code会重新在那个目录下打开展工作区,左侧文件树看到的是服务器上的真实文件。

接着处理解释器:Ctrl+Shift+P执行“Python: Select Interpreter”,选择服务器上那个conda环境。安装Python和Pylance到SSH侧之后,改动代码时的补全、跳转、重命名全部生效。比如要在train.py里改学习率,光标移到learning_rate这个变量上,按F2重命名,项目内所有引用都会一起改,这在以前用终端vim是做不到的。

调试就按F5。第一次会提示生成launch.json,程序入口选train.py,然后在代码行左侧点打断点,运行到断点时,左侧面板能看到全部变量值,调用栈也能逐层看。这是一种完全不一样的开发感受:跑在服务器上的进程,所有调试能力都展现在本地编辑器的图形界面里。

长任务不想占着调试器的话,直接打开远程终端,用tmux new -s train起一个会话,在里面跑训练脚本。想退出窗口时先Ctrl+b然后按d分离会话,下次连上服务器执行tmux attach -t train就能回到那个还在跑的会话。断线、关笔记本都不影响训练进程。

5.3 断线重连、tmux兜底与传输大文件

再稳的网络也架不住突然断线。Remote - SSH重连很快,重新执行Connect to Host,VS Code会恢复上一次打开的工作区和扩展状态。未保存的代码改动有一部分能通过缓冲恢复,但保险起见,重要改动随手Ctrl+S,不要赌自动恢复。

需要传输大文件时,不要用编辑器自带的文件拖拽,几百MB的东西拖半天还容易失败,用rsync更稳:

rsync -avz --progress ./data user@host:/opt/projects/my-train/data

-a保留权限和时间戳,-z压缩传输,--progress让你能看到进度。第一次全量慢一点,后续增量就快很多。

VS Code Server偶尔也会出问题,比如本地VS Code大版本升级后远端server版本不匹配,连接一直卡在“Setting up SSH Host”。这种情况可以直接把远端~/.vscode-server下的旧server暂时备份或删掉,重新连接时VS Code会重新部署:

rm -rf ~/.vscode-server/bin

重新连接之后会自动拉取匹配版本。这个操作不影响你的项目文件,只影响编辑器服务本身,可以放心,但建议先备份再删。

6. 收尾之前的安全加固:钥匙管理与权限最小化

6.1 最该做好的三件事:禁密码、限权限、禁root直登

密钥登录验证通过后,建议顺手把密码登录关掉。编辑服务器端的/etc/ssh/sshd_config,确保以下几行是你要的状态:

PubkeyAuthentication yes PasswordAuthentication no PermitRootLogin no

然后执行sudo systemctl reload ssh。这三行的意义很直接:密码登录是暴力破解的头号目标,而root账号因为用户名固定,是最容易被盯上的。关掉root直登后,日常操作全部通过普通用户加sudo完成,命令审计也更加清晰。

改之前有个重要提醒:先单独开一个终端,确认密钥登录确实能用,再动手改配置。我见过有人改完PasswordAuthentication no才发现公钥压根没传上去,最后只能去机房恢复系统的惨案。改配置的时候最好保留一个已经登录的SSH会话不关,万一新配置有问题,还能靠旧会话补救。

同时检查远端~/.ssh权限。一条命令可以自查:

ls -ld ~/.ssh ~/.ssh/authorized_keys

正常情况下目录是700,文件是600。权限太宽松,sshd会认为不安全而拒绝使用。

6.2 私钥保护与ssh-agent的配合

公钥可以随便给人,私钥才是你的“钥匙本体”。私钥文件无论如何不要传到任何服务器、聊天工具或网盘里。如果之前生成时没设passphrase,可以补上:

ssh-keygen -p -f ~/.ssh/id_ed25519

设了passphrase之后,每次使用私钥都要输一次口令,有点麻烦但安全性大增。解决麻烦的办法是使用ssh-agent,把私钥暂时放进内存里的agent进程,之后连接不会再反复问口令:

eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519

VS Code的Remote - SSH也支持SSH Agent的挂载。不过要特别小心“Agent Forwarding”这个选项,也就是ssh -A或config里的ForwardAgent yes。它会把本地agent的认证能力转发给中间机器,如果跳板机被攻破,攻击者理论上可以拿着这个转发通道去登录你的其它机器。能用ProxyJump解决的场景尽量不要开Agent Forwarding,这是我在真实环境里踩过坑之后得出的结论。

6.3 公网主机的额外防线

如果你的开发机有公网IP,那它的22端口几乎每时每刻都在被扫描。不用慌,这是常态,但也要认真对待。最基础的就是前面说的禁密码、root不能直登,这一步做到了,大部分扫描攻击都是隔靴搔痒。

有条件的可以把安全组或防火墙的放行范围收紧,只允许你常用办公地点的出口IP访问22端口。这样即使密钥泄露,攻击者也要先绕过来源IP限制。也可以部署登录失败自动封禁类的工具,连续输错几次密码就把来源IP拉黑一段时间,能显著减少日志里的噪音。把SSH端口改成一个高位随机端口也能减少大量扫描,但这只是降低被盯上的概率,不是安全机制本身,真正的底线还是密钥和策略组合。

我自己现在拿到一台新开发机,第一件事就是按这个流程把Remote - SSH配好,然后检查密钥、权限和sshd配置。这套东西搭完之后,日常开发的舒适度提升是肉眼可见的,而且因为所有操作都走SSH加密通道,本地的代码和传输内容都有保护,心里也踏实很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进