注意
本文档适用于 Ceph 的开发版本。
Lua Scripting
Pacific 版本新增。
此功能允许用户为 Lua 脚本分配执行上下文。支持的上下文包括:
prerequest:将在执行每个操作之前执行脚本
postrequest:将在执行每个操作之后执行脚本
background:将在指定的间隔时间内执行脚本
getdata:将在下载对象时对对象数据执行脚本
putdata:将在上传对象时对对象数据执行脚本
请求(pre 或 post)或数据(get 或 put)上下文脚本可以限制为属于特定租户用户的操作。请求上下文脚本还可以访问请求中的字段并修改某些字段,以及访问全局 RGW 表。数据上下文脚本可以访问对象的内容以及请求字段和全局 RGW 表。所有 Lua 语言功能都可以在所有上下文中使用。在某个上下文中执行脚本最多可以使用 500K 字节的内存。这包括 Lua 使用的所有库,但不包括 RGW 本身管理的、可以通过 Lua 访问的内存。要更改此默认值,请使用 rgw_lua_max_memory_per_state 配置参数。请注意,带有标准库的 Lua 的基本开销约为 32K 字节。要禁用此限制,请使用零。默认情况下,Lua 脚本的执行时间限制为最大 1000 毫秒。可以使用 rgw_lua_max_runtime_per_state 配置参数更改此限制。如果 Lua 脚本超过此运行时,它将被终止。要禁用运行时限制,请使用零。
警告
修改内存限制时请谨慎。如果当前内存使用量超过新设置的限制,则背景状态中所有先前存储的数据将丢失。
警告
禁用运行时限制可能会导致脚本无限期执行,从而导致资源消耗过多,并可能影响 RADOS Gateway 的可用性。
默认情况下,所有 Lua 标准库都可在脚本中使用,但是,为了允许在脚本中使用额外的 Lua 模块,我们支持将包添加到允许列表
确保在主机上安装了
luarocks包管理器。将 Lua 包添加到允许列表或从允许列表中删除包不会安装或删除它。为了使更改生效,应调用“reload”命令。
此外,允许列表中的所有包在 radosgw 重启时都会使用 luarocks 包管理器重新安装。
要添加包含需要编译的 C 源代码的包,请使用
--allow-compilation标志。在这种情况下,主机上需要有 C 编译器Lua 包安装在 radosgw 本地目录中并从该目录中使用。这意味着允许列表中的 Lua 包与主机上可用的任何 Lua 包是分开的。默认情况下,此目录为
/tmp/luarocks/<entity name>。其前缀部分 (/tmp/luarocks/) 可以通过rgw_luarocks_location配置参数设置为其他位置。请注意,不应将此参数设置为 luarocks 安装包的默认位置之一(例如$HOME/.luarocks、/usr/lib64/lua、/usr/share/lua)。
通过 CLI 进行脚本管理
上传脚本
# radosgw-admin script put --infile={lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}]
当上传具有
background上下文的脚本时,不应指定租户名称。
# cephadm shell radosgw-admin script put --infile=/rootfs/{lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}]
将脚本内容打印到标准输出
# radosgw-admin script get --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}]
删除脚本
# radosgw-admin script rm --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}]
通过 CLI 进行包管理
将包添加到允许列表
# radosgw-admin script-package add --package={package name} [--allow-compilation]
将特定版本的包添加到允许列表
# radosgw-admin script-package add --package='{package name} {package version}' [--allow-compilation]
当添加列表中已存在的包的不同版本时,新添加的版本将覆盖现有版本。
当添加包时未指定版本,将添加该包的最新版本。
从允许列表中删除包
# radosgw-admin script-package rm --package={package name}
从允许列表中删除特定版本的包
# radosgw-admin script-package rm --package='{package name} {package version}'
当删除包时未指定版本,将删除该包的任何现有版本。
打印允许列表中的包列表
# radosgw-admin script-package list
将允许列表中的更改应用于所有 RGW
# radosgw-admin script-package reload
无上下文函数
调试日志
RGWDebugLog() 函数接受一个字符串并将其以优先级 20 打印到调试日志中。每条日志消息都以 Lua INFO: 为前缀。此函数没有返回值。
请求字段
警告
此功能处于实验阶段。将来字段可能会被删除或重命名。
注意
尽管 Lua 是一种区分大小写的语言,但 radosgw 提供的字段名称不区分大小写。函数名称仍然区分大小写。
标有“optional”的字段可以具有 nil 值。
标有“iterable”的字段可以与 pairs() 函数和 # 长度运算符一起使用。
所有表字段都可以与方括号运算符
[]一起使用。time字段是具有以下格式的字符串:%Y-%m-%d %H:%M:%S。
字段 |
类型 |
描述 |
可迭代 |
可写 |
可选 |
|---|---|---|---|---|---|
|
string |
radosgw 操作 |
no |
no |
no |
|
string |
已解码 URI |
no |
no |
no |
|
integer |
请求大小 |
no |
no |
no |
|
table |
字符串到字符串的通用属性映射 |
yes |
no |
no |
|
table |
对请求的响应 |
no |
no |
no |
|
integer |
HTTP 状态码 |
no |
yes |
no |
|
string |
HTTP 状态文本 |
no |
yes |
no |
|
integer |
radosgw 错误码 |
no |
yes |
no |
|
string |
响应消息 |
no |
yes |
no |
|
string |
swift 账户名称 |
no |
no |
yes |
|
table |
存储桶信息 |
no |
no |
no |
|
string |
存储桶租户 |
no |
no |
yes |
|
string |
存储桶名称(仅在 |
no |
yes |
no |
|
string |
存储桶标记(初始 id) |
no |
no |
yes |
|
string |
存储桶 id |
no |
no |
yes |
|
string |
存储桶区域组 id |
no |
no |
yes |
|
time |
存储桶创建时间 |
no |
no |
yes |
|
time |
存储桶修改时间 |
no |
no |
yes |
|
table |
存储桶配额 |
no |
no |
yes |
|
integer |
存储桶配额最大大小 |
no |
no |
no |
|
integer |
存储桶配额最大对象数 |
no |
no |
no |
|
boolean |
存储桶配额已启用 |
no |
no |
no |
|
boolean |
存储桶配额已四舍五入到 4K |
no |
no |
no |
|
table |
存储桶放置规则 |
no |
no |
yes |
|
string |
存储桶放置规则名称 |
no |
no |
no |
|
string |
存储桶放置规则存储类别 |
no |
no |
no |
|
string |
拥有者用户/账户 id |
no |
no |
yes |
|
table |
对象信息 |
no |
no |
yes |
|
string |
对象名称 |
no |
no |
no |
|
string |
对象版本 |
no |
no |
no |
|
string |
对象 id |
no |
no |
no |
|
integer |
对象大小 |
no |
no |
no |
|
time |
对象修改时间 |
no |
no |
no |
|
table |
复制操作信息 |
no |
no |
yes |
|
string |
源对象租户 |
no |
no |
no |
|
string |
源对象存储桶 |
no |
no |
no |
|
table |
源对象。请参阅: |
no |
no |
yes |
|
table |
对象拥有者 |
no |
no |
no |
|
string |
对象拥有者显示名称 |
no |
no |
no |
|
string |
拥有者用户/账户 id。请参阅: |
no |
no |
yes |
|
string |
区域组名称 |
no |
no |
no |
|
string |
区域组端点 |
no |
no |
no |
|
table |
用户 ACL |
no |
no |
no |
|
table |
用户 ACL 拥有者。请参阅: |
no |
no |
no |
|
table |
用户 ACL 字符串到授权的映射 注意:没有 Id 的授权在迭代时不显示,并且只能通过方括号访问其中一个 |
yes |
no |
no |
|
table |
用户 ACL 授权 |
no |
no |
no |
|
integer |
用户 ACL 授权类型 |
no |
no |
no |
|
string |
用户 ACL 授权用户/账户 id |
no |
no |
no |
|
integer |
用户 ACL 授权组类型 |
no |
no |
yes |
|
string |
用户 ACL 授权引用者 |
no |
no |
yes |
|
table |
存储桶 ACL。请参阅: |
no |
no |
no |
|
table |
对象 ACL。请参阅: |
no |
no |
no |
|
table |
字符串到字符串的环境映射 |
yes |
no |
no |
|
table |
policy |
no |
no |
yes |
|
string |
策略文本 |
no |
no |
no |
|
string |
策略 Id |
no |
no |
yes |
|
table |
字符串语句列表 |
yes |
no |
no |
|
table |
用户策略列表 |
yes |
no |
no |
|
table |
用户策略。请参阅: |
no |
no |
no |
|
string |
radosgw 主机 id: |
no |
no |
no |
|
table |
HTTP 标头 |
no |
no |
no |
|
table |
字符串到字符串的参数映射 |
yes |
no |
no |
|
table |
字符串到字符串的资源映射 |
yes |
no |
no |
|
table |
字符串到字符串的元数据映射 |
yes |
yes |
no |
|
string |
存储类别 |
no |
yes |
yes |
|
string |
主机名 |
no |
no |
no |
|
string |
HTTP 方法 |
no |
no |
no |
|
string |
URI |
no |
no |
no |
|
string |
HTTP 查询字符串 |
no |
no |
no |
|
string |
域名 |
no |
no |
no |
|
time |
请求时间 |
no |
no |
no |
|
string |
“S3” 或 “Swift” |
no |
no |
no |
|
string |
请求 Id |
no |
no |
no |
|
string |
事务 Id |
no |
no |
no |
|
table |
对象标签映射 |
yes |
no |
no |
|
table |
触发请求的用户 |
no |
no |
no |
|
string |
触发用户租户 |
no |
no |
no |
|
string |
触发用户 id |
no |
no |
no |
|
table |
跟踪信息 |
no |
no |
no |
|
boolean |
跟踪已启用 |
no |
yes |
no |
请求函数
操作日志
Request.Log() 函数将请求打印到操作日志中。此函数没有参数。成功返回 0,失败返回错误代码。
Tracing
跟踪函数只能在 postrequest 上下文中使用。
Request.Trace.SetAttribute(<key>, <value>)- 设置请求跟踪的属性。该函数接受两个参数:第一个是key,必须是字符串;第二个是value,可以是字符串或数字(整数或双精度)。然后可以使用此属性来定位特定的跟踪。Request.Trace.AddEvent(<name>, <attributes>)- 向请求跟踪的第一个 span 添加事件。事件由事件名称、事件时间和零个或多个事件属性定义。该函数接受一个或两个参数:第一个参数应为包含事件name的字符串,后跟事件attributes,对于没有属性的事件,这是可选的。事件的属性必须是一个字符串表。
后台上下文
background 上下文可用于分析、监控、为其他上下文执行缓存数据等目的。- 后台脚本执行的默认间隔为 5 秒。
数据上下文
getdata 和 putdata 上下文都具有以下字段:- Data 是只读且可迭代的(逐字节)。如果对象分块上传或检索,Data 字段将一次包含一个块的数据。- Offset 包含块在整个对象中的偏移量。- Request 字段和后台 RGW 表也在此上下文中可用。
全局 RGW 表
RGW Lua 表可从所有上下文访问,并保存执行期间写入的数据,以便以后在同一上下文或不同上下文的其他执行期间读取和使用。- 每个 RGW 实例都有自己的私有且临时的 RGW Lua 表,该表在守护程序重新启动时丢失。请注意,background 上下文脚本将在每个实例上运行。- 表中的最大条目数为 100,000。每个条目都有一个字符串键和一个值,总长度不超过 1KB。如果条目数或条目大小超过这些限制,Lua 脚本将中止并出错。- RGW Lua 表使用字符串索引,并且可以存储类型为:string、integer、double 和 boolean 的值
增/减函数
由于 RGW 表中的条目可以同时从多个地方访问,我们需要一种原子地增加和减少其中的数值的方法。为此,应使用以下函数:- RGW.increment(<key>, [value]) 如果提供了 value,则将 key 的值增加 value;如果未提供,则增加 1。- RGW.decrement(<key>, [value]) 如果提供了 value,则将 key 的值减少 value;如果未提供,则减少 1。- 如果 key 的值不是数字,脚本执行将失败。- 如果我们尝试增加或减少非数字值,脚本执行将失败。
Lua 代码示例
在复制情况下打印源对象和目标对象的信息
function print_object(object)
RGWDebugLog(" Name: " .. object.Name)
RGWDebugLog(" Instance: " .. object.Instance)
RGWDebugLog(" Id: " .. object.Id)
RGWDebugLog(" Size: " .. object.Size)
RGWDebugLog(" MTime: " .. object.MTime)
end
if Request.CopyFrom and Request.Object and Request.CopyFrom.Object then
RGWDebugLog("copy from object:")
print_object(Request.CopyFrom.Object)
RGWDebugLog("to object:")
print_object(Request.Object)
end
通过“通用函数”打印 ACL
function print_owner(owner)
RGWDebugLog("Owner:")
RGWDebugLog(" Display Name: " .. owner.DisplayName)
RGWDebugLog(" Id: " .. owner.User.Id)
RGWDebugLog(" Tenant: " .. owner.User.Tenant)
end
function print_acl(acl_type)
index = acl_type .. "ACL"
acl = Request[index]
if acl then
RGWDebugLog(acl_type .. "ACL Owner")
print_owner(acl.Owner)
RGWDebugLog(" there are " .. #acl.Grants .. " grant for owner")
for k,v in pairs(acl.Grants) do
RGWDebugLog(" Grant Key: " .. k)
RGWDebugLog(" Grant Type: " .. v.Type)
RGWDebugLog(" Grant Group Type: " .. v.GroupType)
RGWDebugLog(" Grant Referer: " .. v.Referer)
RGWDebugLog(" Grant User Tenant: " .. v.User.Tenant)
RGWDebugLog(" Grant User Id: " .. v.User.Id)
end
else
RGWDebugLog("no " .. acl_type .. " ACL in request: " .. Request.Id)
end
end
print_acl("User")
print_acl("Bucket")
print_acl("Object")
仅在错误情况下使用操作日志
if Request.Response.HTTPStatusCode ~= 200 then
RGWDebugLog("request is bad, use ops log")
rc = Request.Log()
RGWDebugLog("ops log return code: " .. rc)
end
将值设置到错误消息中
if Request.Response.HTTPStatusCode == 500 then
Request.Response.Message = "<Message> something bad happened :-( </Message>"
end
向对象添加客户端未发送的元数据
在 prerequest 上下文中我们应该添加
if Request.RGWOp == 'put_obj' then
Request.HTTP.Metadata["x-amz-meta-mydata"] = "my value"
end
在 postrequest 上下文中我们查看元数据
RGWDebugLog("number of metadata entries is: " .. #Request.HTTP.Metadata)
for k, v in pairs(Request.HTTP.Metadata) do
RGWDebugLog("key=" .. k .. ", " .. "value=" .. v)
end
使用模块创建基于 Unix 套接字的 JSON 编码“访问日志”
首先我们应该将以下包添加到允许列表
# radosgw-admin script-package add --package=lua-cjson --allow-compilation
# radosgw-admin script-package add --package=luasocket --allow-compilation
然后,运行一个服务器来侦听 Unix 套接字。例如,使用“netcat”
# rm -f /tmp/socket
# nc -vklU /tmp/socket
最后,重新启动 radosgw 并将以下脚本上传到 postrequest 上下文
if Request.RGWOp == "get_obj" then
local json = require("cjson")
local socket = require("socket")
local unix = require("socket.unix")
local s = assert(unix())
E = {}
msg = {bucket = (Request.Bucket or (Request.CopyFrom or E).Bucket).Name,
time = Request.Time,
operation = Request.RGWOp,
http_status = Request.Response.HTTPStatusCode,
error_code = Request.Response.HTTPStatus,
object_size = Request.Object.Size,
trans_id = Request.TransactionId}
assert(s:connect("/tmp/socket"))
assert(s:send(json.encode(msg).."\n"))
assert(s:close())
end
仅跟踪特定存储桶的请求
默认情况下禁用跟踪,因此我们应该为这个特定存储桶启用跟踪
if Request.Bucket.Name == "my-bucket" then
Request.Trace.Enable = true
end
如果 RGW 上启用了跟踪,则 Request.Trace.Enable 的值为 true,因此我们应该为所有其他与存储桶名称不匹配的请求禁用跟踪。在 prerequest 上下文中
if Request.Bucket.Name ~= "my-bucket" then
Request.Trace.Enable = false
end
请注意,更改 Request.Trace.Enable 不会更改跟踪器的状态,而只会禁用或启用请求的跟踪。
为请求跟踪添加信息
在 postrequest 上下文中,我们可以向请求的跟踪添加属性和事件。
Request.Trace.AddEvent("lua script execution started")
Request.Trace.SetAttribute("HTTPStatusCode", Request.Response.HTTPStatusCode)
event_attrs = {}
for k,v in pairs(Request.GenericAttributes) do
event_attrs[k] = v
end
Request.Trace.AddEvent("second event", event_attrs)
对象的熵值可用于检测对象是否已加密。以下脚本计算上传对象的熵和大小并打印到调试日志
在 putdata 上下文中,添加以下脚本
function object_entropy()
local byte_hist = {}
local byte_hist_size = 256
for i = 1,byte_hist_size do
byte_hist[i] = 0
end
local total = 0
for i, c in pairs(Data) do
local byte = c:byte() + 1
byte_hist[byte] = byte_hist[byte] + 1
total = total + 1
end
entropy = 0
for _, count in ipairs(byte_hist) do
if count ~= 0 then
local p = 1.0 * count / total
entropy = entropy - (p * math.log(p)/math.log(byte_hist_size))
end
end
return entropy
end
local full_name = Request.Bucket.Name.."\\"..Request.Object.Name
RGWDebugLog("entropy of chunk of: " .. full_name .. " at offset:" .. tostring(Offset) .. " is: " .. tostring(object_entropy()))
RGWDebugLog("payload size of chunk of: " .. full_name .. " is: " .. #Data)