背景介绍

Ingress作为K8S集群中一种独立的组件,我们需要通过创建它来控制外部访问流量的策略,并通过Ingress Controller将其分配到一个或多个SVC中。K8S官方维护的Controller为Nginx Ingress,该Ingress支持通过配置注解(英文:annotations)的方式来实现蓝绿发布、灰度发布和A/B测试等场景。

使用案例

使用Nginx Ingress实现灰度发布适用场景主要取决于业务流量切分的策略,目前Nginx Ingress支持基于Header、Cookie和服务权重三种流量切分的策略,基于这三种策略可实现以下两种发布场景:

案例一:切分部分用户流量到新版本

假设线上已运行了一套对外提供七层服务的服务A,此时开发了一些新的特性,需要发布上线一个新的版本服务A',但又不想直接替换原有的服务A,而是期望将Header中含有foo=bar或Cookie中包含foo=bar用户的请求,转发到新版本服务A'中。等运行稳定一段时间后,再逐步全量上新版本的服务,实现平滑下线旧版本服务。如下:

案例二:切分一定比例的流量到新版本

假如线上已运行了一套对外提供服务的服务B,在修复了一些bug之后,需发布为新版本的服务B',但此时又不想直接替换原来的服务B,而是期望将20%的流量,切换到新版本服务B'中。当运行一段时间稳定后,再逐步将所有的流量从旧版本切换到新版本中,从而实现旧版本的平滑下线。

注解说明

Nginx Ingress支持通过注解方式annotations来实现不同场景下的发布,可满足灰度发布、蓝绿发布、A/B测试等业务场景。具体实现过程如下:为服务创建两个Ingress,一个为常规Ingress,另一个为带nginx.ingress.kubernetes.io/canary: "true"注解的Ingress,称为Canary Ingress;为Canary Ingress配置流量切分策略annotation,两个Ingress相互配合,即可实现多种场景的发布和测试。Nginx Ingress的annotation支持以下几种规则

  • nginx.ingress.kubernetes.io/canary-by-header,基于Header的流量的切分,适用于灰度发布。当请求头中包含指定的header名称时,且值为“always”,那就会将该请求转发到Canary Ingress定义对应的后端服务。当值为“never”时,则不转发,适用于回滚到旧的服务版本。当为其他值时,则忽略该annotation,并通过优先级策略,将请求流量请求分发到其他的规则策略。

  • nginx.ingress.kubernetes.io/canary-weight,基于服务权重的流量切分,适用于蓝绿部署。比如,当设置为100,则表示所有流量都将会转发给Canary Ingress对应的后端服务。

  • nginx.ingress.kubernetes.io/canary-by-header-value,必须与canary-by-header一起使用,可自定义请求头的取值,包含但不限于“always”或“never”。当请求头的值命中指定的自定义值时,请求将会转发给Canary Ingress定义的对应后端服务,如果是其他值则忽略该annotation,并通过优先级将请求流量分配到其他规则。

  • nginx.ingress.kubernetes.io/canary-by-header-pattern,与canary-by-header-value类似,唯一区别是该annotation用正则表达式匹配请求头的值,而不是某一个固定值

  • nginx.ingress.kubernetes.io/canary-by-cookie,基于cookie的流量转发,适用于灰度发布,与canary-by-header类似,这个annotation用于cookie,仅支持“always”和“never”,无法自定义取值。

    操作说明

1、前置准备:

  • 使用Nginx Ingress作为实现灰度发布插件,所以首先需安装nginx-ingress插件作为Ingress Controller,并对外暴露统一的流量入口。

  • 已上传Nginx镜像至容器镜像服务。为方便观测流量切分效果,Nginx镜像包含新旧两个版本,欢迎页分别为“Old Nginx”和“New Nginx”。

2、资源创建方式说明:下面是提供通过yaml的方式部署Service和Deployment:将本文的示例yaml保存为文件,然后再执行对应的kubectl。例如:kubectl apply -f aaa.yaml。

同时部署两个相同版本的服务

在集群中部署两个版本的Nginx服务,并通过Nginx Ingress对外提供访问。

