VSCode云端开发环境搭建 (Remote-Container

2019-12-07  本文已影响0人  啤酒沫

为什么要使用云端开发环境

2019年5月份,微软发布一组VSCode插件“Remote-Development”。它可以让开发者在VSCode中直接访问远程的目录进行开发工作。这样我们的代码和开发环境就可以和终端电脑分离了 ,并且可以随意在远端搭建多个不同的开发环境随时切换。 听起来是不是有点小激动呢?

其实远程开发的模式并不新鲜。很久以前我就通过FTP或SFTP链接,直接在服务器上进行开发。但这种方式成本比较高,需要一台远程服务器支持,而且多人同时使用的时候可能产生版本依赖的冲突。这几年容器技术和应用场景犹如获得神速力的加持般飞速发展。结合容器技术可以有效的将不同的开发环境进行区隔,并且以容器为单位,进行复制、迁移变得前所未有的简单。

所以使用云端开发环境有以下几个优点:

先介绍一下 Remote-Developement 插件组

微软发布了3个远程开发插件,分别是 “Remote-SSH”、“Remote-Containers”、“Remote-WSL”,并将它们放入了插件包 “Remote-Developement” 中一同发布。

今天我们着重介绍如何使用“Remote-Containers”,开始吧。

准备Docker环境

环境说明

我的桌面系统是MacOS,和Windows的差异,小伙伴们可以自行脑补。

在安装Docker的时候,我们并不需要安装官网提供的标准安装包,因为那包括了Docker EngineDocker Client

所以我们需要安装的是docker-toolbox。MacOS可以通过brew search docker-toolbox找到,其他系统可以通过github下载 https://github.com/docker/toolbox/releases

$ brew cask install docker-toolbox     

docker-toolbox包含以下几部分内容

  • docker-cli : 客户端命令行,目前的版本是19.03.1
  • docker-machine : 可以在本机启动用于Docker Engine虚拟机并管理他们
  • docker-compose : docker提供的编排工具,支持compose文件,这个并不常用。
  • Kitematic : Docker的客户端GUI,官方已经废弃了。
  • Boot2Docker ISO : 用于创建Docker Engine虚拟机的镜像。由于包中的这个版本并不是最新的,所以创建虚拟机的时候可能会需要重新下载。
  • VirtualBox : 虚拟机

创建Docker Machine

$ docker-machine create --driver virtualbox \
    --virtualbox-cpu-count 2 \
    --virtualbox-memory 2048 \
    default
Running pre-create checks...
Creating machine...
(default) Copying ${HOME}/.docker/machine/cache/boot2docker.iso to ${HOME}/.docker/machine/machines/default/boot2docker.iso...
(default) Creating VirtualBox VM...
(default) Creating SSH key...
(default) Starting the VM...
(default) Check network to re-create if needed...
(default) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env default

创建成功后,连接到Machine

$ eval $(docker-machine env default)
$ docker version

Client: Docker Engine - Community
 Version:           19.03.1
 API version:       1.40
 Go version:        go1.12.5
 Git commit:        74b1e89
 Built:             Thu Jul 25 21:18:17 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea838
  Built:            Wed Nov 13 07:28:45 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
$
$ # OK,连接成功!

从官方Sample开始

先取得官方的Sample项目。在github上查找 vscode-remote-try 我们可以找到一堆项目,都是微软官方提供的不同语言环境的Sample。这里我们用 vscode-remote-try-python 作为例子。

$ git clone https://github.com/microsoft/vscode-remote-try-python.git
$ cd vscode-remote-try-python/
$ 
$ # 打开项目目录
$ /Applications/Visual\ Studio\ Code.app/Contents/MacOS/Electron ./
$ # windows 使用命令 "code .\"

Sample的目录结构

[workspace]
|- .devcontainer :              开发环境配置目录
|  |- devcontainer.json :       环境配置文件
|  |- Dockerfile :              环境的Docker镜像生成文件
|- .vscode :                    vscode使用的配置文件(容器端使用)
|  |- launch.json :             debuger 配置文件(容器端使用)
|- static :                     Sample项目的静态页面目录
|  |- index.html :              Sample项目首页
|- .gitattributes :             git 文件属性定义
|- .gitignore :                 git 忽略文件
|- app.py :                     flask项目入口
|- LICENSE
|- README.md
|- requirements.txt :           项目的环境所需要的python模块,通过pip安装

下面我们着重介绍 devcontainer.jsonDockerfile两个文件

.devcontainer/devcontainer.json

这个文件是用于启动开发容器的配置。点击查看官方文档。下面我们介绍一下配置属性。

属性 类型 描述
通用参数
name 字符串 容器显示名称
extensions 数组 需要安装到容器中的vscode扩展。 缺省值"[]"
settings json对象 添加到容器中的vscode settings.json
postCreateCommand 字符串,数组 容器创建后第一次启动时执行的一组命令。命令执行目录是容器中workspaceFolder指定的目录。多条命令之间使用&&进行连接。 缺省值 none
devPort 整数 允许给vscode server指定一个端口。缺省为一个随机可用端口。
Dockerfile或Image
image 字符串 必填 使用已存在镜像时必填。 vscode会使用镜像名称来创建开发容器。
dockerFile 字符串 必填 使用Dockerfile时必填。 指定一个用来生成Docker镜像的Dockerfile文件。路径相对于devcontainer.json文件。 可以在这个地址找到各种Dockerfile样例。
context 字符串 指定运行docker build命令时的上下文目录。 路径是基于devcontainer.json文件的相对路径。 缺省值"."
appPort 整数,字符串,数组 容器运行时发布到Host的端口。多个端口用数组表示。 缺省值"[]"
workspaceMount 字符串 覆盖缺省的mount参数。语法参见Docker文档Docker CLI --mount flag。 可以使用${localWorkspaceFolder}引用本地的工作区目录,或使用${env:VARNAMEHERE}应用环境变量
workspaceFolder 字符串 设置vscode连接到容器后缺省的工作目录。 通常结合workspaceMount属性使用。
runArgs 数组 运行容器时的命令行参数Docker CLI arguments。 缺省值"[]"。 可以使用${localWorkspaceFolder}引用本地的工作区目录,或使用${env:VARNAMEHERE}应用环境变量
overrideCommand 布尔 告诉容器在启动时是否执行命令 /bin/sh -c "while sleep 1000; do :; done",用以覆盖缺省的启动执行命令。 缺省值"true"
shutdownAction 枚举: none,stopContainer 指定在vscode断开连接或者关闭时,是否停止容器。 缺省值"stopContainer"
Docker Compose
dockerComposeFile 字符串,数组 必填 指定一个Docker Compose文件,路径相对于devcontainer.json文件。 当需要扩展Docker Compose配置时,可以使用数组。数组的顺序和重要,后面的文件内容会覆盖之前的设置。 缺省的.env文件会在项目的根路径下寻找,但可以通过Docker Compose文件中的env_file指定另外的路径。
service 字符串 必填 指定启动后vscode连接哪个service。
runServices 数组 指定Docker Compose文件中的哪些services需要启动。同时在断开连接后,这些services将会根据shutdownAction的设置决定是否关闭。 缺省值为所有的services。
workspaceFolder 字符串 连接到容器后进入的工作目录。缺省值"/"
shutdownAction 枚举: none,stopCompose 指定在vscode断开连接或者关闭时,是否停止容器。 缺省值"stopCompose"

那么我们看看Sample中的.devcontainer文件内容。(为了方便显示,我过滤的原文件中的注释)

{
    "name": "Python Sample",
    "dockerFile": "Dockerfile",

    "appPort": [9000],

    "runArgs": ["-u", "vscode"],

    "settings": { 
        "terminal.integrated.shell.linux": "/bin/bash",
        "python.pythonPath": "/usr/local/bin/python",
        "python.linting.pylintEnabled": true,
        "python.linting.pylintPath": "/usr/local/bin/pylint",
        "python.linting.enabled": true
    },

    "postCreateCommand": "sudo pip install -r requirements.txt",

    "extensions": [
        "ms-python.python"
    ]
}

根据这个配置,我们可以知道。

上述内容基本上是在说,容器启动时需要做的事情。
这里需要强调一点: Remote-Containers是通过"镜像"来管理环境的 。容器只是运行时环境,容器是可以随时删除、重建,并同时要保证环境是持续可用的。
所以,容器运行时的配置都放到了devcontainer.josn文件中。

下面我们来看看镜像的生成 - Dockerfile

.devcontainer/Dockfile

关于Dockerfile的格式,参见官方文档
看看Sample中的内容。


# 基于官方的"python:3"镜像
FROM python:3

# 切换到非交互模式避免警告
ENV DEBIAN_FRONTEND=noninteractive

# 指定创建的非root用户
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# 首先更新系统
RUN apt-get update \
    # 安装vscode server需要的基础软件
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
    && apt-get -y install git procps lsb-release \
    #
    # 安装 pylint
    && pip --disable-pip-version-check --no-cache-dir install pylint \
    #
    # 创建一个非root用户 (为啥需要这个,请看 - https://aka.ms/vscode-remote/containers/non-root-user)
    && groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
    # [可选] 添加sudo命令
    && apt-get install -y sudo \
    # 将新创建的非root用户添加到sudoers
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME \
    #
    # 打扫卫生
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=

依靠上述两个文件,vscode会创建指定的镜像,和容器。并连接容器进入工作区。
但由于在墙内,我们安装的速度会比较慢,所以我们需要随上述文件做些修改。提高下载速度。

更改安装源,提高下载速度

修改Dockerfile

...
# 将debian的更新源改为aliyun的镜像
RUN sed -i -e 's/\w\+\.debian\.org/mirrors.aliyun.com/g' /etc/apt/sources.list \
    # 首先更新系统
    && apt-get update \
...
    # 安装 pylint (使用aliyun镜像)
    && pip --disable-pip-version-check --no-cache-dir install pylint -i https://mirrors.aliyun.com/pypi/simple/ \

...

修改devcontainer.json

{
  "postCreateCommand": "sudo pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/",
}

现在配置都已经准备好了,下面我们需要连接安装好的Docker Engine

现在需要通过vscode连接Docker Engine

vscode连接Docker Engine是通过settings进行配置的。我们当然可以直接修改全局的settings.json但这样会污染全局参数。我建议通过workspace级别的settings进行设置。
我们要创建一个新的workspace
在Sample项目的目录下,新建一个文件,命名为python.code-workspace,内容如下:

{
    "folders":[
        {
            "name":"Python Sample",
            "path":"."
        }
    ],
    "settings": {
        "docker.host": "${DOCKER_HOST}",
        "docker.tlsVerify": "1",
        "docker.certPath": "${DOCKER_CERT_PATH}"
    }
}

执行"docker-machine env default"会输出Docker Machine的连接地址和证书目录。

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://xxx.xxx.xxx.xxx:2376"
export DOCKER_CERT_PATH="/xxx..."
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell: 
# eval $(docker-machine env default)

使用DOCKER_HOST,DOCKER_CERT_PATH对应的值填入上面的json文件。

然后打开Remote-Containers扩展,选择Reopen in Container

vscode-remote-containers

图解Remote-Containers的部署过程

vscode连接容器的过程中都做了什么工作呢?我们可以分析一下连接过程输出的日志信息。
总结后的内容参见下图。

Remote-Containers的部署过程
1. 连接到Docker Engine:

vscode通过在python.code-workspace文件中的三项配置连接Docker Engine。

由于我们是使用Docker Machine创建的服务器和连接,证书的生成和配置docker-machine命令已经帮我们做好了。

2. 构建镜像

再强调一次 Remote-Containers是通过"镜像"来管理环境的

3. 创建并启动容器
4. 在容器中安装vscode扩展插件

vscode在连接到容器的环境后,会根据不同的容器加载不同的插件。这些插件是安装在容器中的,不会污染本机的插件环境。
安装哪些插件由devcontianer.json中的"extensions"属性指定。
插件会安装到容器中的${HOME}/.vscode-server/extensions目录。

5. 安装 "VS Code Server"

"VS Code Server"是做什么用的?官网有张图可以说明。


Architecture summary

按照我的理解,"VS Code Server"是用来管理容器中的插件并使其可以在本地的vscode中使用。

开发过程

重新构建镜像

由于样例中使用的是pythondebian镜像。个人感觉镜像比较大,安装慢。我比较喜欢Alpine镜像。python本身有3.7.5-alpine3.10镜像,但我更喜欢直接使用alpine:3.10,因为装完python环境后比官方的Python或小那么一点点。

添加alpine.Dockerfile

内容如下

FROM alpine:3.10

ENV DEBIAN_FRONTEND=noninteractive

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

RUN sed -i -e 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/' /etc/apk/repositories\
    && apk --update add --no-cache \
        libuuid \
        gcc \
        libc-dev \
        linux-headers \
        make  \
        automake   \
        g++  \
        python3-dev \
        sudo \
        bash \
        git \
        curl \
        python3 \
    && curl https://bootstrap.pypa.io/get-pip.py| python3 - \
    && pip --disable-pip-version-check --no-cache-dir install pylint -i https://mirrors.aliyun.com/pypi/simple/ \
    && addgroup -g $USER_GID $USERNAME \
    && adduser -s /bin/bash -u $USER_UID -G $USERNAME -D $USERNAME \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME

VOLUME [ "/workspace" ]

ENV DEBIAN_FRONTEND=


### 修改`devcontainer.json`
```json
{
  "dockerFile": "alpine.Dockerfile",
  "settings": { 
    "python.pythonPath": "/usr/bin/python3",
    "python.linting.pylintPath": "/usr/bin/pylint",
  }

启动Sample项目

启动容器后进入flask项目目录。然后启动开发进程。

$ FLASK_ENV=development flask run --host 0.0.0.0 --port 9000
 * Environment: development
 * Debug mode: on
 * Running on http://0.0.0.0:9000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 320-680-031

切换到本地笔记本,查看一下Docker主机的IP,由于我们使用Docker Machine建立的主机,可以通过下面命令查看。

$ docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER     ERRORS
default   *        virtualbox   Running   tcp://192.168.99.122:2376           v19.03.5  

192.168.99.122就是Docker主机IP了。在浏览器访问http://192.168.99.122:9000

由于之前在devcontainer.json文件中配置了"appPort": ["0.0.0.0:9000:9000"],所以容器在主机上映射了9000端口。
但这种配置在多人共享一台Docker主机,或者同时调试多个环境并产生端口冲突时会比较麻烦。
那么下面我介绍一下Forward端口

Forward端口

延续上一小节的环境,我们在容器中启动了开发进程,开放的9000端口。现在我们做如下操作

  1. devcontainer.json中删除"appPort"属性
  2. 在命令行中使用docker rm -f sample-python删除容器
  3. 重新在容器中启动项目
  4. 点击左下角状态栏绿色的部分
  5. 在出现命令菜单中选择Remote-Containers: Forward Port from Container...
  6. 在出现的下一步的菜单中选择Forwarding 9000
  7. 在浏览器打开http://localhost:9000地址就可以看到页面了。

停止Forward

  1. 点击左下角状态栏绿色的部分
  2. 在出现命令菜单中选择Remote-Containers: Forward Port from Container...
  3. 在出现的下一步的菜单中选择Stop Forwarding 9000->9000
  4. 就可以取消端口转发

Forward端口的好处是不会占用Docker主机的端口资源

如果两个容器环境同时需要Forward同样的端口怎么办么?
vscode在Forward的时候,如果发现本机端口被占用,则会随机找一个可用端口Fowrard到容器中

程序调试

在sample项目中有一个文件.vscode/launch.josn这个就是调试配置文件。sample中已经有了Flask的配置内容。我们将其中"FLASK_APP"项修改成准确的路径指向app.py。然后启动调试进程。

Remote-Containers的调试过程-step

启动进程后我们在app.pyhello()方法中加入断点。然后在浏览器访问http://127.0.0.1:9000(别忘了Forward端口)。
这是你应该可以看到,vscode停在了断点处。

到此为止VSCode使用Remote-Containers连接Docker容器的过程基本介绍完毕了。

上一篇下一篇

猜你喜欢

热点阅读