xenvman:灵活的微服务测试环境(以及更多)

大家好!


我想谈谈过去六个月来我一直在从事的项目。 我在业余时间从事该项目,但是创建它的动机来自于主要工作中的观察。


在一个正在运行的项目中,我们使用微服务的体系结构,随着时间的流逝,越来越多的这些主要问题之一逐渐显现出来,那就是测试。 当某个服务依赖于五到七个其他服务,再加上其他一些(或什至几个)数据库来启动时,那么以“实时”形式进行测试非常不便。 您必须在各个方面都紧紧套上摩卡,甚至无法自己完成测试。 好吧,或者以某种方式组织一个可以真正启动所有依赖项的测试环境。


实际上,为了方便第二种选择,我只是坐下来写xenvman 。 简而言之,这类似于docker-compose和test容器的混合体,只是没有绑定到Java(或任何其他语言),并且具有通过HTTP API动态创建和配置环境的能力。


xenvman用Go编写,并实现为简单的HTTP服务器,它使您可以使用可使用该协议的任何语言的所有可用功能。


xenvman可以做的主要事情是:


  • 使用简单的JavaScript脚本灵活地描述环境内容
  • 即时创建图像
  • 创建正确数量的容器并将它们组合成一个隔离的网络
  • 将环境的内部端口转发到外部,以便测试甚至可以从其他主机获得必要的服务
  • 在旅途中动态更改环境的组成(停止,启动和添加新容器),而无需停止工作环境。

环境环境


xenvman的主要特征是环境。 这是一个孤立的气泡,其中启动了服务的所有必需依赖项(打包在Docker容器中)。



上图显示了xenvman服务器以及运行不同服务和数据库的活动环境。 每个环境都是直接从集成测试代码创建的,完成后将被删除。


模式


环境的一部分直接由模板决定,模板是JS中的小脚本。 xenvman具有此语言的内置解释器,并且在收到创建新环境的请求时,它仅执行指定的模板,每个模板将一个或多个容器添加到列表中以供执行。


选择JavaScript是为了允许动态更改/添加模板而无需重建服务器。 此外,模板通常仅使用语言的基本功能和数据类型(很好的老式ES5,没有DOM,React和其他魔术),因此即使对于完全了解JS的人,使用模板也不会造成特殊的困难。


模板是可参数化的,也就是说,我们可以通过在HTTP请求中传递某些参数来完全控制模板的逻辑。


即时创建图像


我认为,xenvman最方便的功能之一是在配置环境的过程中直接创建Docker映像。 为什么这有必要?
好吧,例如,在我们的项目中,为了获取服务的映像,您需要将更改提交到单独的分支,启动并等待直到Gitlab CI收集并填充映像。
如果仅一项服务已更改,则可能需要3-5分钟。


而且,如果我们正在积极地看到服务中的新功能,或者试图了解为什么它不起作用,来回添加旧的fmt.Printf ,或者经常以某种方式更改代码,那么即使延迟5分钟也可以消除性能(我们作为代码编写者)。 相反,我们可以简单地将所有必需的调试添加到代码中,在本地进行编译,然后将完成的二进制文件附加到HTTP请求。


获得此类批准后,模板将使用此二进制文件并在旅途中创建一个临时图像,我们可以从中启动容器,就好像什么都没发生一样。


例如,在我们的项目中,在服务的主模板中,我们检查参数中是否存在二进制信息,如果存在,则我们将在旅途中收集图像,否则,我们将下载latest版本的dev分支。 这两个选项的创建容器的进一步代码是相同的。


一个小例子


为了清楚起见,让我们看一下微型示例。


假设我们编写了某种奇迹服务器(我们称其为wut ),它需要一个数据库来存储所有内容。 好吧,作为基础,我们选择了MongoDB。 因此,为了进行全面测试,我们需要一个工作正常的Mongo服务器。 当然,您可以在本地安装和运行它,但是出于示例的简单性和可见性,我们假设出于某种原因很难做到这一点(对于实际系统中的其他更复杂的配置,这更像是事实)。


因此,我们将尝试使用xenvman来创建一个运行Mongo和wut服务器的环境。


