目录 - 认证 - 鉴权 - 准入 Mutating Validating Admission - 限流 - APIServer对象的实现
(相关资料图)
kube-apiserver是Kubernetes最重要的核心组件之一,主要提供以下的功能
Kubernetes API的每个请求都会经过多阶段的访问控制之后才会被接受,这包括认证、授权以及 准入控制(Admission Control)等。
开启TLS时,所有的请求都需要首先认证。Kubernetes支持多种认证机制,并支持同时
开启多个认证插件(只要有一个认证通过即可)。如果认证成功,则用户的username会
传入授权模块做进一步授权验证;而对于认证失败的请求则返回HTTP 401。
https://github.com/appscode/guard
需要依照Kubernetes规范,构建认证服务,用来认证tokenreviewrequest
构建认证服务
{ "apiVersion": "authentication.k8s.io/v1beta1", "kind": "TokenReview","spec": { "token": "(BEARERTOKEN)" } } { "apiVersion": "authentication.k8s.io/v1beta1", "kind": "TokenReview", "status": { "authenticated": true, "user": { "username": "janedoe@example.com", "uid": "42", "groups": [ "developers", "qa" ]}} }
解码认证请求
decoder := json.NewDecoder(r.Body)var tr authentication.TokenReviewerr := 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}
转发认证请求至认证服务器
//CheckUserts:=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(),"")iferr!=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}
认证结果返回给APIServer
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, })
{ "kind": "Config", "apiVersion": "v1", "preferences": {}, "clusters": [ { "name": "github-authn", "cluster": { "server": "http://localhost:3000/authenticate" } }], "users": [ { "name": "authn-apiserver", "user": { "token": "secret" } }], "contexts": [ { "name": "webhook", "context": { "cluster": "github-authn", "user": "authn-apiserver" } }], "current-context": "webhook"}
可以是任何认证系统
修改apiserver设置,开启认证服务,apiserver保证将所有收到的 请求中的token信息,发给认证服务进行验证
配置文件需要mount进Pod
配置文件中的服务器地址需要指向authService
{"kind": "Config","apiVersion": "v1","preferences": {},"clusters": [{ "name": "github-authn", "cluster": { "server": "http://localhost:3000/authenticate" }}],"users": [{"name": "authn-apiserver","user": { "token": "secret" }}],"contexts": [{"name": "webhook","context": { "cluster": "github-authn", "user": "authn-apiserver" }}],"current-context": "webhook"}
基于Keystone的认证插件导致Keystone故障且无法恢复Keystone是企业关键服务Kubernetes以Keystone作为认证插件Keystone在出现故障后会抛出 401 错误Kubernetes发现 401 错误后会尝试重新认证大多数controller都有指数级backoff,重试间隔越来越慢但gophercloud针对过期token会一直retry大量的request积压在Keystone导致服务无法恢复Kubernetes成为压死企业认证服务的最后一根稻草
解决方案?授权主要是用于对集群资源的访问控制,通过检查请求包含的相关属性值,与相对应的访问策略相比较,API请求必须满足某 些策略才能被处理。跟认证类似,Kubernetes也支持多种授权机制,并支持同时开启多个授权插件(只要有一个验证通过即 可)。如果授权成功,则用户的请求会发送到准入控制模块做进一步的请求验证;对于授权失败的请求则返回HTTP 403。
Kubernetes授权仅处理以下的请求属性:
目前,Kubernetes支持以下授权插件:
ABAC(Attribute Based Access Control)本来是不错的概念,但是在Kubernetes 中的实现比较 难于管理和理解,而且需要对Master 所在节点的SSH 和文件系统权限,要使得对授权的变更成 功生效,还需要重新启动API Server。
而RBAC 的授权策略可以利用kubectl 或者Kubernetes API 直接进行配置。RBAC 可以授权给用户, 让用户有权进行授权管理,这样就可以无需接触节点,直接进行授权管理。RBAC 在Kubernetes 中被映射为API 资源和操作。
Role(角色)是一系列权限的集合,例如一个角色可以包含读取Pod 的权限和列出Pod 的权限。 Role只能用来给某个特定namespace中的资源作鉴权,对多namespace和集群级的资源或者是非 资源类的API(如/healthz)使用ClusterRole。
# Role示例kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata: namespace: default name: pod-readerrules:- apiGroups: [""] # "" indicates the core API group resources: ["pods"] verbs: ["get", "watch", "list"]# ClusterRole示例kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1metadata:# "namespace" omitted since ClusterRoles are not namespaced name: secret-readerrules:- apiGroups: [""] resources: ["secrets"] verbs: ["get", "watch", "list"]
# RoleBinding示例(引用ClusterRole)# This role binding allows "dave" to read secrets in the "development"namespace.kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:name: read-secretsnamespace: development # This only grants permissions within the"development" namespace.subjects:- kind: User name: dave apiGroup: rbac.authorization.k8s.ioroleRef: kind: ClusterRole name: secret-reader apiGroup: rbac.authorization.k8s.io
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。
它包含若干主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。
组的概念:
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: read-secrets-globalsubjects: - kind: Group name: manager # "name" 是区分大小写的 apiGroup: rbac.authorization.k8s.ioroleRef: kind: ClusterRole name: secret-reader apiGroup: rbac.authorization.k8s.ioapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: read-secrets-globalsubjects: - kind: Group name: system:serviceaccounts:qa apiGroup: rbac.authorization.k8s.ioroleRef: kind: ClusterRole name: secret-reader apiGroup: rbac.authorization.k8s.io
User
SystemAccount
在cluster创建时,创建自定义的role,比如namespace-creator
Namespace-creatorrole定义用户可操作的对象和对应的读写操作。
创建自定义的namespaceadmissioncontroller
创建RBAC controller
ClusterRole是非namespace绑定的,针对整个集群生效
通常需要创建一个管理员角色,并且绑定给开发运营团队成员
ThirdPartyResource和CustomResourceDefinition是全局资源,普通用户创建 ThirdPartyResource以后,需要管理员授予相应权限后才能真正操作该对象
针对所有的角色管理,建议创建spec,用源代码驱动
权限是可以传递的,用户A可以将其对某对象的某操作,抽取成一个权限,并赋给用户B
防止海量的角色和角色绑定对象,因为大量的对象会导致鉴权效率低,同时给apiserver增加负担
ServiceAccount也需要授权的,否则你的component可能无法操作某对象
Tips:SSH到master节点通过insecureport访问apiserver可绕过鉴权,当需要做管理操作又没 有权限时可以使用(不推荐)
案例1:
案例2:
为资源增加自定义属性
只有当namespace中有有效用户信息时,我们才可以在namespace创建时,自动绑定用户权限, namespace才可用。
准入控制(Admission Control)在授权后对请求做进一步的验证或添加默认参数。不同于授权 和认证只关心请求的用户和操作,准入控制还处理请求的内容,并且仅对创建、更新、删除或连 接(如代理)等有效,而对读操作无效。
准入控制支持同时开启多个插件,它们依次调用,只有全部插件都通过的请求才可以放过进入系 统。
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中创建一个LimitRange对 象
InitialResources:根据镜像的历史使用记录,为容器设置默认资源请求和限制
NamespaceLifecycle:确保处于termination状态的namespace不再接收新的对象创建请求, 并拒绝请求不存在的namespace
DefaultStorageClass:为PVC设置默认StorageClass
DefaultTolerationSeconds:设置Pod的默认forgivenesstoleration为 5 分钟
PodSecurityPolicy:使用PodSecurity Policies时必须开启
NodeRestriction:限制kubelet仅可访问node、endpoint、pod、service以及secret、 configmap、PV和PVC等相关的资源
除默认的准入控制插件以外,Kubernetes预留了准入控制插件的扩展点,用户可自定义准入控制 插件实现自定义准入功能
MutatingWebhookConfiguration:变形插件,支持对准入对象的修改
ValidatingWebhookConfiguration:校验插件,只能对准入对象合法性进行校验,不能修改
# {{if eq .k8snode_validating "enabled"}}apiVersion: admissionregistration.k8s.io/v1beta1kind: MutatingWebhookConfigurationmetadata: name: ns-mutating.webhook.k8s.iowebhooks: - clientConfig: caBundle: {{.serverca_base64}} url: https://admission.local.tess.io/apis/admission.k8s.io/v1alpha1/ ns-mutating failurePolicy: Fail name: ns-mutating.webhook.k8s.io namespaceSelector: {}rules:- apiGroups: - "" apiVersions: - "*" operations: - CREATE resources: - nodessideEffects: Unknown# {{end}}
准入控制
配额管理
方案:
原理就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;
如果没有达到设定的阈值,则接受该请求,且计数加 1 。
当时间窗口结束时,重置计数器为 0 。
在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计 数器。
当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。
平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面 新增一个小窗口,将新的请求放在新增的小窗口中。
同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。
漏斗算法的原理也很容易理解。请求来了之后会首先进到漏斗里,然后漏斗以恒定的速率将请求 流出进行处理,从而起到平滑流量的作用。
当请求的流量过大时,漏斗达到最大容量时会溢出,此时请求被丢弃。
在系统看来,请求永远是以平滑的传输速率过来,从而起到了保护系统的作用。
令牌桶算法是对漏斗算法的一种改进,除了能够起到限流的作用外,还允许一定程度的流量突发。
在令牌桶算法中,存在一个令牌桶,算法中存在一种机制以恒定的速率向令牌桶中放入令牌。
令牌桶也有一定的容量,如果满了令牌就无法放进去了。
当请求来时,会首先到令牌桶中去拿令牌,如果拿到了令牌,则该请求会被处理,并消耗掉拿到 的令牌;
如果令牌桶为空,则该请求会被丢弃。
max-requests-inflight:在给定时间内的最大non-mutating 请求数
max-mutating-requests-inflight:在给定时间内的最大mutating 请求数,调整apiserver的 流控qos
代码 staging/src/k8s.io/apiserver/pkg/server/filters/maxinflight.go:WithMaxInFlightLimit()
某些特别重要的请求不受制于此特性施加的任何限制。这些豁免可防止不当的流控配置完全禁用 API 服务器。
一个PriorityLevelConfiguration表示单个隔离类型。 每个PriorityLevelConfigurations对未完成的请求数有各自的限制,对排队中的请求数也有限制。
FlowSchema匹配一些入站请求,并将它们分配给优先级。
每个入站请求都会对所有FlowSchema测试是否匹配,首先从matchingPrecedence数值最低的匹配开始 (我们认为这是逻辑上匹配度最高),然后依次进行,直到首个匹配出现
kubectlget --raw /debug/api_priority_and_fairness/dump_priority_levels
kubectlget --raw /debug/api_priority_and_fairness/dump_queues
kubectlget --raw /debug/api_priority_and_fairness/dump_requests
kube-apiserver--feature-gates=AllAlpha=true --runtime-config=api/all=true \--requestheader-allowed-names=front-proxy-client \--client-ca-file=/etc/kubernetes/pki/ca.crt \--allow-privileged=true \--experimental-bootstrap-token-auth=true \--storage-backend=etcd3 \--requestheader-username-headers=X-Remote-User \--requestheader-extra-headers-prefix=X-Remote-Extra-\--service-account-key-file=/etc/kubernetes/pki/sa.pub \--tls-cert-file=/etc/kubernetes/pki/apiserver.crt \--tls-private-key-file=/etc/kubernetes/pki/apiserver.key\--kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt \--enabled-hooks=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota \--requestheader-group-headers=X-Remote-Group \--kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key\--secure-port=6443 \--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname\--service-cluster-ip-range=10.96.0.0/12 \--advertise-address=192.168.0.20 --etcd-servers=http://127.0.0.1:2379
apiserver是无状态的RestServer
无状态所以方便ScaleUp/down
负载均衡
随着集群中节点数量不断增多,APIServer对CPU和内存的开销也不断增大。过少的CPU资源会降 低其处理效率,过少的内存资源会导致Pod被OOMKilled,直接导致服务不可用。在规划 APIServer资源时,不能仅看当下需求,也要为未来预留充分。
APIServer的参数“--max-requests-inflight”和“--max-mutating-requests-inflight”支持 在给定时间内限制并行处理读请求(包括Get、List和Watch操作)和写请求(包括Create、 Delete、Update和Patch操作)的最大数量。当APIServer接收到的请求超过这两个参数设定的 值时,再接收到的请求将会被直接拒绝。通过速率限制机制,可以有效地控制APIServer内存的使 用。如果该值配置过低,会经常出现请求超过限制的错误,如果配置过高,则APIServer可能会因 为占用过多内存而被强制终止,因此需要根据实际的运行环境,结合实时用户请求数量和 APIServer的资源配置进行调优。
客户端在接收到拒绝请求的返回值后,应等待一段时间再发起重试,无间隔的重试会加重 APIServer的压力,导致性能进一步降低。针对并行处理请求数的过滤颗粒度太大,在请求数量比 较多的场景,重要的消息可能会被拒绝掉,自1.18版本开始,社区引入了优先级和公平保证 (Priority and Fairness)功能,以提供更细粒度地客户端请求控制。该功能支持将不同用户或 不同类型的请求进行优先级归类,保证高优先级的请求总是能够更快得到处理,从而不受低优先 级请求的影响。
APIServer与etcd之间基于gRPC协议进行通信,gRPC协议保证了二者在大规模集群中的数据高速 传输。gRPC基于连接复用的HTTP/2协议,即针对相同分组的对象,APIServer和etcd之间共享相 同的TCP连接,不同请求由不同的stream传输。
一个HTTP/2连接有其stream配额,配额的大小限制了能支持的并发请求。APIServer提供了集 群对象的缓存机制,当客户端发起查询请求时,APIServer默认会将其缓存直接返回给客户端。缓 存区大小可以通过参数“--watch-cache-sizes”设置。针对访问请求比较多的对象,适当设置 缓存的大小,极大降低对etcd的访问频率,节省了网络调用,降低了对etcd集群的读写压力,从 而提高对象访问的性能。
但是APIServer也是允许客户端忽略缓存的,例如客户端请求中ListOption中没有设置 resourceVersion,这时APIServer直接从etcd拉取最新数据返回给客户端。客户端应尽量避免此 操作,应在ListOption中设置resourceVersion为 0 ,APIServer则将从缓存里面读取数据,而不 会直接访问etcd。
当查询请求的返回数据较大且此类请求并发量较大时,容易引发TCP链路的阻塞,导致其他查询 操作超时。因此基于Kubernetes开发组件时,例如某些DaemonSet和Controller,如果要查询 某类对象,应尽量通过长连接ListWatch监听对象变更,避免全量从APIServer获取资源。如果在 同一应用程序中,如果有多个Informer监听APIServer资源变化,可以将这些Informer合并,减 少和APIServer的长连接数,从而降低对APIServer的压力。
对外部客户(user/client/admin),永远只通过LoadBalancer访问
只有当负载均衡出现故障时,管理员才切换到apiserverIP进行管理
内部客户端,优先访问cluster IP?(是否一定如此?)
授信
隔离
资源管理
与企业现有认证系统集成
选择认证插件 ➢ 选择webhook作为认证插件(*以此为例展开) ➢ 也可以选择Keystone作为认证插件,以MicrosoftAd作为backend搭建keystone服务
一旦认证完成,Kubernetes即可获取当前用户信息(主要是用户名),并针对该用户做授权。授权和准入控 制完成后,该用户的请求完成。
apiVersion: apiregistration.k8s.io/v1kind: APIServicemetadata:labels: kube-aggregator.kubernetes.io/automanaged: onstartname: v1.spec: groupPriorityMinimum: 18000 version: v1 versionPriority: 1status: conditions: - lastTransitionTime: "2020- 08 - 16T05:35:33Z" message: Local APIServices are always available reason: Local status: "True" type: Available
ABAC有期局限性,针对每个account都需要做配置,并且需要重启apiserver
RBAC更灵活,更符合我们通常熟知的权限管理
User
SystemAccount
在cluster创建时(kube-up.sh),创建自定义的role,比如namespace-creator
namespace-creatorrole定义用户可操作的对象和对应的读写操作。
创建自定义的namespaceadmissioncontroller
创建RBAC controller
ClusterRole是非namespace绑定的,针对整个集群生效
通常需要创建一个管理员角色,并且绑定给开发运营团队成员
ThirdPartyResource和CustomResourceDefinition是全局资源,普通用户创建ThirdPartyResource以后, 需要管理员授予相应权限后才能真正操作该对象
针对所有的角色管理,建议创建spec,用源代码驱动
权限是可以传递的,用户A可以将其对某对象的某操作,抽取成一个权限,并赋给用户B
防止海量的角色和角色绑定对象,因为大量的对象会导致鉴权效率低,同时给apiserver增加负担
ServiceAccount也需要授权的,否则你的component可能无法操作某对象
Tips:SSH到master节点通过insecureport访问apiserver可绕过鉴权,当需要做管理操作又没有权限时可以 使用(不推荐)
https://github.com/kubernetes/apimachineryhttps://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery->.
Group
Kind
Version
pkg/apis/core/register.go 定义group GroupName 定义groupversion
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version:runtime.APIVersionInternal}
定义SchemeBuildervar (SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)AddToScheme = SchemeBuilder.AddToScheme)
将对象加入SchemeBuildfunc addKnownTypes(scheme *runtime.Scheme) error {if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil {return err}scheme.AddKnownTypes(SchemeGroupVersion,&Pod{},&PodList{},}}
List
单一对象数据结构
GlobalTags
LocalTags
pkg/registry/core/configmap/storage/storage.go
funcNewREST(optsGetter generic.RESTOptionsGetter) *REST {store := &genericregistry.Store{NewFunc: func() runtime.Object { return&api.ConfigMap{} },NewListFunc: func() runtime.Object { return&api.ConfigMapList{} },DefaultQualifiedResource: api.Resource("configmaps"),CreateStrategy: configmap.Strategy,UpdateStrategy: configmap.Strategy,DeleteStrategy: configmap.Strategy,TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},}options := &generic.StoreOptions{RESTOptions: optsGetter}if err := store.CompleteWithOptions(options); err != nil {panic(err) // TODO: Propagate error up}return &REST{store}}
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {_ = obj.(*api.ConfigMap)}func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {cfg := obj.(*api.ConfigMap)return validation.ValidateConfigMap(cfg)}func (strategy) PrepareForUpdate(ctx context.Context, newObj, oldObj runtime.Object) {_ = oldObj.(*api.ConfigMap)_ = newObj.(*api.ConfigMap)}
什么是subresource,内嵌在kubernetes对象中,有独立的操作逻辑的属性集合,如podstatus
statusStore.UpdateStrategy = pod.StatusStrategyvar StatusStrategy = podStatusStrategy{Strategy}func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object){newPod := obj.(*api.Pod)oldPod := old.(*api.Pod)newPod.Spec = oldPod.SpecnewPod.DeletionTimestamp = nil// don"t allow the pods/status endpoint to touch owner references since oldkubelets corrupt them in a way// that breaks garbage collectionnewPod.OwnerReferences = oldPod.OwnerReferences}
定义Storage
configMapStorage := configmapstore.NewREST(restOptionsGetter)restStorageMap := map[string]rest.Storage{"configMaps": configMapStorage,}
定义对象的StorageMap
apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap
将对象注册至APIServer(挂载handler)if err :=m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix,&apiGroupInfo); err != nil {klog.Fatalf("Error in registering group versions: %v", err)}
deepcopy-gen
- 为对象生成DeepCopy方法,用于创建对象副本 client-gen - 创建Clientset,用于操作对象的CRUD informer-gen - 为对象创建Informer框架,用于监听对象变化 lister-gen - 为对象构建Lister框架,用于为Get和List操作,构建客户端缓存 coversion-gen - 为对象构建Conversion方法,用于内外版本转换以及不同版本号的转换https://github.com/kubernetes/code-generator^83
依赖
BUILD_TARGETS=( vendor/k8s.io/code-generator/cmd/client-gen vendor/k8s.io/code-generator/cmd/lister-gen vendor/k8s.io/code-generator/cmd/informer-gen )
生成命令
${GOPATH}/bin/deepcopy-gen --input-dirs{versioned-package-pach}
https://cncamp.notion.site/kube-apiserver-10d5695cbbb14387b60c6d622005583d
标签: