作者:李勇
本文以Istio 1.3.0 在Kubernetes中的部署为例,结合其bookinfo例子(https://istio.io/docs/examples/bookinfo/#deploying-the-application)对Istio的 v1apha3 路由API进行简要的介绍。其中Istio采用了Evaluation Install(https://istio.io/docs/setup/install/kubernetes/)。正文内容需要读者对Kubernetes和Istio的基本概念有基本的了解。
在《API 网关 kong 实战》(https://cloud.tencent.com/developer/article/1477672)一文中,我们介绍了为什么要采用API网关“ 如果让每一个后台系统都实现鉴权、限流、负载均衡、审计等基础功能是不合适的,通用的做法是把这些功能抽离出来放到网关层。”这解决了外部请求访问服务的问题,但是微服务之间的内部调用也需要进行鉴权、限流、负载均衡,一种简单的解决方案是让微服务之间的调用也通过网关进行,然而这种方案有很多问题:
正如API网关是在流量入口处设置一个反向代理,service mesh的做法是为每一个微服务部署一个代理,这些代理通过iptables劫持所有进入服务的流量,完成鉴权、限流等步骤后再把请求发送到后端服务中,因此service mesh对应用程序来说是透明的。在Istio的实现中,这个代理采用了Envoy,通过Sidecar方式实现,如下图所示:
图片来自Pattern: Service Mesh
多个微服务之间相互调用的时候,形成了服务网格(Service Mesh),sidecar proxy和service之间的业务流量是数据面,而每个sidecar proxy还跟Control Plane相连,这是控制面,用于下发配置和规则。
图片来自Pattern: Service Mesh
为了允许外部服务访问网格内部的资源,Istio同样提供了Gateway。
Istio的官方博文《Introducing the Istio v1alpha3 routing API》(https://istio.io/blog/2018/v1alpha3-routing/)中写道:
A typical mesh will have one or more load balancers (we call them gateways) that terminate TLS from external networks and allow traffic into the mesh. Traffic then flows through internal services via sidecar gateways. It is also common for applications to consume external services (e.g., Google Maps API). These may be called directly or, in certain deployments, all traffic exiting the mesh may be forced through dedicated egress gateways. The following diagram depicts this mental model.
我有意不对翻译这段文字,以免错误的翻译为大家带来误导,但我会在文章结尾的总结处给出自己的理解。这里有一篇翻译供参考:《Istio服务网格中的网关》(https://www.yangcs.net/posts/istio-ingress/)
Istio v1alpha3路由模型引入了4种配置资源,分别是
这些资源的控制流如下图所示
下面结合bookinfo例子简要介绍这4种资源,在继续阅读前,建议浏览这里((https://istio.io/docs/examples/bookinfo/#deploying-the-application))以便对bookinfo有一个大概的了解。
简单的说,bookinfo中部署了三个版本的的reviews服务:
Istio的Gateway其实是运行在Kubernetes中的一组工作负载,可以有多个Gateway的工作负载共存,它们也可以有多个实现。目前Istio的Gateway其实也是Envoy,暂时不清楚目前是否支持其他实现,比如nginx-ingress之类的。
Bookinfo的网关配置(samples/bookinfo/networking/bookinfo-gateway.yaml)如下:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
这个配置的意思是,在所有符合spec.selector指定标签中的pod中通过80端口监听http请求,接受来自所有主机名的请求
其中spec.selector是istio: ingressgateway,来看看这是个什么pod
# kubectl get pods -listio=ingressgateway -n istio-system
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-54f6c8db59-tp8jl 1/1 Running 0 21d
如果你在创建gateway资源之前登入这个pod,可以看到envoy只监听了两个端口,15000是Envoy admin port (commands/diagnostics),15090是Proxy
# kubectl exec -it istio-ingressgateway-54f6c8db59-tp8jl /bin/sh -n istio-system
# netstat -ntlup
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:15090 0.0.0.0:* LISTEN 28/envoy
tcp 0 0 127.0.0.1:15000 0.0.0.0:* LISTEN 28/envoy
tcp6 0 0 :::15020 :::* LISTEN 1/pilot-agent
在pod中执行一个循环去检查端口监听的情况while true; do date; netstat -ntlup; sleep 1; done
,然后在另一个终端中创建gateway资源
# kubectl get gw
No resources found.
# date; kubectl apply -f bookinfo-gateway.yaml ; kubectl get gw -w
Sun Oct 13 15:30:00 CST 2019
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo unchanged
NAME AGE
bookinfo-gateway 0s
回到gateway pod中,我们可以看到大约在命令执行两秒后envoy开始监听80端口:
# while true; do date; netstat -ntlup; sleep 1; done
...
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:15090 0.0.0.0:* LISTEN 28/envoy
tcp 0 0 127.0.0.1:15000 0.0.0.0:* LISTEN 28/envoy
tcp6 0 0 :::15020 :::* LISTEN 1/pilot-agent
Sun Oct 13 07:30:02 UTC 2019
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 28/envoy
tcp 0 0 0.0.0.0:15090 0.0.0.0:* LISTEN 28/envoy
tcp 0 0 127.0.0.1:15000 0.0.0.0:* LISTEN 28/envoy
tcp6 0 0 :::15020 :::* LISTEN 1/pilot-agent
我们现在知道这些gateway的workload是怎么处理Gateway资源并创建对应的监听端口的,可是外部请求是怎么进来的呢?在Istio Evaluation Install中默认部署了一个type为LoadBalancer的服务:
# kubectl get svc -n istio-system -l istio=ingressgateway -o yaml
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
annotations:
...
creationTimestamp: 2019-09-21T14:10:17Z
labels:
app: istio-ingressgateway
chart: gateways
heritage: Tiller
istio: ingressgateway
release: istio
name: istio-ingressgateway
namespace: istio-system
resourceVersion: "8604840388"
selfLink: /api/v1/namespaces/istio-system/services/istio-ingressgateway
uid: 8a23bbe1-dc79-11e9-a35b-ea6d8aa3e629
spec:
clusterIP: 172.19.63.164
externalTrafficPolicy: Cluster
ports:
...
- name: http2
nodePort: 31380
port: 80
protocol: TCP
targetPort: 80
...
selector:
app: istio-ingressgateway
istio: ingressgateway
release: istio
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: <load balancer ip>
kind: List
metadata:
resourceVersion: ""
selfLink: ""
通过里面的selector我们可以看到正是这个服务把请求发送到刚才看到的pod中
# kubectl get pods -lapp=istio-ingressgateway,istio=ingressgateway,release=istio -n istio-system
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-54f6c8db59-tp8jl 1/1 Running 0 21d
而这个pod是istio-ingressgateway这个deployment的一个实例,可以进行水平扩展
# kubectl get deployment istio-ingressgateway -n istio-system
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
istio-ingressgateway 1 1 1 1 21d
Gateway接受到请求后,需要根据请求所附带的信息进行路由转发,对于http请求来说通常包括主机名、端口号、请求路径等。在Istio中,这是通过VirtualService实现的。在bookinfo-gateway.yaml中还包含了下面内容:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
这里通过spec.gateways
跟刚才创建的bookinfo-gateway关联,也就是说这个gateway上收到的请求,如果它们的请求路径匹配/productpage、/static、/login、/logout、或是/api/v1/products中的任何一个,将会按照route中的destination进行转发,这里的目标productpage其实也是kubernetes中的一个service
# kubectl get svc productpage
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
productpage ClusterIP 172.19.63.254 <none> 9080/TCP 21d
Kubernetes支持通过使用多个不同deployment来部署同一个应用的不同版本,并通过同一个Service对外暴露服务,上文中提高到,reviews这个Service实际上包含了三个不同的版本,每个版本都只有一个pod:
# kubectl get svc -lapp=reviews -o yaml
apiVersion: v1
items:
- apiVersion: v1
kind: Service
metadata:
...
spec:
clusterIP: 172.19.63.193
ports:
- name: http
port: 9080
protocol: TCP
targetPort: 9080
selector:
app: reviews
# kubectl get pods -l app=reviews
NAME READY STATUS RESTARTS AGE
reviews-v1-5787f7b87-b9vjc 2/2 Running 0 21d
reviews-v2-6d8b975647-md6x5 2/2 Running 0 21d
reviews-v3-7d5549f9-sxjn4 2/2 Running 0 21d
在上面的配置中,访问/productpage时,如果多次刷新页面,请求将会随机被发送到三个reviews服务中的一个。因此使用这种方法进行金丝雀发布无法进行精细的流量控制,比如把1%的请求流量发送到reviews-v3,因为它跟每个版本Pod的个数有关,用户请求落到v1/v2/v3每个版本的概率都是1/3。虽然我们可以手工调整pod的数量来调整流量的比例,但是这带来了另一个问题,Deployment不能再按照负载进行自动扩缩容(HPA)了,因为一做扩缩容,流量的比例就变了。
Istio的Destination Rule为这种场景提供了支持,还是以reviwes service为例,destination-rule-all.yaml
包含这么一段:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
这个规则的意思是对于subsets标记为v1的,发送到label为version: v1
的workload中,如此类推,我们可以看到这些reviews的pod确实是包含了对应version的label:
# kubectl get pods -l app=reviews --show-labels
NAME READY STATUS RESTARTS AGE LABELS
reviews-v1-5787f7b87-b9vjc 2/2 Running 0 21d app=reviews,pod-template-hash=5787f7b87,version=v1
reviews-v2-6d8b975647-md6x5 2/2 Running 0 21d app=reviews,pod-template-hash=6d8b975647,version=v2
reviews-v3-7d5549f9-sxjn4 2/2 Running 0 21d app=reviews,pod-template-hash=7d5549f9,version=v3
Destination Rule需要配置VirutalService来使用,默认情况下,如果VirtualService中没有指定subsets配置,仅仅像上面那样指定Destination Rules跟Kuberetes的Service的负载均衡策略是一致的,流量会以均等的几率落到三个版本中。我们可以为Reviews配置一个这样的VirtualService:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10
这个配置中,90%的流量会发给subset v1,而10%的流量会发给subset v2,v3不会收到任何请求。此时流量比例不再受到kuerbernetes中每个版本pod个数的影响,此时可以启用HPA,当我们调整流量比例的时候,对不同版本的deployment进行自动扩缩容。
Isito还可以做更复杂的流量管理,比如下面这个配置的意思是:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v3
如上文所述,网格内部的服务通常还需要访问外部服务,有两种做法,一是直接访问,二是经由Egress Gateway间接访问。比如应用需要访问http://foo.com,它可以直接向这个地址发起请求,也可以通过ServiceEntry把http://foo.com映射为foo.service,仿佛它是网格内部的一个服务, 当应用访问foo.service的时候,通过Egress Gateway出去。
ServiceEntry优点有包括:
应用程序通过Kubernetes的LoadBalancer把请求发送到Ingress Gateway,Gateway根据对应的VirtualService进行路由转发,并在转发后的Desntination Route中应用Istio的策略如流量管理等,最终访问到服务网格内部的应用。如果应用需要访问外部服务,可以发起直接的请求,也可以经由Egress Gateway应用Isto的策略后再转发出去。
不怎么务正业的程序员,BUG制造者、CPU0杀手。从事过开发、运维、SRE、技术支持等多个岗位。原Oracle系统架构和性能服务团队成员,目前在腾讯从事运营系统开发。
本文分享自 云服务与SRE架构师社区 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!