更新 2024-02-29
此篇内容已经过时,现在可以直接通过Remote-SSH
插件连接到远程服务器后打开包含.devcontainer
的文件夹直接运行容器。除非有特殊的需求,不建议将docker socket
映射至本地。
起因
实验室的服务器之前都是很老的 CentOS,最近更新到 Ubuntu 20.04 之后安装了最新版本的 docker,于是打算用它配置一下新的开发环境。不过因为实验室的服务器都处于内网,配置过程并不是很顺利,配好之后顺便把过程记录一下。
SSH 代理设置
具体的网络结构是这样的:家里的电脑 -> 作为跳板的公网服务器 -> 放在实验室的电脑 -> 实验室的服务器 -> 服务器上的 docker 容器。
因为实验室不让在服务器上做内网穿透,所以只能放了一台电脑在实验室再通过 frp
把 22
端口映射到公网服务器上。
把这一层简化之后结构就是:家里的电脑 -> 跳板服务器 -> 实验室的服务器 -> 服务器上的 docker 容器。
虽然结构有点扭曲,不过配合 ~/.ssh/config
(Windows 在 %USERPROFILE%/.ssh/config
)的设置可以通过 Remote-SSH 正常连接到服务器进行开发。
具体的配置可以参考这篇,里面用的是 ProxyCommand
,不过 OpenSSH 7.3 之后支持了新的 ProxyJump
,配置更加简单了。
简化后的 .ssh/config
大概是下面这个样子,IdentityFile
默认会使用 .ssh/id_rsa
,如果没有特别修改过名字的话可以缺省。
Host proxy
HostName <proxy-ip-or-url>
User <username>
Port 10022 # 默认22,因为是公网服务器最好改掉
Host lab_*
User <username>
ProxyJump proxy
Host lab_server1
HostName <server-ip-or-url>
Host lab_server2
HostName <server-ip-or-url>
...
这样就实现了对所有实验室内网服务器的 ssh 代理。如果在 VSCode 中安装了 Remote-SSH 插件的话,可以打开左侧的导航栏的远程资源管理器,在 SSH Targets
中选择希望连接的服务器并打开远程开发窗口。
配置过程
准备
根据官方文档,要使用远程容器首先得有 Docker CLI,我的平台是 Win11 所以直接安装了 docker desktop。此外,VSCode 上需要安装 Docker 插件和 Remote-Containers 插件。
SSH Agent
接下来,我们需要把自己的私钥添加到 SSH agent。
Windows 参考这篇,以管理员模式启动 PowerShell
,运行
Set-Service ssh-agent -StartupType Automatic
Start-Service ssh-agent
ssh-add .\.ssh\id_rsa
如果想在 WSL2 中使用 Remote-Containers 的功能的话,还需要进一步配置。
基本上在 rc 文件中添加 eval "$(ssh-agent -s)" > /dev/null 2>&1
就可以了。
如果是 fish shell
的话,在 .config/fish/config.fish
中添加 eval (ssh-agent -c) > /dev/null 2>&1
即可。
另外还有一种基于 keychain
的方式,可以参照这篇文章。
这样一来,在 WSL2 中也能愉快地使用 Remote-Containers 了。
本地转发
根据官方文档,连接远程服务器中的 docker 需要事先设定本地 VSCode Docker 插件的 docker.host
属性,相当于设置环境变量 DOCKER_HOST
。
基本写法是 "docker.host":"ssh://myuser@mymachine"
,不过这里的 mymachine
并不会依据 .ssh/config
中的设置进行代理,因此除非服务器有公网IP,按照官方文档配置是没有意义的。这时候就需要用到 ssh 的 LocalForward
功能了,具体做法是在 .ssh/config
中希望连接的服务器的配置下面添加一行:
Host lab_server1
HostName <server-ip-or-url>
LocalForward localhost:23333 /var/run/docker.sock
相当于把本地的 23333
端口变成远程服务器上的 docker.sock
,然后将 docker.host
设置为 tcp://localhost:23333
即可。
这个 issue 里有人提出使用 LocalForward /tmp/foo_bar_.sock /var/run/docker.sock
和 "docker.host": "unix:///tmp/foo_bar_.sock"
的组合,原理也差不多。
完成这一步后,通过 Remote-SSH 启动一个远程服务器窗口或者直接在终端内 ssh 到远程服务器,再打开一个本地 VSCode 窗口,这时在侧边栏点开 Docker 插件,你应该能成功看到远程服务器的镜像和容器了(如果有的话)。
注意在使用远程服务器的容器的过程中不要关闭通过 Remote-SSH 启动的远程服务器窗口(会切断
LocalForward
),否则会提示Failed to connect. Is Docker running?
。
Docker 镜像
现在,只要 Remote-SSH 到远程服务器的窗口常驻,或者一直有终端保持 ssh 到远程服务器,我们就可以在本地使用远程服务器的 docker。
这次打算我安装的是 JupyterLab 开发环境。在本地新建文件夹,名字随意,并新建 Dockerfile
, docker-compose.yml
以及一个工作目录 work
。两个文件的内容如下:
Dockerfile
FROM jupyter/datascience-notebook
# 切换到清华源加速
RUN pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip3 install ...
docker-compose.yml
version: '3'
services:
jupyter_notebook:
build: .
ports:
- '8888:8888'
environment:
- JUPYTER_ENABLE_LAB=yes
command: start-notebook.sh --NotebookApp.token=''
在这个阶段用 docker-compose up
生成镜像并启动容器后,打开 localhost:8888
就能在 JupyterLab 中进行开发,不过要在 VSCode 里直接进行开发还需要进一步配置。
Dev Container
最后一步是添加 Remote-Containers 的配置文件,详细内容可以参考官方文档。
这里只把步骤写下来。
首先在 docker-compose.yml
同一路径下添加文件夹 .devcontainer
,在其中写入两个文件 devcontainer.json
和 docker-compose.extend.yml
,内容分别为
devcontainer.json
{
"name": "Jupyter Notebook",
"dockerComposeFile": ["../docker-compose.yml", "docker-compose.extend.yml"],
"service": "jupyter_notebook",
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
"extensions": ["ms-python.python", "ms-python.vscode-pylance"]
}
docker-compose.extend.yml
version: '3'
services:
jupyter_notebook:
volumes:
- /absolute/path/on/remote/machine:/workspace:cached
command: /bin/sh -c "while sleep 1000; do :; done"
注意
devcontainer.json
和docker-compose.extend.xml
中定义的service
名称要和主目录下的docker-compose.yml
相对应,比如这里都叫jupyter_notebook
。volumes
的配置有两种方式,这里使用了远程服务器上的绝对路径,另一种需要手动创建volume
的方式可参考文档。
完成之后在本地用 VSCode 打开 .devcontainer
所在文件夹就会自动弹出对话框,
选择 Reopen in Container
就可以享用啦!
简化配置
上面的配置虽然可以正常使用,但是文件太多,而且被放得到处都是,强迫症表示不太能忍。
于是我又根据 devcontainer.json
的文档简化了一下上面的配置,把配置文件缩减到一个 devcontainer.json
和一个 Dockerfile
,并且都放在 .devcontainer
下一起管理。
{
"name": "Jupyter Notebook",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
},
"extensions": ["ms-python.python", "ms-python.vscode-pylance"],
"forwardPorts": [8888],
"remoteEnv": {"JUPYTER_ENABLE_LAB": "yes"},
"workspaceFolder": "/workspace",
"workspaceMount": "source=/absolute/path/on/remote/machine,target=/workspace,type=bind,consistency=cached",
"postStartCommand": "nohup start-notebook.sh --NotebookApp.token='' &",
"overrideCommand": true,
"runArgs": ["--name", "jupyter-notebook"],
}
Dockerfile
同上,不过要移动到 .devcontainer
里。
总结
其实关键在于 LocalForward
那一步,其他都跟着文档来就行。
不过 Windows 上面总是要配置两套,自己一套,WSL 一套,有点烦。
但是有一说一,微软做的东西还是很香的(