如何优雅解决团队开发环境的一致性问题

公众号关注 「奇妙的 Linux 世界」

设为「星标」,每天带你玩转 Linux !

a3133b82713e82e361f868b31269d0d4.png

作为开发人员想必大家在成长的道路上,或多或少都说过这句经典的话,我在本地环境测试过...都正常的呀...为什么一到上线就不行了呢?

开发同学埋怨运维同学上线的姿势不对,运维同学也表示很无奈,我已经换了无数姿势了,你还想要怎样?你自己开发的代码质量怎么样,心里就没个数吗?

ad2604c77ab2d50155f53ed72e2227e4.png
你还想要怎样?

直到 Docker 的出现,应用程序容器化打包部署后,保证了线上环境的一致性,这种声音逐渐减少了,就算有,也只是一些小摩擦,开发同学和运维同学终于可以愉快的在一起玩耍了...

但是,开发同学真的快乐了吗?事实上并没有,为什么呢?

一般公司不同 Team 之间使用的技术栈都不同,每一位开发同学在准备写代码之前,必须要先配置本地开发环境。比较传统的做法是,团队內用到什么技术栈,开发同学在本地环境就会安装什么,然后他们会用各自喜欢的 IDE 进行调试开发,之后通过 CI/CD 流水线交付到相应的环境,看上去很美好对不对?

f6e926895120f29a19f90d50436af159.png
真的完美吗?

这里我先提几个问题:

  1. 你是写 Python 的,后面转岗到其他团队,同时需要写 Golang 和 Java 了,你准备怎么弄?

  2. 你的团队是个大杂烩,Python、TypeScript、Golang 都做,此时有新人进来了,你准备让新人怎么开始?你觉得新人的心情是啥样的?

  3. 你的团队因为一些历史原因,不同项目所使用的编程语言或者构建工具的版本可能都不一样,来回切换上下文,你自己有没有奔溃过?

  4. 你作为一位技术大拿,突然被临时指派做一个排期非常紧张的项目,但是你本地没有工作环境,安装那套环境又比较费时。果然...你在配置环境上花费了些许时间,大拿的技术反而没发挥出来,此刻你的心情又是怎样?

753830681782b957452edcc3be423476.png
现实其实很残酷

想必大家也明白我的意思了吧,我们虽然通过 Docker 保证了线上环境的一致性,解决了交付的问题(也缓和了开发同学和运维同学的关系),但是对于开发同学来说,如何保证开发环境的一致性同样至关重要。

我曾经见过一位新人,自个儿闷声不吭的在本地机器捣鼓开发环境,折腾近一周了,还没出个结果,相当的沮丧,他都开始怀疑人生了。

那么问题来了,有没有一种方式,既可以保证开发环境的一致性,又可以在 IDE 下进行调试,且又能够将环境共享给团队內的其他成员呢?

这正是本篇文章要分享的主题。

为什么是 Dev Container

我们最初是采用比较笨的方法,Team 內会做一些共识,统一语言版本、统一安装依赖、统一内核版本以及统一 Build Tools 版本,就算需要升级也是大家讨论后各自在开发环境一起升(可以说这是内部开发人员需要遵循的指南)。不过这种方法只适合小的团队,只要团队一扩展,你就会发现很难去协调,有些人甚至很犟,非要升。

Q: "为什么不让用新版本,旧版本问题太多了,你这样的约定会让我很不舒服的...balabala..."
A: "对...你说的没错..."

有时,确实要为了考虑新人的感受不得已做出一些妥协,不然人家刚进来就开始躺平,那就不好玩了...

让我们重新回到容器这个话题,Docker 既然能够解决应用交付环境一致性的问题,那它能不能用来做为本地的开发环境呢?如果可以的话,我们就能够使用相同的 Base image,至少能保证开发环境一致性这个问题了。

显然这个方法是行的通的,我们可以将本地的代码,通过卷的方式挂载到容器內,在容器內做一些构建的工作,然后在容器內将应用跑起来。

docker run --rm \
  -v $(pwd)/workspace:/workspace \
  -p 3000:3000 \
  <IMAGE-NAME> <COMMAND>

这个想法是好的,但我们还是太天真了,相信大家平时在写代码的过程中,做的最多的一件事情就是 Debug 吧,而且还是利用 IDE 的断点调试。如果单单采用 docker run 的方式来开发,IDE 这个强大有力的工具,就失去了它原本的意义了。

好在,现在主流的 IDE 都提供支持远程开发模式,比如 VS Code 就有远程调试的功能模块,正好解决了上面这个问题。

