按照我目前看的k8s的v1.34版本,其中源码有上百万行,想要系统的学习源码是不太可能的事情,因此我喜欢使用每次熟悉一个功能点的方式学习源码,这样可以把其他的复杂逻辑,都先跳过去。我们知道apiserver是通过https的方式,和外部进行通信。那么apiserver中的https服务是如何启动的。本文我们就基于v1.34版本的代码看一下代码流程。
启动流程
- kubernetes的启动位置都在cmd目录下,所以自然apiserver的启动脚本在cmd/kube-apiserver/apiserver.go文件
- 由于命令行是使用的cobra库,那么我们就直接看RunE方法的逻辑,方法内大部分初始化内容我们都不关心,只看到了最后又跳入了一个Run方法
- 在Run方法内的逻辑比较清晰,就是先初始化配置、预执行、执行的流程
- 跳入到执行函数prepare.Run(ctx),方法内的逻辑都比较简单,就是一直跳转,最终执行到了staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go 中的如下函数内
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
ctx := wait.ContextForChannel(stopCh)
return s.RunWithContext(ctx)
}
在RunWithContext(ctx) 方法内执行了如下代码
stoppedCh, listenerStoppedCh, err := s.NonBlockingRunWithContext(stopHTTPServerCtx, shutdownTimeout)
if err != nil {
return err
}
在NonBlockingRunWithContext() 方法内执行了如下代码
if s.SecureServingInfo != nil && s.Handler != nil {
var err error
stoppedCh, listenerStoppedCh, err = s.SecureServingInfo.Serve(s.Handler, shutdownTimeout, internalStopCh)
if err != nil {
close(internalStopCh)
return nil, nil, err
}
}
在s.SecureServingInfo.Serve 方法内执行了如下代码
secureServer := &http.Server{
Addr: s.Listener.Addr().String(),
Handler: handler,
MaxHeaderBytes: 1 << 20,
TLSConfig: tlsConfig,
IdleTimeout: 90 * time.Second, // matches http.DefaultTransport keep-alive timeout
ReadHeaderTimeout: 32 * time.Second, // just shy of requestTimeoutUpperBound
}
//......
// apply settings to the server
if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
return nil, nil, fmt.Errorf("error configuring http2: %v", err)
}
到这里我们可以看到http服务起来了,是使用的golang.org/x/net/http 库启动的http服务。 看到这里服务是起来了,但是我们还有两个疑问:
- secureServer.Addr 这个参数是从哪里传进来的?
- secureServer.Handler 这个http最核心的处理业务逻辑的handler到底在哪里?
apiserver的HTTPS的地址配置
在前面我们已经在staging/src/k8s.io/apiserver/pkg/server/secure_serving.go 的SecureServingInfo.Serve 方法中找到了最终http服务启动时使用的地址,既然我们要找哪里赋值过来的,就使用IED的跳转功能,向上一层层追溯。在staging/src/k8s.io/apiserver/pkg/server/options/serving.go 文件中,我们看到了赋值代码
func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error {
if s == nil {
return nil
}
if s.BindPort <= 0 && s.Listener == nil {
return nil
}
if s.Listener == nil {
var err error
addr := net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort))
c := net.ListenConfig{}
ctls := multipleControls{}
if s.PermitPortSharing {
ctls = append(ctls, permitPortReuse)
}
if s.PermitAddressSharing {
ctls = append(ctls, permitAddressReuse)
}
if len(ctls) > 0 {
c.Control = ctls.Control
}
s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr, c)
if err != nil {
return fmt.Errorf("failed to create listener: %v", err)
}
} else {
if _, ok := s.Listener.Addr().(*net.TCPAddr); !ok {
return fmt.Errorf("failed to parse ip and port from listener")
}
s.BindPort = s.Listener.Addr().(*net.TCPAddr).Port
s.BindAddress = s.Listener.Addr().(*net.TCPAddr).IP
}
*config = &server.SecureServingInfo{
Listener: s.Listener,
HTTP2MaxStreamsPerConnection: s.HTTP2MaxStreamsPerConnection,
DisableHTTP2: s.DisableHTTP2Serving,
}
// ...
}
这里有两个逻辑,如果s.Lisener == nil 不存在,就使用s.BindAddress 和s.BindPort两个字段来拼接地址, 那我们就继续追溯,看下这两个字段的值,哪里来的。在staging/src/k8s.io/apiserver/pkg/server/serving.go 文件中,看到了如下赋值代码
func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
if s == nil {
return
}
fs.IPVar(&s.BindAddress, "bind-address", s.BindAddress, ""+
"The IP address on which to listen for the --secure-port port. The "+
"associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+
"clients. If blank or an unspecified address (0.0.0.0 or ::), all interfaces and IP address families will be used.")
desc := "The port on which to serve HTTPS with authentication and authorization."
if s.Required {
desc += " It cannot be switched off with 0."
} else {
desc += " If 0, don't serve HTTPS at all."
}
fs.IntVar(&s.BindPort, "secure-port", s.BindPort, desc)
}
通过这里可以看到,我们可以命令行启动时赋值bind-address 、 bing-port 字段来设置http服务的启动地址。 那如果没有外部设置值时,默认值是什么呢,继续查看设置值的代码,找到了pkg/kubeapiserver/options/serving.go 如下代码:
func NewSecureServingOptions() *genericoptions.SecureServingOptionsWithLoopback {
o := genericoptions.SecureServingOptions{
BindAddress: netutils.ParseIPSloppy("0.0.0.0"),
BindPort: 6443,
Required: true,
ServerCert: genericoptions.GeneratableKeyCert{
PairName: "apiserver",
CertDirectory: "/var/run/kubernetes",
},
}
return o.WithLoopback()
}
到这里我们就知道了,apiserver的https服务地址都是怎么来的了
Handler的具体业务逻辑在哪里
在前面追踪http服务启动流程时,看到了http服务时通过接收一个handler参数来启动,那么这个handler参数是哪里来的,我们也通过IED一步步向上跳转追踪,在staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go 文件发下如下代码
func (s preparedGenericAPIServer) NonBlockingRunWithContext(ctx context.Context, shutdownTimeout time.Duration) (<-chan struct{}, <-chan struct{}, error) {
// ....
if s.SecureServingInfo != nil && s.Handler != nil {
var err error
stoppedCh, listenerStoppedCh, err = s.SecureServingInfo.Serve(s.Handler, shutdownTimeout, internalStopCh)
if err != nil {
close(internalStopCh)
return nil, nil, err
}
}
// .....
}
通过这里知道是preparedGenericAPIServer.Handler赋值的,继续向上找,谁赋值给了它,在同文件,发现如下代码
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
// .....
return preparedGenericAPIServer{s}
}
也就是说prepareGenericAPIServer.Handler = GenericAPIServer.Handler ,那继续找GenericAPIServer.Handler 是哪里赋值过来的,在staging/src/k8s.io/apiserver/pkg/server/config.go 中,有如下代码:
func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*GenericAPIServer, error) {
// ...
handlerChainBuilder := func(handler http.Handler) http.Handler {
return c.BuildHandlerChainFunc(handler, c.Config)
}
// ...
apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler())
s := &GenericAPIServer{
Handler: apiServerHandler,
}
// ...
继续看NewAPIServerHandler方法的实现
func NewAPIServerHandler(name string, s runtime.NegotiatedSerializer, handlerChainBuilder HandlerChainBuilderFn, notFoundHandler http.Handler) *APIServerHandler {
director := director{
name: name,
goRestfulContainer: gorestfulContainer,
nonGoRestfulMux: nonGoRestfulMux,
}
return &APIServerHandler{
FullHandlerChain: handlerChainBuilder(director),
GoRestfulContainer: gorestfulContainer,
NonGoRestfulMux: nonGoRestfulMux,
Director: director,
}
}
我们算是找到了handler逻辑的最终位置,我们知道http服务的handler都需要实现一个ServerHTTP方法,我们看下APIServerHandler.ServerHTTP方法的实现
func (a *APIServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.FullHandlerChain.ServeHTTP(w, r)
}
我们看到真正执行逻辑的是FullHandlerChain , 而FullHandlerChain 是由handlerChainBuilder(director) 方法创建的,查看代码
handlerChainBuilder := func(handler http.Handler) http.Handler {
return c.BuildHandlerChainFunc(handler, c.Config)
}
继续查看BuildHandlerChainFunc 方法,发现是使用的staging/src/k8s.io/apisever/pkg/server/config.go 文件DefaultBuildHandlerChain 方法 到这里就清楚了,一个https请求过来后,apiserver是在什么位置处理逻辑的
结论
- apiserver的https服务的默认地址是0.0.0.0:6443 ,也可以通过命令行参数bind-address , bind-port 来自定义地址
- http的请求,最终都会落到staging/src/k8s.io/apiserver/pkg/server/config.go 文件下的DefaultBuildHandlerChain 方法内