本文基于kubesphere v3.3.2版本解读ks-apiserver的启动及路由注册相关代码
命令行框架使用cobra,在cmd/ks-apiserver/app/server.go
使用NewAPIServerCommand
创建一个cobra command然后execute.
NewAPIServerCommand
先从文件读取配置:
goconf, err := apiserverconfig.TryLoadFromDisk()
配置使用的是viper包,数据结构在pkg/apiserver/config
定义。
然后构建一个cobra Command,将Run函数注册进去。
注册一个helper函数,用于打印命令行帮助信息。
go usageFmt := "Usage:\n %s\n"
cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
cliflag.PrintSections(cmd.OutOrStdout(), namedFlagSets, cols)
})
添加一个version子命令,用于打印版本信息。
首先调用NewAPIServer
构建一个APIServer对象(cmd/ks-apiserver/app/options/options
),在这个函数中会把所有要用到的client对象、informer、http server以及其它的helper给创建出来,放到APIServer中。
然后调用apiserver.PrepareRun
,这个函数主要是用来注册路由,下面详解。
最后但用apiserver.Run
, 调用waitForResourceSync
等待informer同步,然后启动http server.
注意Run函数的声明:
gofunc Run(s *options.ServerRunOptions, ctx context.Context) error {
参数里的context,是在调用时传入,这里用到了k8s社区的signals包注册信号处理:
goimport (
"sigs.k8s.io/controller-runtime/pkg/manager/signals
)
// ......
return Run(s, signals.SetupSignalHandler())
这个context会传给apiserver.Run
, 当context close时关闭http server.
ks-apiserver使用"github.com/emicklei/go-restful"
框架,在这个框架中一个路由对象称为Container
. 在apiserver的PrepareRun
函数中(pkg/apiserver/apiserver.go
)注册handler以及midware. 包含以下几种路由:
installKubesphereAPIs
注册kubesphere自有的apibuildHandlerChain
-> WithKubeAPIServer
将/api开头的请求转发到k8s apiserverbuildHandlerChain
-> WithMultipleClusterDispatcher
将url中指定集群的请求转发过去kubesphere的api在pkg/kapis/
中定义,每一类api都会定义一个AddToContainer函数,然后在installKubesphereAPIs
中调用,以notification为例:
gourlruntime.Must(notificationv1.AddToContainer(s.container, s.Config.NotificationOptions.Endpoint))
notification有三个版本,v1实际上没有内容,只是把请求转发到其它的版本:
go// there are no versions specified cause we want to proxy all versions of requests to backend service
var GroupVersion = schema.GroupVersion{Group: "notification.kubesphere.io", Version: ""}
func AddToContainer(container *restful.Container, endpoint string) error {
proxy, err := generic.NewGenericProxy(endpoint, GroupVersion.Group, GroupVersion.Version)
if err != nil {
return err
}
return proxy.AddToContainer(container)
}
notification v2beta1的AddToContainer是这样的:
gofunc AddToContainer(container *restful.Container, option *nm.Options) error {
//创建一个接口handler
h := newHandler(option)
//创建一个webservice,相当于一个路由group
ws := runtime.NewWebService(GroupVersion)
//注册路由
ws.Route(ws.POST("/configs/notification/verification").
Reads("").
To(h.Verify).
Returns(http.StatusOK, api.StatusOK, http.Response{}.Body)).
Doc("Provide validation for notification-manager information")
ws.Route(ws.POST("/configs/notification/users/{user}/verification").
To(h.Verify).
Param(ws.PathParameter("user", "user name")).
Returns(http.StatusOK, api.StatusOK, http.Response{}.Body)).
Doc("Provide validation for notification-manager information")
container.Add(ws)
return nil
}
buildHandlerChain
函数用于给apiserver的http server添加调用链,相当于配置http中间件。添加的顺序是:
go//转发k8s原生api请求
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
//审计
handler = filters.WithAuditing(handler,audit.NewAuditing(s.InformerFactory, s.Config.AuditingOptions, stopCh))
//鉴权
handler = filters.WithAuthorization(handler, authorizers)
//身份认证
handler = filters.WithAuthentication(handler, authn)
//保存请求信息到context
handler = filters.WithRequestInfo(handler, requestInfoResolver)
执行时,最后添加的handler会最先执行,也就是说,首先执行的是将解析请求为RequestInfo然后保存到context中。
本文作者:renbear
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC 2.0 许可协议。转载请注明出处!