Remove global map from healthz

It currently is impossible to use two healthz handlers on different
ports in the same process.  This removes the global variables in favor
of requiring the consumer to specify all health checks up front.
This commit is contained in:
Clayton Coleman 2015-03-19 18:56:29 -04:00
parent 1ce5dda691
commit 015bc3b7bd
9 changed files with 100 additions and 73 deletions

View File

@ -32,7 +32,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
nodeControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller" nodeControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota" "github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota"
"github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/service"

View File

@ -26,12 +26,17 @@ import (
"runtime" "runtime"
"github.com/GoogleCloudPlatform/kubernetes/cmd/kube-controller-manager/app" "github.com/GoogleCloudPlatform/kubernetes/cmd/kube-controller-manager/app"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func init() {
healthz.DefaultHealthz()
}
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewCMServer() s := app.NewCMServer()

View File

@ -26,7 +26,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy" "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"

View File

@ -22,12 +22,17 @@ import (
"runtime" "runtime"
"github.com/GoogleCloudPlatform/kubernetes/cmd/kube-proxy/app" "github.com/GoogleCloudPlatform/kubernetes/cmd/kube-proxy/app"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func init() {
healthz.DefaultHealthz()
}
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewProxyServer() s := app.NewProxyServer()

View File

@ -20,73 +20,83 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"net/http" "net/http"
"sync"
) )
var ( // HealthzChecker is a named healthz check.
// guards names and checks type HealthzChecker interface {
lock = sync.RWMutex{} Name() string
// used to ensure checks are performed in the order added Check(req *http.Request) error
names = []string{}
checks = map[string]*healthzCheck{}
)
func init() {
http.HandleFunc("/healthz", handleRootHealthz)
// add ping health check by default
AddHealthzFunc("ping", func(_ *http.Request) error {
return nil
})
} }
// AddHealthzFunc adds a health check under the url /healhz/{name} // DefaultHealthz installs the default healthz check to the http.DefaultServeMux.
func AddHealthzFunc(name string, check func(r *http.Request) error) { func DefaultHealthz(checks ...HealthzChecker) {
lock.Lock() InstallHandler(http.DefaultServeMux, checks...)
defer lock.Unlock() }
if _, found := checks[name]; !found {
names = append(names, name) // PingHealthz returns true automatically when checked
} var PingHealthz HealthzChecker = ping{}
checks[name] = &healthzCheck{name, check}
// ping implements the simplest possible health checker.
type ping struct{}
func (ping) Name() string {
return "ping"
}
// PingHealthz is a health check that returns true.
func (ping) Check(_ *http.Request) error {
return nil
}
// NamedCheck returns a health checker for the given name and function.
func NamedCheck(name string, check func(r *http.Request) error) HealthzChecker {
return &healthzCheck{name, check}
} }
// InstallHandler registers a handler for health checking on the path "/healthz" to mux. // InstallHandler registers a handler for health checking on the path "/healthz" to mux.
func InstallHandler(mux mux) { func InstallHandler(mux mux, checks ...HealthzChecker) {
lock.RLock() if len(checks) == 0 {
defer lock.RUnlock() checks = []HealthzChecker{PingHealthz}
mux.HandleFunc("/healthz", handleRootHealthz) }
mux.Handle("/healthz", handleRootHealthz(checks...))
for _, check := range checks { for _, check := range checks {
mux.HandleFunc(fmt.Sprintf("/healthz/%v", check.name), adaptCheckToHandler(check.check)) mux.Handle(fmt.Sprintf("/healthz/%v", check.Name()), adaptCheckToHandler(check.Check))
} }
} }
// mux is an interface describing the methods InstallHandler requires. // mux is an interface describing the methods InstallHandler requires.
type mux interface { type mux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) Handle(pattern string, handler http.Handler)
} }
// healthzCheck implements HealthzChecker on an arbitrary name and check function.
type healthzCheck struct { type healthzCheck struct {
name string name string
check func(r *http.Request) error check func(r *http.Request) error
} }
func handleRootHealthz(w http.ResponseWriter, r *http.Request) { var _ HealthzChecker = &healthzCheck{}
lock.RLock()
defer lock.RUnlock() func (c *healthzCheck) Name() string {
return c.name
}
func (c *healthzCheck) Check(r *http.Request) error {
return c.check(r)
}
// handleRootHealthz returns an http.HandlerFunc that serves the provided checks.
func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
failed := false failed := false
var verboseOut bytes.Buffer var verboseOut bytes.Buffer
for _, name := range names { for _, check := range checks {
check, found := checks[name] err := check.Check(r)
if !found {
// this should not happen
http.Error(w, fmt.Sprintf("Internal server error: check \"%q\" not registered", name), http.StatusInternalServerError)
return
}
err := check.check(r)
if err != nil { if err != nil {
fmt.Fprintf(&verboseOut, "[-]%v failed: %v\n", check.name, err) fmt.Fprintf(&verboseOut, "[-]%v failed: %v\n", check.Name(), err)
failed = true failed = true
} else { } else {
fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.name) fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.Name())
} }
} }
// always be verbose on failure // always be verbose on failure
@ -98,19 +108,21 @@ func handleRootHealthz(w http.ResponseWriter, r *http.Request) {
if _, found := r.URL.Query()["verbose"]; !found { if _, found := r.URL.Query()["verbose"]; !found {
fmt.Fprint(w, "ok") fmt.Fprint(w, "ok")
return return
} else { }
verboseOut.WriteTo(w) verboseOut.WriteTo(w)
fmt.Fprint(w, "healthz check passed\n") fmt.Fprint(w, "healthz check passed\n")
} })
} }
func adaptCheckToHandler(c func(r *http.Request) error) func(w http.ResponseWriter, r *http.Request) { // adaptCheckToHandler returns an http.HandlerFunc that serves the provided checks.
return func(w http.ResponseWriter, r *http.Request) { func adaptCheckToHandler(c func(r *http.Request) error) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := c(r) err := c(r)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError)
} else { } else {
fmt.Fprint(w, "ok") fmt.Fprint(w, "ok")
} }
} })
} }

