KT-env快速上手

简介: KT-env是阿里开源的一种基于ServiceMesh的微服务环境复用工具,本文主要带你了解KT-env的作用、功能、部署和实战。

概述

KtEnv 是一种基于ServiceMesh的微服务环境复用工具,源于阿里内部的“项目环境”实践。


通过识别Pod上的虚拟环境标签,KtEnv能够自动将测试环境网络动态隔离成多个虚拟隔离域,
同时以简单规则在隔离域间局部复用Pod实例,
从而达到只需很少资源成本即可创建大量不同微服务版本组合的独立测试环境的目的。



在本系列文章中,我们将会详细介绍 KT-env 的原理,使用,实战等等。


Mesh基础知识及请求路由

想要了解 KT-env 相关的功能和使用,首先需要了解一下什么是 mesh 以及请求路由相关的功能。


ServiceMesh 相关介绍


首先,我们来了解一下大家对 ServiceMesh 的定义:


服务网格(Service Mesh)是处理服务间通信的基础设施层。
它负责构成现代云原生应用程序的复杂服务拓扑来可靠地交付请求。
在实践中,Service Mesh 通常以轻量级网络代理阵列的形式实现,
这些代理与应用程序代码部署在一起,对应用程序来说无需感知代理的存在。**


ServiceMesh 通常有如下特点:


  • 它是应用程序间通信的中间层。
  • 轻量级网络代理。
  • 应用程序无感知。
  • 解耦应用程序的重试/超时、监控、追踪和服务发现。


ServiceMesh 仅仅是一种定义和概念,而关于 ServiceMesh 的具体实现,则有多种不同的服务实现方式,例如 Istio, Linkerd。


其中,Istio 是目前最流行 ServiceMesh 服务了,我们也将会继续对 Istio 来进行详细的说明。


Istio 概述


Istio 是 ServiceMesh 的一种实现。


Istio 服务网格从逻辑上分为数据平面和控制平面。


  • 数据平面: 由一组智能代理(Envoy)组成,被部署为 Sidecar。这些代理负责协调和控制微服务之间的所有网络通信。它们还收集和报告所有网格流量的遥测数据。
  • 控制平面: 管理并配置代理来进行流量路由。


下图展示了组成每个平面的不同组件:



Envoy


Istio 使用 Envoy 代理的扩展版本。Envoy 是用 C++ 开发的高性能代理,用于协调服务网格中所有服务的入站和出站流量。
Envoy 代理是唯一与数据平面流量交互的 Istio 组件。


Envoy 代理被部署为服务的 Sidecar,在逻辑上为服务增加了 Envoy 的许多内置特性,例如:


  • 动态服务发现
  • 负载均衡
  • TLS
  • HTTP/2 与 grpc 代理
  • 熔断器
  • 健康检查
  • 基于百分比流量分割的分阶段发布
  • 故障注入
  • 丰富的监控指标


这种 Sidecar 部署允许 Istio 可以执行策略决策,并提取丰富的遥测数据,接着将这些数据发送到监视系统以提供有关整个网格行为的信息。


Sidecar 代理模型还允许您向现有的部署添加 Istio 功能,而不需要重新设计架构或重写代码。


由 Envoy 代理启用的一些 Istio 的功能和任务包括:


  • 流量控制功能:通过丰富的 HTTP、gRPC、WebSocket 和 TCP 流量路由规则来执行细粒度的流量控制。
  • 网络弹性特性:重试设置、故障转移、熔断器和故障注入。
  • 安全性和身份认证特性:执行安全性策略,并强制实行通过配置 API 定义的访问控制和速率限制。
  • 基于 WebAssembly 的可插拔扩展模型,允许通过自定义策略执行和生成网格流量的遥测。


Istiod


Istiod 提供服务发现、配置和证书管理。


Istiod 将控制流量行为的高级路由规则转换为 Envoy 特定的配置,并在运行时将其传播给 Sidecar。
Pilot 提取了特定平台的服务发现机制,并将其综合为一种标准格式,任何符合 Envoy API 的 Sidecar 都可以使用。


Istio 可以支持发现多种环境,如 Kubernetes 或 VM。


您可以使用 Istio 流量管理 API 让 Istiod 重新构造 Envoy 的配置,以便对服务网格中的流量进行更精细的控制。


Istiod 安全通过内置的身份和凭证管理,实现了强大的服务对服务和终端用户认证。
您可以使用 Istio 来升级服务网格中未加密的流量。
使用 Istio,运营商可以基于服务身份而不是相对不稳定的第 3 层或第 4 层网络标识符来执行策略。
此外,您可以使用 Istio 的授权功能控制谁可以访问您的服务。


Istiod 还充当证书授权(CA),并生成证书以允许在数据平面中进行安全的 mTLS 通信。


Istio 请求路由控制


Istio 提供的核心功能之一就是 服务发现


