kube-apiserver 初始命名空間實(shí)現(xiàn)方式
在我們第 1 回僅啟動(dòng)一個(gè) kube-apiserver 組件的情況下,Kubernetes 就創(chuàng)建了四個(gè)初始命名空間:
-
default:默認(rèn)命名空間 -
kube-node-lease:用于保存與各個(gè)節(jié)點(diǎn)關(guān)聯(lián)的 Lease(租約)對(duì)象 -
kube-public:所有(包括未經(jīng)身份驗(yàn)證)的客戶端都可以讀取,主要用于集群內(nèi)部共享資源 -
kube-system:用于 Kubernetes 系統(tǒng)創(chuàng)建資源,包括 Kubernetes 運(yùn)行所需的核心組件
$ kubectl get ns
NAME STATUS AGE
default Active 2d22h
kube-node-lease Active 2d22h
kube-public Active 2d22h
kube-system Active 2d22h
如果刪除這四個(gè)初始命名空間:
$ kubectl delete ns default kube-node-lease kube-public kube-system
namespace "kube-node-lease" deleted
Error from server (Forbidden): namespaces "default" is forbidden: this namespace may not be deleted
Error from server (Forbidden): namespaces "kube-public" is forbidden: this namespace may not be deleted
Error from server (Forbidden): namespaces "kube-system" is forbidden: this namespace may not be deleted
會(huì)發(fā)現(xiàn)只有 kube-node-lease 允許刪除,而且經(jīng)過(guò)觀察,刪除后 1 分鐘內(nèi),該命名空間也會(huì)自動(dòng)重新創(chuàng)建:
$ kubectl get ns
NAME STATUS AGE
default Active 2d22h
kube-node-lease Active 10s
kube-public Active 2d22h
kube-system Active 2d22h
這四個(gè)初始命名空間的維護(hù)工作,實(shí)際是由 GenericAPIServer 的 Hook 機(jī)制完成的。
在第 5 回啟動(dòng) HTTP Server 的 NonBlockingRun 方法中:
// k8s.io/apiserver/pkg/server/genericapiserver.go
func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}, shutdownTimeout time.Duration) (<-chan struct{}, <-chan struct{}, error) {
// ...
if s.SecureServingInfo != nil && s.Handler != nil {
// 啟動(dòng) HTTP Server
stoppedCh, listenerStoppedCh, err = s.SecureServingInfo.Serve(s.Handler, shutdownTimeout, internalStopCh)
// ...
}
// HTTP Server 退出通知
go func() {
<-stopCh
close(internalStopCh)
}()
// 運(yùn)行 Hook
s.RunPostStartHooks(stopCh)
// ...
}
當(dāng) HTTP Server 啟動(dòng)成功后,就會(huì)開(kāi)始運(yùn)行所有的 Hook :
// k8s.io/apiserver/pkg/server/hooks.go
type PostStartHookFunc func(context PostStartHookContext) error
// Hook 實(shí)體
type postStartHookEntry struct {
// hook 方法
hook PostStartHookFunc
// hook 方法的調(diào)用棧信息
originatingStack string
done chan struct{}
}
func (s *GenericAPIServer) RunPostStartHooks(stopCh <-chan struct{}) {
s.postStartHookLock.Lock()
defer s.postStartHookLock.Unlock()
s.postStartHooksCalled = true
context := PostStartHookContext{
LoopbackClientConfig: s.LoopbackClientConfig,
StopCh: stopCh,
}
// postStartHooks 是一個(gè)保存所有 Hook 的 map[string]postStartHookEntry
// 遍歷所有 Hook 并運(yùn)行其 hook 方法
for hookName, hookEntry := range s.postStartHooks {
go runPostStartHook(hookName, hookEntry, context)
}
}
func runPostStartHook(name string, entry postStartHookEntry, context PostStartHookContext) {
var err error
func() {
// don't let the hook *accidentally* panic and kill the server
defer utilruntime.HandleCrash()
// 運(yùn)行其 hook 方法
err = entry.hook(context)
}()
// if the hook intentionally wants to kill server, let it.
if err != nil {
klog.Fatalf("PostStartHook %q failed: %v", name, err)
}
close(entry.done)
}
在 s.postStartHooks 位置斷點(diǎn)調(diào)試,可以看到當(dāng)前版本一共有 24 種不同功能的 Hook :
其中 start-system-namespaces-controller 便是本文的主角。
如果從 debug 的角度去找,可以看其對(duì)應(yīng)的 hook 的調(diào)用棧信息定位到該 Hook 的創(chuàng)建位置:
或者繼續(xù)跟隨我的思路,來(lái)到第 3 回注冊(cè) /api/* 核心路由的 InstallLegacyAPI 方法:
// pkg/controlplane/instance.go
func (m *Instance) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic.RESTOptionsGetter) error {
// 創(chuàng)建 RESTStorage
legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(c.ExtraConfig.APIResourceConfigSource, restOptionsGetter)
controllerName := "bootstrap-controller"
client := kubernetes.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
// Kubernetes clusters contains the following system namespaces:
// kube-system, kube-node-lease, kube-public, default
// 注冊(cè) start-system-namespaces-controller Hook
m.GenericAPIServer.AddPostStartHookOrDie("start-system-namespaces-controller", func(hookContext genericapiserver.PostStartHookContext) error {
go systemnamespaces.NewController(client, c.ExtraConfig.VersionedInformers.Core().V1().Namespaces()).Run(hookContext.StopCh)
return nil
})
bootstrapController, err := c.NewBootstrapController(legacyRESTStorage, client)
if err != nil {
return fmt.Errorf("error creating bootstrap controller: %v", err)
}
m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook)
m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook)
// 核心路由注冊(cè)
if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
return fmt.Errorf("error in registering group versions: %v", err)
}
return nil
}
在 RESTStorage 創(chuàng)建之后,核心路由注冊(cè)之前,注冊(cè)了兩個(gè) Hook :bootstrap-controller 和 start-system-namespaces-controller 。
Hook 注冊(cè)的 AddPostStartHookOrDie 方法實(shí)際就是將 Hook 添加到 postStartHooks 中:
// k8s.io/apiserver/pkg/server/hooks.go
func (s *GenericAPIServer) AddPostStartHookOrDie(name string, hook PostStartHookFunc) {
if err := s.AddPostStartHook(name, hook); err != nil {
klog.Fatalf("Error registering PostStartHook %q: %v", name, err)
}
}
func (s *GenericAPIServer) AddPostStartHook(name string, hook PostStartHookFunc) error {
// ...
// 添加到 postStartHooks map 中
s.postStartHooks[name] = postStartHookEntry{hook: hook, originatingStack: string(debug.Stack()), done: done}
return nil
}
因?yàn)?Hook 運(yùn)行時(shí)實(shí)際是直接調(diào)用 hook 方法,所以重點(diǎn)還是各個(gè) Hook 注冊(cè)時(shí)自身所傳入的 hook PostStartHookFunc 參數(shù)。
對(duì)于 start-system-namespaces-controller Hook 注冊(cè)的 PostStartHookFunc 方法:
func(hookContext genericapiserver.PostStartHookContext) error {
// 創(chuàng)建 start-system-namespaces-controller 并調(diào)用 Run 運(yùn)行
go systemnamespaces.NewController(client, c.ExtraConfig.VersionedInformers.Core().V1().Namespaces()).Run(hookContext.StopCh)
return nil
}
該 controller 的實(shí)現(xiàn)很簡(jiǎn)單,我直接全部貼出來(lái):
// pkg/controlplane/controller/systemnamespaces/system_namespaces_controller.go
// 創(chuàng)建 start-system-namespaces-controller
func NewController(clientset kubernetes.Interface, namespaceInformer coreinformers.NamespaceInformer) *Controller {
// 需要維護(hù)的四個(gè)初始命名空間
systemNamespaces := []string{metav1.NamespaceSystem, metav1.NamespacePublic, v1.NamespaceNodeLease, metav1.NamespaceDefault}
// 定時(shí)周期為 1 分鐘
interval := 1 * time.Minute
return &Controller{
client: clientset,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,
systemNamespaces: systemNamespaces,
interval: interval,
}
}
// 運(yùn)行 start-system-namespaces-controller
func (c *Controller) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer klog.Infof("Shutting down system namespaces controller")
klog.Infof("Starting system namespaces controller")
if !cache.WaitForCacheSync(stopCh, c.namespaceSynced) {
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
return
}
// 定時(shí)執(zhí)行 c.sync 方法,定時(shí)周期為 1 分鐘
go wait.Until(c.sync, c.interval, stopCh)
<-stopCh
}
func (c *Controller) sync() {
// Loop the system namespace list, and create them if they do not exist
// 遍歷四個(gè)初始命名空間
for _, ns := range c.systemNamespaces {
// 如果需要?jiǎng)t創(chuàng)建
if err := c.createNamespaceIfNeeded(ns); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to create required kubernetes system Namespace %s: %v", ns, err))
}
}
}
func (c *Controller) createNamespaceIfNeeded(ns string) error {
if _, err := c.namespaceLister.Get(ns); err == nil {
// the namespace already exists
// 命名空間已存在
return nil
}
// 命名空間不存在,就去創(chuàng)建命名空間
newNs := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
Namespace: "",
},
}
_, err := c.client.CoreV1().Namespaces().Create(context.TODO(), newNs, metav1.CreateOptions{})
if err != nil && errors.IsAlreadyExists(err) {
err = nil
}
return err
}
這就是為什么啟動(dòng)一個(gè) kube-apiserver 組件就創(chuàng)建了四個(gè)初始命名空間,并且 kube-node-lease 命名空間刪除后 1 分鐘內(nèi)會(huì)自動(dòng)重新創(chuàng)建的原因。
對(duì)于其它 23 種 Hook ,感興趣也可以直接全局搜索 Hook name 快速定位到其注冊(cè)位置去看。