View File

@ -59,12 +59,13 @@ func TestMulitipleChecks(t *testing.T) {
for i, test := range tests { for i, test := range tests {
mux := http.NewServeMux() mux := http.NewServeMux()
checks := []HealthzChecker{PingHealthz}
if test.addBadCheck { if test.addBadCheck {
AddHealthzFunc("bad", func(_ *http.Request) error { checks = append(checks, NamedCheck("bad", func(_ *http.Request) error {
return errors.New("this will fail") return errors.New("this will fail")
}) }))
} }
InstallHandler(mux) InstallHandler(mux, checks...)
req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com%v", test.path), nil) req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com%v", test.path), nil)
if err != nil { if err != nil {
t.Fatalf("case[%d] Unexpected error: %v", i, err) t.Fatalf("case[%d] Unexpected error: %v", i, err)

View File

@ -110,9 +110,11 @@ func NewServer(host HostInterface, enableDebuggingHandlers bool) Server {
// InstallDefaultHandlers registers the default set of supported HTTP request patterns with the mux. // InstallDefaultHandlers registers the default set of supported HTTP request patterns with the mux.
func (s *Server) InstallDefaultHandlers() { func (s *Server) InstallDefaultHandlers() {
healthz.AddHealthzFunc("docker", s.dockerHealthCheck) healthz.InstallHandler(s.mux,
healthz.AddHealthzFunc("hostname", s.hostnameHealthCheck) healthz.PingHealthz,
healthz.InstallHandler(s.mux) healthz.NamedCheck("docker", s.dockerHealthCheck),
healthz.NamedCheck("hostname", s.hostnameHealthCheck),
)
s.mux.HandleFunc("/podInfo", s.handlePodInfoOld) s.mux.HandleFunc("/podInfo", s.handlePodInfoOld)
s.mux.HandleFunc("/api/v1beta1/podInfo", s.handlePodInfoVersioned) s.mux.HandleFunc("/api/v1beta1/podInfo", s.handlePodInfoVersioned)
s.mux.HandleFunc("/api/v1beta1/nodeInfo", s.handleNodeInfoVersioned) s.mux.HandleFunc("/api/v1beta1/nodeInfo", s.handleNodeInfoVersioned)

View File

@ -28,7 +28,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler"

View File

@ -19,6 +19,7 @@ package main
import ( import (
"runtime" "runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/GoogleCloudPlatform/kubernetes/plugin/cmd/kube-scheduler/app" "github.com/GoogleCloudPlatform/kubernetes/plugin/cmd/kube-scheduler/app"
@ -26,6 +27,10 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func init() {
healthz.DefaultHealthz()
}
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewSchedulerServer() s := app.NewSchedulerServer()