Istio 简单的规则配置和流量路由允许您控制服务之间的流量和 API 调用过程。
Istio 简化了服务级属性(如熔断器、超时和重试)的配置,并且让它轻而易举的执行重要的任务(如 A/B 测试、金丝雀发布和按流量百分比划分的分阶段发布)。


例如,在 Istio 中,有两个非常重要的概念,VirtualServiceDestinationRule


通过合理的 VirtualServiceDestinationRule 的配置,可以实现根据指定 uri, 指定的 headers 信息等等来配置对应的下游地址。


可以参考如下示例: Istio 请求路由


Service 、 VirtualService 、 DestinationRule 关系介绍


Service 是 K8s 中的核心概念之一,它是 K8s 中默认的服务发现机制。
具体来说,K8s 中的 Service 对应于一组指定标签的 Pod 实例,发向该 Service 的请求会被自动转发到对应的 Pod 实例上。


虽然,K8s 中的 Service 已经具备了基本的服务发现的能力,但实际上这种基本的服务发现能力远远无法满足业务对流量治理相关能力,
例如,特定的路由策略、网络重试等功能。


因此,Istio 的出现就是为了进一步提升流量治理的能力,而在 Istio 中,最核心的两个概念就是 VirtualService 和 DestinationRule 了。


其中:


  • VirtualService 的目的是定义一组要在访问 host 时应用的流量路由规则,每个路由规则定义了特定协议流量的匹配规则。如果流量的匹配规则满足的话,则将其发送到配置中定义的目标服务(或其子集/版本)中,此外,还包括了HTTP超时控制、重试、镜像、修改headers等。
  • DestinationRule 的目的定义在路由发生后应用于服务流量的策略。 这些规则指定负载均衡的配置、来自 sidecar 的连接池大小和异常检测设置,以检测和从负载均衡池中驱逐不健康的实例。


VirtualService 故名思义就是虚拟服务,VirtualService 中定义了一系列针对指定服务的流量路由规则。
每个路由规则都是针对特定协议的匹配规则。
如果流量符合这些特征,就会根据规则发送到服务注册表中的目标服务(或者目标服务的子集或版本)。


DestinationRule 是基于已有的 K8s Service 进行 Pod 下的细粒度的分组。
例如,可以将一个 Service 下的 Pod 根据 label 等信息再次分为多个 subset,从而可以在 VirtualService 对流量进行 subset 级别的细粒度分发。


也就是说:VirtualService 和 DestinationRule 都是基于已有的 K8s Service 的条件下进行的功能扩展,必须保证 K8s Service 已经存在。
而在消息具体发送的过程中,envoy 会默认劫持流量并发送给对应的 Pod 实例,而非依赖 kube-proxy 进行请求下发。


示例的 DestinationRule 配置文件如下:


apiVersion: networking.istio.io/v1alpha3

kind: DestinationRule

metadata:

 name: flaskapp

spec:

 host: flaskapp.books.svc.cluster.local

 subsets:

 - name: subset-v1

   labels:

     version: v1

 - name: subset-v2

   labels:

     version: v2


它可以将 books namespace 下的 flaskapp 的 service 下关联的 Pod 进行细粒度的分组,
其中 subset-v1 中包含所有 labels 中 version 为 v1 的 Pod,subset-v2 中包含所有 labels 中 version 为 v2 的 Pod。


VirtualService 的配置文件如下:


apiVersion: networking.istio.io/v1beta1

kind: VirtualService

metadata:

 name: flaskapp-policy

spec:

 hosts:

 -  flaskapp.books.svc.cluster.local

 http:

 - route:

   - destination:

       host: flaskapp.books.svc.cluster.local

       subset: subset-v2

   match:

   - headers:

       end-user:

         exact: jason

 - route:

   - destination:

       host: flaskapp.books.svc.cluster.local

       subset: subset-v1


我们来看一下这个 VirtualService 的配置做了什么事情:
它扩展了 books namespace 下原有 K8s Service flaskapp 的路由策略,当请求的 headers 中包括 end-user 且值为 jason 的话,
则将对应请求发送给 subset-v2 分组的 Pod 实例,否则的话,则将对应请求发送给 subset-v1 分组的 Pod 实例。


KT-env目标及功能

对于微服务的开发者而言,拥有一套干净、独占的完整测试环境无疑能够提高软件研发过程中的功能调试和异常排查效率。


然而在中大型团队里,为每位开发者维护一整套专用测试服务集群,从经济成本和管理成本上考虑都并不现实。为此阿里巴巴的研发团队采用了基于路由隔离的"虚拟环境"方法。


“虚拟环境”的本质是基于服务实例(即Kubernetes的Pod实例)上的“环境标”(在Kubernetes表现为Label)属性,结合约定的路由复用规则,形成一个个开发者视角的专属测试环境。这些虚拟的专属环境从集群管理者的视角来看,称为“隔离域”,它具有如下特点:


  • “隔离域”是由路由规则形成的虚拟边界
  • 每个“环境标”都会形成一个独立的“隔离域”
  • “隔离域”之间可以存在部分或完全重合
  • “隔离域”的成员会随集群中服务实例所带“环境标”动态变化