其实 IntelliJ 全家桶也有提供 Remote debug[1] 的功能,但是太重太耗资源了,相信只要用过,大家都懂的。

本篇文章主要是分享 VSCode 的 Remote debug 流程,Dev Container 在 VS Code 中提供了完美的解决方案,它可以和容器做结合,能够做到为每个项目提供独立的开发环境。只需要在项目里加入几个文件,你就可以为你的团队共享一个环境一致的、可调试的开发环境,且同时包含 VSCode 插件。

我们这里使用 Visual Studio Code Remote - Containers 模式进行配置,原理如下图所示

9e1c4d60746f2d317ef29972df7d5a38.png

先决条件

要使用这个环境,必须在本地机器上安装以下程序

  1. Docker[2]

  2. Visual Studio Code[3]

  3. Visual Studio Code Remote - Containers[4]

如何配置 Dev Container

有两种方式可以配置 Dev Container 流程。

但是,不管你使用哪种方式,都需要确保 .devcontainer 这个目录提交到你的代码库中,这个目录可以说是,你为你的团队创建共享开发环境的一个关键所在。

  1. 通过 VSCode 通过向导模式创建文件

  2. 手动配置

本篇文章主要以 Golang 作为例子,介绍在容器下开发的流程。

1. 通过向导模式自动创建文件

我们使用 Commond+Shift+P 打开命令面板,搜索 Remote-Containers 关键字,然后按下面 5 个步骤依次走完向导流程。

#1. 选择添加开发容器配置文件
b961ce122090311dfec36e7416afa950.png
选择 Add Development Container Configuration Files
#2. 选择开发语言
8b9edbd0ab58e696f0a7f50d305d1eca.png
选择 Go
#3. 选择开发语言版本
bb591588422fabc58ff2417084963584.png
选择 1.17
#4. 提示你是否需要安装 NodeJS,我选择不安装
7a71b391207ee8d4c88ab7da3f8fa7a8.png
选择 none,表示不安装 NodeJS
#5. 按需选择一些附件功能的安装包

additional features 这个模块还是蛮坑的,个人建议还是将需要的依赖包直接做在 Base Image 中比较稳定些

fb685f756ef9940136b4f351202ff105.png
我选择了  Git 和  kubectl-helm-minikube

以上几个步操作完成后,VS Code 会在你项目的根目录下自动创建 .devcontainer 目录,内容如下所示:

➜ tree .devcontainer
.devcontainer
├── Dockerfile
└── devcontainer.json

0 directories, 2 files
Dockerfile

Dockerfile 文件里定义的是,创建容器实例的容器配置,它能为你的团队小伙伴共享同一个开发环境

devcontainer.json

devcontainer.json 是配置 VS Code 环境,用来启动基于 VS Code Remote Container 的关键所在。

再次提醒下,以上定义的文件都是需要签入到项目的代码库中的,这样你团队內的其他小伙伴才可以使用相同的定义,在容器中进行构建和开发。

2. 手动配置文件

对于有经验的同学来说,大可不必通过控制面板这样按部就班的来做,直接创建文件,做进一步的定制配置即可。

3. 如何加载项目

准备工作已就绪,我们现在怎么才能够将这个容器跑起来呢?

再次使用 Commond+Shift+P 打开命令面板,搜索 Remote-Containers 关键字,选择 Rebuild and Reopen in Container 构建容器。

24d1804cd2983e2f182b2f364525424b.png
选择  Rebuild and Reopen in Container

你能够看到左下角提示正在打开远程的容器,右边可以看该容器构建过程中的详细日志

a3412d33ecf4d803a45b78b0671fbcb2.png

等容器构建完成后,就可以正常使用 VS Code 了,但你需要明白,你所看到的内容其实都是跑在容器內的,其实你从右侧的 Terminal 中的文件信息也可以发现了,它并不属于你本地环境的,另外 VS Code 的插件也是在容器內运行的。

点击左下角 Dev Container: Go 弹出的控制面板,可以对当前的容器做进一步操作,大家可以自己一一做尝试,细节我这里就不做介绍了。

8401e34f47c02744da005ead85f42e5c.png

想必大家也发现了,此时你本地环境其实多了这么一个容器,如下所示

➜ docker ps
CONTAINER ID   IMAGE                                                              COMMAND                  CREATED          STATUS          PORTS                               NAMES
9f68c69eeeff   vsc-vscode-remote-labs-ab076eb433b3a4fa05ec199117c419f1-features   "/bin/sh -c 'echo Co…"   14 minutes ago   Up 14 minutes                                       serene_yalow


