• 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
    

    另外还有用于生产或性能测试的配置组合。

    1. default:根据 IstioOperator API 的默认设置启动组件。 建议用于生产部署和 Multicluster Mesh 中的 Primary Cluster。

      您可以运行 istioctl profile dump 命令来查看默认设置。

    2. demo:这一配置具有适度的资源需求,旨在展示 Istio 的功能。 它适合运行 Bookinfo 应用程序和相关任务。

      此配置文件启用了高级别的追踪和访问日志,因此不适合进行性能测试。

    3. minimal:与默认配置文件相同,但只安装了控制平面组件。 它允许您使用 Separate Profile 配置控制平面和数据平面组件(例如 Gateway)。

    4. remote:配置 Multicluster Mesh 的 Remote Cluster。

    5. empty:不部署任何东西。可以作为自定义配置的基本配置文件。

    6. 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 个红色星形图标来显示评分信息。

    image

    上图展示了使用 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 访问应用了:

    image

    刷新页面可以看到 Book Reviews 发生了改变,因为每次请求会被路由到到了不同的 Reviews 服务版本去:

    image

    至此,整个 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 失败了:

    image

    这是因为我们还没有创建 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 的配置成功了。

    image

    基于用户身份的路由

    同样,将上面创建的 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 的弹性,我们将为用户 jasonreviews:v2ratings 服务之间注入一个 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 后,出现如下界面:

    image

    期望 Bookinfo 主页在大约 7 秒钟加载完成并且没有错误。 但是出现了一个问题:Reviews 部分显示了错误消息:

    Sorry, product reviews are currently unavailable for this book.
    

    按照预期,我们引入的 7 秒延迟不会影响到 reviews 服务,因为 reviewsratings 服务间的超时被硬编码为 10 秒。 但是,在 productpagereviews 服务之间也有一个 3 秒的硬编码的超时,再加 1 次重试,一共 6 秒。 结果,productpagereviews 的调用在 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 对象中声明了 v1v2 两个子集服务,在 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: 1http1MaxPendingRequests: 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 服务 v1v2 两个 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
    

    现在就可以看到 v1v2 中都有了访问日志,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
    
  • 相关阅读:
    用命令行执行ROBOT FRAMEWORK
    RF ---library
    RF--- selenium
    RIDE指定log和report的输出目录
    robotframework运行时后台报错UnicodeDecodeError
    rf-demos (request)
    RobotFramework --RIDE介绍
    基于RFS(robot framework selenium)框架模拟POST/GET请求执行自动化接口测试
    volatile非原子性示例
    wait()方法写在while循环中可以在线程接到通知后再一次判断条件
  • 原文地址:https://www.cnblogs.com/huiyichanmian/p/15872696.html
Copyright © 2020-2023  润新知