Vscode系列-远程容器

  • Vscode 远程容器使用及设置

  • 资料来源:

    https://code.visualstudio.com/docs/remote/create-dev-container#_use-docker-compose
    https://code.visualstudio.com/docs/remote/containers-advanced

  • 更新

    1
    2
    2021.06.13 初始
    2021.12.17 填坑

导语

这是把开发环境全部帮到容器的第一步,也是一直没填的坑.(21.06.13)今天也不是填坑.

(21.12.17) 整个自己的配置已经足够稳定了,遂填坑,估计需要几次才能填完.

以 Python 开发为例,下同.

前日谭

前提条件

  • 确保 docker 正常运行 (win10 21h2 和 win11 都支持了 wsl2 的 GPU 加速,能炼丹了)
  • 确保网络正常(编译 docker 镜像时,这一点非常重要)

文档

  • 微软官方文档 非常详细

参考这里 Remote development in Containers 提供了很多官方的例程.

  • python flask 例程, 建议先下个例程跑跑,就知道了.

开始

官方例程很详细了,这里仅根据我正在使用的项目配置,做简要说明.

项目根目录创建 devcontainer 文件夹

  • devcontainer.json: 总的配置文件
  • Dockerfile: 镜像

devcontainer.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"name": "my", //项目名称
"runArgs": [
"--name",
"my",
"--gpus", //炼丹需要这两行
"all",
],
"build": {
"dockerfile": "Dockerfile",
"context": "..",//指定构建的目录
"args": {
//构建时的参数
}
},
// Set *default* container specific settings.json values on container create.
"settings": {
//远程 vsc 配置
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
//远程vsc 安装的插件
],
// Use 'postCreateCommand' to run commands after the container is created.
// 容器创建后执行的命令,这里是安装 python 包
"postCreateCommand": "sudo pip3 install -r requirements.txt",
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
// 容器启动后运行的用户名
"remoteUser": "user"
}

Dockerfile

  • 这里因项目而异了
  • 如果是需要炼丹的项目,一般个人会选择 tensorflow 官方镜像再定制,最后包的体积额 (⊙﹏⊙) 完全控制不住..

高级容器设置

Advanced Container Configuration 目前用的着的.

基本都需要更改后重新 build.

设置环境变量

设置环境变量而无需更改镜像.

ps: 无论如何都要在 Terminal > Integrated: Inherit Env 设置内置终端继承环境变量,否则无法生效.

添加环境变量

devcontainer.json 中.

  • containerEnv: 应用到整个容器,可以引用容器环境变量.
  • remoteEnv: VSC和相关子进程(调试/任务/终端等等),可以引用容器和本地环境变量
1
2
3
4
5
6
7
8
9
"containerEnv": {
"MY_CONTAINER_VAR": "some-value-here",
"MY_CONTAINER_VAR2": "${localEnv:SOME_LOCAL_VAR}"
},
"remoteEnv": {
"PATH": "${containerEnv:PATH}:/some/other/path",
"MY_REMOTE_VARIABLE": "some-other-value-here",
"MY_REMOTE_VARIABLE2": "${localEnv:SOME_LOCAL_VAR}"
}

启动远程容器时从DockerFile或镜像启动,可以使用 containerEnvremoteEnv.

但是从 Docker Compose 启动,只能使用 remoteEnv.

1
2
3
4
5
6
version: '3'
services:
your-service-name-here:
environment:
- YOUR_ENV_VAR_NAME=your-value-goes-here
- ANOTHER_VAR=another-value

环境变量文件

环境变量太多? -> 转成文件吧.

创建 .devcontainer/devcontainer.env.

引用 devcontainer.env

  • devcontainer.json
1
"runArgs": ["--env-file",".devcontainer/devcontainer.env"]
  • docker-compose.yml
1
2
3
4
5
version: '3'
services:
your-service-name-here:
env_file: devcontainer.env
# ...

挂载本地文件

Docker Compose 启动就不说了,一样的配方.

Dockerfile or image 启动需要定义在 devcontainer.json.

1
2
3
4
"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE},target=/host-home-folder,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/app-data,target=/data,type=bind,consistency=cached"
]

可以是绝对路径/引用环境变量(%USERPROFILE% win/$HOME linux)

更改默认源代码目录

默认情况下远程容器会挂载当前工作区.如果有需要可以更改默认挂载的位置.还是在 devcontainer.json

1
2
"workspaceMount": "source=${localWorkspaceFolder}/sub-folder,target=/workspace,type=bind,consistency=delegated",
"workspaceFolder": "/workspace"

不仅可以更改默认挂载目录,还有挂载行为等等.

提高容器磁盘性能

Linux 下基本无问题,就是 Win 下 WSL 比较特别.

WSL2 实际上是个 Hyper-v 虚拟机,跨系统 IO 很烂.最好把源代码扔到 WSL2 内.

