Istio流量控制
Istio 是现在最热门的 Service Mesh 工具,istio 是由 Google、IBM、Lyft 等共同开源的 Service Mesh(服务网格)框架,于2017年初开始进入大众视野。Kubernetes 解决了云原生应用的部署问题,istio 解决的是应用的服务(流量)治理问题。
安装
安装环境说明
系统 | 版本 | 说明 |
---|---|---|
操作系统 | win10 20H2 | M1不兼容Istio,Centos7虚拟机抛各种问题,内存,cm配置等。 |
Docker | Docker Desktop 4.4.2 | |
Kubenetes | v1.22.5 | 直接在Docker Desktop上安装,具体参考 |
Istio | 1.11.6 | 官方文档, GitHub Release 页面 |
Istio安装
前置环境准备就绪后,直接到GitHub Release页面下载对应的操作系统版本,本次下载的是1.11.6。
下载完成后解压到指定的路径下,此次路径:D:\Program\istio。
安装目录包含:
- samples/目录,包含示例应用程序
- bin/目录,
istioctl
客户端二进制文件
为了能直接使用istioctl
,将D:\Program\istio\bin
添加到系统环境变量。
通过istioctl version
查看安装的版本信息:
$ istioctl version
client version: 1.11.6
control plane version: 1.11.6
data plane version: 1.11.6 (2 proxies)
本次安装,采用demo配置组合,选择它是因为它包含了一组专为测试准备的功能集合。
$ istioctl install --set profile=demo -y
✔ Istio core installed
✔ Istiod installed
✔ Egress gateways installed
✔ Ingress gateways installed
✔ Installation complete
另外还有用于生产或性能测试的配置组合。
default:根据
IstioOperator
API 的默认设置启动组件。 建议用于生产部署和 Multicluster Mesh 中的 Primary Cluster。您可以运行
istioctl profile dump
命令来查看默认设置。demo:这一配置具有适度的资源需求,旨在展示 Istio 的功能。 它适合运行 Bookinfo 应用程序和相关任务。
此配置文件启用了高级别的追踪和访问日志,因此不适合进行性能测试。
minimal:与默认配置文件相同,但只安装了控制平面组件。 它允许您使用 Separate Profile 配置控制平面和数据平面组件(例如 Gateway)。
remote:配置 Multicluster Mesh 的 Remote Cluster。
empty:不部署任何东西。可以作为自定义配置的基本配置文件。
preview:预览文件包含的功能都是实验性。这是为了探索 Istio 的新功能。不确保稳定性、安全性和性能(使用风险需自负)。
安装完成后我们可以查看 istio-system 命名空间下面的 Pod 运行状态:
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
istio-egressgateway-cbbc444d-7k6rg 1/1 Running 0 73m
istio-ingressgateway-766fdf996-jwmq6 1/1 Running 0 73m
istiod-5c8784b855-c8sx4 1/1 Running 0 74m
如果都是 Running 状态证明 istio 就已经安装成功了。
示例安装
接下来可以安装官方提供的一个非常经典的 Bookinfo 应用示例,这个示例部署了一个用于演示多种 Istio 特性的应用,该应用由四个单独的微服务构成。 这个应用模仿在线书店的一个分类,显示一本书的信息。页面上会显示一本书的描述,书籍的细节(ISBN、页数等),以及关于这本书的一些评论。
Bookinfo 应用分为四个单独的微服务:
- productpage:这个微服务会调用 details 和 reviews 两个微服务,用来生成页面。
- details:这个微服务中包含了书籍的信息。
- reviews:这个微服务中包含了书籍相关的评论,它还会调用 ratings 微服务。
- ratings:这个微服务中包含了由书籍评价组成的评级信息。
reviews 微服务有 3 个版本:
- v1 版本不会调用 ratings 服务。
- v2 版本会调用 ratings 服务,并使用 1 到 5 个黑色星形图标来显示评分信息。
- v3 版本会调用 ratings 服务,并使用 1 到 5 个红色星形图标来显示评分信息。
上图展示了使用 istio 后,整个应用实际的结构。所有的微服务都和一个 Envoy sidecar
封装到一起,sidecar 拦截所有到达和离开服务的请求。
Istio默认自动注入Sidecar,需要为 default
命名空间打上标签 istio-injection=enabled
(可选):
$ kubectl label namespace default istio-injection=enabled
进入解压的istio目录,执行如下命令:
$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
service/details created
serviceaccount/bookinfo-details created
deployment.apps/details-v1 created
service/ratings created
serviceaccount/bookinfo-ratings created
deployment.apps/ratings-v1 created
service/reviews created
serviceaccount/bookinfo-reviews created
deployment.apps/reviews-v1 created
deployment.apps/reviews-v2 created
deployment.apps/reviews-v3 created
service/productpage created
serviceaccount/bookinfo-productpage created
deployment.apps/productpage-v1 created
如果上面没有为default
命名空间打上 istio-injection=enabled
标签,则需要使用istioctl kube-inject
,手动注入Sidecar:
$ kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml)
注意:
istioctl kube-inject
命令用于在创建 Deployments 之前注入 Istio 的 Sidecar 容器,同样我们也可以直接将当前的命名空间打上一个istio-injection=enabled
的 Label 标签,这样该命名空间就开启了 Sidecar 容器自动注入功能。
这里我们部署的 bookinfo.yaml
资源清单文件就是普通的 Kubernetes 的 Deployment 和 Service 的 yaml 文件,而 istioctl kube-inject
会在这个文件的基础上向其中的 Deployment 追加一个镜像为 istio/proxyv2:1.11.6
的 sidecar 容器。
过一会儿就可以看到如下 service 和 pod 启动:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
details ClusterIP 10.103.231.222 <none> 9080/TCP 17s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 18m
productpage ClusterIP 10.96.94.163 <none> 9080/TCP 16s
ratings ClusterIP 10.107.212.118 <none> 9080/TCP 17s
reviews ClusterIP 10.104.114.185 <none> 9080/TCP 17s
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
details-v1-79f774bdb9-jrb7t 1/1 Running 0 12m
productpage-v1-6b746f74dc-lbqx9 1/1 Running 0 12m
ratings-v1-b6994bb9-bfvqd 1/1 Running 0 12m
reviews-v1-545db77b95-4rs5z 1/1 Running 0 12m
reviews-v2-7bf8c9648f-bs6r6 1/1 Running 0 12m
reviews-v3-84779c7bbc-rkq4k 1/1 Running 0 12m
现在应用的服务都部署成功并启动了,要确认 Bookinfo 应用是否正在运行,请在某个 Pod 中用 curl
命令对应用发送请求,例如 ratings
:
$ kubectl exec -it $(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}') -c ratings -- curl productpage:9080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
如果我们需要在集群外部访问,就需要添加一个 istio gateway。gateway 相当于 k8s 的 ingress controller 和 ingress。它为 HTTP/TCP 流量配置负载均衡,通常在服务网格边缘作为应用的 ingress 流量管理。
创建一个gateway:
$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created
验证gateway是否启动成功:
$ kubectl get gateway
NAME AGE
bookinfo-gateway 9s
要想取访问这个应用,这里我们需要更改下 istio 提供的 istio-ingressgateway 这个 Service 对象,默认是 LoadBalancer 类型的服务:
$ kubectl get svc -n istio-system
...
$ kubectl edit svc istio-ingressgateway -n istio-system
......
type: NodePort # 修改成 NodePort 类型
status:
loadBalancer: {}
这样我们就可以通过 http://<NodeIP>:<nodePort>/productpage
访问应用了:
刷新页面可以看到 Book Reviews 发生了改变,因为每次请求会被路由到到了不同的 Reviews 服务版本去:
至此,整个 istio 就安装并验证成功了。
流量管理
上面搭建BookInfo 应用我们只用到了下面两个资源文件
samples/bookinfo/platform/kube/bookinfo.yaml
samples/bookinfo/networking/bookinfo-gateway.yaml
前者就是通常的 Kubernetes 定义的 Deployment 和 Service 的资源清单文件,后者定义了这个应用的外部访问入口gateway,以及应用内部 productpage 服务的 VirtualService
规则,而其他内部服务的访问规则还没有被定义。
现在访问应用界面并刷新,会看到 Reviews 有时不会显示评分,有时候会显示不同样式的评分,这是因为后面有3个不同的 Reviews 服务版本,而没有配置该服务的路由规则 route rule
的情况下,该服务的几个实例会被随机访问到,有的版本服务会进一步调用 Ratings 服务,有的不会。
到这里我们已经接触到了 Istio 中两个非常重要的流量管理的资源对象了:
- VirtualService 用来在 Istio 中定义路由规则,控制流量路由到服务上的各种行为。
- Gateway 为 HTTP/TCP 流量配置负载均衡器的。
配置请求路由
不同服务版本访问规则
对 Reviews 服务添加一条路由规则,启用 samples/bookinfo/networking/virtual-service-reviews-v3.yaml
定义的 VirtualService 规则
$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v3.yaml
这样,所有访问 reviews 服务的流量就会被引导到 reviews 服务对应的 subset 为 v3 的 Pod 中。
samples/bookinfo/networking/virtual-service-reviews-v3.yaml
内容如下:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v3
然后查看所有的路由规则:
$ kubectl get virtualservices
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 10m
reviews ["reviews"] 5m39s
我们可以看到 reviews 的 VirtualService
已经创建成功了,此时我们去刷新应用的页面,发现访问 Reviews 失败了:
这是因为我们还没有创建 DestinationRule 对象,DestinationRule 对象是 VirtualService 路由生效后,配置应用与请求的策略集,用来将 VirtualService 中指定的 subset 与对应的 Pod 关联起来。
在 samples/bookinfo/networking/destination-rule-all.yaml
文件中有定义所有该应用中要用到的所有 DestinationRule 资源对象,其中有一段就是对 Reviews 相关的 DestinationRule 的定义:
---
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
可以看到 DestinationRule 中定义了 subsets
集合,其中 labels 就和我们之前 Service 的 labelselector
一样是去匹配 Pod 的 labels 标签的,比如我们这里 subsets 中就包含一个名为 v3 的 subset,而这个 subset 匹配的就是具有 version=v3
这个 label 标签的 Pod 集合。
再回到之前的 samples/bookinfo/platform/kube/bookinfo.yaml
文件中,我们可以发现 reviews 的 Deployment 确实有声明不同的 labels->version
:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v3
labels:
app: reviews
version: v3
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v3 # 声明version=v3
template:
metadata:
labels:
app: reviews
version: v3
spec:
serviceAccountName: bookinfo-reviews
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v3:1.16.2
imagePullPolicy: IfNotPresent
env:
- name: LOG_DIR
value: "/tmp/logs"
ports:
- containerPort: 9080
volumeMounts:
- name: tmp
mountPath: /tmp
- name: wlp-output
mountPath: /opt/ibm/wlp/output
securityContext:
runAsUser: 1000
volumes:
- name: wlp-output
emptyDir: {}
- name: tmp
emptyDir: {}
这样就通过 DestinationRule 将 VirtualService 与 Service 不同的版本关联起来了。现在直接创建 DestinationRule 资源:
$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml
destinationrule.networking.istio.io/productpage created
destinationrule.networking.istio.io/reviews created
destinationrule.networking.istio.io/ratings created
destinationrule.networking.istio.io/details created
创建完成后,我们就可以查看目前我们网格中的 DestinationRules:
$ kubectl get destinationrule
NAME HOST AGE
details details 37s
productpage productpage 37s
ratings ratings 37s
reviews reviews 37s
此时再访问应用就成功了,多次刷新页面发现 Reviews 都展示的是 v3 版本带红色星的 Ratings,说明我们VirtualService 的配置成功了。
基于用户身份的路由
同样,将上面创建的 VirtualService 对象删除:
$ kubectl delete virtualservice reviews
virtualservice.networking.istio.io "reviews" deleted
$ kubectl get virtualservice
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 41m
查看文件 samples/bookinfo/networking/virtual-service-reviews-jason-v2-v3.yaml
的定义:
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
这个 VirtualService 对象定义了对 reviews 服务访问的 match
规则。意思是如果当前请求的 header 中包含 jason 这个用户信息,则只会访问到 v2 的 reviews 这个服务版本,即都带黑星的样式,如果不包含该用户信息,则都直接将流量转发给 v3 这个 reviews 的服务。
我们先不启用这个 VirtualService,先去访问下 Bookinfo 这个应用,右上角有登录按钮,在没有登录的情况下刷新页面,reviews 服务是被随机访问的,可以看到有带星不带星的样式,点击登录,在弹窗中 User Name 输入 jason,Password 为空,登录。
登录成功后刷新页面,可以看到跟未登录前的访问规则一样,也是随机的。
现在创建上面的 VirtualService 这个对象:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-jason-v2-v3.yaml
virtualservice.networking.istio.io/reviews created
$ kubectl get virtualservice
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 47m
reviews ["reviews"] 24s
此时再回去刷新页面,发现一直都是黑星的 Reviews 版本(v2)被访问到了。注销退出后再访问,此时又一直是红星的版本(v3)被访问了。
说明我们基于 headers->end-user->exact:jason
的控制规则生效了。在 productpage 服务调用 reviews 服务时,登录的情况下会在 header 中带上用户信息,通过 exact
规则匹配到相关信息后,流量被引向了上面配置的 v2 版本中。
这里要说明一下 match 的匹配规则:一个 match
块里的条件是需要同时满足才算匹配成功的,如下面是 url 前缀和端口都必须都满足才算成功:
- match:
- uri:
prefix: "/wpcatalog"
port: 443
多个 match 块之间是只要有一个 match 匹配成功了,就会被路由到它指定的服务版本去,而忽略其他的。
我们的示例中在登录的条件下,满足第一个 match,所以服务一直会访问到 v2 版本。退出登录后,没有 match 规则满足匹配,所以就走最后一个 route 规则,即 v3 版本。
故障注入
注入HTTP延迟故障
为了测试微服务应用程序 Bookinfo 的弹性,我们将为用户 jason
在 reviews:v2
和 ratings
服务之间注入一个 7 秒的延迟。 这个测试将会发现一个故意引入 Bookinfo 应用程序中的 bug。
我们查看 istio 样例文件夹下面的文件 samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
内容:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
delay:
percentage:
value: 100.0
fixedDelay: 7s
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
这个 VirtualService 定义了一个在 jason 登录的情况下,访问 ratings 服务的 100% 的 7s 访问延迟。前面我们知道,Bookinfo 这个示例 productpage 服务调用 reviews,reviews 的不同版本会对 ratings 进行不同的调用,其中 reviews-v1 不调用 ratings,reviews-v2 和 reviews-v3 会调用 ratings,并做不同样式的渲染。并且在 productpage 访问 reviews 时,代码中有硬编码 6s 中的访问超时限制,而 reviews 访问 ratings 编码了 10s 的访问超时限制。
了解这一点后,我们现在来创建这个 VirtualService 资源对象:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
virtualservice.networking.istio.io/ratings created
$ kubectl get virtualservice
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 62m
ratings ["ratings"] 32s
reviews ["reviews"] 15m
创建完成后,前往 Bookinfo 应用,登录 jason,打开浏览器的 Network,刷新页面,发现请求加载很慢,大约 6s 后,出现如下界面:
期望 Bookinfo 主页在大约 7 秒钟加载完成并且没有错误。 但是出现了一个问题:Reviews 部分显示了错误消息:
Sorry, product reviews are currently unavailable for this book.
按照预期,我们引入的 7 秒延迟不会影响到 reviews
服务,因为 reviews
和 ratings
服务间的超时被硬编码为 10 秒。 但是,在 productpage
和 reviews
服务之间也有一个 3 秒的硬编码的超时,再加 1 次重试,一共 6 秒。 结果,productpage
对 reviews
的调用在 6 秒后提前超时并抛出错误了。
这种类型的错误可能发生在典型的由不同的团队独立开发不同的微服务的企业应用程序中。 Istio 的故障注入规则可以帮助您识别此类异常,而不会影响最终用户。
注入 HTTP abort 故障
测试微服务弹性的另一种方法是引入 HTTP abort 故障。 这个任务将给 ratings
微服务为测试用户 jason
引入一个 HTTP abort。
在这种情况下,我们希望页面能够立即加载,同时显示 Ratings service is currently unavailable
这样的消息。
首先查看samples/bookinfo/networking/virtual-service-ratings-test-abort.yam
的内容:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
abort:
percentage:
value: 100.0
httpStatus: 500
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
通过上面的这个 yaml 文件我们可以看出这个 VirtualService 资源对象配置了在 jason 登录时,reviews 对 ratings 访问时 100% 的返回一个500错误响应。
创建这个资源对象:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml
virtualservice.networking.istio.io/ratings configured
现在我们回到 BookInfo 应用,登录 jason,刷新页面,可以很快就看到 Rating 服务不可用的提示信息。
流量转移
基于权重的服务访问规则
首先移除刚刚创建的 VirtualService 对象,排除对环境的影响:
$ kubectl delete virtualservice reviews
virtualservice.networking.istio.io "reviews" deleted
$ kubectl get virtualservice
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 32m
现在我们再去访问 Bookinfo 应用又回到最初随机访问 Reviews 的情况了。
现在我们查看文件 samples/bookinfo/networking/virtual-service-reviews-90-10.yaml
的定义:
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% 的对 Reviews 的流量会落入 v1 这个 subset,就是没有 Ratings 的这个服务,10% 会落入 v2 带黑色 Ratings 的这个服务,然后我们创建这个资源对象:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-90-10.yaml
virtualservice.networking.istio.io/reviews created
$ kubectl get virtualservice
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 39m
reviews ["reviews"] 36s
我们查看当前网格中的 VirtualService 对象,可以看到已经有 reviews 了,证明已经创建成功了,由于上面我们已经将应用中所有的 DestinationRules 都已经创建过了,所以现在我们直接访问应用就可以了,我们多次刷新,可以发现没有出现 Ratings 的次数与出现黑色星 Ratings 的比例大概在9:1
左右,并且没有红色星的 Ratings 的情况出现,说明我们配置的基于权重的 VirtualService 访问规则配置生效了。
TCP流量转移
本任务展示如何将 TCP 流量从微服务的一个版本逐步迁移到另一个版本。例如,将 TCP 流量从旧版本迁移到新版本。
在 Istio 中,可以通过配置一系列规则来实现此目标,这些规则将一定比例的 TCP 流量路由到不同的服务。在此任务中,将会把 100% 的 TCP 流量分配到 tcp-echo:v1
,接着,再通过配置 Istio 路由权重把 20% 的 TCP 流量分配到 tcp-echo:v2
。
同样查看 samples/tcp-echo/tcp-echo-services.yaml
内容:
apiVersion: v1
kind: Service
metadata:
name: tcp-echo
labels:
app: tcp-echo
service: tcp-echo
spec:
ports:
- name: tcp
port: 9000
- name: tcp-other
port: 9001
# Port 9002 is omitted intentionally for testing the pass through filter chain.
selector:
app: tcp-echo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tcp-echo-v1
labels:
app: tcp-echo
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: tcp-echo
version: v1
template:
metadata:
labels:
app: tcp-echo
version: v1
spec:
containers:
- name: tcp-echo
image: docker.io/istio/tcp-echo-server:1.2
imagePullPolicy: IfNotPresent
args: [ "9000,9001,9002", "one" ]
ports:
- containerPort: 9000
- containerPort: 9001
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tcp-echo-v2
labels:
app: tcp-echo
version: v2
spec:
replicas: 1
selector:
matchLabels:
app: tcp-echo
version: v2
template:
metadata:
labels:
app: tcp-echo
version: v2
spec:
containers:
- name: tcp-echo
image: docker.io/istio/tcp-echo-server:1.2
imagePullPolicy: IfNotPresent
args: [ "9000,9001,9002", "two" ]
ports:
- containerPort: 9000
- containerPort: 9001
上面配置文件部署了两个版本的 tcp-echo 服务,要注意的是 Service 对象中的 Label Selector 是 app=tcp-echo
,这样该对象就会匹配到上面的两个版本的对象。使用如下命令安装:
$ kubectl apply -f samples/tcp-echo/tcp-echo-services.yaml
service/tcp-echo created
deployment.apps/tcp-echo-v1 created
deployment.apps/tcp-echo-v2 created
$ kubectl get pod -l app=tcp-echo
NAME READY STATUS RESTARTS AGE
tcp-echo-v1-7dd5c5dcfb-cgwt5 2/2 Running 0 62s
tcp-echo-v2-56cd9b5c4f-4dswn 2/2 Running 0 62s
接下来将微服务 tcp-echo
的 TCP 流量全部路由到 v1 版本上,直接使用 samples/tcp-echo/tcp-echo-all-v1.yaml
这个示例文件中的对象,如下所示:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: tcp-echo-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 31400
name: tcp
protocol: TCP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: tcp-echo-destination
spec:
host: tcp-echo
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: tcp-echo
spec:
hosts:
- "*"
gateways:
- tcp-echo-gateway
tcp:
- match:
- port: 31400
route:
- destination:
host: tcp-echo
port:
number: 9000
subset: v1
这里创建了一个Gateway
对象,用来路由TCP的请求,要注意,这里匹配的是istio=ingressgateway
这个Service对象的31400
这个TCP端口:
$ kubectl get svc -n istio-system -l istio=ingressgateway -o yaml
......
- name: tcp
nodePort: 31290
port: 31400
protocol: TCP
targetPort: 31400
......
接着在 DestinationRule
对象中声明了 v1
和 v2
两个子集服务,在 VirtualService
对象中声明具体的路由规则,我们可以看到是直接全都路由到了 v1
这个子集中去。
直接创建上面的资源对象:
$ kubectl apply -f samples/tcp-echo/tcp-echo-all-v1.yaml
gateway.networking.istio.io/tcp-echo-gateway created
destinationrule.networking.istio.io/tcp-echo-destination created
virtualservice.networking.istio.io/tcp-echo created
创建完成后我们就可以通过 istio-ingressgateway 去访问上面的 TCP 服务了,要在外部访问该服务,同样我们可以通过 istio-ingressgateway
的 NodePort 来访问,这里的 TCP 对应的 nodePort 端口是 31290,这里可以使用如下所示的命令来测试:
for i in {1..10}; do docker run -e INGRESS_HOST=$INGRESS_HOST \
-e INGRESS_PORT=$INGRESS_PORT -i --rm busybox sh -c "(date; sleep 1) | nc \
$INGRESS_HOST $INGRESS_PORT"; done
one Tue Feb 8 08:45:44 UTC 2022
one Tue Feb 8 08:45:47 UTC 2022
one Tue Feb 8 08:45:50 UTC 2022
one Tue Feb 8 08:45:53 UTC 2022
one Tue Feb 8 08:45:56 UTC 2022
one Tue Feb 8 08:45:59 UTC 2022
one Tue Feb 8 08:46:02 UTC 2022
one Tue Feb 8 08:46:06 UTC 2022
从上面的日志可以看出,所有时间戳的前缀都是 one
,这意味着所有流量都被路由到了 tcp-echo
服务的 v1
版本中。
然后使用新的路由规则将 20% 的流量从 tcp-echo:v1 转移到 tcp-echo:v2,对应的示例文件为 samples/tcp-echo/tcp-echo-20-v2.yaml
,内容如下所示:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: tcp-echo
spec:
hosts:
- "*"
gateways:
- tcp-echo-gateway
tcp:
- match:
- port: 31400
route:
- destination:
host: tcp-echo
port:
number: 9000
subset: v1
weight: 80
- destination:
host: tcp-echo
port:
number: 9000
subset: v2
weight: 20
上面的 VirtualService
对象也很简单,给 v1
子集服务加了 80% 的权重,另外有 20% 的权重被路由到了 v2
这个子集服务中去了,同样直接创建上面的资源对象更新路由规则:
$ kubectl apply -f samples/tcp-echo/tcp-echo-20-v2.yaml
virtualservice.networking.istio.io/tcp-echo configured
等待几秒钟,让新的路由规则生效。然后同样用上面的测试命令向 tcp-echo 服务发送 TCP 请求:
$ for i in {1..10}; do docker run -e INGRESS_HOST=$INGRESS_HOST \
-e INGRESS_PORT=$INGRESS_PORT -i --rm busybox sh -c "(date; sleep 1) | nc \
$INGRESS_HOST $INGRESS_PORT"; done
one Tue Feb 8 08:50:41 UTC 2022
one Tue Feb 8 08:50:45 UTC 2022
one Tue Feb 8 08:50:48 UTC 2022
one Tue Feb 8 08:50:51 UTC 2022
one Tue Feb 8 08:50:54 UTC 2022
one Tue Feb 8 08:50:57 UTC 2022
two Tue Feb 8 08:51:00 UTC 2022
one Tue Feb 8 08:51:03 UTC 2022
one Tue Feb 8 08:51:07 UTC 2022
one Tue Feb 8 08:51:10 UTC 2022
现在我们可以发现,有大约 20% 的流量时间戳前缀是 two
,这意味着有 80% 的 TCP 流量路由到了 tcp-echo 服务的 v1 版本,与此同时有 20% 流量路由到了 v2 版本。
设置请求超时
HTTP 请求的超时可以用路由规则的 timeout 字段来指定。默认情况下,超时是禁用的,本任务中,会把 reviews
服务的超时设置为 1 秒。为了观察效果,还需要在对 ratings
服务的调用上人为引入 2 秒的延迟。
首先,使用samples/bookinfo/networking/virtual-service-all-v1.yaml
初始化版本路由:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
virtualservice.networking.istio.io/productpage created
virtualservice.networking.istio.io/reviews created
virtualservice.networking.istio.io/ratings created
virtualservice.networking.istio.io/details created
刷新BookInfo页面,Reviews 展示的是 v1 版本 Ratings(没有任何星星)。
这时候将请求路由到 reviews
服务的 v2 版本,它会发起对 ratings
服务的调用:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
EOF
给对 ratings
服务的调用添加 2 秒的延迟:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
delay:
percent: 100
fixedDelay: 2s
route:
- destination:
host: ratings
subset: v1
EOF
可以看到 Bookinfo 应用运行正常(显示了评级的星型符号),但是每次刷新页面,都会有 2 秒的延迟。
现在给对 reviews
服务的调用增加一个半秒的请求超时:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
timeout: 0.5s
EOF
刷新BookInfo页面,这时候应该看到 1 秒钟就会返回,而不是之前的 2 秒钟,但 reviews
是不可用的。
本任务中,使用 Istio 为对 reviews
微服务的调用配置了半秒的请求超时。默认情况下请求超时是禁用的。reviews
服务在处理请求时会接着调用 ratings
服务,用 Istio 在对 ratings
的调用中注入了两秒钟的延迟,这样就让 reviews
服务要花费超过半秒的时间来完成调用,因此可以观察到超时。
可以观察到,Bookinfo 的页面(调用 reviews
服务来生成页面)没显示评论,而是显示了消息:Sorry, product reviews are currently unavailable for this book. 这就是它收到了来自 reviews
服务的超时错误信息。
productpage
微服务在调用 reviews
微服务时,还有它自己的应用级的超时(3 秒)设置。注意在本任务中使用 Istio 路由规则设置了半秒的超时。如果将超时设置为大于 3 秒(比如 4 秒),则超时将不会有任何影响,因为这两个超时的限制性更强。
还有一点关于 Istio 中超时控制方面的补充说明,除了像本文一样在路由规则中进行超时设置之外,还可以进行请求一级的设置,只需在应用的对外请求中加入 x-envoy-upstream-rq-timeout-ms
请求头即可。在这个请求头中的超时设置单位是毫秒而不是秒。
最后,删除应用程序的路由规则:
$ kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml
熔断
熔断是创建弹性微服务应用程序的重要模式,熔断能够使我们的应用程序具备应对来自故障、潜在峰值和其他未知网络因素影响的能力,熔断在微服务框架中也是必备的一个功能。
同样在 Istio 中也是原生就支持熔断功能的,首先部署示例服务 samples/httpbin/httpbin.yaml
:
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
如果i起了Sidecar自动注入,直接使用以下命令部署httpbin服务:
$ kubectl apply -f samples/httpbin/httpbin.yaml
serviceaccount/httpbin created
service/httpbin created
deployment.apps/httpbin created
$ kubectl get pods -l app=httpbin
NAME READY STATUS RESTARTS AGE
httpbin-74fb669cc6-dgjtv 2/2 Running 0 5m27s
否则,必须在部署 httpbin
应用程序前进行手动注入,部署命令如下:
$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml)
应用部署完成后,接着创建一个目标规则,在调用 httpbin 服务时配置熔断设置。
这里通过一个 DestinationRule
对象来创建一个如下所示的资源对象:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1 # 最大连接数为1
http:
http1MaxPendingRequests: 1 # 最大请求数为1
maxRequestsPerConnection: 1
outlierDetection:
consecutiveErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
EOF
目标规则创建完成后,接着我们来创建一个客户端应用来发送流量请求到 httpbin
服务,这里我们使用一个名为 Fortio 的测试客户端应用,该应用可以控制连接数、并发数及发送 HTTP 请求的延迟,通过 Fortio 能够有效的触发前面在 DestinationRule
中设置的熔断策略。
客户端应用资源清单文件位于 samples/httpbin/sample-client/fortio-deploy.yaml
,内容如下所示:
apiVersion: v1
kind: Service
metadata:
name: fortio
labels:
app: fortio
service: fortio
spec:
ports:
- port: 8080
name: http
selector:
app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fortio-deploy
spec:
replicas: 1
selector:
matchLabels:
app: fortio
template:
metadata:
annotations:
# This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats
# in addition to the stats normally served by Istio. The Circuit Breaking example task
# gives an example of inspecting Envoy stats via proxy config.
proxy.istio.io/config: |-
proxyStatsMatcher:
inclusionPrefixes:
- "cluster.outbound"
- "cluster_manager"
- "listener_manager"
- "server"
- "cluster.xds-grpc"
labels:
app: fortio
spec:
containers:
- name: fortio
image: fortio/fortio:latest_release
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http-fortio
- containerPort: 8079
name: grpc-ping
直接部署上面的客户端应用,记得注入 Istio Sidecar 代理,以便 Istio 对其网络交互进行管理:
$ kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml
service/fortio created
deployment.apps/fortio-deploy created
部署完成后我们可以进入到客户端应用中使用 Fortio
工具调用 httpbin
服务。-curl
参数表明发送一次调用:
$ export FORTIO_POD=$(kubectl get pod | grep fortio | awk '{ print $1 }')
$ kubectl exec -it $FORTIO_POD -c fortio -- /usr/bin/fortio load -curl http://httpbin:8000/get
由于在windows下,git bash
运行上面的命令会报错。这里选了个折中 的办法,同样可以达到观察效果,在git bash
下执行kubectl get pod | grep fortio | awk '{ print $1 }
获取到pod的名称,或者直接kubectl get pod
获取。对命令进行拼接,将$FORTIO_POD
换成获取到的Pod名,然后在CMD中执行:
$ kubectl exec -i fortio-deploy-687945c6dc-rp7gf -c fortio -- /usr/bin/fortio load -curl http://httpbin:8000/get
HTTP/1.1 200 OK
server: envoy
date: Tue, 08 Feb 2022 10:25:39 GMT
content-type: application/json
content-length: 594
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 2
{
"args": {},
"headers": {
"Host": "httpbin:8000",
"User-Agent": "fortio.org/fortio-1.17.1",
"X-B3-Parentspanid": "92ea278cc8d9e481",
"X-B3-Sampled": "1",
"X-B3-Spanid": "bc19af06a43cba86",
"X-B3-Traceid": "22995ef1fa7574e892ea278cc8d9e481",
"X-Envoy-Attempt-Count": "1",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=ac2d4f98ba69b6e3119f9ea6356cad51805b7a132ac08ba208786ab0229bf2cb;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
},
"origin": "127.0.0.6",
"url": "http://httpbin:8000/get"
}
可以看到调用后端服务的请求是成功的,然后我们来测试下熔断。
在 DestinationRule
配置中,我们定义了 maxConnections: 1
和 http1MaxPendingRequests: 1
,表示如果并发的连接和请求数超过一个,则在 istio-proxy
进行进一步的请求和连接时,后续的请求或连接将被阻止。
比如这里我们发送并发数为 2 的连接(-c 2),请求 20 次(-n 20)来观察下现象:
$ kubectl exec -it $FORTIO_POD -c fortio /usr/bin/fortio -- load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
当然也可以通过 istio-proxy
状态来了解更多关于熔断的详情,使用如下命令进行查看:
$ kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending
最后清理规则和下限httpbin服务和客户端:
$ kubectl delete deploy httpbin fortio-deploy
$ kubectl delete svc httpbin
镜像
流量镜像,也称为影子流量,是一个以尽可能低的风险为生产带来变化的强大的功能。镜像会将实时流量的副本发送到镜像服务。镜像流量发生在主服务的关键请求路径之外。
在此任务中,首先把流量全部路由到测试服务的 v1
版本。然后,执行规则将一部分流量镜像到 v2
版本。
首先部署 v1
版本的 httpbin 服务:
$ cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-v1
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
ports:
- containerPort: 80
EOF
部署v2版本的httpbin服务:
cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-v2
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v2
template:
metadata:
labels:
app: httpbin
version: v2
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
ports:
- containerPort: 80
EOF
创建一个 httpbin 的 Service 对象,关联上面的两个版本服务:
kubectl create -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
EOF
启动 sleep
服务,这样就可以使用 curl
来提供负载:
$ cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: curlimages/curl
command: ["/bin/sleep","infinity"]
imagePullPolicy: IfNotPresent
EOF
默认情况下,Kubernetes 在 httpbin
服务的两个版本之间进行负载均衡。在此步骤中会更改该行为,把所有流量都路由到 v1
版本。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- httpbin
http:
- route:
- destination:
host: httpbin
subset: v1
weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
EOF
上面对象创建完成后,所有流量都会转到 httpbin:v1
服务下面去,使用如下所示的命令来测试以下:
$ export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
$ kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers' | python -m json.tool
{
{
"headers": {
"Accept": "*/*",
"Host": "httpbin:8000",
"User-Agent": "curl/7.81.0-DEV",
"X-B3-Parentspanid": "58dc016ffa182ab2",
"X-B3-Sampled": "1",
"X-B3-Spanid": "784a42ac78511bb3",
"X-B3-Traceid": "9c1177945f2c5dd658dc016ffa182ab2",
"X-Envoy-Attempt-Count": "1",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=aeee5046ea2b74f8127a4b2600232d473a29eb99dfcb0f163e27c2631c902f86;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
}
}
然后可以分别查看 httpbin 服务 v1
和 v2
两个 Pods 的日志,正常情况下只会看到 v1
会产生日志,v2
中没有:
$ export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
$ kubectl logs -f $V1_POD -c httpbin
127.0.0.6 - - [08/Feb/2022:11:21:15 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
......
$ export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
$ kubectl logs -f $V2_POD -c httpbin
这是因为上面我们创建的路由规则是将所有的请求都路由到了 v1
这个版本的服务中去。接下来我们更改下流量规则,将流量镜像到 v2
版本的服务中去:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- httpbin
http:
- route:
- destination:
host: httpbin
subset: v1
weight: 100
mirror:
host: httpbin
subset: v2
mirror_percent: 100
EOF
这个路由规则会发送 100% 流量到 v1
这个子集服务,然后通过 mirror
属性配置了将流量也 100% 镜像到了 httpbin:v2
服务。当流量被镜像时,请求将发送到镜像服务中,并在 headers 中的 Host/Authority
属性值上追加 -shadow
。
需要注意的是这些被镜像的流量是 即发即弃
的,也就是说镜像请求的响应会被丢弃。
现在我们在用上面的命令来测试发送一次请求:
$ kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers' | python -m json.tool
现在就可以看到 v1
和 v2
中都有了访问日志,v2
中的访问日志就是由镜像流量产生的,这些请求的实际目标是 v1
。
$ kubectl logs -f $V1_POD -c httpbin
127.0.0.6 - - [08/Feb/2022:11:21:15 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
127.0.0.6 - - [08/Feb/2022:11:28:41 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
$ kubectl logs -f $V2_POD -c httpbin
127.0.0.6 - - [08/Feb/2022:11:28:41 +0000] "GET /headers HTTP/1.1" 200 569 "-" "curl/7.81.0-DEV"
最后删除规则以及关闭Httpbin服务端和客户端:
$ kubectl delete virtualservice httpbin
$ kubectl delete destinationrule httpbin
$ kubectl delete deploy httpbin-v1 httpbin-v2 sleep
$ kubectl delete svc httpbin
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY