门户网站的定义百度指数使用方法
文章目录
- 一、API Server简介
- 1、访问控制流程概览
- 2、访问控制细节
- 二、认证
- 1、认证插件(附x509认证实现原理)
- 2、基于webhook的认证服务集成
- (1)构建符合Kubernetes规范的认证服务
- (2)开发adapter适配器
- (3)配置认证服务(Adapter服务)
- (4)配置apiserver
- 三、授权
- 1、简介
- 2、RBAC和ABAC的对比
- 3、Role,ClusterRole
- 4、RoleBinding,ClusterRoleBinding(Group群组赋权)
- 5、规划系统角色
- 6、自动化多集群权限管控
- 7、注意事项
- 四、准入控制
- 1、为资源增加自定义属性(Mutating Example)
- 2、配额管理(Validation Example)
- (1)ResourceQuota方案流程
- (2)ResourceQuota限制实战
- (3)准入插件(官方自带)
- (4)自定义准入控制webhook插件
- 【1】实现原理
- 【2】mutating demo(代码实现)
- 五、限流
- 1、限流算法相关
- (1)计数器固定窗口算法
- (2)计数器滑动窗口算法
- (3)漏斗算法
- (4)令牌桶算法
- (5)总结四种传统方法局限性
- 2、apiserver中的限流
- (1)API Priority and Fairness(简称APF)
- (2)APF实现
- 【1】PriorityLevelConfiguration
- 【2】FlowSchema
- 【3】APF总结
- 【4】豁免请求
- 【5】查看当前集群的priority_level列表
- 六、如何构建高可用apiserver
- 七、搭建多租户的 Kubernetes 集群
- 八、apimachinery
- 1、复习GKV
一、API Server简介
kube-apiserver是Kubernetes最重要的核心组件之一,主要提供以下的功能:
- 提供集群管理的
REST API接口
,包括认证授权、数据校验以及集群状态变更等 - 提供
其他模块之间的数据交互和通信的枢纽
(其他模块通过API Server查询或修改数据,只有API Server才直接操作etcd
)
1、访问控制流程概览
Kubernetes API的每个请求都会经过多阶段的访问控制之后才会被接受
,这包括认证
、授权
以及准入控制
(Admission Control)等。
2、访问控制细节
从代码层级来看访问控制的一些细节。
- panic recovery:apiserver相当于一个http服务器,后台会启动多个goruntine来处理不同的请求,这个模块就确保当出现panic时,不会让apiserver挂掉。
- request timeout:请求从发送到apiserver到返回给客户端的超时时间,超过的话,请求返回失败。
- authentication:认证
- audit:审计。记录谁对哪些对象做了哪些操作。
- impersonation:request发送到apiserver时,可以给request加一些header,这样就可以模拟这个request是给谁用的,代表哪个用户。可以用在集群联邦中。
- max-in-flight:限流。看当前apiserver有多少个处理中的请求,如果达到上限,请求会被拒绝。
- authorization:鉴权
- kube-admission aggregator & CRDs:判断request是不是标准的k8s对象,如果是的话apiserver就直接处理。如果不是,比如CRD,那么apiserver会转发request给aggregator apiserver。
- decoding:将json对象解码成go对象。
- request conversion & defaulting:将请求对象从外部版本(external version 面向用户)转换成内部版本(internal version 内部实现)。
- admission:包括了mutating webhooks–>validating–>validating webhooks。如果有webhooks就调用,没有就内部validating。
- etcd:存储到etcd中。
- result conversion:将内部对象转换成外部版本的go对象
- encode:将go对象编码返回给客户端
二、认证
开启TLS时,所有的请求都需要首先认证。Kubernetes支持多种认证机制,并支持同时开启多个认证插件(只要有一个认证通过即可)
。如果认证成功,则用户的username会传入授权模块做进一步授权验证;而对于认证失败的请求则返回HTTP 401
。
1、认证插件(附x509认证实现原理)
- X509证书
使用X509客户端证书只需要API Server启动时配置--client-ca-file=SOMEFILE。在证书认证时,其CN域用作用户名,而组织机构域则用作group名。
我们可以发现其实~/.kube/config
就是采用这种client证书认证方式。那么你肯定好奇这是怎样实现的呢?实现流程如下:
// Create private key and csr
openssl genrsa -out myuser.key 2048
openssl req -new -key myuser.key -out myuser.csr
openssl req -new -x509 -days 10000 -key .key -out <CA_public>.crt // Encode csr
cat myuser.csr | base64 | tr -d "\n"// Replace request and create csr
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:name: myuser
spec:request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ3l6Q0NBYk1DQVFBd2dZVXhDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoVGFHRnVaMmhoYVRFUgpNQThHQTFVRUJ3d0lVMmhoYm1kb1lXa3hEekFOQmdOVkJBb01CbU51WTJGdGNERVBNQTBHQTFVRUN3d0dZMjVqCllXMXdNUTh3RFFZRFZRUUREQVpqYm1OaGJYQXhIVEFiQmdrcWhraUc5dzBCQ1FFV0RtTnVZMkZ0Y0VBeE5qTXUKWTI5dE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBeG5LN09PRDEvR1FBR1ZZKwpBeVhhem85Y1FmKzFVTndNYk93M0pyaHV5R0ZvbnphU0FuSXhoZlFlVFRKSzJJUWlNODA5VU9ud2NpMnJ5RjRHCnVlaWRiMjlTZFc4VmJXZE9xWGRkWWhmTGJnZ0FtbFFNQmc5dTZPRWMrTW5OdStnM29ENGQvNTh0ZlBDbDQydnoKMURKbmFwa21ZWUs2UWpxanVQT2tqcUZQeElrMFphTFRWTlAvVk0zbGpGVnZWL2xNZlZ4RjdTMzEwSmhtRWNhcApMMHdXN0VNazdwV242aFJ3SkFsSkxQTHlSNGRiT1NhRFlpYWVqbHF5RWt4WFBWbE1EVmFieEF5Z01Ca2pJbGlxCjJ2dnJ3UERzYnorWXppMFRxVEpYamtzV3REUngxeXladU5DVkl4V01ib3IxdEI0c3NoOWk2dnhEakxFWGMwVjEKK204MmVRSURBUUFCb0FBd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFESWJ3S1JxVnQxWllmN3QzdVlsL3pybgpiV2JFbEFNUmY4UEhOTDl3SXRCRGpDMFZjb1pNdTFMVW9JNXN0V0V2Z0UwbFJLVTUwTkg4MG1XbmphbUViWGFMCkJWbXZNcGxFSEVVbk9tOXY1SGNIUzNZV3NEU1hocTVIOE5yb1dkNTNKN3NOazVkU25XYWJZa3RHMm9mWFIweVgKbEt4MGt5bW16ajJlTkFvMUFLTGIrUmVocW9od08xdlhnWEU4WXJvNUV6cFU0NFhGa0dHN2RyVWJ5MlZCMG5hQwpXZHZ1MVl0VGk1RVVhblNpK3BncC94aUtrb0s0MVlZMHIrOENSOUtHQk1vY2UvUGlRcDU0RThRMHNheU1KWXZuCmYxVTZmT3BiQUJ2aXIzWU9Vd3QwOHFZNlNnWmo3ZjZBRU5qcUU3T3pJVzg3QTZ2Z3B6WWNubXpOTFlWakp0OD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==signerName: kubernetes.io/kube-apiserver-clientexpirationSeconds: 86400 # one dayusages:- client auth
EOF// Approve csr
kubectl certificate approve myuser// Check csr
kubectl get csr/myuser -o yaml// Extract crt
kubectl get csr myuser -o jsonpath='{.status.certificate}'| base64 -d > myuser.crt// Set credential, 会将证书信息和user append到~/.kube/config中去
kubectl config set-credentials myuser --client-key=myuser.key --client-certificate=myuser.crt --embed-certs=true// Grant permission
kubectl create role developer --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods
kubectl create rolebinding developer-binding-myuser --role=developer --user=myuser// Get pod, 在config中可能存在多个user,因此需要指定user, kubectl就会使用指定用户的client证书去连接apiserver
kubectl get pods --user=myuser
- 静态Token文件
🐯 使用静态Token文件认证只需要API Server启动时配置--token-auth-file=SOMEFILE。
🐯 该文件为csv格式,每行至少包括三列token,username,user id,token,user,uid,"group1,group2,group3”
- 引导Token
🌹 为了支持平滑地启动引导新的集群,Kubernetes 包含了一种动态管理的持有者令牌类型, 称作启动引导令牌(Bootstrap Token)。
🌹 这些令牌以 Secret 的形式保存在 kube-system 名字空间中,可以被动态管理和创建。
🌹 控制器管理器包含的 TokenCleaner 控制器能够在启动引导令牌过期时将其删除。
🌹 在使用kubeadm部署Kubernetes时,可通过kubeadm token list命令查询。
- 静态密码文件
• 需要API Server启动时配置–basic-auth-file=SOMEFILE,文件格式为csv,每行至少三列password, user, uid
,后面是可选
的group名
password,user,uid,"group1,group2,group3”
- ServiceAccount
ServiceAccount是Kubernetes自动生成的,并会自动挂载到容器的/run/secrets/kubernetes.io/serviceaccount目录中。
- OpenID
OAuth 2.0的认证机制
- Webhook 令牌身份认证
🔥 --authentication-token-webhook-config-file 指向一个配置文件,其中描述 如何访问远程的 Webhook 服务。
🔥 --authentication-token-webhook-cache-ttl 用来设定身份认证决定的缓存时间。 默认时长为 2 分钟。
- 匿名请求
如果使用AlwaysAllow以外的认证模式,则匿名请求默认开启,但可用--anonymous-auth=false禁止匿名请求。
2、基于webhook的认证服务集成
(1)构建符合Kubernetes规范的认证服务
需要依照Kubernetes规范,构建认证服务,用来认证tokenreview request
- 构建认证服务
认证服务需要满足如下Kubernetes的规范
🔥 URL: https://authn.example.com/authenticate
🔥 Method: POST
🔥 Input:
// post消息体
{ "apiVersion": "authentication.k8s.io/v1beta1", "kind": "TokenReview", "spec": { "token": "(BEARERTOKEN)" } }
我们可以发现这是一种TokenReview crd资源。TokenReview就是k8s和ldap server交互的数据标准
, 在代码中反应出来就是一个TokenReview struct
。
🔥 Output:
// 认证结果的返回必须也要遵守k8s的TokenReview数据标准
{ "apiVersion": "authentication.k8s.io/v1beta1","kind": "TokenReview","status": {"authenticated": true,"user": {"username": "janedoe@example.com","uid": "42","groups": ["developers","qa"]}}
}
一般来说我们不建议ldap server直接对k8s 标准特殊对待,而对ldap server侵入专门用一个handler去做适配。而是在ldap和k8s中间做一个adapter适配器,协调两者的request、response
。
(2)开发adapter适配器
// 解码认证请求
decoder := json.NewDecoder(r.Body)
var tr authentication.TokenReview
err := decoder.Decode(&tr)
if err != nil {log.Println("[Error]", err.Error())w.WriteHeader(http.StatusBadRequest)json.NewEncoder(w).Encode(map[string]interface{}{"apiVersion": "authentication.k8s.io/v1beta1","kind": "TokenReview","status": authentication.TokenReviewStatus{Authenticated: false,},})return
}
// 转发认证请求至认证服务器
// Check User
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 { // 认证失败log.Println("[Error]", err.Error())w.WriteHeader(http.StatusUnauthorized)json.NewEncoder(w).Encode(map[string]interface{}{"apiVersion": "authentication.k8s.io/v1beta1","kind": "TokenReview","status": authentication.TokenReviewStatus{Authenticated: false, // 返回false},})return
}
// err 为nil, 认证成功
w.WriteHeader(http.StatusOK)
trs := authentication.TokenReviewStatus{Authenticated: true, // 返回trueUser: authentication.UserInfo{Username: *user.Login,UID: *user.Login,},
}
json.NewEncoder(w).Encode(map[string]interface{}{"apiVersion": "authentication.k8s.io/v1beta1","kind": "TokenReview","status": trs,
})
我们可以发现,ldap和k8s中间层的这个adapter适配器其实就是负责解析k8s认证请求,将tokenReview中的字段token取出然后向github请求认证,然后得到github认证返回结果,然后拿到用户名、id、认证结果等塞进TokenReview结构体中去,再返回给k8s即可
, 这就是一种协调适配的中间层。
(3)配置认证服务(Adapter服务)
mkdir -p /etc/kubernetes/config
cd /etc/kubernetes/config
cat webhook-config.json
{"kind": "Config","apiVersion": "v1","preferences": {},"clusters": [{"name": "github-authn","cluster": {"server": "http://127.0.0.1:3000/authenticate" // adapter适配器服务地址}}],"users": [{"name": "authn-apiserver","user": {"token": "secret"}}],"contexts": [{"name": "webhook","context": {"cluster": "github-authn","user": "authn-apiserver"}}],"current-context": "webhook"
}
- 备份并修改apiserver.yaml:
cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml
vim kube-apiserver.yaml
添加:- --authentication-token-webhook-config-file=/etc/kubernetes/config/webhook-config.json
添加:- mountPath: /etc/kubernetes/configname: webhook-configreadOnly: true
添加:- hostPath:path: /etc/kubernetes/configtype: DirectoryOrCreatename: webhook-config
- 获取第三方token,并添加到kubeconfig:
这里使用GitHub的Personal access tokens,https://github.com/settings/tokens:
- 将访问凭证token放进config文件中即可。
vim ~/.kube/config- name: wgh9626user:token: ghp_Yx45P....
因为token是github生成的,因此这个token github肯定也能解析
,adapter把这个token转给github肯定就能解析到相应结果。因此实现了认证。其实像rancher、kubesphere那种user,对接ldap时,其实就是把token换成了用户名密码罢了
。
- 最后验证是否成功对接ldap:
kubectl get po --user xxxx
(4)配置apiserver
可以是任何认证系统:比如ldap,ad,keystone等。
- 在用户认证完成后,生成代表用户身份的 token;
- 该 token 通常是有失效时间的;
- 用户获取该 token 以后,将 token 配置到kubeconfig。
修改 apiserver 设置,开启认证服务,apiserver保证将所有收到的请求中的 token 信息,发给认证服务进行验证。
--authentication-token-webhook-config-file
,该文件描述如何访问 认证服务(adapter service);--authentication-token-webhook-cache-ttl
,认证的有效期时间。默认2分钟。可以设置长时间,减少外部认证服务器的压力。
配置文件需要 mount 进 Pod。
注意:当token过期时,认证插件会去renew,如果认证服务器返回了错误,插件没有设置指数级back off,而是不停的重试,会导致认证服务器down掉.
解决方法:
- 认证服务器要添加
限流(Circuit break)
- apiserver端端认证插件要设置
指数级等待(Rate limit)
。
三、授权
1、简介
鉴权主要是用于对集群资源的访问控制,哪些用户有哪些对象的那些操作的权限
。
跟认证类似,Kubernetes 也支持多种授权机制,并支持同时开启多个授权插件(只要有一个验证通过即可)
。
如果授权成功,则用户的请求会发送到准入控制模块做进一步的请求验证;对于授权失败的请求则返回 HTTP 403
。
仅处理以下的请求属性:
user, group, extra
API、请求方法(如 get、 post、 update、 patch和delete)和请求路径(如/api)
请求资源和子资源
Namespace
API Group
支持的授权插件:
- ABAC
- RBAC
- Webhook
- Node
2、RBAC和ABAC的对比
- RBAC(Role Based Access Control):谁(subject)能对谁(资源对象)做什么操作(operation)。
- ABAC (Attribute Based Access Control):在k8s中类似静态密码/token,需要配置一个csv文件,标明用户和权限,重新启动 API Server才能实现权限变更。
而 RBAC 可以利用 kubectl 或者 Kubernetes API 直接进行配置。RBAC 可以授权给用户,让用户有权进行授权管理,这样就可以无需接触节点,直接进行授权管理。RBAC 在Kubernetes 中被映射为 API 资源和操作。
3、Role,ClusterRole
Role(角色)是一系列权限的集合,例如一个角色可以包含读取 get Pod 的权限和列出list Pod 的权限。Role 的作用域是某个特定 namespace 。
ClusterRole的作用域是集群级的资源或者是非资源类的 API(如 /healthz)
4、RoleBinding,ClusterRoleBinding(Group群组赋权)
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户
。
它包含若干 主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。
组的概念:
- 当与外部认证系统对接时,用户信息 (Userlnfo)可包含 Group 信息,授权可针对用户群组;
- 当对 ServiceAccount 授权时,Group 代表某个 namespace 下的所有 ServiceAccount。
ClusterRole可以和RoleBinding或者ClusterRoleBinding绑定。
5、规划系统角色
对于一个多用户的集群,规划系统角色是非常有必要的。
- user
🔥 系统管理员:是否需要所有资源的所有权限?不是的,因为namespace下可能有secret,secret中包括了一些敏感信息,需要把secret exclude掉。
🔥 普通用户:是否有该用户创建的 namespace 下的所有 object 的操作权限?对其他用户的 namespace 资源是否可读,是否可写?需要看业务场景,比如强隔离,不想让用户知道别的namespace和object,只能看到自己namespace下的object。弱隔离的场景,用户可以读取所有公共资源,写只能写自己的namespace。
- SystemAccount
🔥 SystemAccount 是开发者创建应用后,与 apiserver 通讯需要的身份。
🔥 用户可以创建自定的 ServiceAccount,Kubernetes 也为每个 namespace 创建 defaultServiceAccount。
🔥 Default ServiceAccount 通常需要给定权限以后才能对apiserver 做写操作。
6、自动化多集群权限管控
在 cluster创建时,创建自定义的 role,比如 namespace-creator。
namespace-creator clusterrole 定义用户可操作的对象和对应的读写操作。
'注意':因为此用户是namespace创建者,因此应该给予用户所有资源对象的所有操作权限。
问:有人可能会问clusterrole的所有权限不会权限太大了吗?
答:其实我们后面会创建 rolebinding 对象与之绑定,因此权限就从cluster缩小到了namespace所有权限了。
-
创建自定义的 namespace admission controller:
创建mutating webhook,当apiserver收到创建namespace请求时,获取当前用户信息并给这个namespace添加annotation
。 -
创建 RBAC controller:
🔥 Watch namespace 的创建事件;
🔥 获取当前 namespace 的创建者信息;
🔥 在当前 namespace 创建 rolebinding 对象,并将 namespace-creator 角色和用户绑定。
注意:切勿创建clusterrolebinding 对象,不然这个用户就是集群管理员了。
7、注意事项
- ClusterRole 是非 namespace 绑定的,针对整个集群生效。
ClusterRole给集群管理员,普通用户通过RoleBinding绑定
。- ThirdPartyResource 和 CustomResourceDefinition 是全局资源,
普通用户创建 CRD以后,需要管理员授予相应权限后才能真正操作该对象
。 权限是可以传递的
,用户A可以将其对某对象的某操作,抽取成一个权限,并赋给用户B;防止海量的Role和RoleBinding
,因为大量的对象会导致鉴权效率低,同时给 apiserver 增加负担。- ServiceAccount 也需要授权的,否则可能无法操作某对象。
Tips: SSH 到 master 节点通过 insecure port 访问 apiserver 可绕过鉴权,当需要做管理操作又没有权限时可以使用(不推荐)。
四、准入控制
什么是准入(Admission Control)?假如kubectl向apiserver发起一个request(比如新建了个pod或namespace),我们可以给这个request中携带的ns object(struct or yaml)进行字段的增、删、改,这个就称之为Mutation
,比如给ns加一个creator的annotation,给pod加一些初始化的环境变量(podpreset)
等;mutation之后还需要判断这个值是否合法,这个过程就是Validation,注意此过程不修改object,只验证字段合法性。Mutation + Validation 就是准入控制
。
不同于授权和认证只关心请求的用户和操作,准入控制还处理请求的内容,并且仅对创建、更新、删除或连接(如代理)等有效,而对读操作无效。
1、为资源增加自定义属性(Mutating Example)
作为多租户集群方案中的一环,我们需要在 创建namespace 的准入控制中,获取创建ns的用户信息,并将用户信息更新到 namespace 的 annotation中。
只有当 namespace 中有有效用户信息时,我们才可以在namespace 创建时,自动给这个用户绑定这个ns的所有权限,namespace 才可用。
2、配额管理(Validation Example)
为什么需要做配额管理,如何限定某个用户有多少资源?
因为整个集群的资源是有限的,etcd也有存储上限的,不能让每个用户都无限制的创建资源,这就需要做限制。具体实现参见下面的方案。
(1)ResourceQuota方案流程
[1] 预定义每个 namespace 的 ResourceQuota,并把 spec 保存为 configmap;
- 用户可以创建多少个 Pod;
BestEffortPod //yaml中没有声明需要多少资源
QoSPod //yaml中声明了需要多少资源
- 用户可以创建多少个 service;
- 用户可以创建多少个 ingress;
- 用户可以创建多少个 service VIP;
[2] 创建 ResourceQuota Controller(自动化)
监控 namespace 创建事件,当 namespace 创建时,在该 namespace 创建对应的 ResourceQuota对象。
[3] apiserver 中开启 ResourceQuota 的 admission plugin。
此步骤以上都是如何做ResourceQuota自动创建,只有开启了 ResourceQuota 的admission plugin才能实现资源限制的目标。'实际原理':ResourceQuota admission plugin其实主要是做的Validation这部分,核对当前集群的已有资源是否已达到此ns的ResourceQuota对象中所设定的阈值,若达到则拒绝当前对象的创建。
(2)ResourceQuota限制实战
创建ResourceQuota对象:
vim quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:name: object-countsnamespace: default
spec:hard:configmaps: "1"k apply -f quota.yaml
创建一个configmap:
k create -f nginx-server-block1.yaml
注意:每个ns默认就会有个kube-root-ca.crt cm
,因此上面已经是第二个cm了,创建失败!
(3)准入插件(官方自带)
准入控制支持同时开启多个插件,它们依次调用,只有全部插件都通过的请求才可以放过进入系统
。
AlwaysAdmit:接受所有请求。
Alwayspullmages:总是拉取最新镜像,在多租户场景下非常有用。
DenyEscalatingExec:禁止特权容器的 exec 和 attach 操作。
ImagePolicyWebhook:通过 Webhook 来判断image是否合法 ,需要同时配置 --admission-control-config-file。(容器镜像做安全扫描)
ServiceAccount:自动创建默认 ServiceAccount,并确保 Pod 引用的 ServiceAccount 已经存在。
SecurityContextDeny:拒绝包含非法 SecurityContext 配置的容器。
ResourceQuota:限制 Pod 的请求不会超过配额,需要在namespace中创建一个ResourceQuota 对象。
LimitRanger: 为 Pod 设置默认资源请求和限制,需要在 namespace 中创建一个 LimitRange对象。
InitialResources:根据镜像的历史使用记录,为容器设置默认资源请求和限制
NamespaceLifecycle: 确保处于 termination 状态的 namespace 不再接收新的对象创建请求,并拒绝请求不存在的 namespace。
DefaultStorageclass:为PVC 设置默认 Storageclass。
DefaultTolerationSeconds:设置 Pod 的默认 forgiveness toleration 为5分钟。
PodSecurityPolicy:使用 Pod Security Policies(PSP) 时必须开启。
NodeRestriction: 限制 kubelet 仅可访问 node、 endpoint、 pod、 service 以及 secret、configmap、PV 和 PVC 等相关的资源。
查看apiserver支持哪些准入控制插件
kubectl exec -it kube-apiserver-node1 -n kube-system -- kube-apiserver -h
有部分是默认开启,有部分需要手动开启。
(4)自定义准入控制webhook插件
如果apiserver提供的插件不能满足需求,那么就可以自定义webhook来实现。
- MutatingWebhookConfiguration:
变形插件
,支持对准入对象的修改。 - ValidatingWebhookConfiguration:
校验插件
,只能对准入对象合法性进行校验,不能修改
。
【1】实现原理
当一个request经过了认证鉴权,apiserver就会看admission的配置。如果有对应的webhook,就会发起一个AdmissionReview(参考之前TokenReview)
的请求到webhook。假如需要变形,那么会先经MutatingWebhookConfiguration变形,再经过ValidatingWebhookConfiguration验证变形后的属性是否合法。并返回结果给apiserver,如果合法,apiserver会继续向下传递该request。
// 查看MutatingWebhookConfiguration
k get MutatingWebhookConfiguration cert-manager-webhook -o yaml
上面是将mutating webhook服务部署到了k8s里,使用外部webhook如下:
apiVersion:
admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:name: ns-mutating.webhook.k8s.iowebhooks: - clientConfig:caBundle: {{.serverca_base64}}url: https://admission.local.tess.io/apis/admission.k8s.io/v1alpha1/mutate
【2】mutating demo(代码实现)
确保pod以非root用户运行
,判断pod的runAsNonRoot
是否设置,没有的话设置true,如果设置了false改成true。并且统一使用用户id为1234启动pod
。
// Clone admission demo
git clone https://github.com/cncamp/admission-controller-webhook-demo.git
部署demo
// Deploy webhook
cd admission-controller-webhook-demo/
./deploy.sh// Check webhook status
k get deployment.apps/webhook-server -n webhook-demo
k get po -n webhook-demo
部署测试pod,查看属性:
// Create demo pod and verify
kubectl create -f examples/pod-with-defaults.yaml
kubectl get po pod-with-defaults -oyaml
kubectl logs -f pod-with-defaults
// More details
https://github.com/cncamp/admission-controller-webhook-demo/blob/main/README.md
五、限流
1、限流算法相关
(1)计数器固定窗口算法
原理:对一段固定时间窗口
内的请求进行计数,如果请求数超过了阈值,则舍弃该请求
;如果没有达到设定的阈值,则接受该请求,且计数加1
。当时间窗口结束时,重置计数器为0
。
缺点: 是如果请求都集中在两个窗口之间,那么整个系统是在接受2倍的请求数量。
(2)计数器滑动窗口算法
在固定窗口的基础上,将一个计时窗口分成了若干个小窗口
,然后每个小窗口维护一个独立的计数器
。
假如限流窗口是1分钟,分成了60个1秒,每1秒让窗口向后滑一格,限流永远是60秒。
同时要保证整个窗口中所有小窗口的请求数目之和不能超过设定的阈值
。
缺点: 每个小窗口也可能有高峰期。
(3)漏斗算法
请求来了之后会首先进到漏斗里,然后漏斗以恒定的速率将请求流出进行处理
。当请求的流量过大时,漏斗达到最大容量时会溢出,此时请求被丢弃
。在系统看来,请求永远是以平滑的传输速率
过来,从而起到了保护系统的作用。
缺点: 无法处理突发流量,超出漏斗容量的请求就被丢弃了。
(4)令牌桶算法
令牌桶算法是对漏斗算法的一种改进,除了能够起到限流的作用外,还允许一定程度的流量突发
。
在令牌桶算法中,存在一个令牌桶,算法中存在一种机制以恒定的速率向令牌桶中放入令牌。令牌桶也有一定的容量,如果满了令牌就无法放进去了
。当请求来时,会首先到令牌桶中去拿令牌,如果拿到了令牌,则该请求会被处理,并消耗掉拿到的令牌;
如果令牌桶为空,则该请求会被丢弃。如果请求没那么多了,令牌是一直在放的,桶里面就会保存一些令牌,等突发流量过来,就能够处理一部分
。
(5)总结四种传统方法局限性
- 粒度粗:无法为不同用户,不同场景设置不通的限流.
- 单队列:共享限流窗口/桶,一个坏用户可能会将整个系统堵塞,其他正常用户的请求无法被及时处理。
- 不公平:正常用户的请求会被排到队尾,无法及时处理而饿死。
- 无优先级:重要的系统指令一并被限流,系统故障难以恢复。
2、apiserver中的限流
- max-requests-inflight:在给定时间内的最大并发数(inflight:apiserevr已接受请求但没有处理完的请求)
- max-mutating-requests-inflight:在给定时间内的最大 mutating 请求数,即所有写操作的请求数。调整apiserver 的流控 qos。(mutating:所有对k8s对象的修改,包括创建,删除,修改)
(1)API Priority and Fairness(简称APF)
- APF 以更细粒度的方式对请求进行分类和隔离。
- 它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。
- 把
整个集群分为不同的限流等级
,把相近的用户的请求分到不同的等级中,不同的用户用不同的flow。同一个flow也会有不同的队列
,防止一个bad的请求饿死其他请求(即使优先级相同) - APF 的
核心
:多等级、多队列
(2)APF实现
首先APF最关键的就是Flowshema
和PriorityLevelConfiguration
,FlowSchema以及其中的Flow是对请求的分类,PriorityLevelConfiguration apiserver用来按照一定规则处理请求的数据结构。
APF 对请求进行更细粒度的分类,每一个请求分类对应一个 FlowSchema (FS)
,FS内的请求又会根据 distinguisher 进一步划分为不同的 Flow
。PL 中维护了一个 QueueSet
里面有多个Queue,每个 Flow 通过 shuffle sharding 算法
从 QueueSet 随机选取特定几个的 Queue 缓存请求
,每次从 QueueSet 中取请求执行时,会先应用公平排队算法
从 QueueSet 中选中一个queue,然后从这个 queue 中取出oldest 请求执行。所以即使是同一个 PL 内的请求,也不会出现一个 Flow 内的请求一直占用资源的不公平现象
,因为一个PL(QueueSet)
存在多个Queue,一个Queue又存有多个Flow的数据。
扩展:
- 优先级 (Priority Level, PL),
不同优先级的并发资源是隔离的
。所以不同优先级的资源不会相互排挤
。特定优先级的请求可以被高优处理。 - 一个 PL 可以对应多个 FS,
PL 中维护了一个 QueueSet
,用于缓存不能及时处理的请求,请求不会因为超出PL的并发限制而被丢弃。
注意: 一个PL有多个Queue(QueueSet)
,一个Queue存有来自多个Flow的请求。同样一个Flow会选取特定几个的 Queues 缓存请求。至于有几个QueueSet
,一个Queue的size多大
,一个Flow选取几个Queue
缓存请求在PriorityLevelConfiguration中可以配置。
【1】PriorityLevelConfiguration
一个 PriorityLevelConfiguration 表示单个隔离类型。
每个 PriorityLevelConfigurations 对未完成的请求数有各自的限制,对排队中的请求数也有限制。
k get prioritylevelconfiguration
k get prioritylevelconfiguration global-default -o yaml
【2】FlowSchema
FlowSchema 匹配一些入站请求,并将它们分配给优先级。
每个入站请求都会对所有 FlowSchema 测试是否匹配,首先从 matchingPrecedence 数值最低的匹配开始,然后依次进行,直到首个匹配出现。
k get flowschema
k get flowschema global-default -o yaml
【3】APF总结
- 传入的请求通过
FlowSchema 按照其属性分类
,并分配优先级。 每个优先级维护自定义的并发限制
,加强了隔离
度,这样不同优先级的请求,就不会相互饿死。- 在同一个优先级内,
公平排队算法可以防止来自不同Flow 的请求相互饿死
。 - 该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。
优先级:
- 如果未启用 APF,API服务器中的整体并发量将受到 kube-apiserver 的参数–max-requests-inflight 和 --max-mutating-requests-inflight 的限制。
- 启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的优先级中。每个传入的请求都会分配一个优先级;
- 每个优先级都有各自的配置,设定允许分发的并发请求数。
- 例如,默认配置包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。
- 这表示即使异常的 Pod 向 API服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。
排队:
- 即使在同一优先级内,也可能存在大量不同的流量源。
- 在过载情况下,防止一个请求流饿死其他流是非常有价值的(尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向kube-apiserver 发送请求,理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。
- 公平排队算法在处理具有相同优先级的请求时,实现了上述场景。
- 每个请求都被分配到某个流中,该流由对应的 FlowSchema 的名字加上一个流区分项(FlowDistinguisher)来标识。
- 这里的流区分项可以是发出请求的用户、目标资源的名称空间或什么都不是。
- 系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。
- 将请求划分到流中之后,APF 功能将请求分配到队列中。
- 分配时使用一种称为混洗分片(Shuffle-Sharding)的技术。该技术可以相对有效地利用队列隔离低强度流与高强度流。
- 混洗分片(Shuffle-Sharding):FS对应的是QueueSet,QueueSet是多队列。限制一个flow最多允许进几个队列。这样即使这几个坏了也不影响其他。
- 排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、公平性(当总流量超标时,各个独立的流将都会取得进展)、突发流量的容忍度以及排队引发的额外延迟之间进行权衡。
【4】豁免请求
某些特别重要的请求不受制于此特性施加的任何限制。这些豁免可防止不当的流控配置完全禁用API 服务器。
- system:用于 System:nodes 组(即 kubelets)的请求;kubelets 必须能连上 API 服务器,以便工作负载能够调度到其上。
- leader-election:用于内置控制器的领导选举的请求(特别是来自 kube-system 名称空间中 system:kube-controller-manager 和 system:kube-scheduler 用户和服务账号,针对endpoints、 configmaps 或 leases 的请求)。
- 将这些请求与其他流量相隔离非常重要,因为领导者选举失败会导致控制器发生故障并重新启动,这反过来会导致新启动的控制器在同步信息时,流量开销更大。
- workload-high:优先级用于内置控制器的请求。
- workload-low:优先级适用于来自任何服务帐户的请求,通常包括来自 Pods 中运行的控制器的所有请求。
- global-default:优先级可处理所有其他流量,例如:非特权用户运行的交互式 kubectl命令。
- exempt: 优先级的请求完全不受流控限制:它们总是立刻被分发。特殊的 exempt FlowSchema 把system:masters 组的所有请求都归入该优先级组。
- catch-all:
1、优先级与特殊的 catch-all FlowSchema 结合使用,以确保每个请求都分类。
2、一般不应该依赖于 catch-all 的配置,而应适当地创建自己的 catch-all FlowSchema 和PriorityLevelConfigurations(或使用默认安装的 global-default 配置)。
3、为了帮助捕获部分请求未分类的配置错误,强制要求 catch-all 优先级仅允许5个并发份额,并且不对请求进行排队,使得仅与 catch-all FlowSchema 匹配的流量被拒绝的可能性更高,并显示 HTTP 429 错误。
【5】查看当前集群的priority_level列表
# 所有优先级及其当前状态的列表
kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels# 所有队列及其当前状态的列表
kubectl get --raw /debug/api_priority_and_fairness/dump_queues# 当前正在队列中等待的所有请求的列表
kubectl get --raw /debug/api_priority_and_fairness/dump_requests
六、如何构建高可用apiserver
基础架构不是安全可靠的,实现冗余部署。apiserver 是无状态的 Rest Server,无状态所以方便扩缩容。
1、负载均衡方法
- 在多个 apiserver 实例之上,配置负载均衡
- 证书可能需要加上 Loadbalancer VIP 重新生成
2、预留充足的 CPU、内存资源:随着集群中节点数量不断增多,API Server 对 CPU 和内存的开销也不断增大。过少的 CPU 资源会降低其处理效率,过少的内存资源会导致 Pod 被 OOMKilled,直接导致服务不可用。在规划API Server 资源时,不能仅看当下需求,也要为未来预留充分。
3、善用速率限制(RateLimit):
APIServer 的参数“–max-requests-inflight" 和“–max-mutating-requests-inflight〞支持在给定时间内限制并行处理读请求(包括 Get、 List 和 Watch 操作)和写请求(包括 Create、Delete、 Update 和 Patch 操作)的最大数量。
当 APIServer 接收到的请求超过这两个参数设定的值时,再接收到的请求将会被直接拒绝。通过速率限制机制,可以有效地控制 APIServer 内存的使用。
如果该值配置过低,会经常出现请求超过限制的错误,如果配置过高,则 APIServer 可能会因为占用过多内存而被强制终止,因此需要根据实际的运行环境,结合实时用户请求数量和APIServer 的资源配置进行调优。
客户端在接收到拒绝请求的返回值后,应等待一段时间再发起重试,无间隔的重试会加重APIServer 的压力,导致性能进一步降低。
4、设置合适的缓存大小
API Server 与 etcd 之间基于 gRPC 协议进行通信,gRPC 基于连接复用的 HTTP/2 协议,即针对相同分组的对象,API Server 和 etcd 之间共享相同的TCP 连接,不同请求由不同的 stream 传输。一个 HTTP/2 连接有其 stream 配额,配额的大小限制了能支持的并发请求。
API Server 从etcd获取数据时,它会有一个本地的watch-cache
,实际是一个ring buffer(环状结构)
,如果ring buffer没满,所有的信息都会被缓存掉。如果ring buffer满了,之前缓存掉信息就会被覆盖掉。
当客户端发起查询请求时,API Server默认会将其缓存直接返回给客户端。缓存区大小可以通过参数“--watch-cache-sizes〞 设置
。
针对访问请求比较多的对象,适当设置缓存的大小,极大降低对 etcd 的访问频率,节省了网络调用,降低了对 etcd 集群的读写压力,从而提高对象访问的性能
。
但是 API Server 也是允许客户端忽略缓存
的,例如客户端请求中 没有设置resourceVersion,这时 API Server 直接从 etcd 拉取最新数据返回给客户端
。
客户端应尽量避免此操作,应在请求中设置 resourceVersion 为 0
,API Server 则将从缓存里面读取数据
,而不会直接访问 etcd。
5、客户端尽量使用长连接
当查询请求的返回数据较大且此类请求并发量较大时,容易引发TCP 链路的阻塞
,导致其他查询操作超时。
因此基于 Kubernetes 开发组件时,例如某些 DaemonSet 和 Controller,如果要查询某类对象,应尽量通过长连接 ListWatch 监听对象变更,避免使用轮询全量从 API Server 获取资
源。
如果在同一应用程序中,如果有多个 Informer 监听 API Server 资源变化,可以将这些 Informer合并,减少和 API Server 的长连接数,从而降低对 API Server 的压力
。
6、如何访问 API Server(统一访问入口)
apiserver外部是通过负载均衡的vip访问,内部是通过kube-proxy转发service的cluster ip来访问。这样就导致同一个apiserver有不同的访问入口
,可能会导致一个入口是通的,一个入口坏了,整个集群是自动化的,导致节点状态无法上报,就会导致pod被驱逐。
对外部客户(user/client/admin),永远只通过 LoadBalancer 访问。只有当负载均衡出现故障时,管理员才切换到 apiserver IP 进行管理内部客户端。
七、搭建多租户的 Kubernetes 集群
首先k8s是没有租户的概念的,它提供的是一些基础能力,这些能力可以实现多租户的功能。
- 授信
🔥 认证:禁止匿名访问,只允许可信用户做操作。
🔥 授权:基于授信的操作,防止多用户之间互相影响,比如普通用户删除 Kubernetes 核心服务,或者 A 用户删除或修改B用户的应用。 - 隔离
🔥 可见行隔离:用户只关心自己的应用,无需看到其他用户的服务和署。
🔥 资源隔离:比如有的用户需要GPU,这样就需要隔离。可以通过添加taint或者node selector来实现。
🔥 应用访问隔离:用户创建的服务,添加安全策略只允许部分用户访问。 - 资源管理
Quota 管理:谁能用多少资源?
八、apimachinery
就是apiserver的运作机制。
1、复习GKV
对象的一些重要属性:typemeta:定义了这个对象是什么。
- Group:把对象根据业务场景分类
- Kind:这个对象的类型是什么(pod,secret,service等,都属于core group)
- Version:v1,v1alpha1。向前兼容。
🔥 Internel version(面向集群内部的,就是存在etcd中的版本) 和 External version(面向集群外部的)
🔥 版本转换:任何 External version发送到apiserver的请求在存到etcd之前,都会转成 Internel version。双向转换。