阿里巴巴DevOps实践指南(九)| 本地开发

简介: 开发一个需求,需要先进行代码的编写和个人验证,验证功能符合预期之后,再提交代码,并进入到集成环境,进行进一步的验证及验收。而这个编码和验证的过程占据了整个需求交付的大部分时间,因此提高这部分工作的效率就显得至关重要。

image.png

编者按:本文源自阿里云云效团队出品的《阿里巴巴DevOps实践指南》,扫描上方二维码或前往:https://developer.aliyun.com/topic/devops,下载完整版电子书,了解阿里十年DevOps实践经验。

开发一个需求,需要先进行代码的编写和个人验证,验证功能符合预期之后,再提交代码,并进入到集成环境,进行进一步的验证及验收。而这个编码和验证的过程占据了整个需求交付的大部分时间,因此提高这部分工作的效率就显得至关重要。

问题

有什么因素降低了开发调试的效率呢?

给定下面一个系统,其中为了开发某个需求,修改了 A 和 D 这两个应用(这里的应用指的是一个可提供服务的一组独立进程加上可选的负载均衡,比如一个 kubernetes 下的 service 及其后端的 deployment)。

image.png

接下来看看为了本地调测这两个应用,会遇到什么问题。

本地难以启动整个系统

我们通常都在开发一个复杂系统中的一个应用,这个应用可能在系统的最前端,也可能在系统的中间位置,有时候为了端到端验证整个流程,需要把相关的应用都启动起来。

比如上图中的应用 A 为最前端应用,应用 D 处在中间位置,而黑框中部分是为了完整的测试这个需求而涉及到的应用,如果是 Java 应用,开发机上启动这样 5 个进程,就已经不堪重负了,而很多时候需要完整启动的应用数量会远大于这个数字。

依赖系统不稳定

既然不能把整个系统都在本地启动起来,那么本地就会一部分依赖于公共测试环境。虽然前面提到应该本地测试符合预期之后再把代码部署到测试环境,但不可避免的还是会出现一些 bug,导致测试环境不可用(这也是测试环境的价值所在,尽早的发现问题)。一旦依赖系统不可用,就无法正常的进行测试。

云原生开发模式下的测试环境的连通性

在基于 Kubernetes 的基础设施下,整个系统中大部分的应用通常不需要通过 Ingress 暴露到公网。如果你的测试环境是独立的 K8S 集群,那就意味着无法从本地无法访问到集群内的应用,那么依赖公共测试环境这件事情都无法进行,比如上图中 A->C,D->E,D->F 的依赖。

还有另外一种依赖,即上游应用对本地应用的依赖,比如 C->D 的依赖。但因为 C 是公共测试环境,不可以将所有的 C 对 D 的请求都打到本地来,这就需要某种机制来保证只有特定规则的请求会路由到开发本地的 D 应用。

外部依赖系统到开发环境的连通性

有一些测试链路需要接受一些外部依赖系统的回调,比如微信或者支付宝的回调等。而本地应用通常没有公网地址,这也给调试带来了一些困难。

中间件的隔离

分布式系统中经常会用到 RocketMQ 等消息中间件,如果使用了公共测试环境,就意味着 MQ 也是共用的,那么 MQ 的消息到底是应该被测试环境消费,还是某个个人的开发环境消费呢,这也是需要解决的问题。

高效本地开发

为了进行全流程的高效开发,应该尽量使用反馈比较快的验证方式,并及早发现问题,逐步进行更加集成,更加真实的测试。

一般来讲,一个开发过程可以经过下面的三个阶段:

  1. 编码+单元测试。在小的逻辑单元的层面保证正确性。
  2. 针对单个应用的集成测试,可能需要对依赖的应用进行 HTTP 级别的 mock。
  3. 结合公共测试环境进行完整的集成测试。

基于上面的三个阶段,可以使用以下的方式来解决前面提到的几个问题。

  1. 使用各个语言相应的测试工具(比如 JUnit)来进行单元测试。
  2. 使用 moco 等 HTTP Mock 工具来解决本地隔离验证的问题,完成单个应用的集成测试。
  3. 使用 kt-connect 和 virtual-environment 等工具来解决云原生基础设施下,本地和测试环境的互相连通性问题,及 http 请求链路的染色和路由。
  4. 使用 ngrok 等工具解决外部依赖调用本地应用的问题。
  5. 使用“主干稳定环境”作为公共测试环境,提高其稳定性。
  6. 使用中间件的染色隔离能力保证 http 请求之外的其它链路(比如消息)的染色和路由。