步骤1:创建第一个版本的Deployment和Service,本文以old-nginx为例。yaml示例如下:

apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: old-nginx 
spec: 
  replicas: 2 
  selector: 
    matchLabels: 
      app: old-nginx 
  template: 
    metadata: 
      labels: 
        app: old-nginx 
    spec: 
      containers: 
      - image: nginx:old
        name: container-0 
        resources: 
          limits: 
            cpu: 100m 
            memory: 200Mi 
          requests: 
            cpu: 100m 
            memory: 200Mi 
      imagePullSecrets: 
      - name: default-secret 

--- 

apiVersion: v1 
kind: Service 
metadata: 
  name: old-nginx 
spec: 
  selector: 
    app: old-nginx 
  ports: 
  - name: service0 
    targetPort: 80 
    port: 8080 
    protocol: TCP 
  type: NodePort

步骤2:创建第二个版本的Deployment和Service,这里以new-nginx为例。yaml示例如下:

apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: new-nginx 
spec: 
  replicas: 2 
  selector: 
    matchLabels: 
      app: new-nginx 
  template: 
    metadata: 
      labels: 
        app: new-nginx 
    spec: 
      containers: 
      - image: nginx:new
        name: container-0 
        resources: 
          limits: 
            cpu: 100m 
            memory: 200Mi 
          requests: 
            cpu: 100m 
            memory: 200Mi 
      imagePullSecrets: 
      - name: default-secret 

--- 

apiVersion: v1 
kind: Service 
metadata: 
  name: new-nginx 
spec: 
  selector: 
    app: new-nginx 
  ports: 
  - name: service0 
    targetPort: 80 
    port: 8080 
    protocol: TCP 
  type: NodePort

步骤3:创建Ingress,对外暴露服务,指向老版本的服务。yaml的示例如下:

apiVersion: networking.k8s.io/v1beta1 
kind: Ingress 
metadata: 
  name: gray-release 
  namespace: default 
  annotations: 
    kubernetes.io/ingress.class: nginx
    kubernetes.io/elb.port: '80' 
spec: 
  rules: 
    - host: www.example.com 
      http: 
        paths: 
          - path: '/' 
            backend: 
              serviceName: old-nginx 
              servicePort: 80

执行以下命令,进行访问验证。

curl -H "Host: www.example.com"  http://<EXTERNAL_IP>

其中,Nginx Ingress为对外暴露的IP地址。

预期执行结果如下:

Old Nginx

通过灰度的方式发布新版服务

设置访问新版本服务的流量切分策略。设置基于Header、Cookie和服务权重三种策略,来实现灰度发布和蓝绿发布,大家可以根据实际情况选择:

基于Header、Cookie和服务权重三种流量切分策略,均可实现灰度发布;基于服务权重的流量切分策略,当调整新服务权重为100%时,即可实现蓝绿发布。

一、基于Header头的流量切分

以下示例仅Header中包含Region且值为bj或gz的请求才能转发到新版本服务,模拟灰度新版本给北京和广州地域的用户。

创建Canary Ingress,指向新版本的后端服务,并增加annotation。

apiVersion: networking.k8s.io/v1beta1 
kind: Ingress 
metadata: 
  name: canary-ingress 
  namespace: default 
  annotations: 
    kubernetes.io/ingress.class: nginx 
    nginx.ingress.kubernetes.io/canary: "true" 
    nginx.ingress.kubernetes.io/canary-by-header: "Region" 
    nginx.ingress.kubernetes.io/canary-by-header-pattern: "bj|gz" 
spec: 
  rules: 
    - host: www.example.com 
      http: 
        paths: 
          - path: '/' 
            backend: 
              serviceName: new-nginx
              servicePort: 80

执行以下脚本进行访问的测试。

$ curl -H "Host: www.example.com" -H "Region: bj" http://<EXTERNAL_IP> 
New Nginx 
$ curl -H "Host: www.example.com" -H "Region: sh" http://<EXTERNAL_IP> 
Old Nginx 
$ curl -H "Host: www.example.com" -H "Region: gz" http://<EXTERNAL_IP> 
New Nginx 
$ curl -H "Host: www.example.com" http://<EXTERNAL_IP> 
Old Nginx

