什么是admission 在kube-apiserver的参数中,有如下参数:--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,ResourceQuota 这个参数表示的就是admission。来看下官方文档对admission的定义:”An admission control plug-in is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized.”,即admission的plugin会在认证和授权后对请求进行检查。admission会使用每一个plugin对请求进行检查,一旦有一个不通过,则拒绝该请求。目前admission主要有如下plugin:
AlwaysAdmit: 允许所有的请求通过; 
AlwaysDeny: 拒绝所有的请求; 
DenyEscalatingExec: 拒绝执行”系统命令”的请求; 
ImagePolicyWebhook: 镜像下载相关,没有进行详细研究; 
ServiceAccount: ServiceAccount会在生成的容器中注入安全相关的信息以访问Kubernetes集群; 
SecurityContextDeny: PodSecurityPolicy相关,PodSecurityPolicy可以对容器进行各种限制; 
ResourceQuota: ResourceQuota可以对用户命名空间下的配额进行检查; 
LimitRanger: 对Pods申请的资源的大小进行限制; 
NamespaceLifecycle: NamespaceLifecycle会拒绝在正在删除中的Namespace下创建资源的请求。 
 
所以,本次分析将着重于admission框架的实现。
注册 来看下plugin是如何注册的,在/cmd/kube-apiserver/app/plugins.go中有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import  (
	
	_ "k8s.io/kubernetes/pkg/cloudprovider/providers" 
	
	_ "k8s.io/kubernetes/plugin/pkg/admission/admit" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/deny" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/exec" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/gc" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/initialresources" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/limitranger" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/resourcequota" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" 
	_ "k8s.io/kubernetes/plugin/pkg/admission/storageclass/default" 
)
 
Go语言在引入包时会自动执行包的init()方法,以resourcequota为例来看下init()方法,定义在/plugin/pkg/admission/resourcequota/admission.go中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func  init ()   {
	
	admission.RegisterPlugin("ResourceQuota" ,
		func (client clientset.Interface, config io.Reader)  (admission.Interface, error)   {
			
			
			registry := install.NewRegistry(client, nil )
			return  NewResourceQuota(client, registry, 5 , make (chan  struct {}))
		})
}
func  NewResourceQuota (client clientset.Interface, registry quota.Registry, numEvaluators int , stopCh <-chan  struct {})  (admission.Interface, error)   {
	quotaAccessor, err := newQuotaAccessor(client)
	if  err != nil  {
		return  nil , err
	}
	go  quotaAccessor.Run(stopCh)
	evaluator := NewQuotaEvaluator(quotaAccessor, registry, nil , numEvaluators, stopCh)
	return  "aAdmission{
		Handler:   admission.NewHandler(admission.Create, admission.Update),
		evaluator: evaluator,
	}, nil 
}
其中,还需要注意的是,每个plugin都只针对一定的操作进行检查,如resourcequota只会检查Create和Update的请求。操作的管理通过Handler进行。
所以,plugin初始化的过程就是向admission的plugins变量中注册plugin的名称和初始化函数。现在,只需要记着admission的plugins注册着plugin名称及其初始始化函数即可。
plugins 我们来看admission的plugins,定义在/pkg/admission/plugins.go中:1
2
3
4
5
6
7
8
9
10
11
var  (
	pluginsMutex sync.Mutex
	
	plugins = make (map [string ]Factory)
	
	PluginEnabledFn = func (name string , config io.Reader)  bool   {
		return  true 
	}
)
plugins.go中还定义有如下函数:
GetPlugins() GetPlugins()中可以返回在plugins中注册的所有plugin的名称。1
2
3
4
5
6
7
8
9
10
11
func  GetPlugins ()  []string   {
	pluginsMutex.Lock()
	defer  pluginsMutex.Unlock()
	keys := []string {}
	for  k := range  plugins {
		keys = append (keys, k)
	}
	sort.Strings(keys)
	return  keys
}
RegisterPlugin() RegisterPlugin()提供了向plugins注册的方法,即plugin通过调用RegisterPlugins()向plugins注册。1
2
3
4
5
6
7
8
9
10
func  RegisterPlugin (name string , plugin Factory)   {
	pluginsMutex.Lock()
	defer  pluginsMutex.Unlock()
	_, found := plugins[name]
	if  found {
		glog.Fatalf("Admission plugin %q was registered twice" , name)
	}
	glog.V(1 ).Infof("Registered admission plugin %q" , name)
	plugins[name] = plugin
}
getPlugin() getPlugin()可以从plugins中获取指定plugin的初始化函数,并创建该plugin。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func  getPlugin (name string , client clientset.Interface, config io.Reader)  (Interface, bool , error)   {
	pluginsMutex.Lock()
	defer  pluginsMutex.Unlock()
	f, found := plugins[name]
	if  !found {
		return  nil , false , nil 
	}
	config1, config2, err := splitStream(config)
	if  err != nil  {
		return  nil , true , err
	}
	if  !PluginEnabledFn(name, config1) {
		return  nil , true , nil 
	}
	ret, err := f(client, config2)
	return  ret, true , err
}
InitPlugin() InitPlugin()通过getPlugin()获取plugin,并返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func  InitPlugin (name string , client clientset.Interface, configFilePath string )  Interface   {
	var  (
		config *os.File
		err    error
	)
	if  name == ""  {
		glog.Info("No admission plugin specified." )
		return  nil 
	}
	if  configFilePath != ""  {
		config, err = os.Open(configFilePath)
		if  err != nil  {
			glog.Fatalf("Couldn't open admission plugin configuration %s: %#v" ,
				configFilePath, err)
		}
		defer  config.Close()
	}
	plugin, found, err := getPlugin(name, client, config)
	if  err != nil  {
		glog.Fatalf("Couldn't init admission plugin %q: %v" , name, err)
	}
	if  !found {
		glog.Fatalf("Unknown admission plugin: %s" , name)
	}
	return  plugin
}
handler handler可以判断该plugin是否需要处理某请求。admission的handler定义在/pkg/admission/handler.go中:1
2
3
4
type  Handler struct  {
	operations sets.String
	readyFunc  ReadyFunc
}
其中:
operations: 为操作的集合; 
readyFunc: 标识该plugin是否做好处理请求的准备。 
 
