_(°:з」∠)_/VSCode Remote-Containers 配置记录

Created Fri, 05 Nov 2021 00:00:00 +0000

更新 2024-02-29

此篇内容已经过时,现在可以直接通过Remote-SSH插件连接到远程服务器后打开包含.devcontainer的文件夹直接运行容器。除非有特殊的需求,不建议将docker socket映射至本地。

起因

实验室的服务器之前都是很老的 CentOS,最近更新到 Ubuntu 20.04 之后安装了最新版本的 docker,于是打算用它配置一下新的开发环境。不过因为实验室的服务器都处于内网,配置过程并不是很顺利,配好之后顺便把过程记录一下。

SSH 代理设置

具体的网络结构是这样的:家里的电脑 -> 作为跳板的公网服务器 -> 放在实验室的电脑 -> 实验室的服务器 -> 服务器上的 docker 容器。 因为实验室不让在服务器上做内网穿透,所以只能放了一台电脑在实验室再通过 frp22 端口映射到公网服务器上。 把这一层简化之后结构就是:家里的电脑 -> 跳板服务器 -> 实验室的服务器 -> 服务器上的 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.jsondocker-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.jsondocker-compose.extend.xml 中定义的 service 名称要和主目录下的 docker-compose.yml 相对应,比如这里都叫 jupyter_notebookvolumes 的配置有两种方式,这里使用了远程服务器上的绝对路径,另一种需要手动创建 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 一套,有点烦。 但是有一说一,微软做的东西还是很香的(