Skip to content

Commit f56cbfa

Browse files
committed
add healthz to genericapiserver
1 parent 37122c2 commit f56cbfa

File tree

10 files changed

+132
-30
lines changed

10 files changed

+132
-30
lines changed

Diff for: cmd/kube-apiserver/app/server.go

+1
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ func Run(s *options.ServerRunOptions) error {
305305
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
306306
genericConfig.OpenAPIConfig.Definitions = generatedopenapi.OpenAPIDefinitions
307307
genericConfig.EnableOpenAPISupport = true
308+
genericConfig.EnableMetrics = true
308309
genericConfig.OpenAPIConfig.SecurityDefinitions = securityDefinitions
309310

310311
config := &master.Config{

Diff for: pkg/genericapiserver/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ func (c completedConfig) New() (*GenericAPIServer, error) {
449449

450450
enableOpenAPISupport: c.EnableOpenAPISupport,
451451
openAPIConfig: c.OpenAPIConfig,
452+
453+
postStartHooks: map[string]postStartHookEntry{},
452454
}
453455

454456
s.HandlerContainer = mux.NewAPIContainer(http.NewServeMux(), c.Serializer)

Diff for: pkg/genericapiserver/genericapiserver.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
genericmux "k8s.io/kubernetes/pkg/genericapiserver/mux"
4343
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
4444
"k8s.io/kubernetes/pkg/genericapiserver/routes"
45+
"k8s.io/kubernetes/pkg/healthz"
4546
"k8s.io/kubernetes/pkg/runtime"
4647
utilnet "k8s.io/kubernetes/pkg/util/net"
4748
"k8s.io/kubernetes/pkg/util/sets"
@@ -143,10 +144,15 @@ type GenericAPIServer struct {
143144
// PostStartHooks are each called after the server has started listening, in a separate go func for each
144145
// with no guaranteee of ordering between them. The map key is a name used for error reporting.
145146
// It may kill the process with a panic if it wishes to by returning an error
146-
postStartHooks map[string]PostStartHookFunc
147147
postStartHookLock sync.Mutex
148+
postStartHooks map[string]postStartHookEntry
148149
postStartHooksCalled bool
149150

151+
// healthz checks
152+
healthzLock sync.Mutex
153+
healthzChecks []healthz.HealthzChecker
154+
healthzCreated bool
155+
150156
// See Config.$name for documentation of these flags:
151157

152158
MasterCount int
@@ -188,6 +194,9 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
188194
Config: s.openAPIConfig,
189195
}.Install(s.HandlerContainer)
190196
}
197+
198+
s.installHealthz()
199+
191200
return preparedGenericAPIServer{s}
192201
}
193202

Diff for: pkg/genericapiserver/healthz.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2016 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package genericapiserver
18+
19+
import (
20+
"fmt"
21+
22+
"k8s.io/kubernetes/pkg/healthz"
23+
)
24+
25+
// AddHealthzCheck allows you to add a HealthzCheck.
26+
func (s *GenericAPIServer) AddHealthzChecks(checks ...healthz.HealthzChecker) error {
27+
s.healthzLock.Lock()
28+
defer s.healthzLock.Unlock()
29+
30+
if s.healthzCreated {
31+
return fmt.Errorf("unable to add because the healthz endpoint has already been created")
32+
}
33+
34+
s.healthzChecks = append(s.healthzChecks, checks...)
35+
return nil
36+
}
37+
38+
// installHealthz creates the healthz endpoint for this server
39+
func (s *GenericAPIServer) installHealthz() {
40+
s.healthzLock.Lock()
41+
defer s.healthzLock.Unlock()
42+
s.healthzCreated = true
43+
44+
healthz.InstallHandler(&s.HandlerContainer.NonSwaggerRoutes, s.healthzChecks...)
45+
}

Diff for: pkg/genericapiserver/hooks.go

+47-8
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ limitations under the License.
1717
package genericapiserver
1818

1919
import (
20+
"errors"
2021
"fmt"
22+
"net/http"
2123

2224
"github.com/golang/glog"
2325

2426
"k8s.io/kubernetes/pkg/client/restclient"
27+
"k8s.io/kubernetes/pkg/healthz"
2528
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
2629
)
2730

@@ -47,6 +50,13 @@ type PostStartHookProvider interface {
4750
PostStartHook() (string, PostStartHookFunc, error)
4851
}
4952