Handles() Handles()可以判断请求的动作是否在plugin的处理动作列表中。1
2
3
func  (h *Handler)  Handles (operation Operation)  bool   {
	return  h.operations.Has(string (operation))
}
NewHandler() NewHandler()可以生成一个新的handler,但只能设置operations。1
2
3
4
5
6
7
8
9
func  NewHandler (ops ...Operation)  *Handler   {
	operations := sets.NewString()
	for  _, op := range  ops {
		operations.Insert(string (op))
	}
	return  &Handler{
		operations: operations,
	}
}
SetReadyFunc() SetReadyFunc()可以设置handler的readyFunc字段。1
2
3
func  (h *Handler)  SetReadyFunc (readyFunc ReadyFunc)   {
	h.readyFunc = readyFunc
}
WaitForReady() WaitForReady()可以判断该plugin是否已经完成初始化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func  (h *Handler)  WaitForReady ()  bool   {
	
	if  h.readyFunc == nil  {
		return  true 
	}
	return  h.waitForReadyInternal(time.After(timeToWaitForReady))
}
func  (h *Handler)  waitForReadyInternal (timeout <-chan  time.Time)  bool   {
	
	if  h.readyFunc == nil  {
		return  true 
	}
	for  !h.readyFunc() {
		select  {
		case  <-time.After(100  * time.Millisecond):
		case  <-timeout:
			return  h.readyFunc()
		}
	}
	return  true 
}
chainAdmissionHandler chainAdmissionHandler定义在/pkg/admission/chain.go中,是admission的入口:1
2
type  chainAdmissionHandler []Interface
NewFromPlugins() NewFromPlugins()可以依据plugins生成chainAdmissionHandler。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func  NewFromPlugins (client clientset.Interface, pluginNames []string , configFilePath string , plugInit PluginInitializer)  (Interface, error)   {
	plugins := []Interface{}
	for  _, pluginName := range  pluginNames {
		plugin := InitPlugin(pluginName, client, configFilePath)
		if  plugin != nil  {
			plugins = append (plugins, plugin)
		}
	}
	plugInit.Initialize(plugins)
	
	if  err := Validate(plugins); err != nil  {
		return  nil , err
	}
	
	return  chainAdmissionHandler(plugins), nil 
}
Admit() Admit()使用每个plugin对请求进行检查,如果某plugin不能处理该请求的动作,则直接略过。每个plugin都实现有Admit()方法。1
2
3
4
5
6
7
8
9
10
11
12
13
func  (admissionHandler chainAdmissionHandler)  Admit (a Attributes)  error   {
	for  _, handler := range  admissionHandler {
		if  !handler.Handles(a.GetOperation()) {
			continue 
		}
		err := handler.Admit(a)
		if  err != nil  {
			return  err
		}
	}
	return  nil 
}
Handles() Handles()判断chainAdmissionHandler中是否存在plugin可以处理该请求。1
2
3
4
5
6
7
8
9
func  (admissionHandler chainAdmissionHandler)  Handles (operation Operation)  bool   {
	for  _, handler := range  admissionHandler {
		if  handler.Handles(operation) {
			return  true 
		}
	}
	return  false 
}
调用 Kubernetes在/cmd/kube-apiserver/app/server.go中生成admisionController:1
2
3
4
admissionController, err := admission.NewFromPlugins(client, admissionControlPluginNames, s.GenericServerRunOptions.AdmissionControlConfigFile, pluginInitializer)
if  err != nil  {
	glog.Fatalf("Failed to initialize plugins: %v" , err)
}
生成admissionController之后,在/pkg/admission/chain.go中,使用1
2
3
4
5
6
7
8
9
if  admit != nil  && admit.Handles(admission.Delete) {
	userInfo, _ := api.UserFrom(ctx)
	err = admit.Admit(admission.NewAttributesRecord(nil , nil , scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo))
	if  err != nil  {
		scope.err(err, res.ResponseWriter, req.Request)
		return 
	}
}
来处理请求。