第3章 流控............................................................................................................... 1
3.1 更加智能的金丝雀........................................................................................ 1
3.2流量路由...................................................................................................... 2
3.2.1路由到特定版本的部署........................................................................ 3
3.2.2 Recommendation服务v2版本的金丝雀部署........................................... 4
3.2.3 继续加码recommendation服务的v2版本.............................................. 5
3.2.4 基于HTTP头的路由.......................................................................... 6
3.3 暗部署......................................................................................................... 8
3.4对外请求(Egress)..................................................................................... 10
前面讲过Istio包括一个控制平面和一个数据平面。数据平面由代理组成,代理以边车容器形式和业务容器共存。前面还介绍了“边车”这种代理部署方式,其中每个应用实例都有自己专属的代理,网络流量在到达应用实例之前都会经过该代理。这些边车代理可以独立地配置路由、过滤和扩张网络流量。本章中,我们会介绍利用Istio的各种流控模式。你将发现这些模式其实跟一些大的互联网公司比如Netfilix、Amzaon和Fracebook用的东西差不多。
过去几年中金丝雀部署(canary deployment)概念变得越来越流行。这个名字来自于“煤矿中的金丝雀”概念。过去矿工们常把一只鸟笼中的金丝雀放进矿井中去检测是否有危险气体存在,因为金丝雀比人类对这类气体更加敏感。金丝雀不仅能为矿工们放声歌唱,而且一旦发现有危险气体泄露,矿工们就能迅速逃离矿井。
金丝雀部署也有类似作用。在这种部署模式下,你部署一个新版本代码到生产环境中,但只允许一部分流量访问到它。也许只是测试客户,也许只是组织内部员工,也许只是iOS用户等等。然后你可以监控这个版本的故障、出错、SLA的变化等等。如果这个版本没有问题,那就可以逐步引导更多的流量给到它;如果有问题,那就很容易将它从生产环境中移除。金丝雀部署让你部署更快,而且将可能的有问题代码带来的影响控制到最小。
默认地,Kubernetes提供Service提供轮询负债功能能力。如果你只想为最新代码pod导入10%的网络流量,那么你不得不将新代码pod的数目设置为老代码pod的十分之一。利用Istio,你可以做到更精细的控制。你可以设置只将20%的网络流量导给三个最新代码的pod。Istio还能让你逐渐增加导入给新代码pod的流量,直到所有流量都被导入给它,然后老代码版本就能从生产环境中移除了。
使用Istio,你可以设置路由规则(routing rule)来控制流量导入指定pod中。特别地,Istio使用DestionationRule和VirtualServer资源来描述这些规则。下面是一个DestionationRule示例:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: recommendation
namespace: tutorial
spec:
host: recommendation
subsets:
- labels:
version: v1
name: version-v1
- labels:
version: v2
name: version-v2
下面是一个VirtualServer示例,它通过一个集合(subset)和权重(weighting factor)来指定流量导入:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: recommendation
namespace: tutorial
spec:
hosts:
- recommendation
http:
- route:
- destination:
host: recommendation
subset: version-v1
weight: 100
使用此VirtualService定义,你可以配置特定百分比的流量定向到recommendation服务的特定版本上。在上面的例子中,recommendation服务的100%流量将始终流向与标签version:v1匹配的pod。此处Pod的选择方式与Kubernetes基于标签的选择器模型非常相似。因此,服务网格中与recommendation服务进行通信的任何流量都将始终被路由到recommendation服务的v1版本。
上述路由行为不仅仅针对进入流量(ingress trafic)。实际上,可针对所有进入了网格的流量。这适用于网格内的所有服务间通信。如本例所示,这些路由规则适用于可能在服务调用图中的深层服务。如果你将不属于服务网格的服务部署到Kubernetes,它将不会看到这些规则,而会遵循默认的Kubernetes负载平衡规则。
为了在类似金丝雀部署的场景中展示Istio更复杂的路由能力,我们来部署recommendation服务的v2版本。首先,你要修改recommendation服务的一些源码。修改
com.redhat.developer.demos.recommendation.RecommendationVerticle中的RESPONSE_STRING_FORMAT所定义的字符串为“v2”:
private static final String RESPONSE_STRING_FORMAT = "recommendation v2 from '%s': %d\n";
然后再编译和打包新代码为v2版本:
cd recommendation/java/vertx
mvn clean package
docker build -t example/recommendation:v2 .
你可以执行jar文件以快速测试代码修改:
java -jar target/recommendation.jar
然后你在另一个中断中运行下面的curl命令:
curl localhost:8080
recommendation v2 from 'unknown': 1
最后,注入Istio边车代理并部署到Kubernetes中:
oc apply -f <(istioctl kube-inject -f ../../kubernetes/Deployment-v2.yml) -n tutorial
现在,你可以运行oc get pods命令去查看pod。如果所有pod都成功运行的话,会是下面这样子:
NAME READY STATUS RESTARTS AGE
customer-3600192384-fpljb 2/2 Running 0 17m
preference-243057078-8c5hz 2/2 Running 0 15m
recommendation-v1-60483540 2/2 Running 0 12m
recommendation-v2-99634814 2/2 Running 0 15s
现在,运行curl访问customer服务端点,就会看到流量在两个recommendation版本中做负载均衡。你会看到下面这样子:
#!/bin/bash
while true
do curl customer-tutorial.$(minishift ip).nip.io
sleep .1
done
customer => preference => recommendation v1 from '60483540': 29
customer => preference => recommendation v2 from '99634814': 1
customer => preference => recommendation v1 from '60483540': 30
customer => preference => recommendation v2 from '99634814': 2
customer => preference => recommendation v1 from '60483540': 31
customer => preference => recommendation v2 from '99634814': 3
现在,创建DestionalRule和VirtualService实例来将所有流量导入recommendation服务的v1版本。你要转到源代码的根目录,进入istio-tutorial目录,运行下面的命令:
oc -n tutorial create -f istiofiles/destination-rule-recommendation-v1-v2.yml
oc -n tutorial create -f istiofiles/virtual-service-recommendation-v1.yml
现在,运行curl命令去访问customer服务的话,你会看到流量全部被导到了v1版本:
customer => preference => recommendation v1 from '60483540': 32
customer => preference => recommendation v1 from '60483540': 33
customer => preference => recommendation v1 from '60483540': 34
VirtualService已经创建了到由DesetionationRule指定的目标子集的路由,子集中只有recommendation服务的v1版本。
现在,所有访问流量都被导向recommendation服务的v1版本。你可以使用创建一个VirtualService实例来进行金丝雀部署:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: recommendation
namespace: tutorial
spec:
hosts:
- recommendation
http:
- route:
- destination:
host: recommendation
subset: version-v1
weight: 90
- destination:
host: recommendation
subset: version-v2
weight: 10
这个VirtualService实例指定90%的流量被导向v1版本,10%的流量被导向v2版本。现在,使用这个VirtualService实例来覆盖之前的实例,使用下面的命令:
oc -n tutorial replace -f istiofiles/virtual-service-recommendation-v1_and_v2.yml
现在,使用curl命令访问customer服务的话,只有10%的流量被导向recommendation服务的v2版本。V2就是一个金丝雀版本。监控其日志、指标和跟踪系统来查看这个版本有没有引入任何错误或非期望的行为到你的环境中。
现在,如果没有任何不期望的事情发生,你对recommendation服务的v2版本的信心会大一些了。你可能会想往v2版本导入更多的流量。此时,你只需要将如下VirtualService定义替换已有的:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: recommendation
namespace: tutorial
spec:
hosts:
- recommendation
http:
- route:
- destination:
host: recommendation
subset: version-v1
weight: 50
- destination:
host: recommendation
subset: version-v2
weight: 50
使用这个VirtualService实例后,各有50%的流量会被导向v1和v2版本。要应用该配置,只需要运行下面的命令:
oc -n tutorial replace –f istiofiles/virtual-service-recommendation-v1_and_v2_50_50.yml
运行成功后,你会看到流量行为的变化,一半流量被导向了v2版本,另一半到v2版本。输出类似下面这样子:
customer => ... => recommendation v1 from '60483540': 192
customer => ... => recommendation v2 from '99634814': 37
customer => ... => recommendation v2 from '99634814': 38
customer => ... => recommendation v1 from '60483540': 193
customer => ... => recommendation v2 from '99634814': 39
customer => ... => recommendation v2 from '99634814': 40
最后,如果一切正常的话,你可将所有流量导向v2版本,只需应用下面的VirtualService实例:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: recommendation
namespace: tutorial
spec:
hosts:
- recommendation
http:
- route:
- destination:
host: recommendation
subset: version-v2
weight: 100
现在,你会看到所有流量被导向v2版本:
customer => preference => recommendation v2 from '99634814': 43
customer => preference => recommendation v2 from '99634814': 44
customer => preference => recommendation v2 from '99634814': 45
customer => preference => recommendation v2 from '99634814': 46
customer => preference => recommendation v2 from '99634814': 47
customer => preference => recommendation v2 from '99634814': 48
在继续下面的步骤前,要恢复到默认流量路由行为,运行下面的命令删除recommendation服务的VirtualService实例即可,然后你会看到Kubernetes自己的轮询负载均衡效果:
oc delete virtualservice/recommendation -n tutorial
前面介绍了Istio基于服务的元数据所做的细粒度路由控制。你还可以用Istio基于请求的元数据进行控制。比如,你可以使用匹配原语去创建基于请求的特定路由规则。比如,你想基于请求的地域、移动设备或者浏览器去将部分流量导向特定服务。我们来看下如何利用Istio做到这些。使用Istio,你可以在VirtualService中设置匹配规则。例如下面的VirtualService定义:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
creationTimestamp: null
name: recommendation
namespace: tutorial
spec: hosts:
- recommendation
http:
- match:
- headers:
baggage-user-agent:
regex: .*Safari.*
route:
- destination:
host: recommendation
subset: version-v2
- route:
- destination:
host: recommendation
subset: version-v1
这条规则匹配HTTP请求头,只将由“Safari”浏览器发起的请求导入recommendation服务的v2版本。运行下面的命令以安装这条规则:
oc -n tutorial create -f istiofiles/virtual-service-safari-recommendation-v2.yml
然后试一下:
curl customer-tutorial.$(minishift ip).nip.io
customer => preference => recommendation v1 from '60483540': 465
如果在curl命令中添加“user-agent:Safari”头,那么访问会被导向v2版本:
curl -H 'User-Agent: Safari' customer-tutorial.$(minishift ip).nip.io
customer => preference => recommendation v2 from '99634814': 318
如果用Firefox浏览器,那该访问会被导向v1版本:
curl -A Firefox customer-tutorial.$(minishift ip).nip.io
customer => preference => recommendation v1 from '60483540': 465
注意:如果你使用真实的浏览器进行测试,请注意在macOS系统上Chome浏览器会被识别为Safari。
Istio的DestiontionRule和VirtualService对象是使用CRD定义的,你可以象使用Kubernetes原生对象类型(比如Deployment、Pod、Service等)一样去使用它们。运行下面的命令去获取跟recommendation有关的Istio对象:
oc get crd | grep virtualservice
kubectl describe destinationrule recommendation -n tutorial
oc get virtualservice recommendation -o yaml -n tutorial
注意:基本上oc和kubectl两个命令可以交替使用,因为oc是kubectl的一个超级,它多了一些跟login、project和new-app相关的命令,这些命令弥补了原生Kubernetes的一些不足。
继续下面的步骤前,请使用下面的命令去清理所安装的Istio对象:
oc delete virtualservice recommendation -n tutorial
oc delete destinationrule recommendation -n tutorial
暗部署(Dark Launch)对不同的人有不同的含义。本质上,暗部署是对客户不可见的一种生产系统的部署。此时,Istio允许你复制或镜像流量到应用的新版本中,然后将其行为与在线应用的行为进行对比。这种方式允许你将生产品质的请求导向新服务,同时不影响生产环境。
例如,recommendation的v1版本是生产环境,而v2版本是新部署环境。可使用Istio将到v1的浏览镜像到v2。但Istio做浏览镜像时,它采用“触发并忘记”(fire-and-forget)模式。其含义是,Istio会做生产浏览的异步镜像,将被镜像的请求发往测试pod,因此不用担心它会返回什么。我们来尝试下。
首先,请确保环境中没有DeistionationRule和VirtualService实例存在:
oc get destinationrules -n tutorial
No resources found.
oc get virtualservices -n tutorial
No resources found.
我们来看下配置浏览镜像的VirtualService定义:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: recommendation
namespace: tutorial
spec:
hosts:
- recommendation
http:
- route:
- destination:
host: recommendation
subset: version-v1
mirror:
host: recommendation
subset: version-v2
该定义中,所有流量被导向recommendation服务的v1版本,在mirror部分,指定了接受镜像流量的host和subset。
然后,转到从Istio Tutorial克隆下来的代码的根目录,运行下面的命令:
oc -n tutorial create -f istiofiles/destination-rule-recommendation-v1-v2.yml
oc -n tutorial create -f istiofiles/virtual-service-recommendation-v1-mirror-v2.yml
在终端中,输出recommendation服务v2版本pod的日志:
oc -n tutorial logs -f `oc get pods|grep recommendation-v2|awk '{ print $1 }'` -c recommendation
你也可以使用stern去查看recommendation服务的v1和v2版本pod的日志:
stern recommendation
在另一个窗口中,用curl访问customer服务:
curl customer-tutorial.$(minishift ip).nip.io
customer => preference => recommendation v1 from '60483540': 466
从输出中,你能看到recommendation服务的v1版本被访问到了。再查看v2版本pod的日志,你会看到该服务处理进行流量所产生的日志条目。
流量镜像对于发布前的测试会有很大帮助,但是还是有一些挑战。比如,一个服务的新版本可能会访问数据库或其它关联服务。要进行微服务中的数据处理,建议你阅读Edson Yanaga所写的Migrating to Microservice Databases(O’Reilly)一书。要了解流量镜像更多的细节,可阅读Christian的博文“Advanced Traffic-Shadowing Patterns for Microservices with Istio Service Mesh” (https://blog.christianposta.com/microservices/advanced-traffic-shadowing-patterns-for-microservices-with-istio-service-mesh/)。
请记得在继续后面的步骤前删除DestionationRule和VirtualService实例:
oc delete virtualservice recommendation -n tutorial
oc delete destinationrule recommendation -n tutorial
默认地,服务的所有流量都会通过Istio代理,代理和应用服务部署在一起。这个代理负责校验路由规则,决定如何转发请求。Istio很好的一点是它默认阻止所有发到集群外的请求流量,除非显式创建规则去允许这种访问。因此,你可以在不信任网络和传统私有云环境中使用Istio。这些场景中,Istio会帮助阻止恶意代理访问应用服务进而获得网络的完全访问权限。通过默认阻止对外访问请求,并允许利用路由规则去控制内外部流量,你可以更从容地应对外面攻击,不管它们是从哪里发起的。
要进行测试验证,可以进入一个pod,然后用curl发起请求:
oc get pods -n tutorial 结果你会从now.httpbin.org收到404 NOT Found错误信息。要能正常访问,你需要定义一个ServiceEntry实例,下面这个正是我们将要应用的: apiVersion: networking.istio.io/v1alpha3 首先你要创建DesionationRule实例,然后应用ServiceEntry实例: oc -n tutorial create -f istiofiles/destination-rule-recommendation-v1-v2.yml 现在,你再进入pod,运行curl后你会得到200返回: oc exec -it recommendation-v2-99634814 /bin/bash 你可以列表所有出口规则: oc get serviceentry -n tutorial 最后,清理所有Istio实例,回到默认的Kubernetes行为: oc delete serviceentry httpbin-egress-rule -n tutorial 本章中的这些示例都很简单,但都是你探索Istio流控功能的一个良好的起点。 书籍英文版下载链接为 https://developers.redhat.com/books/introducing-istio-service-mesh-microservices/,作者 Burr Sutter 和 Christian Posta。 本中文译稿版权由本人所有。水平有限,错误肯定是有的,还请海涵。 感谢您的阅读,欢迎关注我的微信公众号:
NAME READY STATUS RESTARTS AGE
customer-6564ff969f-jqkkr 2/2 Running 0 19m
preference-v1-5485dc6f49-hrlxm 2/2 Running 0 19m
recommendation-v1-60483540 2/2 Running 0 20m
recommendation-v2-99634814 2/2 Running 0 7m
oc exec -it recommendation-v2-99634814 /bin/bash
[[email&#160;protected] ~]$ curl -v now.httpbin.org
* About to connect() to now.httpbin.org port 80 (#0)
* Trying 54.174.228.92...
* Connected to now.httpbin.org (54.174.228.92) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: now.httpbin.org
> Accept: */*
>
* Connection #0 to host now.httpbin.org left intact
[[email&#160;protected] ~]$ exit
kind: ServiceEntry
metadata:
name: httpbin-egress-rule
namespace: tutorial
spec:
hosts:
- now.httpbin.org
ports:
- name: http-80
number: 80
protocol: http
oc -n tutorial create -f istiofiles/service-entry-egress-httpbin.yml -n tutorial
[[email&#160;protected] ~]$ curl now.httpbin.org
{"now": {"epoch": 1543782418.7876487...
SERVICE-ENTRY NAME HOSTS PORTS NAMESPACE AGE
httpbin-egress-rule now.httpbin.org http/80 tutorial 5m
oc delete virtualservice recommendation -n tutorial
oc delete destinationrule recommendation -n tutorial