深入理解 Kubernetes API Server:核心机制与实践

深入理解 Kubernetes API Server:核心机制与实践

Kubernetes API Server (kube-apiserver) 是 Kubernetes 控制平面的核心组件,扮演着集群”大脑”的角色。它是所有管理操作的入口点,负责处理 REST 请求、验证请求、执行业务逻辑并将结果持久化到 etcd。理解 API Server 的工作原理对于有效管理和扩展 Kubernetes 集群至关重要。本文将深入探讨其核心功能、访问控制流程、限流机制、高可用性、多租户实现以及 API 对象的内部实现。

1. API Server 核心功能与架构

kube-apiserver 主要承担以下职责:

  • 提供统一的 REST API 入口:为集群内外的客户端(如 kubectl、控制器、调度器等)提供一致的 CRUD (创建、读取、更新、删除) 和 Watch 操作接口。
  • 集群状态的网关:作为唯一直接与 etcd 交互的组件,负责集群状态的持久化和读取。
  • 请求处理流水线:执行认证、授权、准入控制等一系列检查和处理步骤。
  • API 注册与发现:支持 API Group 和 Versioning,允许扩展自定义资源 (CRD)。

图:API Server 在 Kubernetes 架构中的位置

2. API 请求处理流程

当一个请求到达 kube-apiserver 时,它会经过一系列的处理 Handler(过滤器),最终到达具体的 API 资源处理逻辑。

图:API Server 请求处理链

这个流程主要包括:

  1. Panic Recovery: 捕获处理过程中的 panic,防止 apiserver 崩溃。
  2. Request Timeout: 为请求设置超时时间。
  3. Authentication (认证): 验证请求者的身份是谁。
  4. Audit (审计): 记录请求的详细信息。
  5. Impersonation (模拟): 允许一个用户临时扮演另一个用户的身份。
  6. Max-in-flight / Rate Limiting (限流): 限制并发请求数量或速率,防止过载 (APF 机制在此生效)。
  7. Authorization (授权): 检查认证通过的用户是否有权限执行请求的操作。
  8. Admission Control (准入控制): 在对象持久化之前,进行最后的验证或修改。

图:访问控制三大环节:认证、授权、准入控制

接下来,我们将详细探讨访问控制的三个关键环节。

3. 认证 (Authentication) - 你是谁?

认证是确定请求来源身份的过程。Kubernetes 支持多种认证机制,API Server 会按顺序尝试配置的认证器,一旦某个认证器成功识别用户身份,后续的认证器就不会再执行。

图:多种认证方式

以下是常见的认证机制:


3.1 X.509 客户端证书

  • 原理: 基于 TLS 双向认证。客户端提供证书,API Server 使用配置的 CA (--client-ca-file) 验证该证书。证书的 Common Name (CN) 字段作为用户名,Organization (O) 字段作为用户组。
  • 配置:
    • 生成 CA 及客户端证书 (如前文所示)。
    • 启动 API Server 时指定 --client-ca-file
  • 适用场景: 集群组件(如 kubelet, kube-proxy)、管理员用户。

3.2 Service Account Tokens

  • 原理: Kubernetes 内部机制,主要用于 Pod 访问 API Server。每个 Namespace 都有一个默认的 ServiceAccount,也可以创建自定义的。Token (JWT 格式) 以 Secret 形式存储,并自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/token。API Server 使用 --service-account-key-file 指定的公钥验证 Token 签名。
  • 优点: Kubernetes 原生集成,自动管理。
  • 适用场景: Pod 内应用访问 API Server。

3.3 OpenID Connect (OIDC) Tokens

  • 原理: 集成外部身份提供商 (IdP),如 Keycloak, Google, Dex 等。用户通过 IdP 获取 ID Token (JWT),并在请求头 Authorization: Bearer <token> 中提供给 API Server。API Server 使用 IdP 的公钥验证 Token,并从中提取用户信息(用户名、组等)。
  • 配置: 需要配置 --oidc-issuer-url, --oidc-client-id, --oidc-username-claim, --oidc-groups-claim 等参数。
  • 适用场景: 集成企业 SSO 系统,为人类用户提供认证。

3.4 Webhook Token 认证

  • 原理: 将 Token 发送给一个外部 Webhook 服务进行验证。API Server 向配置的 Webhook 端点发送 TokenReview 对象,Webhook 服务返回包含用户信息的 TokenReviewStatus
  • 配置:
    • 创建 Webhook 配置文件 (如前文所示)。
    • 启动 API Server 时指定 --authentication-token-webhook-config-file
  • 适用场景: 实现自定义或复杂的认证逻辑,集成现有认证系统。
3.4.1 基于 Webhook 的认证服务集成示例

构建一个符合 TokenReview API 规范的 Webhook 服务。

  • 输入: TokenReview 对象,包含待验证的 Token。
  • 处理: 服务根据 Token 查询用户信息(例如,调用 GitHub API 验证 Personal Access Token)。
  • 输出: TokenReview 对象,status 字段包含认证结果 (authenticated: true/false) 和用户信息 (user: {username, uid, groups}).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//解码认证请求
decoder := json.NewDecoder(r.Body)
var tr authentication.TokenReview
err := decoder.Decode(&tr)
if err != nil {
//...错误处理
return
}

// 转发认证请求至认证服务器(以github为例)
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: tr.Spec.Token},
)
tc := oauth2.NewClient(oauth2.NoContext, ts)
client := github.NewClient(tc)
user, _, err := client.Users.Get(context.Background(), "")
if err != nil {
//...错误处理
return
}
// 认证结果返回给APIServer
w.WriteHeader(http.StatusOK)
trs := authentication.TokenReviewStatus{
Authenticated: true,
User: authentication.UserInfo{
Username: *user.Login,
UID: *user.Login,
},
}
json.NewEncoder(w).Encode(map[string]interface{}{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": trs,
})
3.4.2 Keystone 认证集成陷阱
  • 问题: 使用某些版本的 gophercloud 库与 Keystone 集成时,当 Keystone 返回 Token 过期错误,gophercloud 可能会无限重试,导致 API Server 持续向 Keystone 发送请求,最终压垮 Keystone。
  • 解决方案:
    • 熔断: 在 API Server 的 Keystone 认证插件或 Webhook 服务中实现熔断逻辑,当错误率超过阈值时暂时停止向 Keystone 发送请求。
    • 限流: 对发往 Keystone 的请求进行限流。
    • 更新库: 使用修复了此问题的 gophercloud 版本。

3.5 引导 Token (Bootstrap Token)

  • 原理: 用于 kubeadm join 过程,让新节点安全地加入集群。Token 以 Secret 形式存储在 kube-system 命名空间,有 TTL,由 kube-controller-managertokencleaner 控制器自动清理。
  • 适用场景: 集群节点引导。

3.6 静态 Token 文件 & 静态密码文件

  • 原理: 将预定义的 Token 或用户名密码存储在 CSV 文件中 (--token-auth-file, --basic-auth-file)。API Server 加载文件内容进行匹配。
  • 缺点: 安全性差(明文存储、无过期),管理不便。不推荐用于生产环境。

3.7 匿名请求

  • 原理: 如果所有配置的认证器都无法识别请求者身份,且 API Server 启用了匿名认证 (--anonymous-auth=true,默认启用),则请求被视为匿名用户 (system:anonymous),属于 system:unauthenticated 组。
  • 注意: 需要配合授权策略(如 RBAC)限制匿名用户的权限。生产环境通常建议禁用 (--anonymous-auth=false) 或严格限制其权限。

4. 授权 (Authorization) - 你能做什么?

认证之后,授权环节决定用户是否有权限对目标资源执行请求的操作(如 GET, POST, DELETE)。API Server 支持多种授权模式,通过 --authorization-mode 参数指定,可以指定多个模式,按顺序检查,只要有一个模式授权通过即可。

图:授权流程

常见的授权模式:

  • Node: 特殊模式,用于授权 Kubelet 发出的 API 请求。它根据 Kubelet 自身注册的 Node 对象,限制其只能访问与自身节点相关的资源(如 Pods, Secrets, ConfigMaps 等)。
  • RBAC (Role-Based Access Control): 推荐使用。基于角色的访问控制,通过 Role/ClusterRole 定义权限,通过 RoleBinding/ClusterRoleBinding 将角色绑定到用户、组或 ServiceAccount。
  • Webhook: 将授权决策委托给外部 Webhook 服务。API Server 发送 SubjectAccessReview 对象,Webhook 服务返回授权结果。
  • ABAC (Attribute-Based Access Control): 基于属性的访问控制,通过策略文件定义规则。配置复杂,管理困难,已不推荐。
  • AlwaysAllow / AlwaysDeny: 测试模式,允许或拒绝所有请求。

4.1 RBAC 详解

RBAC 是 Kubernetes 中最常用且推荐的授权机制。

  • 核心概念:

    • Subject (主体): User, Group, ServiceAccount。
    • Role (角色): 定义在一组 Namespace 范围 内的权限(允许对哪些资源执行哪些操作)。
    • ClusterRole (集群角色): 定义 集群范围 的权限(适用于所有 Namespace 的资源或集群级别的资源,如 Node, Namespace)。
    • RoleBinding (角色绑定): 将 Role 绑定到一个或多个 Subject,使其在 特定 Namespace 内拥有该 Role 定义的权限。
    • ClusterRoleBinding (集群角色绑定): 将 ClusterRole 绑定到一个或多个 Subject,使其在 整个集群 拥有该 ClusterRole 定义的权限。
  • 示例:为用户组授权读取 Secrets

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # ClusterRole 定义读取 Secret 的权限
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
    name: secret-reader
    rules:
    - apiGroups: [""] # "" 表示 core API group
    resources: ["secrets"]
    verbs: ["get", "watch", "list"]

    ---
    # ClusterRoleBinding 将 'secret-reader' 角色绑定到 'manager' 组
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: read-secrets-global
    subjects:
    - kind: Group
    name: manager # 组名区分大小写
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: ClusterRole
    name: secret-reader
    apiGroup: rbac.authorization.k8s.io

4.2 规划系统角色与权限

