...

Source file src/github.com/datawire/ambassador/v2/cmd/entrypoint/healthcheck_server.go

Documentation: github.com/datawire/ambassador/v2/cmd/entrypoint

     1  package entrypoint
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"net/http"
     7  	"net/http/httputil"
     8  	"net/http/pprof"
     9  	"net/url"
    10  
    11  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    12  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    13  
    14  	"github.com/datawire/ambassador/v2/pkg/acp"
    15  	"github.com/datawire/ambassador/v2/pkg/debug"
    16  	"github.com/datawire/dlib/dhttp"
    17  )
    18  
    19  func handleCheckAlive(w http.ResponseWriter, r *http.Request, ambwatch *acp.AmbassadorWatcher) {
    20  	// The liveness check needs to explicitly try to talk to Envoy...
    21  	ambwatch.FetchEnvoyReady(r.Context())
    22  
    23  	// ...then check if the watcher says we're alive.
    24  	ok := ambwatch.IsAlive()
    25  
    26  	if ok {
    27  		_, _ = w.Write([]byte("Ambassador is alive and well\n"))
    28  	} else {
    29  		http.Error(w, "Ambassador is not alive\n", http.StatusServiceUnavailable)
    30  	}
    31  }
    32  
    33  func handleCheckReady(w http.ResponseWriter, r *http.Request, ambwatch *acp.AmbassadorWatcher) {
    34  	// The readiness check needs to explicitly try to talk to Envoy, too. Why?
    35  	// Because if you have a pod configured with only the readiness check but
    36  	// not the liveness check, and we don't try to talk to Envoy here, then we
    37  	// will never ever attempt to talk to Envoy at all, Envoy will never be
    38  	// declared alive, and we'll never consider Ambassador ready.
    39  	ambwatch.FetchEnvoyReady(r.Context())
    40  
    41  	ok := ambwatch.IsReady()
    42  
    43  	if ok {
    44  		_, _ = w.Write([]byte("Ambassador is ready and waiting\n"))
    45  	} else {
    46  		http.Error(w, "Ambassador is not ready\n", http.StatusServiceUnavailable)
    47  	}
    48  }
    49  
    50  func healthCheckHandler(ctx context.Context, ambwatch *acp.AmbassadorWatcher) error {
    51  	dbg := debug.FromContext(ctx)
    52  
    53  	// We need to do some HTTP stuff by hand to catch the readiness and liveness
    54  	// checks here, but forward everything else to diagd.
    55  	sm := http.NewServeMux()
    56  
    57  	// Handle the liveness check and the readiness check directly, by handing them
    58  	// off to our functions.
    59  
    60  	livenessTimer := dbg.Timer("check_alive")
    61  	sm.HandleFunc("/ambassador/v0/check_alive",
    62  		livenessTimer.TimedHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    63  			handleCheckAlive(w, r, ambwatch)
    64  		}))
    65  
    66  	readinessTimer := dbg.Timer("check_ready")
    67  	sm.HandleFunc("/ambassador/v0/check_ready",
    68  		readinessTimer.TimedHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    69  			handleCheckReady(w, r, ambwatch)
    70  		}))
    71  
    72  	// Serve any debug info from the golang codebase.
    73  	sm.Handle("/debug", dbg)
    74  
    75  	// Serve pprof endpoints to aid in live debugging.
    76  	sm.HandleFunc("/debug/pprof/", pprof.Index)
    77  
    78  	// For everything else, use a ReverseProxy to forward it to diagd.
    79  	//
    80  	// diagdOrigin is where diagd is listening.
    81  	diagdOrigin, _ := url.Parse("http://127.0.0.1:8004/")
    82  
    83  	// This reverseProxy is dirt simple: use a director function to
    84  	// swap the scheme and host of our request for the ones from the
    85  	// diagdOrigin. Leave everything else (notably including the path)
    86  	// alone.
    87  	reverseProxy := &httputil.ReverseProxy{
    88  		Director: func(req *http.Request) {
    89  			req.URL.Scheme = diagdOrigin.Scheme
    90  			req.URL.Host = diagdOrigin.Host
    91  
    92  			// If this request is coming from localhost, tell diagd about that.
    93  			if acp.HostPortIsLocal(req.RemoteAddr) {
    94  				req.Header.Set("X-Ambassador-Diag-IP", "127.0.0.1")
    95  			}
    96  		},
    97  	}
    98  
    99  	// Finally, use the reverseProxy to handle anything coming in on
   100  	// the magic catchall path.
   101  	sm.HandleFunc("/", reverseProxy.ServeHTTP)
   102  
   103  	// Create a listener by hand, so that we can listen on TCP v4. If we don't
   104  	// explicitly say "tcp4" here, we seem to listen _only_ on v6, and Bad Things
   105  	// Happen.
   106  	//
   107  	// XXX Why, exactly, is this? That's a lovely question -- we _should_ be OK
   108  	// here on a proper dualstack system, but apparently we don't have a proper
   109  	// dualstack system? It's quite bizarre, but Kubernetes won't become ready
   110  	// without this.
   111  	//
   112  	// XXX In fact, should we set up another Listener for v6??
   113  	listener, err := net.Listen("tcp4", ":8877")
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	s := &dhttp.ServerConfig{
   119  		Handler: sm,
   120  	}
   121  
   122  	return s.Serve(ctx, listener)
   123  }
   124  

View as plain text