
编者按:本文源自阿里云云效团队出品的《阿里巴巴DevOps实践指南》,前往:https://developer.aliyun.com/topic/devops,下载完整版电子书,了解阿里十年DevOps实践经验。
开发一个需求,需要先进行代码的编写和个人验证,验证功能符合预期之后,再提交代码,并进入到集成环境,进行进一步的验证及验收。而这个编码和验证的过程占据了整个需求交付的大部分时间,因此提高这部分工作的效率就显得至关重要。
有什么因素降低了开发调试的效率呢?
给定下面一个系统,其中为了开发某个需求,修改了 A 和 D 这两个应用(这里的应用指的是一个可提供服务的一组独立进程加上可选的负载均衡,比如一个 kubernetes 下的 service 及其后端的 deployment)。

接下来看看为了本地调测这两个应用,会遇到什么问题。
我们通常都在开发一个复杂系统中的一个应用,这个应用可能在系统的最前端,也可能在系统的中间位置,有时候为了端到端验证整个流程,需要把相关的应用都启动起来。
比如上图中的应用 A 为最前端应用,应用 D 处在中间位置,而黑框中部分是为了完整的测试这个需求而涉及到的应用,如果是 Java 应用,开发机上启动这样 5 个进程,就已经不堪重负了,而很多时候需要完整启动的应用数量会远大于这个数字。
既然不能把整个系统都在本地启动起来,那么本地就会一部分依赖于公共测试环境。虽然前面提到应该本地测试符合预期之后再把代码部署到测试环境,但不可避免的还是会出现一些 bug,导致测试环境不可用(这也是测试环境的价值所在,尽早的发现问题)。一旦依赖系统不可用,就无法正常的进行测试。
在基于 Kubernetes 的基础设施下,整个系统中大部分的应用通常不需要通过 Ingress 暴露到公网。如果你的测试环境是独立的 K8S 集群,那就意味着无法从本地无法访问到集群内的应用,那么依赖公共测试环境这件事情都无法进行,比如上图中 A->C,D->E,D->F 的依赖。
还有另外一种依赖,即上游应用对本地应用的依赖,比如 C->D 的依赖。但因为 C 是公共测试环境,不可以将所有的 C 对 D 的请求都打到本地来,这就需要某种机制来保证只有特定规则的请求会路由到开发本地的 D 应用。
有一些测试链路需要接受一些外部依赖系统的回调,比如微信或者支付宝的回调等。而本地应用通常没有公网地址,这也给调试带来了一些困难。
分布式系统中经常会用到 RocketMQ 等消息中间件,如果使用了公共测试环境,就意味着 MQ 也是共用的,那么 MQ 的消息到底是应该被测试环境消费,还是某个个人的开发环境消费呢,这也是需要解决的问题。
为了进行全流程的高效开发,应该尽量使用反馈比较快的验证方式,并及早发现问题,逐步进行更加集成,更加真实的测试。
一般来讲,一个开发过程可以经过下面的三个阶段:
基于上面的三个阶段,可以使用以下的方式来解决前面提到的几个问题。
其中第 1、4 是成熟的技术,这里不再赘述。第 5、6 点会在后面的测试环境相关的章节中我们详细讲解。本文主要就第 2、3 点展开讲解。
比如对应用 D 而言,测试范围如下图的橙色框所示:

