使用Eureka的微服务如何上Istio

由于当前公司的绝大多数应用都还是使用eureka来做服务发现,算法团队又提出一些灰度的需求。从不重复造轮子以及技术趋势的角度,自然想到是否能够将Istio整合到平台产品中。但是,据我所知,eureka2.0已经被放弃了,Istio也在早期版本中,直接废弃了对Eureka做服务发现的支持。因此,这可能需要对现有的脚手架做一定改造,这个后面具体再论。另一封面,听说istio在架构上发生了很大的变化,于是下载了1.5版本来亲自体验了一下。

v1.5架构变化

其实也不能够算是软件架构的大变化,更准确的说法应该是部署架构的变化。就是将之前控制面上多个微服务都变为模块的形势,统一放到istiod里面去运行。这样一个比较大的好处是,部署起来就方便多了。当然,提到部署的便捷性,istioctl这个工具还是相当不错的。基于istioctl manifest apply 可以快速按照用户指定的profile拉起来一个istio环境。但是,除了istio自带的模块被打包到了istiod里面之外,如果你还启用了调用链、监控、遥测之类的功能的话,在istio-system中,依然还有一大堆pod。如果想要彻底变革,这些是不是也可以考虑整合一把,哪怕是放到一个pod里面去,比如说kiali其实是高度依赖于prometheus的数据的。

使用Istio的顾虑

早年openstack上neutron还不够成熟的时候,经常出现VM网络不通,然后又各种折腾排查,动不动就需要在某个接口上tcpdump。说实在的,这样的日子如履薄冰,天天神经脆弱。我所理解的Istio中traffic的部分,本质是在容器层面的SDN;比起VM网络,envoy上大量的XDS规则,一旦有问题,可能比VM网络更难排查。所以,长久以来,对这货都不敢轻易引入(当年v1.0似乎也是在赶鸭子上架,很快速的又推出了新版本)。

好在如今的版本中,通过istioctl pc等工具,极大的降低了登录到各个数据面节点上排查路由规则的复杂度,同时,istioctl ps也提供了控制面板到数据面配置一致性的debug手法。所以,现在采用istio,在功能层面,我是没啥顾虑的;那么,唯一可能比较担心的还是在于性能层面。

但是,我们完全可以将该技术先只使用到开发测试环境,等待其数据面代理性能得到一个整体提升后,再最终用到生产中。

最适用的功能

在Istio提供的所有功能中,其实也没必要全都采纳,比如调用链APM这块,我就觉得比较二,对代码的入侵性太强,我更愿意在平台类产品中整合pinpoint或者skywalking之类专业的调用链。

另外,数据安全加密这块,对于绝大多数的应用场景,其实都是非必要的,关键它还对转发性能造成了较大影响。就像做管理决策,当我们排优先级的时候,哪些事情是能够为企业带来更大价值产出的,那么我认为这些事理应提高其优先级。

如前面所述,先在开发测试环境中,使用Istio的traffic控制相关的功能,同时,能够使用遥测数据来生成项目微服务全局的流量状况视图,应该算是比较稳妥的。

与eureka/consul兼容

我这里提到consul,其实并非是想将consul与Istio的pilot-discovery对接,因为之前在研究这块的时候,感觉不过是一个炫头,大家一般都不这么用。

这里有一个插曲,大概是2018年的时候,有一次我在团队里面提出过:“到底istio的数据面是如何转发流量的?”

这个问题是源于istio默认结合k8s的服务发现来工作,我们都知道,K8S的服务发现,是通过watch service上的cluster IP变化来刷新dns的(这个设计很巧妙,为啥不是直接刷新pod IP,而要使用cluster IP呢?因为dns client一般都有缓存,而cluster IP到pod ip的路由是由K8S实时控制的,因此…)。

我们回来讲Istio,因为Istio是基于K8S来做服务发现的,如果服务A要访问B,不管A和B的sidecar会对流量做怎么的操作。毕竟A的程序看不到sidecar,它要转流量到B之前,总是会查询B的地址,但是在DNS查询返回的IP列表里面,目的IP是cluster IP。这时,报文被封装,并基于网络协议栈发送出去,再被A的sidecar拦截。此时A的sidecar会基于路由规则来修改A的真实转发地址。那么,在这个过程中,A发送出去报文的目的IP地址,到底有啥“卵用”?