路由规则


虚拟环境的路由规则很简单:**如果请求是来自一个带有环境标的服务实例,
它会优先寻找跟它具有相同环境标的实例,如果没有,则会寻找它上一级的环境标,直至到达顶级的默认服务实例**。


假设A、B、C、D四个微服务组成了一个完整的测试环境。


此时若为集群中的服务实例全部赋予环境标dev,就形成了一个隔离域,如下图蓝色部分所示。结合虚拟环境的实践,我们称这个隔离域为“公共基础环境”(也称默认环境)。



当进行特定项目开发的时候,开发者不需要重新部署整套微服务系统,而是单独部署需要修改的部分服务(如服务C和服务D),为它们赋予一个子级环境标,比如dev.proj,然后加入到测试环境中。


根据路由规则,当请求来自带有dev.proj环境标的实例C,需要调用服务B,但是发现没有带环境标dev.proj的服务B实例,于是会寻找带有上一级环境标dev的服务B实例。同理当链路到达服务C时,会由dev环境标的实例响应,而由于服务D存在dev.proj环境标的实例,所以这部分实例会接管到达服务D的请求。


因此dev.proj环境标所形成的隔离域边界下图红色区域所示,这便是dev.proj虚拟环境的服务实例集合。



不难看出,dev.proj虚拟环境复用了部分公共基础环境的资源(服务A和服务B)。这样做的好处是,第一不会占用大量的计算资源;第二,不会影响公共基础环境的稳定性。


开发者还可以将本地开发机加入虚拟环境。比如小明在本地启动了一个服务C的实例,他给这个服务实例打上环境标dev.proj.local,基于前面介绍的路由规则,带环境标的服务发出的请求会优先寻找带相同环境标的服务实例,如果找不到则会逐级寻找带有上一级环境标的实例。于是环境标为dev.proj.local的服务C、环境标为dev.proj服务D和公共基础环境dev中的服务A、服务B就组成了一个新的的虚拟环境,如下图红色部分所示。



这时小明的同事在本地启动了一个服务A,如果他没有对这个服务打环境标,则他的所有调用请求会默认使用“公共基础环境”进行测试。因此小明在自己的虚拟环境中的任何调试都不会影响到他的同事,反之亦然。如下图所示。



若小明的同事加入了小明所在的项目,他们之间需要进行“联调”。这时,他只需将本地的服务打上一个和小明相同的环境标即可,如下图红色部分所示。



总结而言,虚拟环境是通过在服务实例(以及从这些实例发出的请求)上携带约定标签,将个别需要调试或测试的特定版本服务实例与其他公共服务实例组成临时虚拟集群的一种环境管理实践。


KT-env部署

前置条件


KT-env 部署之前,首先需要有一个 Kubernetes 集群并且已经部署了 Istio 。
同时,本地还需要一个配置好的 kubectl 工具。


下载部署包


首先,我们需要从 发布页面 下载最新的部署文件包,并解压。


wget https://github.com/alibaba/virtual-environment/releases/download/v0.5.4/kt-virtual-environment-v0.5.4.zip

unzip kt-virtual-environment-v0.5.4.zip

cd v0.5.4/


安装相关组件


创建 CRD


在 KT-env 中,定义了一种新的资源对象: VirtualEnvironment 。


下面,我们第一步就来创建对应的 CRD 资源对象:


kubectl apply -f global/ktenv_crd.yaml


CRD组件会在Kubernetes集群内新增一种名为VirtualEnvironment的资源类型,在下一步我们将会用到它。可通过以下命令验证其安装状态:


kubectl get crd virtualenvironments.env.alibaba.com


若输出类似以下信息,则表明KtEnv的CRD组件已经正确部署:


NAME                                  CREATED AT

virtualenvironments.env.alibaba.com   2020-04-21T13:20:35Z


部署 Webhook 组件


Webhook组件用于将Pod的虚拟环境标写入到其Sidecar容器的运行时环境变量内。


kubectl apply -f global/ktenv_webhook.yaml


Webhook组件默认被部署到名为kt-virtual-environment的Namespace中,包含一个Service和一个Deployment对象,以及它们创建的子资源对象,可用以下命令查看:


kubectl -n kt-virtual-environment get all


若输出类似以下信息,则表明KtEnv的Webhook组件已经部署且正常运行:


NAME                                  READY   STATUS    RESTARTS   AGE

pod/webhook-server-5dd55c79b5-rf6dl   1/1     Running   0          86s


NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE

service/webhook-server   ClusterIP   172.21.0.254   <none>        443/TCP   109s


NAME                             READY   UP-TO-DATE   AVAILABLE   AGE

deployment.apps/webhook-server   1/1     1            1           109s


NAME                                        DESIRED   CURRENT   READY   AGE

replicaset.apps/webhook-server-5dd55c79b5   1         1         1       86s


创建 Role 和 ServiceAccount


