tl;dr:CEL成本非常受限,尤其是在数组方面,尽管从逻辑上讲并不比原生OpenAPI表达式更昂贵
发生了什么?
- 我将一个非常复杂的、N^2的原生OpenAPI验证迁移到了一个简单的O(1)CEL表达式
- CRD现在因为超出最大成本而被拒绝
你期望发生什么?
我可以从复杂的表达式迁移到简单的CEL表达式
我们如何尽可能精确地重现它?
我试图做的是在CRD中表示protobuf。特别是,protobuf的 oneOf
方面。请注意,proto中的 "oneOf" 与OpenAPI不同:它允许无设置,也一样。
这在OpenAPI中很难表示,尤其是如果您有一个消息或嵌套消息中有多个oneOf
时。在这种情况下,我找到的最佳实现最终是N^2:
oneOf:
- not:
anyOf:
- required:
- simple
- properties:
consistentHash:
allOf:
- oneOf:
- not:
anyOf:
- required:
- httpHeaderName
- required:
- httpCookie
- required:
- useSourceIp
- required:
- httpQueryParameterName
- required:
- httpHeaderName
- required:
- httpCookie
- required:
- useSourceIp
- required:
- httpQueryParameterName
- oneOf:
- not:
anyOf:
- required:
- ringHash
- required:
- maglev
- required:
- ringHash
- required:
- maglev
properties:
minimumRingSize: {}
required:
- consistentHash
- required:
- simple
- properties:
consistentHash:
allOf:
- oneOf:
- not:
anyOf:
- required:
- httpHeaderName
- required:
- httpCookie
- required:
- useSourceIp
- required:
- httpQueryParameterName
- required:
- httpHeaderName
- required:
- httpCookie
- required:
- useSourceIp
- required:
- httpQueryParameterName
- oneOf:
- not:
anyOf:
- required:
- ringHash
- required:
- maglev
- required:
- ringHash
- required:
- maglev
properties:
minimumRingSize: {}
required:
- consistentHash
properties:
consistentHash:
properties:
httpCookie:
description: Hash based on HTTP cookie.
properties:
name:
description: Name of the cookie.
type: string
path:
description: Path to set for the cookie.
type: string
ttl:
description: Lifetime of the cookie.
type: string
type: object
httpHeaderName:
description: Hash based on a specific HTTP header.
type: string
httpQueryParameterName:
description: Hash based on a specific HTTP query
parameter.
type: string
maglev:
description: The Maglev load balancer implements
consistent hashing to backend hosts.
properties:
tableSize:
description: The table size for Maglev hashing.
type: integer
type: object
minimumRingSize:
description: Deprecated.
type: integer
ringHash:
description: The ring/modulo hash load balancer
implements consistent hashing to backend hosts.
properties:
minimumRingSize:
description: The minimum number of virtual nodes
to use for the hash ring.
type: integer
type: object
useSourceIp:
description: Hash based on the source IP address.
type: boolean
type: object
由于认知复杂性和成本(它的数量巨大——对我们API的少量更改很容易进一步膨胀),我想用CEL替换它。这可以通过一个简单的O(1)CEL表达式来实现。 (切换到另一个更简单的消息,所以它不是巨大的):
allowOrigins:
description: String patterns that match allowed origins.
items:
properties:
exact:
type: string
prefix:
type: string
regex:
description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).
type: string
type: object
x-kubernetes-validations:
- message: At most one of [exact prefix regex] should
be set
reason: FieldValueDuplicate
rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
type: array
这里的问题是 array
。也许我们应该有一个 maxItems
,但我们没有,而且这样做对于我们的用户来说是一个向后不兼容的破坏性更改。
我理解理论上的成本是 unbounded list size * really small number
(我认为“非常小的数字”是 6
,如果我正确计算了成本)。但同样的道理也适用于原生OpenAPI!如果我理解正确的话,这个限制本质上意味着在任何 array
API中,我们不能使用CEL并被迫坚持使用原生OpenAPI验证。它在简单的情况下有效,但我们没有简单的情况下。这将有效地阻止我们在一些简单的用例之外采用任何CEL;我们大量的API都是复杂对象嵌套在无限制的 arrays
下。请注意,即使对于我们的使用情况,maxItems边界也需要相当极端;我在某些情况下直到将其降低到100才得到错误信息。
以下显示了一个最小的复现器。在现实世界中,显然相同的规则不会被重复--这只是个简单的例子。
# Generated with `kubectl kustomize "https://github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v0.8.0"`
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: gatewayclasses.example.io
spec:
group: example.io
names:
categories:
- gateway-api
kind: GatewayClass
listKind: GatewayClassList
plural: gatewayclasses
singular: gatewayclass
scope: Cluster
versions:
- name: v1alpha2
schema:
openAPIV3Schema:
properties:
allowOrigins:
items:
properties:
exact:
type: string
prefix:
type: string
regex:
type: string
type: object
x-kubernetes-validations:
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
- rule: (has(self.exact)?1:0)+(has(self.prefix)?1:0)+(has(self.regex)?1:0)<=1
type: array
type: object
served: true
storage: true
我们需要了解其他什么吗?
有人告诉我要报告这样的bug并标记@TristonianJones@cici37和@zielenski关于CEL可用性的问题,所以我正在这样做
8条答案
按热度按时间puruo6ea1#
/sig api-machinery
njthzxwz2#
链接到Slack讨论:https://kubernetes.slack.com/archives/C0EG7JC6T/p1696276649106449
3lxsmp7m3#
感谢您提出这个问题!
在成本评估方面,我们既有预估成本,也有运行时成本。在大多数情况下,运行时成本实际上比我们设定的限制要小,但在预估成本方面,由于我们希望尽可能早地防止昂贵的验证规则运行,这与向左迁移的努力保持一致,如果没有指定
maxtItems
的无界列表大小将会非常大,尤其是在多次选择操作之后。我们在这里特别提到了最佳实践:https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#resource-use-by-validation-functions ,以鼓励用户遵循最佳实践。对于替换
OneOf
的使用场景,您是否考虑过使用 cel 宏,如exists_one
(参考:https://github.com/google/cel-go#macros)?我相信这在这种情况下可能会有很大帮助。谢谢!rta7y2nd4#
对于替换OneOf的使用场景,你是否考虑过使用cel宏,如exists_one(参考:https://github.com/google/cel-go#macros)?我相信这在这种情况下可能会有很大帮助。谢谢!
CEL表达式在这里并不重要,我可以放
'true'
,它仍然超过了成本限制(我记得 - 我昨天试过了)。我们特别强调了最佳实践,即通过maxItems、maxProperties和maxLength为将在验证规则中处理的任何内容设置限制,以防止在成本估算期间出现验证错误。
我了解最佳实践,但这不是全新的领域。我们是使用CRDs的最大规模之一的项目之一,我们的CRs部署了数百万个示例。对我们的API进行重大更改实际上是不可能的,尤其是当原因是为了满足(某种任意的)CRD限制时。
对于我们的一些CRDs,为了满足整体执行限制,我们不得不将maxItems限制为100 - 这仅仅是为了添加一个CEL表达式;我们计划(或计划)添加100个。我们有实际的用户在生产环境中拥有超过100个项目
wbgh16ku5#
在这里,CEL表达式无关紧要,我可以放
'true'
,它仍然超过了成本限制(我记得 - 我昨天试过)。很好奇为什么只有
true
在表达式中就超过了限制。表达式本身是针对估计的成本进行评估的,而您提到的上面的really small number
是CEL为验证表达式中使用的操作提供的估计成本。我知道最佳实践,但这不是全新的领域。我们是目前使用CRDs的最大项目之一,我们的CRs部署了数百万个示例。对我们的API进行破坏性更改实际上是不可能的,尤其是当原因是为了满足(某种任意的)CRD限制时。
该功能目前仍处于beta阶段,cel侧的bug修复确实导致了一些不向后兼容的问题(#120821)。我们将努力防止破坏性更改发生,其中一个努力是 CRD Validation Ratcheting 。如果
Ensure updates to a CRD that don't change CEL rules are allowed even if the existing CEL rules exceed cost estimates
和我们一起工作,这将大大有助于解决这个问题。对于我们的一些CRDs,我们不得不将最大项限制为100以满足整体执行限制--这只是为了添加一个CEL表达式;我们计划(或计划)添加100个。我们有实际的用户在生产环境中有超过100个项目
如果生产用户有一个非常大的项目并尝试进行一些昂贵的cel调用,由于我们已经设置了多个限制,如每个表达式的成本限制、整体表达式限制等,它将无法通过。不幸的是,我们没有无限的资源提供,至少比集群崩溃要好 :)
yyhrrdl86#
遗憾的是,我们没有无限的资源来提供,至少这比集群崩溃要好。
是的,但我们已经在原生OpenAPI中进行了完全相同的复杂验证,没有任何限制。在我们的情况下,我们正在从一个复杂的OpenAPI表达式转移到一个简单的CEL表达式 - 我们正在降低执行成本,但由于它是CEL,我们(我认为)受到了惩罚。
更糟糕的是,大部分这种逻辑实际上只是推送到webhooks。我们原本计划完全迁移出webhook并使用CEL。webhook的成本(无论是在计算方面(在webhook和api服务器上),还是复杂性方面)都是评估简单CEL表达式的几个数量级更高。
yhived7q7#
/triage accepted
ohtdti5x8#
/remove-kind bug
/kind 支持功能
当前的成本估算是准确的最坏情况,这就是我们必须使用以防止滥用。我不认为一个新功能在资源使用方面比旧功能做得更好是一个bug,确切地说。