0%

案例系列(一)虚拟机与容器流量调度

背景

线上并存虚拟机服务以及容器环境服务,两者存在互相调用的场景。那么如何处理这部分流量就是本文要讲解的内容。

集群版本

  • 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)
-- 提取host
local host, key = getHost(request_handle)
...
-- 如果不能匹配到 *.*.demo.com 则略过下面过滤
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"])
...
-- 替换成 Version 无关域名
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 之间调度如何区流量呢?