-
Notifications
You must be signed in to change notification settings - Fork 589
/
server.go
184 lines (158 loc) · 5.58 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package diagnostics
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/pprof"
"sync"
"time"
"github.com/go-logr/logr"
"github.com/kong/go-database-reconciler/pkg/file"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
)
const (
defaultHTTPReadHeaderTimeout = 10 * time.Second
// diagnosticConfigBufferDepth is the size of the channel buffer for receiving diagnostic
// config dumps from the proxy sync loop. The chosen size is essentially arbitrary: we don't
// expect that the receive end will get backlogged (it only assigns the value to a local
// variable) but do want a small amount of leeway to account for goroutine scheduling, so it
// is not zero.
diagnosticConfigBufferDepth = 3
)
// Server is an HTTP server running exposing the pprof profiling tool, and processing diagnostic dumps of Kong configurations.
type Server struct {
logger logr.Logger
profilingEnabled bool
configDumps util.ConfigDumpDiagnostic
successfulConfigDump file.Content
failedConfigDump file.Content
configLock *sync.RWMutex
}
// ServerConfig contains configuration for the diagnostics server.
type ServerConfig struct {
// ProfilingEnabled enables profiling endpoints.
ProfilingEnabled bool
// ConfigDumpsEnabled enables config dumps endpoints.
ConfigDumpsEnabled bool
// DumpSensitiveConfig makes config dumps to include sensitive information.
DumpSensitiveConfig bool
}
// NewServer creates a diagnostics server ready to start listening.
func NewServer(logger logr.Logger, cfg ServerConfig) Server {
s := Server{
logger: logger,
profilingEnabled: cfg.ProfilingEnabled,
configLock: &sync.RWMutex{},
}
if cfg.ConfigDumpsEnabled {
s.configDumps = util.ConfigDumpDiagnostic{
DumpsIncludeSensitive: cfg.DumpSensitiveConfig,
Configs: make(chan util.ConfigDump, diagnosticConfigBufferDepth),
}
}
return s
}
// ConfigDumps returns an object allowing dumping succeeded and failed configuration updates.
// It will return a zero value of the type in case the config dumps are not enabled.
func (s *Server) ConfigDumps() util.ConfigDumpDiagnostic {
return s.configDumps
}
// Listen starts up the HTTP server and blocks until ctx expires.
func (s *Server) Listen(ctx context.Context, port int) error {
mux := http.NewServeMux()
if s.configDumps != (util.ConfigDumpDiagnostic{}) {
s.installDumpHandlers(mux)
}
if s.profilingEnabled {
installProfilingHandlers(mux)
}
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
ReadHeaderTimeout: defaultHTTPReadHeaderTimeout,
}
errChan := make(chan error)
go s.receiveConfig(ctx)
go func() {
err := httpServer.ListenAndServe()
if err != nil {
if !errors.Is(err, http.ErrServerClosed) {
s.logger.Error(err, "Could not start diagnostics server")
errChan <- err
}
}
}()
s.logger.Info("Diagnostics server is starting to listen", "addr", port)
select {
case <-ctx.Done():
s.logger.Info("Shutting down diagnostics server")
return httpServer.Shutdown(context.Background()) //nolint:contextcheck
case err := <-errChan:
return err
}
}
// receiveConfig watches the config update channel.
func (s *Server) receiveConfig(ctx context.Context) {
for {
select {
case dump := <-s.configDumps.Configs:
s.configLock.Lock()
if dump.Failed {
s.failedConfigDump = dump.Config
} else {
s.successfulConfigDump = dump.Config
}
s.configLock.Unlock()
case <-ctx.Done():
if err := ctx.Err(); err != nil && !errors.Is(err, context.Canceled) {
s.logger.Error(err, "Shutting down diagnostic config collection: context completed with error")
return
}
s.logger.V(util.InfoLevel).Info("Shutting down diagnostic config collection: context completed")
return
}
}
}
// installProfilingHandlers adds the Profiling webservice to the given mux.
func installProfilingHandlers(mux *http.ServeMux) {
mux.HandleFunc("/debug/pprof", redirectTo("/debug/pprof/"))
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/heap", pprof.Index)
mux.HandleFunc("/debug/pprof/mutex", pprof.Index)
mux.HandleFunc("/debug/pprof/goroutine", pprof.Index)
mux.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
mux.HandleFunc("/debug/pprof/block", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
// installDumpHandlers adds the config dump webservice to the given mux.
func (s *Server) installDumpHandlers(mux *http.ServeMux) {
mux.HandleFunc("/debug/config/successful", s.handleLastValidConfig)
mux.HandleFunc("/debug/config/failed", s.handleLastFailedConfig)
}
// redirectTo redirects request to a certain destination.
func redirectTo(to string) func(http.ResponseWriter, *http.Request) {
return func(rw http.ResponseWriter, req *http.Request) {
http.Redirect(rw, req, to, http.StatusFound)
}
}
func (s *Server) handleLastValidConfig(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("Content-Type", "application/json")
s.configLock.RLock()
defer s.configLock.RUnlock()
if err := json.NewEncoder(rw).Encode(s.successfulConfigDump); err != nil {
rw.WriteHeader(http.StatusInternalServerError)
}
}
func (s *Server) handleLastFailedConfig(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("Content-Type", "application/json")
s.configLock.RLock()
defer s.configLock.RUnlock()
if err := json.NewEncoder(rw).Encode(s.failedConfigDump); err != nil {
rw.WriteHeader(http.StatusInternalServerError)
}
}