其中第 1、4 是成熟的技术,这里不再赘述。第 5、6 点会在后面的测试环境相关的章节中我们详细讲解。本文主要就第 2、3 点展开讲解。

单应用的集成测试方案

比如对应用 D 而言,测试范围如下图的橙色框所示:

image.png

应用本身的持久化等依赖使用真实的(一般使用本地 DB),但外部应用(应用 E、F)使用基于 HTTP协议的测试替身。这样就可以保证所有的依赖都是稳定的。并且也可以很方便的修改测试替身的行为,以进行特定场景的测试。

应用 D 依赖了两个应用:

  1. org-service(应用 F):提供查询组织信息等能力
  2. user-service(应用 E):提供查询用户信息等能力

这两个应用的访问地址配置在应用 D 的配置项中:

...
org-service-host: org-service
user-service-host: user-service
...

我们使用 docker compose + moco 的方案来讲解如何使用本地测试替身。

首先创建如下的目录结构:

├── 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 是一种实现单应用集成测试的方式,你可以根据项目的具体情况选择合适的工具和方案。

本地和公共测试环境的互访及链路隔离

完成单应用的集成测试之后,可以获得单个应用级别的质量信心,但更大范围的验证还是需要和真实的依赖集成在一起进行。

image.png

如上图所示,为了能够在本地按需启动应用(A 和 D),并复用测试环境的其他应用(C),就需要解决两个问题:

  1. 本地如何调用到公共测试环境的应用,即 A 如何调用到 C
  2. 公共测试环境如何调用到本地,即 C 如何调用到本地的 D

关于第一点,如果本地环境和测试环境的网络是直接可达的,则直接修改本地应用 A 的配置项即可。如果你使用了云原生的基础设施,那么就需要类似云效 kt-connect 之类的工具来进行打通,这里不再展开,有需求要的可以参看 kt-connect 的 connect 部分。

关于第二点,需要解决三个问题:

  1. 从测试环境的 A 发起的调用链,应该最终访问到测试环境的 D,而从本地环境的 A 发起的调用链,应该最终访问到本地环境的 D,互不影响。为了能够对这两种调用进行区分,需要对调用链进行“染色”,这里采用的染色的方式是在请求中加入一个额外的 header。
  2. 根据这个染色的标志,即“染色标”,进行路由。
  3. 一个调用链会贯穿多个应用,要保证在调用到不同的应用时,染色标要能够自动的传递下去。

关于第一点和第二点,在阿里巴巴内部有一套完整的方案进行染色和路由,这套方案不仅仅适用于 HTTP 链路,也适用于 RPC,异步消息等。而在开源领域,也有基于云原生基础设施的 kt-connect 可以用,使用kt-connect 的 mesh 功能就可以针对特定染色规则的调用链进行路由。

image.png

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 方案加上全链路染色标的方案,就可以轻松的在本地按需启动应用,并进行开发调测。

总结

  1. 使用单元测试、单应用集成测试、端到端集成测试结合的方式进行本地调测,提高获得反馈的效率。
  2. 本地按需启动应用进行端到端集成测试的关键技术是:全链路染色和路由。在不同的基础设施下可以有不同的实现方式。

免费下载《阿里巴巴DevOps实践指南》

阿里巴巴合伙人和业界多位大佬力荐、何勉、陈鑫等17位阿里资深技术专家联袂出品、阿里十年DevOps经验沉淀总结、阿里巴巴DevOps落地实践一本通。

前往:https://developer.aliyun.com/topic/devops,下载完整版电子书。

image.png