(21.12.17) 其实如果不是重 IO 的开发,还好.

添加非 root 用户

许多 Docker image 默认的用户就是 root 用户,但是这有一些权限问题.

  • mac 没用过,不表.
  • win 上用户权限和 linux 不同,没法直接映射.因此所有文件 win 可读可写.
  • linux 上就麻烦一点了,要和宿主机文件权限保持一致,至少要 uid 和 gid 保持一致.

为 vsc 指定用户

如果使用的容器和镜像提供了非 root 用户,但是默认不是非 root 运行.可以在配置文件 devcontainer.json.

1
"remoteUser": "user-name-goes-here"

linux 上如果引用了 dockerfile 或 image vsc 可以自行适配制定用户的 uid/gid.如果是 docker-compose 只能手动在 docker-compose 里手动指定了.

指定默认容器用户

可能有时需要容器都是非 root 用户运行.

dockerfile or image

1
"containerUser": "user-name-goes-here"
  • 在 linux 上,vsc 会自动更新 uid/gid.

docker-compose

1
user: user-name-or-UID-goes-here

创建非 root 用户

许多 dockerfile or image 没有提供 root 用户,只能自行创建了.这里以 debian/ubuntu 为例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ARG USERNAME=user-name-goes-here
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# Create the user
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
#
# [Optional] Add sudo support. Omit if you don't need to install software after connecting.
&& apt-get update \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME

# ********************************************************
# * Anything else you want to do like clean up goes here *
# ********************************************************

# [Optional] Set the default user. Omit if you want to keep the default as root.
USER $USERNAME

如果遇到错误,看看是不是已经有了非 root 用户.

当然无论怎样修改,都需要重新 rebild.

更改现有用户的 uid/gid

使用 remoteUser 时,在 linux 上 vsc 可以自动更新 uid/gid.

如果必须手动指定 uid/gid 可以在 dockerfile 中通过环境变量指定.

1
2
3
4
5
6
7
ARG USERNAME=user-name-goes-here
ARG USER_UID=1000
ARG USER_GID=$USER_UID

RUN groupmod --gid $USER_GID $USERNAME \
&& usermod --uid $USER_UID --gid $USER_GID $USERNAME \
&& chown -R $USER_UID:$USER_GID /home/$USERNAME

alpine 上还需要安装 shadow.

1
RUN apk add --no-cache shadow

连接多个容器

一般而言,一个远程容器只需要启动一个容器.但是如果你需要一次性连接多个容器,vsc 也能做到.

文件树

  • 所有容器都在 docker-compose 中定义.
  • 每个容器都带有 .devcontainer.json.
  • 为了代码管理同步,根目录的 .git 一般要挂载到每个容器.
  • 微软给的示例是 go 和 js.
1
2
3
4
5
6
7
8
9
📁 project-root
📁 .git
📁 container1-src
📄 .devcontainer.json
📄 hello.go
📁 container2-src
📄 .devcontainer.json
📄 hello.js
📄 docker-compose.yml

docker-compose 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3'
services:
container-1:
image: ubuntu:bionic
volumes:
# Mount the root folder that contains .git
- .:/workspace:cached
command: /bin/sh -c "while sleep 1000; do :; done"
links:
- container-2
# ...

container-2:
image: ubuntu:bionic
volumes:
# Mount the root folder that contains .git
- .:/workspace:cached
command: /bin/sh -c "while sleep 1000; do :; done"
# ...

container1-src/.devcontainer.json go 示例

1
2
3
4
5
6
7
8
9
{
"name": "Container 1",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "container-1",
"shutdownAction": "none",
"extensions": ["golang.go"],
// Open the sub-folder with the source code
"workspaceFolder": "/workspace/container1-src"
}

container2-src/.devcontainer.json node 示例

1
2
3
4
5
6
7
8
{
"name": "Container 2",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "container-2",
"shutdownAction": "none",
"extensions": ["dbaeumer.vscode-eslint"],
"workspaceFolder": "/workspace/container2-src"
}

shutdownAction 是可选项,设置后当关闭窗口后也不会退出容器.防止手贱..

连接多个容器:

  • 通过 **Remote-Containers: Open Folder in Container** 打开项目文件夹,选择容器 1.
  • 创建新窗口,再次 **Remote-Containers: Open Folder in Container** 打开项目文件夹,选择容器 2.

容器使用 git

参见 > https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container

vsc 能够实现 git 的自动转发,需要命令配置 ssh-add ,添加 github 的私钥.

1
ssh-add $HOME/.ssh/github_rsa

windows 下多个步骤,要确保 ssh-agent 正常开启

1
2
3
4
# 管理员身份开启
Set-Service ssh-agent -StartupType Automatic
Start-Service ssh-agent
Get-Service ssh-agent