如果 K8s 集群中,已经开启了 RBAC 权限控制,那么,为了保证我们的 Operator 可以正常工作,还需要部署相应的Role和ServiceAccount。


kubectl apply -n default -f ktenv_service_account.yaml


部署 KT-env Operator


Operator是由CRD组件定义的虚拟环境管理器实例,需要在每个使用虚拟环境的Namespace里单独部署


以使用default Namespace为例,通过以下命令完成部署:


kubectl apply -n default -f ktenv_operator.yaml


此外,为了让Webhook组件对目标Namespace起作用,还应该为其添加值为enabled的environment-tag-injection标签。


kubectl label namespace default environment-tag-injection=enabled


现在,Kubernetes集群就已经具备使用虚拟环境能力了。


KT-env实战

前置条件


KT-env 部署之前,首先需要有一个 Kubernetes 集群并且已经部署了 Istio 。
同时,本地还需要一个配置好的 kubectl 工具。


下载部署包


首先,我们需要从 发布页面 下载最新的部署文件包,并解压。


wget https://github.com/alibaba/virtual-environment/releases/download/v0.5.4/kt-virtual-environment-v0.5.4.zip

unzip kt-virtual-environment-v0.5.4.zip

cd v0.5.4/


安装相关组件


创建 CRD


在 KT-env 中,定义了一种新的资源对象: VirtualEnvironment 。


下面,我们第一步就来创建对应的 CRD 资源对象:


kubectl apply -f global/ktenv_crd.yaml


CRD组件会在Kubernetes集群内新增一种名为VirtualEnvironment的资源类型,在下一步我们将会用到它。可通过以下命令验证其安装状态:


kubectl get crd virtualenvironments.env.alibaba.com


若输出类似以下信息,则表明KtEnv的CRD组件已经正确部署:


NAME                                  CREATED AT

virtualenvironments.env.alibaba.com   2020-04-21T13:20:35Z


部署 Webhook 组件


Webhook组件用于将Pod的虚拟环境标写入到其Sidecar容器的运行时环境变量内。


kubectl apply -f global/ktenv_webhook.yaml


Webhook组件默认被部署到名为kt-virtual-environment的Namespace中,包含一个Service和一个Deployment对象,以及它们创建的子资源对象,可用以下命令查看:


kubectl -n kt-virtual-environment get all


若输出类似以下信息,则表明KtEnv的Webhook组件已经部署且正常运行:


NAME                                  READY   STATUS    RESTARTS   AGE

pod/webhook-server-5dd55c79b5-rf6dl   1/1     Running   0          86s


NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE

service/webhook-server   ClusterIP   172.21.0.254   <none>        443/TCP   109s


NAME                             READY   UP-TO-DATE   AVAILABLE   AGE

deployment.apps/webhook-server   1/1     1            1           109s


NAME                                        DESIRED   CURRENT   READY   AGE

replicaset.apps/webhook-server-5dd55c79b5   1         1         1       86s


创建 Role 和 ServiceAccount


如果 K8s 集群中,已经开启了 RBAC 权限控制,那么,为了保证我们的 Operator 可以正常工作,还需要部署相应的Role和ServiceAccount。


kubectl apply -n default -f ktenv_service_account.yaml


部署 KT-env Operator


Operator是由CRD组件定义的虚拟环境管理器实例,需要在每个使用虚拟环境的Namespace里单独部署


以使用default Namespace为例,通过以下命令完成部署:


kubectl apply -n default -f ktenv_operator.yaml


此外,为了让Webhook组件对目标Namespace起作用,还应该为其添加值为enabled的environment-tag-injection标签。


kubectl label namespace default environment-tag-injection=enabled


现在,Kubernetes集群就已经具备使用虚拟环境能力了。


前置条件


KT-env 部署之前,首先需要有一个 Kubernetes 集群并且已经部署了 Istio 。
同时,本地还需要一个配置好的 kubectl 工具。


下载部署包


首先,我们需要从 发布页面 下载最新的部署文件包,并解压。


wget https://github.com/alibaba/virtual-environment/releases/download/v0.5.4/kt-virtual-environment-v0.5.4.zip

unzip kt-virtual-environment-v0.5.4.zip

cd v0.5.4/


安装相关组件


创建 CRD


在 KT-env 中,定义了一种新的资源对象: VirtualEnvironment 。


下面,我们第一步就来创建对应的 CRD 资源对象:


kubectl apply -f global/ktenv_crd.yaml


CRD组件会在Kubernetes集群内新增一种名为VirtualEnvironment的资源类型,在下一步我们将会用到它。可通过以下命令验证其安装状态:


kubectl get crd virtualenvironments.env.alibaba.com


若输出类似以下信息,则表明KtEnv的CRD组件已经正确部署:


NAME                                  CREATED AT

virtualenvironments.env.alibaba.com   2020-04-21T13:20:35Z


部署 Webhook 组件


Webhook组件用于将Pod的虚拟环境标写入到其Sidecar容器的运行时环境变量内。


kubectl apply -f global/ktenv_webhook.yaml


