mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
Merge pull request #111661 from alexanderConstantinescu/etp-local-svc-hc-kube-proxy
[Proxy]: add `healthz` verification when determining HC response for eTP:Local
This commit is contained in:
commit
86bf570711
@ -122,6 +122,7 @@ type hcPayload struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
LocalEndpoints int
|
LocalEndpoints int
|
||||||
|
ServiceProxyHealthy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type healthzPayload struct {
|
type healthzPayload struct {
|
||||||
@ -129,12 +130,21 @@ type healthzPayload struct {
|
|||||||
CurrentTime string
|
CurrentTime string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeProxierHealthChecker struct {
|
||||||
|
healthy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fake fakeProxierHealthChecker) IsHealthy() bool {
|
||||||
|
return fake.healthy
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer(t *testing.T) {
|
func TestServer(t *testing.T) {
|
||||||
listener := newFakeListener()
|
listener := newFakeListener()
|
||||||
httpFactory := newFakeHTTPServerFactory()
|
httpFactory := newFakeHTTPServerFactory()
|
||||||
nodePortAddresses := utilproxy.NewNodePortAddresses([]string{})
|
nodePortAddresses := utilproxy.NewNodePortAddresses([]string{})
|
||||||
|
proxyChecker := &fakeProxierHealthChecker{true}
|
||||||
|
|
||||||
hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses)
|
hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses, proxyChecker)
|
||||||
hcs := hcsi.(*server)
|
hcs := hcsi.(*server)
|
||||||
if len(hcs.services) != 0 {
|
if len(hcs.services) != 0 {
|
||||||
t.Errorf("expected 0 services, got %d", len(hcs.services))
|
t.Errorf("expected 0 services, got %d", len(hcs.services))
|
||||||
@ -351,9 +361,29 @@ func TestServer(t *testing.T) {
|
|||||||
testHandler(hcs, nsn2, http.StatusServiceUnavailable, 0, t)
|
testHandler(hcs, nsn2, http.StatusServiceUnavailable, 0, t)
|
||||||
testHandler(hcs, nsn3, http.StatusOK, 7, t)
|
testHandler(hcs, nsn3, http.StatusOK, 7, t)
|
||||||
testHandler(hcs, nsn4, http.StatusOK, 6, t)
|
testHandler(hcs, nsn4, http.StatusOK, 6, t)
|
||||||
|
|
||||||
|
// fake a temporary unhealthy proxy
|
||||||
|
proxyChecker.healthy = false
|
||||||
|
testHandlerWithHealth(hcs, nsn2, http.StatusServiceUnavailable, 0, false, t)
|
||||||
|
testHandlerWithHealth(hcs, nsn3, http.StatusServiceUnavailable, 7, false, t)
|
||||||
|
testHandlerWithHealth(hcs, nsn4, http.StatusServiceUnavailable, 6, false, t)
|
||||||
|
|
||||||
|
// fake a healthy proxy
|
||||||
|
proxyChecker.healthy = true
|
||||||
|
testHandlerWithHealth(hcs, nsn2, http.StatusServiceUnavailable, 0, true, t)
|
||||||
|
testHandlerWithHealth(hcs, nsn3, http.StatusOK, 7, true, t)
|
||||||
|
testHandlerWithHealth(hcs, nsn4, http.StatusOK, 6, true, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testHandler(hcs *server, nsn types.NamespacedName, status int, endpoints int, t *testing.T) {
|
func testHandler(hcs *server, nsn types.NamespacedName, status int, endpoints int, t *testing.T) {
|
||||||
|
tHandler(hcs, nsn, status, endpoints, true, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHandlerWithHealth(hcs *server, nsn types.NamespacedName, status int, endpoints int, kubeProxyHealthy bool, t *testing.T) {
|
||||||
|
tHandler(hcs, nsn, status, endpoints, kubeProxyHealthy, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tHandler(hcs *server, nsn types.NamespacedName, status int, endpoints int, kubeProxyHealthy bool, t *testing.T) {
|
||||||
instance := hcs.services[nsn]
|
instance := hcs.services[nsn]
|
||||||
for _, h := range instance.httpServers {
|
for _, h := range instance.httpServers {
|
||||||
handler := h.(*fakeHTTPServer).handler
|
handler := h.(*fakeHTTPServer).handler
|
||||||
@ -379,6 +409,9 @@ func testHandler(hcs *server, nsn types.NamespacedName, status int, endpoints in
|
|||||||
if payload.LocalEndpoints != endpoints {
|
if payload.LocalEndpoints != endpoints {
|
||||||
t.Errorf("expected %d endpoints, got %d", endpoints, payload.LocalEndpoints)
|
t.Errorf("expected %d endpoints, got %d", endpoints, payload.LocalEndpoints)
|
||||||
}
|
}
|
||||||
|
if payload.ServiceProxyHealthy != kubeProxyHealthy {
|
||||||
|
t.Errorf("expected %v kubeProxyHealthy, got %v", kubeProxyHealthy, payload.ServiceProxyHealthy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,12 +467,13 @@ func testHealthzHandler(server httpServer, status int, t *testing.T) {
|
|||||||
func TestServerWithSelectiveListeningAddress(t *testing.T) {
|
func TestServerWithSelectiveListeningAddress(t *testing.T) {
|
||||||
listener := newFakeListener()
|
listener := newFakeListener()
|
||||||
httpFactory := newFakeHTTPServerFactory()
|
httpFactory := newFakeHTTPServerFactory()
|
||||||
|
proxyChecker := &fakeProxierHealthChecker{true}
|
||||||
|
|
||||||
// limiting addresses to loop back. We don't want any cleverness here around getting IP for
|
// limiting addresses to loop back. We don't want any cleverness here around getting IP for
|
||||||
// machine nor testing ipv6 || ipv4. using loop back guarantees the test will work on any machine
|
// machine nor testing ipv6 || ipv4. using loop back guarantees the test will work on any machine
|
||||||
nodePortAddresses := utilproxy.NewNodePortAddresses([]string{"127.0.0.0/8"})
|
nodePortAddresses := utilproxy.NewNodePortAddresses([]string{"127.0.0.0/8"})
|
||||||
|
|
||||||
hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses)
|
hcsi := newServiceHealthServer("hostname", nil, listener, httpFactory, nodePortAddresses, proxyChecker)
|
||||||
hcs := hcsi.(*server)
|
hcs := hcsi.(*server)
|
||||||
if len(hcs.services) != 0 {
|
if len(hcs.services) != 0 {
|
||||||
t.Errorf("expected 0 services, got %d", len(hcs.services))
|
t.Errorf("expected 0 services, got %d", len(hcs.services))
|
||||||
|
@ -41,6 +41,8 @@ type ProxierHealthUpdater interface {
|
|||||||
|
|
||||||
// Run starts the healthz HTTP server and blocks until it exits.
|
// Run starts the healthz HTTP server and blocks until it exits.
|
||||||
Run() error
|
Run() error
|
||||||
|
|
||||||
|
proxierHealthChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ProxierHealthUpdater = &proxierHealthServer{}
|
var _ ProxierHealthUpdater = &proxierHealthServer{}
|
||||||
@ -94,6 +96,37 @@ func (hs *proxierHealthServer) QueuedUpdate() {
|
|||||||
hs.oldestPendingQueued.CompareAndSwap(zeroTime, hs.clock.Now())
|
hs.oldestPendingQueued.CompareAndSwap(zeroTime, hs.clock.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsHealthy returns the proxier's health state, following the same definition
|
||||||
|
// the HTTP server defines.
|
||||||
|
func (hs *proxierHealthServer) IsHealthy() bool {
|
||||||
|
isHealthy, _, _ := hs.isHealthy()
|
||||||
|
return isHealthy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs *proxierHealthServer) isHealthy() (bool, time.Time, time.Time) {
|
||||||
|
var oldestPendingQueued, lastUpdated time.Time
|
||||||
|
if val := hs.oldestPendingQueued.Load(); val != nil {
|
||||||
|
oldestPendingQueued = val.(time.Time)
|
||||||
|
}
|
||||||
|
if val := hs.lastUpdated.Load(); val != nil {
|
||||||
|
lastUpdated = val.(time.Time)
|
||||||
|
}
|
||||||
|
currentTime := hs.clock.Now()
|
||||||
|
|
||||||
|
healthy := false
|
||||||
|
switch {
|
||||||
|
case oldestPendingQueued.IsZero():
|
||||||
|
// The proxy is healthy while it's starting up
|
||||||
|
// or the proxy is fully synced.
|
||||||
|
healthy = true
|
||||||
|
case currentTime.Sub(oldestPendingQueued) < hs.healthTimeout:
|
||||||
|
// There's an unprocessed update queued, but it's not late yet
|
||||||
|
healthy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return healthy, lastUpdated, currentTime
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts the healthz HTTP server and blocks until it exits.
|
// Run starts the healthz HTTP server and blocks until it exits.
|
||||||
func (hs *proxierHealthServer) Run() error {
|
func (hs *proxierHealthServer) Run() error {
|
||||||
serveMux := http.NewServeMux()
|
serveMux := http.NewServeMux()
|
||||||
@ -123,26 +156,7 @@ type healthzHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h healthzHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
func (h healthzHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||||
var oldestPendingQueued, lastUpdated time.Time
|
healthy, lastUpdated, currentTime := h.hs.isHealthy()
|
||||||
if val := h.hs.oldestPendingQueued.Load(); val != nil {
|
|
||||||
oldestPendingQueued = val.(time.Time)
|
|
||||||
}
|
|
||||||
if val := h.hs.lastUpdated.Load(); val != nil {
|
|
||||||
lastUpdated = val.(time.Time)
|
|
||||||
}
|
|
||||||
currentTime := h.hs.clock.Now()
|
|
||||||
|
|
||||||
healthy := false
|
|
||||||
switch {
|
|
||||||
case oldestPendingQueued.IsZero():
|
|
||||||
// The proxy is healthy while it's starting up
|
|
||||||
// or the proxy is fully synced.
|
|
||||||
healthy = true
|
|
||||||
case currentTime.Sub(oldestPendingQueued) < h.hs.healthTimeout:
|
|
||||||
// There's an unprocessed update queued, but it's not late yet
|
|
||||||
healthy = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Header().Set("Content-Type", "application/json")
|
resp.Header().Set("Content-Type", "application/json")
|
||||||
resp.Header().Set("X-Content-Type-Options", "nosniff")
|
resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
if !healthy {
|
if !healthy {
|
||||||
|
@ -52,7 +52,13 @@ type ServiceHealthServer interface {
|
|||||||
SyncEndpoints(newEndpoints map[types.NamespacedName]int) error
|
SyncEndpoints(newEndpoints map[types.NamespacedName]int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServiceHealthServer(hostname string, recorder events.EventRecorder, listener listener, factory httpServerFactory, nodePortAddresses *utilproxy.NodePortAddresses) ServiceHealthServer {
|
type proxierHealthChecker interface {
|
||||||
|
// IsHealthy returns the proxier's health state, following the same
|
||||||
|
// definition the HTTP server defines.
|
||||||
|
IsHealthy() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServiceHealthServer(hostname string, recorder events.EventRecorder, listener listener, factory httpServerFactory, nodePortAddresses *utilproxy.NodePortAddresses, healthzServer proxierHealthChecker) ServiceHealthServer {
|
||||||
|
|
||||||
nodeAddresses, err := nodePortAddresses.GetNodeAddresses(utilproxy.RealNetwork{})
|
nodeAddresses, err := nodePortAddresses.GetNodeAddresses(utilproxy.RealNetwork{})
|
||||||
if err != nil || nodeAddresses.Len() == 0 {
|
if err != nil || nodeAddresses.Len() == 0 {
|
||||||
@ -75,14 +81,15 @@ func newServiceHealthServer(hostname string, recorder events.EventRecorder, list
|
|||||||
recorder: recorder,
|
recorder: recorder,
|
||||||
listener: listener,
|
listener: listener,
|
||||||
httpFactory: factory,
|
httpFactory: factory,
|
||||||
|
healthzServer: healthzServer,
|
||||||
services: map[types.NamespacedName]*hcInstance{},
|
services: map[types.NamespacedName]*hcInstance{},
|
||||||
nodeAddresses: nodeAddresses,
|
nodeAddresses: nodeAddresses,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceHealthServer allocates a new service healthcheck server manager
|
// NewServiceHealthServer allocates a new service healthcheck server manager
|
||||||
func NewServiceHealthServer(hostname string, recorder events.EventRecorder, nodePortAddresses *utilproxy.NodePortAddresses) ServiceHealthServer {
|
func NewServiceHealthServer(hostname string, recorder events.EventRecorder, nodePortAddresses *utilproxy.NodePortAddresses, healthzServer proxierHealthChecker) ServiceHealthServer {
|
||||||
return newServiceHealthServer(hostname, recorder, stdNetListener{}, stdHTTPServerFactory{}, nodePortAddresses)
|
return newServiceHealthServer(hostname, recorder, stdNetListener{}, stdHTTPServerFactory{}, nodePortAddresses, healthzServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
@ -93,6 +100,8 @@ type server struct {
|
|||||||
listener listener
|
listener listener
|
||||||
httpFactory httpServerFactory
|
httpFactory httpServerFactory
|
||||||
|
|
||||||
|
healthzServer proxierHealthChecker
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
services map[types.NamespacedName]*hcInstance
|
services map[types.NamespacedName]*hcInstance
|
||||||
}
|
}
|
||||||
@ -226,14 +235,15 @@ func (h hcHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
count := svc.endpoints
|
count := svc.endpoints
|
||||||
|
kubeProxyHealthy := h.hcs.healthzServer.IsHealthy()
|
||||||
h.hcs.lock.RUnlock()
|
h.hcs.lock.RUnlock()
|
||||||
|
|
||||||
resp.Header().Set("Content-Type", "application/json")
|
resp.Header().Set("Content-Type", "application/json")
|
||||||
resp.Header().Set("X-Content-Type-Options", "nosniff")
|
resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
if count == 0 {
|
if count != 0 && kubeProxyHealthy {
|
||||||
resp.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
} else {
|
|
||||||
resp.WriteHeader(http.StatusOK)
|
resp.WriteHeader(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
resp.WriteHeader(http.StatusServiceUnavailable)
|
||||||
}
|
}
|
||||||
fmt.Fprint(resp, strings.Trim(dedent.Dedent(fmt.Sprintf(`
|
fmt.Fprint(resp, strings.Trim(dedent.Dedent(fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
@ -241,9 +251,10 @@ func (h hcHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
|||||||
"namespace": %q,
|
"namespace": %q,
|
||||||
"name": %q
|
"name": %q
|
||||||
},
|
},
|
||||||
"localEndpoints": %d
|
"localEndpoints": %d,
|
||||||
|
"serviceProxyHealthy": %v
|
||||||
}
|
}
|
||||||
`, h.name.Namespace, h.name.Name, count)), "\n"))
|
`, h.name.Namespace, h.name.Name, count, kubeProxyHealthy)), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hcs *server) SyncEndpoints(newEndpoints map[types.NamespacedName]int) error {
|
func (hcs *server) SyncEndpoints(newEndpoints map[types.NamespacedName]int) error {
|
||||||
|
@ -268,7 +268,7 @@ func NewProxier(ipFamily v1.IPFamily,
|
|||||||
masqueradeMark := fmt.Sprintf("%#08x", masqueradeValue)
|
masqueradeMark := fmt.Sprintf("%#08x", masqueradeValue)
|
||||||
klog.V(2).InfoS("Using iptables mark for masquerade", "ipFamily", ipt.Protocol(), "mark", masqueradeMark)
|
klog.V(2).InfoS("Using iptables mark for masquerade", "ipFamily", ipt.Protocol(), "mark", masqueradeMark)
|
||||||
|
|
||||||
serviceHealthServer := healthcheck.NewServiceHealthServer(hostname, recorder, nodePortAddresses)
|
serviceHealthServer := healthcheck.NewServiceHealthServer(hostname, recorder, nodePortAddresses, healthzServer)
|
||||||
|
|
||||||
proxier := &Proxier{
|
proxier := &Proxier{
|
||||||
svcPortMap: make(proxy.ServicePortMap),
|
svcPortMap: make(proxy.ServicePortMap),
|
||||||
|
@ -411,7 +411,7 @@ func NewProxier(ipFamily v1.IPFamily,
|
|||||||
|
|
||||||
nodePortAddresses := utilproxy.NewNodePortAddresses(nodePortAddressStrings)
|
nodePortAddresses := utilproxy.NewNodePortAddresses(nodePortAddressStrings)
|
||||||
|
|
||||||
serviceHealthServer := healthcheck.NewServiceHealthServer(hostname, recorder, nodePortAddresses)
|
serviceHealthServer := healthcheck.NewServiceHealthServer(hostname, recorder, nodePortAddresses, healthzServer)
|
||||||
|
|
||||||
// excludeCIDRs has been validated before, here we just parse it to IPNet list
|
// excludeCIDRs has been validated before, here we just parse it to IPNet list
|
||||||
parsedExcludeCIDRs, _ := netutils.ParseCIDRs(excludeCIDRs)
|
parsedExcludeCIDRs, _ := netutils.ParseCIDRs(excludeCIDRs)
|
||||||
|
@ -701,7 +701,7 @@ func NewProxier(
|
|||||||
|
|
||||||
// windows listens to all node addresses
|
// windows listens to all node addresses
|
||||||
nodePortAddresses := utilproxy.NewNodePortAddresses(nil)
|
nodePortAddresses := utilproxy.NewNodePortAddresses(nil)
|
||||||
serviceHealthServer := healthcheck.NewServiceHealthServer(hostname, recorder, nodePortAddresses)
|
serviceHealthServer := healthcheck.NewServiceHealthServer(hostname, recorder, nodePortAddresses, healthzServer)
|
||||||
|
|
||||||
hns, supportedFeatures := newHostNetworkService()
|
hns, supportedFeatures := newHostNetworkService()
|
||||||
hnsNetworkName, err := getNetworkName(config.NetworkName)
|
hnsNetworkName, err := getNetworkName(config.NetworkName)
|
||||||
|
Loading…
Reference in New Issue
Block a user