在 OpenResty 或 Nginx 进程中列出已加载的 Lua 模块
在排查在线 Lua 应用(包括由 OpenResty 或 Nginx 运行的应用)时,检查某些持久性 Lua 数据结构是一个常见需求。对于 Nginx 而言,它仍然使用 OpenResty 的核心组件,即 Lua Nginx 模块。这些 Lua 数据结构可能深深嵌入到一些已加载的 Lua 模块中,或通过 upvalues 被某些 Lua 函数引用等。传统检查此类 Lua 数据的方法是暴露特殊的 API,用于序列化和导出相应的 Lua 数据。但是,通过 动态追踪 技术在运行中的进程内部检查 Lua 数据会更加灵活和高效。
OpenResty XRay 提供了一种与 Lua 兼容的语言,称为 YLua,支持编写此类工具,以 100% 非侵入式的方式检查运行中进程内的任何 Lua 数据。本教程演示了一个简单的用例,即在运行中的 OpenResty 和 LuaJIT 应用中导出所有已加载 Lua 模块的名称。我们将在未来的教程中展示更多使用 YLua 的复杂用例。
系统环境
在此我们以 Red Hat Enterprise Linux 8 系统为例。任何 OpenResty XRay 支持的 Linux 发行版都应同样适用,如 Ubuntu、Debian、Fedora、Rocky、Alpine 等。
我们使用未经修改的开源 OpenResty 二进制构建作为目标应用。您可以使用任何 OpenResty 或 Nginx 二进制文件,包括自行编译的版本。您现有的服务器安装或进程无需特殊的构建选项、插件或库。这正是 动态追踪 技术的优势所在。它是真正非侵入式的。
我们还在同一系统上运行 OpenResty XRay 的 Agent 守护进程,并已 安装和配置 了 openresty-xray-cli
包中的命令行工具。
已加载 Lua 模块的名称
使用标准工具
在目标进程中转储已加载 Lua 模块的最简单方法是使用标准 OpenResty XRay 工具 lj-loaded-modules
。
使用 ps
命令找到我们要分析的 nginx 工作进程。在这里是 1046
。
$ ps aux | grep nginx
1 S root 1059 1 0 80 0 - 73941 - 15:03 ? 00:00:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
5 S nobody 1046 1059 0 80 0 - 82123 - 15:03 ? 00:00:00 nginx: worker process
现在让我们使用 openresty-xray-cli
包中的 orxray
命令行工具运行标准分析器 lj-dump-loaded-mods
。-p
选项用于指定要分析的目标进程 ID(或 PID)。
# 4096 is the target process's PID
$ orxray analyzer run lj-loaded-modules -p 4096
Start tracing...
jit: table
math: table
coroutine: table
debug: table
...
Go to https://x5vrki.xray.openresty.com/targets/68/history/751715 for charts.
使用自定义 YLua 工具
标准工具在底层是用 YLua 语言实现的。让我们看看如何使用 YLua 自己重新创建这个工具。我们知道,已加载的 Lua 模块总是缓存在 LuaJIT 虚拟机(和标准 Lua 5.1 解释器的虚拟机)的 package.loaded
中。键是模块名称,而值是模块数据。现在我们可以创建一个新的 YLua 源文件,命名为 my-dump-loaded-mods.ylua
:
probe process.begin
for k, v in pairs(package.loaded) do
print(k, ": ", type(v))
end
exit()
end
这里,probe
关键字为探测点 process.begin
定义了一个新的探测处理程序,该探测点在工具附加到目标进程时触发1。然后,我们通过在 YLua 中迭代 package.loaded
Lua 表来转储 Lua 模块,这与 Lua 的语法完全相同。在打印出模块名称及其数据类型后,我们调用 exit()
来结束整个跟踪过程。我们可以使用 openresty-xray-cli
包中的 run-ylua
命令行工具来运行这个 YLua 源文件。
检查 OpenResty 或 Nginx Lua 应用
使用 ps
命令找到我们想要分析的 nginx 工作进程。在这里,它是 1046
。
$ ps aux | grep nginx
1 S root 1059 1 0 80 0 - 73941 - 15:03 ? 00:00:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
5 S nobody 1046 1059 0 80 0 - 82123 - 15:03 ? 00:00:00 nginx: worker process
现在让我们使用 openresty-xray-cli
包中的 run-ylua
命令行工具来运行之前的 YLua 源文件。-p
选项用于指定要分析的目标进程 ID(或 PID)。
$ run-ylua -p 1046 my-dump-loaded-mods.ylua
Start tracing...
resty.core.uri: table
resty.core.exit: table
resty.core.base64: table
resty.core.request: table
resty.core.response: table
string: table
pgmoon: table
openresty_org.controller: table
ndk: table
table: table
resty.core.utils: table
resty.lrucache: table
table.new: function
debug: table
table.clear: function
...
package: table
math: table
resty.core.base: table
jit.opt: table
ngx: table
_G: table
resty.core.var: table
resty.core.worker: table
resty.core.regex: table
resty.core.shdict: table
resty.core.time: table
resty.core.hash: table
为了简洁起见,我们省略了大部分输出。我们可以通过将输出管道传递给 wc -l
命令来查看总数:
$ run-ylua -p 1046 my-dump-loaded-mods.ylua | wc -l
Start tracing...
38
大多数模块都是 table
类型。毕竟,Lua 模块通常是由 Lua 表来表示的。有几个模块比较特殊,它们是 function
类型。例如,table.new
是一个由 LuaJIT 专门实现的函数类型模块。
io
、os
、string
、table
、math
、debug
是 Lua 5.1 语言定义的标准 Lua 模块。jit
、bit
、ffi
、jit.opt
、table.new
、table.clear
是标准 LuaJIT 模块。resty.*
、coroutine
、ndk
、ngx
是 OpenResty 引入的模块。
检查独立的 LuaJIT 应用
我们还可以检查不涉及 Nginx 或 OpenResty 的独立 luajit
进程。
首先,像往常一样找到 luajit
进程的 PID:
$ ps aux | grep luajit
root 3371845 0.0 0.0 4428 2524 pts/3 S 22:17 0:00 luajit t.lua
然后,我们使用这个 PID,3371845
,来运行 run-ylua
命令:
$ orxray analyzer run lj-loaded-modules -p 3371845
Start tracing...
jit: table
math: table
coroutine: table
debug: table
os: table
_G: table
package: table
string: table
jit.opt: table
bit: table
io: table
table: table
正如我们所见,独立的 luajit
程序通常默认加载的 Lua 模块要少得多。
我们可以通过将输出管道传递给 wc -l
命令来查看总数:
$ orxray analyzer run lj-loaded-modules -p 3371845 | wc -l
Start tracing...
12
直接在 Web 控制台中运行
用户可以选择在 OpenResty XRay 的 Web 控制台中直接执行本教程中涵盖的任何工具。这些工具甚至可以在高 CPU 使用率等有趣事件发生时自动触发。
来自 openresty-xray-cli
的命令行实用程序便于演示目的。它们也易于由 DevOps 和 SRE 人员自动化并集成到其他系统中。
追踪容器内的应用
OpenResty XRay 工具支持透明地追踪容器化应用。Docker 和 Kubernetes (K8s) 容器都可以透明地工作。与普通应用进程一样,目标容器不需要任何应用或额外权限。OpenResty XRay Agent 守护进程应该在目标容器外部运行(如直接在主机操作系统中或在其自己的特权容器中)。
让我们看一个例子。我们首先使用 docker ps
命令检查容器名称或容器 ID。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4465297209d9 openresty/openresty:1.19.3.1-2-alpine-fat "/usr/local/openrest…" 18 months ago Up 3 minutes angry_mclaren
这里容器名称是 angry_mclaren
。然后我们可以找出此容器中目标进程的 PID。
$ docker top angry_mclaren
UID PID PPID C STIME TTY TIME CMD
root 3373047 3373026 0 22:23 ? 00:00:00 nginx: master process /usr/local/openresty/bin/openresty -g daemon off;
nobody 3373101 3373047 0 22:23 ? 00:00:00 nginx: worker process
openresty
工作进程的 PID 是 3373101
。然后我们像往常一样针对这个 PID 运行 OpenResty XRay 分析器。
$ orxray analyzer run lj-loaded-modules -p 3373101
Start tracing...
table: table
ngx: table
resty.core.var: table
table.new: function
resty.core.regex: table
resty.core.shdict: table
resty.core.time: table
...
table.clear: function
resty.core.worker: table
resty.lrucache: table
io: table
OpenResty XRay 还能够自动检测长时间运行的进程,并将其识别为特定类型的"应用"(如 “OpenResty”、“Python” 等)。
工具的实现方式
所有工具都是使用 Y 语言 实现的。OpenResty XRay 通过 Stap+2 或 eBPF3 后端执行这些工具,这两种后端都使用了 100% 非侵入式的动态追踪技术,基于 Linux 内核的 uprobes
和 kprobes
功能。YLua 脚本首先被编译成 Y 语言,然后进一步编译成可执行的动态追踪工具。
我们不需要目标应用和进程的任何配合。我们不使用也不需要任何日志数据或指标数据。我们直接以严格的只读方式分析运行中进程的进程空间。而且,我们绝不会向目标进程注入任何字节码或其他可执行代码。这是 100% 干净和安全的。
工具的开销
本教程中演示的动态追踪工具非常高效,适合在线执行。
当工具未运行且未主动采样时,对系统和目标进程的开销严格为零。我们从不向目标应用和进程注入任何额外的代码或插件;因此,不存在固有的开销。
在采样期间,对于本教程中涉及的工具,平均请求延迟和吞吐量的开销通常是不可测量的。这里的工具只进行一次性检查。
关于作者
章亦春是开源 OpenResty® 项目创始人兼 OpenResty Inc. 公司 CEO 和创始人。
章亦春(Github ID: agentzh),生于中国江苏,现定居美国湾区。他是中国早期开源技术和文化的倡导者和领军人物,曾供职于多家国际知名的高科技企业,如 Cloudflare、雅虎、阿里巴巴, 是 “边缘计算“、”动态追踪 “和 “机器编程 “的先驱,拥有超过 22 年的编程及 16 年的开源经验。作为拥有超过 4000 万全球域名用户的开源项目的领导者。他基于其 OpenResty® 开源项目打造的高科技企业 OpenResty Inc. 位于美国硅谷中心。其主打的两个产品 OpenResty XRay(利用动态追踪技术的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最适合微服务和分布式流量的全能型网关软件),广受全球众多上市及大型企业青睐。在 OpenResty 以外,章亦春为多个开源项目贡献了累计超过百万行代码,其中包括,Linux 内核、Nginx、LuaJIT、GDB、SystemTap、LLVM、Perl 等,并编写过 60 多个开源软件库。
关注我们
如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:
翻译
我们提供了英文版原文和中译版(本文)。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!