找回密码
 立即注册
首页 业界区 科技 使用openresty+lua来实现grafana中自动切换实时群集/历 ...

使用openresty+lua来实现grafana中自动切换实时群集/历史群集对应的vmselect

章娅萝 2025-9-30 21:39:09
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!

  • cnblogs博客
  • zhihu
  • Github
  • 公众号:一本正经的瞎扯
    1.png

我曾设计了这样的 VictoriaMetrics 中的实时群集和历史群集:
2.png

see: deploy_VictoriaMetrics_cluster
期待的效果是:

  • 实时群集存储最近 7 天的数据,保障足够快足够可靠,提供告警查询和当前的系统监控。

    • 为了保障实时群集的稳定性,通过牺牲存储时间来减少存储的数据

  • 历史群集提供长周期的(例如半年),且降采样的数据的存储

    • 以低成本的方式提供长周期的数据查询

部署完成后,我部署了一个新的 vmselect 节点,来连接到所有的实时群集和历史群集的vmselect节点上。
可是当我执行如下的查询时,发现数据是正确数据的两倍:
  1. sum by (path) (increase(http_request_total{job="myApp"}[1m]))
复制代码
很明显, sum() 时,把实时群集和历史群集中同样的 time series 上的值加了两次。
研究过 dedup 的源码,并未发现明显问题。
无奈,只能通过别的办法绕过去。
于是想到:如果能够自动发现用户查询的时间范围,当用户查询七天以内时转发到实时群集,而查询超过七天就转到历史群集,那么就不用把历史群集和实时群集混合在一起了。
下面是这个思路的详细解决办法:
3.png