我们通过 Docker inspect 命令查看,容器內的数据就是将本机的代码做了挂载

➜ docker inspect serene_yalow | jq '.[0].Mounts[] | {local_path: .Source, destination_container_path: .Destination}'
{
  "local_path": "/Users/linqiong/workspace/github/lqshow/vscode-remote-labs",
  "destination_container_path": "/workspaces/vscode-remote-labs"
}

需要说明一下,容器项目在第一次加载一般都会比较慢,因为它需要在你本地环境构建镜像,后面只要大家不更改 Dockerfile 文件,容器项目基本上都是秒启动的。

最佳实践

上面可以说是 Visual Studio Code Remote - Containers 的开箱式体验,直接拿来用肯定是不行的,让我们带着问题,来优化一下配置吧。

9eb5e454e7286804c9cc0f96591f1406.png

#1. 在容器里如何做 check in?

默认情况下,在容器內,我们是没有权限将修改后的代码,提交到代码仓库里的,它会提示没有权限操作。

d79e3a805db1292891e4e0bb95135db0.png
无权限做 Push 操作

同样,容器內也没有权限 clone 私有仓库。

98767a19d729adf6db3e48012d80328b.png
无权限 clone 私有仓库

如何解决这个问题呢?很简单,只需将本地的 SSH 凭证挂载到容器內就可以了

827f87a8e2bf8fa9c539987fd2649957.png

我们在 devcontainer.json 里主要添加以下几行代码

{
    "runArgs": [
        ...
        // 挂载本地 .ssh 目录到容器的 .ssh-localhost 目录
        "-v", "${env:HOME}${env:USERPROFILE}/.ssh:/root/.ssh-localhost:ro"
    ],
    // 设置在容器创建完成后运行的命令.
    "postCreateCommand": [
        "${containerWorkspaceFolder}/.devcontainer/run-devbox.sh"
    ],
}

这里有必要说明一下,为什么 postCreateCommand 我会使用 run-devbox.sh 执行脚本?

postCreateCommand 作为容器创建完成后运行的命令,后续需要执行的语句可能非常多,放在一个脚本文件里能够方便的维护。

不过现在 run-devbox.sh 内容很简单,只有跟 .ssh 配置文件相关的操作

