回忆:应用无损发布

  • 滚动更新;
  • 蓝绿发布;
  • 灰度发布(金丝雀发布);

一、Ingress-Nginx Canary介绍

Nginx Ingress Controller 作为项目对外的流量入口和项目中各个服务的反向代理。

官方文档概述:https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary

Nginx Annotations 的几种 Canary 规则:

Annotation 说明
nginx.ingress.kubernetes.io/canary 必须设置该Annotation值为 true,否则其它规则将不会生效。
true:启用 canary功能。
false:不启用 canary功能。
nginx.ingress.kubernetes.io/canary- by-header 表示基于请求头的名称进行灰度发布。
请求头名称的特殊取值:
always :无论什么情况下,流量均会进入灰度服务。
never :无论什么情况下,流量均不会进入灰度服务。
若没有指定请求头名称的值,则只要该头存在,都会进行流量转发。
nginx.ingress.kubernetes.io/canary- by-header-value 表示基于请求头的值进行灰度发布。 需要与 canary-by-header头配合使用。
nginx.ingress.kubernetes.io/canary- by-header-pattern 表示基于请求头的值进行灰度发布,并对请求头 的值进行正则匹配。需要与 canary-by-header头配合使用。 取值为用于匹配请求头的值的正则表达式。
nginx.ingress.kubernetes.io/canary- by-cookie 表示基于Cookie进行灰度发布。例如,
nginx.ingress.kubernetes.io/canary-by-cookie: foo 。
Cookie内容的取值:
always :当 foo=always ,流量会进入灰度服务。
never :当 foo=never ,流量不会进入灰度服务。
只有当Cookie存在,且值为 always 时,才会进行流量转发。
nginx.ingress.kubernetes.io/canary- weight 表示基于权重进行灰度发布。
取值范围:0~权重总值。
若未设定总值,默认总值为100。
nginx.ingress.kubernetes.io/canary- weight-total 表示设定的权重总值。
若未设定总值,默认总值为100。

注意:不同灰度方式的优先级 由高到低 为:

canary-by-header --> canary-by-cookie --> canary-weight

二、ingress-nginx Canary实现

2.1 基于客户端请求的流量切分场景

需求:

假设当前线上环境,您已经有一套服务Service V1对外提供7层服务,此时上线了一些新 的特性,需要发布上线一个新的版本Service V2。

希望将请求头中包含 foo=bar或者Cookie中包含 foo=bar的客户端请求转发到Service V2服务中。

待运行一段时间稳定后,可将所有的流量从Service V1切换到Service V2服务中,再平滑 地将Service V1服务下线。

Aspose.Words.e4b07d36-e6eb-4c56-9733-b8ce4e5d9e1c.021

通过上面的annotation来实现灰度发布,其思路如下

  1. 在集群中部署两套系统,一套是stable版本(old-nginx),一套是canary版本(new- nginx),两个版本都有自己的service;
  2. 定义两个ingress配置,一个正常提供服务,一个增加canary的annotation;
  3. 待canary版本无误后,将其切换成stable版本,并且将旧的版本下线,流量全部接 入新的stable版本

old-nginx 创建Deployment、Service、Ingress。

[root@master01 6]# vim ingress-canary.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: old-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: old-nginx
  template:
    metadata:
      labels:
        run: old-nginx
    spec:
      containers:
        - name: old-nginx
          image: registry.cn-hangzhou.aliyuncs.com/abroad_images/old-nginx:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
              protocol: TCP
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: old-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    run: old-nginx
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: default
spec:
  ingressClassName: nginx
  rules:
    - host: nginx.zhang-qing.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: old-nginx
                port:
                  number: 80

# 应用
[root@master01 6]# kaf ingress-canary.yaml

测试验证:(预期输出:old)

[root@master01 6]# curl -H "Host: nginx.zhang-qing.com" http://nginx.zhang-qing.com
old

灰度发布新版本服务:

new-nginx 创建Deployment、Service。

[root@master01 6]# vim ingress-canary-new.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: new-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: new-nginx
  template:
    metadata:
      labels:
        run: new-nginx
    spec:
      containers:
        - name: new-nginx
          image: registry.cn-hangzhou.aliyuncs.com/abroad_images/new-nginx:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
              protocol: TCP
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: new-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    run: new-nginx
  sessionAffinity: None
  type: ClusterIP

# 应用
[root@master01 6]# kaf ingress-canary-new.yaml

设置访问新版本服务的路由规则。

  • 设置满足特定规则的客户端才能访问新版本服务。以下示例仅请求头中满足 foo=bar的客户端请求才能路由到新版本服务。
[root@master01 6]# vim ingress-canary-new-ing.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: new-nginx-ingress
  namespace: default
  annotations:
    #开启Canary
    nginx.ingress.kubernetes.io/canary: "true"
    #请求头为foo
    nginx.ingress.kubernetes.io/canary-by-header: "foo"
    #请求头为foo的值为bar时,请求才会被路由到新版本服务new-nginx
    nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
spec:
  ingressClassName: nginx
  rules:
    - host: nginx.zhang-qing.com  # 保持原域名(注意检查拼写)
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: new-nginx  # 关联新版服务
                port:
                  number: 80

#应用
[root@master01 6]# kaf ingress-canary-new-ing.yaml

按照header头信息转发流量:

#正常访问:
[root@master01 6]# curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
old

#访问新的服务:
[root@master01 6]# curl -H "Host: nginx.zhang-qing.com" -H "foo: bar" http://nginx.zhang-qing.com
new

可以看到,仅请求头中满足 foo=bar的客户端请求才能路由到新版本服务。

需求:请求头中满足 foo=bar的客户端请求,若不包含该请求头,再将50%的流量路由到新版本服务中。

# 修改ingress文件
[root@master01 6]# vim ingress-canary-new-ing.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: new-nginx-ingress
  namespace: default
  annotations:
    #开启Canary
    nginx.ingress.kubernetes.io/canary: "true"
    #请求头为foo
    nginx.ingress.kubernetes.io/canary-by-header: "foo"
    #请求头为foo的值为bar时,请求才会被路由到新版本服务new-nginx
    nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
    # 50%流量比例
    nginx.ingress.kubernetes.io/canary-weight: "50"
spec:
  ingressClassName: nginx
  rules:
    - host: nginx.zhang-qing.com
      http:
        paths:
          - path: /              # 路径匹配规则
            pathType: Prefix
            backend:
              service:
                name: new-nginx   # 关联新版服务
                port:
                  number: 80

#重新应用
[root@master01 6]# kaf ingress-canary-new-ing.yaml

测试验证:(几乎可以达到50%请求分布)

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
old

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
old

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
old

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
new

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
new

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com"  http://nginx.zhang-qing.com
old

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com" http://nginx.zhang-qing.com
old

系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。

为了达到该目标,需要将旧版本的Service指向新版本服务的Deployment,并且删除旧版本的Deployment和新版本的Service。

# 方式一:通过yaml文件进行修改
[root@master01 6]# vim ingress-canary.yaml
...
...
apiVersion: v1
kind: Service
metadata:
  name: old-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    # 指向新服务
    run: new-nginx
  sessionAffinity: None
  type: ClusterIP
...
...

# 方式二:通过edit进行修改
[root@master01 6]# k edit svc old-nginx
...
...
  selector:
    # 指向新服务
    run: new-nginx
...
...

重新测试,观察到预期输出都为new

[root@master01 6]#curl -H "Host: nginx.zhang-qing.com" http://nginx.zhang-qing.com
new