Nginx基于cookies控制流量的灰度发布

前言:

        因为公司业务发版需要进行灰度发布,基于一定的权重来指向用户流量到不同的版本应用中,这里用nginx的两种不同的方法来实现。

参考:

        nginx会话保持之sticky模块 - 天生帅才 - 博客园 (cnblogs.com)

方法一:利用Nginx-sticky

我将在这里介绍容器化的nginx配置,如果是主机部署的nginx则会简单点(需添加了sticky模块的nginx),下面可提供参考。

部署一个deployment并将配置文件挂载到对应configmap。镜像名为:runnable/sticky-nginx:v1.8.1

对应关系:

/etc/nginx/nginx.conf => nginx-conf(nginx.conf)

/etc/nginx/conf.d => conf-d(*.conf)

1.首先我们创建一个比较通用的nginx.conf配置文件内容的ConfigMap:

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-conf
  namespace: proxy
data:
  nginx.conf: |-
    # 设置工作进程数为 4。通常这个值设置为 CPU 核心数,但根据负载情况可以调整。
    worker_processes  4;

    # 将错误日志输出到标准错误流,以便容器化环境中能捕获和管理日志。
    error_log  /dev/stderr notice;

    events {
        # 设置每个工作进程的最大连接数为 1024。
        worker_connections  1024;
    }

    http {
        # 包含 mime.types 文件,该文件定义了各种文件扩展名和 MIME 类型的映射。
        include       mime.types;

        # 包含 conf.d 目录下的所有配置文件。方便分模块管理配置。
        include       conf.d/*.conf;

        # 设置默认的 MIME 类型为 application/octet-stream。用于未定义 MIME 类型的文件。
        default_type  application/octet-stream;

        # 启用 sendfile 选项,提高文件传输效率。
        sendfile        on;

        # 设置 keep-alive 超时时间为 65 秒。这个值可以根据需要调整。
        keepalive_timeout  65;

        # 定义日志格式,命名为 main。日志格式包含客户端 IP、用户信息、请求时间、请求行、响应状态、发送字节数、引用页面、用户代理、转发信息、Cookie 和上游服务器地址。
        log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for" "$http_cookie" => "$upstream_addr"';

        # 将访问日志输出到标准输出流,以便容器化环境中能捕获和管理日志。
        access_log /dev/stdout main;

        server {
            # 监听 80 端口。
            listen       80;

            # 设置服务器名称为 localhost。
            server_name  localhost;

            location / {
                # 设置网站根目录为 html。
                root   html;

                # 设置默认首页文件为 index.html 和 index.htm。
                index  index.html index.htm;
            }

            # 定义错误页面,当出现 500、502、503、504 错误时,重定向到 /50x.html。
            error_page   500 502 503 504  /50x.html;
            
            location = /50x.html {
                # 设置错误页面的根目录为 html。
                root   html;
            }
        }
    }

    # 禁用 Nginx 守护进程模式,使 Nginx 以前台模式运行。这在容器化环境中非常有用,可以确保日志输出到标准输出。
    daemon off;

2.再创建一个针对我们灰度发布所需要的配置文件内容的ConfigMap

kind: ConfigMap
apiVersion: v1
metadata:
  name: conf-d
  namespace: proxy
data:
  test-sticky.conf: |-
    upstream backend {
       sticky;    #使用sticky自动set-cookie来控制流量指向哪个后端,后面有介绍用法,默认可用此
       server your_backend_server1 weight=3; #30%流量到后端服务1
       server your_backend_server2  weight=7; #70%流量到后端服务2
       #可以通过权重来平滑过渡,因为是k8s容器部署的nginx,所以平滑度会比主机部署的nginx效果好一点,理论上用户是完全无感的。
    }
    server {
        listen 80;
        server_name your_domain.com;
        location ~/ {
            proxy_pass http://backend;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $host;
        }
    }

3.创建Nginx  Deployment

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx-sticky
  namespace: proxy
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      volumes:
        - name: nginx-conf
          configMap:
            name: conf-d #configmap名对应的是目录
            items:
              - key: test.conf
                path: ./test.conf
            defaultMode: 420
        - name: nginx-conf-d
          configMap:
            name: nginx-conf #configmap名对应的是文件
            defaultMode: 420
      containers:
        - name: nginx
          image: 'runnable/sticky-nginx:v1.8.1'
          ports:
            - name: http-0
              containerPort: 80
              protocol: TCP
          resources: {}
          volumeMounts:
            - name: nginx-conf
              readOnly: true
              mountPath: /etc/nginx/conf.d
            - name: nginx-conf-d
              readOnly: true
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          imagePullPolicy: IfNotPresent
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      dnsPolicy: ClusterFirst
      serviceAccountName: default
      serviceAccount: default
      securityContext: {}
      schedulerName: default-scheduler
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

 部署好Nginx容器后我们可以做出尝试。

4.效果如图

5.sticky的更多配置

# 参数,解析

sticky [name=route] [domain=.foo.bar] [path=/] [expires=1h] 
       [hash=index|md5|sha1] [no_fallback] [secure] [httponly];
[name]       设置用来记录会话的cookie名称 默认是route
[domain=.foo.bar]    设置cookie作用的域名
[path=/]          设置cookie作用的URL路径,默认根目录
[expires=1h]        设置cookie的生存期,默认不设置,浏览器关闭即失效,需要是大于1秒的值
[hash=index|md5|sha1]   设置cookie中服务器的标识是用明文还是使用md5值,默认使用md5
[no_fallback]       设置该项,当sticky的后端机器挂了以后,nginx返回502 (Bad Gateway or Proxy Error) ,而不转发到其他服务器,不建议设置
[secure]          设置启用安全的cookie,需要HTTPS支持
[httponly]         允许cookie不通过JS泄漏,没用过


方法二:利用逻辑判断来控制cookie的办法 

我们可以利用nginx的if语句和add_header来进行配置。

我们通过正则判断cookies是否含有我们的关键cookie信息,要注意正则判断容易出现的涵盖情况(这里我踩过一个问题,例如我需要鉴别router=a或router=ab,if判断a的时候,前者成立,if判断ab,两者都成立(因为是判断"始于"),所以可以注意if判断顺序或正则表达再精确点。)

# 定义两个上游服务器,分别是 webserver1 和 webserver2
upstream webserver1 {
    server 192.168.66.1;
}

upstream webserver2 {
    server 192.168.2.1;
}

# 使用map来定义后端目标
map $http_cookie $route_cookie {
    default "";
    "~*route=webserver1" "webserver1";
    "~*route=webserver2" "webserver2";
}

# 使用时间字符串来进行伪随机流量划分,70% 的流量转发到 webserver1,30% 的流量转发到 webserver2
split_clients "${date_gmt}" $route_cookie_endpoint {
    70%    "webserver1";
    30%    "webserver2";
}

server {
    listen 80;
    server_name your_domain.com; # 监听域名

    location / {
        # 如果请求中没有包含名为 "route" 的 cookie,则添加一个新的 cookie,并根据流量划分规则设置后端服务器
        if ($route_cookie = "" ) {
            set $route_cookie $route_cookie_endpoint;
            add_header Set-Cookie "route=$route_cookie; Path=/; Max-Age=3600;" always;
        }

        # 根据正则判断,将请求转发到相应的上游服务器
        if ($route_cookie = "webserver1") {
            proxy_pass http://webserver1;
        }

        if ($route_cookie = "webserver2") {
            proxy_pass http://webserver2;
        }

        # 设置代理相关的头部信息
        proxy_set_header Accept-Encoding "gzip";
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_read_timeout 7200s;
        proxy_send_timeout 7200s;
        proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
        proxy_next_upstream_tries 5;
        
        # 添加 CORS 支持
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-Version,clienttoken,x-timestamp,X-Sign,tenant-id,x-access-token,X-Token,storeid,contenttype';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    }
}

配置解析:

1.首先我定义了两个版本的应用为webserver1 和webserver2,在此基础上可以增加更多的服务器再控制权重来达到负载均衡。

2.使用map提取出cookies中我们会被灰度命中的cookie,如果没有则为空。

3.使用split_clients函数和date_gmt变量来实现伪随机的效果(nginx默认应该没有真随机的函数)

4.通过三个判断,第一个判断是否为空,空则伪随机分配并设置好相应cookie;第二个第三个则根据cookie来控制流量的版本指向;


文章写的比较粗糙,如果有其他问题欢迎交流讨论啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值