背景
下面我将以这个思维导图来总结一下apiserver。包括简介,认证,授权机制,准入控制,限流方法,如何实现高可用和apiserver的运作机制。
简介
1.主要功能
apiserver是k8s中最重要的组件之一。
提供了集群管理的 REST API 接口,包括认证授权、数据校验以及集群状态变更等;其他模块通过apierver查询或修改数据,只有apierver才能直接操作 etcd。(因为apiserver可以对etcd进行保护,比如认证鉴权,限流等,apiserver本身也有缓存机制,很多读操作就直接在apiserver端处理掉了,减少了etcd的并发请求)
2.访问控制概览
客户端请求发送到apiserver–>etcd的流程图如下所示:
- request发送到apiserver,http handler接收到请求后,会将请求转发给认证鉴权模块。
- 认证:知道你是谁来访问apiserver。鉴权:看你也没有对应的操作权限。
- 假如请求中有个属性没有设置,apiserver会给它一个默认值;或者请求中属性值是a,需要改成b。这时就需要mutating(变形),也可以通过webhook来实现mutating。
- 验证对象schema是否符合k8s规范。
- 如果需要附加校验,通过Validation admission plugin调用webhook来实现。
- 最后数据才会发送到etcd中
3.访问控制细节
从代码层级来看访问控制的一些细节。
- 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中。
- request conversion:请求返回给客户端。
认证机制
apiserver有一个配置是secure-port,开启后,通过这个端口访问都是开启认证的。反之通过insecure-port访问的都是不加认证的。
开启 TLS时,所有的请求都需要首先认证。Kubernetes 支持多种认证机制,并支持同时开启多个认证插件(只要有一个认证通过即可)。如果认证成功,则用户的 username 会传入授权模块
做进一步授权验证;而对于认证失败的请求则返回 HTTP 401。
1.认证插件
X509 证书
使用 X509 客户端证书只需要 API Server 启动时配置 –client-ca-file=SOMEFILE。在证书认证时,其 CN 域用作用户名,而组织机构域则用作 group 名。
- 生成证书
openssl genrsa -out myuser.key 2048 openssl req -new -key myuser.key -out myuser.csr
- 解码证书
cat myuser.csr | base64 | tr -d "\n"
- 创建csr
cat <<EOF | kubectl apply -f - apiVersion: certificates.k8s.io/v1 kind: CertificateSigningRequest metadata: name: myuser spec: request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ3lqQ0NBYklDQVFBd2dZUXhDekFKQmdOVkJBWVRBa05PTVJFd0R3WURWUVFJREFoSVlXNW5XbWh2ZFRFUgpNQThHQTFVRUJ3d0lTR0Z1WjNwb2IzVXhEekFOQmdOVkJBb01CbTE1ZFhObGNqRVBNQTBHQTFVRUN3d0diWGwxCmMyVnlNUTh3RFFZRFZRUUREQVp0ZVhWelpYSXhIREFhQmdrcWhraUc5dzBCQ1FFV0RXMTVkWE5sY2tCeGNTNWoKYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUM2T1pxdit3Yk5CWXRGdEpEOQpHZjlsWkxRa1BlVENCck1uSGpvSVZMTkRYams3QUplekRpVmp1NGNVUmVQd0NMc1RXTzQ1dnMxNU1aVE1GTzRyCjFTYVZzd3dtTWpHUndBRTYrT2ZIU0xhM1Joc1YzVWZianE5eTVmbXhNUTJIa0Z2RFdwcHZ4L1R6cVVETEN3WmQKLzg5Skx3WHdUMTg5K3h4enMvYy9CRDFkeUttc0xLaW9UMEhtVXVHTzg0OURiYmFSaGhMRXQ4ejNWZXFTRUo1Qgo1OGdDVkdUbC92eTJacURveURmNUhJQk1ubzNYSURZZFBmSjhLaThKUkhQT3pvNlZLeFN1bGhVMnZWQ3JkTEpTCldsRDBKanBuK0lnVFRXTGl4R0VuanZORUFxZVFMSmlZc1dDNlI3Z0xQTWpTb0xVT3pXVWk2d3RJaHg1bFY3MGQKWUhldkFnTUJBQUdnQURBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWZMNEs1TmdVKzJ0UkV4REZIR0FLZGxkNwprSklyeW16V0E2bHhBeTRUWFRvYUZVV05BYjF4OXJTdFdFelpIVGFCQkF6U25zdDhIY3AxQ2dJNFpIMDJIRERaCnBVNDNnRmlVcjloYlJIVTRRaUpPeHllMDhtSWxHUXY0dHlCUStFbHhuUTY1MDFqalkveTBEOGhmLzZDejdpTmwKYjhGR3JwZXN4c2pJWldjQVZDdjVlRjRKNDFsaWtUbzBveVpoM21QQThFK05FWER1TE4rTDd0cjhWUy9HMU1mKwpkbE5ZeU84TkFtellzVWVSVFNIZDFRZndLcStYVC9VYWVWQ1hnSm4zQ0dsdVVFcm1vY3JsV0RDQ3MydVhmM1NCCmMvZ2JVRTBIV3Y4U2FFdW5XdGdGTitiNlhaTnBIdW9rV2llYlgyTGhGalo1Mkd5ZjZvR3RyQXo2RWVmV2FRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg== signerName: kubernetes.io/kube-apiserver-client expirationSeconds: 86400 # one day usages: - client auth EOF
可以看到csr状态是pending,请求apiserver给myuser签发一个证书。 - 签发证书
kubectl certificate approve myuser
- 查看csr
kubectl get csr/myuser -o yaml
- 获取签发好的证书
kubectl get csr myuser -o jsonpath='{.status.certificate}'| base64 -d > myuser.crt
- 设置credentials
kubectl config set-credentials myuser --client-key=myuser.key --client-certificate=myuser.crt --embed-certs=true cat ~/.kube/config k get po --user=myuser
这里get报错是因为myuser没有权限获取pod。
- 授权
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
- 获取pod
k get po --user=myuser
静态 Token 文件
使用静态 Token 文件认证只需要 API Server 启动时配置 –token-auth-file=SOMEFILE。
该文件为 cSV 格式,每行至少包括三列 token,username,user id,token,user,uid,"group1,group2,group3"
- 生成静态token,并放到auth目录中
mkdir -p /etc/kubernetes/auth
cd /etc/kubernetes/auth
echo ‘wgh-token,wgh,1000,"group1,group2,group3"’ > static-token(token名,用户,uid,group )
- 备份apiserver.yaml
cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml - 修改apiserver.yaml,把静态token文件mount到apiserver的pod里
vim /etc/kubernetes/manifests/kube-apiserver.yaml 添加 - --token-auth-file=/etc/kubernetes/auth/static-token 添加 - mountPath: /etc/kubernetes/auth name: auth-files readOnly: true 添加:- hostPath: path: /etc/kubernetes/auth type: DirectoryOrCreate name: auth-files
apiserver会自动重启
- 测试静态token
curl https://127.0.0.1:6443/api/v1/namespaces/default -H "Authorization: Bearer cncamp-token" -k
这里会返回403,是鉴权没过,认证已经过了,因为用户wgh已经被识别到了。
引导 Token
为了支持平滑地启动引导新的集群,Kubernetes 包含了一种动态管理的持有者令牌类型,称作启动引导令牌(Bootstrap Token)。
这些令牌以 Secret 的形式保存在 kube-system 名字空间中,可以被动态管理和创建。
控制器管理器包含的 TokenCleaner 控制器能够在启动引导令牌过期时将其删除。
在使用 kubeadm 部署 Kubernetes 时,可通过如下命令查询。
kubeadm token create --print-join-command
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 目录中。
- 查看当前ServiceAccount和对应的secret
k get sa default -o yaml k get secret default-token-xndbc -o yaml
- base64解码token
echo ZXlKaGJHY2lPa... | base64 -d
解析出来的token是以“."为分隔,分别为header,body,checksum。
- 使用sa获取ns
curl https://127.0.0.1:6443/api/v1/namespaces/default -H "Authorization: Bearer eyJhbGciOiJS... " -k
OpenID
OAuth 2.0 的认证机制
Webhook 令牌身份认证
–authentication-token-webhook-config-file 指向一个配置文件,其中描述如何访问远程的Webhook 服务。
–authentication-token-webhook-cache-ttl 用来设定身份认证决定的缓存时间。默认时长为2分钟。
匿名请求
如果使用 AlwaysAllow 以外的认证模式,则匿名请求默认开启,但可用 –anonymous-auth=false 禁止匿名请求。
2.基于webhook的认证
需要依照 Kubernetes 规范,构建认证服务,用来认证 tokenreview request。
apiserevr会以post的方式发送给URL,让外面的服务代替apiserver去认证。
- URL: https: //authn.example.com /authenticate
- Method: POST
- Input:
- Output:
开发
可以参考 https://qithub.com/appscode/quard
这里采用的main.go如下:
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"github.com/google/go-github/github"
"golang.org/x/oauth2"
authentication "k8s.io/api/authentication/v1beta1"
)
func main() {
http.HandleFunc("/authenticate", func(w http.ResponseWriter, r *http.Request) {
// 解码认证请求
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
}
log.Print("receving request")
// 转发认证请求至认证服务器
// Check User
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: tr.Spec.Token},
)
tc := oauth2.NewClient(context.Background(), 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,
},
})
return
}
log.Printf("[Success] login as %s", *user.Login)
// 认证结果返回给 API Server
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,
})
})
log.Println(http.ListenAndServe(":3000", nil))
}
如何使用
- 启动认证服务
make build ./bin/amd64/authn-webhook
- 创建webhook配置
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" } } ], "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/config name: webhook-config readOnly: true 添加:- hostPath: path: /etc/kubernetes/config type: DirectoryOrCreate name: webhook-config
- 获取第三方token,并添加到kubeconfig
这里使用GitHub的Personal access tokens,
https://github.com/settings/tokens
vim ~/.kube/config (- name: wgh9626 user: token: ghp_Yx45P....
- 获取pod
kubectl get po --user wgh9626
- 查看server端输出
apiserver配置
可以是任何认证系统:比如ldap,ad,keystone等。
- 在用户认证完成后,生成代表用户身份的 token;
- 该 token 通常是有失效时间的;
- 用户获取该 token 以后,将 token 配置到kubeconfig。
修改 apiserver 设置,开启认证服务,apiserver保证将所有收到的请求中的 token 信息,发给认证服务进行验证。
- –authentication-token-webhook-config-file,
该文件描述如何访问认证服务; - –authentication-token-webhook-cache-ttl,
认证的有效期时间。默认2分钟。可以设置长时间,减少外部认证服务器的压力。
配置文件需要 mount 进 Pod。
配置文件中的服务器地址需要指向 authService。
注意
当token过期时,认证插件会去renew,如果认证服务器返回了错误,插件没有设置指数级back off,而是不停的重试,会导致认证服务器down掉。
解决:
认证服务器要添加限流
apiserver端端认证插件要设置指数级等待。
鉴权机制
1.概念
鉴权主要是用于对集群资源的访问控制,哪些用户有哪些对象的操作权限。
跟认证类似,Kubernetes 也支持多种授权机制,并支持同时开启多个授权插件(只要有一个验证通过即可)。
如果授权成功,则用户的请求会发送到准入控制模块做进一步的请求验证;对于授权失败的请求则返回 HTTP 403。
仅处理以下的请求属性:
- user, group, extra
- API、请求方法(如 get、 post、 update、 patch和delete)和请求路径(如/api)
- 请求资源和子资源
- Namespace
- API Group
支持的授权插件:
- ABAC
- RBAC
- Webhook
- Node
2.RBAC
RBAC(Role Based Access Control)谁能对什么对象做什么。
和ABAC的对比
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
角色绑定(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.实践
上文中使用webhook进行认证最后报错没有权限,这里就创建一个RoleBinding,来赋予wgh9626用户权限。
cat /etc/kubernetes/config/wgh9626-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: default-admin
namespace: default
subjects:
- kind: User
name: wgh9626
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
#查看cluster-admin角色权限,可以看到是所有资源的所有权限。
k get ClusterRole cluster-admin -o yaml
#将wgh9626和cluster-admin绑定
k apply -f wgh9626-rolebinding.yaml
#获取default中的pod
k get po --user wgh9626
#删除pod
k delete po nginx-6cc88c7947-ld75n --user wgh9626
7.自动化多集群权限管控
思路
定义一个角色namespace-admin,用户创建一个namespace后,他拥有这个namespace的所有权限。这样就只需要给用户授予创建namespace的权限即可。
怎么做
在 cluster创建时,创建自定义的 role,比如 namespace-creator。
Namespace-creator role 定义用户可操作的对象和对应的读写操作。
- 创建自定义的 namespace admission controller:
- 创建mutating webhook,当apiserver收到创建namespace请求时,获取当前用户信息并给这个namespace添加annotation。
- 创建 RBAC controller:
- Watch namespace 的创建事件;
- 获取当前 namespace 的创建者信息;
- 在当前 namespace 创建 rolebinding 对象,并将 namespace-admin 角色和用户绑定。
8.注意事项
- ClusterRole 是非 namespace 绑定的,针对整个集群生效。
- ClusterRole给集群管理员,普通用户通过RoleBinding绑定。
- ThirdPartyResource 和 CustomResourceDefinition 是全局资源,普通用户创建 CRD 以后,需要管理员授予相应权限后才能真正操作该对象。
- 权限是可以传递的,用户A可以将其对某对象的某操作,抽取成一个权限,并赋给用户B;
- 防止海量的Role和RoleBinding,因为大量的对象会导致鉴权效率低,同时给 apiserver 增加负担。
- ServiceAccount 也需要授权的,否则可能无法操作某对象。
- Tips: SSH 到 master 节点通过 insecure port 访问 apiserver 可绕过鉴权,当需要做管理操作又没有权限时可以使用(不推荐)。
案例
案例1:
研发人员为提高系统效率,将 update 方法修改为 patch;
研发人员本地非安全测试环境测试通过;
上生产,发现不生效;
原因:忘记更新 rolebinding,对应的 serviceaccount 没有patch 权限。
案例2:
研发人员创建 CRD,并针对该 CRD 编程;
上生产后不生效;
原因,该 CRD 未授权,对应的组件 get 不到对应的 CRD 资源。
准入控制
什么是准入(Admission Control)?假如有一个request,有个值没有赋值,apiserver需要给一个默认值;或者request中的值是a,需要改成(变形)b;不变形或者变形后都需要判断这个值是否合法,这个过程就是准入。
不同于授权和认证只关心请求的用户和操作,准入控制还处理请求的内容,并且仅对创建、更新、删除或连接(如代理)等有效,而对读操作无效。
1.为资源增加自定义属性
作为多租户集群方案中的一环,我们需要在 namespace 的准入控制中,获取用户信息,并将用户信息更新到 namespace 的 annotation中。
只有当 namespace 中有有效用户信息时,我们才可以在namespace 创建时,自动绑定用户权限,namespace 才可用。
2.配额管理
为什么需要做配额管理,如何限定某个用户有多少资源?
因为整个集群的资源是有限的,etcd也有存储上限的,不能让每个用户都无限制的创建资源,这就需要做限制。具体实现参见下面的方案。
方案:
-
预定义每个 namespace 的 ResourceQuota,并把 spec 保存为 configmap;
-
用户可以创建多少个 Pod;
- BestEffortPod //yaml中没有声明需要多少资源
- QoSPod //yaml中声明了需要多少资源
-
用户可以创建多少个 service;
-
用户可以创建多少个 ingress;
-
用户可以创建多少个 service VIP;
-
- 创建 ResourceQuota Controller:(自动化)
- 监控 namespace 创建事件,当 namespace 创建时,在该 namespace 创建对应的 ResourceQuota对象。
- apiserver 中开启 ResourceQuota 的 admission plugin。
例子
查看当前configmap数量
k get cm
导出cm
k get cm nginx-server-block -o yaml > nginx-server-block1.yaml
修改cm中的name
vim nginx-server-block1.yaml
name: nginx-server-block1
编辑quota.yaml,指定default的ns只能创建1个cm。
vim quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: object-counts
namespace: default
spec:
hard:
configmaps: "1"
k apply -f quota.yaml
查看reourcequota
k get resourcequota object-counts -o yaml
创建新的cm,可以看到已经过了认证鉴权,创建报错,超出配额。
k create -f nginx-server-block1.yaml
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支持哪些准入控制插件
ks exec -it kube-apiserver-master -- kube-apiserver -h
4.准入控制webhook插件
如果apiserver提供的插件不能满足需求,那么就可以自定义webhook来实现。
- MutatingWebhookConfiguration:变形插件,支持对准入对象的修改。
- ValidatingWebhookConfiguration:校验插件,只能对准入对象合法性进行校验,不能修改。
实现原理
当一个request经过了认证鉴权,apiserver就会看admission的配置。如果有对应的webhook,就会发起一个AdmissionReview的请求到webhook。假如需要变形,那么会先经过MutatingWebhookConfiguration变形,再经过ValidatingWebhookConfiguration验证变形后的属性是否合法。并返回结果给apiserver,如果合法,apiserver会继续向下传递该request。
查看MutatingWebhookConfiguration
k get MutatingWebhookConfiguration cert-manager-webhook -o yaml
demo实现
整体逻辑
确保pod以非root用户运行,判断pod的runAsNonRoot 是否设置,没有的话设置true,如果设置了false改成true,并且修改用户id为1234。
部署demo
查看部署情况
查看demo-webhook的mutatingwebhookconfiguration
部署测试pod
查看pod属性
这个1234就是 MutatingWebhookConfiguration 塞进来的。
限流方法
1.计数器固定窗口算法
- 原理就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;
- 如果没有达到设定的阈值,则接受该请求,且计数加1。
- 当时间窗口结束时,重置计数器为0。
- 缺点是如果请求都集中在两个窗口之间,那么整个系统是在接受2倍的请求数量。
2.计数器滑动窗口算法
- 在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。
- 假如限流窗口是1分钟,分成了60个1秒,每1秒让窗口向后滑一格,限流永远是60秒。
- 同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。
- 缺点是每个小窗口也可能有高峰期。
3.漏斗算法
- 请求来了之后会首先进到漏斗里,然后漏斗以恒定的速率将请求流出进行处理。
- 当请求的流量过大时,漏斗达到最大容量时会溢出,此时请求被丢弃。
- 在系统看来,请求永远是以平滑的传输速率过来,从而起到了保护系统的作用。
- 缺点是无法处理突发流量,超出漏斗容量的请求就被丢弃了。
4.令牌桶算法
- 令牌桶算法是对漏斗算法的一种改进,除了能够起到限流的作用外,还允许一定程度的流量突发。
- 在令牌桶算法中,存在一个令牌桶,算法中存在一种机制以恒定的速率向令牌桶中放入令牌。
- 令牌桶也有一定的容量,如果满了令牌就无法放进去了。
- 当请求来时,会首先到令牌桶中去拿令牌,如果拿到了令牌,则该请求会被处理,并消耗掉拿到的令牌;
- 如果令牌桶为空,则该请求会被丢弃。
- 如果请求没那么多了,令牌是一直在放的,桶里面就会保存一些令牌,等突发流量过来,就能够处理一部分。
5.apiserver中的限流
- max-requests-inflight:在给定时间内的最大并发数(inflight:apiserevr已接受请求但没有处理完的请求)
- max-mutating-requests-inflight:在给定时间内的最大 mutating 请求数,即所有写操作的请求数。调整 apiserver 的流控 qos。(mutating:所有对k8s对象的修改,包括创建,删除,修改)
传统方法的局限性
- 粒度粗:无法为不同用户,不同场景设置不通的限流.
- 单队列:共享限流窗口/桶,一个坏用户可能会将整个系统堵塞,其他正常用户的请求无法被及时处理。
- 不公平:正常用户的请求会被排到队尾,无法及时处理而饿死。
- 无优先级:重要的系统指令一并被限流,系统故障难以恢复。
API Priority and Fairness
介绍
- APF 以更细粒度的方式对请求进行分类和隔离。
- 它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。
- 把整个集群分为不同的限流等级,把相近的用户的请求分到不同的等级中,不同的用户用不同的flow。同一个flow也会有不同的队列,防止一个bad的请求饿死其他请求(即使优先级相同)
- APF 的核心:
- 多等级
- 多队列
实现
- APF 的实现依赖两个非常重要的资源 FlowSchema, PriorityLevelConfiguration。
- APF 对请求进行更细粒度的分类,每一个请求分类对应一个 FlowSchema (FS)。
- FS内的请求又会根据 distinguisher 进一步划分为不同的 Flow。
- FS 会设置一个优先级 (Priority Level, PL),不同优先级的并发资源是隔离的。所以不同优先级的资源不会相互排挤。特定优先级的请求可以被高优处理。
- 一个 PL 可以对应多个 FS,PL 中维护了一个 QueueSet,用于缓存不能及时处理的请求,请求不会因为超出 PL 的并发限制而被丢弃。
- FS 中的每个 Flow 通过 shuffle sharding 算法从 QueueSet 选取特定的 queues 缓存请求。
- 每次从 QueueSet 中取请求执行时,会先应用公平排队算法从 QueueSet 中选中一个queue,然后从这个 queue 中取出 oldest 请求执行。所以即使是同一个 PL 内的请求,也不会出现一个 Flow 内的请求一直占用资源的不公平现象。
PriorityLevelConfiguration
一个 PriorityLevelConfiguration 表示单个隔离类型。
每个 PriorityLevelConfigurations 对未完成的请求数有各自的限制,对排队中的请求数也有限制。
k get prioritylevelconfiguration
k get prioritylevelconfiguration global-default -o yaml
FlowSchema
FlowSchema 匹配一些入站请求,并将它们分配给优先级。
每个入站请求都会对所有 FlowSchema 测试是否匹配,首先从 matchingPrecedence 数值最低的匹配开始,然后依次进行,直到首个匹配出现。
k get flowschema
k get flowschema global-default -o yaml
总结
- 传入的请求通过 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最多允许进几个队列。这样即使这几个坏了也不影响其他。
- 排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、公平性(当总流量超标时,各个独立的流将都会取得进展)、突发流量的容忍度以及排队引发的额外延迟之间进行权衡。
豁免请求
某些特别重要的请求不受制于此特性施加的任何限制。这些豁免可防止不当的流控配置完全禁用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:
- 优先级与特殊的 catch-all FlowSchema 结合使用,以确保每个请求都分类。
- 一般不应该依赖于 catch-all 的配置,而应适当地创建自己的 catch-all FlowSchema 和PriorityLevelConfigurations(或使用默认安装的 global-default 配置)。
-为了帮助捕获部分请求未分类的配置错误,强制要求 catch-all 优先级仅允许5个并发份额,并且不对请求进行排队,使得仅与 catch-all FlowSchema 匹配的流量被拒绝的可能性更高,并显示 HTTP 429 错误。
6.调试
/debug/api_priority_and _fairness/dump_priority_levels ——所有优先级及其当前状态的列表
kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels
/debug/api_priority_and_fairness/dump_queues ——所有队列及其当前状态的列表
kubectl get --raw /debug/api_priority_and_fairness/dump_queues
/debug /api_priority_and _fairness/dump_requests ——当前正在队列中等待的所有请求的列表
kubectl get --raw /debug/api_priority_and_fairness/dump_requests
高可用apiserver
如何构建
基础架构不是安全可靠的,实现冗余部署。apiserver 是无状态的 Rest Server,无状态所以方便扩缩容
负载均衡
- 在多个 apiserver 实例之上,配置负载均衡
- 证书可能需要加上 Loadbalancer VIP 重新生成
预留充足的 CPU、内存资源
随着集群中节点数量不断增多,API Server 对 CPU 和内存的开销也不断增大。过少的 CPU 资源会降低其处理效率,过少的内存资源会导致 Pod 被 OOMKilled,直接导致服务不可用。在规划
API Server 资源时,不能仅看当下需求,也要为未来预留充分。
善用速率限制(RateLimit)
APIServer 的参数“–max-requests-inflight" 和“–max-mutating-requests-inflight〞支持在给定时间内限制并行处理读请求(包括 Get、 List 和 Watch 操作)和写请求(包括 Create、Delete、 Update 和 Patch 操作)的最大数量。
当 APIServer 接收到的请求超过这两个参数设定的值时,再接收到的请求将会被直接拒绝。通过速率限制机制,可以有效地控制 APIServer 内存的使用。
如果该值配置过低,会经常出现请求超过限制的错误,如果配置过高,则 APIServer 可能会因为占用过多内存而被强制终止,因此需要根据实际的运行环境,结合实时用户请求数量和APIServer 的资源配置进行调优。
客户端在接收到拒绝请求的返回值后,应等待一段时间再发起重试,无间隔的重试会加重APIServer 的压力,导致性能进一步降低。
设置合适的缓存大小
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。
客户端尽量使用长连接
当查询请求的返回数据较大且此类请求并发量较大时,容易引发TCP 链路的阻塞,导致其他查询操作超时。
因此基于 Kubernetes 开发组件时,例如某些 DaemonSet 和 Controller,如果要查询某类对象,应尽量通过长连接 ListWatch 监听对象变更,避免使用轮询全量从 API Server 获取资源。
如果在同一应用程序中,如果有多个 Informer 监听 API Server 资源变化,可以将这些 Informer合并,减少和 API Server 的长连接数,从而降低对 API Server 的压力。
如何访问 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 管理:
谁能用多少资源?
- Quota 管理:
认证,授权,规划系统角色(见上文)
apimachinery
就是apiserver的运作机制。
GKV
对象的一些重要属性:typemeta:定义了这个对象是什么。
- Group:把对象根据业务场景分类
- Kind:这个对象的类型是什么(pod,secret,service等,都属于core group)
- Version:v1,v1alpha1。向前兼容。
- Internel version(面向集群内部的,就是存在etcd中的版本) 和 External version(面向集群外部的)
- 版本转换:任何 External version发送到apiserver的请求在存到etcd之前,都会转成 Internel version。双向转换。
参考链接
https://github.com/kubernetes/apimachinery
https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery