Change:
Quoted from 's Splitting healthz/ into livez/ and readyz/
This issue is currently awaiting triage. If a SIG or subproject determines this is a relevant issue, they will accept it by applying the label and provide further guidance. The label can be by org members by writing in a comment. <detailsInstructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/cc
/sig scheduling
I read all the content of the PR comment and am willing to work on this issue Do we need to continue some discussion for this issue
Hi , thanks for offering your help. But I was working in progress before knowing you can work on this. However, please feel free to contribute the similar issue
/assign
The Kubernetes project currently lacks enough contributors to adequately respond to all issues. This bot triages un-triaged issues according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Mark this issue as fresh with Close this issue with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /lifecycle stale [1]:
/remove-lifecycle stale

Discussion:
defer cc.EventBroadcaster.Shutdown() // Setup healthz checks. <del> var checks []healthz.HealthChecker </del> <ins> var checks, readyzChecks []healthz.HealthChecker </ins> if cc.ComponentConfig.LeaderElection.LeaderElect { checks = append(checks, cc.LeaderElection.WatchDog) <ins> readyzChecks = append(readyzChecks, cc.LeaderElection.WatchDog) </ins> } <ins> readyzChecks = append(readyzChecks, healthz.NewShutdownHealthz(ctx.Done())) </ins> waitingForLeader := make(chan struct{}) isLeader := func() bool {

} } <ins> handlerSyncReadyCh := make(chan struct{}) handlerSyncCheck := healthz.NamedCheck("sched-handler-sync", func(_ *http.Request) error { select { case <-handlerSyncReadyCh: return nil default: } return fmt.Errorf("waiting for handlers to sync") }) readyzChecks = append(readyzChecks, handlerSyncCheck) </ins> // Start up the healthz server. if cc.SecureServing != nil { <del> handler := buildHandlerChain(newHealthzAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks...), cc.Authentication.Authenticator, cc.Authorization.Authorizer) </del> <ins> handler := buildHandlerChain(newHealthEndpointsAndMetricsHandler(&cc.ComponentConfig, cc.InformerFactory, isLeader, checks, readyzChecks), cc.Authentication.Authenticator, cc.Authorization.Authorizer) </ins> // TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve if _, _, err := cc.SecureServing.Serve(handler, 0, ctx.Done()); err != nil { // fail early for secure handlers, removing the old error loop from above

logger.Error(err, "waiting for handlers to sync") } <ins> close(handlerSyncReadyCh) </ins> logger.V(3).Info("Handlers synced") } if !cc.ComponentConfig.DelayCacheUntilActive || cc.LeaderElection == nil {

}) } <del> // newHealthzAndMetricsHandler creates a healthz server from the config, and will also </del> <ins> // newHealthEndpointsAndMetricsHandler creates an API health server from the config, and will also </ins> // embed the metrics handler. <del> func newHealthzAndMetricsHandler(config *kubeschedulerconfig.KubeSchedulerConfiguration, informers informers.SharedInformerFactory, isLeader func() bool, checks ...healthz.HealthChecker) http.Handler { </del> <ins> // TODO: healthz check is deprecated, please use livez and readyz instead. Will be removed in the future. func newHealthEndpointsAndMetricsHandler(config *kubeschedulerconfig.KubeSchedulerConfiguration, informers informers.SharedInformerFactory, isLeader func() bool, healthzChecks, readyzChecks []healthz.HealthChecker) http.Handler { </ins> pathRecorderMux := mux.NewPathRecorderMux("kube-scheduler") <del> healthz.InstallHandler(pathRecorderMux, checks...) </del> <ins> healthz.InstallHandler(pathRecorderMux, healthzChecks...) healthz.InstallLivezHandler(pathRecorderMux) healthz.InstallReadyzHandler(pathRecorderMux, readyzChecks...) </ins> installMetricHandler(pathRecorderMux, informers, isLeader) slis.SLIMetricsWithReset{}.Install(pathRecorderMux)