设计 RBAC 策略时,应遵循最小权限原则。常见角色划分:

  • Cluster Admin: 拥有集群所有权限 (绑定到 cluster-admin ClusterRole)。
  • Namespace Admin: 拥有特定 Namespace 内所有资源的完全控制权。
  • Developer: 在特定 Namespace 内创建、更新、删除工作负载(Deployments, Pods 等)的权限,可能有限制地访问 Secrets, ConfigMaps。
  • Viewer: 对特定 Namespace 或整个集群有只读权限。
  • CI/CD System (ServiceAccount): 部署应用所需的权限。

4.3 实现自定义授权逻辑 (Namespace Owner 示例)

有时需要更动态的授权逻辑,例如让 Namespace 的创建者自动拥有该 Namespace 的管理权限。

  1. 创建基础角色: 定义一个 namespace-admin Role (或 ClusterRole,如果权限需要在所有 Namespace 中一致)。
  2. 准入控制器 (Mutating Webhook): 创建一个 Mutating Admission Webhook。当创建 Namespace 的请求 (CREATE Namespace) 到达时,该 Webhook 自动将请求用户的身份信息(用户名或组)添加到 Namespace 的 Annotation 中,例如 owner: user-a
  3. RBAC 控制器 (自定义 Controller): 创建一个自定义控制器,监听 Namespace 的创建和更新事件。当检测到新的 Namespace 或 owner Annotation 变化时,该控制器:
    • 读取 owner Annotation 获取用户信息。
    • 在对应的 Namespace 中创建或更新一个 RoleBinding,将步骤 1 中定义的 namespace-admin Role 绑定到该用户。

4.4 授权最佳实践与陷阱

  • 最小权限原则: 只授予必要的权限。
  • 使用 Role 和 RoleBinding 优先于 ClusterRole 和 ClusterRoleBinding,以限制权限范围。
  • 定期审计: 定期检查 RoleBinding 和 ClusterRoleBinding,移除不再需要的权限。
  • 管理 ServiceAccount 权限: 不要给 ServiceAccount 过高的权限,特别是 default ServiceAccount。
  • 避免使用 default Namespace: 为不同应用或团队创建独立的 Namespace。
  • 源代码管理 RBAC 配置: 将 RBAC YAML 文件纳入版本控制。
  • 注意权限传递: 用户 A 可以创建 RoleBinding 将自己拥有的权限授予用户 B。
  • 避免大量 Role/Binding: 过多的 RBAC 对象会影响鉴权性能。
  • 绕过鉴权 (不推荐): SSH 到 Master 节点通过 insecure port (如果启用) 访问 apiserver 可以绕过认证和授权,仅用于紧急情况。

5. 准入控制 (Admission Control) - 规则检查与修改

准入控制是请求处理流程的最后一道关卡,发生在请求通过认证和授权之后、对象持久化到 etcd 之前。它用于执行更复杂的验证逻辑、强制实施策略或在对象创建/更新时自动进行修改。

准入控制器只对 CREATE, UPDATE, DELETE, CONNECT 操作生效,对 GET, LIST, WATCH 无效。

API Server 通过 --enable-admission-plugins 参数启用一系列内置的准入控制器,并通过 --disable-admission-plugins 禁用某些默认启用的插件。执行顺序很重要。

常见的内置准入控制器:

  • NamespaceLifecycle: 防止在正在被删除的 Namespace 中创建新对象,防止删除包含活跃资源的 kube-system, kube-public, kube-node-lease Namespace。
  • LimitRanger: 读取 Namespace 下的 LimitRange 对象,为 Pod 设置默认的资源请求 (Request) 和限制 (Limit),并校验 Pod 的资源设置不超过 LimitRange 的限制。
  • ServiceAccount: 为 Pod 自动挂载 ServiceAccount Token,如果 Pod 没有指定 ServiceAccount,则使用 default ServiceAccount。
  • ResourceQuota: 读取 Namespace 下的 ResourceQuota 对象,确保创建或更新资源不会超出 Namespace 的配额限制(如 CPU, Memory, 对象数量等)。
  • PodSecurityPolicy (Deprecated in 1.21, Removed in 1.25) / PodSecurity (Stable in 1.25+): 强制实施 Pod 安全标准(如禁止特权容器、限制 HostPath 挂载等)。PodSecurity 通过 Namespace Label 控制策略级别 (privileged, baseline, restricted)。
  • MutatingAdmissionWebhook: 调用外部 Webhook 服务来修改对象。
  • ValidatingAdmissionWebhook: 调用外部 Webhook 服务来验证对象。
  • NodeRestriction: 限制 Kubelet 只能修改其自身注册的 Node 对象,以及只能获取/修改运行在自身节点上的 Pod 对象。

5.1 自定义准入控制 (Webhooks)

可以通过创建 MutatingWebhookConfigurationValidatingWebhookConfiguration 对象来注册自定义的准入逻辑。

  • Mutating Webhook: 在内置的修改逻辑之后、对象模式校验之前执行。可以修改请求中的对象。常用于自动注入 Sidecar 容器、设置默认标签等。
  • Validating Webhook: 在对象模式校验之后、持久化之前执行。只能验证对象,不能修改。常用于强制实施自定义策略、检查资源规范等。