# Setting ssh config
mkdir -p /root/.ssh && cp -r /root/.ssh-localhost/* /root/.ssh && chmod 700 /root/.ssh && chmod 600 /root/.ssh/*

让我们再次使用 Commond+Shift+P 打开命令面板,搜索 Remote-Containers 关键字,这次选择 Rebuild Container 来重建容器。

afdcb9e34deb85f98c7ba05938bd7ae6.png
重新构建容器

注意:后续所有不管是对 devcontainer.json 或者 Dockerfile 做出修改,都需要执行这一步操作后,容器內的环境才会生效。

重建容器后,开发同学就可以和在本地环境一样,无缝使用 git 命令进行操作了。

root ➜ /workspaces/vscode-remote-labs (main) $ ssh -T git@github.com
Hi lqshow! You've successfully authenticated, but GitHub does not provide shell access.
2c9c8da8b9c91504184cbab9b9547d96.png
git push 成功

#2. 如何打通 Kubernetes 开发集群?

有人问,现在都是微服务架构了,我们的服务都部署在 Kubernetes 集群內,这种利用 VS Code 构建基于容器的开发模式,能够和 Kubernetes 集群內的其他服务进行通信吗?这个当然可以有。

首先需要让容器环境可以访问到 Kubernetes 集群,在 devcontainer.json 里主要添加以下几行代码

{
    "runArgs": [
        ...
        // 挂载本地的 KUBECONFIG 配置文件到容器内
        "-v", "${env:HOME}${env:USERPROFILE}/.kube:/root/.kube-localhost"
    ],

    "containerEnv": {
        // [Optional] 默认将同步开关设置成 true,将本地的 KUBECONFIG 配置文件同步到容器内
        "SYNC_LOCALHOST_KUBECONFIG": "true"
    },

    // 在创建容器之前,使用 `initializeCommand` 命令,合并你本机的所有的 KUBECONFIG 到一个配置文件中(~/.kube/gen-config)
    // 需要特别注意的是,该参数中的命令,是在你本机环境里执行
    "initializeCommand": [
        "${localWorkspaceFolder}/.devcontainer/export-local-kube-config.sh",
    ]
}

然后在 Dockerfile 里安装 kubectl 客户端以及一些常用工具,比如 helm 和 kubectx。

# Install kubectx
RUN git clone https://github.com/ahmetb/kubectx /opt/kubectx \
    && ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx \
    && ln -s /opt/kubectx/kubens /usr/local/bin/kubens

# Install kubectl
# Note: Latest version of kubectl may be found at:
# https://github.com/kubernetes/kubernetes/releases
ARG KUBE_LATEST_VERSION="v1.19.2"
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl
RUN mv ./kubectl /usr/bin/kubectl

# Install helm
# Note: Latest version of helm may be found at
# https://github.com/kubernetes/helm/releases
ARG HELM_VERSION="v3.7.2"
RUN wget -q https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz -O - | tar -xzO linux-amd64/helm > /usr/local/bin/helm \
    && chmod +x /usr/local/bin/helm

最后在 run-devbox.sh 脚本里追加以下命令,主要来用设置容器內 Kubernetes 集群的上下文

# Setting the namespace preference
namespace="default"
if [[ $KUBERNETES_NAMESPACE ]]; then
  namespace=${KUBERNETES_NAMESPACE}
fi

kubectl config set-context --current --namespace=${namespace}


对于小白来说,我觉得有必要在这里解释一下上面的执行流程

  1. 在创建容器之前,首先会执行 export-local-kube-config.sh 脚本,将你本地环境所有的 kubeconfig file 合并到你本机的 ~/.kube/gen-config 文件里

  2. 启动容器时,将本机 ~/.kube 整个目录挂载到容器的 /root/.kube-localhost 目录下

  3. 容器启动后,执行容器內的 /usr/local/share/copy-kube-config.sh 脚本,将执行 cp -r /root/.kube-localhost/gen-config $HOME/.kube/config 命令,并设置 KUBECONFIG 环境变量


重建容器以后,容器內就可以访问到内网的 Kubernetes 集群了,见下图

162440c5da0ca352043eea6429935b19.png
容器里可以直接访问 Kubernetes 集群

但是,这也只能说明在容器內,可以访问集群的资源而已。那么怎么才能够和集群內的其他服务直接进行通信呢?

我们可以将 Telepresence 集成到 Base Image 里完成这个事情,只需在 Dockerfile 里添加以下代码

# Install telepresence
RUN curl -fL https://app.getambassador.io/download/tel2/linux/amd64/latest/telepresence -o /usr/local/bin/telepresence && chmod a+x /usr/local/bin/telepresence


然后重建容器,在容器內通过telepresence connect进行连接就可以了

da50574a80630d313433eddc6c6a2a99.png
容器內直接通过 svc + namespace + port 访问集群內服务

需要注意的是,因为 telepresence connect 命令需要 Need root privileges to run 才能顺利执行,所以需要给容器加上权限

{
    "runArgs": [
        "--privileged"
    ]
}

#3. 初次构建镜像太慢怎么办?

上面我也提到过,容器项目在第一次启动一般都会比较慢,因为它需要在你本地环境构建镜像。

我们尽量将 Dockerfile 里已经确定的安装流程抽取做成一个 Base Image,这样你团队內的其他成员就可以节省在自己机器上的 Build 时间了(你可以将这个 Base Image 理解为默认的容器內开发环境)。

聪明如你,你将会为你的 team 准备好适配各个技术栈的 Base Images。

#4. 同步本地的 VS Code 插件

开发同学一般都是希望容器內的 IDE 和主机上的 IDE 保持一致的插件,这里有个小技巧,直接可以将本机的插件同步进来,而不需要另外在容器里重新安装

{
    "runArgs": [
        // 挂载本地的 vscode extensions 到容器,避免在容器中重新安装扩展
        "-v", "${env:HOME}${env:USERPROFILE}/.vscode/extensions:/root/.vscode-server/extensions"
    ]
}

#5. 定制适合自己的开发环境

有些同学可能会觉得团队提供的 Base image 和自己平时用的不一样,使用起来会不舒服。没关系...不用担心,你完全可以对 Base image 做扩展,说不定你提交的模块,其他小伙伴也非常喜欢呢。

你可以利用 Dockerfile 中的 ARG 参数做到这点,比如下面这个例子,默认在容器內不安装 NodeJS, 只有传了指定的值,才会去做安装。

Dockerfile

# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

devcontainer.json

{
    "build": {
        "dockerfile": "Dockerfile",
        "args": {
            // Options
            "NODE_VERSION": "none"
        }
    }
}

说明一下,这里的 Dockerfile 只是用来作为开发环境用的,所以镜像做的大一点没关系,这个镜像不会用来上生产环境。

#6. 关闭 VS Code 后,怎么重新进入?

在容器退出或者 VS Code 关闭后,其实你本机的容器还是在的,只是处于退出状态,你可以用 docker ps -a 看下。

➜ docker ps -a
CONTAINER ID   IMAGE                                                     COMMAND                  CREATED              STATUS                     PORTS     NAMES
7b9d0bc6fec8   vsc-vscode-remote-labs-ab076eb433b3a4fa05ec199117c419f1   "/bin/sh -c 'echo Co…"   About a minute ago   Exited (0) 4 seconds ago             vscode-remote-labs
(base)


你再次打开 VS Code 的时候,应该会发现有两个名字相同的项目。

你直接点击 vscode-remote-labs[Dev Container] 就可以快速唤醒它,是不是很给力!

4582196385c14b07589151e2485855c5.png
直接点击红框的项目

我们再次通过 docker ps 查看,该容器已经处于 Running 状态了。

➜ docker ps
CONTAINER ID   IMAGE                                                     COMMAND                  CREATED         STATUS         PORTS     NAMES
7b9d0bc6fec8   vsc-vscode-remote-labs-ab076eb433b3a4fa05ec199117c419f1   "/bin/sh -c 'echo Co…"   4 minutes ago   Up 9 seconds             vscode-remote-labs
(base)

#7. devcontainer.json 参数选项

devcontainer.json 文件中的参数非常多,文中目前只用到了以下表格部分

所有参数选项,请移步官网文档:devcontainer.json reference[5]

KeyDesc
build定义容器的创建方式,主要和 Dockerfile 相关
runArgs被当做 docker run 的参数部分传入
containerEnv本地机器的环境变量,可以共享给容器
initializeCommand在创建容器之前运行的命令
postCreateCommand设置在容器创建完成后运行的命令

现在已经完成大部分配置了,你可以真正的在容器內进行开发了,希望后面你能拥有比德芙还丝滑的远程开发体验。

官方提供了一个各种 vscode-dev-containers 的配置,我相信你会在里面发现很多很有意思的东西。

另外我在文章里我分享的一些配置,大家如果感兴趣的话,可以移步这里 vscode-remote-labs[6]

写在最后

我们作为一个开发者,真心不希望把时间浪费在维护语言的各个版本以及任何降低程序运行的事上,大家可以尝试花些时间在自己的项目下配置 Dev Container,指不定就被它吸引了 😄😄😄

其实 VS Code 也只是一个工具,用的顺手就行,如果你的机器资源足够,或许 IntelliJ Remote debug[7] 也是一个不错的选择,自己喜欢就好。

希望这篇分享对大家都有所帮助。

参考资料

[1]

IntelliJ Remote debug: https://www.jetbrains.com/help/idea/tutorial-remote-debug.html

[2]

Docker: https://www.docker.com/

[3]

Visual Studio Code: https://code.visualstudio.com/

[4]

Visual Studio Code Remote - Containers: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers

[5]

devcontainer.json reference: https://code.visualstudio.com/docs/remote/devcontainerjson-reference

[6]

vscode-remote-labs: https://github.com/lqshow/vscode-remote-labs

[7]

IntelliJ Remote debug: https://www.jetbrains.com/help/idea/tutorial-remote-debug.html

本文转载自:「Cloud Native 101」,原文:https://url.hi-linux.com/za1G8,版权归原作者所有。欢迎投稿,投稿邮箱: editor@hi-linux.com。

2f77cee2005f10f3245fe81db9411fe9.gif

最近,我们建立了一个技术交流微信群。目前群里已加入了不少行业内的大神,有兴趣的同学可以加入和我们一起交流技术,在 「奇妙的 Linux 世界」 公众号直接回复 「加群」 邀请你入群。

27ba6334507bda56d41ea91d4a30079d.png

你可能还喜欢

点击下方图片即可阅读

df13cccc2d93e369ee7c6b3cc257cf98.png

全网最详保姆级 Kubernetes 应用调试中文指南

c3607b6f323cd5d669ff1dc078cfc969.png
点击上方图片,『美团|饿了么』外卖红包天天免费领

687fd9c1eaef36504f233c055d1376ec.png

更多有趣的互联网新鲜事,关注「奇妙的互联网」视频号全了解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值