Webhook组件默认被部署到名为kt-virtual-environment的Namespace中,包含一个Service和一个Deployment对象,以及它们创建的子资源对象,可用以下命令查看:


kubectl -n kt-virtual-environment get all


若输出类似以下信息,则表明KtEnv的Webhook组件已经部署且正常运行:


NAME                                  READY   STATUS    RESTARTS   AGE

pod/webhook-server-5dd55c79b5-rf6dl   1/1     Running   0          86s


NAME                     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE

service/webhook-server   ClusterIP   172.21.0.254   <none>        443/TCP   109s


NAME                             READY   UP-TO-DATE   AVAILABLE   AGE

deployment.apps/webhook-server   1/1     1            1           109s


NAME                                        DESIRED   CURRENT   READY   AGE

replicaset.apps/webhook-server-5dd55c79b5   1         1         1       86s


创建 Role 和 ServiceAccount


如果 K8s 集群中,已经开启了 RBAC 权限控制,那么,为了保证我们的 Operator 可以正常工作,还需要部署相应的Role和ServiceAccount。


kubectl apply -n default -f ktenv_service_account.yaml


部署 KT-env Operator


Operator是由CRD组件定义的虚拟环境管理器实例,需要在每个使用虚拟环境的Namespace里单独部署


以使用default Namespace为例,通过以下命令完成部署:


kubectl apply -n default -f ktenv_operator.yaml


此外,为了让Webhook组件对目标Namespace起作用,还应该为其添加值为enabled的environment-tag-injection标签。


kubectl label namespace default environment-tag-injection=enabled


现在,Kubernetes集群就已经具备使用虚拟环境能力了。


Kt-env实战

在之前的内容中,我们已经完成了 KT-env 的环境搭建。
同时,也了解了请求路由的基本原理。


下面,我们就来具体使用 KT-env 来实现对应的虚拟环境。


QuickStart


如果是一个新的 namespace,需要在新的 namespace 下部署 operator 和 role 相关信息,参考 deploy


Step1: 创建虚拟环境。


创建一个virtual-environment-cr.yaml 文件


apiVersion: env.alibaba.com/v1alpha2

kind: VirtualEnvironment

metadata:

 name: demo-virtualenv

spec:

 envHeader:

   name: ali-env-mark

   autoInject: true

 envLabel:

   name: virtual-env

   splitter: "."

   defaultSubset: dev


实例创建后,会自动监听所在Namespace中的所有Service、Deployment和StatefulSet对象并自动生成路由隔离规则,形成虚拟环境。


kubectl apply -n default -f virtual-environment-cr.yaml


Step2: 拉取Git仓库,进入示例代码目录。


git clone https://github.com/alibaba/virtual-environment.git

cd virtual-environment/examples


Step3: 设置 default namespace 自动注入 sidecar 并使得 webhook 能够为 Envoy 自动注入环境变量。


kubectl label namespace default environment-tag-injection=enabled

kubectl label namespace default istio-injection=enabled


Step4: 在集群随意创建一个临时的Pod作为发送测试请求的客户端。


kubectl create deployment sleep --image=virtualenvironment/sleep --dry-run -o yaml | kubectl apply -n default -f -


Step5: 部署相关应用


KtEnv支持Deployment和StatefulSet对象的路由隔离,
在这个例子中将部署3种不同语言实现的示例应用,
其中app-js和app-java被部署为Deployment,而app-go被部署为StatefulSet。


修改 app.sh 脚本中 apply_pods 函数如下:


apply_pods() {

   type=${1}

   ee=`echo ${e} | sed -e "s/\./-/g"`

   echo $s

   cat ${basepath}/${type}.yaml | sed -e "s/service-name-env-placeholder/${s}-${ee}/g" \

                       -e "s/service-name-placeholder/${s}/g" \

                       -e "s/app-env-placeholder/${e}/g" \

                       -e "s/app-image-placeholder/`hget images ${s}`/g" \

                       -e "s#app-url-placeholder#`hget urls ${s}`#g" \

                 | kubectl ${action} --validate=false -n ${namespace} -f -

}


使用app.sh脚本快速创建示例所需的VirtualEnvironment、Deployment、StatefulSet和Service资源:


# default namespace 下启动演示的服务实例

deploy/app.sh apply default


依次使用kubectl get virtualenvironmentkubectl get deploymentkubectl get statefulsetkubectl get service
命令查看各资源的创建情况,等待所有资源部署完成。


Step6: 进入同Namespace的任意一个Pod,例如前面步骤创建的sleep容器。


# 进入集群中的容器

kubectl exec -n default -it $(kubectl get -n default pod -l app=sleep -o jsonpath='{.items[0].metadata.name}') -- /bin/sh


Step7: 实验一下


分别在请求头加上不同的虚拟环境名称,使用curl工具调用app-js服务。


3个服务的关系是: app-js -> app-go -> app-java


注意该示例创建的VirtualEnvironment实例配置使用.作为环境层级分隔符,同时配置了传递标签Header的键名为ali-env-mark