部署 openresty 的 deployment 的代码如下:
  1. # openresty.yaml
  2. # nginx 的配置文件放在 configMap 中
  3. apiVersion: v1
  4. kind: ConfigMap
  5. metadata:
  6.   name: openresty-config
  7. data:
  8.   # nginx 的 配置文件
  9.   nginx.conf: |
  10.     worker_processes  1;
  11.     events {
  12.         worker_connections  1024;
  13.     }
  14.     http {
  15.         access_log /dev/stdout;
  16.         error_log /dev/stderr warn;
  17.         lua_package_path "/usr/local/openresty/nginx/lua/?.lua;;";
  18.         upstream realtime {
  19.             server vmselect-realtime:8481;  # 实时群集的 vmselect
  20.         }
  21.         upstream historical {
  22.             server vmselect-historical:8481;  # 历史群集的 vmselect
  23.         }
  24.         server {
  25.             listen 8401;
  26.             location /select/0/prometheus/api/v1/query_range {  # 核心是修改 query_range 这条 api
  27.                 content_by_lua_file /usr/local/openresty/nginx/lua/router.lua;
  28.             }
  29.             # 其它所有路径默认走 realtime
  30.             location / {
  31.                 proxy_pass http://realtime;
  32.             }
  33.             location @toRealtime {
  34.                 proxy_pass http://realtime;
  35.             }
  36.             location @toHistorical {
  37.                 proxy_pass http://historical;
  38.             }
  39.         }
  40.     }
  41.   # lua 脚本的代码
  42.   router.lua: |
  43.     -- 时间值要支持三种格式:数值,字符串,grafana中的简写
  44.     local function parse_start(val)
  45.         if not val then return nil end
  46.         local num = tonumber(val)
  47.         if num then
  48.             if num > 1e12 then
  49.                 return math.floor(num / 1000)
  50.             else
  51.                 return num
  52.             end
  53.         end
  54.         local year, mon, day, hour, min, sec =
  55.             val:match("^(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)")
  56.         if year then
  57.             return os.time({
  58.                 year = tonumber(year),
  59.                 month = tonumber(mon),
  60.                 day = tonumber(day),
  61.                 hour = tonumber(hour),
  62.                 min = tonumber(min),
  63.                 sec = tonumber(sec)
  64.             })
  65.         end
  66.         local num, unit = val:match("^([%-]?%d+)([smhdw])$")
  67.         if num and unit then
  68.             num = tonumber(num)
  69.             local seconds = 0
  70.             if unit == "s" then seconds = num
  71.             elseif unit == "m" then seconds = num * 60
  72.             elseif unit == "h" then seconds = num * 3600
  73.             elseif unit == "d" then seconds = num * 86400
  74.             elseif unit == "w" then seconds = num * 7 * 86400
  75.             end
  76.             return ngx.time() + seconds
  77.         end
  78.         return nil
  79.     end
  80.     local args = ngx.req.get_uri_args()
  81.     local is_post = (ngx.req.get_method() == "POST")
  82.     local post_args = {}
  83.     if is_post then
  84.         ngx.req.read_body()
  85.         post_args = ngx.req.get_post_args()
  86.         for k,v in pairs(post_args) do
  87.             args[k] = v
  88.         end
  89.     end
  90.     local start = parse_start(args["start"])
  91.     local now = ngx.time()
  92.     local days = 7  -- 这里设定一个七天的范围:七天以内在实时群集查询,超过七天在历史群集查询
  93.     local n_days_ago = now - days*24*3600
  94.     local step = "300s"  -- 当查询历史群集时,使用历史群集的降采样后的间隔,即 5 分钟
  95.     if start ~= nil then
  96.         if start > n_days_ago then
  97.             return ngx.exec("@toRealtime")
  98.         else
  99.             if is_post then
  100.                 post_args["step"] = step
  101.                 local body_tbl = {}
  102.                 for k,v in pairs(post_args) do
  103.                     table.insert(body_tbl, ngx.escape_uri(k) .. "=" .. ngx.escape_uri(v))
  104.                 end
  105.                 local new_body = table.concat(body_tbl, "&")
  106.                 ngx.req.set_body_data(new_body)
  107.             else
  108.                 args["step"] = step
  109.                 ngx.req.set_uri_args(args)
  110.             end
  111.             return ngx.exec("@toHistorical")
  112.         end
  113.     else
  114.         return ngx.exec("@toRealtime")
  115.     end
  116. ---
  117. # 这里是部署 openresty 的 deployment
  118. apiVersion: apps/v1
  119. kind: Deployment
  120. metadata:
  121.   name: openresty
  122. spec:
  123.   replicas: 1
  124.   selector:
  125.     matchLabels:
  126.       app: openresty
  127.   template:
  128.     metadata:
  129.       labels:
  130.         app: openresty
  131.     spec:
  132.       containers:
  133.         - name: openresty
  134.           image: openresty/openresty:1.27.1.2-alpine
  135.           ports:
  136.             - containerPort: 8401
  137.           volumeMounts:
  138.             - name: config
  139.               mountPath: /usr/local/openresty/nginx/conf/nginx.conf
  140.               subPath: nginx.conf
  141.             - name: config
  142.               mountPath: /usr/local/openresty/nginx/lua/router.lua
  143.               subPath: router.lua
  144.           command: ["/usr/local/openresty/bin/openresty"]
  145.           args: ["-g", "daemon off;", "-c", "/usr/local/openresty/nginx/conf/nginx.conf"]
  146.       volumes:
  147.         - name: config
  148.           configMap:
  149.             name: openresty-config
  150. ---
  151. apiVersion: v1
  152. kind: Service
  153. metadata:
  154.   name: openresty
  155. spec:
  156.   selector:
  157.     app: openresty
  158.   ports:
  159.     - protocol: TCP
  160.       port: 8401
  161.       targetPort: 8401
  162.   type: ClusterIP
复制代码
通过命令行部署:
  1. KUBECONFIG=~/my-test-k8s.yaml kubectl apply -f ./openresty.yaml -n my-namespace
复制代码
通过 grafana 创建新的数据源,或者可以使用命令查询:
  1. curl -G "http://127.0.0.1:8401/select/0/prometheus/api/v1/query_range?start=-7d" -v
复制代码
可以通过 header X-Server-Hostname 观察数据由哪个服务返回。

Have Fun.
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册