// that we can register that the api-server is no longer ready while we attempt to gracefully // shutdown. func (s *GenericAPIServer) addReadyzShutdownCheck(stopCh <-chan struct{}) error { <del> return s.AddReadyzChecks(shutdownCheck{stopCh}) </del> <ins> return s.AddReadyzChecks(healthz.NewShutdownHealthz(stopCh)) </ins> } // installHealthz creates the healthz endpoint for this server

s.livezRegistry.installHandler(s.Handler.NonGoRestfulMux) } <del> // shutdownCheck fails if the embedded channel is closed. This is intended to allow for graceful shutdown sequences // for the apiserver. type shutdownCheck struct { StopCh <-chan struct{} } func (shutdownCheck) Name() string { return "shutdown" } func (c shutdownCheck) Check(req *http.Request) error { select { case <-c.StopCh: return fmt.Errorf("process is shutting down") default: } return nil } </del> // delayedHealthCheck wraps a health check which will not fail until the explicitly defined delay has elapsed. This // is intended for use primarily for livez health checks. func delayedHealthCheck(check healthz.HealthChecker, clock clock.Clock, delay time.Duration) healthz.HealthChecker {

return "informer-sync" } <ins> type shutdown struct { stopCh <-chan struct{} } // NewShutdownHealthz returns a new HealthChecker that will fail if the embedded channel is closed. // This is intended to allow for graceful shutdown sequences. func NewShutdownHealthz(stopCh <-chan struct{}) HealthChecker { return &shutdown{stopCh} } func (s *shutdown) Name() string { return "shutdown" } func (s *shutdown) Check(req *http.Request) error { select { case <-s.stopCh: return fmt.Errorf("process is shutting down") default: } return nil } </ins> func (i *informerSync) Check(_ *http.Request) error { stopCh := make(chan struct{}) // Close stopCh to force checking if informers are synced now.

