Skip to content

Commit 8f777f5

Browse files
authored
Add probes to IntegrationSource & -Sink deployments (#8867)
* Add probes to IntegrationSource deployments * Fix auth-proxy SINK_URI missing value error The auth-proxy container was crashing with "required key SINK_URI missing value" due to a circular dependency in the reconciliation order. The deployment (with auth-proxy) was being created before the IntegrationSink status.Address was set, causing the auth-proxy to fail during startup. This commit fixes the issue by deriving the SINK_URI directly from the sink name and namespace (using network.GetServiceHostname()) instead of reading it from status.Address. This matches how the reconcileAddress() function constructs the URL, but without requiring the status to be set first. The same approach is now used for the SINK_AUDIENCE when OIDC authentication is enabled. This eliminates the circular dependency and ensures the auth-proxy always has a valid SINK_URI, regardless of reconciliation timing. * Add readiness check to auth-proxy * Fix unit tests * Use same timings for auth-proxy probes
1 parent 4ee1519 commit 8f777f5

9 files changed

Lines changed: 451 additions & 11 deletions

File tree

cmd/auth_proxy/main.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ package main
2222

2323
import (
2424
"context"
25+
"errors"
2526
"net"
2627
"os"
2728
"sync"
29+
"time"
2830

2931
//nolint:gosec
3032
"crypto/tls"
@@ -70,6 +72,7 @@ type envConfig struct {
7072
TargetHTTPSPort int `envconfig:"TARGET_HTTPS_PORT" default:"8443"`
7173
ProxyHTTPPort int `envconfig:"PROXY_HTTP_PORT" default:"3128"`
7274
ProxyHTTPSPort int `envconfig:"PROXY_HTTPS_PORT" default:"3129"`
75+
ProxyHealthPort int `envconfig:"PROXY_HEALTH_PORT" default:"8090"`
7376

7477
SinkURI string `envconfig:"SINK_URI" required:"true"`
7578
SinkNamespace string `envconfig:"SINK_NAMESPACE" required:"true"`
@@ -143,6 +146,20 @@ func main() {
143146
logger.Fatalw("Failed to start services", zap.Error(err))
144147
}
145148

149+
// Start health server
150+
healthServer := startHealthServer(config, handler)
151+
go func() {
152+
logger.Infof("Starting health server on port %d", config.ProxyHealthPort)
153+
if err := healthServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
154+
logger.Errorw("Health server error", zap.Error(err))
155+
}
156+
}()
157+
defer func() {
158+
if err := healthServer.Shutdown(context.Background()); err != nil {
159+
logger.Errorw("Error shutting down health server", zap.Error(err))
160+
}
161+
}()
162+
146163
logger.Info("Starting auth proxy servers...")
147164
if err = serverManager.StartServers(ctx); err != nil {
148165
logger.Fatalw("StartServers() returned an error", zap.Error(err))
@@ -317,6 +334,34 @@ func (h *ProxyHandler) invalidateSubjectsCache(logger *zap.SugaredLogger) {
317334
logger.Debug("Invalidated subjects cache due to EventPolicy change")
318335
}
319336

337+
// startHealthServer creates and returns an HTTP server for health checks
338+
func startHealthServer(config envConfig, handler *ProxyHandler) *http.Server {
339+
mux := http.NewServeMux()
340+
341+
// Readiness endpoint - checks if auth-proxy is ready to validate tokens
342+
mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
343+
if handler.authVerifier.IsReady() {
344+
w.WriteHeader(http.StatusOK)
345+
_, _ = w.Write([]byte("ready"))
346+
} else {
347+
w.WriteHeader(http.StatusServiceUnavailable)
348+
_, _ = w.Write([]byte("not ready"))
349+
}
350+
})
351+
352+
// Liveness endpoint - always returns OK if the server is running
353+
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
354+
w.WriteHeader(http.StatusOK)
355+
_, _ = w.Write([]byte("alive"))
356+
})
357+
358+
return &http.Server{
359+
Addr: fmt.Sprintf(":%d", config.ProxyHealthPort),
360+
Handler: mux,
361+
ReadHeaderTimeout: 10 * time.Second,
362+
}
363+
}
364+
320365
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
321366
ctx := h.withContext(r.Context())
322367
logger := logging.FromContext(ctx)

pkg/auth/verifier.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,15 @@ func (v *Verifier) initOIDCProvider(ctx context.Context, features feature.Flags)
298298
return nil
299299
}
300300

