需求
- 根據(jù)提供的api路由規(guī)范灭美,確定后續(xù)嚴格遵循 /api/xxx 來請求志笼,通過固定匹配的FQDN導(dǎo)致來轉(zhuǎn)發(fā)
- 根據(jù)cookie來判斷選擇對應(yīng)的FQDN
- 當匹配cookie后,對應(yīng)的FQDN地址沒有上游server服務(wù)地址,則需要降級辽俗,轉(zhuǎn)發(fā)給默認的FQDN
location /api/bpm/ {
set $backend http://dmo-sso.default.svc.cluster.local;
if ($cookie_userTag ~ ^beta){
set $backend http://dmo-sso-beta.default.svc.cluster.local;
}
proxy_pass $backend;
}
思路
根據(jù)需求坐榆,可以使用openresty來實現(xiàn)拴魄,或者是使用基于nginx+resty_lua_module或者是ltengine+resty_lua_module來實現(xiàn),并且提前將對應(yīng)的路徑名存入redis席镀,來實現(xiàn)動態(tài)的獲取和轉(zhuǎn)發(fā)匹中,基于cookie可以拼接對應(yīng)的FQDN地址,檢測不存在的情況下豪诲,也可以通過自定義引入降級地址來轉(zhuǎn)發(fā)流量顶捷。
實現(xiàn)
Lua代碼
Lua腳本
-- 根據(jù)location第二段 /api/xxx ,來轉(zhuǎn)發(fā)到對應(yīng)名稱的后端FQDN
-- 分割字字符串,局部函數(shù)
local function split( str,reps )
local resultStrList = {}
string.gsub(str,'[^'..reps..']+',function ( w )
table.insert(resultStrList,w)
end)
return resultStrList
end
-- 通過請求uri屎篱,提取接口字符串服赎,并從redis里獲取對應(yīng)的值
local uri_data = ngx.var.uri
local key = split(ngx.var.host, '.')[1]
local field = split(uri_data, '/')[2]
local db = 0
local res = ngx.location.capture("/redis_hget", { args = { key = key , field = field , db = db } })
-- redis取值狀態(tài)判斷
if res.status ~= 200 then
ngx.log(ngx.ERR, "【redis server returned bad status】: ", res.status)
ngx.exit(res.status)
end
-- redis取值內(nèi)容非空判斷
if not res.body then
ngx.log(ngx.ERR, "【redis returned empty body】")
ngx.exit(500)
end
-- redis解析,多值返回 (redis無密碼)
local parser = require "redis.parser"
local results = parser.parse_replies(res.body, 2)
for i, result in ipairs(results) do
if i == 2 then
server = result[1]
typ = result[2]
end
end
-- 檢查結(jié)果類型
if typ ~= parser.BULK_REPLY or not server then
ngx.log(ngx.ERR, "【bad redis response】: ", res.body)
ngx.exit(500)
end
-- 自定義拼接FQDN
local default_fqdn = ".default.svc.cluster.local"
local target = server .. default_fqdn
-- 降級默認地址
local degrade_target = "dmo-apaas-gateway.default.svc.cluster.local"
-- cookie_userTag 檢測
local upstream = require "ngx.upstream"
local get_servers = upstream.get_servers
-- 獲取單個指定的userTag
local userTag = ngx.var.cookie_userTag
-- 判斷非空和空值
if( userTag ~= nil ) and ( userTag ~= "" ) then
ngx.log(ngx.NOTICE, "【userTag】: ", userTag)
cookie_target = server .. "-" .. userTag .. default_fqdn
ngx.log(ngx.NOTICE, "【cookie_target】: ", cookie_target)
local servers, err = get_servers(cookie_target)
-- 判斷FQDN地址對應(yīng)的后端服務(wù)是否存在
if( servers ~= nil ) then
ngx.var.target = cookie_target
else
ngx.log(ngx.ERR, "【failed to get servers in upstream】: ", err)
-- 降級
ngx.var.target = degrade_target
end
else
ngx.var.target = target
end
ngx.log(ngx.NOTICE, "【target】: ", ngx.var.target)
nginx配置
access_by_lua
階段交播,進行路由的動態(tài)轉(zhuǎn)發(fā)重虑,nginx中的rdis配置的是internal,只有nginx內(nèi)部才能調(diào)用秦士,
server {
listen 8080;
# 內(nèi)部訪問redis
location = /redis_hget {
internal;
set_unescape_uri $key $arg_key;
set_unescape_uri $field $arg_field;
set_unescape_uri $db $arg_db;
redis2_query select $db;
redis2_query hget $key $field;
redis2_pass redis-idgenerator.default.svc.cluster.local:16379;
}
# 接口
location ^~ /api/ {
set $target '';
access_by_lua_file lua/test.lua;
proxy_pass http://$target;
}
}
redis
從redis獲取對應(yīng)key的value嚎尤,來確定后續(xù)的轉(zhuǎn)發(fā)地址
redis中的值,可以動態(tài)的調(diào)整伍宦,初始芽死,根據(jù)nginx配置文件中已有路由條目,編寫好初始插入redis的數(shù)據(jù)
例如:
# 插入數(shù)據(jù) 到redis的 db0
cat dynamic_api_hash.txt | redis-cli -h redis-idgenerator.default.svc.cluster.local -p 16379 -n 0
redis里面的鍵值對可以使用hmset來批量插入
# cat dynamic_api_key_value.txt
HMSET passport1 i18n dmo-lego-i18n-rest eadmin dmo-lego-org-framework-rest kakashi dmo-lego-kakashi
鍵值對也可以單個插入次洼,方便修改和查詢(也可以使用HSET)
ECHO =========app===========
HMSET app standardOpen dmo-lego-standard-openapi
HMSET app config dmo-lego-config
HMSET app develop dmo-lego-develop-rest
HMSET app resource dmo-lego-resource-rest
HMSET app shareGroup dmo-lego-club
預(yù)期
Mac本地測試关贵,由于無法連接k8s網(wǎng)絡(luò),只能根據(jù)打印日志展示
匹配cookie和降級
編譯
-
tengine
+resty_lua_module
- 下載需要使用到的依賴包代碼
- 編寫 makefile (使用buildx卖毁,支持arm和amd x86)
- 編寫 dockerfile
all: build push
build:
docker buildx build --platform linux/arm64,linux/amd64 -t registry.bizsaas.net/tengine_lua:2.3.2.3 -f Dockerfile.buildx . --push
.PHONY: all build
FROM alpine:latest
MAINTAINER Browser <yunjie.xiao@clickpaas.com>
# 版本號
ENV VER_TENGINE 2.3.2
ENV VER_LUAJIT2 2.1-20220411
ENV VER_NGX_DEVEL_KIT 0.3.1
ENV VER_ECHO_NGINX_MODULE 0.62
ENV VER_LUA_NGINX_MODULE 0.10.14
ENV VER_LUA_REDIS_PARSER 0.13
ENV VER_LUA_RESTY_CORE 0.1.23
ENV VER_LUA_RESTY_REDIS 0.29
ENV VER_LUA_UPSTREAM_NGINX_MODULE 0.07
ENV VER_REDIS2_NGINX_MODULE 0.15
ENV VER_SET_MISC_NGINX_MODULE 0.33
# 國內(nèi)源
RUN echo "https://mirrors.ustc.edu.cn/alpine/v3.9/main" > /etc/apk/repositories \
&& echo "https://mirrors.ustc.edu.cn/alpine/v3.9/community" >> /etc/apk/repositories
# 依賴
RUN apk update
RUN apk add --no-cache --virtual .build-deps \
gcc \
libc-dev \
make \
openssl \
openssl-dev \
pcre \
pcre-dev \
zlib-dev \
linux-headers \
curl \
gnupg \
libxslt-dev \
gd-dev \
geoip-dev \
perl-dev
# 編譯
COPY app /app
RUN chown root.root -R /app && cd /app \
&& tar zxvf tengine-${VER_TENGINE}.tar.gz \
&& tar zxvf v${VER_LUAJIT2}.tar.gz \
&& tar zxvf v${VER_NGX_DEVEL_KIT}.tar.gz \
&& tar zxvf v${VER_ECHO_NGINX_MODULE}.tar.gz \
&& tar zxvf v${VER_LUA_NGINX_MODULE}.tar.gz \
&& tar zxvf v${VER_LUA_REDIS_PARSER}.tar.gz \
&& tar zxvf v${VER_LUA_RESTY_CORE}.tar.gz \
&& tar zxvf v${VER_LUA_RESTY_REDIS}.tar.gz \
&& tar zxvf v${VER_LUA_UPSTREAM_NGINX_MODULE}.tar.gz \
&& tar zxvf v${VER_REDIS2_NGINX_MODULE}.tar.gz \
&& tar zxvf v${VER_SET_MISC_NGINX_MODULE}.tar.gz \
&& cd luajit2-${VER_LUAJIT2} && make install PREFIX=/usr/local/luajit && cd - \
&& export LUAJIT_LIB=/usr/local/luajit/lib \
&& export LUAJIT_INC=/usr/local/luajit/include/luajit-2.1 \
&& cd lua-resty-core-${VER_LUA_RESTY_CORE} && make install PREFIX=/usr/local && cd - \
&& cd lua-resty-redis-${VER_LUA_RESTY_REDIS} && make install PREFIX=/usr/local && cd - \
&& cd lua-redis-parser-${VER_LUA_REDIS_PARSER} && make LUA_INCLUDE_DIR=/usr/local/luajit/include/luajit-2.1 && make install && cd - \
&& cd tengine-${VER_TENGINE} \
&& ./configure \
--prefix=/etc/nginx \
--with-pcre \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=root \
--group=root \
--with-http_ssl_module \
--with-http_realip_module \
--with-http_addition_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_random_index_module \
--with-http_secure_link_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-http_slice_module \
--with-mail \
--with-mail_ssl_module \
--with-file-aio \
--with-ipv6 \
--with-stream \
--with-stream_ssl_module \
--with-ld-opt="-Wl,-rpath,/usr/local/luajit/lib" \
--add-module=../ngx_devel_kit-${VER_NGX_DEVEL_KIT} \
--add-module=../echo-nginx-module-${VER_ECHO_NGINX_MODULE} \
--add-module=../lua-nginx-module-${VER_LUA_NGINX_MODULE} \
--add-module=../redis2-nginx-module-${VER_REDIS2_NGINX_MODULE} \
--add-module=../set-misc-nginx-module-${VER_SET_MISC_NGINX_MODULE} \
--add-module=../lua-upstream-nginx-module-${VER_LUA_UPSTREAM_NGINX_MODULE} \
&& make -j2 \
&& make install
# nginx 配置
RUN cp -rf /app/nginx.conf /etc/nginx/nginx.conf \
&& cp -rf /app/*.html /etc/nginx/html \
&& mkdir -p /var/log/nginx \
&& mkdir -p /var/cache/nginx \
&& touch /var/log/nginx/access.log \
&& touch /var/log/nginx/error.log \
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
# nginx-controller
RUN cp -rf /app/Shanghai /etc/localtime \
&& tar zxvf /app/nginx-controller.tar.gz -C / \
&& chown root.root /nginx-controller \
&& echo "nohup ./nginx-controller -log_dir=/var/log/nginx &" >> /run.sh \
&& echo "nginx -g 'daemon off;'" >> /run.sh \
&& chmod a+x /run.sh \
&& rm -rf /app
WORKDIR /
EXPOSE 80 443
CMD ["/bin/sh","-c","/run.sh"]