Webhook 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: admissionregistration.k8s.io/v1 # v1beta1 已弃用
kind: MutatingWebhookConfiguration
metadata:
name: my-mutating-webhook
webhooks:
- name: my-webhook.example.com
clientConfig:
service: # 可以是 Service 或 URL
name: my-admission-webhook-service
namespace: my-admission-system
path: "/mutate"
port: 443
caBundle: # CA 证书,用于验证 Webhook Server 的 TLS 证书
"LS0t..."
rules: # 定义哪些操作和资源会触发此 Webhook
- operations: [ "CREATE", "UPDATE" ]
apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
failurePolicy: Fail # 如果 Webhook 调用失败,是拒绝请求 (Fail) 还是忽略 (Ignore)
sideEffects: None # 指明 Webhook 是否有副作用(如调用其他 API)
admissionReviewVersions: ["v1"] # 支持的 AdmissionReview API 版本
  • Webhook 服务需要实现一个 HTTPS 端点,接收 AdmissionReview 请求,并返回包含决策 (allowed: true/false, patch (for mutating), status (for validating)) 的 AdmissionReview 响应。

5.2 配额管理 (ResourceQuota)

  • 目的: 防止单个 Namespace 或用户耗尽集群资源。
  • 实现:
    1. 在 API Server 中启用 ResourceQuota 准入控制器 (--enable-admission-plugins=...,ResourceQuota,...)。
    2. 在需要限制的 Namespace 中创建 ResourceQuota 对象,定义资源限制(计算资源、存储资源、对象数量)。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      apiVersion: v1
      kind: ResourceQuota
      metadata:
      name: compute-quota
      namespace: my-namespace
      spec:
      hard:
      requests.cpu: "4"
      requests.memory: 16Gi
      limits.cpu: "8"
      limits.memory: 32Gi
      pods: "10"
      secrets: "20"
    3. 可选: 创建一个控制器或 Webhook,在 Namespace 创建时自动为其创建默认的 ResourceQuota 对象(可以从 ConfigMap 读取模板)。

6. 限流 (Rate Limiting) 与 API 优先级和公平性 (APF)

为了保护 API Server 不被过量请求压垮,需要进行限流。

6.1 传统限流 (Max-in-flight)

早期 Kubernetes 版本主要通过两个参数进行全局限流:

  • --max-requests-inflight: 同时处理的最大非变更性请求数 (GET, LIST, WATCH)。默认 400。
  • --max-mutating-requests-inflight: 同时处理的最大变更性请求数 (POST, PUT, DELETE, PATCH)。默认 200。

局限性:

  • 粒度粗: 无法区分请求来源或重要性。
  • 不公平: 一个行为异常的客户端可能耗尽所有并发额度,影响其他正常客户端。
  • 无优先级: 关键系统组件(如控制器)的请求可能被普通用户请求阻塞。

6.2 API 优先级和公平性 (API Priority and Fairness - APF)

APF (GA in 1.20) 引入了更精细化的限流机制,旨在解决传统限流的不足。

图:APF 架构示意图

核心概念:

  • PriorityLevelConfiguration (PLC): 定义一个优先级级别。每个 PLC 拥有独立的并发份额 (assuredConcurrencyShares) 和排队机制。可以配置多个 PLC,代表不同的重要程度。
    • type: Limited: 受限流控制。
      • assuredConcurrencyShares: 保证的并发执行单元数。API Server 总并发数按比例分配给各 PLC。
      • limitResponse: 当并发达到限制时的行为。
        • type: Queue: 请求进入队列等待。
          • queues: 队列数量。
          • queueLengthLimit: 每个队列的最大长度。
          • handSize: 用于 Shuffle Sharding 的参数。
        • type: Reject: 直接拒绝请求 (HTTP 429)。
    • type: Exempt: 不受限流控制。
  • FlowSchema (FS): 定义规则,将传入的请求分类 (match) 到某个 PriorityLevelConfiguration。匹配规则可以基于用户、组、ServiceAccount、请求的 Verb (GET, POST)、资源类型 (pods, deployments) 等。
    • matchingPrecedence: 多个 FlowSchema 可能匹配同一个请求,优先级高的先生效。
    • distinguisherMethod: 如何在同一个 FlowSchema 内进一步区分请求流 (Flow)。
      • type: ByUser: 按请求用户区分。
      • type: ByNamespace: 按请求资源的 Namespace 区分。
  • Request (请求): 每个到达的 API 请求。
  • Flow (流): 根据 FlowSchema 的 distinguisherMethod 划分的一组请求,例如来自同一个用户的所有请求,或针对同一个 Namespace 的所有请求。
  • Queue (队列): 每个 Limited 类型的 PLC 内部包含多个队列。
  • Shuffle Sharding: 一种将 Flow 分配到 Queue 的算法。它确保高流量的 Flow 不会完全阻塞低流量的 Flow。每个 Flow 会被哈希到 handSize 个可能的队列中,然后选择当前最不繁忙的一个队列加入。
  • Fair Queuing: 从队列中选择下一个要执行的请求的算法。确保同一个 PLC 内的不同 Flow 能够公平地获得执行机会,防止某个 Flow 饿死其他 Flow。