已知各服务输出文本结构为[项目名 @ 响应的Pod所属虚拟环境] <- 请求标签上的虚拟环境名称。


观察实际响应的服务实例情况:


# 使用dev.proj1标签

> curl -H 'ali-env-mark: dev.proj1' app-js:8080/demo

 [springboot @ dev.proj1] <-dev.proj1

 [go @ dev] <-dev.proj1

 [node @ dev.proj1] <-dev.proj1


# 使用dev.proj1.feature1标签

> curl -H 'ali-env-mark: dev.proj1.feature1' app-js:8080/demo

 [springboot @ dev.proj1.feature1] <-dev.proj1.feature1

 [go @ dev] <-dev.proj1.feature1

 [node @ dev.proj1] <-dev.proj1.feature1


# 使用dev.proj2标签

> curl -H 'ali-env-mark: dev.proj2' app-js:8080/demo

 [springboot @ dev] <-dev.proj2

 [go @ dev.proj2] <-dev.proj2

 [node @ dev] <-dev.proj2


# 不带任何标签访问

# 由于启用了AutoInject配置,经过node服务后,请求自动加上了Pod所在虚拟环境的标签

> curl app-js:8080/demo

 [springboot @ dev] <-dev

 [go @ dev] <-dev

 [node @ dev] <-empty


VirtualEnvironment 配置文件解读


虚拟环境实例通过VirtualEnvironment类型的Kubernetes资源定义。其内容结构示例如下:


apiVersion: env.alibaba.com/v1alpha2

kind: VirtualEnvironment

metadata:

 name: demo-virtualenv

spec:

 envHeader:

   name: ali-env-mark

   autoInject: true

 envLabel:

   name: virtual-env

   splitter: "."

   defaultSubset: dev


参数作用如下表所示:

配置参数

默认值

说明

envHeader.name

X-Virtual-Env

用于透传虚拟环境名的HTTP头名称(虽然有默认值,建议显性设置)

envHeader.autoInject

false

是否为没有虚拟环境HTTP头记录的请求自动注入HTTP头(建议开启)

envLabel.name

virtual-env

Pod上标记虚拟环境名用的标签名称(除非确实必要,建议保留默认值)

envLabel.splitter

.

虚拟环境名中用于划分环境默认路由层级的字符(只能是单个字符)

envLabel.defaultSubset

请求未匹配到任何存在的虚拟环境时,进行兜底虚拟环境名(默认为随机路由)


注意:VirtualEnvironment实例只对其所在的Namespace有效。如有需要,可以通过在多个Namespace分别创建相同配置的实例。


Kt-env源码解读

了解了 KT-env 的基本功能、原理和使用之后,我们最后再来了解一下 KT-env 相关的源码实现。


KT-env 的源码仓库地址: virtual-environment


其中,我们先来了解一下它们的目录结构和功能:


  • cmd: 二进制程序的入口文件 - 核心目录。
  • pkg: 相关功能模块的封装模块 - 核心目录。
  • version: 版本信息。
  • build: 编译脚本。
  • deploy: 部署脚本。
  • docs: 文档。
  • examples: 示例相关代码。
  • sdk: 透传 headers 相关的SDK。


其中,核心的源码目录是 cmd 和 pkg 两个目录。


查看 cmd 目录下的内容,可以很容易的看出,它一共涉及到三个二进制可执行程序,分别是 operatorinspectorwebhook


其中,inspector 的功能非常简单,就是一个 HTTP 客户端,可以调用 operator 的接口查询 operator 模块的版本信息、状态以及修改日志级别,不再赘述。


我们重点来分析 operatorwebhook 两个程序的功能。


webhook


在 KT-env 中包含了一个全局的Mutating Admission Webhook组件
它的主要作用是将Pod上的环境标信息通过环境变量注入到Sidecar容器里,便于Sidecar为出口流量的Header添加恰当的环境标。


那么什么是 Mutating Admission Webhook 呢?这是 K8s 中的一个特有的概念,我们先来了解一下。


Admission webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制。
K8s 中可以定义两种类型的 admission webhook,即 validating admission webhook 和 mutating admission webhook。
其中,Mutating admission webhook 会先被调用。
它们可以更改发送到 API 服务器的对象以执行自定义的设置默认值操作。


K8s的具体的处理流程如下图所示:



而 Kt-env 中的 Webhook 其实就是这么一个组件,它主要用于将Pod上的环境标信息通过环境变量注入到Sidecar容器里,
即在 Webhook 阶段,修改了 Sidecar 的配置,向其中设置了对应的环境变量。


了解了 webhook 的原理之后,我们就来看一下 KT-env 中的 webhook 是如何实现的吧。


首先,我们先来简单看一下 Webhook 对应的 yaml 配置文件:


apiVersion: admissionregistration.k8s.io/v1beta1

kind: MutatingWebhookConfiguration

metadata:

 name: virtual-environment-webhook