<ins> /* Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package serving import ( "crypto/tls" "crypto/x509" "fmt" "io" "net/http" "os" "path" "strings" "testing" "k8s.io/klog/v2/ktesting" kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" kubeschedulertesting "k8s.io/kubernetes/cmd/kube-scheduler/app/testing" "k8s.io/kubernetes/test/integration/framework" ) func TestHealthEndpoints(t *testing.T) { server, configStr, _, err := startTestAPIServer(t) if err != nil { t.Fatalf("Failed to start kube-apiserver server: %v", err) } defer server.TearDownFn() apiserverConfig, err := os.CreateTemp("", "kubeconfig") if err != nil { t.Fatalf("Failed to create config file: %v", err) } defer func() { _ = os.Remove(apiserverConfig.Name()) }() if _, err = apiserverConfig.WriteString(configStr); err != nil { t.Fatalf("Failed to write config file: %v", err) } brokenConfigStr := strings.ReplaceAll(configStr, "127.0.0.1", "127.0.0.2") brokenConfig, err := os.CreateTemp("", "kubeconfig") if err != nil { t.Fatalf("Failed to create config file: %v", err) } if _, err := brokenConfig.WriteString(brokenConfigStr); err != nil { t.Fatalf("Failed to write config file: %v", err) } defer func() { _ = os.Remove(brokenConfig.Name()) }() tests := []struct { name             string path             string useBrokenConfig  bool wantResponseCode int }{ { "/healthz", "/healthz", false, http.StatusOK, }, { "/livez", "/livez", false, http.StatusOK, }, { "/livez with ping check", "/livez/ping", false, http.StatusOK, }, { "/readyz", "/readyz", false, http.StatusOK, }, { "/readyz with sched-handler-sync", "/readyz/sched-handler-sync", false, http.StatusOK, }, { "/readyz with shutdown", "/readyz/shutdown", false, http.StatusOK, }, { "/readyz with broken apiserver", "/readyz", true, http.StatusInternalServerError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt := tt _, ctx := ktesting.NewTestContext(t) configFile := apiserverConfig.Name() if tt.useBrokenConfig { configFile = brokenConfig.Name() } result, err := kubeschedulertesting.StartTestServer( ctx, []string{"--kubeconfig", configFile, "--leader-elect=false", "--authorization-always-allow-paths", tt.path}) if err != nil { t.Fatalf("Failed to start kube-scheduler server: %v", err) } if result.TearDownFn != nil { defer result.TearDownFn() } client, base, err := clientAndURLFromTestServer(result) if err != nil { t.Fatalf("Failed to get client from test server: %v", err) } req, err := http.NewRequest("GET", base+tt.path, nil) if err != nil { t.Fatalf("failed to request: %v", err) } r, err := client.Do(req) if err != nil { t.Fatalf("failed to GET %s from component: %v", tt.path, err) } body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("failed to read response body: %v", err) } if err = r.Body.Close(); err != nil { t.Fatalf("failed to close response body: %v", err) } if got, expected := r.StatusCode, tt.wantResponseCode; got != expected { t.Fatalf("expected http %d at %s of component, got: %d %q", expected, tt.path, got, string(body)) } }) } } // TODO: Make this a util function once there is a unified way to start a testing apiserver so that we can reuse it. func startTestAPIServer(t *testing.T) (server *kubeapiservertesting.TestServer, apiserverConfig, token string, err error) { // Insulate this test from picking up in-cluster config when run inside a pod // We can't assume we have permissions to write to /var/run/secrets/... from a unit test to mock in-cluster config for testing originalHost := os.Getenv("KUBERNETES_SERVICE_HOST") if len(originalHost) > 0 { if err = os.Setenv("KUBERNETES_SERVICE_HOST", ""); err != nil { return } defer func() { err = os.Setenv("KUBERNETES_SERVICE_HOST", originalHost) }() } // authenticate to apiserver via bearer token token = "flwqkenfjasasdfmwerasd" // Fake token for testing. var tokenFile *os.File tokenFile, err = os.CreateTemp("", "kubeconfig") if err != nil { return } if _, err = tokenFile.WriteString(fmt.Sprintf(`%s,system:kube-scheduler,system:kube-scheduler,""`, token)); err != nil { return } if err = tokenFile.Close(); err != nil { return } // start apiserver server = kubeapiservertesting.StartTestServerOrDie(t, nil, []string{ "--token-auth-file", tokenFile.Name(), "--authorization-mode", "AlwaysAllow", }, framework.SharedEtcd()) apiserverConfig = fmt.Sprintf(` apiVersion: v1 kind: Config clusters: - cluster: server: %s certificate-authority: %s name: integration contexts: - context: cluster: integration user: kube-scheduler name: default-context current-context: default-context users: - name: kube-scheduler user: token: %s `, server.ClientConfig.Host, server.ServerOpts.SecureServing.ServerCert.CertKey.CertFile, token) return server, apiserverConfig, token, nil } func clientAndURLFromTestServer(s kubeschedulertesting.TestServer) (*http.Client, string, error) { secureInfo := s.Config.SecureServing secureOptions := s.Options.SecureServing url := fmt.Sprintf("https://%s", secureInfo.Listener.Addr().String()) url = strings.ReplaceAll(url, "[::]", "127.0.0.1") // switch to IPv4 because the self-signed cert does not support [::] // read self-signed server cert disk pool := x509.NewCertPool() serverCertPath := path.Join(secureOptions.ServerCert.CertDirectory, secureOptions.ServerCert.PairName+".crt") serverCert, err := os.ReadFile(serverCertPath) if err != nil { return nil, "", fmt.Errorf("Failed to read component server cert %q: %w", serverCertPath, err) } pool.AppendCertsFromPEM(serverCert) tr := &http.Transport{ TLSClientConfig: &tls.Config{ RootCAs: pool, }, } client := &http.Client{Transport: tr} return client, url, nil } </ins>

<ins> /* Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package serving import ( "testing" "k8s.io/kubernetes/test/integration/framework" ) func TestMain(m *testing.M) { framework.EtcdMain(m.Run) } </ins>