Skip to content

Commit e2191b5

Browse files
authored
Merge pull request #2473 from joelanford/export-runnable-server
✨ Export HTTP server manager runnable implementation
2 parents ee7cdce + d86d5f8 commit e2191b5

File tree

4 files changed

+76
-25
lines changed

4 files changed

+76
-25
lines changed

pkg/manager/internal.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,8 @@ func (cm *controllerManager) addHealthProbeServer() error {
284284
mux.Handle(cm.livenessEndpointName+"/", http.StripPrefix(cm.livenessEndpointName, cm.healthzHandler))
285285
}
286286

287-
return cm.add(&server{
288-
Kind: "health probe",
289-
Log: cm.logger,
287+
return cm.add(&Server{
288+
Name: "health probe",
290289
Server: srv,
291290
Listener: cm.healthProbeListener,
292291
})
@@ -302,9 +301,8 @@ func (cm *controllerManager) addPprofServer() error {
302301
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
303302
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
304303

305-
return cm.add(&server{
306-
Kind: "pprof",
307-
Log: cm.logger,
304+
return cm.add(&Server{
305+
Name: "pprof",
308306
Server: srv,
309307
Listener: cm.pprofListener,
310308
})
@@ -384,11 +382,12 @@ func (cm *controllerManager) Start(ctx context.Context) (err error) {
384382
}
385383
}
386384

387-
// First start any internal HTTP servers, which includes health probes, metrics and profiling if enabled.
385+
// First start any HTTP servers, which includes health probes and profiling, if enabled.
388386
//
389-
// WARNING: Internal HTTP servers MUST start before any cache is populated, otherwise it would block
390-
// conversion webhooks to be ready for serving which make the cache never get ready.
391-
if err := cm.runnables.HTTPServers.Start(cm.internalCtx); err != nil {
387+
// WARNING: HTTPServers includes the health probes, which MUST start before any cache is populated, otherwise
388+
// it would block conversion webhooks to be ready for serving which make the cache never get ready.
389+
logCtx := logr.NewContext(cm.internalCtx, cm.logger)
390+
if err := cm.runnables.HTTPServers.Start(logCtx); err != nil {
392391
return fmt.Errorf("failed to start HTTP servers: %w", err)
393392
}
394393

pkg/manager/runnable_group.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ func newRunnables(baseContext BaseContextFunc, errChan chan error) *runnables {
5454
// The runnables added after Start are started directly.
5555
func (r *runnables) Add(fn Runnable) error {
5656
switch runnable := fn.(type) {
57-
case *server:
57+
case *Server:
58+
if runnable.NeedLeaderElection() {
59+
return r.LeaderElection.Add(fn, nil)
60+
}
5861
return r.HTTPServers.Add(fn, nil)
5962
case hasCache:
6063
return r.Caches.Add(fn, func(ctx context.Context) bool {

pkg/manager/runnable_group_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
. "github.com/onsi/ginkgo/v2"
1111
. "github.com/onsi/gomega"
1212
"k8s.io/utils/ptr"
13+
1314
"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
1415
"sigs.k8s.io/controller-runtime/pkg/webhook"
1516
)
@@ -22,7 +23,7 @@ var _ = Describe("runnables", func() {
2223
})
2324

2425
It("should add HTTP servers to the appropriate group", func() {
25-
server := &server{}
26+
server := &Server{}
2627
r := newRunnables(defaultBaseContext, errCh)
2728
Expect(r.Add(server)).To(Succeed())
2829
Expect(r.HTTPServers.startQueue).To(HaveLen(1))

pkg/manager/server.go

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,89 @@ import (
2121
"errors"
2222
"net"
2323
"net/http"
24+
"time"
2425

25-
"github.com/go-logr/logr"
26+
crlog "sigs.k8s.io/controller-runtime/pkg/log"
2627
)
2728

28-
// server is a general purpose HTTP server Runnable for a manager
29-
// to serve some internal handlers such as health probes, metrics and profiling.
30-
type server struct {
31-
Kind string
32-
Log logr.Logger
33-
Server *http.Server
29+
var (
30+
_ Runnable = (*Server)(nil)
31+
_ LeaderElectionRunnable = (*Server)(nil)
32+
)
33+
34+
// Server is a general purpose HTTP server Runnable for a manager.
35+
// It is used to serve some internal handlers for health probes and profiling,
36+
// but it can also be used to run custom servers.
37+
type Server struct {
38+
// Name is an optional string that describes the purpose of the server. It is used in logs to distinguish
39+
// among multiple servers.
40+
Name string
41+
42+
// Server is the HTTP server to run. It is required.
43+
Server *http.Server
44+
45+
// Listener is an optional listener to use. If not set, the server start a listener using the server.Addr.
46+
// Using a listener is useful when the port reservation needs to happen in advance of this runnable starting.
3447
Listener net.Listener
48+
49+
// OnlyServeWhenLeader is an optional bool that indicates that the server should only be started when the manager is the leader.
50+
OnlyServeWhenLeader bool
51+
52+
// ShutdownTimeout is an optional duration that indicates how long to wait for the server to shutdown gracefully. If not set,
53+
// the server will wait indefinitely for all connections to close.
54+
ShutdownTimeout *time.Duration
3555
}
3656

37-
func (s *server) Start(ctx context.Context) error {
38-
log := s.Log.WithValues("kind", s.Kind, "addr", s.Listener.Addr())
57+
// Start starts the server. It will block until the server is stopped or an error occurs.
58+
func (s *Server) Start(ctx context.Context) error {
59+
log := crlog.FromContext(ctx)
60+
if s.Name != "" {
61+
log = log.WithValues("name", s.Name)
62+
}
63+
log = log.WithValues("addr", s.addr())
3964

4065
serverShutdown := make(chan struct{})
4166
go func() {
4267
<-ctx.Done()
4368
log.Info("shutting down server")
44-
if err := s.Server.Shutdown(context.Background()); err != nil {
69+
70+
shutdownCtx := context.Background()
71+
if s.ShutdownTimeout != nil {
72+
var shutdownCancel context.CancelFunc
73+
shutdownCtx, shutdownCancel = context.WithTimeout(context.Background(), *s.ShutdownTimeout)
74+
defer shutdownCancel()
75+
}
76+
77+
if err := s.Server.Shutdown(shutdownCtx); err != nil {
4578
log.Error(err, "error shutting down server")
4679
}
4780
close(serverShutdown)
4881
}()
4982

5083
log.Info("starting server")
51-
if err := s.Server.Serve(s.Listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
84+
if err := s.serve(); err != nil && !errors.Is(err, http.ErrServerClosed) {
5285
return err
5386
}
5487

5588
<-serverShutdown
5689
return nil
5790
}
5891

59-
func (s *server) NeedLeaderElection() bool {
60-
return false
92+
// NeedLeaderElection returns true if the server should only be started when the manager is the leader.
93+
func (s *Server) NeedLeaderElection() bool {
94+
return s.OnlyServeWhenLeader
95+
}
96+
97+
func (s *Server) addr() string {
98+
if s.Listener != nil {
99+
return s.Listener.Addr().String()
100+
}
101+
return s.Server.Addr
102+
}
103+
104+
func (s *Server) serve() error {
105+
if s.Listener != nil {
106+
return s.Server.Serve(s.Listener)
107+
}
108+
return s.Server.ListenAndServe()
61109
}

0 commit comments

Comments
 (0)