您可以使用宽松模式的流量泳道实现应用版本隔离,基于Baggage请求头透传引流请求头,并将流量路由到不同泳道。泳道中服务相互调用时,若目标服务不存在当前泳道则转发至基线泳道,保障链路完整性,简化流量管理。
功能介绍
Baggage是OpenTelemetry推出的一种标准化机制,旨在实现分布式系统调用链路中跨进程传递上下文信息。它通过在HTTP头部增加名为“Baggage”的字段实现,字段值为键值对格式,可传递租户ID、追踪ID、安全凭证等上下文数据,支持链路追踪、日志关联等功能而无需修改代码。例如:
baggage: userId=alice,serverNode=DF%2028,isProduction=false
本示例使用三个服务(mocka、mockb、mockc)创建三条泳道(s1、s2、s3)模拟调用链路。基线泳道s1包含全部服务,s2仅包含mocka和mockc,s3只有mockb。首先利用OpenTelemetry自动插装为服务启用Baggage能力,再创建三条宽松模式泳道,通过流量权重策略进行流量引导。
前提条件
已添加集群到ASM实例。具体操作,请参见添加集群到ASM实例。
已创建名称为ingressgateway的ASM网关。具体操作,请参见创建入口网关。
已创建名称为ingressgateway且命名空间为istio-system的网关规则。具体操作,请参见管理网关规则。
步骤一:配置服务透传Baggage上下文
本节主要展示如何通过OpenTelemetry Operator自动插装的方法,为Kubernetes集群中的服务添加Baggage透传能力。
部署OpenTelemetry Operator。
通过kubectl连接到ASM实例添加的Kubernetes集群。执行以下命令,创建opentelemetry-operator-system命名空间。
kubectl create namespace opentelemetry-operator-system
执行以下命令,使用Helm在opentelemetry-operator-system命名空间下安装OpenTelemetry Operator。(关于Helm安装步骤,请参见安装Helm)
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts helm install \ --namespace=opentelemetry-operator-system \ --version=0.46.0 \ --set admissionWebhooks.certManager.enabled=false \ --set admissionWebhooks.certManager.autoGenerateCert=true \ --set manager.image.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/opentelemetry-operator" \ --set manager.image.tag="0.92.1" \ --set kubeRBACProxy.image.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/kube-rbac-proxy" \ --set kubeRBACProxy.image.tag="v0.13.1" \ --set manager.collectorImage.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/opentelemetry-collector" \ --set manager.collectorImage.tag="0.97.0" \ --set manager.opampBridgeImage.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/operator-opamp-bridge" \ --set manager.opampBridgeImage.tag="0.97.0" \ --set manager.targetAllocatorImage.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/target-allocator" \ --set manager.targetAllocatorImage.tag="0.97.0" \ --set manager.autoInstrumentationImage.java.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-java" \ --set manager.autoInstrumentationImage.java.tag="1.32.1" \ --set manager.autoInstrumentationImage.nodejs.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-nodejs" \ --set manager.autoInstrumentationImage.nodejs.tag="0.49.1" \ --set manager.autoInstrumentationImage.python.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-python" \ --set manager.autoInstrumentationImage.python.tag="0.44b0" \ --set manager.autoInstrumentationImage.dotnet.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-dotnet" \ --set manager.autoInstrumentationImage.dotnet.tag="1.2.0" \ --set manager.autoInstrumentationImage.go.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/opentelemetry-go-instrumentation" \ --set manager.autoInstrumentationImage.go.tag="v0.10.1.alpha-2-aliyun" \ opentelemetry-operator open-telemetry/opentelemetry-operator
执行以下命令,检查opentelemetry-operator是否正常运行。
kubectl get pod -n opentelemetry-operator-system
预期输出:
NAME READY STATUS RESTARTS AGE opentelemetry-operator-854fb558b5-pvllj 2/2 Running 0 1m
配置自动插装(auto-instrumentation)。
使用以下内容,创建instrumentation.yaml文件。
apiVersion: opentelemetry.io/v1alpha1 kind: Instrumentation metadata: name: demo-instrumentation spec: propagators: - baggage sampler: type: parentbased_traceidratio argument: "1"
执行以下命令,在default命名空间下声明自动插装。
kubectl apply -f instrumentation.yaml
说明对于OpenTelemetry框架来说,其最佳实践还包括部署OpenTelemetry Collector以收集可观测数据。由于本文主要演示OpenTelemetry自动插装实现的Baggage链路透传,因此没有包含部署OpenTelemetry Collector的步骤。有关服务网格ASM如何通过OpenTelemetry上报链路追踪数据,请参考见将链路追踪数据采集到阿里云可观测链路OpenTelemetry版。
步骤二:部署示例服务
为default命名空间启用Sidecar网格代理自动注入。具体操作,请参见管理全局命名空间。
说明关于自动注入的更多信息,请参见配置Sidecar注入策略。
使用以下内容,创建mock.yaml文件。
对于每个实例服务Pod,都加入了
instrumentation.opentelemetry.io/inject-java: "true"
和instrumentation.opentelemetry.io/container-names: "default"
两个注解,以声明该实例服务使用Java语言实现,并要求OpenTelemetry Operator对名称为default
的容器进行自动插装。执行以下命令,部署实例服务。
kubectl apply -f mock.yaml
基于OpenTelemetry自动插装机制,部署的服务Pod将自动具有在调用链路中传递Baggage的能力。
步骤三:创建泳道组和对应泳道
创建泳道组。
登录ASM控制台,在左侧导航栏,选择 。
在网格管理页面,单击目标实例名称,然后在左侧导航栏,选择 。
在流量泳道页面,单击创建泳道组,在创建泳道组面板中,配置相关信息,然后单击确定。
配置项
说明
泳道组名称
本示例配置为test。
入口网关
选择ingressgateway。
泳道模式
选择宽松模式。
调用链路上下文透传方式
选择透传baggage,并在下方引流请求头中填写x-asm-prefer-tag
泳道服务
选择目标Kubernetes集群和default命名空间,在下方列表中选中mocka、mockb和mockc服务,单击
图标,将目标服务添加到已选择区域。
创建s1、s2、s3泳道,并分别绑定v1、v2、v3版本。
在流量泳道页面的流量规则定义区域,单击创建泳道。
在创建泳道对话框,配置相关信息,然后单击确定。
配置项
说明
泳道名称
三条泳道分别配置为s1、s2、s3。
配置服务标签
标签名称:配置为ASM_TRAFFIC_TAG。
标签值:三条泳道分别配置为v1、v2和v3。
添加服务
s1泳道:选择mocka(default)、mockb(default)、mockc(default)。
s2泳道:选择mocka(default)、mockc(default)。
s3泳道:选择mockb(default)。
创建s1泳道的示例图如下。
三个泳道创建完成后,示例效果如下。
默认情况下,您在泳道组中创建的第一个泳道将被设定为基线泳道。您也可以修改基线泳道,当流量发往其它泳道中不存在的服务时,通过回退机制将请求转发至基线泳道。关于修改基线泳道的具体操作,请参见在宽松模式中修改基线泳道。
您可以在控制台左侧导航栏,选择流量管理中心 > 目标规则或虚拟服务查看泳道组中的每个服务的泳道规则自动生成的目标规则DestinationRule和虚拟服务VirtualService。例如,针对mocka服务会自动创建如下DestinationRule和VirtualService。
创建基于权重的统一引流规则。
在流量泳道页面的流量规则定义区域,单击引流策略中的基于权重引流。
在设定统一引流规则对话框中,配置相关信息,然后单击确定。以下以泳道服务对应入口API为/mock为例,为三条泳道配置统一的引流规则。
配置项
说明
域名
配置为*。
匹配请求的URI
配置匹配方式为前缀,匹配内容为/。
设定统一引流规则的示例图如下:
设定三条泳道的引流权重,引流权重确定了流量向每条泳道发送的比例。
在流量泳道页面的流量规则定义区域,在每条泳道的引流权重列,单击数字右侧的
按钮,在编辑引流权重对话框,配置相关信息,然后单击确定。
配置项
说明
入口服务
三条泳道都配置为mocka.default.svc.cluster.local。
权重数值
对于s1泳道,配置为60。
对于s2泳道,配置为20。
对于s3泳道,配置为20。
编辑流量权重的示例图如下。
步骤四:验证全链路灰度功能是否生效
获取ASM网关的公网IP。具体操作,请参见步骤二:获取ASM网关地址。
执行以下命令,设置环境变量。xxx.xxx.xxx.xxx为上一步获取的IP。
export ASM_GATEWAY_IP=xxx.xxx.xxx.xxx
验证全链路灰度功能是否生效。
执行以下命令,查看三条泳道的访问效果。
for i in {1..100}; do curl http://${ASM_GATEWAY_IP}/ ; echo ''; sleep 1; done;
预期输出:
-> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v3, ip: 192.168.0.2)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v2, ip: 192.168.0.184)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v2, ip: 192.168.0.189) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190) -> mocka(version: v1, ip: 192.168.0.193)-> mockb(version: v1, ip: 192.168.0.1)-> mockc(version: v1, ip: 192.168.0.190)
由预期输出得到,流量将以约6:2:2的比例发送到s1、s2、s3泳道,并由s1作为基线泳道,当调用链路中不存在某个服务的特定版本时,将会调用s1泳道中的对应服务。
- 本页导读