14f2cf84eabf4cf4674cfbbc3cca2c57.gif

Ingress-Nginx是一个K8S ingress工具,支持配置Ingress Annotations来实现不同场景下的灰度发布和测试。NginxAnnotations 支持以下几种Canary规则:

假设我们现在部署了两个版本的服务,老版本和canary版本

nginx.ingress.kubernetes.io/canary-by-header:基于RequestHeader的流量切分,适用于灰度发布以及 A/B 测试。当RequestHeader 设置为 always时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never时,请求不会被发送到 Canary 入口。

nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 RequestHeader 的值,用于通知 Ingress 将请求路由到Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。

nginx.ingress.kubernetes.io/canary-weight:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。权重为60意味着60%流量转到canary。权重为 100 意味着所有请求都将被发送到 Canary 入口。

nginx.ingress.kubernetes.io/canary-by-cookie:基于 Cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。当 cookie 值设置为 always时,它将被路由到 Canary 入口;当 cookie 值设置为 never时,请求不会被发送到 Canary 入口。

部署两个版本的服务

这里以简单的 nginx 为例,先部署一个 v1 版本:

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-v1

spec:

  replicas: 1

  selector:

    matchLabels:

      app: nginx

      version: v1

  template:

    metadata:

      labels:

        app: nginx

        version: v1

    spec:

      containers:

      - name: nginx

        image:"openresty/openresty:centos"

        ports:

        - name: http

          protocol: TCP

          containerPort: 80

        volumeMounts:

        - mountPath:/usr/local/openresty/nginx/conf/nginx.conf

          name: config

          subPath: nginx.conf

      volumes:

      - name: config

        configMap:

          name: nginx-v1

---

apiVersion: v1

kind: ConfigMap

metadata:

  labels:

    app: nginx

    version: v1

  name: nginx-v1

data:

  nginx.conf: |-

    worker_processes  1;

    events {

        accept_mutex on;

        multi_accept on;

        use epoll;

        worker_connections  1024;

    }

    http {

        ignore_invalid_headers off;

        server {

            listen 80;

            location / {

                access_by_lua '

                    local header_str =ngx.say("nginx-v1")

                ';

            }

        }

    }

---

apiVersion: v1

kind: Service

metadata:

  name: nginx-v1

spec:

  type: ClusterIP

  ports:

  - port: 80

    protocol: TCP

    name: http

  selector:

    app: nginx

    version: v1

再部署一个 v2 版本:

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-v2

spec:

  replicas: 1

  selector:

    matchLabels:

      app: nginx

      version: v2

  template:

    metadata:

      labels:

        app: nginx

        version: v2

    spec:

      containers:

      - name: nginx

        image:"openresty/openresty:centos"

        ports:

        - name: http

          protocol: TCP

          containerPort: 80

        volumeMounts:

        - mountPath:/usr/local/openresty/nginx/conf/nginx.conf

          name: config

          subPath: nginx.conf

      volumes:

      - name: config

        configMap:

          name: nginx-v2

---

apiVersion: v1

kind: ConfigMap

metadata:

  labels:

    app: nginx

    version: v2

  name: nginx-v2

data:

  nginx.conf: |-

    worker_processes  1;

    events {

        accept_mutex on;

        multi_accept on;

        use epoll;

        worker_connections  1024;

    }

    http {

        ignore_invalid_headers off;

        server {

            listen 80;

            location / {

                access_by_lua '

                    local header_str =ngx.say("nginx-v2")

                ';

            }

        }

    }

---

apiVersion: v1

kind: Service

metadata:

  name: nginx-v2

spec:

  type: ClusterIP

  ports:

  - port: 80

    protocol: TCP

    name: http

  selector:

    app: nginx

    version: v2

再创建一个 Ingress,对外暴露服务,指向 v1 版本的服务:

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: nginx

  annotations:

    kubernetes.io/ingress.class: nginx

spec:

  rules:

  - host: canary.example.com

    http:

      paths:

      - backend:

          serviceName: nginx-v1

          servicePort: 80

        path: /

访问验证一下:

$ curl -H "Host:canary.example.com" http://EXTERNAL-IP # EXTERNAL-IP 替换为 NginxIngress 自身对外暴露的 IP

nginx-v1

基于 Header 的流量切分

