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
构建认证服务,用于认证
TokenReview
Request
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
Priority
andFairness
- 以
更细粒度
对请求进行分类
和隔离
- 引入了
空间有限
的排队机制,在非常短暂
的突发
情况下,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-inflight
APF
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