默认配置: Kubernetes 自带了一些默认的 PLC 和 FS,用于区分系统组件、领导者选举、高优先级工作负载、普通工作负载和默认流量。

  • PLC 示例 (global-default):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    apiVersion: flowcontrol.apiserver.k8s.io/v1beta1 # 或 v1
    kind: PriorityLevelConfiguration
    metadata:
    name: global-default
    spec:
    type: Limited
    limited:
    assuredConcurrencyShares: 20 # 保证的并发份额
    limitResponse:
    type: Queue # 超出并发时排队
    queuing:
    queues: 128 # 队列数量
    queueLengthLimit: 50 # 每个队列长度
    handSize: 6 # Shuffle Sharding 参数
  • FS 示例 (kube-scheduler):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    apiVersion: flowcontrol.apiserver.k8s.io/v1beta1 # 或 v1
    kind: FlowSchema
    metadata:
    name: kube-scheduler
    spec:
    priorityLevelConfiguration:
    name: workload-high # 关联到高优先级 PLC
    matchingPrecedence: 800 # 匹配优先级
    distinguisherMethod:
    type: ByUser # 按用户区分流
    rules:
    - subjects: # 匹配来自 kube-scheduler 用户的请求
    - kind: User
    user:
    name: system:kube-scheduler
    resourceRules: # 匹配所有资源和操作
    - verbs: ["*"]
    apiGroups: ["*"]
    resources: ["*"]

调试 APF: 可以通过 API Server 的 /debug/api_priority_and_fairness/ 端点查看当前的 PLC 状态、队列信息和请求统计。

7. 高可用 API Server

由于 API Server 是控制平面的核心,其高可用性至关重要。

  • 无状态设计: API Server 本身是无状态的 REST 服务,状态存储在 etcd 中。这使得横向扩展(运行多个实例)变得容易。
  • 多副本部署: 运行至少 3 个 API Server 实例,分布在不同的物理节点或可用区。
  • 负载均衡: 在多个 API Server 实例前放置一个负载均衡器(如 Nginx, HAProxy, 或云厂商提供的 LB 服务)。
    • 所有客户端(包括集群内部组件如 Kubelet, Controller Manager, Scheduler)都应配置为访问负载均衡器的 VIP 或 DNS 名称。
    • 证书: API Server 的 TLS 证书需要包含负载均衡器的 VIP 和 DNS 名称,以及各个 API Server 实例的 IP 和主机名。
  • etcd 集群: API Server 依赖高可用的 etcd 集群来存储状态。确保 etcd 集群也是高可用的(通常 3 或 5 个成员)。