301+
// IsReady returns true if the OIDC provider has been initialized and the verifier
302+
// is ready to validate tokens. This is used by health checks to ensure the auth-proxy
303+
// doesn't receive traffic before it can properly validate authentication.
304+
func (v *Verifier) IsReady() bool {
305+
v.m.RLock()
306+
defer v.m.RUnlock()
307+
return v.provider != nil
308+
}
309+
301310
func (v *Verifier) getHTTPClientForKubeAPIServer() (*http.Client, error) {
302311
client, err := rest.HTTPClientFor(v.restConfig)
303312
if err != nil {

pkg/reconciler/integration/helper.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strings"
2424

2525
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/util/intstr"
2627
)
2728

2829
func GenerateEnvVarsFromStruct(prefix string, s interface{}) []corev1.EnvVar {
@@ -132,3 +133,54 @@ func Labels(name string) map[string]string {
132133
"app.kubernetes.io/name": name,
133134
}
134135
}
136+
137+
func LivenessProbe(port int32, scheme corev1.URIScheme) *corev1.Probe {
138+
return &corev1.Probe{
139+
FailureThreshold: 3,
140+
ProbeHandler: corev1.ProbeHandler{
141+
HTTPGet: &corev1.HTTPGetAction{
142+
Path: "/q/health/live",
143+
Port: intstr.FromInt32(port),
144+
Scheme: scheme,
145+
},
146+
},
147+
InitialDelaySeconds: 5,
148+
PeriodSeconds: 10,
149+
SuccessThreshold: 1,
150+
TimeoutSeconds: 10,
151+
}
152+
}
153+
154+
func ReadinessProbe(port int32, scheme corev1.URIScheme) *corev1.Probe {
155+
return &corev1.Probe{
156+
FailureThreshold: 3,
157+
ProbeHandler: corev1.ProbeHandler{
158+
HTTPGet: &corev1.HTTPGetAction{
159+
Path: "/q/health/ready",
160+
Port: intstr.FromInt32(port),
161+
Scheme: scheme,
162+
},
163+
},
164+
InitialDelaySeconds: 5,
165+
PeriodSeconds: 10,
166+
SuccessThreshold: 1,
167+
TimeoutSeconds: 10,
168+
}
169+
}
170+
171+
func StartupProbe(port int32, scheme corev1.URIScheme) *corev1.Probe {
172+
return &corev1.Probe{
173+
FailureThreshold: 3,
174+
ProbeHandler: corev1.ProbeHandler{
175+
HTTPGet: &corev1.HTTPGetAction{
176+
Path: "/q/health/started",
177+
Port: intstr.FromInt32(port),
178+
Scheme: scheme,
179+
},
180+
},
181+
InitialDelaySeconds: 5,
182+
PeriodSeconds: 10,
183+
SuccessThreshold: 1,
184+
TimeoutSeconds: 10,
185+
}
186+
}

pkg/reconciler/integration/sink/integrationsink_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,48 @@ func makeDeployment(sink *sinksv1alpha1.IntegrationSink, ready *corev1.Condition
363363
ReadOnly: true,
364364
},
365365
},
366+
LivenessProbe: &corev1.Probe{
367+
FailureThreshold: 3,
368+
ProbeHandler: corev1.ProbeHandler{
369+
HTTPGet: &corev1.HTTPGetAction{
370+
Path: "/q/health/live",
371+
Port: intstr.FromInt32(8080),
372+
Scheme: corev1.URISchemeHTTP,
373+
},
374+
},
375+
InitialDelaySeconds: 5,
376+
PeriodSeconds: 10,
377+
SuccessThreshold: 1,
378+
TimeoutSeconds: 10,
379+
},
380+
ReadinessProbe: &corev1.Probe{
381+
FailureThreshold: 3,
382+
ProbeHandler: corev1.ProbeHandler{
383+
HTTPGet: &corev1.HTTPGetAction{
384+
Path: "/q/health/ready",
385+
Port: intstr.FromInt32(8080),
386+
Scheme: corev1.URISchemeHTTP,
387+
},
388+
},
389+
InitialDelaySeconds: 5,
390+
PeriodSeconds: 10,
391+
SuccessThreshold: 1,
392+
TimeoutSeconds: 10,
393+
},
394+
StartupProbe: &corev1.Probe{
395+
FailureThreshold: 3,
396+
ProbeHandler: corev1.ProbeHandler{
397+
HTTPGet: &corev1.HTTPGetAction{
398+
Path: "/q/health/started",
399+
Port: intstr.FromInt32(8080),
400+
Scheme: corev1.URISchemeHTTP,
401+
},
402+
},
403+
InitialDelaySeconds: 5,
404+
PeriodSeconds: 10,
405+
SuccessThreshold: 1,
406+
TimeoutSeconds: 10,
407+
},
366408
},
367409
},
368410
},
@@ -385,21 +427,25 @@ func makeDeploymentWithAuthProxy(sink *sinksv1alpha1.IntegrationSink, ready *cor
385427
Ports: []corev1.ContainerPort{
386428
{ContainerPort: 3128, Protocol: corev1.ProtocolTCP, Name: "http"},
387429
{ContainerPort: 3129, Protocol: corev1.ProtocolTCP, Name: "https"},
430+
{ContainerPort: 8090, Protocol: corev1.ProtocolTCP, Name: "health"},
388431
},
389432
Env: []corev1.EnvVar{
390433
{Name: "TARGET_HTTP_PORT", Value: "8080"},
391434
{Name: "TARGET_HTTPS_PORT", Value: "8443"},
392435
{Name: "PROXY_HTTP_PORT", Value: "3128"},
393436
{Name: "PROXY_HTTPS_PORT", Value: "3129"},
437+
{Name: "PROXY_HEALTH_PORT", Value: "8090"},
394438
{Name: "SYSTEM_NAMESPACE", Value: system.Namespace()},
395439
{Name: "PARENT_API_VERSION", Value: "sinks.knative.dev/v1alpha1"},
396440
{Name: "PARENT_KIND", Value: "IntegrationSink"},
397441
{Name: "PARENT_NAME", Value: sink.Name},
398442
{Name: "PARENT_NAMESPACE", Value: sink.Namespace},
399443
{Name: "SINK_NAMESPACE", Value: sink.Namespace},
444+
{Name: "SINK_URI", Value: "http://" + deploymentName + "." + sink.Namespace + ".svc.cluster.local"},
400445
{Name: "SINK_TLS_CERT_FILE", Value: "/etc/" + certificates.CertificateName(sink.Name) + "/tls.crt"},
401446
{Name: "SINK_TLS_KEY_FILE", Value: "/etc/" + certificates.CertificateName(sink.Name) + "/tls.key"},
402447
{Name: "SINK_TLS_CA_FILE", Value: "/etc/" + certificates.CertificateName(sink.Name) + "/ca.crt"},
448+
{Name: "SINK_AUDIENCE", Value: "sinks.knative.dev/integrationsink/" + sink.Namespace + "/" + sink.Name},
403449
},
404450
VolumeMounts: []corev1.VolumeMount{
405451
{
@@ -408,6 +454,48 @@ func makeDeploymentWithAuthProxy(sink *sinksv1alpha1.IntegrationSink, ready *cor
408454
ReadOnly: true,
409455
},
410456
},
457+
ReadinessProbe: &corev1.Probe{
458+
ProbeHandler: corev1.ProbeHandler{
459+
HTTPGet: &corev1.HTTPGetAction{
460+
Path: "/readyz",
461+
Port: intstr.FromInt32(8090),
462+
Scheme: corev1.URISchemeHTTP,
463+
},
464+
},
465+
InitialDelaySeconds: 5,
466+
PeriodSeconds: 10,
467+
SuccessThreshold: 1,
468+
FailureThreshold: 3,
469+
TimeoutSeconds: 10,
470+
},
471+
LivenessProbe: &corev1.Probe{
472+
ProbeHandler: corev1.ProbeHandler{
473+
HTTPGet: &corev1.HTTPGetAction{
474+
Path: "/healthz",
475+
Port: intstr.FromInt32(8090),
476+
Scheme: corev1.URISchemeHTTP,
477+
},
478+
},
479+
InitialDelaySeconds: 5,
480+
PeriodSeconds: 10,
481+
SuccessThreshold: 1,
482+
FailureThreshold: 3,
483+
TimeoutSeconds: 10,
484+
},
485+
StartupProbe: &corev1.Probe{
486+
ProbeHandler: corev1.ProbeHandler{
487+
HTTPGet: &corev1.HTTPGetAction{
488+
Path: "/readyz",
489+
Port: intstr.FromInt32(8090),
490+
Scheme: corev1.URISchemeHTTP,
491+
},
492+
},
493+
InitialDelaySeconds: 5,
494+
PeriodSeconds: 10,
495+
SuccessThreshold: 1,
496+
FailureThreshold: 3,
497+
TimeoutSeconds: 10,
498+
},
411499
}
412500

413501
d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, authProxyContainer)

0 commit comments

Comments
 (0)