ip-webhook流程

973 阅读12分钟

WebHook 是一种 HTTP 回调:某些条件下触发的 HTTP POST 请求;通过 HTTP POST 发送的简单事件通知。一个基于 web 应用实现的 WebHook 会在特定事件发生时把消息发送给特定的 URL。

具体来说,当在判断用户权限时,Webhook 模式会使 Kubernetes 查询外部的 REST 服务。

Admission Webhook?

Admission Webhook 是一种接收准入请求并对其进行处理的 HTTP 回调机制。

有两种类型的准入 webhook:

MutatingAdmissionWebhook 会先被调用。它们可以更改发送到 API 服务器的对象以执行自定义的设置默认值操作。在完成了所有对象修改并且 API 服务器也验证了所传入的对象之后, ValidatingAdmissionWebhook 会被调用,并通过拒绝请求的方式来强制实施自定义的策略。

先决条件

  • 确保 Kubernetes 集群版本至少为 v1.16(以便使用 admissionregistration.k8s.io/v1 API) 或者 v1.9 (以便使用 admissionregistration.k8s.io/v1beta1 API)。
  • 确保启用 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 控制器。 这里 是一组推荐的 admission 控制器,通常可以启用。
  • 确保启用了 admissionregistration.k8s.io/v1beta1 API。

Webhook 服务器(ip-webhook)

webhook服务器 处理由 apiserver 发送的 AdmissionReview 请求,并且将其决定作为 AdmissionReview 对象以相同版本发送回去。

请求

向 Webhook 发送 POST 请求时,请设置 Content-Type: application/json 并对 admission.k8s.io API 组中的 AdmissionReview 对象进行序列化,将所得到的 JSON 作为请求的主体。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "request": {
    # 唯一标识此准入回调的随机 uid
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",

    # 传入完全正确的 group/version/kind 对象
    "kind": {"group":"autoscaling","version":"v1","kind":"Scale"},
    # 修改 resource 的完全正确的的 group/version/kind
    "resource": {"group":"apps","version":"v1","resource":"deployments"},
    # subResource(如果请求是针对 subResource 的)
    "subResource": "scale",

    # 在对 API 服务器的原始请求中,传入对象的标准 group/version/kind
    # 仅当 webhook 指定 `matchPolicy: Equivalent` 且将对 API 服务器的原始请求转换为 webhook 注册的版本时,这才与 `kind` 不同。
    "requestKind": {"group":"autoscaling","version":"v1","kind":"Scale"},
    # 在对 API 服务器的原始请求中正在修改的资源的标准 group/version/kind
    # 仅当 webhook 指定了 `matchPolicy:Equivalent` 并且将对 API 服务器的原始请求转换为 webhook 注册的版本时,这才与 `resource` 不同。
    "requestResource": {"group":"apps","version":"v1","resource":"deployments"},
    # subResource(如果请求是针对 subResource 的)
    # 仅当 webhook 指定了 `matchPolicy:Equivalent` 并且将对 API 服务器的原始请求转换为该 webhook 注册的版本时,这才与 `subResource` 不同。
    "requestSubResource": "scale",

    # 被修改资源的名称
    "name": "my-deployment",
    # 如果资源是属于名字空间(或者是名字空间对象),则这是被修改的资源的名字空间
    "namespace": "my-namespace",

    # 操作可以是 CREATE、UPDATE、DELETE 或 CONNECT
    "operation": "UPDATE",

    "userInfo": {
      # 向 API 服务器发出请求的经过身份验证的用户的用户名
      "username": "admin",
      # 向 API 服务器发出请求的经过身份验证的用户的 UID
      "uid": "014fbff9a07c",
      # 向 API 服务器发出请求的经过身份验证的用户的组成员身份
      "groups": ["system:authenticated","my-admin-group"],
      # 向 API 服务器发出请求的用户相关的任意附加信息
      # 该字段由 API 服务器身份验证层填充,并且如果 webhook 执行了任何 SubjectAccessReview 检查,则应将其包括在内。
      "extra": {
        "some-key":["some-value1", "some-value2"]
      }
    },

    # object 是被接纳的新对象。
    # 对于 DELETE 操作,它为 null。
    "object": {"apiVersion":"autoscaling/v1","kind":"Scale",...},
    # oldObject 是现有对象。
    # 对于 CREATE 和 CONNECT 操作,它为 null。
    "oldObject": {"apiVersion":"autoscaling/v1","kind":"Scale",...},
    # options 包含要接受的操作的选项,例如 meta.k8s.io/v CreateOptions、UpdateOptions 或 DeleteOptions。
    # 对于 CONNECT 操作,它为 null。
    "options": {"apiVersion":"meta.k8s.io/v1","kind":"UpdateOptions",...},

    # dryRun 表示 API 请求正在以 `dryrun` 模式运行,并且将不会保留。
    # 带有副作用的 Webhook 应该避免在 dryRun 为 true 时激活这些副作用。
    # 有关更多详细信息,请参见 http://k8s.io/docs/reference/using-api/api-concepts/#make-a-dry-run-request
    "dryRun": false
  }
}

