VSCode 容器开发环境

作为从没有插件的 v0.x 年代开始使用 VSCode 的铁杆用户,所有的开发几乎全都使用 VSCode 进行。
由于 VSCode 本身只是一个编辑器,要实现各种功能需要大量的插件支持,想要达到 100 个插件并不是一个复杂的事情。
而实际上并非所有环境都需要所有的插件,让 VSCode 保持一个功能刚好够用的 “IDE” 是很有必要的。

自动启用/禁用插件

最简单的想法是,每个文件夹/工作区可以配置启用哪些插件。
目前 VSCode 存在该功能,但是对用户黑盒。通过右键插件可以控制在当前工作区禁用(不影响其他工作区),所有工作区级别的禁用/启用并没有一个对用户可见的文件存储,也就无法像 settings.jsonextensions.json 那样放置在文件夹内。
虽然对于同一台电脑,用户并没有手动维护的必要性,但是如果文件夹路径修改,可能就需要重新配置

在 Github 上,有人提出了 这个问题,希望可以在 extensions.json 中添加 disabled 字段来控制禁用哪些插件,但 VSCode 团队认为 extensions.json 存在副作用,由于其是存放在 .vscode 文件夹内,原则上是和代码绑定的,用户 A 使用哪些插件不应该影响用户 B。
(看上去很有道理,但是 settings.json 实际上已经存在这个问题了,而且用户可以通过配置 .gitignore 来规避)

最近,官方推出了 settings-profile,可以把上面提到的 “黑盒配置” 共享出来。但是这并没有完美解决问题,仍然不是一个比较优雅的解决方案(甚至可以认为完全没有解决)。

Remote Container

VSCode 最有名的功能是 Remote SSH,图形界面远程开发怎么想都很跨时代。和这个功能一起推出的是 Remote WSL 和 Remote Container,前者由于经常在 WSL 开发,所以用得很多,而后者则由于存在一定的学习门槛因此没用过。

首先简单描述下 Remote Container 的大致思路,用户通过预先配置开发环境容器(可以是一个镜像,也可以是一个 Dockerfile,亦或是 Docker Compose),VSCode 会自动拉取/构建对应的镜像文件,并按照配置启动容器,并将代码挂载入容器内进行开发

这里以 golang 开发为例,我们希望容器包含如下内容

  • go 二进制
  • go 的相关插件
  • go 的基本配置
  • 一些个人习惯的配置

为了方便在基础 Golang 镜像的基础上进行配置,使用 Dockerfile 来生成镜像。
镜像本身没有需要特别注意的地方,唯一可能会有问题的是用户权限,vscode 会自动绑定当前用户/用户组和容器内的用户/用户组,因此需要确保容器内拥有对应的配置

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

RUN groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME
USER ${USERNAME}

接下来就是控制如何启动容器。
对于端口映射,这里有两种不一样的实现,但是效果差别不大

  • 容器本身的端口映射:在容器创建前通过 -p 指定,容器只要运行就会自动映射
  • VSCode 转发:类似 ssh 的端口转发,只要会话连接存在,转发就可用。但是如果连接关闭(ssh 断开/vscode 关闭,那么转发就会停止,即使服务器/容器还在运行)

前者不支持修改,创建容器时配置了哪些,就只能是哪些,“几乎”不能修改(参考“如何为已经启动的容器添加映射端口”);而后者则支持动态转发。
前者的配置通过 "appPort": [ 3000, "8921:5000" ],后者则是 "forwardPorts": [3000, 3001]
如果需要指定映射的端口,可以通过 "forwardPorts": [3000, 3001] 来配置,也可以在容器启动后手动指定。

容器默认会挂载代码目录,但是如果有特殊的需要也可以通过 "mounts": ["source=${localWorkspaceFolder}/app-scripts,target=/usr/local/share/app-scripts,type=bind,consistency=cached"] 指定挂载其他目录。
为了保持开发环境的“干净”,不建议加这些东西

对于一些特殊功能,可以使用官方的 feature 来进行配置,比如 Docker in Docker、git 或是一些基础的开发环境。不过这部分完全可以写在 Dockerfile 里,可控性更高一点。

大部分常用的配置就这些,如果不希望容器名过于随机,可以使用 "runArgs": ["--name=dev-go-example"] 强行指定

在 vscode 关闭后,容器会自动停止,可以通过配置 "shutdownAction": "none" 禁用

下面给出一份 golang 的简单开发环境

{
    "name": "golang",
    "build": {
        "dockerfile": "Dockerfile"
    },
    "runArgs": [
        "--name=dev-go-example"
    ],
    "extensions": [
        "golang.go",
        "ms-azuretools.vscode-docker"
    ]
}
FROM golang:1.17.0

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

RUN groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME
USER ${USERNAME}

RUN go install github.com/cweill/gotests/gotests@latest
RUN go install github.com/fatih/gomodifytags@latest
RUN go install github.com/josharian/impl@latest
RUN go install github.com/haya14busa/goplay/cmd/goplay@latest
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN go install honnef.co/go/tools/cmd/staticcheck@latest
RUN go install golang.org/x/tools/gopls@latest

RUN curl -o ${HOME}/.bashrc \
    -k \
    "https://proxy.ohyee.cc/gist.githubusercontent.com/OhYee/87228bbce831b4b7027e3d6407e7b2f8/raw/.bashrc"   

Remote SSH + Remote Container

值得一提的是,到现在位置,实际上大部分开发并不在本地进行,服务器开发往往可以获得更高配置。而值得一提的是,VSCode 支持通过 remote ssh 使用 remote container,也即使用服务器上的 Docker 进行开发。

这一切对于大部分场景而言,完全和本机一致,用户完全无感。特殊情况下可能会存在下面的问题

  • 用户没有 docker 执行权限,需要确保用户在 docker 用户组,并且 docker 允许同组用户读写
    # 将当前用户添加至用户组
    usermod -a -G docker <当前用户>
    
    # 查看 docker socket 文件权限(应该是 srw-rw----)
    ls -l /var/run/docker.sock 
    
  • 服务器上有多个用户,而且大家都在用 remote-container。由于临时文件可能存在权限冲突,可以考虑把不同的用户临时文件放在不同的文件夹内,比如在 /etc/profile.d/ 添加一个 tmp.sh 文件并写入下下面的内容
    export TMPDIR=/tmp/${USER}
    mkdir -p ${TMPDIR}
    

    当然,如果只想修改自己用户的临时文件夹,也可以加入到 ~/.bashrc

就目前而言,微软和 Docker 的合作出的内容可谓是非常贴心的,从 WSL + Docker 到 Remote SSH + Docker,存在很多非常细节上的正向优化,完全不像是那个开发 Windows 的微软。

后记

为不同的项目配置不同的容器开发环境,一方面可以保证开发环境的干净。虽然 vscode 的插件存在启动触发判断,但是这取决于开发者的自觉,一个 python 插件完全可以申请 java 的触发。而即使同样是同一种语言,有时插件间也可能存在冲突,不同的项目需要选择不同的插件使用。

另一方面,一个可移植的开发环境有助于别人环境搭建。开源或是公司项目自不必说,保持开发环境一致至关重要。而即使是个人项目,时间长了环境部署也可能会存在问题,而且在同时多个项目开发时,由于不同项目环境可能存在冲突,有存在引入玄学 bug 的可能。

参考资料