微服务敏捷开发不简单
安得环境千万套,大庇开发小哥俱欢笑
微服务给大家带来了敏捷开发的特性,基于敏捷开发带来的便利,让我们可以在同一个时间内多个迭代/feature 并行开发。但微服务架构本身也给开发环境带来了一定的复杂性:每个 feature 的修改点都可能会被分散在多个应用中,需要多个应用互相配合才能完成整体的逻辑。这些应用既需要互相配合好,又不能让他们互相影响,所以敏捷开发有时候也不是那么容易。
相信实践过微服务敏捷开发的同学都曾经遇到过以下情况:
a. 开发接口时,应用无法独立地联调测试,需要依赖于下游的返回,所以一般都需要一个完整的开发环境,这个环境需要包含所有的其他应用。
b. A 同学辛辛苦苦,终于开发好了一个接口,但是部署到开发环境后,发现返回值一直是错的,就是不符合预期,百思不得其解。最终根据日志、arthas 层层跟踪下去,发现原来是另一个同事更新了下游应用的代码,导致原有逻辑发生了变更。
c. A 同学准备开始联调测试了,这时候他要找到开发 B 和 C 吼一嗓子确认:“我要开始测试了哈兄弟们,你们都别动环境,不要重启和 debug 哈”。B 同学 和 C 同学一脸懵逼:“我自己这还有个逻辑没理清楚呢,刚改完代码准备测一发,你这一测试联调我就不能动环境了,我这功能得等到什么时候才能开发好”。
d. 排查问题好麻烦啊,要不直接 debug 一下吧,这 IDEA 远程 debug 刚连上去呢,立马就传来了同事的声音:“谁 XX 又在瞎动环境啊,怎么刚刚还能跑的接口现在就出错了”。
以上这些问题显然会影响项目的进度,非常容易造成项目延期。对于此刻的开发小哥哥而言,拥有一套属于自己的独立环境,带来的幸福感也许比有一套属于自己的小房子还大。
流量闭环是微服务敏捷开发的基础
上文中提到的问题,其实都是因为没有在开发环境中,精准地控制流量在 feature 环境内流转。
为什么精准地控制流量如此重要?举个最简单的微服务架构图来说明,这里假设应用的调用链路为 A ---> B ---> C ---> D ,现在同时开发两个 feature, feature1 和 feature2 。feature1 需要修改 A 和 C 的代码, feature2 需要修改 B、C 和 D 的代码。
为了方便表述,我们用 A、B、C、D 来代指 A、B、C、D 的线上稳定版本,也叫做基线版本;A1、C1 来代指 feature1 环境中的 A 和 C ;B2、C2、D2 来来代指 feature2 环境中 B、C、D。
那么开发测试 feature1 的同学会要求他的请求,准确地在 A1 ---> B ---> C1 ---> D 中流转。为什么一定要这样,我们来简单分析一下:
a. 如果流量走到 A 或者 C 的基线环境,因为他们都没有包含 feature1 相关的代码,所以肯定是无法正常测试和联调 feature1 对应的功能。
b. 如果流量走到 B2、D2 环境,大多数情况下是可以正常工作的,因为正常情况下 B2 和 D2 中的修改是不会影响 feature1 的。但是因为 feature1 和 feature2 可能是由不同的同学开发的,或者有不同的开发排期和节奏,他们有自己的开发、重启、debug 节奏,所以大概率还是会出现上文中提到的场景。
综上所述,让流量在 feature 环境内流转非常重要,是微服务敏捷开发的基础。
如何准确地让请求在 feature 环境内流转呢?最简单的办法是每个迭代/Feature 的都享有一套独立的完整环境,这套独立的环境包含了整个微服务应用集所有的应用,包含注册中心和接入层,这样就能确保流量在 feature 环境里闭环,不用担心应用之间互相影响。
这个解决方案虽然简单,但是问题也很显而易见,成本比较大。我们假设微服务应用有10 个,每个应用只部署一台,以 java 为例,部署一个 java 应用按 2C4G 的 共享标准型 ECS 进行计算,维护一套环境一年的成本是 10 × 140 × 12 = 16800 元,如果同时有 4 套环境,即只支持两个迭代并行开发,每个迭代只有 2 个 feature,这样一年的成本就是 67200 元,而且我们可以发现,这里面计算公式使用的是乘法,当应用增加和环境增加时,成本的增加是成倍的。
注意,这里只是单纯地计算了应用使用的 ECS 的成本,其他周边的配套设施我们还没有计算,因为我们的开发、联调、测试是需要确保端到端的全流程都是足够顺利的,那这里就还会涉及到 域名/SLB/网关/注册中心这些资源,这些资源一般比较固定,不会需要进行大的修改,但是在多套环境的方案下这些资源也需要维护多套,成本还会进一步上升。
那么,有没有一个比较优雅地方式,既能享受到微服务架构带来的敏捷开发的便利,又不会给日常开发环境的搭建带来很大的成本呢?基于 MSE(微服务引擎 MSE,以下简称 MSE)标签路由功能使用开发环境隔离方案是您的不二之选。
如何低成本玩转敏捷开发
什么是 MSE 开发环境隔离,简单地说就是将 feature 环境的隔离方式从简单的物理隔离转为逻辑隔离。借助于 MSE 提供的逻辑隔离,您只需要维护一套完整的基线环境,在增加 feature 环境时,只需要单独部署这个 feature 所涉及到改动的应用即可,而不需要在每个 feature 环境都部署整套的微服务应用及其配套设施。
我们称这唯一的一套完整的环境为基线环境。基线环境包含了所有微服务应用,也包含了服务注册中心、域名、SLB、网关 等其他设施,而 feature 环境中只包含了这个 feature 中需要修改的应用。这样维护 n 套 feature 环境的成本,就变成了加法,而不是原来的乘法,由 n × m 变成了 n + m。差不多相当于零成本增加 feature 环境,这样我们就可以放心地扩容出多套 feature 环境,每个开发小哥哥都可以轻松拥有属于自己的独立环境,尽情地享受微服务敏捷开发。
从上图中我们可以看到,feature1 对应的流量,在发现 feature1 中存在 A1 应用时,一定会去往 A1 节点,A1在调用B的时候发现 feature1 环境中不存在 B1 ,则会将请求发到 基线版本的 B 中;B在调用C时,发现 feature1 环境存在 C1 应用,又会返回到 feature1 环境中,依次类推,确保了流量会在 feature1 环境中闭环。
注意,在这个过程中,您不需要修改任何代码和配置,直接接入 MSE 微服务治理即可使用,不会给您增加任何开发成本。
如何使用 MSE 开发环境隔离
场景分析
在描述如何使用开发环境隔离之前,我们先分析一下目前常用的开发环境具体的场景,这里选了三种典型的场景。
场景一
所有的开发环境都在本地,或者说公司内自建的 IDC ,这类场景下开发环境的所有应用都部署在本地的机房。
场景二
公司通过专线打通了办公网与阿里云上的 VPC ,两边的网络实现了互联互通。
开发、测试环境主要部署在阿里云,但是正在开发的工程,有一部分是跑在本地办公网的,甚至是直接跑在开发同事的个人电脑上。
场景三
公司内并没有专线来打通了办公网与阿里云上的 VPC。
但是希望能让本地启动的应用,连接上阿里云上的开发测试集群,并且实现精准的流量隔离。
首先说一下结论,这上面的三个场景,目前都是可以完整的支持的。其中场景一和场景二都不涉及到网络打通这部分的内容,其实只需要根据基本的 MSE 接入方式和 MSE 打标方式即可直接使用起来。
场景三比场景一和场景二多了一个网络打通的功能,所以会多一个端云互联的步骤,这里面会要求注册中心需要使用 MSE 提供的 Nacos。
在这个最佳实践中,我们会以场景三来实操。如果您的使用场景不是场景三,那么您可以忽略,同时也不需要关注端云互联相关的操作。
一 、开通 MSE 微服务治理专业版
登录MSE治理中心控制台,如果您尚未开通 MSE 微服务治理,请根据提示开通专业版。如果您已经开通了MSE 微服务治理基础版,请根据概览页中右侧的提示,升级到 专业版。
二 、 完成基线环境接入
1. 接入 MSE 治理
首先,您需要将基线环境的所有应用接入到 MSE 中,接入方式与您开发环境中应用部署环境有关。这里我们使用的是阿里云容器服务 ACK。更多接入场景请参考 MSE 帮助文档 MSE 微服务治理快速入门。
- 在ACK中安装MSE治理中心组件
a. 登录容器服务控制台。
b. 在左侧导航栏单击市场 > 应用目录。
c. 在应用目录页面搜索并单击ack-mse-pilot
。
d. 在ack-mse-pilot页面右侧集群列表中选择集群,然后单击创建。
安装MSE微服务治理组件大约需要2分钟,请耐心等待。
创建成功后,会自动跳转到目标集群的发布页面,检查安装结果。如果出现以下页面,展示相关资源,则说明安装成功。
- 为ACK命名空间中的应用开启MSE微服务治理
a. 登录MSE治理中心控制台。
b. 在左侧导航栏选择微服务治理中心 > K8s集群列表。
c. 在K8s集群列表页面搜索框列表中选择集群名称或集群ID,然后输入相应的关键字,单击 图标。
d. 单击目标集群操作列的管理。
e. 在集群详情页面命名空间列表区域,单击目标命名空间操作列下的开启微服务治理。(如果您的基线环境部署在 default 这个 namespace 中,则目标命名空间为 default)
f. 在开启微服务治理对话框中单击确认
2. 部署基线应用
首先,我们将分别部署 spring-cloud-zuul、spring-cloud-a、spring-cloud-b、spring-cloud-c 这四个业务应用,模拟出一个真实的调用链路。
Demo 应用的结构图下图,应用之间的调用,既包含了 Spring Cloud 的调用,也包含了 Dubbo 的调用,覆盖了当前市面上最常用的两种微服务框架。这些应用都是最简单的 Spring Cloud 、 Dubbo 的标准用法,您也可以直接在 https://github.com/aliyun/ali... 项目上查看源码。
您可以使用 kubectl 或者直接使用 ACK 控制台来部署应用。部署所使用的 yaml 文件如下。
注意,上文中提到,使用端云互联的前提是注册中心使用的是 MSE 中的 Nacos,所以请您在部署之前修改 yaml 文件中的 spring.cloud.nacos.discovery.server-addr 和 dubbo.registry.address 为您自己购买的 MSE Nacos 地址,否则应用是无法正常运行的。若您使用的是 MSE Nacos 域名为公网域名,还需要确保开启了白名单。
# 部署业务应用
# 部署业务应用
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-zuul
spec:
selector:
matchLabels:
app: spring-cloud-zuul
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-zuul
labels:
app: spring-cloud-zuul
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
- name: spring.cloud.nacos.discovery.server-addr
value: 'mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-zuul:1.0.0
imagePullPolicy: Always
name: spring-cloud-zuul
ports:
- containerPort: 20000
---
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
service.beta.kubernetes.io/alicloud-loadbalancer-address-type: internet
name: zuul-slb
spec:
ports:
- port: 80
protocol: TCP
targetPort: 20000
selector:
app: spring-cloud-zuul
type: LoadBalancer
status:
loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-a
spec:
selector:
matchLabels:
app: spring-cloud-a
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-a
labels:
app: spring-cloud-a
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
- name: spring.cloud.nacos.discovery.server-addr
value: 'mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
- name: dubbo.registry.address
value: 'nacos://mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-a:1.0.0
imagePullPolicy: Always
name: spring-cloud-a
ports:
- containerPort: 20001
livenessProbe:
tcpSocket:
port: 20001
initialDelaySeconds: 10
periodSeconds: 30
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-b
spec:
selector:
matchLabels:
app: spring-cloud-b
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-b
labels:
app: spring-cloud-b
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
- name: spring.cloud.nacos.discovery.server-addr
value: 'mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
- name: dubbo.registry.address
value: 'nacos://mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-b:1.0.0
imagePullPolicy: Always
name: spring-cloud-b
ports:
- containerPort: 20002
livenessProbe:
tcpSocket:
port: 20002
initialDelaySeconds: 10
periodSeconds: 30
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-cloud-c
spec:
selector:
matchLabels:
app: spring-cloud-c
template:
metadata:
annotations:
msePilotCreateAppName: spring-cloud-c
labels:
app: spring-cloud-c
spec:
containers:
- env:
- name: JAVA_HOME
value: /usr/lib/jvm/java-1.8-openjdk/jre
- name: spring.cloud.nacos.discovery.server-addr
value: 'mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
- name: dubbo.registry.address
value: 'nacos://mse-xxxxxxx-nacos-ans.mse.aliyuncs.com:8848'
image: registry.cn-shanghai.aliyuncs.com/yizhan/spring-cloud-c:1.0.0
imagePullPolicy: Always
name: spring-cloud-c
ports:
- containerPort: 20003
livenessProbe:
tcpSocket:
port: 20003
initialDelaySeconds: 10
periodSeconds: 30
3. 验证基线环境接入成功
完成上述步骤后,您的基线环境就已经部署好了。您可以在 MSE 控制台中找到对应的 Region 查看应用列表,以及应用详情页的节点情况。
在本地配置好 K8s 集群对应的 kubeconfig 文件,执行命令,结果如下
➜ ~ kubectl get svc,deploy
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 192.168.0.1 443/TCP 7d
service/zuul-slb LoadBalancer 192.168.87.95 47.94.143.53 80:31983/TCP 9m30s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/spring-cloud-a 1/1 1 1 9m30s
deployment.apps/spring-cloud-b 1/1 1 1 9m30s
deployment.apps/spring-cloud-c 1/1 1 1 9m30s
deployment.apps/spring-cloud-zuul 1/1 1 1 9m30s
在这里我们执行一下 curl http://47.94.143.53:80/A/a 发起调用,并查看返回结果
➜ ~ curl http://47.94.143.53:80/A/a
A[10.242.0.90] -> B[10.242.0.91] -> C[10.242.0.152]%
三、 IDEA 启动的应用接入 feature 环境
在这一步中,我们将演示如何在网络没有打通的情况下,将你本机启动的应用接入到 feature 环境。首先您需要将您 K8s 集群的 kubeconfig 文件保存到本机,并进行如下操作。
- 下载源码
本工程所有源码都在 https://github.com/aliyun/ali... 中,将代码 git clone 到本地,并且找到 mse-simple-demo 文件夹中的 A、B、C、gateway 四个应用,就是本次最佳实践所使用的公测。 - 安装 CloudToolkit 插件
安装最新版本的 Cloud Toolkit,安装详情请参考官网 https://www.aliyun.com/produc...。 - 填写阿里云 AK、SK
由于使用端云互联功能的时候,需要访问您 MSE 的资源,所以这里需要您填写您的 AK、SK,并确保此 AK、SK拥有访问 MSE 资源的权限。
点击 IDEA 的 Tools 中找到 Preference ,找到 Alibaba Cloud Toolkit 中 Accounts ,配置 Access Key ID 和 Access Key Secret 信息,并点击保存。 - 配置 MSE 参数
点击 IDEA 的 Tools 中找到 Preference ,找到 Alibaba Cloud Toolkit 中 Microservice 下的 MSE ,点击 开启微服务治理,并安装下图的方式进行配置即可。
对图中的几个参数做一下说明
- LicenseKey
您阿里云账号对应的 MSE 产品的 LicenseKey ,请在https://mse.console.aliyun.co... 中的选择 ECS 集群,在 安装 MSE Agent 章节找到 LicenseKey 的值。
注意:请您做好 LicenseKey 的保密工作。
注意:各个 Region 的 LicenseKey 值可能不一致,请选择对应的 Region,并和基线环境接入的 Region 保持一致。 - App Name
应用在接入 MSE 时所使用的应用名,请根据实际业务情况进行配置,注意这个值需要和本次所启动的应用保持一致。 - Tag
此应用所属的环境 Tag,基线不用填,其他请根据实际业务情况进行填写。如果此应用属于 feature1 环境,请填写 feature1。 - Agent 地址
选择自己应用所在的地域,需要和 LienseKey 所在的地域、以及基线环境接入的地域 都保持一致。 - 开启 RPC 灰度 ✅
支持对 Spring Cloud 和 Dubbo 近5年内的所有版本的流量进行精准控制。默认情况下请开启,除非您明确知道关闭此选项的使用场景,否则请勿关闭此选项。 - 开启标签染色 ✅
推荐开启,开启后经过此应用的流量就只会在对应的 Tag 环境中流转。 - 开启消息灰度 ✅
请根据业务实际情况选择是否开启,目前仅支持 RocketMQ 4.5 及以上版本。更多消息灰度相关的信息,请参考全链路灰度,消息部分。
5.配置端云互联参数
我们已经知道,在网络未打通的时候,联通本机环境和云上 K8s 集群需要使用 端云互联功能,所以这里我们需要配置一下端云互联。
首先需要配置一下代理模式为 K8s,点击 IDEA 的 Tools 中找到 Preference ,找到 Alibaba Cloud Toolkit 中 Microservice 下的 Proxy ,点击 AddProfile 增加一个名称为 k8s 的代理。然后点击右侧的 Add 按钮,选择代理类型 为 Kubernetes,并选择正确的 配置文件地址 和 命名空间。
然后,点击 IDEA 的 Tools 中找到 Preference ,找到 Alibaba Cloud Toolkit 中 Microservice 下的 MicroService ,找到 端云互联 功能打 ✅。选择产品为 微服务引擎 MSE,并选择与上面部署时一致的 Region 、 实例 和 命名空间 ,代理选择刚刚配置好的 k8s,如果您的应用是 Spring Cloud 应用,还必须在 本地 Spring Cloud 服务端口中 配置 Tomcat 的启动端口。
![在这里插入图片描述](/img/bVcXOsQ)
四 启动应用,轻松开始联调和测试
同时为了验证 MSE 微服务治理是否接入成功,我们可以登录 MSE 控制台的应用列表界面进行查看。在控制台中我们也可以看到,我的本机应用已经成功接入到 MSE ,并且成功打上了 feature1 的标签。
- 发起流量调用
假设我们发往网关的请求是 http 请求,希望这个请求再 feature1 中完成闭环,只需要在请求的 header 中添加 x-mse-tag=feature1 即可,流量会自动在 feature1 环境内完成闭环。注意这里的 key 为 x-mse-tag 为固定值,feature1 则需要和环境的标签(即上文中配置的 alicloud.service.tag)保持一致。
如果您的请求来源为 Dubbo,则需要在 RpcContext 中增加 Attachment ,内容也是 x-mse-tag=feature1。
➜ ~ curl http://47.94.143.53:80/A/a
A[10.242.0.90] -> B[10.242.0.91] -> C[10.242.0.152]%
➜ ~ curl http://47.94.143.53:80/A/a
A[10.242.0.90] -> B[10.242.0.91] -> C[10.242.0.152]%
➜ ~ curl -H"x-mse-tag:feature1" http://47.94.143.53:80/A/a
Afeature1[xx.xxx.12.118] -> B[10.242.0.91] -> C[10.242.0.152]%
➜ ~ curl -H"x-mse-tag:feature1" http://47.94.143.53:80/A/a
Afeature1[xx.xxx.12.118] -> B[10.242.0.91] -> C[10.242.0.152]%
➜ ~ curl -H"x-mse-tag:feature1" http://47.94.143.53:80/A/a
Afeature1[xx.xxx.12.118] -> B[10.242.0.91] -> C[10.242.0.152]%
如果您不方便在请求中增加 header 的话,还可以在 MSE 控制台配置规则,比如设置成 name=xiaohong 或 xiaohua 的流量进入到 feature1 环境。如下图所示。
开启流量规则之后,我们继续验证,可以看到,满足条件的请求会被转发到 feature1 环境。
➜ ~ curl http://47.94.143.53:80/A/a
A[10.242.0.90] -> B[10.242.0.91] -> C[10.242.0.152]%
➜ ~ curl http://47.94.143.53:80/A/a\?name\=xiaohong
Afeature1[30.225.12.118] -> B[10.242.0.91] -> C[10.242.0.152]%
➜ ~ curl http://47.94.143.53:80/A/a\?name\=xiaohua
Afeature1[30.225.12.118] -> B[10.242.0.91] -> C[10.242.0.152]%
如果您的网关应用不属于Java体系,则需要在网关层配置规则,目前已经支持MSE云原生网关,Nginx和K8s Ingress等,详细的接入方式,请参见 基于MSE云原生网关实现全链路灰度、基于Ingress-nginx网关实现全链路灰度等文档。