启动参数示例 (部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
kube-apiserver \
--advertise-address=<THIS_INSTANCE_IP> \ # 当前实例通告给集群其他成员的 IP
--allow-privileged=true \
--authorization-mode=Node,RBAC \ # 启用 Node 和 RBAC 授权
--client-ca-file=/etc/kubernetes/pki/ca.crt \
--enable-admission-plugins=NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,... \ # 启用准入插件
--etcd-servers=https://etcd-0:2379,https://etcd-1:2379,https://etcd-2:2379 \ # etcd 集群地址
--etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt \
--etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt \
--etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key \
--kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \
--kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \
--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname \
--secure-port=6443 \ # HTTPS 端口
--service-account-key-file=/etc/kubernetes/pki/sa.pub \
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key \
--service-account-issuer=https://kubernetes.default.svc.cluster.local \ # Service Account Issuer
--service-cluster-ip-range=10.96.0.0/12 \ # Service Cluster IP 范围
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt \ # API Server TLS 证书
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key \
--apiserver-count=3 \ # API Server 实例数量 (用于选举等)
--endpoint-reconciler-type=lease \ # 端点控制器类型
--enable-priority-and-fairness=true # 启用 APF
# ... 其他 OIDC, Webhook 等认证/授权/准入配置 ...

其他最佳实践:

  • 为 API Server Pod 配置足够的 CPU 和 Memory 资源请求和限制。
  • 监控 API Server 的请求延迟、错误率、资源使用情况。
  • 客户端(尤其是控制器)应使用长连接 (Watch) 而不是频繁轮询 (List)。
  • 内部客户端(如 Controller Manager, Scheduler)优先通过 Service Cluster IP 访问 API Server,减少对外部负载均衡器的依赖。

8. 构建多租户 Kubernetes 集群

多租户是指在单个 Kubernetes 集群中支持多个独立的租户(用户或团队),同时保证它们之间的隔离和安全性。API Server 的访问控制机制是实现多租户的基础。

核心目标:

  1. 授信 (Authentication & Authorization):
    • 认证: 对接企业身份系统 (如 AD, LDAP, OIDC Provider),确保只有授权用户能登录。可使用 OIDC 或 Webhook 认证。
    • 授权: 使用 RBAC 精确控制每个租户在其指定 Namespace 内的权限。避免使用 ClusterRoleBinding 授予租户用户集群级别的权限。
  2. 隔离:
    • 命名空间 (Namespace): 最基本的隔离单元。每个租户分配一个或多个专属 Namespace。RBAC 规则主要基于 Namespace 进行限制。
    • 网络策略 (NetworkPolicy): 控制不同 Namespace 之间以及 Pod 之间的网络流量,实现网络隔离。
    • 资源配额 (ResourceQuota): 限制每个 Namespace 的资源使用量,防止租户间资源争抢。
    • 节点隔离 (Node Selectors/Affinity/Taints/Tolerations): 可以将特定节点分配给特定租户使用(硬隔离),但这会降低资源利用率。
    • 运行时隔离 (RuntimeClass, gVisor, Kata Containers): 使用沙箱容器技术增强 Pod 间的内核级隔离。
    • 可见性隔离: 通过 RBAC 限制用户只能看到其有权限访问的 Namespace 中的资源。
  3. 资源管理 (Quota Management):
    • 使用 ResourceQuota 限制计算资源、存储资源和 API 对象数量。
    • 使用 LimitRanger 设置默认资源请求和限制。

实现要点:

  • 租户 onboarding: 需要自动化流程来创建 Namespace、设置 RBAC (RoleBindings)、配置 ResourceQuota 和 NetworkPolicy。
  • 自定义控制器/Operator: 可以开发 Operator 来管理租户生命周期和相关资源的配置。
  • 监控与审计: 对各租户的资源使用和 API 访问进行监控和审计。

9. API Server 对象实现原理

Kubernetes API 基于 “资源 (Resource)” 的概念构建。理解 API 对象的内部表示和处理方式有助于进行扩展开发(如 CRD)。

9.1 GKV 模型 (Group, Kind, Version)

Kubernetes API 使用 GKV 来唯一标识一个 API 对象类型。

  • Group (组): 相关 API 功能的集合。例如 apps, batch, rbac.authorization.k8s.io。核心 API (如 Pod, Service, Namespace) 属于空字符串 "" 组 (core group)。Group 使得 API 可以独立演进和扩展。
  • Version (版本): 代表 API 的成熟度和稳定性。例如 v1, v1beta1, v2alpha1。版本允许 API 随时间演进,同时保持向后兼容性。
    • External Version: 暴露给客户端使用的版本 (如 apps/v1)。
    • Internal Version: API Server 内部处理和存储时使用的统一版本。所有外部版本在内部都会转换成内部版本进行处理。
  • Kind (类型): 具体的资源类型。例如 Deployment, Pod, RoleBinding。Kind 在同一个 GroupVersion 内必须是唯一的。

图:GKV 示例

9.2 定义 API Group 和类型 (Go 代码示例)

在 Kubernetes 代码库中 (或自定义 API Server/CRD 控制器中),通常这样定义 GKV 和对象类型:

  1. 注册 Group 和 Version (pkg/apis/{group}/register.go):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package core // 假设是 core group

    import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
    )

    // GroupName 是 API Group 的名称,core group 为空字符串
    const GroupName = ""

    // SchemeGroupVersion 是此包定义的 Group 和内部 Version
    var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}

    // SchemeBuilder 用于向 Scheme 注册类型
    var (
    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    AddToScheme = SchemeBuilder.AddToScheme
    )

    // addKnownTypes 将此 GroupVersion 下的类型注册到 Scheme
    func addKnownTypes(scheme *runtime.Scheme) error {
    // 注册 Pod 和 PodList 类型到内部版本
    scheme.AddKnownTypes(SchemeGroupVersion,
    &Pod{},
    &PodList{},
    // ... 其他 core 类型 ...
    )

    // 注册外部版本 (例如 v1)
    metav1.AddToGroupVersion(scheme, schema.GroupVersion{Group: GroupName, Version: "v1"})
    // 需要一个 v1 包来定义 v1.Pod, v1.PodList 并实现转换逻辑

    return nil
    }
  2. 定义对象类型 (pkg/apis/{group}/{version}/types.go):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package v1 // 假设是 v1 版本

    import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )

    // +genclient // Tag: 生成 client 代码
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Tag: 生成 DeepCopy 方法

    // Pod 是 v1 版本的 Pod 对象定义
    type Pod struct {
    metav1.TypeMeta `json:",inline"` // 包含 Kind 和 APIVersion
    metav1.ObjectMeta `json:"metadata,omitempty"` // 包含 Name, Namespace, Labels, Annotations 等元数据

    Spec PodSpec `json:"spec,omitempty"` // Pod 的期望状态
    Status PodStatus `json:"status,omitempty"` // Pod 的实际状态
    }

    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

    // PodList 是 Pod 对象的集合
    type PodList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"` // 包含 resourceVersion 等列表元数据

    Items []Pod `json:"items"` // Pod 列表
    }

    // --- PodSpec 和 PodStatus 的定义 ---
    type PodSpec struct {
    Containers []Container `json:"containers"`
    // ... 其他字段 ...
    }

    type PodStatus struct {
    Phase PodPhase `json:"phase,omitempty"`
    // ... 其他字段 ...
    }

    // ... Container, PodPhase 等类型的定义 ...

9.3 代码生成 Tags

