Kubernetes-apiServer源码深度分析(六)HTTPServer具体是怎么跑起来的?

324 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

记得看代码中的注释哈,理解都在里面 源码基于v1.24

本篇文章介绍HTTPServer具体是怎么跑起来的,寻找到调用链,了解Server生命周期状态的流转的流程

先找到调用链

核心调用链代码,下面是按照函数调用顺序展示的代码,省略的逻辑写在注释里

func NewAPIServerCommand() *cobra.Command {

    return Run(completedOptions, genericapiserver.SetupSignalHandler())

}

func Run(completeOptions completedServerRunOptions, stopCh <-chan struct{}) error {

prepared, err := server.PrepareRun()
    if err != nil {
       return err
    }

    return prepared.Run(stopCh)
}

func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) {
    /*
    注册openapi 相关的controller到post start hook中
    */
    prepared := s.GenericAPIServer.PrepareRun()

}

func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
    /*  
      制作openapi,server healthz,
      Livez,readyz等endpoints
    */
}

func (s preparedAPIAggregator) Run(stopCh <-chan struct{}) error {
   return s.runnable.Run(stopCh)
}

func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {

stoppedCh, listenerStoppedCh, err := s.NonBlockingRun(stopHttpServerCh, shutdownTimeout)

}

func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}, shutdownTimeout time.Duration) (<-chan struct{}, <-chan struct{}, error) {
   // Use an internal stop channel to allow cleanup of the listeners on error.
   internalStopCh := make(chan struct{})
   var stoppedCh <-chan struct{}
   var listenerStoppedCh <-chan struct{}
   if s.SecureServingInfo != nil && s.Handler != nil {
      var err error
      /*
          TLS的设置SecureServingInfo.Serve
      */
      stoppedCh, listenerStoppedCh, err = s.SecureServingInfo.Serve(s.Handler, shutdownTimeout, internalStopCh)
      if err != nil {
         close(internalStopCh)
         return nil, nil, err
      }
   }

   // Now that listener have bound successfully, it is the
   // responsibility of the caller to close the provided channel to
   // ensure cleanup.
   go func() {
      <-stopCh
      close(internalStopCh)
   }()
    /*
        调用启动后的HOOK
    */
   s.RunPostStartHooks(stopCh)

   if _, err := systemd.SdNotify(true, "READY=1\n"); err != nil {
      klog.Errorf("Unable to send systemd daemon successful start message: %v\n", err)
   }

   return stoppedCh, listenerStoppedCh, nil
}

   /* 
   HTTP2.0设置,TLS设置,启动Server
   */

func (s *SecureServingInfo) Serve(handler http.Handler, shutdownTimeout time.Duration, stopCh <-chan struct{}) (<-chan struct{}, <-chan struct{}, error) {
   if s.Listener == nil {
      return nil, nil, fmt.Errorf("listener must not be nil")
   }

   tlsConfig, err := s.tlsConfig(stopCh)
   if err != nil {
      return nil, nil, err
   }

   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
   }

   // At least 99% of serialized resources in surveyed clusters were smaller than 256kb.
   // This should be big enough to accommodate most API POST requests in a single frame,
   // and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`.
   const resourceBody99Percentile = 256 * 1024

   http2Options := &http2.Server{
      IdleTimeout: 90 * time.Second, // matches http.DefaultTransport keep-alive timeout
   }

   // shrink the per-stream buffer and max framesize from the 1MB default while still accommodating most API POST requests in a single frame
   http2Options.MaxUploadBufferPerStream = resourceBody99Percentile
   http2Options.MaxReadFrameSize = resourceBody99Percentile

   // use the overridden concurrent streams setting or make the default of 250 explicit so we can size MaxUploadBufferPerConnection appropriately
   if s.HTTP2MaxStreamsPerConnection > 0 {
      http2Options.MaxConcurrentStreams = uint32(s.HTTP2MaxStreamsPerConnection)
   } else {
      http2Options.MaxConcurrentStreams = 250
   }

   // increase the connection buffer size from the 1MB default to handle the specified number of concurrent streams
   http2Options.MaxUploadBufferPerConnection = http2Options.MaxUploadBufferPerStream * int32(http2Options.MaxConcurrentStreams)

   if !s.DisableHTTP2 {
      // apply settings to the server
      if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
         return nil, nil, fmt.Errorf("error configuring http2: %v", err)
      }
   }

   // use tlsHandshakeErrorWriter to handle messages of tls handshake error
   tlsErrorWriter := &tlsHandshakeErrorWriter{os.Stderr}
   tlsErrorLogger := log.New(tlsErrorWriter, "", 0)
   secureServer.ErrorLog = tlsErrorLogger

   klog.Infof("Serving securely on %s", secureServer.Addr)
   return RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
}