创建 Canary Ingress,指定 v2 版本的后端服务,且加上一些 annotation,实现仅将带有名为 Region 且值为 cd 或 sz 的请求头的请求转发给当前 Canary Ingress,模拟灰度新版本给成都和深圳地域的用户:

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  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: "cd|sz"

  name: nginx-canary

spec:

  rules:

  - host: canary.example.com

    http:

      paths:

      - backend:

          serviceName: nginx-v2

          servicePort: 80

        path: /

测试访问:

$ curl -H "Host:canary.example.com" -H "Region: cd" http://EXTERNAL-IP #EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP

nginx-v2

$ curl -H "Host:canary.example.com" -H "Region: bj" http://EXTERNAL-IP

nginx-v1

$ curl -H "Host:canary.example.com" -H "Region: cd" http://EXTERNAL-IP

nginx-v2

$ curl -H "Host:canary.example.com" http://EXTERNAL-IP

nginx-v1

可以看到,只有 header Region 为 cd 或 sz 的请求才由 v2 版本服务响应。

基于 Cookie 的流量切分

与前面 Header 类似,不过使用 Cookie 就无法自定义 value 了,这里以模拟灰度成都地域用户为例,仅将带有名为 user_from_cd 的 cookie 的请求转发给当前 Canary Ingress 。先删除前面基于 Header 的流量切分的 Canary Ingress,然后创建下面新的 Canary Ingress:

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  annotations:

    kubernetes.io/ingress.class: nginx

    nginx.ingress.kubernetes.io/canary:"true"

   nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_cd"

  name: nginx-canary

spec:

  rules:

  - host: canary.example.com

    http:

      paths:

      - backend:

          serviceName: nginx-v2

          servicePort: 80

        path: /

测试访问:

$ curl -s -H "Host:canary.example.com" --cookie "user_from_cd=always"http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP

nginx-v2

$ curl -s -H "Host:canary.example.com" --cookie "user_from_bj=always" http://EXTERNAL-IP

nginx-v1

$ curl -s -H "Host:canary.example.com" http://EXTERNAL-IP

nginx-v1

可以看到,只有 cookie user_from_cd 为 always 的请求才由 v2 版本的服务响应。

基于服务权重的流量切分

基于服务权重的 Canary Ingress 就简单了,直接定义需要导入的流量比例,这里以导入 10% 流量到 v2 版本为例 (如果有,先删除之前的 Canary Ingress):

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  annotations:

    kubernetes.io/ingress.class: nginx

    nginx.ingress.kubernetes.io/canary:"true"

    nginx.ingress.kubernetes.io/canary-weight:"10"

  name: nginx-canary

spec:

  rules:

  - host: canary.example.com

    http:

      paths:

      - backend:

          serviceName: nginx-v2

          servicePort: 80

        path: /

测试访问:

$ for i in {1..10}; do curl -H"Host: canary.example.com" http://EXTERNAL-IP; done;

nginx-v1

nginx-v1

nginx-v1

nginx-v1

nginx-v1

nginx-v1

nginx-v2

nginx-v1

nginx-v1

nginx-v1

可以看到,大概只有十分之一的几率由 v2 版本的服务响应,符合 10% 服务权重的设置

END

微信公众号

7ee00558eb40bf36e67462ad2c09a1ad.png

作者微信:luckylucky421302

23d6f8604bc7080218c66f44b1a88f20.png

精彩文章推荐

K8s 常见问题

k8s超详细解读

K8s 超详细总结!

K8S 常见面试题总结

k8s控制器Deployment详细介绍:资源清单编写技巧

k8s安装隐患如何优化?从以下维度分享

使用k3s部署轻量Kubernetes集群快速教程

基于Jenkins和k8s构建企业级DevOps容器云平台

k8s原生的CI/CD工具tekton

K8S二次开发-自定义CRD资源

基于Jenkins+git+harbor+Helm+k8s+Istio构建DevOps流水线

基于k8s+Prometheus+Alertmanager+Grafana构建企业级监控告警系统

k8s开启临时容器ephemeral进行debug调试

k8s更新策略-系列文章第一篇:蓝绿发布

k8s更新策略-系列文章第二篇:滚动更新

k8s更新策略-系列文章第三篇:灰度发布

7648883685d81b218d204f7b9446204c.png

                   点亮,服务器10年不宕机e5e9799d1e8619b9df878efcbc35f266.gif

ea7602892b2b05dd9582040c6d161a72.gif 点击阅读 | 了解更多

Logo

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

更多推荐