Kubernetes 大量使用代码生成来自动创建客户端、Informer、Lister、DeepCopy 方法等。通过在 Go 代码中添加特定格式的注释 (Tags) 来指导代码生成器。

  • Global Tags: 通常在包的 doc.go 文件中定义,作用于整个包。
    • // +k8s:deepcopy-gen=package: 为包中所有需要深拷贝的类型生成 DeepCopy() 方法。
    • // +groupName=example.com: 指定 API Group 名称。
  • Local Tags: 定义在具体类型或字段上方。
    • // +genclient: 为该类型生成对应的 ClientSet 代码。
    • // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object: 明确指示该类型需要实现 runtime.Object 接口的 DeepCopyObject() 方法。
    • // +optional: 标记字段为可选。

9.4 实现存储接口 (etcd Storage)

API Server 需要将对象持久化到 etcd。每个 API 资源都需要一个实现了 registry.Store 接口的存储后端。Kubernetes 提供了通用的存储实现 genericregistry.Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// pkg/registry/core/pod/storage/storage.go (示例简化)
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/kubernetes/pkg/apis/core" // 内部 API 类型
"k8s.io/kubernetes/pkg/printers"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/pod" // Pod 的 Strategy
)

// NewREST 创建 Pod 资源的 REST storage
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *LogREST, *ExecREST, *AttachREST, *PortForwardREST, *BindingREST, *EvictionREST, *ProxyREST) {
store := &genericregistry.Store{
// 指定如何创建新的空对象和列表对象
NewFunc: func() runtime.Object { return &core.Pod{} },
NewListFunc: func() runtime.Object { return &core.PodList{} },
// 资源名称 (用于 URL 路径)
PredicateFunc: pod.MatchPod, // 用于 List/Watch 的字段选择器
DefaultQualifiedResource: core.Resource("pods"), // GVR 中的 Resource

// 定义 Create, Update, Delete 操作的业务逻辑和验证
CreateStrategy: pod.Strategy,
UpdateStrategy: pod.Strategy,
DeleteStrategy: pod.Strategy,

// 定义如何将对象转换为表格输出 (kubectl get)
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printers.AddHandlers)},
}
options := &generic.StoreOptions{
RESTOptions: optsGetter, // 包含 etcd 连接信息等
AttrFunc: pod.GetAttrs, // 用于字段选择器
}
// 初始化 store (连接 etcd 等)
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // Handle error appropriately
}

// ... 创建 Status, Log, Exec 等子资源的 REST storage ...
statusStore := *store // Status 通常共享大部分 Store 配置
statusStore.UpdateStrategy = pod.StatusStrategy // Status 更新有特殊的 Strategy

// ... 返回主资源和子资源的 REST 对象 ...
return &REST{store}, &StatusREST{store: &statusStore}, ...
}

// REST 实现了 k8s.io/apiserver/pkg/registry/rest.StandardStorage 接口
type REST struct {
*genericregistry.Store
}

// StatusREST 实现了处理 /status 子资源的接口
type StatusREST struct {
store *genericregistry.Store
}
// Implement methods like Get, Update for StatusREST

9.5 定义业务逻辑 (Strategy)

Strategy 对象封装了特定资源类型在 Create, Update, Delete 操作时的业务逻辑和验证规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// pkg/registry/core/pod/strategy.go (示例简化)
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
)

// podStrategy 实现 rest.RESTCreateStrategy, rest.RESTUpdateStrategy, rest.RESTDeleteStrategy
type podStrategy struct {
runtime.ObjectTyper // 用于获取 GVK
names.NameGenerator // 用于生成名称 (如果需要)
}

// Strategy 是 Pod 的标准策略实例
var Strategy = podStrategy{core.Scheme, names.SimpleNameGenerator}

// NamespaceScoped 返回 true 因为 Pod 是 Namespace 范围的资源
func (podStrategy) NamespaceScoped() bool { return true }

// PrepareForCreate 在对象创建持久化前调用,可以设置默认值
func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
pod := obj.(*core.Pod)
// 设置默认的 RestartPolicy, DNSPolicy 等
// ...
}

// Validate 在对象创建持久化前调用,执行验证逻辑
func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
pod := obj.(*core.Pod)
// 调用 validation 包进行复杂的校验
return validation.ValidatePod(pod)
}

// Canonicalize 标准化对象 (通常不需要)
func (podStrategy) Canonicalize(obj runtime.Object) {}

// AllowCreateOnUpdate 返回 false,不允许通过 Update 操作创建对象
func (podStrategy) AllowCreateOnUpdate() bool { return false }

// PrepareForUpdate 在对象更新持久化前调用,可以修改新对象或基于旧对象设置字段
func (podStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPod := obj.(*core.Pod)
oldPod := old.(*core.Pod)
// 不允许修改某些字段,例如 Pod 的 NodeName (通常由调度器设置)
newPod.Spec.NodeName = oldPod.Spec.NodeName
// ...
}

// ValidateUpdate 在对象更新持久化前调用,验证更新操作是否合法
func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newPod := obj.(*core.Pod)
oldPod := old.(*core.Pod)
// 调用专门的 Status 更新验证逻辑
return validation.ValidatePodStatusUpdate(newPod, oldPod)
}

// AllowUnconditionalUpdate 返回 false,不允许无条件的更新 (需要 resourceVersion 匹配)
func (podStrategy) AllowUnconditionalUpdate() bool { return false }