webhooks:

 - name: webhook-server.kt-virtual-environment.svc

   failurePolicy: Fail

   clientConfig:

     service:

       name: webhook-server

       namespace: kt-virtual-environment

       path: "/inject"

     caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURYVENDQWtXZ0F3SUJBZ0lVZG42TEl2bDNaV2ltbndEVGwxS3U3ODhKcDBrd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1BqRThNRG9HQTFVRUF3d3pWbWx5ZEhWaGJDQkZiblpwY205dWJXVnVkQ0JCWkcxcGMzTnBiMjRnUTI5dQpkSEp2Ykd4bGNpQlhaV0pvYjI5cklFTkJNQjRYRFRJd01EZ3lOVEUxTURrMU5Wb1hEVE13TURneU16RTFNRGsxCk5Wb3dQakU4TURvR0ExVUVBd3d6Vm1seWRIVmhiQ0JGYm5acGNtOXViV1Z1ZENCQlpHMXBjM05wYjI0Z1EyOXUKZEhKdmJHeGxjaUJYWldKb2IyOXJJRU5CTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQwpBUUVBeDYrQkZIaXlkZk9uR3FMRmt4Y2lpL21ZMG9XU3dRV2krTHYwdmNqeklQTndKa2c0V0FJYTRWK0RqdTV2CmZ5NlE2RFhUaitlRnhlK212MmtVSEFtYjNsWS9iT3RaWGp2VnQ4bnBHS1VLdlBBN0hVRFdhUkZKOTR1eUJpQm8KQnlXTnZtTGNka0VFTjRVMVVGTlVIV3B1L1lHNXNaSC9ZQWZjZGEyMDhIUzVkQmllNTNYMmJjdjQrNGhzS3oyOAp0UUR2MmR3ZkxFT2crZ2lVUWRWRHUrN0lXUXpjRkp4NmdpdlBpVkl1UVRHVk04K0tya2dITlhXVjU4OGIwcTU1CnNPNjR2YWQ4cW5XT0t5bk5oSThyZzN0dGVJVkFHdkEvUnJGczFocytERHVBZlMxamhiRWUyUEJHSHVMeGx6TGIKNjR2NUV5VEhEdDVVOS93bFpxS0hMWlZQeFFJREFRQUJvMU13VVRBZEJnTlZIUTRFRmdRVVg1cEI4dkYzbnducQpUUjA1TlVhVlhaQzd4VjB3SHdZRFZSMGpCQmd3Rm9BVVg1cEI4dkYzbnducVRSMDVOVWFWWFpDN3hWMHdEd1lEClZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBbFU0YkxNMGlzbm5KaDA1dCs3TDkKWkFtS2M1eXhGdkUremlsK2Y5aEUzdzJpYjI0bVNYTVpOSWEvUWd6akxGK0owQVlIbUxXZTRQa2I4eXliQnVjRQp6d2VRNEc5Y3U2QWV5VFRHTk9zbU5lREx4WGRVOUJ1aElvRmZsR2ZDV1pudHA5ajZsbnFJbndqdjZJWDBEQmc1CkFEbXRNdVVrS0gyMXdUTHhXNVBWSmhQWnZiL3p1ZGNlNUxWRG1zT0Z5cjFkK2lnVnZPVzJJam51QUw3eGpXWFQKenVGYng5NnZBYTJjS0hWRjYyVzdoekp6NURiN0cwdWxJMXptOTF0ZXJaZjRYSHlUT3FzUC8wczFsYjV1V1k5cAo2NUZiZCtHMXJOL2NBcUM4NXo0K1Rrc2QrdTV1ZDFVREc5MEsvRG9UdS9vcis3U3o2bWNWdjVBejlLSHVxRzE2CnlBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=

   rules:

     - operations: ["CREATE"]

       apiGroups: [""]

       apiVersions: ["v1"]

       resources: ["pods"]

   namespaceSelector:

     matchLabels:

       environment-tag-injection: enabled


上述配置文件表示针对:


  • 设置了 environment-tag-injection: enabled 的 namespace 下
  • Pod 资源对象在创建时
  • 会触发 Mutating Admission Webhook 调用
  • 调用的请求地址是 webhook-server.kt-virtual-environment.svc/inject


而具体的注入环境变量的逻辑则在 webhook-server 模块内,
其核心代码见 main.go


我们选择其中部分的核心代码进行说明:


envLabels := os.Getenv("envLabel")

if envLabels == "" {

   logger.Fatal("Cannot determine env label !!")

}

envLabelList := strings.Split(envLabels, ",")

envTag := ""

for _, label := range envLabelList {

   if value, ok := pod.Labels[label]; ok {

       envTag = value

       break

   }

}


从环境变量中读取 envLabel 的配置,并判断 Pod 上是否存在其中的某个 label,如果存在,则表示使用该 label 对应的 value 用于 headers 追加。


var patches []PatchOperation

