X509证书
静态Token文件
引导Token
静态密码文件
需要API Server启动时配置–basic-auth-file=SOMEFILE,文件格式为csv,每行至少三列password, user, uid,后面是可选的group名
ServiceAccount
OpenID
Webhook 令牌身份认证
匿名请求
{ "apiVersion": "authentication.k8s.io/v1beta1", "kind": "TokenReview",
"spec": { "token": "(BEARERTOKEN)" } }
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "[email protected]",
"uid": "42",
"groups": [
"developers",
"qa"
]}}
}
可以是任何认证系统
• 但在用户认证完成后,生成代表用户身份的token
• 该token通常是有失效时间的
• 用户获取该token以后以后,将token配置进kubeconfig
修改apiserver设置,开启认证服务,apiserver保证将所有收到的请求中的token信息,发给认证服务进行验证
token信息,发给认证服务进行验证
• --authentication-token-webhook-config-file,该文件描述如何访问认证服务
• --authentication-token-webhook-cache-ttl,默认2分钟
配置文件需要mount进Pod
配置文件中的服务器地址需要指向authService
基于Keystone的认证插件导致Keystone故障且无法恢复
Keystone是企业关键服务
Kubernetes以Keystone作为认证插件
Keystone在出现故障后会抛出401错误
Kubernetes发现401错误后会尝试重新认证
大多数controller都有指数级back off,重试间隔越来越慢
但gophercloud针对过期token会一直retry
大量的request积压在Keystone导致服务无法恢复
Kubernetes成为压死企业认证服务的最后一根稻草
解决方案
• Circuit break
• Rate limit
# Role示例
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
# ClusterRole示例
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# "namespace" omitted since ClusterRoles
are not namespaced
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
# RoleBinding示例(引用ClusterRole)
# This role binding allows "dave" to read secrets in the "development"
namespace.
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-secrets
namespace: development # This only grants permissions within the
"development" namespace.
subjects:
- kind: User
name: dave
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
subjects:
- kind: Group
name: system:serviceaccounts:qa
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io`
为资源增加自定义属性
准入控制(Admission Control)在授权后对请求做进一步的验证或添加默认参数。不同于授权和认证只关心请求的用户和操作,准入控制还处理请求的内容,并且仅对创建、更新、删除或连接(如代理)等有效,而对读操作无效
准入控制支持同时开启多个插件,它们依次调用,只有全部插件都通过的请求才可以放过进入系统
准入控制插件
# {{if eq .k8snode_validating "enabled"}}
apiVersion:
admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: ns-mutating.webhook.k8s.io
webhooks:
- clientConfig:
caBundle: {{.serverca_base64}}
url:
https://admission.local.tess.io/apis/admissio
n.k8s.io/v1alpha1/ ns-mutating
failurePolicy: Fail
name: ns-mutating.webhook.k8s.io
namespaceSelector: {}
rules:
- apiGroups:
- ""
apiVersions:
- '*'
operations:
- CREATE
resources:
- nodes
sideEffects: Unknown
# {{end}}
• APF 以更细粒度的方式对请求进行分类和隔离。
• 它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。
• 通过使用公平排队技术从队列中分发请求,这样, 一个行为不佳的控制器就不会饿死其他控制
器(即使优先级相同)。
• 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 中取请求执行时,会先应用 fair queuing 算法从 QueueSet 中选中一个 queue,然后从这个 queue
中取出 oldest 请求执行。所以即使是同一个 PL 内的请求,也不会出现一个 Flow 内的请求一直占用资源的不公平现象
• 传入的请求通过FlowSchema 按照其属性分类,并分配优先级。
• 每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。
• 在同一个优先级内,公平排队算法可以防止来自不同flow 的请求相互饿死。
• 该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败
• 如果未启用 APF,API 服务器中的整体并发量将受到 kube-apiserver 的参数 --max-requests-inflight 和 --max-mutating-requests-inflight 的限制。
• 启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的优先级 中。 每个传入的请求都会分配一个优先级;
• 每个优先级都有各自的配置,设定允许分发的并发请求数。
• 例如,默认配置包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。这表示即使异常的 Pod 向 API 服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功
• 即使在同一优先级内,也可能存在大量不同的流量源。
• 在过载情况下,防止一个请求流饿死其他流是非常有价值的 (尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向 kube-apiserver 发送请求, 理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。
• 公平排队算法在处理具有相同优先级的请求时,实现了上述场景。
• 每个请求都被分配到某个 流 中,该流由对应的 FlowSchema 的名字加上一个 流区分项(FlowDistinguisher) 来标识。
• 这里的流区分项可以是发出请求的用户、目标资源的名称空间或什么都不是。
• 系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。
• 将请求划分到流中之后,APF 功能将请求分配到队列中。
• 分配时使用一种称为 混洗分片(Shuffle-Sharding) 的技术。 该技术可以相对有效地利用队列隔离低强度流与高强流。
• 排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、 公平性(当总流量超标时,各个独立的流将都会取得进展)、 突发流量的容忍度以及排队引发的额外延迟之间进行权衡
system
leader-election
workload-high
• 优先级用于内置控制器的请求。
workload-low
• 优先级适用于来自任何服务帐户的请求,通常包括来自 Pods 中运行的控制器的所有请求。
global-default
• 优先级可处理所有其他流量,例如:非特权用户运行的交互式 kubectl 命令。
exempt
catch-all
apiVersion:
flowcontrol.apiserver.k8s.io/v1beta1
kind: PriorityLevelConfiguration
metadata:
name: global-default
spec:
limited:
assuredConcurrencyShares: 20
limitResponse:
queuing:
handSize: 6
queueLengthLimit: 50
queues: 128
type: Queue
type: Limited
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: FlowSchema
metadata:
name: kube-scheduler
spec:
distinguisherMethod:
type: ByNamespace
matchingPrecedence: 800
priorityLevelConfiguration:
name: workload-high
rules:
- resourceRules:
- resources:
- '*'
verbs:
- '*'
subjects:
- kind: User
user:
name: system:kube-scheduler
• /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 ——当前正在队列中等待的所有请求
的列表
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对CPU和内存的开销也不断增大。过少的CPU资源会降低其处理效率,过少的内存资源会导致Pod被OOMKilled,直接导致服务不可用。在规划APIServer资源时,不能仅看当下需求,也要为未来预留充分
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
kube-aggregator.kubernetes.io/automanaged: onstart
name: v1.
spec:
groupPriorityMinimum: 18000
version: v1
versionPriority: 1
status:
conditions:
- lastTransitionTime: "2020-08-16T05:35:33Z"
message: Local APIServices are always available
reason: Local
status: "True"
type: Available
pkg/apis/core/register.go
定义group
GroupName
定义groupversion
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version:
runtime.APIVersionInternal}
定义SchemeBuilder
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
将对象加入SchemeBuild
func addKnownTypes(scheme *runtime.Scheme) error {
if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil {
return err
}
scheme.AddKnownTypes(SchemeGroupVersion,
&Pod{},
&PodList{},
}}
Global Tags
Local Tags:
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient
// +genclient:nonNamespaced
// +genclient:noVerbs
// +genclient:onlyVerbs=create,delete
//
+genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watc
h
//
+genclient:method=Create,verb=create,result=k8s.io/apimachinery/pkg/apis/
meta/v1.Status
package storage
import (
"github.com/kubernetes/kubernetes/pkg/api"
"github.com/kubernetes/kubernetes/pkg/apis/configmap"
"github.com/kubernetes/kubernetes/pkg/genericregistry"
"github.com/kubernetes/kubernetes/pkg/printers"
printersinternal "github.com/kubernetes/kubernetes/pkg/printers/internalversion"
"github.com/kubernetes/kubernetes/pkg/runtime"
"github.com/kubernetes/kubernetes/pkg/storage"
"github.com/kubernetes/kubernetes/pkg/storage/printerstorage"
)
func NewREST(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)
}
statusStore.UpdateStrategy = pod.StatusStrategy
var StatusStrategy = podStatusStrategy{Strategy}
func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPod := obj.(*api.Pod)
oldPod := old.(*api.Pod)
newPod.Spec = oldPod.Spec
newPod.DeletionTimestamp = nil
// don't allow the pods/status endpoint to touch owner references since old kubelets corrupt them in a way
// that breaks garbage collection
newPod.OwnerReferences = oldPod.OwnerReferences
}
configMapStorage := configmapstore.NewREST(restOptionsGetter)
restStorageMap := map[string]rest.Storage{
"configMaps": configMapStorage,
}
apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap
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方法,用于内外版本转换以及不同版本号的转换