从本质上来说,这货确实没啥用,因为都被替换了。但是,也不是绝对的,假设A、B之间发送的报文是基于HTTP协议。我们知道HTTP协议有一个header,header里面有一个叫做“host”的一级公民。其实envoy就是依据这个host来首先过滤流量的。比如说,A要发送请求个B服务,如果它使用的是service-b这个域名的话,虽然协议栈会解析出service-b的IP来做底层IP报文的destination IP。但是顶层的HTTP协议封装的时候,依然会使用“host:service-b”来封装header。另一方面,所有TCP报文送出pod的时候,都会被其sidecar拦截并基于转发逻辑转发报文;所以,都还没有等IP报文中的destination IP发挥作用,就被sidecar替换了。因此,这个只要我们保障HTTP的“host”字段匹配了envoy中的规则,目的IP不重要。

比如,A要发送报文给B,当我们配置好了Istio规则之后,A在封装报文的时候,只需要确保HTTP的header中包含“host:B”,而目的IP可以随便填写。

所以,结论就是:A怎么知道B的目的IP这个事情,真的不重要!A是通过consul,eureka还是基于K8S dns查询到B的地址的,都一样。唯一重要的是是否满足envoy的转发规则。

但是,HTTP的很多SDK库都会忽略掉我们在上层直接设置的host值,而采用所访问域名或者目的IP地址。这就是一个坑,所以,需要对代码脚手架做一些改造,目标就是强制设置该host值。

实践

以下是一个调用链的例子,我写了一个程序,通过环境变量传入微服务名称和工作模式,其中stub模式为叶子节点,ingress模式为流量入口(树根节点),需要指定其下一跳节点(各分支或叶子)。所有的服务起来之后,都会将自己注册到consul上面,consul会基于注册的心跳时间来对各个服务发起健康检查。当服务A要访问服务B的时候,会在consul上查询存活的B列表,并访问B服务。

调用链拓扑为:

1
2
a - > c
- > b - > d

我们实际部署后,在kiali上看到的流量转发图是这样的:
流量拓扑

除了业务的调用链之外,我们看到consul有往各个service做健康检查的流量。由于consul发往各个service的流量的host我们没法做修改,因此看到了匹配passthrough cluster的力量。这个是Istio上如果流量没法匹配对应的路由,就会发送到passthrough cluster,最终基于报文的目标IP来转发(当然也可以配置成黑洞模式,这样没法匹配istio中路由规则的流量就会全部被干掉)。

