注意

本文档适用于 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

字段

类型

描述

可迭代

可写

可选

Request.RGWOp

string

radosgw 操作

no

no

no

Request.DecodedURI

string

已解码 URI

no

no

no

Request.ContentLength

integer

请求大小

no

no

no

Request.GenericAttributes

table

字符串到字符串的通用属性映射

yes

no

no

Request.Response

table

对请求的响应

no

no

no

Request.Response.HTTPStatusCode

integer

HTTP 状态码

no

yes

no

Request.Response.HTTPStatus

string

HTTP 状态文本

no

yes

no

Request.Response.RGWCode

integer

radosgw 错误码

no

yes

no

Request.Response.Message

string

响应消息

no

yes

no

Request.SwiftAccountName

string

swift 账户名称

no

no

yes

Request.Bucket

table

存储桶信息

no

no

no

Request.Bucket.Tenant

string

存储桶租户

no

no

yes

Request.Bucket.Name

string

存储桶名称(仅在 prerequest 上下文中可写)

no

yes

no

Request.Bucket.Marker

string

存储桶标记(初始 id)

no

no

yes

Request.Bucket.Id

string

存储桶 id

no

no

yes

Request.Bucket.ZoneGroupId

string

存储桶区域组 id

no

no

yes

Request.Bucket.CreationTime

time

存储桶创建时间

no

no

yes

Request.Bucket.MTime

time

存储桶修改时间

no

no

yes

Request.Bucket.Quota

table

存储桶配额

no

no

yes

Request.Bucket.Quota.MaxSize

integer

存储桶配额最大大小

no

no

no

Request.Bucket.Quota.MaxObjects

integer

存储桶配额最大对象数

no

no

no

Reques.Bucket.Quota.Enabled

boolean

存储桶配额已启用

no

no

no

Request.Bucket.Quota.Rounded

boolean

存储桶配额已四舍五入到 4K

no

no

no

Request.Bucket.PlacementRule

table

存储桶放置规则

no

no

yes

Request.Bucket.PlacementRule.Name

string

存储桶放置规则名称

no

no

no

Request.Bucket.PlacementRule.StorageClass

string

存储桶放置规则存储类别

no

no

no

Request.Bucket.User

string

拥有者用户/账户 id

no

no

yes

Request.Object

table

对象信息

no

no

yes

Request.Object.Name

string

对象名称

no

no

no

Request.Object.Instance

string

对象版本

no

no

no

Request.Object.Id

string

对象 id

no

no

no

Request.Object.Size

integer

对象大小

no

no

no

Request.Object.MTime

time

对象修改时间

no

no

no

Request.CopyFrom

table

复制操作信息

no

no

yes

Request.CopyFrom.Tenant

string

源对象租户

no

no

no

Request.CopyFrom.Bucket

string

源对象存储桶

no

no

no

Request.CopyFrom.Object

table

源对象。请参阅:Request.Object

no

no

yes

Request.ObjectOwner

table

对象拥有者

no

no

no

Request.ObjectOwner.DisplayName

string

对象拥有者显示名称

no

no

no

Request.ObjectOwner.User

string

拥有者用户/账户 id。请参阅:Request.Bucket.User

no

no

yes

Request.ZoneGroup.Name

string

区域组名称

no

no

no

Request.ZoneGroup.Endpoint

string

区域组端点

no

no

no

Request.UserAcl

table

用户 ACL

no

no

no

Request.UserAcl.Owner

table

用户 ACL 拥有者。请参阅:Request.ObjectOwner

no

no

no

Request.UserAcl.Grants

table

用户 ACL 字符串到授权的映射 注意:没有 Id 的授权在迭代时不显示,并且只能通过方括号访问其中一个

yes

no

no

Request.UserAcl.Grants["<name>"]

table

用户 ACL 授权

no

no

no

Request.UserAcl.Grants["<name>"].Type

integer

用户 ACL 授权类型

no

no

no

Request.UserAcl.Grants["<name>"].User

string

用户 ACL 授权用户/账户 id

no

no

no

Request.UserAcl.Grants["<name>"].GroupType

integer

用户 ACL 授权组类型

no

no

yes

Request.UserAcl.Grants["<name>"].Referer

string

用户 ACL 授权引用者

no

no

yes

Request.BucketAcl

table

存储桶 ACL。请参阅:Request.UserAcl

no

no

no

Request.ObjectAcl

table

对象 ACL。请参阅:Request.UserAcl

no

no

no

Request.Environment

table

字符串到字符串的环境映射

yes

no

no

Request.Policy

table

policy

no

no

yes

Request.Policy.Text

string

策略文本

no

no

no

Request.Policy.Id

string

策略 Id

no

no

yes

Request.Policy.Statements

table

字符串语句列表

yes

no

no

Request.UserPolicies

table

用户策略列表

yes

no

no

Request.UserPolicies[<index>]

table

用户策略。请参阅:Request.Policy

no

no

no

Request.RGWId

string

radosgw 主机 id: <host>-<zone>-<zonegroup>

no

no

no

Request.HTTP

table

HTTP 标头

no

no

no

Request.HTTP.Parameters

table

字符串到字符串的参数映射

yes

no

no

Request.HTTP.Resources

table

字符串到字符串的资源映射

yes

no

no

Request.HTTP.Metadata

table

字符串到字符串的元数据映射

yes

yes

no

Request.HTTP.StorageClass

string

存储类别

no

yes

yes

Request.HTTP.Host

string

主机名

no

no

no

Request.HTTP.Method

string

HTTP 方法

no

no

no

Request.HTTP.URI

string

URI

no

no

no

Request.HTTP.QueryString

string

HTTP 查询字符串

no

no

no

Request.HTTP.Domain

string

域名

no

no

no

Request.Time

time

请求时间

no

no

no

Request.Dialect

string

“S3” 或 “Swift”

no

no

no

Request.Id

string

请求 Id

no

no

no

Request.TransactionId

string

事务 Id

no

no

no

Request.Tags

table

对象标签映射

yes

no

no

Request.User

table

触发请求的用户

no

no

no

Request.User.Tenant

string

触发用户租户

no

no

no

Request.User.Id

string

触发用户 id

no

no

no

Request.Trace

table

跟踪信息

no

no

no

Request.Trace.Enable

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 秒。

数据上下文

getdataputdata 上下文都具有以下字段:- 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)

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入