相关文章
|
2月前
|
监控 安全 Devops
DevOps实践中,如何平衡开发速度和安全审核的效率
DevOps实践中,如何平衡开发速度和安全审核的效率
|
17天前
|
监控 安全 Devops
DevOps实践中,如何平衡开发速度和安全审核的效率?
DevOps实践中,如何平衡开发速度和安全审核的效率?
|
5月前
|
监控 Devops 测试技术
利用DevOps提升开发效率:技术实践与策略
【7月更文挑战第4天】DevOps通过自动化、CI/CD、协作与沟通等手段,显著提升了软件开发的效率和质量。随着云计算、容器化、自动化测试等技术的不断发展,DevOps的实践将更加深入和广泛。未来,更多的企业将采用DevOps文化,构建高效、灵活、可靠的软件开发和运维体系,以应对快速变化的市场需求。 总之,利用DevOps提升开发效率是软件开发领域的重要趋势。通过实施上述实践策略,企业可以加速产品迭代,提高市场竞争力,实现可持续发展。
|
7月前
|
Ubuntu 安全 Docker
【DevOps】Docker 最佳实践指南(绝对干货)
祝您的 Docker 之旅一切顺利!
210 4
|
4月前
|
运维 Devops 持续交付
自动化运维之路:从脚本到DevOps探索后端开发:从基础到高级实践
【8月更文挑战第28天】在数字化时代的浪潮中,企业对于IT运维的要求越来越高。从最初的手动执行脚本,到如今的自动化运维和DevOps实践,本文将带你领略运维的演变之旅。我们将探索如何通过编写简单的自动化脚本来提升效率,进而介绍DevOps文化的兴起及其对现代运维的影响。文章将为你揭示,通过持续集成、持续部署和微服务架构的实践,如何构建一个高效、可靠的运维体系。准备好让你的运维工作变得更加智能化和自动化了吗?让我们一起踏上这段旅程。 【8月更文挑战第28天】 本文旨在为初学者和有一定经验的开发者提供一个深入浅出的后端开发之旅。我们将一起探索后端开发的多个方面,包括语言选择、框架应用、数据库设计
|
4月前
|
敏捷开发 运维 Devops
DevOps文化:打破开发与运维之间的壁垒
【8月更文挑战第14天】DevOps文化是现代软件开发和运维的重要趋势之一。通过打破开发与运维之间的壁垒,实现自动化、持续集成/持续部署以及紧密协作等关键实践,可以显著提高软件交付的质量和效率。对于任何希望在数字化时代保持竞争力的企业来说,拥抱DevOps文化无疑是一个明智的选择。
|
4月前
|
前端开发 Java UED
JSF遇上Material Design:一场视觉革命,如何让传统Java Web应用焕发新生?
【8月更文挑战第31天】在当前的Web开发领域,用户体验和界面美观性至关重要。Google推出的Material Design凭借其独特的动画、鲜艳的颜色和简洁的布局广受好评。将其应用于JavaServer Faces(JSF)项目,能显著提升应用的现代感和用户交互体验。本文介绍如何通过PrimeFaces等组件库在JSF应用中实现Material Design风格,包括添加依赖、使用组件及响应式布局等步骤,为用户提供美观且功能丰富的界面。
49 0
|
4月前
|
前端开发 Devops 持续交付
【前端自动化新高度】Angular与Azure DevOps完美结合:从零构建持续集成与持续部署的全自动流水线,提升开发效率与软件交付质量!
【8月更文挑战第31天】Angular作为领先的前端框架,以强大功能和灵活性深受开发者喜爱。Azure DevOps提供一站式DevOps服务,涵盖源码管理、持续集成(CI)及持续部署(CD)。本文将指导你如何在Azure DevOps中搭建Angular项目的CI/CD流程,并通过具体示例代码展示整个过程。首先,我们将创建一个Angular项目并初始化Git仓库;然后,在Azure DevOps中设置CI流水线,定义YAML文件以自动化构建和部署流程。最终实现每次提交代码后自动构建并部署至Azure Web App,极大提升了开发效率和软件交付速度,使团队更专注于创新。
38 0
|
4月前
|
运维 Devops 数据库
太卷了!DevOps,就是开发要把运维卷跑了?
太卷了!DevOps,就是开发要把运维卷跑了?
113 0
|
6月前
|
监控 Devops 测试技术
告别繁琐流程,云效DevOps让开发更轻松!
【6月更文挑战第11天】云效DevOps是一款集成代码托管、自动化构建、持续集成/部署、测试管理和监控告警的云原生研发协作平台,旨在提高软件开发效率和质量。它提供代码版本控制、协同开发、自动化测试及灰度发布等功能,打破传统开发流程壁垒,实现开发、测试、运维的无缝协作。通过自动化构建和YAML配置,开发者能轻松实现代码编译、打包和部署,确保快速、安全的线上服务。云效DevOps助力开发者更专注业务逻辑,提升软件竞争力。
51 2