kubernetes CEL反馈:数组内的表达式代价太高

z3yyvxxp  于 8个月前  发布在  Kubernetes
关注(0)|答案(8)|浏览(93)

tl;dr:CEL成本非常受限,尤其是在数组方面,尽管从逻辑上讲并不比原生OpenAPI表达式更昂贵

发生了什么?

  1. 我将一个非常复杂的、N^2的原生OpenAPI验证迁移到了一个简单的O(1)CEL表达式
  2. 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可用性的问题,所以我正在这样做

3lxsmp7m

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)?我相信这在这种情况下可能会有很大帮助。谢谢!

rta7y2nd

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个项目

wbgh16ku

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调用,由于我们已经设置了多个限制,如每个表达式的成本限制、整体表达式限制等,它将无法通过。不幸的是,我们没有无限的资源提供,至少比集群崩溃要好 :)

yyhrrdl8

yyhrrdl86#

遗憾的是,我们没有无限的资源来提供,至少这比集群崩溃要好。
是的,但我们已经在原生OpenAPI中进行了完全相同的复杂验证,没有任何限制。在我们的情况下,我们正在从一个复杂的OpenAPI表达式转移到一个简单的CEL表达式 - 我们正在降低执行成本,但由于它是CEL,我们(我认为)受到了惩罚。
更糟糕的是,大部分这种逻辑实际上只是推送到webhooks。我们原本计划完全迁移出webhook并使用CEL。webhook的成本(无论是在计算方面(在webhook和api服务器上),还是复杂性方面)都是评估简单CEL表达式的几个数量级更高。

ohtdti5x

ohtdti5x8#

/remove-kind bug
/kind 支持功能
当前的成本估算是准确的最坏情况,这就是我们必须使用以防止滥用。我不认为一个新功能在资源使用方面比旧功能做得更好是一个bug,确切地说。

相关问题