Lua OpenResty容器化(考古歷程)

WilburXu發表於2021-04-21

原文地址:Lua OpenResty容器化(考古歷程)

背景

公司有幾個“遠古時期”的專案,一直都相對較為穩定,但是專案每天總會在一些時段,請求每分鐘QPS到達峰值800K左右,導致機器的效能出現了一些瓶頸,每到峰值時期,總會出現一個告警,實在是令人頭疼。更糟糕的是這只是遠古時期專案中的其中一個而且都是部署在物理機器上,所有機器加起來接近100臺。

出於穩定性(削峰)和成本的角度考慮,我們最終決定將所有的Lua OpenResty專案上到k8s叢集。

選擇合適的openresty基礎映象

通過檢視線上在使用的openresty版本資訊:

/usr/local/openresty/nginx/sbin/nginx -V
nginx version: openresty/1.13.6.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)
built with OpenSSL 1.1.0h 27 Mar 2018 (running with OpenSSL 1.1.0k 28 May 2019)
TLS SNI support enabled
configure arguments: --prefix=/usr/local/openresty/nginx ...
lua -v
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio

得知在使用的是openresty/1.13.6.2Lua 5.1.4 :

docker pull openresty/openresty:1.13.6.2-2-centos

Q:能不能選擇使用更小的alpine系列的呢?

A:因為專案依賴許多的so庫,都是glibc編譯的,alpine的話是musl-lib,不相容。

Q:為啥不重新編譯?

A:一方面是風險問題,另外一方面是有一些so庫不一定能找到。

查詢專案的動態庫依賴關係

Nginx配置檔案

$ tree -L 3 nginx/conf
nginx/conf
├── vhosts/
│ ├── inner.prometheus.nginx.conf
│ └── project.nginx.conf
└── nginx.conf

自編譯的C動態庫檔案,如binary_protocol.so

編寫好dockerfile,然後將專案打包進容器,執行:

/usr/local/openresty/nginx/sbin/nginx nginx -t

果不其然,報錯:

/usr/local/openresty/nginx/lua/init.lua:1: module 'binary_protocol' not found:
no field package.preload['binary_protocol']
no file '/usr/local/openresty/nginx/lua/binary_protocol.lua'
no file '/usr/local/openresty/nginx/lua_lib/binary_protocol.lua'
no file '/usr/local/openresty/nginx/luarocks/share/lua/5.1/binary_protocol.lua'
no file '/usr/local/openresty/site/lualib/binary_protocol.ljbc'
…… ……
no file '/usr/local/openresty/nginx/luarocks/lib64/lua/5.1/binary_protocol.so'
no file '/usr/local/openresty/site/lualib/binary_protocol.so'
no file '/usr/local/openresty/lualib/binary_protocol.so'
no file '/usr/local/openresty/site/lualib/binary_protocol.so'
no file '/usr/local/openresty/lualib/binary_protocol.so'
no file './binary_protocol.so'
no file '/usr/local/lib/lua/5.1/binary_protocol.so'
no file '/usr/local/openresty/luajit/lib/lua/5.1/binary_protocol.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
no file '/usr/local/openresty/luajit/lib/lua/5.1/binary_protocol.so'

Q:仔細觀察,發現so動態庫是內部編譯出來提供給lua呼叫的,如何找到它們呢?

A:是lddpldd又或者使用lsof檢視動態庫檔案。

通過ldd、pldd命令,可以檢視so所相關的依賴

ldd binary_protocol.so
linux-vdso.so.1 => (0x00007fff40bd4000)
libtolua++.so => not found ## 會告訴我們ldd缺少這個依賴
libcrypto.so.6 => not found
liblog4cplus.so.2 => not found
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f458d9ef000)
libm.so.6 => /lib64/libm.so.6 (0x00007f458d6ed000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f458d4d7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f458d10a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f458df1e000)

通過這些方法,一點點跟蹤,知道找齊所有依賴庫即可。

Luarocks外部包檔案

從線上的nginx.conf找到lua_package_pathlua_package_cpath中包括的luarocks路徑,再從這個路徑中,找到manifest檔案,此檔案有描述安裝了哪些luarocks庫。

luarocks 外部依賴安裝

RUN luarocks --tree=${WORK_DIR}/luarocks install lua-cjson \
&& luarocks --tree=${WORK_DIR}/luarocks install penlight \
&& luarocks --tree=${WORK_DIR}/luarocks install version \
&& luarocks --tree=${WORK_DIR}/luarocks install lua-resty-http \
&& luarocks --tree=${WORK_DIR}/luarocks install luaunit \
&& luarocks --tree=${WORK_DIR}/luarocks install ldoc \
&& luarocks --tree=${WORK_DIR}/luarocks install lua-discount \
&& luarocks --tree=${WORK_DIR}/luarocks install serpent \
&& luarocks --tree=${WORK_DIR}/luarocks install luacov \
&& luarocks --tree=${WORK_DIR}/luarocks install cluacov \
&& luarocks --tree=${WORK_DIR}/luarocks install mmdblua \
&& luarocks --tree=${WORK_DIR}/luarocks install lua-resty-jit-uuid \
&& luarocks --tree=${WORK_DIR}/luarocks install luasocket

RUN luarocks --tree=/usr/local/openresty/nginx/luarocks install nginx-lua-prometheus

遇到的問題及其解決方法

問題1:容器老被OOM Killed

經過分析,的確佔用了非常大的記憶體:

通過ps命令定位到 worker 數量非常多

解決方法:

限定worker數量:worker_processes 4;

Q:為啥會產生這麼多worker?

A:在k8s上,nginx 啟動的 worker process,並沒有遵循我們給 Pod 設定的 limit,而是與 Pod 所在 node 有關。

問題2:nginx worker process exited on signal 9

是由於Deployment設定的記憶體限額太小所致

解決方法:調大requests資源限額

resources:
limits:
cpu: "2000m"
memory: "1Gi"
requests:
cpu: "1000m"
memory: "512Mi"

ps:啟動4個Worker大約消耗200Mi。

問題3:attempt to index upvalue ‘result_dict’ (a nil value)

原因是線上的nginx.conf有相關的定義
而程式碼層面上沒有,加上即可:

lua_shared_dict monitor_status 150m;

縮減映象大小的一個小技巧

借雞生蛋

如何接入Prometheus監控

在OpenResty中接入 Prometheus,https://github.com/knyar/nginx-lua-prometheus

安裝依賴

luarocks --tree=/usr/local/openresty/nginx/luarocks install nginx-lua-prometheus

新增配置

nginx/conf/vhosts/project.nginx.conf增加:

lua_shared_dict prometheus_metrics 10M;
log_by_lua_block {
metric_requests:inc(1, {ngx.var.server_name, ngx.var.status})
metric_latency:observe(tonumber(ngx.var.request_time), {ngx.var.server_name})
}

新增配置檔案

新增nginx/conf/vhosts/inner.prometheus.nginx.conf

server {
listen 8099;
location /metrics {
content_by_lua_block {
metric_connections:set(ngx.var.connections_reading, {"reading"})
metric_connections:set(ngx.var.connections_waiting, {"waiting"})
metric_connections:set(ngx.var.connections_writing, {"writing"})
prometheus:collect()
}
}
}

更新deployment配置

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ${name}
namespace: ${namespace}
labels:
test-app: test-server
spec:
replicas: ${replicas}
template:
metadata:
labels:
test-app: test-server
annotations: # <----------------------- 新增
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "8099"

總結

至此,lua的一個專案容器化完成,中途遇到的問題還是蠻多的,上面也只記錄了幾個主要的步驟和問題。

相關文章