Skip to content

Commit c01da8c

Browse files
added health probes
1 parent 056a18a commit c01da8c

File tree

6 files changed

+761
-24
lines changed

6 files changed

+761
-24
lines changed

pkg/healthz/doc.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2014 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+
// This package is almost exact copy of apiserver's healthz package:
18+
// https://github.com/kubernetes/apiserver/tree/master/pkg/server/healthz
19+
//
20+
// Except that LogHealthz checker removed
21+
// and some style fixes is made to satisfy linters
22+
package healthz
23+
24+
import (
25+
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
26+
)
27+
28+
var log = logf.RuntimeLog.WithName("healthz")

pkg/healthz/healthz.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
Copyright 2014 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 healthz
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"net/http"
23+
"strings"
24+
25+
"k8s.io/apimachinery/pkg/util/sets"
26+
)
27+
28+
// HealthzChecker is a named healthz checker.
29+
type Checker interface {
30+
Name() string
31+
Check(req *http.Request) error
32+
}
33+
34+
// PingHealthz returns true automatically when checked
35+
var PingHealthz Checker = ping{}
36+
37+
// ping implements the simplest possible healthz checker.
38+
type ping struct{}
39+
40+
func (ping) Name() string {
41+
return "ping"
42+
}
43+
44+
// PingHealthz is a health check that returns true.
45+
func (ping) Check(_ *http.Request) error {
46+
return nil
47+
}
48+
49+
// NamedCheck returns a healthz checker for the given name and function.
50+
func NamedCheck(name string, check func(r *http.Request) error) Checker {
51+
return &healthzCheck{name, check}
52+
}
53+
54+
// InstallPathHandler registers handlers for health checking on
55+
// a specific path to mux. *All handlers* for the path must be
56+
// specified in exactly one call to InstallPathHandler. Calling
57+
// InstallPathHandler more than once for the same path and mux will
58+
// result in a panic.
59+
func InstallPathHandler(mux mux, path string, checks ...Checker) {
60+
if len(checks) == 0 {
61+
log.V(5).Info("No default health checks specified. Installing the ping handler.")
62+
checks = []Checker{PingHealthz}
63+
}
64+
65+
log.V(5).Info("Installing healthz checkers:", formatQuoted(checkerNames(checks...)...))
66+
67+
mux.Handle(path, handleRootHealthz(checks...))
68+
for _, check := range checks {
69+
mux.Handle(fmt.Sprintf("%s/%v", path, check.Name()), adaptCheckToHandler(check.Check))
70+
}
71+
}
72+
73+
// mux is an interface describing the methods InstallHandler requires.
74+
type mux interface {
75+
Handle(pattern string, handler http.Handler)
76+
}
77+
78+
// healthzCheck implements HealthzChecker on an arbitrary name and check function.
79+
type healthzCheck struct {
80+
name string
81+
check func(r *http.Request) error
82+
}
83+
84+
var _ Checker = &healthzCheck{}
85+
86+
func (c *healthzCheck) Name() string {
87+
return c.name
88+
}
89+
90+
func (c *healthzCheck) Check(r *http.Request) error {
91+
return c.check(r)
92+
}
93+
94+
// getExcludedChecks extracts the health check names to be excluded from the query param
95+
func getExcludedChecks(r *http.Request) sets.String {
96+
checks, found := r.URL.Query()["exclude"]
97+
if found {
98+
return sets.NewString(checks...)
99+
}
100+
return sets.NewString()
101+
}
102+
103+
// handleRootHealthz returns an http.HandlerFunc that serves the provided checks.
104+
func handleRootHealthz(checks ...Checker) http.HandlerFunc {
105+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
106+
failed := false
107+
excluded := getExcludedChecks(r)
108+
var verboseOut bytes.Buffer
109+
for _, check := range checks {
110+
// no-op the check if we've specified we want to exclude the check
111+
if excluded.Has(check.Name()) {
112+
excluded.Delete(check.Name())
113+
fmt.Fprintf(&verboseOut, "[+]%v excluded: ok\n", check.Name())
114+
continue
115+
}
116+
if err := check.Check(r); err != nil {
117+
// don't include the error since this endpoint is public. If someone wants more detail
118+
// they should have explicit permission to the detailed checks.
119+
log.V(4).Info(fmt.Sprintf("healthz check %v failed: %v", check.Name(), err))
120+
fmt.Fprintf(&verboseOut, "[-]%v failed: reason withheld\n", check.Name())
121+
failed = true
122+
} else {
123+
fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.Name())
124+
}
125+
}
126+
if excluded.Len() > 0 {
127+
fmt.Fprintf(&verboseOut, "warn: some health checks cannot be excluded: no matches for %v\n", formatQuoted(excluded.List()...))
128+
log.Info(fmt.Sprintf("cannot exclude some health checks, no health checks are installed matching %v",
129+
formatQuoted(excluded.List()...)))
130+
}
131+
// always be verbose on failure
132+
if failed {
133+
log.V(2).Info(fmt.Sprintf("%vhealthz check failed", verboseOut.String()))
134+
http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
135+
return
136+
}
137+
138+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
139+
w.Header().Set("X-Content-Type-Options", "nosniff")
140+
if _, found := r.URL.Query()["verbose"]; !found {
141+
fmt.Fprint(w, "ok")
142+
return
143+
}
144+
145+
_, err := verboseOut.WriteTo(w)
146+
if err != nil {
147+
log.V(2).Info(fmt.Sprintf("%vhealthz check failed", verboseOut.String()))
148+
http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
149+
return
150+
}
151+
fmt.Fprint(w, "healthz check passed\n")
152+
})
153+
}
154+
155+
// adaptCheckToHandler returns an http.HandlerFunc that serves the provided checks.
156+
func adaptCheckToHandler(c func(r *http.Request) error) http.HandlerFunc {
157+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
158+
err := c(r)
159+
if err != nil {
160+
http.Error(w, fmt.Sprintf("internal server error: %v", err), http.StatusInternalServerError)
161+
} else {
162+
fmt.Fprint(w, "ok")
163+
}
164+
})
165+
}
166+
167+
// checkerNames returns the names of the checks in the same order as passed in.
168+
func checkerNames(checks ...Checker) []string {
169+
// accumulate the names of checks for printing them out.
170+
checkerNames := make([]string, 0, len(checks))
171+
for _, check := range checks {
172+
checkerNames = append(checkerNames, check.Name())
173+
}
174+
return checkerNames
175+
}
176+
177+
// formatQuoted returns a formatted string of the health check names,
178+
// preserving the order passed in.
179+
func formatQuoted(names ...string) string {
180+
quoted := make([]string, 0, len(names))
181+
for _, name := range names {
182+
quoted = append(quoted, fmt.Sprintf("%q", name))
183+
}
184+
return strings.Join(quoted, ",")
185+
}

0 commit comments

Comments
 (0)