Skip to content

Commit 980917c

Browse files
authored
fix: handle health checks in admin not proxy handler (#1448)
A fix in 0.12.0 made empty hosts wildcard matchers which triggered a bug forwarding health check probes to user applications. The smart health check detection logic in the proxy handler was replaced by having the admin handler respond to health check requests. In theory there is a behavioral change as only headers were used before to detect health check probes, we now rely on the configured /healthz and /readyz endpoints. Signed-off-by: Vincent Link <[email protected]>
1 parent 24f6f48 commit 980917c

12 files changed

Lines changed: 103 additions & 179 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ This changelog keeps track of work items that have been completed and are ready
3737

3838
### Fixes
3939

40-
- **General**: TODO ([#TODO](https://github.com/kedacore/http-add-on/issues/TODO))
40+
- **Interceptor**: Fix health probes failing when HTTPScaledObject has no hosts configured ([#1447](https://github.com/kedacore/http-add-on/issues/1447))
4141

4242
### Deprecations
4343

config/interceptor/deployment.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ spec:
6464
livenessProbe:
6565
httpGet:
6666
path: /livez
67-
port: proxy
67+
port: admin
6868
readinessProbe:
6969
httpGet:
7070
path: /readyz
71-
port: proxy
71+
port: admin
7272
# TODO(pedrotorres): set better default values avoiding overcommitment
7373
resources:
7474
requests:

interceptor/admin.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-logr/logr"
7+
8+
"github.com/kedacore/http-add-on/pkg/queue"
9+
)
10+
11+
// BuildAdminHandler creates the handler for the admin endpoint.
12+
func BuildAdminHandler(logger logr.Logger, counter queue.Counter, probeHandler http.Handler) http.Handler {
13+
mux := http.NewServeMux()
14+
15+
mux.Handle("/readyz", probeHandler)
16+
mux.Handle("/livez", probeHandler)
17+
18+
queue.AddCountsRoute(
19+
logger,
20+
mux,
21+
counter,
22+
)
23+
24+
return mux
25+
}

interceptor/admin_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/go-logr/logr"
9+
)
10+
11+
func TestAdminHandler(t *testing.T) {
12+
tests := map[string]struct {
13+
path string
14+
wantProbeCalled bool
15+
wantStatus int
16+
}{
17+
"livez": {
18+
path: "/livez",
19+
wantProbeCalled: true,
20+
wantStatus: http.StatusOK,
21+
},
22+
"readyz": {
23+
path: "/readyz",
24+
wantProbeCalled: true,
25+
wantStatus: http.StatusOK,
26+
},
27+
"other": {
28+
path: "/other",
29+
wantProbeCalled: false,
30+
wantStatus: http.StatusNotFound,
31+
},
32+
}
33+
34+
for name, tt := range tests {
35+
t.Run(name, func(t *testing.T) {
36+
probeCalled := false
37+
probeHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
38+
probeCalled = true
39+
w.WriteHeader(http.StatusOK)
40+
})
41+
42+
handler := BuildAdminHandler(logr.Discard(), nil, probeHandler)
43+
44+
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
45+
rec := httptest.NewRecorder()
46+
handler.ServeHTTP(rec, req)
47+
48+
if probeCalled != tt.wantProbeCalled {
49+
t.Error("expected probe to be called")
50+
}
51+
if rec.Code != tt.wantStatus {
52+
t.Errorf("got status = %d, want %d", rec.Code, tt.wantStatus)
53+
}
54+
})
55+
}
56+
}

interceptor/handler/probe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Probe struct {
1414
healthy atomic.Bool
1515
}
1616

17-
func NewProbe(healthChecks []util.HealthChecker) *Probe {
17+
func NewProbe(healthChecks ...util.HealthChecker) *Probe {
1818
return &Probe{
1919
healthCheckers: healthChecks,
2020
}

interceptor/handler/probe_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@ var _ = Describe("ProbeHandler", func() {
2424
)
2525

2626
var b bool
27-
healthCheckers := []util.HealthChecker{
28-
util.HealthCheckerFunc(func(_ context.Context) error {
29-
b = true
27+
healthCheckers := util.HealthCheckerFunc(func(_ context.Context) error {
28+
b = true
3029

31-
return ret
32-
}),
33-
}
30+
return ret
31+
})
3432

3533
ph := NewProbe(healthCheckers)
3634
Expect(ph).NotTo(BeNil())

interceptor/main.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func main() {
189189
eg.Go(func() error {
190190
setupLog.Info("starting the admin server", "port", adminPort)
191191

192-
if err := runAdminServer(ctx, ctrl.Log, queues, adminPort); !util.IsIgnoredErr(err) {
192+
if err := runAdminServer(ctx, ctrl.Log, adminPort, queues, routingTable); !util.IsIgnoredErr(err) {
193193
setupLog.Error(err, "admin server failed")
194194
return err
195195
}
@@ -271,20 +271,20 @@ func main() {
271271
func runAdminServer(
272272
ctx context.Context,
273273
lggr logr.Logger,
274-
q queue.Counter,
275274
port int,
275+
q queue.Counter,
276+
routingTable routing.Table,
276277
) error {
277278
lggr = lggr.WithName("runAdminServer")
278-
adminServer := http.NewServeMux()
279-
queue.AddCountsRoute(
280-
lggr,
281-
adminServer,
282-
q,
283-
)
279+
280+
probeHandler := handler.NewProbe(routingTable)
281+
go probeHandler.Start(ctx)
282+
283+
adminHandler := BuildAdminHandler(lggr, q, probeHandler)
284284

285285
addr := fmt.Sprintf("0.0.0.0:%d", port)
286286
lggr.Info("admin server starting", "address", addr)
287-
return kedahttp.ServeContext(ctx, addr, adminServer, nil)
287+
return kedahttp.ServeContext(ctx, addr, adminHandler, nil)
288288
}
289289

290290
func runMetricsServer(
@@ -310,18 +310,12 @@ func runProxyServer(
310310
tlsCfg *tls.Config,
311311
tracingConfig config.Tracing,
312312
) error {
313-
probeHandler := handler.NewProbe([]util.HealthChecker{
314-
routingTable,
315-
})
316-
go probeHandler.Start(ctx)
317-
318313
// Build handler chain using the shared builder
319314
rootHandler := BuildProxyHandler(&ProxyHandlerConfig{
320315
Logger: logger,
321316
Queue: q,
322317
WaitFunc: waitFunc,
323318
RoutingTable: routingTable,
324-
ProbeHandler: probeHandler,
325319
Reader: reader,
326320
Timeouts: timeouts,
327321
Serving: serving,

interceptor/middleware/routing.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net/http"
77
"net/url"
8-
"regexp"
98

109
corev1 "k8s.io/api/core/v1"
1110
"k8s.io/apimachinery/pkg/types"
@@ -17,24 +16,16 @@ import (
1716
"github.com/kedacore/http-add-on/pkg/util"
1817
)
1918

20-
var (
21-
kubernetesProbeUserAgent = regexp.MustCompile(`(^|\s)kube-probe/`)
22-
googleHCUserAgent = regexp.MustCompile(`(^|\s)GoogleHC/`)
23-
awsELBserAgent = regexp.MustCompile(`(^|\s)ELB-HealthChecker/`)
24-
)
25-
2619
type Routing struct {
2720
routingTable routing.Table
28-
probeHandler http.Handler
2921
upstreamHandler http.Handler
3022
reader client.Reader
3123
tlsEnabled bool
3224
}
3325

34-
func NewRouting(routingTable routing.Table, probeHandler http.Handler, upstreamHandler http.Handler, reader client.Reader, tlsEnabled bool) *Routing {
26+
func NewRouting(routingTable routing.Table, upstreamHandler http.Handler, reader client.Reader, tlsEnabled bool) *Routing {
3527
return &Routing{
3628
routingTable: routingTable,
37-
probeHandler: probeHandler,
3829
upstreamHandler: upstreamHandler,
3930
reader: reader,
4031
tlsEnabled: tlsEnabled,
@@ -46,11 +37,6 @@ var _ http.Handler = (*Routing)(nil)
4637
func (rm *Routing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4738
httpso := rm.routingTable.Route(r)
4839
if httpso == nil {
49-
if rm.isProbe(r) {
50-
rm.probeHandler.ServeHTTP(w, r)
51-
return
52-
}
53-
5440
sh := handler.NewStatic(http.StatusNotFound, nil)
5541
sh.ServeHTTP(w, r)
5642

@@ -130,9 +116,3 @@ func (rm *Routing) streamFromHTTPSO(ctx context.Context, httpso *httpv1alpha1.HT
130116
Host: fmt.Sprintf("%s.%s:%d", reference.GetServiceName(), httpso.GetNamespace(), port),
131117
}, nil
132118
}
133-
134-
func (rm *Routing) isProbe(r *http.Request) bool {
135-
ua := r.UserAgent()
136-
137-
return kubernetesProbeUserAgent.Match([]byte(ua)) || googleHCUserAgent.Match([]byte(ua)) || awsELBserAgent.Match([]byte(ua))
138-
}

0 commit comments

Comments
 (0)