背景
线上并存虚拟机服务以及容器环境服务,两者存在互相调用的场景。那么如何处理这部分流量就是本文要讲解的内容。
集群版本
K8s 版本:v1.21.3
Istio 版本:1.13.1
环境 我们将测试、预发合并为单一 K8s 集群,生产环境单独一套集群。本文主要介绍如何处理测试、预发集群与基于虚拟机服务的流量调度策略。
版本与环境 如何处理好版本与环境?我们以 ticket 服务为例,预发环境由于只会有一个版本存在,我们将它的域名定为:https://ticket.staging.demo.com。其中 staging 代表环境为预发环境。
测试环境是一个比较复杂多样的环境,因为不同的开发人员都会在测试环境进行发布测试(与其说是测试环境不如说是开发环境更为合适)。假如当前存在两个并行的开发业务,分支分别为:feature/v1,feature/v2,那么基于该分支会创建出两个测试环境,即:https://v1-ticket.test.demo.com,https://v2-ticket.test.demo.com。其中:test 为测试环境,v1、v2 为版本。当然,测试环境还有另外一个称作稳定版本的 https://ticket.test.demo.com。 这些子域名在经过 Istio 网关的时候会进行处理,将它们转换成类似 https://ticket.test.demo.com 的域名。由于证书原因,我们让域名包括了环境的信息。其中的原有域名的版本信息我们把它提取出来作为 label 进行传递(其实环境也会提取出来作为 label 传递,只不过让它保留在了域名里)。
以上是通过什么处理的呢?没错!EnvoyFilter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function envoy_on_request (request_handle) local host, key = getHost(request_handle) ... if not match (host, "^[^%.]+%.[^%.]+%." .. commonDomain .. "$" ) then return end local SVE = getVersionSystemEnvFrom(request_handle) setRequestHeader(request_handle, versionKey, SVE["version" ]) setRequestHeader(request_handle, systemKey, SVE["system" ]) setRequestHeader(request_handle, envKey, SVE["env" ]) ... if SVE["system" ] then host = SVE["system" ] .. "." .. SVE["env" ] .. "." .. commonDomain end request_handle:headers():replace(key, host) request_handle:logDebug("[HOST(replaced)] -> " .. request_handle:headers():get(key)) end
这里我们省略掉了辅助代码,保留大体思路。重造 host 域名,将版本相关信息作为 label 向后传递。将该过滤器作用到 GATEWAY 上,Envoy.filters.network.http_connection_manager/envoy.filters.http.cors 之前。
虚拟机到容器 有了以上的内容说明,我们知道流量的传递方式。但是问题来了,测试、预发集群整合到了一起,但是虚拟机的集群并非如此。那么我们怎么处理这部分流量呢?其实我们主要区分出预发环境的流量就行了。因为容器环境,预发环境只有一套服务,不像测试环境会存在多套。而预发环境的 ip 我们都有登记,那么这样一来就好说了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 apiVersion: networking.istio.io /v1alpha3 kind: EnvoyFilter metadata: name: internal-env-filter namespace: istio-system spec: configPatches: - applyTo: HTTP_FILTER match : context: GATEWAY listener: filterChain: filter: name: envoy.filters.network.http_connection_manager subFilter: name: envoy.filters.http.fault portNumber: 8080 patch: operation: INSERT_AFTER value: name: internal-env.lua typed_config: '@type' : type .googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inlineCode: | local shouldTagedIps = { "172.10.0.0" , "172.10.0.10" , } local envKey = "x-request-demo-env" function envoy_on_request (request_handle) local headers = request_handle:headers() local ip = request_handle:streamInfo():downstreamDirectRemoteAddress() ip = string .sub (ip, 1 , string .find (ip, ":" ) - 1 ) for i, v in pairs (shouldTagedIps) do if (v == ip) then request_handle:logDebug("add header for ip : " .. ip .. ":80" ) headers:replace(envKey, "staging" ) break end end end workloadSelector: labels: app: internal-istio-ingressgateway
我们只需要确定来源 ip,然后为对应的 ip 打上 label 就可以了。这里需要注意的地点就是来源 ip 的获取 api,一定要用 downstreamDirectRemoteAddress()
,请求头添加也一定要用 headers:replace()
,因为只要该 api 才会强制替换原有请求头。
有人可能好奇,预发环境解决了。但是测试环境版本多,怎么解决这部分流量呢?我们提到过,流量都是用 label 进行区分了。那么虚拟机要关心的就是如何传递这些 label,其实这块就交给自研的 SDK 解决了。
容器到虚拟机 无论是虚拟机到容器还是容器到虚拟机,我们都是通过 SVC 来处理 ip 问题的。虚拟机上,我们配置好 DNS 服务器,将 SVC 域名解析到 Istio 的入口网关。而容器上,那么处于虚拟机的服务怎么访问呢?很简单,ServiceEntry!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: demo-se namespace: demo spec: endpoints: - address: 172.31 .0 .0 labels: env: test ports: http: 10010 - address: 172.31 .0 .10 labels: env: staging ports: http: 10010 hosts: - demo.demo.svc.cluster.local location: MESH_EXTERNAL ports: - name: http number: 80 protocol: HTTP resolution: STATIC
我们为虚拟上的服务创建 VS、DR、SVC、SE 资源,将流量导入到指定的虚拟机上。这样一来,流量问题就解决了!但是我们思考个问题,流量调度是根据 label 标识的,那么 pod 与 pod 之间调度如何区流量呢?