53+
type postStartHookEntry struct {
54+
hook PostStartHookFunc
55+
56+
// done will be closed when the postHook is finished
57+
done chan struct{}
58+
}
59+
5060
// AddPostStartHook allows you to add a PostStartHook.
5161
func (s *GenericAPIServer) AddPostStartHook(name string, hook PostStartHookFunc) error {
5262
if len(name) == 0 {
@@ -62,14 +72,15 @@ func (s *GenericAPIServer) AddPostStartHook(name string, hook PostStartHookFunc)
6272
if s.postStartHooksCalled {
6373
return fmt.Errorf("unable to add %q because PostStartHooks have already been called", name)
6474
}
65-
if s.postStartHooks == nil {
66-
s.postStartHooks = map[string]PostStartHookFunc{}
67-
}
6875
if _, exists := s.postStartHooks[name]; exists {
6976
return fmt.Errorf("unable to add %q because it is already registered", name)
7077
}
7178

72-
s.postStartHooks[name] = hook
79+
// done is closed when the poststarthook is finished. This is used by the health check to be able to indicate
80+
// that the poststarthook is finished
81+
done := make(chan struct{})
82+
s.AddHealthzChecks(postStartHookHealthz{name: "poststarthook/" + name, done: done})
83+
s.postStartHooks[name] = postStartHookEntry{hook: hook, done: done}
7384

7485
return nil
7586
}
@@ -82,20 +93,48 @@ func (s *GenericAPIServer) RunPostStartHooks() {
8293

8394
context := PostStartHookContext{LoopbackClientConfig: s.LoopbackClientConfig}
8495

85-
for hookName, hook := range s.postStartHooks {
86-
go runPostStartHook(hookName, hook, context)
96+
for hookName, hookEntry := range s.postStartHooks {
97+
go runPostStartHook(hookName, hookEntry, context)
8798
}
8899
}
89100

90-
func runPostStartHook(name string, hook PostStartHookFunc, context PostStartHookContext) {
101+
func runPostStartHook(name string, entry postStartHookEntry, context PostStartHookContext) {
91102
var err error
92103
func() {
93104
// don't let the hook *accidentally* panic and kill the server
94105
defer utilruntime.HandleCrash()
95-
err = hook(context)
106+
err = entry.hook(context)
96107
}()
97108
// if the hook intentionally wants to kill server, let it.
98109
if err != nil {
99110
glog.Fatalf("PostStartHook %q failed: %v", name, err)
100111
}
112+
113+
close(entry.done)
114+
}
115+
116+
// postStartHookHealthz implements a healthz check for poststarthooks. It will return a "hookNotFinished"
117+
// error until the poststarthook is finished.
118+
type postStartHookHealthz struct {
119+
name string
120+
121+
// done will be closed when the postStartHook is finished
122+
done chan struct{}
123+
}
124+
125+
var _ healthz.HealthzChecker = postStartHookHealthz{}
126+
127+
func (h postStartHookHealthz) Name() string {
128+
return h.name
129+
}
130+
131+
var hookNotFinished = errors.New("not finished")
132+
133+
func (h postStartHookHealthz) Check(req *http.Request) error {
134+
select {
135+
case <-h.done:
136+
return nil
137+
default:
138+
return hookNotFinished
139+
}
101140
}

Diff for: pkg/master/master.go

+12-15
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ func (c completedConfig) New() (*Master, error) {
192192
}
193193
m.InstallAPIs(c.Config.GenericConfig.APIResourceConfigSource, restOptionsFactory.NewFor, restStorageProviders...)
194194

195+
if c.Tunneler != nil {
196+
m.installTunneler(c.Tunneler, coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes())
197+
}
195198
m.InstallGeneralEndpoints(c.Config)
196199

197200
return m, nil
@@ -215,29 +218,23 @@ func (m *Master) InstallLegacyAPI(c *Config, restOptionsGetter genericapiserver.
215218
}
216219
}
217220

221+
func (m *Master) installTunneler(tunneler genericapiserver.Tunneler, nodeClient coreclient.NodeInterface) {
222+
tunneler.Run(nodeAddressProvider{nodeClient}.externalAddresses)
223+
m.GenericAPIServer.AddHealthzChecks(healthz.NamedCheck("SSH Tunnel Check", genericapiserver.TunnelSyncHealthChecker(tunneler)))
224+
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
225+
Name: "apiserver_proxy_tunnel_sync_latency_secs",
226+
Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.",
227+
}, func() float64 { return float64(tunneler.SecondsSinceSync()) })
228+
}
229+
218230
// TODO this needs to be refactored so we have a way to add general health checks to genericapiserver
219231
// TODO profiling should be generic
220232
func (m *Master) InstallGeneralEndpoints(c *Config) {
221-
// Run the tunneler.
222-
healthzChecks := []healthz.HealthzChecker{}
223-
if c.Tunneler != nil {
224-
nodeClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes()
225-
c.Tunneler.Run(nodeAddressProvider{nodeClient}.externalAddresses)
226-
227-
healthzChecks = append(healthzChecks, healthz.NamedCheck("SSH Tunnel Check", genericapiserver.TunnelSyncHealthChecker(c.Tunneler)))
228-
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
229-
Name: "apiserver_proxy_tunnel_sync_latency_secs",
230-
Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.",
231-
}, func() float64 { return float64(c.Tunneler.SecondsSinceSync()) })
232-
}
233-
healthz.InstallHandler(&m.GenericAPIServer.HandlerContainer.NonSwaggerRoutes, healthzChecks...)
234-
235233
if c.GenericConfig.EnableProfiling {
236234
routes.MetricsWithReset{}.Install(m.GenericAPIServer.HandlerContainer)
237235
} else {
238236
routes.DefaultMetrics{}.Install(m.GenericAPIServer.HandlerContainer)
239237
}
240-
241238
}
242239