其中,为Nginx Ingress对外暴露的IP。

可以看出,仅当Header中包含Region且值为bj或gz的请求才由新版本服务响应。

二、基于Cookie的流量切分

以下例子是仅在Cookie中包含ingress_for_test的请求时,才能转发到新版本服务,模拟灰度新版本发布。

创建Canary Ingress,指向新版本的后端服务,并且增加annotation。

apiVersion: networking.k8s.io/v1beta1 
kind: Ingress 
metadata: 
  name: canary-ingress 
  namespace: default 
  annotations: 
    kubernetes.io/ingress.class: nginx 
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-cookie: "ingress_for_test"
    kubernetes.io/elb.port: '80' 
spec: 
  rules: 
    - host: www.example.com 
      http: 
        paths: 
          - path: '/' 
            backend: 
              serviceName: new-nginx
              servicePort: 80

执行以下脚本进行访问测试。

$ curl -s -H "Host: www.example.com" --cookie "ingress_for_test=always" http://<EXTERNAL_IP> 
New Nginx 
$ curl -s -H "Host: www.example.com" --cookie "user_from_gz=always" http://<EXTERNAL_IP> 
Old Nginx 
$ curl -s -H "Host: www.example.com" http://<EXTERNAL_IP> 
Old Nginx

其中,Nginx Ingress是对外暴露的IP地址。

可以看到,只有当Cookie中包含ingress_for_test且值为always的请求时,才会由新版本的服务进行响应。

三、基于服务权重的流量切分

例子一:仅允许20%的流量被转发到新版本服务中,实现灰度发布。

创建Canary Ingress,并增加annotation,将20%的流量导入新版本的后端服务。

apiVersion: networking.k8s.io/v1beta1 
kind: Ingress 
metadata: 
  name: canary-ingress 
  namespace: default 
  annotations: 
    kubernetes.io/ingress.class: nginx 
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "20"
    kubernetes.io/elb.port: '80' 
spec: 
  rules: 
    - host: www.example.com 
      http: 
        paths: 
          - path: '/' 
            backend: 
              serviceName: new-nginx
              servicePort: 80

执行以下命令,进行访问测试。

for i in {1..20}; do curl -H "Host: www.example.com" http://<EXTERNAL_IP>; done; 
Old Nginx 
Old Nginx 
Old Nginx 
New Nginx 
Old Nginx 
New Nginx 
Old Nginx 
New Nginx 
Old Nginx 
Old Nginx 
Old Nginx 
Old Nginx 
Old Nginx 
New Nginx 
Old Nginx 
Old Nginx 
Old Nginx 
Old Nginx 
Old Nginx 
Old Nginx

其中,为Nginx Ingress对外暴露的IP。

我们可以看到,这里有4/20的几率由新版本服务响应,符合20%服务权重的设置。

例子二:将允许所有的流量被转发到新版本服务中,实现蓝绿发布的测试。

通过创建Canary Ingress,并增加annotation,将100%的流量切到新版本的后端服务。

apiVersion: networking.k8s.io/v1beta1 
kind: Ingress 
metadata: 
  name: canary-ingress 
  namespace: default 
  annotations: 
    kubernetes.io/ingress.class: nginx 
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "100"
    kubernetes.io/elb.port: '80' 
spec: 
  rules: 
    - host: www.example.com 
      http: 
        paths: 
          - path: '/' 
            backend: 
              serviceName: new-nginx
              servicePort: 80

执行如下脚本进行访问测试,

for i in {1..10}; do curl -H "Host: www.example.com" http://<EXTERNAL_IP>; done; 
New Nginx 
New Nginx 
New Nginx 
New Nginx 
New Nginx 
New Nginx 
New Nginx 
New Nginx 
New Nginx 
New Nginx

其中,为Nginx Ingress对外暴露的IP。

从上面我们可以看到,这里所有的访问均是新版本服务来响应请求,从而成功实现了蓝绿版本发布。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