Angular 资源集锦
github-circle-white-transparent
作者:
汪志成
发表:
2019年3月4日
修改:
2019年3月5日

收录:
雪狼湖

基于 Docker 的同构开发环境

前面我们讲了开发人员应该掌握的一些 Docker 基础知识。这篇文章将专注于一个重型主题:利用 Docker 构建多环境同构的开发环境。

你是否曾饱受 "Showcase 必败定律" 的困扰?是否曾在上线的日子里默念阿弥陀佛?是否曾在上线后被人从被窝里呼起来解决问题?这篇文章将尝试给你一个解决方案。

多节点系统的部署挑战

无论是传统的 SOA,还是正在流行的微服务,都属于多节点系统,也难免会遇到同一个问题:你要为本地、DEV、SIT、UAT、Prod 等环境分别编写启动/部署脚本,做得好一点的会把这些环境的信息参数化,以减少脚本数量。

但这仍然不是理想的解决方案。根本问题在于这些环境不是同构的,因此只有等推进到相应的阶段时才能发现一些与部署有关的问题,而发现问题越晚,解决它的代价就越大。极端的例子是生产环境出问题,这种大锅恐怕没几个人背得起。

要解决它,通常是靠经验,如果你以前吃过亏,现在就知道小心,这固然很好,无奈这种级别的程序员可不好找。但是现在,Docker 让我们看到了另一种可能性:如果能保持各个环境始终是同构的,那么与部署有关的问题就可以尽早暴露出来,而不必过于依赖经验。

问题的分解

测试/发布阶段

SIT、UAT 之间相似度最高,Prod 也很相似,主要是数据不同、宿主机配置和数量不同。但 k8s 等成熟的工具提供了一层坚实的抽象,可以让我们不用担心负载均衡等问题,所以,解决这三者之间的差异就相对容易一些。

这三个环境之间最难以调和的问题在于 IP 地址 —— 开发人员往往无法控制它们的 IP 地址,甚至在 DevOps 体系下都不容易控制。因此,我们要采用两个主要策略:

  1. 使用虚拟子网

    我在前面的文章中讲过,Docker 的各个容器之间可以组成一个虚拟子网,这个虚拟子网不受环境的影响,可以自行分配其中的所有 IP,而不用担心冲突。甚至,在 k8s 或 swarm 等网络下,我们都可以不用关心容器在宿主机上的分配问题。

  2. 使用机器名(域名)

    容器的 IP 对于维护工作来说仍然很不友好,因此我们应该使用机器名来代替 IP,在虚拟子网中,机器名默认为容器的 name,比如用 docker run --name www 启动的容器,在虚拟子网中就可以直接通过 www 这个名字来访问。

在这样的方式下,测试与发布阶段的几个环境就基本同构了。不过,由于 k8s/swarm 抽象层的存在,当你在容器中读取本机 IP 时,它并不是此节点对外暴露的 IP。因此,这三个环境下,最好都使用同一个容器管理系统,即使条件不允许,也至少应该都使用跨宿主组网的方式,以便尽早发现问题。

开发阶段

如果你的开发机足够好,那么直接在本机构建一个与测试/发布阶段完全相同的容器组当然是最理想的,不过一般人没这个条件,况且还要为将来接手的人考虑。

比较理想的方式是架设一台组内共享的高配开发服务器,在这台服务器上运行开发版的容器组。而自己正在开发的程序,则通过某种方式接入到这个容器组中,进行开发调试。

现在,问题集中在:如何让本机接入容器组?

最简单的方法是:我们可以把开发服务器上的每个服务都通过不同的端口映射出来,并从本地开发环境远程连接它们。虽然这样要牺牲本地开发环境与其它环境的同构性,但是用这点小代价来换取技术简单性还是值得的,毕竟还有后续的步骤能进行验证呢。

这种方式只支持把本地环境作为消费者接入,无法作为生产者接入。但是由于大多数开发场景都可以通过直接测试生产者来验证,因此这种方案已经足够覆盖大部分需求了。

稍微复杂一点的方法是使用 docker swarm。

也就是把本机上的容器加入到 swarm 中。这种方式可以把本地环境作为消费者接入,也可以作为生产者接入,但是作为生产者接入时要先把开发服务器上的相应容器踢下线,并用自己代替它。同时,当多人共享同一个环境时,可能需要协调多个开发者。让本机加入 swarm 网络需要配置端口转发、tunnel 等穿透私网的手段,在局域网环境下配置比较复杂,这里就不展开它了,有兴趣的可以参照 docker 官方文档进行试验,特别要注意开放相应的端口。

略复杂,但是普适性更高的方式是 vpn。

也就是说,把所有容器都加入到同一个专用 vpn 网络中,这样,它们就可以无视自己的物理或虚拟位置而以独立节点的身份相互通讯,甚至不用依赖 swarm 等跨宿主机方案。这种方式可以把本地开发环境作为消费者接入,也可以作为生产者接入,但是作为生产者时会面临和 swarm 方案一样的问题。另外,这种方案需要在每个 docker 容器中都部署 vpn 客户端,在一定程度上损害了同构性。

最理想的方案是 proxy + tunnel。

也就是说,本机和开发服务器上的所有容器都使用同一个专用的可控代理服务器,这个代理服务器作为一个独立容器部署于 docker 网络中,原有的服务容器不需要做任何修改。这个代理位于 docker 网络中,因此当外部发请求给它时,就可以使用 docker 网络内部的名字,而这个代理会找到正确的容器,并把该请求转发过去。这个代理服务器可以把自己的代理端口映射到开发服务器本身,而本地开发环境可以把它设置为自己的代理,这样就能统一而简易的访问开发服务器中的各个容器了,各个环境下的配置得到了统一。

当作为消费者时,这种方式配置更加简单,对同构性的影响也最小,但优势也不是很明显。它的突出优势在于作为生产者时,它可以通过远程命令控制其代理逻辑,因此,你可以让所有的 docker 容器都使用这一个代理,这样,它就相当于一个多路开关,你可以通过命令来控制它把对开发服务器上某个服务的请求临时转向本机环境。

当然,你肯定想到了一个问题:代理位于 docker 网络中,它访问不到你位于本机环境下的开发服务器。别担心,还有 tunnel 呢,所以这个方案才叫 proxy + tunnel,你可以把本地的特定端口映射到那个 proxy 容器中的某个端口上,再让 proxy 服务器把对该服务名的请求转发到那个端口,再转发回你的本机开发环境。

另外,在这个方案中,本地开发环境实际上不一定需要 docker,你可以沿用传统的开发方式来接入开发服务器。

这个方案虽然有一定的技术难度,不过只要写好了这个代理服务器(其中可以集成 tunnel 服务),在后面的实施和使用上反倒很简单:只要把这个 docker 容器加入 docker 网络中,并做好映射就行了。

结语 —— 一个小目标

所以,问题在于:这个代理服务器要到哪里找呢?目前我还没有找到这样一个代理程序,所以把它列入了我今年的开源开发计划。不过,如果你知道哪里有一个现成的能满足类似需求的开源软件,欢迎推荐给我,免得我重复造轮子。