响应

Webhook 使用 HTTP 200 状态码、Content-Type: application/json 和一个包含 AdmissionReview 对象的 JSON 序列化格式来发送响应。该 AdmissionReview 对象与发送的版本相同,且其中包含的 response 字段已被有效填充。

response 至少必须包含以下字段:

  • uid,从发送到 webhook 的 request.uid 中复制而来
  • allowed,设置为 truefalse

当允许请求时,mutating 准入 Webhook 可以选择修改传入的对象。 通过在响应中使用 patchpatchType 字段来完成的。 当前唯一支持的 patchTypeJSONPatch。 对于 patchType: JSONPatchpatch 字段包含一个以 base64 编码的 JSON patch 操作数组。

Webhook 配置

要注册准入 Webhook,请创建 MutatingWebhookConfigurationValidatingWebhookConfiguration API 对象。

每种配置可以包含一个或多个 Webhook。如果在单个配置中指定了多个 Webhook,则应为每个 webhook 赋予一个唯一的名称。

每个 Webhook 定义以下内容。

匹配请求-规则:rules

webhook 的规则列表: 指定特定的资源,通过 apiserver 请求发送到 webhook 服务器

  • operations 列出一个或多个要匹配的操作。 可以是 CREATEUPDATEDELETECONNECT* 以匹配所有内容。
  • apiGroups 列出了一个或多个要匹配的 API 组。"" 是核心 API 组。"*" 匹配所有 API 组。
  • apiVersions 列出了一个或多个要匹配的 API 版本。"*" 匹配所有 API 版本。
  • resources列出了一个或多个要匹配的资源。
    • "*" 匹配所有资源,但不包括子资源。
    • "*/*" 匹配所有资源,包括子资源。
    • "pods/*" 匹配 pod 的所有子资源。
    • "*/status" 匹配所有 status 子资源。
  • scope指定要匹配的范围。有效值为"Cluster""Namespaced""*"。 子资源匹配其父资源的范围。在 Kubernetes v1.14+ 版本中才被支持。 默认值为"*",对应 1.14 版本之前的行为。
    • "Cluster" 表示只有集群作用域的资源才能匹配此规则(API 对象 Namespace 是集群作用域的)。
    • "Namespaced" 意味着仅具有名字空间的资源才符合此规则。
    • "*" 表示没有范围限制。

如果传入请求与任何 Webhook 规则的指定操作、组、版本、资源和范围匹配,则该请求将发送到 Webhook。

匹配请求:objectSelector

在版本 v1.15+ 中, 通过指定 objectSelector,Webhook 能够根据 可能发送的对象的标签来限制哪些请求被拦截。 如果指定,则将对 objectSelector 和可能发送到 Webhook 的 object 和 oldObject 进行评估。如果两个对象之一与选择器匹配,则认为该请求已匹配。

空对象(对于创建操作而言为 oldObject,对于删除操作而言为 newObject), 或不能带标签的对象(例如 DeploymentRollbackPodProxyOptions 对象) 被认为不匹配。

仅当选择使用 webhook 时才使用对象选择器,因为最终用户可以通过设置标签来 跳过准入 Webhook。

匹配请求:namespaceSelector

通过指定 namespaceSelector,Webhook 可以根据具有名字空间的资源所处的 名字空间的标签来选择拦截哪些资源的操作。

namespaceSelector 根据名字空间的标签是否匹配选择器,决定是否针对具名字空间的资源 (或 Namespace 对象)的请求运行 webhook。 如果对象是除 Namespace 以外的集群范围的资源,则 namespaceSelector 标签无效。

匹配请求:matchPolicy

API 服务器可以通过多个 API 组或版本来提供对象。 例如,Kubernetes API 服务器允许通过 extensions/v1beta1apps/v1beta1apps/v1beta2apps/v1 API 创建和修改 Deployment 对象。

例如,如果一个 webhook 仅为某些 API 组/版本指定了规则(例如 apiGroups:["apps"], apiVersions:["v1","v1beta1"]),而修改资源的请求 是通过另一个 API 组/版本(例如 extensions/v1beta1)发出的, 该请求将不会被发送到 Webhook。

