1. API Server
1.1. 概述
API Server 是 Kubernetes 控制平面的核心组件,作为整个集群的”大脑”,它负责暴露 Kubernetes API 并处理所有 REST 操作,是集群内所有组件交互的中枢。
基本架构:
客户端(kubectl/Controller/Node等)
↓
API Server (kube-apiserver)
↓
etcd (集群状态存储)
TeX核心功能:
- API 暴露:
- 提供 Kubernetes 所有资源的 CRUD 接口
- 支持 RESTful 风格的 API
- 提供 Watch 机制监听资源变化
- 请求处理:
- 认证(Authentication)
- 授权(Authorization)
- 准入控制(Admission Control)
- 请求验证(Validation)
- 集群状态管理:
- 与 etcd 交互,持久化集群状态
- 保证数据的一致性和正确性
- 通信枢纽:
- 所有控制平面组件(Controller Manager, Scheduler)都通过 API Server 工作
- 所有节点上的 kubelet 都与 API Server 保持通信
工作流程:
- 请求接收
- 监听 HTTPS 端口(默认 6443)
- 接收来自 kubectl、控制器、节点等的请求
- 认证阶段
- 授权阶段
- 准入控制
- 动态修改或验证请求
- 常见准入控制器
- MutatingAdmissionWebhook
- ValidatingAdmissionWebhook
- 资源验证
- 验证资源对象的字段和规范
- 确保符合 API 架构定义
- etcd 交互
- 将合法变更持久化到 etcd
- 从 etcd 读取数据返回给客户端
- 响应生成
- 返回适当的 HTTP 状态码
- 返回请求的资源或操作结果
1.2. API 组织形式
Kubernetes API 是一个 RESTful API,其组织形式遵循一种非常清晰和层级化的结构。可以将其理解为一个层层递进的路径。
核心组织形式:
# 核心 API
/api/{version}/namespaces/{namespace}/{resource}/{name}
# 非核心 API
/apis/{apiGroup}/{version}/namespaces/{namespace}/{resource}/{name}
Bash1.2.1. API 根路径 (/api
或 /apis
)
这是所有 API 请求的起点。Kubernetes 有两类 API 组,它们的根路径不同:
/api
:这是 核心(Core)API 组 的路径,也称为 Legacy Group。这里包含的是 Kubernetes 最早期、最核心的对象,例如pods
,services
,nodes
,namespaces
,events
,configmaps
,secrets
等。这些对象非常重要,以至于它们没有独立的 API 组名。/apis
:这是所有 命名 API 组(Named API Groups) 的路径。这是现代 Kubernetes API 的组织方式,它将功能相关的对象分类到不同的组中,更易于管理和扩展。例如,所有的apps
(Deployments, StatefulSets 等)都在apps
组里。- 简单记忆:除了最核心的几个对象,其他所有 API 都通过
/apis
访问。
1.2.2. API 组 (apiGroup
)
这一层用于对功能相关的 API 进行分类。这避免了所有资源都堆积在一个扁平的空间里,减少了命名冲突,并允许各组独立发展。
- 核心组(在
/api
下): 这一层没有显式的组名。版本直接跟在/api
后面。- 示例:
/api/v1
- 示例:
- 命名 API 组(在
/apis
下): 组名是路径的一部分。apps
:包含部署和状态化应用相关的资源,如deployments
,statefulsets
,daemonsets
,replicasets
。- 路径示例:
/apis/apps/v1
- 路径示例:
networking.k8s.io
:包含网络相关的资源,如ingresses
,networkpolicies
。- 路径示例:
/apis/networking.k8s.io/v1
- 路径示例:
storage.k8s.io
:包含存储相关的资源,如storageclasses
。- 路径示例:
/apis/storage.k8s.io/v1
- 路径示例:
batch
:包含批处理任务相关的资源,如jobs
,cronjobs
。- 路径示例:
/apis/batch/v1
- 路径示例:
rbac.authorization.k8s.io
:包含基于角色的访问控制资源,如roles
,rolebindings
。- 路径示例:
/apis/rbac.authorization.k8s.io/v1
- 路径示例:
- 还有很多其他组,如
autoscaling
,policy
,certificates.k8s.io
等。
1.2.3. API 版本 (version
)
Kubernetes 支持 API 版本化,这是为了在引入新功能和改进时能平滑升级和向后兼容。常见的版本有:
v1
:稳定的正式发布版本。功能和特性已经确定,会长期支持。v1beta1
,v2alpha1
等:测试版(Beta) 或 实验版(Alpha)。Beta 版本通常功能比较稳定,但细节可能还会变化;Alpha 版本是实验性的,可能包含错误,默认可能被禁用,并且随时可能被删除而不另行通知。
1.2.4. 命名空间 (namespaces/{namespace}
)
这一层将资源隔离到不同的虚拟集群中。并非所有资源都是命名空间级的。
- 命名空间级资源(Namespaced Resources):如 Pods, Services, Deployments。它们的 API 路径中包含命名空间。
- 示例:
/api/v1/namespaces/default/pods/my-pod
- 这表示获取
default
命名空间中名为my-pod
的 Pod。
- 示例:
- 集群级资源(Cluster-Scoped Resources):如 Nodes, PersistentVolumes, Namespaces 本身。它们的 API 路径中没有
namespaces/{namespace}
这部分。- 示例:
/api/v1/nodes/my-node
- 示例:
/apis/storage.k8s.io/v1/storageclasses/standard
- 示例:
1.2.5. 资源类型 (resource
)
这一层指定你要操作的具体对象类型,例如 pods
, services
, deployments
, configmaps
。资源名称通常是复数形式。
1.2.6. 资源名称 (name
)
这一层指定你要操作的特定对象的名称。如果省略名称(只到 resource
层),则表示操作该类型的所有资源集合(例如,列出所有 Pod)。
1.2.7. 完整示例
假设我们有一个如下定义的 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
namespace: web-app
spec:
...
Bash要通过 HTTP REST 访问这个特定的 Deployment 对象,其 API 路径将是:/apis/apps/v1/namespaces/web-app/deployments/my-nginx-deployment
/apis
:使用命名 API 组(非核心组)。apps
:API 组名。v1
:API 版本。namespaces/web-app
:该资源所在的命名空间。deployments
:资源类型(复数)。my-nginx-deployment
:资源的具体名称。
1.3. 访问 API
最简单的方法是使用 kubectl proxy
来代理 API Server,然后用 curl
或浏览器访问。
# 启动代理,这会在你的本地 localhost:8080 创建一个到 API Server 的代理,并自动处理身份认证。(使用你当前的 kubeconfig 文件,如 ~/.kube/config 中的证书或令牌)
kubectl proxy --port=8080
# 访问 API
# 访问根路径,查看所有可用的 API
$ curl http://localhost:8080/api/
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "192.168.2.153:6443"
}
]
}
$ curl http://localhost:8080/apis/
{
"kind": "APIGroupList",
"apiVersion": "v1",
"groups": [
{
"name": "apiregistration.k8s.io",
"versions": [
{
"groupVersion": "apiregistration.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "apiregistration.k8s.io/v1",
"version": "v1"
}
},
{
"name": "apps",
"versions": [
{
"groupVersion": "apps/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "apps/v1",
"version": "v1"
}
},
{
"name": "events.k8s.io",
"versions": [
{
"groupVersion": "events.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "events.k8s.io/v1",
"version": "v1"
}
},
{
"name": "authentication.k8s.io",
"versions": [
{
"groupVersion": "authentication.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "authentication.k8s.io/v1",
"version": "v1"
}
},
{
"name": "authorization.k8s.io",
"versions": [
{
"groupVersion": "authorization.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "authorization.k8s.io/v1",
"version": "v1"
}
},
{
"name": "autoscaling",
"versions": [
{
"groupVersion": "autoscaling/v2",
"version": "v2"
},
{
"groupVersion": "autoscaling/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "autoscaling/v2",
"version": "v2"
}
},
{
"name": "batch",
"versions": [
{
"groupVersion": "batch/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "batch/v1",
"version": "v1"
}
},
{
"name": "certificates.k8s.io",
"versions": [
{
"groupVersion": "certificates.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "certificates.k8s.io/v1",
"version": "v1"
}
},
{
"name": "networking.k8s.io",
"versions": [
{
"groupVersion": "networking.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "networking.k8s.io/v1",
"version": "v1"
}
},
{
"name": "policy",
"versions": [
{
"groupVersion": "policy/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "policy/v1",
"version": "v1"
}
},
{
"name": "rbac.authorization.k8s.io",
"versions": [
{
"groupVersion": "rbac.authorization.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "rbac.authorization.k8s.io/v1",
"version": "v1"
}
},
{
"name": "storage.k8s.io",
"versions": [
{
"groupVersion": "storage.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "storage.k8s.io/v1",
"version": "v1"
}
},
{
"name": "admissionregistration.k8s.io",
"versions": [
{
"groupVersion": "admissionregistration.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "admissionregistration.k8s.io/v1",
"version": "v1"
}
},
{
"name": "apiextensions.k8s.io",
"versions": [
{
"groupVersion": "apiextensions.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "apiextensions.k8s.io/v1",
"version": "v1"
}
},
{
"name": "scheduling.k8s.io",
"versions": [
{
"groupVersion": "scheduling.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "scheduling.k8s.io/v1",
"version": "v1"
}
},
{
"name": "coordination.k8s.io",
"versions": [
{
"groupVersion": "coordination.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "coordination.k8s.io/v1",
"version": "v1"
}
},
{
"name": "node.k8s.io",
"versions": [
{
"groupVersion": "node.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "node.k8s.io/v1",
"version": "v1"
}
},
{
"name": "discovery.k8s.io",
"versions": [
{
"groupVersion": "discovery.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "discovery.k8s.io/v1",
"version": "v1"
}
},
{
"name": "flowcontrol.apiserver.k8s.io",
"versions": [
{
"groupVersion": "flowcontrol.apiserver.k8s.io/v1",
"version": "v1"
},
{
"groupVersion": "flowcontrol.apiserver.k8s.io/v1beta3",
"version": "v1beta3"
}
],
"preferredVersion": {
"groupVersion": "flowcontrol.apiserver.k8s.io/v1",
"version": "v1"
}
},
{
"name": "metrics.k8s.io",
"versions": [
{
"groupVersion": "metrics.k8s.io/v1beta1",
"version": "v1beta1"
}
],
"preferredVersion": {
"groupVersion": "metrics.k8s.io/v1beta1",
"version": "v1beta1"
}
}
]
}
# 访问特定组和版本
curl http://localhost:8080/apis/apps/v1
Bash如果不使用代理,则需要自行处理证书身份认证,详细的身份认证,安全机制见后面的文章。
使用 kubectl get
查看 API 路径:
kubectl get
命令的 -v
(详细)选项可以显示它实际发起的 API 请求,这对于调试和理解非常有帮助。
$ kubectl get deployments.apps -v=6
I0820 11:04:52.110022 8450 loader.go:395] Config loaded from file: /home/admin/.kube/config
I0820 11:04:52.124037 8450 round_trippers.go:553] GET https://api-server:8443/apis/apps/v1/namespaces/default/deployments?limit=500 200 OK in 10 milliseconds
No resources found in default namespace.
Bash1.4. Pod 调度过程中的 List-Watch 机制
1.4.1. 概念
List-Watch 是 Kubernetes 中一种客户端与 API Server 之间保持数据同步的机制。它由两个基本操作组成:
- List: 调用资源的 LIST API,一次性获取指定资源的所有实例(例如,所有 Pod)。
- Watch: 调用资源的 WATCH API,建立一个长连接,持续监听该资源所有后续的变更事件(例如,Pod 的创建、更新、删除)。
核心思想:客户端首先通过 List
获取全量数据,然后通过 Watch
接收后续的增量变更,从而在本地维护一个与 API Server 完全一致的、最新的资源状态缓存。
1.4.2. List-Watch 作用
在分布式系统中,组件需要感知集群状态的变化。最朴素的方法是轮询(Polling),即客户端定期向服务器发送请求询问:“有变化吗?”。
轮询的缺点非常明显:
- 高延迟: 变化发生和客户端感知到变化之间存在一个轮询间隔,实时性差。
- 高开销: 即使没有任何变化,也会产生大量无用的请求和响应,浪费 CPU、网络带宽,给 API Server 带来巨大压力。
List-Watch 采用了一种 “订阅-推送” 模型,完美解决了轮询的问题:
- 低延迟: 变更几乎可以立即推送给客户端。
- 高效率: 只有在变化发生时才会产生网络流量。
2. Controller Manager
核心职责:实时监听集群状态(通过 API Server List-Watch 机制),一旦检测到当前状态(Current State)与期望状态(Desired State)不一致,就发起必要的调协(Reconciliation)操作,驱动集群向期望状态收敛。
Controller Manager 是 k8s 集群中的资源控制器集合,集群中的资源对象基本都是被对应的 Controller 自动管理着的。
核心架构与工作模式:
- 核心组件:Informer (Reflector)
- Controller Manager 并不直接轮询 API Server。它使用我们之前讨论的 List-Watch 机制。
- 每个控制器都通过一个叫
Informer
的组件来订阅它关心的资源(如 Pod、Node、Service)。 Informer
通过 Watch API Server,将其关注的资源对象全量缓存(Store) 在本地内存中,并持续接收变更事件。- 当事件发生时(如 Pod 被删除),
Informer
会调用控制器注册的回调函数(EventHandler),将事件放入一个工作队列(Work Queue) 中。
- 控制循环 (Control Loop):每个控制器都是一个独立的循环,其工作流程可以概括为以下步骤。
- 读取期望状态: 从 API Server 中读取用户通过 YAML 文件定义的“期望状态”。(例如,Deployment 期望有 3 个副本的 Nginx Pod)。
- 观察当前状态: 从本地的 Informer 缓存中读取“当前状态”。(例如,当前只有 2 个 Nginx Pod 在运行)。
- 比较与调协: 比较“当前状态”和“期望状态”。
- 状态一致: 什么都不做,继续循环。
- 状态不一致: 执行“调协操作(Reconcile)”,使当前状态向期望状态靠拢。
- 循环: 不断重复上述过程。
3. Scheduler
如果说 Controller Manager 是负责“决策”的大脑,那么 Scheduler 就是负责“派工”的调度中心。它的任务非常简单明确,但却至关重要:为新创建的 Pod 选择一个最适合的 Node 来运行它。
- 它的核心职责: 监视未被调度的 Pod(即
spec.nodeName
为空的 Pod),通过一系列复杂的筛选和评分算法,为每个 Pod 分配一个最优的 Node,并将这个分配决定写回 API Server。 - 它不负责启动 Pod: Scheduler 只做决定,不执行。它通过更新 Pod 的
spec.nodeName
字段来做出调度决策。随后,该 Node 上的 kubelet 监听到这个变化,才会真正去创建和运行 Pod 的容器。
调度流程详解:
Scheduler 为一个 Pod 选择 Node 的过程是一个多阶段的流水线,主要分为两个核心步骤:过滤(Filtering) 和 评分(Scoring)。
- 阶段一:过滤 (Filtering) / 预选 (Predicates)
- 目标: 从所有可用的 Node 中,筛掉那些不满足 Pod 要求的 Node。
- 过程: Scheduler 会依次运行一系列的过滤策略。如果一个 Node 无法通过其中任何一个策略,它就会被排除。这个过程结束后,会得到一个候选 Node 列表。
- 常见的过滤策略:
PodFitsResources
:检查 Node 的可用资源(CPU、内存)是否满足 Pod 的请求(requests
)。PodFitsHostPorts
:检查 Pod 申请的宿主机端口(hostPort
)在 Node 上是否已经被占用。MatchNodeSelector
:检查 Node 的标签(labels
)是否匹配 Pod 的nodeSelector
或nodeAffinity
规则。Volume
相关:检查 Node 是否能挂载 Pod 声明的卷(例如,检查AzureDisk
是否在正确的可用区)。Taint
和Toleration
: 如果 Node 上有 Pod 不能容忍的污点(Taint),则会被过滤掉。
- 如果过滤后没有候选节点,Pod 将一直处于 Pending 状态,直到有符合条件的 Node 出现(例如,集群扩容了)。
- 阶段二:评分 (Scoring) / 优选 (Priorities)
- 目标: 在通过过滤的候选 Node 列表中,为每个 Node 打分,选择一个最优的 Node。
- 过程: Scheduler 会依次运行一系列的评分策略。每个策略都会给 Node 打一个分数(通常 0-10 分)。最后,将所有策略的分数加权求和,得到每个 Node 的最终得分。得分最高的 Node 被选中。
- 常见的评分策略:
LeastRequested
:优先选择资源请求更少的 Node。其计算公式类似于(Node可用CPU / Node总CPU) + (Node可用Memory / Node总Memory)) / 2 * 10
。这有助于将负载分散到不同的 Node 上。BalancedResourceAllocation
:优先选择CPU和内存资源使用率更均衡的 Node。例如,一个 Node 的 CPU 用了 80%,内存用了 20%,不如一个 CPU 和内存都用了 50% 的 Node 均衡。这与LeastRequested
配合,能更好地利用资源。ImageLocality
:优先选择已经存在了 Pod 所需容器镜像的 Node。这可以避免从镜像仓库拉取镜像的开销,加速 Pod 启动。InterPodAffinity
:根据 Pod 间亲和性(podAffinity
/podAntiAffinity
)规则进行评分,尽可能将 Pod 部署到满足亲和性或反亲和性要求的 Node 上。NodeAffinity
:根据节点亲和性规则进行评分。