// ... 实现 Delete 相关方法 ...

9.6 子资源 (Subresource)

子资源是附属在主资源下的特定部分,拥有独立的 API 端点和操作逻辑。例如 Pod 的 /status, /log, /exec

  • 实现: 通常为子资源定义一个独立的 Strategy (如 podStatusStrategy) 和一个独立的 REST 存储对象 (如 StatusREST)。
  • Status 子资源: 特别常见,用于更新对象的状态字段。其 PrepareForUpdate 逻辑通常会阻止对 Spec 字段的修改,只允许更新 Status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// pkg/registry/core/pod/strategy.go (Status Strategy 示例)
var StatusStrategy = podStatusStrategy{Strategy} // 继承基础 Strategy

type podStatusStrategy struct {
podStrategy
}

// PrepareForUpdate for status ensures that updates only modify status
func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPod := obj.(*core.Pod)
oldPod := old.(*core.Pod)
// 关键:强制新对象的 Spec 与旧对象一致,只允许 Status 变化
newPod.Spec = oldPod.Spec
// 清除 DeletionTimestamp,因为 status 更新不应触发删除
newPod.DeletionTimestamp = nil
// 防止 status 更新意外修改 OwnerReferences (旧 Kubelet 可能存在 bug)
newPod.OwnerReferences = oldPod.OwnerReferences
}

// ValidateUpdate for status ensures that updates only modify status
func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newPod := obj.(*core.Pod)
oldPod := old.(*core.Pod)
// 调用专门的 Status 更新验证逻辑
return validation.ValidatePodStatusUpdate(newPod, oldPod)
}

9.7 注册 APIGroup 到 API Server

定义好类型、存储和策略后,需要将它们注册到 API Server 的 HTTP Handler 中。

  1. 创建 Storage Map: 为每个 API Version 创建一个映射,将资源名称 (如 “pods”) 映射到其 REST Storage 对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // pkg/master/master.go (示例简化)
    restStorageMap := map[string]rest.Storage{
    "pods": podStorage.Pod, // Pod 主资源
    "pods/attach": podStorage.Attach,
    "pods/status": podStorage.Status,
    "pods/log": podStorage.Log,
    "pods/exec": podStorage.Exec,
    "pods/portforward": podStorage.PortForward,
    "pods/proxy": podStorage.Proxy,
    "pods/binding": podStorage.Binding,
    "bindings": podStorage.Binding, // Binding 也可以作为顶级资源访问
    "pods/eviction": podStorage.Eviction,
    // ... 其他 core v1 资源 ...
    "services": serviceRest.Service,
    "services/status": serviceRest.Status,
    "services/proxy": serviceRest.Proxy,
    // ...
    }
  2. 创建 APIGroupInfo: 包含 GroupVersion 信息、Scheme、参数编解码器以及 VersionedResourcesStorageMap。
    1
    2
    3
    4
    // pkg/master/master.go (示例简化)
    apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(core.GroupName, Scheme, ParameterCodec, Codecs)
    // 将 v1 版本的 Storage Map 关联到 APIGroupInfo
    apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap
  3. 安装 APIGroup: 调用 InstallLegacyAPIGroup (用于 core group) 或 InstallAPIGroup (用于命名 group) 将 Handler 挂载到 API Server。
    1
    2
    3
    4
    5
    // pkg/master/master.go (示例简化)
    // m 是 *Master 对象,包含 GenericAPIServer
    if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
    klog.Fatalf("Error in registering group versions: %v", err)
    }

9.8 代码生成工具

Kubernetes 社区维护了一套用于生成客户端、Informer、Lister、DeepCopy 方法等的代码生成工具: k8s.io/code-generator

  • deepcopy-gen: 生成 DeepCopy()DeepCopyObject() 方法。
  • client-gen: 生成类型化的客户端 (ClientSet)。
  • informer-gen: 生成 Informer,用于高效地监听资源变化。
  • lister-gen: 生成 Lister,用于从 Informer 缓存中读取资源。
  • conversion-gen: 生成不同 API 版本之间的转换函数。

通常使用 hack/update-codegen.sh 脚本来调用这些生成器。

1
2
3
4
5
# 示例:生成 DeepCopy 代码
${GOPATH}/bin/deepcopy-gen --input-dirs k8s.io/kubernetes/pkg/apis/core/v1 \
-O zz_generated.deepcopy \
--bounding-dirs k8s.io/kubernetes/pkg/apis/core \
--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt

总结

Kubernetes API Server 是集群的中枢神经系统,理解其访问控制流程(认证、授权、准入控制)、限流机制(特别是 APF)、高可用部署方式以及 API 对象的内部实现原理,对于深入掌握 Kubernetes、进行集群管理、故障排查和二次开发都至关重要。希望本文的梳理能帮助你构建更清晰的知识体系。

若想深入了解 apiserver 源码,可以参考:https://cncamp.notion.site/kube-apiserver-10d5695cbbb14387b60c6d622005583d


深入理解 Kubernetes API Server:核心机制与实践
https://mfzzf.github.io/2025/03/17/kubernetes-API-server/
作者
Mzzf
发布于
2025年3月17日
许可协议