Server生命周期状态的流转

在go语言中,事件传递状态流转常用channel来实现,在下面这个函数中,就是Server的生命周期状态的流转,其中有很多的channel,代表不同的事件

//  HTTPServerStoppedListening (httpServerStoppedListeningCh)
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {
   delayedStopCh := s.lifecycleSignals.AfterShutdownDelayDuration
   shutdownInitiatedCh := s.lifecycleSignals.ShutdownInitiated

   // Clean up resources on shutdown.
   defer s.Destroy()

   // spawn a new goroutine for closing the MuxAndDiscoveryComplete signal
   // registration happens during construction of the generic api server
   // the last server in the chain aggregates signals from the previous instances
   go func() {
      for _, muxAndDiscoveryCompletedSignal := range s.GenericAPIServer.MuxAndDiscoveryCompleteSignals() {
         select {
         case <-muxAndDiscoveryCompletedSignal:
            continue
         case <-stopCh:
            klog.V(1).Infof("haven't completed %s, stop requested", s.lifecycleSignals.MuxAndDiscoveryComplete.Name())
            return
         }
      }
      s.lifecycleSignals.MuxAndDiscoveryComplete.Signal()
      klog.V(1).Infof("%s has all endpoints registered and discovery information is complete", s.lifecycleSignals.MuxAndDiscoveryComplete.Name())
   }()

   go func() {
      defer delayedStopCh.Signal()
      defer klog.V(1).InfoS("[graceful-termination] shutdown event", "name", delayedStopCh.Name())

      <-stopCh

      // As soon as shutdown is initiated, /readyz should start returning failure.
      // This gives the load balancer a window defined by ShutdownDelayDuration to detect that /readyz is red
      // and stop sending traffic to this server.
      shutdownInitiatedCh.Signal()
      klog.V(1).InfoS("[graceful-termination] shutdown event", "name", shutdownInitiatedCh.Name())

      time.Sleep(s.ShutdownDelayDuration)
   }()

   // close socket after delayed stopCh
   shutdownTimeout := s.ShutdownTimeout
   if s.ShutdownSendRetryAfter {
      // when this mode is enabled, we do the following:
      // - the server will continue to listen until all existing requests in flight
      //   (not including active long running requests) have been drained.
      // - once drained, http Server Shutdown is invoked with a timeout of 2s,
      //   net/http waits for 1s for the peer to respond to a GO_AWAY frame, so
      //   we should wait for a minimum of 2s
      shutdownTimeout = 2 * time.Second
      klog.V(1).InfoS("[graceful-termination] using HTTP Server shutdown timeout", "ShutdownTimeout", shutdownTimeout)
   }

   notAcceptingNewRequestCh := s.lifecycleSignals.NotAcceptingNewRequest
   drainedCh := s.lifecycleSignals.InFlightRequestsDrained
   stopHttpServerCh := make(chan struct{})
   go func() {
      defer close(stopHttpServerCh)

      timeToStopHttpServerCh := notAcceptingNewRequestCh.Signaled()
      if s.ShutdownSendRetryAfter {
         timeToStopHttpServerCh = drainedCh.Signaled()
      }

      <-timeToStopHttpServerCh
   }()

   // Start the audit backend before any request comes in. This means we must call Backend.Run
   // before http server start serving. Otherwise the Backend.ProcessEvents call might block.
   // AuditBackend.Run will stop as soon as all in-flight requests are drained.
   if s.AuditBackend != nil {
      if err := s.AuditBackend.Run(drainedCh.Signaled()); err != nil {
         return fmt.Errorf("failed to run the audit backend: %v", err)
      }
   }

   stoppedCh, listenerStoppedCh, err := s.NonBlockingRun(stopHttpServerCh, shutdownTimeout)
   if err != nil {
      return err
   }

   httpServerStoppedListeningCh := s.lifecycleSignals.HTTPServerStoppedListening
   go func() {
      <-listenerStoppedCh
      httpServerStoppedListeningCh.Signal()
      klog.V(1).InfoS("[graceful-termination] shutdown event", "name", httpServerStoppedListeningCh.Name())
   }()

   // we don't accept new request as soon as both ShutdownDelayDuration has
   // elapsed and preshutdown hooks have completed.
   preShutdownHooksHasStoppedCh := s.lifecycleSignals.PreShutdownHooksStopped
   go func() {
      defer klog.V(1).InfoS("[graceful-termination] shutdown event", "name", notAcceptingNewRequestCh.Name())
      defer notAcceptingNewRequestCh.Signal()

      // wait for the delayed stopCh before closing the handler chain
      <-delayedStopCh.Signaled()

      // Additionally wait for preshutdown hooks to also be finished, as some of them need
      // to send API calls to clean up after themselves (e.g. lease reconcilers removing
      // itself from the active servers).
      <-preShutdownHooksHasStoppedCh.Signaled()
   }()

   go func() {
      defer klog.V(1).InfoS("[graceful-termination] shutdown event", "name", drainedCh.Name())
      defer drainedCh.Signal()

      // wait for the delayed stopCh before closing the handler chain (it rejects everything after Wait has been called).
      <-notAcceptingNewRequestCh.Signaled()

//等待所有由RequestTimeout变量限定的请求完成。

//一次HandlerChainWaitGroup。等待被调用,apiserver被调用

//期望拒绝任何传入请求{503,try- after}

//通过WithWaitGroup过滤器进行响应。相反,我们观察到传入的请求得到一个“连接拒绝”错误,这是

//因为,在这一点上,我们已经调用了'Server。关闭”,

// net/http服务器已经停止监听。这导致的

//请求获取一个'connection refused'错误。

//另一方面,如果'ShutdownSendRetryAfter'在传入时启用

//请求将被拒绝{429,try- after} since

//服务器。Shutdown'只会在飞行中请求后调用
      s.HandlerChainWaitGroup.Wait()
   }()

   klog.V(1).Info("[graceful-termination] waiting for shutdown to be initiated")
   <-stopCh

   // run shutdown hooks directly. This includes deregistering from
   // the kubernetes endpoint in case of kube-apiserver.
   func() {
      defer func() {
         preShutdownHooksHasStoppedCh.Signal()
         klog.V(1).InfoS("[graceful-termination] pre-shutdown hooks completed", "name", preShutdownHooksHasStoppedCh.Name())
      }()
      err = s.RunPreShutdownHooks()
   }()
   if err != nil {
      return err
   }

   // Wait for all requests in flight to drain, bounded by the RequestTimeout variable.
   <-drainedCh.Signaled()

   if s.AuditBackend != nil {
      s.AuditBackend.Shutdown()
      klog.V(1).InfoS("[graceful-termination] audit backend shutdown completed")
   }

   // wait for stoppedCh that is closed when the graceful termination (server.Shutdown) is finished.
   <-listenerStoppedCh
   <-stoppedCh

   klog.V(1).Info("[graceful-termination] apiserver is exiting")
   return nil
}

Summary

核心调用链路流程图如下 image.png preparedGenericAPIServer.NonBlockingRun的调用时序图,http.Server一直运行下去直至终结 image.png