首先,我们需要创建一个基本目录 ,其中将存储所有模板:


$ mkdir xenv-templates && cd xenv-templates


接下来,创建两个模板,一个用于Mongo,另一个用于我们的服务器:


$ touch mongo.tpl.js wut.tpl.js


mongo.tpl.js


打开mongo.tpl.js并在其中写入以下内容:


 function execute(tpl, params) { var img = tpl.FetchImage(fmt("mongo:%s", params.tag)); var cont = img.NewContainer("mongo"); cont.SetLabel("mongo", "true"); cont.SetPorts(27017); cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' }); } 

模板必须具有带有两个参数的execute()函数。
第一个是通过其配置环境的tpl对象的实例。 第二个参数(params)只是一个JSON对象,我们将使用它对模板进行参数化。


排队


 var img = tpl.FetchImage(fmt("mongo:%s", params.tag)); 

我们要求xenvman下载docker mongo:<tag>映像mongo:<tag> ,其中<tag>是我们要使用的映像的版本。 原则上,这行代码等效于docker pull mongo:<tag> ,唯一的区别是tpl对象的所有功能本质上都是声明性的,也就是说,只有在xenvman执行了环境配置中指定的所有模板之后,才会实际下载映像。


获得图像后,我们可以创建一个容器:


 var cont = img.NewContainer("mongo"); 

同样,容器不会在这个地方立即创建,可以这么说,我们只是声明了创建它的意图。


接下来,我们在容器上贴一个标签:


 cont.SetLabel("mongo", "true"); 

使用快捷方式可以使容器在环境中相互找到对方,例如在配置文件中输入IP地址或主机名。


现在我们需要将内部Mongo端口(27017)挂出。 这样很容易做到:


  cont.SetPorts(27017); 

在xenvman报告成功创建环境之前,最好确保所有服务都不仅在运行,而且准备接受请求。 Xenvman已为此做好了准备检查
为我们的mongo容器添加一个这样的:


  cont.AddReadinessCheck("net", { "protocol": "tcp", "address": '{{.ExternalAddress}}:{{.Self.ExposedPort 27017}}' }); 

我们可以看到,在地址栏中有一些存根,可以在启动容器之前动态替换必要的值。


代替{{.ExternalAddress}}将替换运行xenvman的主机{{.ExternalAddress}}外部地址,并且代替{{.Self.ExposedPort 27017}}将替换转发到内部27017 {{.Self.ExposedPort 27017}}外部端口。


在此处阅读有关插值的更多信息。


所有这些的结果是,我们可以连接到在环境中运行的Mongo,例如从外部运行测试的主机。


wut.tpl.js


因此,c处理完monga之后,我们将为wut服务器编写另一个模板。
由于我们要随时随地收集图像,因此模板会略有不同:


 function execute(tpl, params) { var img = tpl.BuildImage("wut-image"); img.CopyDataToWorkspace("Dockerfile"); // Extract server binary var bin = type.FromBase64("binary", params.binary); img.AddFileToWorkspace("wut", bin, 0755); // Create container var cont = img.NewContainer("wut"); cont.MountData("config.toml", "/config.toml", {"interpolate": true}); cont.SetPorts(params.port); cont.AddReadinessCheck("http", { "url": fmt('http://{{.ExternalAddress}}:{{.Self.ExposedPort %v}}/', params.port), "codes": [200] }); } 

由于我们BuildImage()此处BuildImage()图像,因此我们使用BuildImage()而不是FetchImage()


  var img = tpl.BuildImage("wut-image"); 

为了组装图像,我们将需要几个文件:
Dockerfile-有关如何组装映像的实际说明
config.toml-我们的wut服务器的配置文件


使用img.CopyDataToWorkspace("Dockerfile");方法img.CopyDataToWorkspace("Dockerfile"); 我们将Dockerfile从模板数据目录复制到临时工作目录


数据目录是一个目录,我们可以在其中存储模板工作所需的所有文件。


在临时工作目录中,我们复制进入映像的文件(使用img.CopyDataToWorkspace())。


以下内容:


  // Extract server binary var bin = type.FromBase64("binary", params.binary); img.AddFileToWorkspace("wut", bin, 0755); 

我们直接以编码形式(base64)的形式将服务器的二进制信息传递给参数。 在模板中,我们只需对其进行解码,然后将结果字符串保存为工作目录下的文件,名称为wut


然后创建一个容器并将配置文件装入其中:


  var cont = img.NewContainer("wut"); cont.MountData("config.toml", "/config.toml", {"interpolate": true}); 

调用MountData()意味着来自数据目录的config.toml文件将以/config.toml的名称安装在容器内。 interpolate标志告诉xenvman服务器在装入文件之前,应替换那里的所有存根。


配置如下所示:


 {{with .ContainerWithLabel "mongo" "" -}} mongo = "{{.Hostname}}/wut" {{- end}} 

在这里,我们寻找带有mongo标签的容器,并替换其主机名,无论该环境如何。


替换后,该文件可能如下所示:


 mongo = “mongo.0.mongo.xenv/wut” 

接下来,我们再次发布端口并开始准备检查,这次是HTTP:


 cont.SetPorts(params.port); cont.AddReadinessCheck("http", { "url": fmt('http://{{.ExternalAddress}}:{{.Self.ExposedPort %v}}/', params.port), "codes": [200] }); 

我们的模板已准备就绪,可以在集成测试代码中使用它们:


 import "github.com/syhpoon/xenvman/pkg/client" import "github.com/syhpoon/xenvman/pkg/def" //  xenvman  cl := client.New(client.Params{}) //      env := cl.MustCreateEnv(&def.InputEnv{ Name: "wut-test", Description: "Testing Wut", // ,      Templates: []*def.Tpl{ { Tpl: "wut", Parameters: def.TplParams{ "binary": client.FileToBase64("wut"), "port": 5555, }, }, { Tpl: "mongo", Parameters: def.TplParams{"tag": “latest”}, }, }, }) //      defer env.Terminate() //     wut  wutCont, err := env.GetContainer("wut", 0, "wut") require.Nil(t, err) //      mongoCont, err := env.GetContainer("mongo", 0, "mongo") require.Nil(t, err) //    wutUrl := fmt.Sprintf("http://%s:%d/v1/wut/", env.ExternalAddress, wutCont.Ports[“5555”]) mongoUrl := fmt.Sprintf("%s:%d/wut", env.ExternalAddress, mongoCont.Ports["27017"]) // !      ,            ,   

似乎编写模板将花费太多时间。
但是,通过正确的设计,这是一项一次性的任务,然后可以通过传递某些参数来对其进行微调,从而使相同的模板可以越来越多地重用(甚至用于不同的语言!)。 如您在上面的示例中看到的,由于我们将所有环境都放在模板中,因此测试代码本身非常简单。


这个小例子并没有显示xenvman的所有功能,此处提供了更详细的分步指南


客户群


当前有两种语言的客户:


去吧
巨蟒


但是添加新的并不困难,因为提供的API非常非常简单。


网页界面


在2.0.0版中,添加了一个简单的Web界面,您可以使用该界面管理环境并查看可用的模板。





xenvman与docker-compose有何不同?


当然,有很多相似之处,但是与文件中的静态配置相比,xenvman在我看来似乎稍微更灵活,更动态。
我认为这是主要的区别特征:


  • 绝对所有的控制都是通过HTTP API进行的,因此我们可以使用能理解HTTP的任何语言的代码来创建环境
  • 由于xenvman可以在其他主机上运行,​​因此我们甚至可以在未安装docker的主机上使用其所有功能。
  • 动态生成图像的能力
  • 在操作过程中更改环境组成(添加/停止容器)的能力
  • 通过使用可参数化的模板,减少了样板代码,改善了结构,并具有重用配置代码的能力

参考文献


Github项目页面
详细的分步示例,英语。


结论


仅此而已。 我计划在不久的将来增加机会
调用模板中的模板,从而使您可以更高效地组合它们。


我将尽力回答任何问题,如果有人发现该项目有用,我将感到高兴。

Source: https://habr.com/ru/post/zh-CN439236/


All Articles