Cloud Custodian 过滤器完全指南:从 Value Filter 到 Reduce Filter 的通用过滤机制
Cloud Custodian 过滤器完全指南:从 Value Filter 到 Reduce Filter 的通用过滤机制
本文是 Cloud Custodian(c7n)通用过滤器(Generic Filters)的实战技术指南,聚焦 docs/source/filters.rst 所定义的核心过滤体系:以 JMESPath 为基础的 Value Filter、列表项过滤、事件过滤与资源归约(reduce)过滤。无论你管理 AWS、Azure 还是 GCP 资源,这些过滤器都适用于所有策略与所有资源类型。读完本文,你将掌握如何用 YAML 编写可嵌套的布尔查询、使用十余种比较与列表运算符、借助 value_type 与 value_from 完成时间/数值/外部数据源的复杂匹配,以及用 reduce 过滤器对资源分组、排序、限量后再执行动作。
一、Generic Filters 概览:一套通用的查询语言
Cloud Custodian 的策略(Policy)由三部分组成:resource(目标资源类型)、filters(筛选条件)与 actions(执行动作)。其中 filters 段使用的就是本文要讲的通用过滤器。它们对任意资源类型都成立,因为其底层是对 JSON 对象进行 JMESPath 查询,并可与可嵌套的布尔条件运算符自由组合。各云厂商的资源专属过滤器(如 image-age、offhours)本质上大多也是在这些通用过滤器之上构建的,因此掌握本文内容等于掌握了全部过滤器的地基。
从源码结构看,通用过滤器的实现集中在 c7n/filters/core.py:文件顶部的 OPERATORS 字典(c7n/filters/core.py#L89-L111)注册了全部比较/列表/模式运算符,VALUE_TYPES 列表(c7n/filters/core.py#L114-L117)声明了全部值类型转换,ValueFilter(c7n/filters/core.py#L589)是通用值过滤器的核心类,ReduceFilter(c7n/filters/core.py#L966)与 ListItemFilter(c7n/filters/core.py#L1203)则分别实现了归约与列表项过滤。
二、Value Filter:最基础的值过滤器
Value Filter(type: value)允许你用 JMESPath 表达式从资源的 describe 调用返回数据中提取字段值,再与期望值进行比较:
filters:
- type: value
key: "State[0]" # 从 describe 调用返回数据中提取的值
value: "running" # 用于比较的目标值
当省略 op 时,过滤器执行的是等值比较(r == v)。这里有一个容易忽略的细节:match() 方法在 op 为 in/not-in 且资源值为 None 时,会把提取值视为空元组 () 参与比较(见 c7n/filters/core.py#L755-L756),从而保证这类运算符不会因空值抛异常。
2.1 如何获取每个资源可用的 key 列表
官方提供了三种途径:
途径一:通过 Custodian CLI。 新建一个只包含 name 与 resource 字段的 YAML 文件,然后运行 custodian run -s OUTPUT_DIR。运行结束后,合法的 key 字段会写入输出目录下的 resources.json 中:
policies:
- name: my-first-policy
resource: aws.ec2
仓库测试数据中就有真实示例:tests/data/output/ec2-running-instances/resources.json。这也是调试过滤器时最常用的手段——先用最小策略把资源字段完整 dump 出来,再挑选需要的 key。
途径二:通过云厂商 CLI。 直接调用相应的 describe API 查看所有可用字段。例如 AWS 执行 aws ec2 describe-instances,Azure 执行 az vm list。
注意:大多数情况下不需要包含最外层的 JSON 字段(如
Reservations、value),因为 Custodian 在返回结果前会剥掉这层壳。
途径三:通过云厂商 SDK 文档。 在对应云厂商 SDK 文档中搜索该资源的 describe API 调用,其返回值下方列出的字段即是可用 key。
2.2 特殊值(Special Values):测试 key 的存在性与空值
以下元值用于判断资源是否包含某 key以及该 key 是否为空:
| 元值 | 匹配条件 |
|---|---|
absent |
key 不存在 |
present |
key 存在 |
empty |
值为 false、空或缺失 |
not-null |
值存在,且非 false、非空 |
假设某 S3 桶只有如下精简属性:
{
"Name": "my_bucket",
"Versioning": {},
"Tags": [{
"Environment": "dev",
"Owner": ""
}]
}
那么以下过滤器全部会匹配该资源:
filters:
- "tag:Environment": "dev"
- "tag:Environment": "not-null"
- "tag:Environment": "present"
- "tag:Owner": "empty"
- "tag:Owner": "present"
- "tag:Team": "empty"
- "tag:Team": "absent"
- "Versioning": "empty"
- "Versioning": "present"
- "Versioning.Status": "empty"
- "Versioning.Status": "absent"
注意 tag: 前缀是 Cloud Custodian 对标签的专有简写——它会被解析为对 Tags 数组的 JMESPath 查询,因此 tag:Environment 等价于从标签列表中取名为 Environment 的标签值。这些特殊值的判定逻辑可以在 c7n/filters/core.py#L764-L782 的 match() 中看到:absent 要求提取值为 None、present 要求非 None、not-null 要求值存在且为真、empty 要求值为假值(not r)。tags.py 中的 ValueRegex 等辅助类也复用了这一套提取逻辑。
2.3 比较运算符(Comparison Operators)
通用值过滤器支持以下比较运算符,支持全称与缩写两种写法:
equal或eq(等于)not-equal或ne(不等于)greater-than或gt(大于)gte或ge(大于等于)less-than或lt(小于)lte或le(小于等于)in(属于)not-in或ni(不属于)contains(包含)
示例:筛选 CPU 核数大于 36 的实例:
filters:
- type: value
key: CpuOptions.CoreCount # describe 调用返回的值
value: 36 # 参与比较的值
op: greater-than # 比较运算符
这些运算符在源码中与 Python operator 模块直接映射(c7n/filters/core.py#L89-L111),例如 gt 即 operator.gt、contains 即 operator.contains。在 match() 中,运算符通过 OPERATORS<a href="https://link.gitcode.com/i/1748cc667c3604f3708a9d431a0f3aa8" target="_blank">self.op] 取出后执行 op(r, v),若类型不匹配引发 TypeError 则安全返回 False([c7n/filters/core.py#L773-L778),不会中断策略运行。
2.4 逻辑运算符(Logical Operators):布尔组合
or或Or(或)and或And(且)not(非)
示例:匹配 CPU 核数为 36 或 42 的实例:
filters:
- or: # 逻辑运算符
- type: value
key: CpuOptions.CoreCount # describe 调用返回的值
value: 36 # 参与比较的值
- type: value
key: CpuOptions.CoreCount # describe 调用返回的值
value: 42 # 参与比较的值
从实现看,And 采用链式短路处理:逐个执行子过滤器,一旦资源集为空立即 break(c7n/filters/core.py#L482-L494)。Not 则基于集合差实现:先让子过滤器处理全部资源,再用 before - after 得到被剔除的集合(c7n/filters/core.py#L514-L537)。特别地,块内过滤器会在资源上写入 c7n:MatchedFilters 等注解,Not 过滤器配合 AnnotationSweeper(c7n/filters/core.py#L540)在取反后清扫未匹配资源的注解,避免下游误读。
2.5 列表运算符(List Operators)
有一组运算符专门用于用户提供的列表。默认求值方向为 value from key(运算符)given value,即"key 中的值"作用于"给定的值"。如果想反转方向(given value 作用于 value from key),可以使用 swap 转换或 contains 运算符。
in(key 的值是否出现在给定列表中)not-in或ni(key 的值是否不在给定列表中)contains(给定列表/值是否包含 key 的值)intersect—— 两个列表存在任意交集即匹配difference—— 第一个列表存在任何不在第二个列表中的值即匹配
示例:匹配 ImageId 出现在预定义列表中的资源:
filters:
- type: value
key: ImageId # describe 调用返回的值
op: in # 列表运算符
value: [ID-123, ID-321] # 参与比较的值列表
有些资源属性本身就是列表。例如 EC2 实例可以挂多个安全组,以下三个示例假设要评估三个实例:
| 实例 | 安全组名称 |
|---|---|
| instance1 | default, common, custom |
| instance2 | common, custom, extra |
| instance3 | common |
示例一: 匹配安全组列表包含 default 的实例(命中 instance1):
filters:
- type: value
key: SecurityGroups[].GroupName
op: contains
value: default
示例二: 用 difference 找出带"预期外"安全组的实例。default 和 extra 不在预期列表 [common, custom] 中,因此命中 instance1 与 instance2:
filters:
- type: value
key: SecurityGroups[].GroupName
op: difference
value:
- common
- custom
示例三: value_type: swap 反转判断方向——检查预期列表中是否有值未出现在实例上。instance3 缺少 custom,因此命中:
filters:
- type: value
key: SecurityGroups[].GroupName
op: difference
value:
- common
- custom
value_type: swap
从源码看,difference 与 intersect 的实现非常直观(c7n/filters/core.py#L77-L82):bool(set(x).difference(y)) 与 bool(set(x).intersection(y)),swap 则在 process_value_type 中交换比较双方(c7n/filters/core.py#L812-L813)。
2.6 模式匹配运算符(Pattern Matching Operators)
glob—— Glob 通配匹配regex—— 正则匹配,忽略大小写(1)regex-case—— 正则匹配,区分大小写(1)
filters:
- type: value
key: FunctionName # describe 调用返回的值,或 resources.json 中的字段
op: regex # 特殊运算符
value: '(custodian|c7n)_\w+' # 正则:匹配所有以 custodian_ 或 c7n_ 开头的值
- type: value
key: name # describe 调用返回的值,或 resources.json 中的字段
op: regex # 特殊运算符
value: '^.*c7n.*$' # 正则:匹配所有包含 c7n 的值
- type: value
key: name # describe 调用返回的值,或 resources.json 中的字段
op: regex # 特殊运算符
value: '^((?!c7n).)*$' # 正则:匹配所有不包含 c7n 的值
(1)这两个运算符基于 Python
re.match实现。如果过滤结果与预期不符,请查阅 Pythonre模块文档中search与match的差异——re.match是从字符串开头开始匹配,而不是任意位置搜索。
源码佐证:regex_match 使用 re.match(regex, value, flags=re.IGNORECASE)(c7n/filters/core.py#L53-L58),regex_case_sensitive_match 不带 IGNORECASE 标志(c7n/filters/core.py#L61-L66)。此外 ValueFilter.validate() 会在策略校验阶段就用 re.compile 预检正则合法性,非法正则直接抛出 PolicyValidationError(c7n/filters/core.py#L671-L677),把错误提前到部署期而非运行期。
2.7 值类型转换(Value Type Transformations)
通过 value_type 关键字可以对提取值做转换,支持的类型如下:
age—— 转换为 datetime(用于过去日期比较)cidr—— 解析为 ipaddresscidr_size—— 网络前缀长度expiration—— 转换为 datetime(用于未来日期比较)integer—— 转换为整数normalize—— 转换为小写resource_count—— 与匹配到的资源总数比较size—— 元素长度swap—— 交换 value 与被求值的 keydate—— 把过滤器的 value 解析为日期
注意:
age与expiration转换期望 value 以天数给出;要匹配短于一天的时间段,请使用浮点数(如0.5表示半天)。
典型示例:
# 获取一个列表的大小(安全组数量)
- type: value
key: SecurityGroups[].GroupId
value_type: size
value: 2
# 用 swap 做成员判断
- type: value
key: SecurityGroups[].GroupId
value_type: swap
op: in
value: sg-49b87f44
# 比较前先转成整数
- type: value
key: tag:Count
op: greater-than
value_type: integer
value: 0
# 只作用于指定日期之后创建的 RDS 实例
- type: value
key: InstanceCreateTime
op: greater-than
value_type: date
value: "2019/05/01"
# 查找最近 31 天内启动的实例
- type: value
key: LaunchTime
op: less-than
value_type: age
value: 32
# 查找过去 12 小时内启动的实例
- type: value
key: LaunchTime
op: less-than
value_type: age
value: 0.5
# 用 resource_count 基于“匹配数量”过滤资源
# 注意:该 value_type 不需要 key,因为它比较的是资源列表的
# 规模而非某个具体字段
- type: value
value_type: resource_count
op: lt
value: 2
age 的比较方向值得一提:源码中做了反转(c7n/filters/core.py#L816-L826),即最终以"资源时间值 > 阈值"的语义执行,因此"32 天前"用 less-than 加 value: 32 表达——直觉上"启动时间早于 32 天前"的实例会命中。expiration 则相反,以"now + 天数"作为阈值用于未来日期(c7n/filters/core.py#L841-L847)。resource_count 走的是独立校验路径:不要求 key,value 必须是整数或列表,且不允许 regex/value_regex(c7n/filters/core.py#L617-L643)。
value_type 与外部数据源结合:intersect 实战
下面的策略用 intersect 运算符,把 RDS 实例的子网组列表与一个外部 S3 txt 文件中的公网子网列表做交集比对:
- name: find-rds-on-public-subnets-using-s3-list
comment: |
The txt file needs to be in utf-8 no BOM format and contain one
subnet per line in the file no quotes around the subnets either.
resource: aws.rds
filters:
- type: value
key: "DBSubnetGroup.Subnets[].SubnetIdentifier"
op: intersect
value_from:
url: s3://cloud-custodian-bucket/PublicSubnets.txt
format: txt
也可以直接内联一个子网列表:
- name: find-rds-on-public-subnets-using-inline-list
resource: aws.rds
filters:
- type: value
key: "DBSubnetGroup.Subnets[].SubnetIdentifier"
op: intersect
value:
- subnet-2a8374658
- subnet-1b8474522
- subnet-2d2736444
2.8 附加 JMESPath 函数(Additional JMESPath Functions)
Cloud Custodian 扩展了标准 JMESPath 函数库,当前支持:
-
split(seperator, input_string) -> list[str]:接收两个参数(分隔符 token 与输入字符串),返回字符串列表。示例:从日志组名中切出 Lambda 函数名,配合
copy-related-tag动作复制标签:policies: - name: copy-related-tag-with-split resource: aws.log-group filters: - type: value key: logGroupName value: "/aws/lambda/" op: in value_type: swap actions: - type: copy-related-tag resource: aws.lambda # 切分日志组名以得到 Lambda 函数名 key: "split(`/`, logGroupName)[-1]" tags: "*" -
from_json(json_encoded_string) -> obj:接收一个 JSON 编码字符串,返回解码后的对象。
从源码看,这两个函数注册在 C7NJmespathFunctions 中(c7n/utils.py#L1184-L1198),_func_split 即 string.split(sep),_func_from_json 即 json.loads(string)(解析失败返回 None)。所有 Custodian 的 JMESPath 调用——包括 jmespath_search(c7n/utils.py#L1218-L1223)与 jmespath_compile(c7n/utils.py#L1242-L1244)——都会注入这组自定义函数,因此过滤器、value_path、value_from 的 expr 等所有 JMESPath 场景都能使用。
2.9 Value Regex:先从字段值中提取子串再比较
使用 Value Filter 时可以指定 value_regex。此时用于比较的值,是对 key 提取出的资源值执行正则后的捕获结果。
- 过滤器要求正则中恰好一个捕获组,但也允许非捕获组,如
(?:newkey|oldkey)。 - 如果正则没有匹配到,返回
None。
典型场景:expiration 比较需要 datetime,但标签里还混着其他数据。用 value_regex 只捕获日期部分,即可正常求值:
# 从标签内容中提取过期时间
- type: value
key: "tag:metadata"
value_type: expiration
value_regex: ".*delete_after=([0-9]{4}-[0-9]{2}-[0-9]{2}).*"
op: less-than
value: 0
源码中 ValueRegex.get_resource_value 用 re.match 求值并返回 capture.group(1)(c7n/filters/core.py#L954-L963)。BaseValueFilter.get_resource_value 会把 value_regex 作为第三个参数透传(c7n/filters/core.py#L704-L705)。对应测试用例覆盖了"匹配首个出现"与"非捕获组"两种场景(见 tests/test_filters.py#L635 与 tests/test_filters.py#L657)。
2.10 Value From:引入外部值
value_from 允许在 Value Filter 中使用外部数据源的值。其底层实现是 c7n.resolver.ValuesFrom(c7n/resolver.py#L129),支持从 URL 拉取 json、csv、行分隔文本等格式,也支持 DynamoDB 查询(url: dynamodb + PartiQL query)。核心参数:
url(必填):数据源地址,支持s3://、http:///https://、azure://(Azure Blob,需 c7n_azure)、dynamodb等协议;format:json、txt、csv、csv2dict四选一;未显式指定时按 URL 扩展名推断;expr:对数据做二次提取——json 用 JMESPath 表达式,csv 可用整数列号或 JMESPath 表达式,csv2dict 用 JMESPath;headers:HTTP 请求的自定义头(如authorization)。
官方示例(出自 ValuesFrom 类 docstring):
value_from:
url: s3://bucket/xyz/foo.json
expr: [].AppId
value_from:
url: http://foobar.com/mydata
format: json
expr: Region."us-east-1"[].ImageId
headers:
authorization: my-token
value_from:
url: s3://bucket/abc/foo.csv
format: csv2dict
expr: key[1]
# 使用 PartiQL 查询 DynamoDB 作为异常名单
value_from:
url: dynamodb
query: |
select resource_id from exceptions
where account_id = '{account_id} and policy = '{policy.name}'
expr: [].resource_id
实现细节值得注意:ValuesFrom 的构造器会用 account_id 与 region 对配置做字符串格式化(c7n/resolver.py#L200-L205),这意味着 {account_id}、{region} 这类占位符可以出现在 url、expr、query 中;解析结果会以 ("value-from", cache_key) 为键缓存在 manager 的缓存中(c7n/resolver.py#L231-L245),多个资源复用同一份外部数据,避免重复拉取。
2.11 Value Path:资源内部两个字段互相比对
value_path 允许用 JMESPath 指定比较值的来源,且该值针对每个正在过滤的资源单独重新计算。注意它只提取当前资源自身的属性,因此非常适合比较同一资源内两个字段/两个列表的关系。
policies:
- name: find-admins-with-user-roles
resource: gcp.project
filters:
- type: iam-policy
doc:
key: bindings[?(role=='roles/admin')].members[]
op: intersect
value_path: bindings[?(role=='roles/user_access')].members[]
这里的 iam-policy 过滤器复用通用 Value Filter 实现,通过 key 与 value_path 对同一资源内的两个成员列表求交集。get_path_value() 的实现即对 value_path 执行 JMESPath 搜索(c7n/filters/core.py#L707-L731)。测试用例验证了 value_path 必须逐资源重新求值、不能被首个资源的结果缓存污染,且 value_from 优先级高于 value_path(tests/test_filters.py#L289-L329)。
三、List Item Filter:深入列表内部做多属性判断
list-item 过滤器让"包含列表值的资源属性"的评估变得容易——它可以针对列表中的每一个元素执行一组子过滤器(attrs),并对命中数量做约束。纯 value 过滤器做不到这一点,因为 regex 等运算符无法直接作用于列表。
3.1 示例一:AWS ECS 任务定义
ECS 任务定义包含一组容器定义(containerDefinitions)。下面的策略匹配"任一容器镜像来自指定账号与区域之外"的任务定义:
policies:
- name: find-task-def-not-using-registry
resource: aws.ecs-task-definition
filters:
- not:
- type: list-item
key: containerDefinitions
attrs:
- not:
- type: value
key: image
value: "${account_id}.dkr.ecr.us-east-2.amazonaws.com.*"
op: regex
这里 not 包裹 list-item 得到"不存在镜像来自指定仓库的容器"语义,而 attrs 内部的 not 表示"镜像不符合预期仓库"。
3.2 示例二:S3 生命周期规则
S3 桶的生命周期策略可能包含多条规则(Lifecycle.Rules)。下面的策略匹配"缺少清理未完成分片上传规则"的桶:
policies:
- name: s3-mpu-cleanup-not-configured
resource: aws.s3
filters:
- not:
- type: list-item
key: Lifecycle.Rules[]
attrs:
- Status: Enabled
- AbortIncompleteMultipartUpload.DaysAfterInitiation: not-null
这里 list-item 确保对每一条生命周期规则同时检查多个属性(启用状态 + 分片清理字段存在),这正是 value 过滤器无法完成的组合判断。
3.3 实现原理与扩展参数
从源码看,ListItemFilter(c7n/filters/core.py#L1203)的 schema 支持三个关键字段:key(JMESPath 定位列表)、attrs(对每个元素执行的子过滤器数组)、count/count_op(对命中元素数量做断言,默认 eq)。其执行流程是:对每个资源提取列表 → 为每个元素打上临时索引 c7n:_id → 用 ListItemResourceManager 对列表元素整体跑一遍子过滤器(c7n/filters/core.py#L1275-L1310)。命中元素会以 key<a href="https://link.gitcode.com/i/ada8288942f6136629624b4fd1f629a0" target="_blank">idx] 形式记入资源的 c7n:ListItemMatches 注解。值得注意的是,attrs 中注册了 value、or、and、not、reduce 子过滤器([c7n/filters/core.py#L1185-L1193),因此列表元素内部同样可以嵌套布尔逻辑。
四、Event Filter:按事件过滤
Event Filter(type: event)针对与资源类型关联的 CloudWatch 事件 JSON 做过滤。此时可用的 key 来自 CloudTrail 事件,而不是 Value Filter 所用的 describe 资源调用。
policies:
- name: no-ec2-public-ips
resource: aws.ec2
mode: make
- type: cloudtrail
events:
- RunInstances
filters:
- type: event
# key 是对 CloudWatch 事件 JSON 的 JMESPath 查询
key: "detail.requestParameters.networkInterfaceSet.items[].associatePublicIpAddress"
# key 表达式返回列表。将 "op: contains" 与 "value: true" 组合,
# 使过滤器在任一网卡带公网 IP 时命中
op: contains
value: true
actions:
- type: terminate
force: true
实现上 EventFilter 继承自 ValueFilter(c7n/filters/core.py#L909-L927),但有两处差异:其一,validate() 强制要求策略必须带 mode(即 Lambda 模式),否则抛出校验错误;其二,process() 直接对事件对象求值——事件不存在时原样返回资源,事件存在时只有事件命中过滤器才返回资源集,否则返回空列表。因此 Event Filter 只适用于事件驱动(CloudTrail/定时器等)策略,配合上述示例中的 mode: cloudtrail 使用。
五、Reduce Filter:分组、排序、限量
reduce 过滤器用于对资源分组、排序、限量,然后再交给后续过滤器或动作。典型场景:批量删除 AMI 时想按批次操作最老的镜像;或者做混沌工程(Chaos Engineering)时从每个 ASG 里随机挑实例,但要保证每个 ASG 最多只影响一台。
处理流程分四步:
- 对资源分组(Group)
- 对每组资源排序(Sort)
- 从每组中选出一定数量资源(Select)
- 合并各组结果(Combine)
5.1 分组资源(Grouping)
依据 group-by 属性提取的值对资源分组。无法提取到值的资源各自独立成组;未指定 group-by 时同样如此(所有资源按无分组处理)。
5.2 排序资源(Sorting)
组内排序由 sort-by 与 order 组合控制:sort-by 决定用哪个值排序,order 决定排序方向。值为 null 的资源默认排到最后,可用 null-order 改为排到最前。
注意:如果
sort-by与order都未指定,则不进行任何排序。
5.3 选择资源(Selecting)
分组排序完成后,先 discard 掉一部分资源,再对剩余资源 limit 最大数量。
- 指定
discard或discard-percent时,取"discard-percent百分比数量"与"discard绝对数量"中的较大者,从每组有序列表的头部丢弃。 - 丢弃后施加数量上限:先应用
limit-percent(占原始数量的百分比),再应用limit(绝对数量)。资源从列表头部开始保留。
用一个 50 个资源的组来推演:
discard: 5
discard-percent: 20
limit: 10
limit-percent: 30
先丢弃前 10 个(50 的 20% 是 10,大于 5),剩 40 个;再施加 limit:40 的 30% 是 12,但 limit 是 10 更小,所以保留前 10 个。若资源编号为 #1-50,则丢弃 1-10,保留 11-20,丢弃 21-50。
再看一组设置:
discard-percent: 25
limit-percent: 50
丢弃 50 的 25%(12 个),剩余 38 个中保留 50%(19 个),最终得到资源 13-31。
注意:这些规则可能让某组资源被清空。例如一个组 20 个资源、另一个组 5 个资源,指定 limit-percent = 10 时,第一组得 2 个、第二组得 0 个。
5.4 合并资源组(Combining)
各组处理完后需要合并回一个资源集合。由于组由 JMESPath 表达式决定,先按 order 属性对组本身排序(与组内排序方式一致),然后简单地拼接各组资源。源码中 limit() 正是按组排序后逐组 discard/limit 再 extend 到结果列表(c7n/filters/core.py#L1142-L1170),且每组的 discard 数量按组内资源数即时计算,避免大组的丢弃量泄漏到后续小组。
5.5 属性一览
-
group-by、sort-by两者定义方式相同,为简化写法可直接给一个字符串,等价于指定
key:key—— 提取值的 JMESPath 表达式value_regex—— 带单个捕获组的正则,从key的结果中再提取一部分value_type—— 解析为以下类型之一:string(默认)numberdate
-
order—— 控制排序方式:asc(默认)—— 基于key升序desc—— 基于key降序reverse—— 反转资源顺序(忽略key)randomize—— 随机化资源顺序(忽略key)
-
null-order—— 排序时 null 值资源的落位:last(默认)—— 列表末尾first—— 列表开头
-
discard—— 每组丢弃前 N 个资源 -
discard-percent—— 每组丢弃前 N% 资源 -
limit—— 每组(丢弃后)保留前 N 个资源 -
limit-percent—— 每组(丢弃后)保留前 N% 资源
源码中 null_sort_value() 对三种 value_type 分别构造极值哨兵(数字用 ±inf、日期用 datetime.min/max、字符串用空串或 '\uffff'),从而让 null 值稳定沉底或浮顶(c7n/filters/core.py#L1121-L1140)。
5.6 实战示例
示例一:混沌工程。 从每个 ASG 中选出运行最久的实例,再随机取其中 10%(总数不超过 15),最后终止它们:
policies:
- name: chaos-engineering
resource: aws.ec2
filters:
- "State.Name": "running"
- "tag:aws:autoscaling:groupName": present
- type: reduce
group-by: "tag:aws:autoscaling:groupName"
sort-by: "LaunchTime"
order: asc
limit: 1
- type: reduce
order: randomize
limit: 15
limit-percent: 10
actions:
- terminate
这里第一个 reduce 每个 ASG 只保留最早启动的 1 台;第二个 reduce 在全局随机化后按 10% 且上限 15 台裁剪。
示例二:限量清理旧 AMI。 只处理按年龄排序前 10 个镜像:
- name: limited-ami-expiration
resource: aws.ami
filters:
- type: image-age
days: 180
op: ge
- type: reduce
sort-by: "CreationDate"
order: asc
limit: 10
actions:
- deregister
示例三:按过期日期排序。 标签里的日期格式可能不统一、无法按文本排序,因此用 date 类型解析后再排序:
- name: ami-expiration-by-expire-date
resource: aws.ami
filters:
- type: value
key: "tag:expire-after"
value_type: age
op: gt
value: 0
- type: reduce
sort-by:
key: "tag:expire-after"
value_type: date
order: asc
limit: 10
actions:
- deregister
六、小结:从过滤器到可治理的策略
至此,Cloud Custodian 的通用过滤器体系已完整呈现:value 过滤器通过 JMESPath + 运算符 + 值类型转换实现绝大多数字段级匹配;list-item 深入列表元素做组合判断;event 把过滤对象切换到事件 JSON;reduce 则在资源集合层面完成分组、排序与限量。所有运算符与转换均在 c7n/filters/core.py 中集中注册,外部值解析在 c7n/resolver.py 中实现,附加 JMESPath 函数位于 c7n/utils.py,对应行为有 tests/test_filters.py 中的大量单测背书(如 value_path 逐资源重算、value_from 优先级、value_regex 捕获组等)。
编写策略时建议遵循这样的排查路径:先跑一个只有 name + resource 的最小策略,从输出目录的 resources.json(参见 tests/data/output/ec2-running-instances/resources.json)确认字段结构;再按"提取 key → 选择运算符 → 决定是否需要 value_type/value_regex/value_from/value_path"的顺序组装过滤器;最后用 custodian run --dryrun 或直接执行策略验证命中集合,再附加 actions 生效。掌握这套通用过滤器,你就能以一致的 YAML 语法在 AWS、Azure、GCP 等多个云上写出可复用、可审计的治理策略。