小Cの已经记不起来的博客

正则表达式进阶:蓝绿部署 匹配技巧

项目最近要从单环境切到蓝绿部署,本来以为改改 upstream 就完事了,结果在网关匹配上折腾了一下午。蓝绿部署听着简单,不就是两套环境切来切去吗?真到 Nginx / Ingress 里写正则,手一抖,流量就跑到还没验证的那套。下面是我这边实测能用的匹配方式,主要拿 Nginx 和 Ingress-nginx 举例,别的网关思路差不多。

先说场景:蓝绿不是两个域名那么简单

有的团队直接蓝绿两个域名,切换 DNS,最省事。但更多时候不想暴露 blue.xxx.com / green.xxx.com,而是同一个入口,靠 header、cookie 或者路径来分。比如默认走蓝,带 X-Env: green 走绿;或者 /blue/api/green/api。这时候正则就不是“能匹配就行”,而是“只能匹配该匹配的”。

第一个坑:blue 会匹配到 blueberry

我最开始图省事,写了个:

if ($http_x_env ~ blue) {
    proxy_pass http://backend_blue;
}

结果 blueberry 也进来了。蓝绿部署里这种误匹配很要命,尤其是测试同学随手传了个 X-Env: greenish,流量直接飘走。正确做法是加锚点和边界,而且别用 if,用 map

map $http_x_env $backend_pool {
    default      backend_blue;
    ~*^blue$     backend_blue;
    ~*^green$    backend_green;
}

~* 是不区分大小写,^$ 卡住整段值。如果 header 值可能带空格,还得写 ~*^\s*green\s*$,别问,问就是被浏览器或者 SDK 坑过。

Cookie 匹配:别写 .*env=blue.*

Nginx 有 $cookie_env,如果 cookie 名就叫 env,直接:

map $cookie_env $backend_pool {
    default      backend_blue;
    ~*^green$    backend_green;
}

但如果你要解析整个 $http_cookie,千万别写:

~*env=blue

它会匹配 notenv=blue,也会匹配 env=blueberry。要写成:

~*(?:^|;\s*)env=blue(?:;|$)

这里 (?:...) 是非捕获组,不影响你,但看着干净。本地可以拿这个测:

printf '%s\n' 'env=blue' 'env=blueberry' 'foo=1; env=blue; bar=2' 'foo=1; notenv=blue' | grep -P '(?:^|;\s*)env=blue(?:;|$)'

能稳稳只留下第一个和第三个。注意 cookie 名一般大小写敏感,想严谨就别用 ~*,用 ~

Nginx map + upstream:按 header 切

完整一点大概这样:

upstream backend_blue {
    server 10.0.0.11:8080;
}

upstream backend_green {
    server 10.0.0.12:8080;
}

map $http_x_env $backend_pool {
    default      backend_blue;
    ~*^blue$     backend_blue;
    ~*^green$    backend_green;
}

server {
    listen 80;

    location / {
        proxy_pass http://$backend_pool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

proxy_pass http://$backend_pool; 这里变量最后拼出来是 upstream 名,Nginx 能认。但如果你后面又加了 resolver,别把自己绕进去。想按 cookie 切,把 $http_x_env 换成 $cookie_env 就行。

路径蓝绿:命名捕获别把 /blue 带给后端

如果 URL 里带环境,比如 /blue/api/user 要转到蓝环境的 /api/user,我试过这种:

location ~ ^/(?<env>blue|green)(?<rest>/.*)$ {
    proxy_pass http://backend_$env$rest$is_args$args;
}

能用,但变量拼 upstream 名和 URI 的时候,规则稍微一变就很容易出双斜杠或者丢参数。后来我改成两个 location,省心:

location ~ ^/blue(/.*)$ {
    proxy_pass http://backend_blue$1$is_args$args;
}

location ~ ^/green(/.*)$ {
    proxy_pass http://backend_green$1$is_args$args;
}

这里 $1 是正则捕获的 /api/user 这类东西,$is_args$args 把查询串带上。注意 ^/blue(/.*)$ 不匹配 /blue 本身,如果非要匹配,得写 ^/blue(?:/(.*))?$,但 proxy_pass 里处理空捕获又麻烦,不如直接 return 301 /blue/

Ingress-nginx:canary 注解里的正则

K8s 里用 Ingress-nginx 的话,蓝绿可以拿 canary 注解凑。主 Ingress 是蓝,另一个 Ingress 加:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "X-Env"
    nginx.ingress.kubernetes.io/canary-by-header-value: "^(?i)green$"

canary-by-header-value 是支持正则的,但别默认它大小写不敏感,想不敏感就自己写 (?i)。还有,canary-by-cookie 一般只能认 alwaysnever,想按自定义 cookie 值切,还是用 header 或者在外面自己写网关。蓝绿不是金丝雀,别顺手把按比例切流打开,不然就不是蓝绿了。

测试正则:眼睛真的靠不住

Nginx 用的是 PCRE,本地可以用 grep -P 或者 pcre2grep 先跑一遍:

printf '%s\n' 'green' 'Green' 'greenhouse' 'green ' | grep -P '^(?i)green$'

greenGreen 会出来,greenhouse 不会,green 也不会。如果你允许前后空格,就写 ^\s*(?i:green)\s*$。改完配置一定 nginx -t,但 nginx -t 只保语法,不保匹配结果。最稳的是 curl -H 'X-Env: green' 打几个请求,看日志里的 $backend_pool 到底是什么。

最后碎碎念

蓝绿部署里正则匹配的核心就一句:把用户可控的东西当输入,别相信它长得像你以为的样子。锚点、边界、非捕获组、大小写,该上就上。bluegreen 这种词太短,出事特别隐蔽。我现在写完都会拿 greenhousenotgreengreen 这种脏数据跑一遍,过了才敢 reload。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放