应用本身的持久化等依赖使用真实的(一般使用本地 DB),但外部应用(应用 E、F)使用基于 HTTP协议的测试替身。这样就可以保证所有的依赖都是稳定的。并且也可以很方便的修改测试替身的行为,以进行特定场景的测试。
应用 D 依赖了两个应用:
这两个应用的访问地址配置在应用 D 的配置项中:
首先创建如下的目录结构:
├── Dockerfile
├── docker-compose.yml
├── moco-runner.jar
└── services
├── org-service
│ └── config.json
└── user-service
└── config.json
Dockerfile:
FROM openjdk:8-jre-slim
ARG SERVICE
ADD moco-runner.jar moco-runner.jar
COPY services/${SERVICE}/config.json config.json
ENTRYPOINT ["java", "-jar", "moco-runner.jar", "http", "-c", "config.json", "-p", "8080"]
docker-compose.yml:
version: '3.1'
services:
service-f:
ports:
- 8091:8080
build:
context: .
dockerfile: Dockerfile
args:
SERVICE: org-service
service-e:
ports:
- 8092:8080
build:
context: .
dockerfile: Dockerfile
args:
SERVICE: user-service
services/org-service/config.json:
[
{
"request": {
"uri": "/"
},
"response": {
"text": "org service stub"
}
},
{
"request": {
"uri": {
"match": "/orgs/[a-z0-9]{24}"
}
},
"response": {
"json": {
"name": "some org name",
"logo": "http://xx.assets.com/xxx.jpg"
}
}
}
]
services/user-service/config.json:
[
{
"request": {
"uri": "/"
},
"response": {
"text": "user service stub"
}
},
{
"request": {
"uri": {
"match": "/users/[a-z0-9]{24}"
}
},
"response": {
"json": {
"name": "somebody",
"email": "somebody@gmail.com"
}
}
}
]
然后使用如下命令来启动两个依赖的应用:
docker-compose up --build
验证下本地测试替身的行为:
$ curl http://localhost:8092/users/111111111111111111111111
{"name":"somebody","email":"somebody@gmail.com"}
$ curl http://localhost:8091/orgs/111111111111111111111111
{"name":"some org name","logo":"http://xx.assets.com/xxx.jpg"}
然后再把应用 D 的依赖配置改成本地测试替身即可进行测试:
... org-service-host: localhost:8091 user-service-host: localhost:8092 ...
至此,我们得到了一个稳定的单应用的集成测试环境。当需要修改依赖的行为时,只需要修改相应应用的config.json 即可。
使用 docker-componse 和 moco 是一种实现单应用集成测试的方式,你可以根据项目的具体情况选择合适的工具和方案。
完成单应用的集成测试之后,可以获得单个应用级别的质量信心,但更大范围的验证还是需要和真实的依赖集成在一起进行。

如上图所示,为了能够在本地按需启动应用(A 和 D),并复用测试环境的其他应用(C),就需要解决两个问题:
关于第一点,如果本地环境和测试环境的网络是直接可达的,则直接修改本地应用 A 的配置项即可。如果你使用了云原生的基础设施,那么就需要类似云效 kt-connect 之类的工具来进行打通,这里不再展开,有需求要的可以参看 kt-connect 的 connect 部分。
关于第二点,需要解决三个问题:
关于第一点和第二点,在阿里巴巴内部有一套完整的方案进行染色和路由,这套方案不仅仅适用于 HTTP 链路,也适用于 RPC,异步消息等。而在开源领域,也有基于云原生基础设施的 kt-connect 可以用,使用kt-connect 的 mesh 功能就可以针对特定染色规则的调用链进行路由。

kt-connect 基于 istio 的 VirtualService 和 DestinationRule 来进行路由。其基本原理是在集群内新建一个影子副本的 service 和 deployment,然后提交一个应用 D 的 DestinationRule 资源,使得包含“local-env: true”header 的请求被路由到应用 D 的影子副本,然后应用 D 的影子副本再把请求转发到本地。在这个过程里,除了提交和更新 is t i o 相关资源的操作需要手动进行之外,其他的事情都可以使用ktctl mesh 命令来完成,详情请参看 mesh 最佳实践。
接下来解决第三点,染色标传递。即需要保证当本地的应用 A 把含有“local-env: true”header 的请求打到测试环境的应用 C 后,应用 C 继续访问应用 D 时候,请求中也应该包含这个 header。
一般的思路是在 Web 层的入口加一个 Interceptor,将染色标记录下来到一个 ThreadLocal 中,然后再出口的 HttpClient 层再从 ThreadLocal 中把这个染色标取出来,并填充到 Request 对象中。这里有一个需要注意的问题,因为染色是放在 ThreadLocal 中的,因此在一个 web 请求的处理中一旦遇到多线程的情况,就需要小心的把这个 ThreadLocal 的值传递到相应的子线程中。所有的应用都正确的将染色标传递下去,就可以保证染色标在全链路进行传递。
使用 kt-connect 的 mesh 方案加上全链路染色标的方案,就可以轻松的在本地按需启动应用,并进行开发调测。
【关于云效】
云原生时代一站式DevOps平台,数十万企业都在用。支持公共云、专有云和混合云多种部署形态,通过云原生新技术和研发新模式,助力创新创业和数字化转型企业快速实现研发敏捷和组织敏捷,打造“双敏”组织,实现多倍效能提升。

我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为