正则表达式进阶:蓝绿部署 匹配技巧
项目最近要从单环境切到蓝绿部署,本来以为改改 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 一般只能认 always 和 never,想按自定义 cookie 值切,还是用 header 或者在外面自己写网关。蓝绿不是金丝雀,别顺手把按比例切流打开,不然就不是蓝绿了。
测试正则:眼睛真的靠不住
Nginx 用的是 PCRE,本地可以用 grep -P 或者 pcre2grep 先跑一遍:
printf '%s\n' 'green' 'Green' 'greenhouse' 'green ' | grep -P '^(?i)green$'
green、Green 会出来,greenhouse 不会,green 也不会。如果你允许前后空格,就写 ^\s*(?i:green)\s*$。改完配置一定 nginx -t,但 nginx -t 只保语法,不保匹配结果。最稳的是 curl -H 'X-Env: green' 打几个请求,看日志里的 $backend_pool 到底是什么。
最后碎碎念
蓝绿部署里正则匹配的核心就一句:把用户可控的东西当输入,别相信它长得像你以为的样子。锚点、边界、非捕获组、大小写,该上就上。blue 和 green 这种词太短,出事特别隐蔽。我现在写完都会拿 greenhouse、notgreen、green 这种脏数据跑一遍,过了才敢 reload。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。