在 v1.15+ 中,matchPolicy 允许 webhook 定义如何使用其 rules 匹配传入的请求。 允许的值为 ExactEquivalent

  • Exact 表示仅当请求与指定规则完全匹配时才应拦截该请求。
  • Equivalent 表示如果某个请求意在修改 rules 中列出的资源, 即使该请求是通过其他 API 组或版本发起,也应拦截该请求。

建议指定 Equivalent,确保升级后启用 API 服务器中资源的新版本时, Webhook 继续拦截他们期望的资源。

调用 Webhook

API 服务器确定请求应发送到 webhook 后,它需要知道如何调用 webhook。 此信息在 webhook 配置的 clientConfig 节中指定。

Webhook 可以通过 URL 或服务引用来调用,并且可以选择包含自定义 CA 包,以用于验证 TLS 连接。

URL

url 以标准 URL 形式给出 webhook 的位置(scheme://host:port/path)。

host 不应引用集群中运行的服务;通过指定 service 字段来使用服务引用。 主机可以通过某些 apiserver 中的外部 DNS 进行解析。 (例如,kube-apiserver 无法解析集群内 DNS,因为这将违反分层规则)。host 也可以是 IP 地址。

请注意,将 localhost127.0.0.1 用作 host 是有风险的。

scheme 必须为 "https";URL 必须以 "https://" 开头。

使用用户或基本身份验证(例如:"user:password@")是不允许的。 使用片段("#...")和查询参数("?...")也是不允许的。

服务引用

clientConfig 内部的 Service 是对该 Webhook 服务的引用。 如果 Webhook 在集群中运行,则应使用 service 而不是 url。 服务的 namespacename 是必需的。 port 是可选的,默认值为 443。path 是可选的,默认为 "/"。

副作用

Webhook 通常仅对发送给他们的 AdmissionReview 内容进行操作。 但是,某些 Webhook 在处理 admission 请求时会进行带外更改。

进行带外更改的(产生“副作用”的) Webhook 必须具有协调机制(如控制器), 该机制定期确定事物的实际状态,并调整由准入 Webhook 修改的带外数据以反映现实情况。 这是因为对准入 Webhook 的调用不能保证所准入的对象将原样保留,或根本不保留。 以后,webhook 可以修改对象的内容,在写入存储时可能会发生冲突,或者 服务器可以在持久保存对象之前关闭电源。

此外,处理 dryRun: true admission 请求时,具有副作用的 Webhook 必须避免产生副作用。 一个 Webhook 必须明确指出在使用 dryRun 运行时不会有副作用, 否则 dry-run 请求将不会发送到该 Webhook,而 API 请求将会失败。

超时

由于 Webhook 会增加 API 请求的延迟,因此应尽快完成自身的操作。 timeoutSeconds 用来配置在将调用视为失败之前,允许 API 服务器等待 Webhook 响应的时间长度。

如果超时在 Webhook 响应之前被触发,则执行失败策略

超时值必须设置在 1 到 30 秒之间。

再调用策略

修改性质的准入插件(包括 Webhook)的任何一种排序方式都不会适用于所有情况。 (参见 issue.k8s.io/64333 示例)。 修改性质的 Webhook 可以向对象中添加新的子结构(例如向 pod 中添加 container), 已经运行的其他修改插件可能会对这些新结构有影响 (就像在所有容器上设置 imagePullPolicy 一样)。

在 v1.15+ 中,允许修改性质的准入插件感应到其他插件所做的更改, 如果修改性质的 Webhook 修改了一个对象,则会重新运行内置的修改性质的准入插件, 并且修改性质的 Webhook 可以指定 reinvocationPolicy 来控制是否也重新调用它们。

可以将 reinvocationPolicy 设置为 NeverIfNeeded。 默认为 Never

  • Never: 在一次准入测试中,不得多次调用 Webhook。
  • IfNeeded: 如果在最初的 Webhook 调用之后被其他对象的插件修改了被接纳的对象, 则可以作为准入测试的一部分再次调用该 webhook。

要注意的重要因素有:

  • 不能保证附加调用的次数恰好是一。
  • 如果其他调用导致对该对象的进一步修改,则不能保证再次调用 Webhook。
  • 使用此选项的 Webhook 可能会重新排序,以最大程度地减少额外调用的次数。
  • 要在确保所有修改都完成后验证对象,请改用验证性质的 Webhook (推荐用于有副作用的 Webhook)。

失败策略

failurePolicy 定义了如何处理准入 webhook 中无法识别的错误和超时错误。允许的值为 IgnoreFail

  • Ignore 表示调用 webhook 的错误将被忽略并且允许 API 请求继续。
  • Fail 表示调用 webhook 的错误导致准入失败并且 API 请求被拒绝。

![image-20210611171206054](/Users/xmly/Library/Application Support/typora-user-images/image-20210611171206054.png)

kubectl describe MutatingWebhookConfiguration mutating-webhook-ip-cfg
#mutating-webhook-ip-cfg
Name:         mutating-webhook-ip-cfg
Namespace:    
Labels:       app=admission-webhook-ip
Annotations:  <none>
API Version:  admissionregistration.k8s.io/v1
Kind:         MutatingWebhookConfiguration
Metadata:
  Creation Timestamp:  2020-10-19T03:26:38Z
  Generation:          1
  Managed Fields:
    API Version:  admissionregistration.k8s.io/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
        f:labels:
          .:
          f:app:
      f:webhooks:
        .:
        k:{"name":"ip.ximalaya.com"}:
          .:
          f:admissionReviewVersions:
          f:clientConfig:
            .:
            f:url:
          f:failurePolicy:
          f:matchPolicy:
          f:name:
          f:namespaceSelector:
          f:objectSelector:
          f:reinvocationPolicy:
          f:rules:
          f:sideEffects:
          f:timeoutSeconds:
    Manager:         kubectl
    Operation:       Update
    Time:            2020-10-19T03:26:38Z
  Resource Version:  133511409
  Self Link:         /apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/mutating-webhook-ip-cfg
  UID:               4380233a-d8e7-4f99-b7d5-baea7c9eb5a2
Webhooks:
  Admission Review Versions:
    v1beta1
  Client Config:
    URL:           https://ops.test.ximalaya.com/webhook/mutating
  Failure Policy:  Ignore
  Match Policy:    Exact
  Name:            ip.ximalaya.com
  Namespace Selector:
  Object Selector:
  Reinvocation Policy:  Never
  Rules:
    API Groups:
      core
    API Versions:
      v1
    Operations:
      CREATE
    Resources:
      pods
    Scope:          *
  Side Effects:     Unknown
  Timeout Seconds:  30
Events:             <none>

mutating-webhook-ip-cfg 逻辑

调用Webhook 的 url:ops.test.ximalaya.com/webhook/mut…

失败策略为:调用 webhook 的错误将被忽略并且允许 API 请求继续。

匹配请求为:仅当请求与指定规则完全匹配时才应拦截该请求

匹配规则:匹配针对 core v1 api 中 pods 资源的 CREATE 请求

ip-webhook 逻辑:

请求到达 mutating接口 -> 过滤 status 为 mutated 的请求 -> 获取 pod 信息 -> 查询该请求的 appName 下的所有pod -> 根据 allocatable-ips 标签获取 ip -> 分配ip,打上 allocated-ip 标签,calico 标签 -> 写入标签,annotations等数据到 AdmissionResponse 并返回

整体流程:

  1. k8s配置 MutatingWebhookConfiguration 设置回调请求规则

  2. k8s集群安装calico

    cat /etc/cni/net.d/10-calico.conflist 查看CNI配置

    {
      "name": "k8s-pod-network",
      "cniVersion": "0.3.1",
      "plugins": [
        {
          "type": "calico",
          "log_level": "info",
          "etcd_endpoints": "https://192.168.3.58:2379,https://192.168.3.59:2379,https://192.168.3.60:2379",
          "etcd_key_file": "/etc/cni/net.d/calico-tls/etcd-key",
          "etcd_cert_file": "/etc/cni/net.d/calico-tls/etcd-cert",
          "etcd_ca_cert_file": "/etc/cni/net.d/calico-tls/etcd-ca",
          "mtu": 1440,
          "ipam": {
              "type": "calico-ipam"
          },
          "policy": {
              "type": "k8s"
          },
          "kubernetes": {
              "kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
          }
        },
        {
          "type": "portmap",
          "snat": true,
          "capabilities": {"portMappings": true}
        },
        {
          "type": "bandwidth",
          "capabilities": {"bandwidth": true}
        }
      ]
    }
    

    确保 "ipam": { "type": "calico-ipam"}

  3. 创建pod前,k8s根据配置的MutatingWebhookConfiguration规则请求ip-webhook服务,给pod打上 allocated-ip 标签,calico 标签

  4. calico根据标签 "cni.projectcalico.org/ipAddrs": "[\"192.168.0.1\"]"就会给pod分配固定ip。

    【注】ip必须在calico ip池范围内并且未被占用,标签必须在创建pod之前打上

参考:

  1. docs.projectcalico.org/about/about…
  2. kubernetes.io/zh/docs/ref…