下面是k8s上,启动整个应用各微服务的yaml文件和istio config文件:

  • services.yaml
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
apiVersion: v1
items:
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
run: centos
name: centos
namespace: default
spec:
replicas: 1
selector:
matchLabels:
run: centos
template:
metadata:
labels:
run: centos
spec:
containers:
- args:
- sleep
- "999999"
image: centos:7
imagePullPolicy: IfNotPresent
name: centos
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: consul
version: v1
name: consul
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: consul
version: v1
template:
metadata:
labels:
app: consul
version: v1
spec:
containers:
- env:
- name: CONSUL_BIND_INTERFACE
value: eth0
image: consul
imagePullPolicy: Always
name: consul
ports:
- containerPort: 8500
protocol: TCP
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: service-a
version: v1
name: service-a
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: service-a
version: v1
template:
metadata:
labels:
app: service-a
version: v1
spec:
containers:
- env:
- name: CONSUL_ADDR
value: consul
- name: CONSUL_PORT
value: "8500"
- name: app
value: service-a
- name: mode
value: ingress
- name: next_services
value: service-b,service-c
image: ljchen/istio-demo
imagePullPolicy: Always
name: service-a
ports:
- containerPort: 9090
name: http
protocol: TCP
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: service-b
version: v1
name: service-b
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: service-b
version: v1
template:
metadata:
labels:
app: service-b
version: v1
spec:
containers:
- env:
- name: CONSUL_ADDR
value: consul
- name: CONSUL_PORT
value: "8500"
- name: app
value: service-b
- name: next_services
value: service-d
image: ljchen/istio-demo
imagePullPolicy: Always
name: service-b
ports:
- containerPort: 9090
name: http
protocol: TCP
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: service-c
version: v1
name: service-c
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: service-c
version: v1
template:
metadata:
labels:
app: service-c
version: v1
spec:
containers:
- env:
- name: CONSUL_ADDR
value: consul
- name: CONSUL_PORT
value: "8500"
- name: app
value: service-c
- name: mode
value: stub
image: ljchen/istio-demo
imagePullPolicy: Always
name: service-c
ports:
- containerPort: 9090
name: http
protocol: TCP
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: service-d
version: v1
name: service-d-v1
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: service-d
version: v1
template:
metadata:
labels:
app: service-d
version: v1
spec:
containers:
- env:
- name: CONSUL_ADDR
value: consul
- name: CONSUL_PORT
value: "8500"
- name: app
value: service-d
- name: mode
value: stub
- name: version
value: v1
image: ljchen/istio-demo
imagePullPolicy: Always
name: service-d-v1
ports:
- containerPort: 9090
name: http
protocol: TCP
- apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: service-d
version: v2
name: service-d-v2
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 1
selector:
matchLabels:
app: service-d
version: v2
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: service-d
version: v2
spec:
containers:
- env:
- name: CONSUL_ADDR
value: consul
- name: CONSUL_PORT
value: "8500"
- name: app
value: service-d
- name: mode
value: stub
- name: version
value: v2
image: ljchen/istio-demo
imagePullPolicy: Always
name: service-d-v2
ports:
- containerPort: 9090
name: http
protocol: TCP
dnsPolicy: ClusterFirst
restartPolicy: Always
- apiVersion: v1
kind: Service
metadata:
labels:
app: consul
version: v1
name: consul
namespace: default
spec:
externalTrafficPolicy: Cluster
ports:
- name: http
nodePort: 31057
port: 8500
protocol: TCP
targetPort: 8500
selector:
app: consul
version: v1
sessionAffinity: None
type: NodePort
- apiVersion: v1
kind: Service
metadata:
labels:
component: apiserver
provider: kubernetes
name: kubernetes
namespace: default
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: 6443
sessionAffinity: None
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
labels:
app: service-a
version: v1
name: service-a
namespace: default
spec:
ports:
- name: http
port: 9090
protocol: TCP
targetPort: 9090
selector:
app: service-a
sessionAffinity: None
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
labels:
app: service-b
version: v1
name: service-b
namespace: default
spec:
ports:
- name: http
port: 9090
protocol: TCP
targetPort: 9090
selector:
app: service-b
sessionAffinity: None
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
labels:
app: service-c
version: v1
name: service-c
namespace: default
spec:
ports:
- name: http
port: 9090
protocol: TCP
targetPort: 9090
selector:
app: service-c
sessionAffinity: None
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
labels:
app: service-d
version: v1
name: service-d
namespace: default
spec:
ports:
- name: http
port: 9090
protocol: TCP
targetPort: 9090
selector:
app: service-d
sessionAffinity: None
type: ClusterIP
kind: List
metadata:
resourceVersion: ""
selfLink: ""
  • istio-config.yaml
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
apiVersion: v1
items:
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: consul
namespace: default
spec:
hosts:
- consul
http:
- route:
- destination:
host: consul
subset: v1
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-a
namespace: default
spec:
gateways:
- gateway
hosts:
- ljchen.net
http:
- route:
- destination:
host: service-a
subset: v1
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-b
namespace: default
spec:
hosts:
- service-b
http:
- route:
- destination:
host: service-b
subset: v1
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-c
namespace: default
spec:
hosts:
- service-c
http:
- route:
- destination:
host: service-c
subset: v1
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: service-d
namespace: default
spec:
hosts:
- service-d
http:
- route:
- destination:
host: service-d
subset: v1
- destination:
host: service-d
subset: v2
weight: 100
- apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- ljchen.net
port:
name: http
number: 80
protocol: HTTP
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: consul
namespace: default
spec:
host: consul
subsets:
- labels:
version: v1
name: v1
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: service-a
namespace: default
spec:
host: service-a
subsets:
- labels:
version: v1
name: v1
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: service-b
namespace: default
spec:
host: service-b
subsets:
- labels:
version: v1
name: v1
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: service-c
namespace: default
spec:
host: service-c
subsets:
- labels:
version: v1
name: v1
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: service-d
namespace: default
spec:
host: service-d
subsets:
- labels:
version: v1
name: v1
- labels:
version: v2
name: v2
kind: List
metadata:
resourceVersion: ""
selfLink: ""
0%