243240
// InstallAPIs will install the APIs for the restStorageProviders if they are enabled.

Diff for: pkg/master/master_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.
9090
config.GenericConfig.APIResourceConfigSource = DefaultAPIResourceConfigSource()
9191
config.GenericConfig.RequestContextMapper = api.NewRequestContextMapper()
9292
config.GenericConfig.LoopbackClientConfig = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: api.Codecs}}
93+
config.GenericConfig.EnableMetrics = true
9394
config.EnableCoreControllers = false
9495
config.KubeletClientConfig = kubeletclient.KubeletClientConfig{Port: 10250}
9596
config.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{

Diff for: test/integration/auth/rbac_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -475,4 +475,10 @@ func TestBootstrapping(t *testing.T) {
475475
}
476476

477477
t.Errorf("missing cluster-admin: %v", clusterRoles)
478+
479+
healthBytes, err := clientset.Discovery().RESTClient().Get().AbsPath("/healthz/poststarthooks/rbac/bootstrap-roles").DoRaw()
480+
if err != nil {
481+
t.Error(err)
482+
}
483+
t.Errorf("expected %v, got %v", "asdf", string(healthBytes))
478484
}

Diff for: test/integration/framework/master_utils.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv
235235
masterReceiver.SetMaster(m)
236236
}
237237

238+
// TODO have this start method actually use the normal start sequence for the API server
239+
// this method never actually calls the `Run` method for the API server
240+
// fire the post hooks ourselves
241+
m.GenericAPIServer.PrepareRun()
242+
m.GenericAPIServer.RunPostStartHooks()
243+
238244
cfg := *masterConfig.GenericConfig.LoopbackClientConfig
239245
cfg.ContentConfig.GroupVersion = &unversioned.GroupVersion{}
240246
privilegedClient, err := restclient.RESTClientFor(&cfg)
@@ -254,12 +260,6 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv
254260
glog.Fatal(err)
255261
}
256262

257-
// TODO have this start method actually use the normal start sequence for the API server
258-
// this method never actually calls the `Run` method for the API server
259-
// fire the post hooks ourselves
260-
m.GenericAPIServer.PrepareRun()
261-
m.GenericAPIServer.RunPostStartHooks()
262-
263263
// wait for services to be ready
264264
if masterConfig.EnableCoreControllers {
265265
// TODO Once /healthz is updated for posthooks, we'll wait for good health
@@ -350,6 +350,7 @@ func NewMasterConfig() *master.Config {
350350
genericConfig.APIResourceConfigSource = master.DefaultAPIResourceConfigSource()
351351
genericConfig.Authorizer = authorizer.NewAlwaysAllowAuthorizer()
352352
genericConfig.AdmissionControl = admit.NewAlwaysAdmit()
353+
genericConfig.EnableMetrics = true
353354

354355
return &master.Config{
355356
GenericConfig: genericConfig,

Diff for: test/integration/openshift/openshift_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func TestMasterExportsSymbols(t *testing.T) {
2929
_ = &master.Config{
3030
GenericConfig: &genericapiserver.Config{
3131
EnableSwaggerSupport: false,
32+
EnableMetrics: true,
3233
},
3334
EnableCoreControllers: false,
3435
EnableUISupport: false,

0 commit comments

Comments
 (0)