Kubernetes - API Server
概述
API Server
- 提供
集群管理的REST API - 提供其它模块之间数据交互和通信的
枢纽- 其它模块通过 API Server 查询或者修改数据,只有 API Server 才能直接操作
etcd
- 其它模块通过 API Server 查询或者修改数据,只有 API Server 才能直接操作
访问控制:
认证+鉴权+准入
Mutating Admission可以修改对象,而Validating Admission不可以修改对象
认证
- 开启
TLS时,所有请求都需要首先认证 - Kubernetes 支持多种认证机制,并支持
同时开启多个认证插件(只需要有1个认证通过即可) - 如果认证成功,进入鉴权模块,如果认证失败,则返回 401
认证插件
- X509 证书
- 在 API Server
启动时配置--client-ca-file CN 域(CommonName)用作用户名,而O 域(Organization)则用作Group 名
- 在 API Server
- 静态 Token 文件
- 在 API Server
启动时配置--token-auth-file - 采用
csv格式:token,user,uid,[group...]
- 在 API Server
- 引导 Token
- 目的:为了支持
平滑地启动引导新集群 - 以
Secret的形式保存在kube-system命名空间中,可以被动态管理和创建 TokenCleaner能够在引导 Token过期时将其删除- 使用 kubeadm 部署 Kubernetes 时,可通过
kubeadm token list查询
- 目的:为了支持
- 静态密码文件
- 在 API Server
启动时配置--basic-auth-file - 采用
csv格式:password,user,uid,[group...]
- 在 API Server
- ServiceAccount
- ServiceAccount 是由 Kubernetes
自动生成的 自动挂载在容器的/var/run/secrets/kubernetes.io/serviceaccount目录
- ServiceAccount 是由 Kubernetes
- OpenID
- OAuth 2.0 的认证机制
- Webhook
--authentication-token-webhook-config-file- 描述如何访问远程的 Webhook 服务
--authentication-token-webhook-cache-ttl- 设定认证的缓存时间,默认为 2 分钟
- 匿名请求(一般禁止)
--anonymous-auth
X509
API Server 本身是一个
CA(颁发证书,双向认证)
1 | $ tree /etc/kubernetes/pki/ |
生成私钥和 CSR
1 | $ openssl genrsa -out zhongmingmao.key 2048 |
编码 CSR
1 | $ cat zhongmingmao.csr | base64 | tr -d "\n" |
向 API Server 提交 CSR
1 | $ cat <<EOF | kubectl apply -f - |
审批 CSR
1 | $ k certificate approve zhongmingmao |
API Server 已颁发证书:
status.certificate
1 | $ k get csr zhongmingmao -oyaml |
提取证书
1 | $ k get csr zhongmingmao -o jsonpath='{.status.certificate}' | base64 -d > zhongmingmao.crt |
设置 Credential
1 | $ k config set-credentials zhongmingmao --client-key=zhongmingmao.key --client-certificate=zhongmingmao.crt --embed-certs=true |
1 | apiVersion: v1 |
授权
1 | $ k create role developer --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods |
1 | $ k get po --user zhongmingmao |
静态 Token 文件
static-token.csv
1 | bob-token,bob,1000,"group1,group2,group3" |
备份 API Server
1 | $ cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml |
修改 kube-apiserver.yaml
1 | apiVersion: v1 |
API Server 重新启动
1 | $ sudo cp ~/kube-apiserver-static-token.yaml /etc/kubernetes/manifests/kube-apiserver.yaml |
HTTP 请求,认证成功,但鉴权失败
1 | $ k get ns -v 9 |
kubeconfig
1 | apiVersion: v1 |
1 | $ k --context bob@kubernetes get ns -v 9 |
ServiceAccount
1 | $ k get serviceaccounts -owide |
该 Token 由 Kubernetes 颁发,Kubernetes 本身能
验签,即可以识别身份
JWT 对应的 Header
1 | { |
JWT 对应的 Payload
1 | { |
1 | $ k get po nginx-deployment-6799fc88d8-4ppnm -oyaml |
Webhook
构建认证服务,用于认证
TokenReviewRequest
Input - spec
1 | { |
Output - status
1 | { |
webhook
1 | package main |
1 | build: |
1 | $ curl -s http://127.1:3000/authenticate | jq |
webhook-config.json
1 | { |
kube-apiserver.yaml -
--authentication-token-webhook-config-file
1 | apiVersion: v1 |
1 | k get po -n kube-system |
~/.kube/config
1 | - name: github_token |
1 | $ k get po --user github_token |
1 | $ ./bin/arm64/authn-webhook |
鉴权
- 授权主要用于对
集群资源的访问控制 - 检查请求包含的相关
属性值,与相对应的访问策略相比较,API 请求必须满足某些策略才能被处理 - Kubernetes 支持
同时开启多个授权插件,只要有1个验证通过即可 - 授权成功,则请求进入
准入模块做进一步的请求验证,否则返回 HTTP 403 - 支持的授权插件:ABAC、
RBAC、Webhook、Node
RBAC
- RBAC 的授权策略可以利用
kubectl或者Kubernetes API直接进行配置 - RBAC 可以授权给用户,让用户有权进行
授权管理 - RBAC 在 Kubernetes 中被映射为
API资源和操作
RBAC
Kubernetes -
RoleBinding可以绑定ClusterRole
1 | $ k api-resources --api-group="rbac.authorization.k8s.io" |
Role 是
权限集合,Role 只能用来给某个特定的Namespace中的资源做鉴权
1 | apiVersion: rbac.authorization.k8s.io/v1 |
ClusterRole 适用于
多 Namespace 的资源、集群级别的资源、非资源类的 API
1 | apiVersion: rbac.authorization.k8s.io/v1 |
Binding - RoleBinding 是与 Namespace 挂钩的,因此用户只有 default 下的权限
1 | apiVersion: rbac.authorization.k8s.io/v1 |
Group
- 与
外部认证系统对接时,用户信息(UserInfo)可以包含Group信息,授权可以针对用户群组 - 对
ServiceAccount授权时,Group 代表某个Namespace下的所有 ServiceAccount
1 | apiVersion: rbac.authorization.k8s.io/v1 |
1 | apiVersion: rbac.authorization.k8s.io/v1 |
多租户
规划系统角色
- User
- 管理员
- 所有资源的所有权限? -
应用的 Secret不应该被管理员访问
- 所有资源的所有权限? -
- 普通用户
- 是否有该用户创建的 Namespace 下所有的 Object 的操作权限?
- 对其它用户的 Namespace 资源是否可读?是否可写?
- 管理员
- SystemAccount
- SystemAccount 是开发者创建应用后,应用与
API Server进行通信所需要的身份 - Kubernetes 会为每个 Namespace 创建
Default SystemAccount- Default SystemAccount 需要授予给定权限后才能对 API Server 进行写操作
- SystemAccount 是开发者创建应用后,应用与
实现方案(自动化):用户创建了某个 Namespace,则该用户拥有了该 Namespace 的所有权限
- 在创建 Cluster 时,创建了一个 ClusterRole(名为 namespace-creator),该 ClusterRole 拥有集群的所有权限
- 创建自定义的 namespace
admission webhook- 当
Namespace 创建请求被处理时,获取当前用户信息并annotate到 namespace –mutating admission Admission在Authentication和Authorization之后,所以能获取到创建者的信息
- 当
- 创建 RBAC Controller
- 监听 Namespace 的创建事件
- 获取当前 Namespace 的创建者信息 – 从上面
mutating admission注入的Annotation来获取 - 在当前 Namespace 创建
RoleBinding,并将 namespace-creator 与创建者绑定- 创建者拥有了该 Namespace 的所有权限
最佳实践
- ClusterRole 是针对
整个集群生效的 - 创建一个
管理员角色,并且绑定给开发运营团队成员 ThirdPartyResource和CustomResourceDefinition是全局资源普通用户创建 ThirdPartyResource 后,需要管理员授权后才能真正操作该对象
- 创建
spec,用源码驱动来进行角色管理 - 权限是可以
传递的- 用户 A 可以将其对某对象的某操作,抽取成一个权限,然后赋给用户 B
- 防止
海量的 Role 和 RoleBinding 对象,否则会导致鉴权效率低下,造成 API Server 的负担 - ServiceAccount 也需要授权
POST vs PUT vs PATCH
1 | apiVersion: v1 |
create - POST
1 | $ k create -f quota.yaml -v 9 |
replace - PUT -All
1 | $ k replace -f quota.yaml -v 9 |
apply - PATCH - Diff
1 | $ k apply -f quota.yaml -v 9 |
准入
场景
mutating admission– 为资源增加自定义属性,如在上述多租户方案中- 需要在 Namespace 的准入控制中,获取用户信息,并将用户信息更新到 Namespace 的 Annotation 中
- 配额管理 – 资源有限,如何限定某个用户有多少资源
- 预定义每个 Namespace 的
ResourceQuota,并把 spec 保存为 configmap- 用户可以创建多少个 Pod:
BestEffortPod/QoSPod - 用户可以创建多少个 Service
- 用户可以创建多少个 Ingress
- 用户可以创建多少个 Service VIP
- 用户可以创建多少个 Pod:
- 创建
ResourceQuota Controller- 监听 Namespace 创建事件,当 Namespace 创建时,在该 Namespace 创建对应的
ResourceQuota对象
- 监听 Namespace 创建事件,当 Namespace 创建时,在该 Namespace 创建对应的
API Server开启 ResourceQuota 的admission plugin
- 预定义每个 Namespace 的
概述
Admission是在Authentication和Authorization之后,对请求做进一步验证或者添加默认参数- 区别
Authentication和Authorization只关心请求的用户和操作Admission还处理请求的内容(创建、更新、删除、连接)
Admission支持同时开启多个插件,依次调用,只有全部插件都通过的请求才可以放进入系统
插件
| Plugin | Desc |
|---|---|
AlwaysAdmit |
接受所有请求 |
AlwaysPullImages |
总是拉取最新镜像 - 多租户 |
DenyEscalatingExec |
禁止特权容器的 exec 和 attach 操作 |
ImagePolicyWebhook |
通过 Webhook 决定 image 策略,需要同时配置 --admission-control-config-file |
ServiceAccount |
自动创建默认的 ServiceAccount,并确保 Pod 引用的 ServiceAccount 已经存在 |
SecurityContextDeny |
拒绝包含非法 SecurityContext 配置的容器 |
ResourceQuota |
限制 Pod 的请求不会超过配额,需要在 Namespace 下创建一个 ResourceQuota 对象 |
LimitRanger |
为 Pod 设置默认资源请求和限制,需要在 Namespace 中创建一个 LimitRanger 对象 |
InitialResources |
根据镜像的历史使用记录,为容器设置默认的资源请求和限制 |
NamespaceLifecycle |
确保处于 termination 状态的 Namespace 不再接收新的对象创建请求,并拒绝请求不存在的 Namespace |
DefaultStorageClass |
为 PVC 设置默认的 StorageClass |
DefaultTolerationSeconds |
设置 Pod 的默认 forgiveness toleration 为 5 分钟 |
PodSecurityPolicy |
使用 Pod Security Policies 时必须开启 |
NodeRestriction |
限制 kubelet 仅可访问 node、endpoint、pod、service、secret、configmap、pv、pvc 等相关资源 |
1 | $ k exec -it -n kube-system kube-apiserver-mac-k8s -- kube-apiserver --help |
1 | $ k api-resources --api-group='admissionregistration.k8s.io' |
开发
Kubernetes 预留了 Admission Plugin 的扩展点
| Plugin | Desc |
|---|---|
MutatingWebhookConfiguration |
变形插件,支持对准入对象的修改 |
ValidatingWebhookConfiguration |
校验插件,只能对准入对象合法性进行校验,不能修改(忽略修改) |
Webhook Server
Go
1 | package main |
1 | package main |
Dockerfile
1 | FROM scratch |
Makefile
1 | .DEFAULT_GOAL := docker-image |
构建
1 | $ tree |
TLS
1 |
|
1 |
|
1 | apiVersion: apps/v1 |
caBundle是为了让 API Server 信任 Webhook Server 的自签证书
1 | $ tree |
1 | $ k get deployments.apps -n webhook-demo -owide |
1 | $ k run --image=nginx nginx |
限流
传统算法
计数器 + 固定窗口
突发流量的上限为窗口阈值的
2 倍
计数器 + 滑动窗口
相对于固定窗口,更平滑
漏桶
恒定的输出速度,不适合突发流量
令牌桶
恒定的令牌产生速度,支持一定的突发流量
API Server
| Parameter | Desc | Default |
|---|---|---|
max-requests-inflight |
在给定时间内最大的 non-mutating 的请求数 |
400 |
max-mutating-requests-inflight |
在给定时间内最大的 mutating 的请求数 |
200 |
传统限流算法的局限性
| Disadvantage | Desc |
|---|---|
粗粒度 |
无法为不同用户、不同场景设置不同的限流 |
单队列 |
共享限流窗口、可能会引起系统阻塞 |
不公平 |
正常用户的请求会被排在队尾,无法及时处理而饿死 |
无优先级 |
重要的系统指令会被一并限流,系统故障难以恢复 |
APF
API
PriorityandFairness
- 以
更细粒度对请求进行分类和隔离 - 引入了
空间有限的排队机制,在非常短暂的突发情况下,API Server不会拒绝任何请求 - 使用
公平排队技术从队列中分发请求,即便一个行为不佳的控制器也不会饿死其他控制器 - 核心思想:
多等级+多队列
实现原理
- APF 的实现依赖于两个重要的资源:
FlowSchema和PriorityLevelConfiguration - APF 对请求进行更细粒度的
分类,每一个请求分类对应一个FlowSchema - FlowSchema 内的请求会根据
distinguisher进一步划分为不同的 Flow FlowSchema会设置一个Priority Level,不同Priority Level的并发资源是隔离的一个 Priority Level对应多个 FlowSchema(一个 FlowSchema 又对应多个 Flow)Priority Level中维护一个QueueSet,用于缓存不能及时处理的请求- 请求不会因为超出 Priority Level 的并发限制而被丢弃
- FlowSchema 的每个
Flow通过shuffle sharding算法从 QueueSet 中选取特定的 Queues 缓存请求 - 每次从 QueueSet 中取请求执行时
- 会先应用
fair queuing算法从 QueueSet 中选择一个Queue,然后从这个 Queue 中取出oldest请求执行
- 会先应用
FlowSchema 匹配一些入站请求,并将它们分配给 Priority Level
每个
入站请求都会按照 matchingPrecedence 从小到大对所有的 FlowSchema 测试是否匹配,直到首个匹配出现
FlowSchema + Distinguisher ==> Flow
| Key | Value |
|---|---|
| distinguisherMethod | Distinguisher |
| matchingPrecedence | 规则优先级 |
| priorityLevelConfiguration | PriorityLevelConfiguration |
1 | $ k api-resources --api-group='flowcontrol.apiserver.k8s.io' |
nonResourceURL - /api/v1/namespaces
1 | $ k get ns default -v 9 |
PriorityLevelConfiguration 表示单个
隔离类型
限制未完成的请求数(assuredConcurrencyShares)和排队中的请求数(Queue)
| Key | Value |
|---|---|
assuredConcurrencyShares |
允许的并发请求 - Flow 的维度 - 限制某个 Flow 占用过多资 |
| queues | 当前 Priority Level 的队列总数 |
| queueLengthLimit | 每个队列中的对象数量 |
handSize |
shuffle sharding 的配置每个 flowschema + distinguisher 的请求会被 enqueue 到多少个队列 |
1 | $ k get prioritylevelconfigurations.flowcontrol.apiserver.k8s.io |
概念:
FlowSchema - 分类规则+PriorityLevel - 并发规则
- 传入的请求通过 FlowSchema 按照其属性
分类,并分配优先级 - 每个
优先级维护自定义的并发规则,不同优先级的请求,不会相互饿死 - 在同一个优先级内,
公平排队算法可以防止来自不同 Flow 的请求相互饿死 - 将请求排队,通过排队机制,防止在平均负载较低时,因通信量徒增而导致请求失败
优先级
- 如果未启用 APF
- API Server 的整体并发量将受到
max-requests-inflight和max-mutating-requests-inflight的限制
- API Server 的整体并发量将受到
- 如果启用了 APF
- 将
max-requests-inflight和max-mutating-requests-inflight求和 - 将总和
分配到一组可配置的优先级中,每个传入的请求都会分配一个优先级
- 将
- 每个优先级都有各自的配置,设定分发的并发请求数
排队
- 在过载情况下,防止一个 Flow 饿死其它 Flow 是非常有价值的
- 每个请求被分配到某个
Flow中,该Flow由对应的FlowSchema和Distinguisher来标识确定 - 系统尝试为具有
相同 PriorityLevel的不同 Flow中的请求赋予近似相等的权重 - 将请求分配到
Flow之后,通过shuffle sharding将请求分配到Priority Level的Queue中shuffle sharding- 可以相对有效地隔离低强度 Flow 和高强度 Flow
- 排队算法的细节可以针对每个
Priority Level进行调整
豁免请求(Exempt)
- 某些特别重要的请求
不受制于此特性施加的任何限制 - 可以防止不当的流控配置
完全禁用API Server
默认配置
1 | $ k get flowschemas.flowcontrol.apiserver.k8s.io |
| Priority Level | Desc |
|---|---|
| exempt | 请求完全不受流控限制,总是被立即分发,将 system:masters 组的所有请求都纳入该 Priority Level |
| system | 用于 system:nodes 的请求(即 kubelets)kubelets 必须要连上 API Server,以便工作负载能调度到到其上 |
| leader-election | 用于内置控制器的选举请求(system:kube-controller-manager + system:kube-scheduler) |
| workload-high | 用于内置控制器的请求 |
| workload-low | 用于来自于任何 ServiceAccount 的请求(包括来自于 Pod 中运行的控制器的所有请求) |
| global-default | 所有其它流量,如非特权用户运行的交互式 kubectl 命令 |
| catch-all | 确保每个请求都有分类,一般不依赖默认的 catch-all 配置仅与 catch-all FlowSchema 匹配的流量 更容易被拒绝(HTTP 429) |
exempt
1 | $ k get flowschemas.flowcontrol.apiserver.k8s.io exempt -oyaml |
probes
1 | $ k get flowschemas.flowcontrol.apiserver.k8s.io probes -oyaml |
system-leader-election
1 | $ k get flowschemas.flowcontrol.apiserver.k8s.io system-leader-election -oyaml |
调试
| URI | Desc |
|---|---|
| /dump_priority_levels | 所有 Priority Level 及其当前状态 |
| /dump_queues | 所有 Queue 及其当前状态 |
| /dump_requests | 当前正在队列中等待的所有请求 |
1 | $ k get --raw /debug/api_priority_and_fairness/dump_priority_levels |
高可用
无状态 + 多副本
- API Server 是
无状态的 Rest Server,方便执行Scale Up/down - 负载均衡
- 在多个 API Server 实例之上,配置负载均衡(如
haproxy) 证书可能需要加上Loadbalancer VIP重新生成
- 在多个 API Server 实例之上,配置负载均衡(如
CPU + Memory
- 随着集群中 Node 不断增多,API Server 对 CPU 和 Memory 的开销会不断增大
- 过少的 CPU 资源会降低其处理效率,过少的内存资源会导致 Pod 被 OOMKilled
RateLimit
max-requests-inflight+max-mutating-requests-inflightAPF
Cache
API Server与etcd之间是基于gRPC协议进行通信的(gRPC 协议保证了在大规模集群中的数据高速传输)- gRPC 是基于
连接复用的HTTP/2- 针对
相同分组的对象,API Server 和 etcd 之间共享相同的 TCP 连接,不同请求使用不同的 Stream
- 针对
- 一个
HTTP/2 连接是有Stream 配额的,配额大小会限制其能支持的并发请求
- gRPC 是基于
- API Server 提供了
集群对象的缓存机制- 当客户端发起查询请求时
- API Server 默认会将其
缓存直接返回给客户端,缓存区大小由参数--watch-cache-sizes控制
- API Server 默认会将其
- 针对访问比较多的对象,设置适当大小的缓存,可以极大降低对 etcd 的访问频率,降低对 etcd 集群的读写压力
- API Server 也允许客户端
忽略缓存,API Server 直接从 etcd 中拉取最新数据返回给客户端
- 当客户端发起查询请求时
长连接
- 当查询请求的返回
数据量比较大且此类请求并发量较大时,容易引起 TCP 链路的阻塞,导致其它查询的操作超时 - 客户端应尽量通过
长连接来List & Watch,避免全量从 API Server 获取数据,降低 API Server 的压力
访问方式
外部用户,永远只通过Loadbalancer VIP访问- 只有当
负载均衡(一般为 Service)出现故障时,管理员才切换到apiserver IP进行管理 内部组件,应使用统一的入口访问 API Server,确保看到一致视图
| Client type | 外部 LB VIP | Service Cluster IP | apiserver IP |
|---|---|---|---|
| Internal | Y | Y | Y |
| External | Y | N | N |
1 | $ k get svc -owide |
对象
APIService
任何
对象都有对应的APIService(对象应该由哪个API Server来处理,kube-aggregator)
1 | $ k api-resources --api-group='apiregistration.k8s.io' |
apimachinery
Scheme, typing, encoding, decoding, and conversion packages for Kubernetes and Kubernetes-like API objects.
GKV
| Version | Desc |
|---|---|
| External version | 面向用户 |
| Internel version | 内部版本 |
| Storage version | etcd |
Group
pkg/apis/core/register.go
定义 GroupVersion
1 | // SchemeGroupVersion is group version used to register these objects |
定义 SchemeBuilder
1 | var ( |
将对象加入 SchemeBuilder
1 | func addKnownTypes(scheme *runtime.Scheme) error { |
对象定义
单一对象的数据结构:
TypeMeta、ObjectMeta、Spec、Status
Internel version
1 | // Pod is a collection of containers, used as either input (create, update) or as output (list, get). |
External version - 多了一些
json tag
代码生成
| Tags | Desc |
|---|---|
Global Tags |
定义在 doc.go |
Local Tags |
定义在 types.go 中的每个对象里 |
Global Tags
Local Tags
| Tags | Desc |
|---|---|
deepcopy-gen |
为对象生成 DeepCopy 方法,用于创建对象副本 |
client-gen |
创建 Clientset,用于操作对象的 CRUD |
informer-gen |
为对象创建 Informer 框架,用于监听对象变化 |
lister-gen |
为对象构建 Lister 框架,用于为 Get 和 List 操作,构建客户端缓存 |
coversion-gen |
为对象构建 Conversion 方法,用于内外版本转换以及不同版本号的转换 |
etcd storage
Strategy - PrepareForCreate、PrepareForUpdate 等都属于 API Server 的框架层
subresource
内嵌在 Kubernetes 对象中,有独立的操作逻辑的属性集合,如PodStatus是 Pod 的 subresource
要更新 Pod Status 时,请求(New)里面的 Pod Spec、Pod OwnerReferences 都会被
etcd里面(Old)的版本覆盖
即当更新 Status 时,所有请求里面的非 Status 信息都会丢掉,采用与 etcd 一致的版本,反之,更新 Spec 亦然
背景:Status 的更新
非常频繁
APIGroup
定义 Storage
1 | configMapStorage, err := configmapstore.NewREST(restOptionsGetter) |
定义对象的 StorageMap
1 | storage := map[string]rest.Storage{} |
将对象注册到 API Server(即挂载 Handler)
1 | if len(groupName) == 0 { |
Static Pod
kubelet 在创建完
Static Pod后,会在API Server补一个 Pod 对象,称为Mirror Pod

