if envVarIndex < 0 {

   patches = append(patches, PatchOperation{

       Op:    "add",

       Path:  fmt.Sprintf("/spec/containers/%d/env/0", sidecarContainerIndex),

       Value: corev1.EnvVar{Name: envVarName, Value: envTag},

   })

} else {

   patches = append(patches, PatchOperation{

       Op:    "replace",

       Path:  fmt.Sprintf("/spec/containers/%d/env/%d/value", sidecarContainerIndex, envVarIndex),

       Value: envTag,

   })

}


表示根据之前的计算规则,生成 patch 操作,用于向 Pod 中追加/修改对应的环境变量,
其中,key 为 VIRTUAL_ENVIRONMENT_TAG,取值为之前步骤中从 Pod 上获取到的 label 对应的值。


通过上述步骤,就完成了将业务 Pod 上中指定的 Label 对应的值设置到 Sidecar 容器中的环境变量中了。


那么,Sidecar 中的环境变量是如何追加到发送请求的 header 中的呢?


这个其实是用到了 Istio 中的功能 EnvoyFilter


如上文所示,在 operator 模块中,会创建 EnvoyFilter,在 EnvoyFilter 中,通过 lua 脚本,在出流量的阶段中,追加了 headers 信息。


示例 EnvoyFilter 配置如下:


kind: EnvoyFilter

apiVersion: networking.istio.io/v1alpha3

metadata:

 name: demo-virtualenv

 namespace: kt-env1

 labels:

   envHeader: ali-env-mark

   envLabel: virtual-env

spec:

 workloadSelector: ~

 configPatches:

   - applyTo: HTTP_FILTER

     match:

       context: SIDECAR_OUTBOUND

       listener:

         filterChain:

           filter:

             name: envoy.http_connection_manager

     patch:

       operation: INSERT_BEFORE

       value:

         name: virtual.environment.lua

         typed_config:

           '@type': type.googleapis.com/envoy.config.filter.http.lua.v2.Lua

           inline_code: |-

             local curEnv = os.getenv("VIRTUAL_ENVIRONMENT_TAG")

             function envoy_on_request(req)

               local env = req:headers():get("ali-env-mark")

               if env == nil and curEnv ~= nil then

                 req:headers():add("ali-env-mark", curEnv)

               end

             end


其中,我们可以重点关注最下方的 lua 脚本,可以看出其基本的逻辑如下:
判断请求头部中是否包含 ali-env-mark,如果没有包含,
则从环境变量中取出 VIRTUAL_ENVIRONMENT_TAG 的值并设置为 ali-env-mark headers 的值。


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
7月前
|
编译器 C++ 开发者
【Conan 入门教程 】使用Conan 2.X和Autotools高效构建C/C++项目
【Conan 入门教程 】使用Conan 2.X和Autotools高效构建C/C++项目
362 1
|
分布式计算 Java Shell
安装SBT环境运行Scala项目
安装SBT环境运行Scala项目
575 0
安装SBT环境运行Scala项目
|
3月前
|
JavaScript 测试技术 Windows
vue配置webpack生产环境.env.production、测试环境.env.development(配置不同环境的打包访问地址)
本文介绍了如何使用vue-cli和webpack为Vue项目配置不同的生产和测试环境,包括修改`package.json`脚本、使用`cross-env`处理环境变量、创建不同环境的`.env`文件,并在`webpack.prod.conf.js`中使用`DefinePlugin`来应用这些环境变量。
137 2
vue配置webpack生产环境.env.production、测试环境.env.development(配置不同环境的打包访问地址)
|
7月前
|
前端开发 测试技术
测Nuxt.js入坑,配置dev、test、pro三种环境的变量env
先下载一个cross-env模块,比较好控制环境
235 5
|
3月前
|
JavaScript 测试技术
vue配置生产环境.env.production、测试环境.env.development
该文章介绍了如何在Vue项目中配置和使用不同的环境变量文件(.env、.env.production、.env.development)以适应开发、测试和生产环境,并通过修改`package.json`中的scripts来实现不同环境的打包。
605 0
vue配置生产环境.env.production、测试环境.env.development
|
7月前
|
JavaScript 开发工具 git
Vue 入门系列:.env 环境变量
Vue 入门系列:.env 环境变量
79 1
|
7月前
|
缓存 算法 开发者
【Conan 入门教程 】了解 Conan2.1 中内置部署策略
【Conan 入门教程 】了解 Conan2.1 中内置部署策略
132 1
|
7月前
|
测试技术 编译器 持续交付
【Conan 入门教程 】深入理解Conan中的测试包:test_package目录的精髓
【Conan 入门教程 】深入理解Conan中的测试包:test_package目录的精髓
292 0
|
7月前
|
物联网 持续交付 开发工具
RT-Thread 学习-Env开发环境搭建(一)
RT-Thread 学习-Env开发环境搭建(一)
133 0
RT-Thread 学习-Env开发环境搭建(一)
|
7月前
|
JavaScript 测试技术
vue环境变量配置——process.env(详细)
vue环境变量配置——process.env(详细)
226 0