diff --git a/cmd/root.go b/cmd/root.go index 56e55f9c8655..9c6613b158ec 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,6 +15,7 @@ import ( "github.com/k0sproject/k0s/cmd/kubeconfig" "github.com/k0sproject/k0s/cmd/kubectl" "github.com/k0sproject/k0s/cmd/start" + "github.com/k0sproject/k0s/cmd/status" "github.com/k0sproject/k0s/cmd/stop" "github.com/k0sproject/k0s/cmd/sysinfo" "github.com/k0sproject/k0s/cmd/token" @@ -46,6 +47,7 @@ func NewRootCmd() *cobra.Command { cmd.AddCommand(kubectl.NewK0sKubectlCmd()) cmd.AddCommand(start.NewStartCmd()) cmd.AddCommand(stop.NewStopCmd()) + cmd.AddCommand(status.NewStatusCmd()) cmd.AddCommand(sysinfo.NewSysinfoCmd()) cmd.AddCommand(token.NewTokenCmd()) cmd.AddCommand(validate.NewValidateCmd()) // hidden+deprecated diff --git a/cmd/root_linux.go b/cmd/root_linux.go index 5b6966a0828d..e55efbd964e8 100644 --- a/cmd/root_linux.go +++ b/cmd/root_linux.go @@ -9,7 +9,6 @@ import ( "github.com/k0sproject/k0s/cmd/keepalived" "github.com/k0sproject/k0s/cmd/reset" "github.com/k0sproject/k0s/cmd/restore" - "github.com/k0sproject/k0s/cmd/status" "github.com/spf13/cobra" ) @@ -20,5 +19,4 @@ func addPlatformSpecificCommands(root *cobra.Command) { root.AddCommand(keepalived.NewKeepalivedSetStateCmd()) // hidden root.AddCommand(reset.NewResetCmd()) root.AddCommand(restore.NewRestoreCmd()) - root.AddCommand(status.NewStatusCmd()) } diff --git a/cmd/status/status.go b/cmd/status/status.go index d1395cf19d41..4cadd6284d22 100644 --- a/cmd/status/status.go +++ b/cmd/status/status.go @@ -1,5 +1,3 @@ -//go:build unix - // SPDX-FileCopyrightText: 2021 k0s authors // SPDX-License-Identifier: Apache-2.0 @@ -9,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "runtime" "github.com/k0sproject/k0s/cmd/internal" "github.com/k0sproject/k0s/pkg/component/status" @@ -51,7 +50,14 @@ func NewStatusCmd() *cobra.Command { pflags := cmd.PersistentFlags() debugFlags.AddToFlagSet(pflags) - pflags.String("status-socket", "", "Full file path to the socket file. (default: /status.sock)") + var socketDefault string + if runtime.GOOS == "windows" { + socketDefault = status.DefaultSocketPath + } else { + socketDefault = "/status.sock" + } + + pflags.String("status-socket", "", "Full path to the k0s status socket (default: "+socketDefault+")") cmd.AddCommand(NewStatusSubCmdComponents()) diff --git a/cmd/worker/worker.go b/cmd/worker/worker.go index 78add1ad0a16..b8af087af89d 100644 --- a/cmd/worker/worker.go +++ b/cmd/worker/worker.go @@ -18,9 +18,11 @@ import ( "github.com/k0sproject/k0s/internal/pkg/stringmap" "github.com/k0sproject/k0s/internal/pkg/sysinfo" "github.com/k0sproject/k0s/internal/supervised" + "github.com/k0sproject/k0s/pkg/build" "github.com/k0sproject/k0s/pkg/component/iptables" "github.com/k0sproject/k0s/pkg/component/manager" "github.com/k0sproject/k0s/pkg/component/prober" + "github.com/k0sproject/k0s/pkg/component/status" "github.com/k0sproject/k0s/pkg/component/worker" workerconfig "github.com/k0sproject/k0s/pkg/component/worker/config" "github.com/k0sproject/k0s/pkg/component/worker/containerd" @@ -298,6 +300,27 @@ func (c *Command) Start(ctx context.Context, nodeName apitypes.NodeName, kubelet addPlatformSpecificComponents(ctx, componentManager, c.K0sVars, controller, certManager) + if controller == nil { + // if running inside a controller, status component is already running + componentManager.Add(ctx, &status.Status{ + Prober: prober.DefaultProber, + StatusInformation: status.K0sStatus{ + Pid: os.Getpid(), + Role: "worker", + Args: os.Args, + Version: build.Version, + Workloads: true, + SingleNode: false, + K0sVars: c.K0sVars, + // worker does not have cluster config. this is only shown in "k0s status -o json". + // todo: if it's needed, a worker side config client can be set up and used to load the config + ClusterConfig: nil, + }, + CertManager: certManager, + Socket: c.K0sVars.StatusSocketPath, + }) + } + // extract needed components if err := componentManager.Init(ctx); err != nil { return err diff --git a/cmd/worker/worker_unix.go b/cmd/worker/worker_unix.go index 60bb534bf800..c593be3bb737 100644 --- a/cmd/worker/worker_unix.go +++ b/cmd/worker/worker_unix.go @@ -7,12 +7,8 @@ package worker import ( "context" - "os" - "github.com/k0sproject/k0s/pkg/build" "github.com/k0sproject/k0s/pkg/component/manager" - "github.com/k0sproject/k0s/pkg/component/prober" - "github.com/k0sproject/k0s/pkg/component/status" "github.com/k0sproject/k0s/pkg/component/worker" "github.com/k0sproject/k0s/pkg/config" ) @@ -20,27 +16,6 @@ import ( func initLogging(context.Context, string) error { return nil } func addPlatformSpecificComponents(ctx context.Context, m *manager.Manager, k0sVars *config.CfgVars, controller EmbeddingController, certManager *worker.CertificateManager) { - // if running inside a controller, status component is already running - if controller == nil { - m.Add(ctx, &status.Status{ - Prober: prober.DefaultProber, - StatusInformation: status.K0sStatus{ - Pid: os.Getpid(), - Role: "worker", - Args: os.Args, - Version: build.Version, - Workloads: true, - SingleNode: false, - K0sVars: k0sVars, - // worker does not have cluster config. this is only shown in "k0s status -o json". - // todo: if it's needed, a worker side config client can be set up and used to load the config - ClusterConfig: nil, - }, - CertManager: certManager, - Socket: k0sVars.StatusSocketPath, - }) - } - m.Add(ctx, &worker.Autopilot{ K0sVars: k0sVars, CertManager: certManager, diff --git a/cmd/worker/worker_windows.go b/cmd/worker/worker_windows.go index 4e8f8f8900ec..18e8f93b6757 100644 --- a/cmd/worker/worker_windows.go +++ b/cmd/worker/worker_windows.go @@ -45,6 +45,6 @@ func initLogging(ctx context.Context, logDir string) error { return nil } -func addPlatformSpecificComponents(context.Context, *manager.Manager, *config.CfgVars, EmbeddingController, *worker.CertificateManager) { +func addPlatformSpecificComponents(ctx context.Context, m *manager.Manager, k0sVars *config.CfgVars, controller EmbeddingController, certManager *worker.CertificateManager) { // no-op } diff --git a/pkg/autopilot/controller/setup.go b/pkg/autopilot/controller/setup.go index 70e2a3be25dd..1606aee57fae 100644 --- a/pkg/autopilot/controller/setup.go +++ b/pkg/autopilot/controller/setup.go @@ -178,9 +178,6 @@ func (sc *setupController) createControlNode(ctx context.Context, cf apcli.Facto return nil } -// TODO re-use from somewhere else -const DefaultK0sStatusSocketPath = "/run/k0s/status.sock" - func getControlNodeAddresses(hostname string) ([]corev1.NodeAddress, error) { addresses := []corev1.NodeAddress{} apiAddress, err := getControllerAPIAddress() @@ -201,7 +198,7 @@ func getControlNodeAddresses(hostname string) ([]corev1.NodeAddress, error) { } func getControllerAPIAddress() (string, error) { - status, err := status.GetStatusInfo(DefaultK0sStatusSocketPath) + status, err := status.GetStatusInfo(status.DefaultSocketPath) if err != nil { return "", err } diff --git a/pkg/autopilot/controller/signal/k0s/init.go b/pkg/autopilot/controller/signal/k0s/init.go index 1f93781ee39c..155eca0c34cc 100644 --- a/pkg/autopilot/controller/signal/k0s/init.go +++ b/pkg/autopilot/controller/signal/k0s/init.go @@ -39,7 +39,7 @@ func RegisterControllers(ctx context.Context, logger *logrus.Entry, mgr crman.Ma logger.Infof("Using effective hostname = '%v'", hostname) k0sVersionHandler := func() (string, error) { - return getK0sVersion(DefaultK0sStatusSocketPath) + return getK0sVersion(status.DefaultSocketPath) } if err := registerSignalController(logger, mgr, signalControllerEventFilter(hostname, apsigpred.DefaultErrorHandler(logger, "k0s signal")), delegate, clusterID, k0sVersionHandler); err != nil { diff --git a/pkg/autopilot/controller/signal/k0s/restart_unix.go b/pkg/autopilot/controller/signal/k0s/restart_unix.go index 99ef7e4d29c6..a9847fd3737c 100644 --- a/pkg/autopilot/controller/signal/k0s/restart_unix.go +++ b/pkg/autopilot/controller/signal/k0s/restart_unix.go @@ -93,7 +93,7 @@ func (r *restart) Reconcile(ctx context.Context, req cr.Request) (cr.Result, err // Get the current version of k0s logger.Info("Determining the current version of k0s") - k0sVersion, err := getK0sVersion(DefaultK0sStatusSocketPath) + k0sVersion, err := getK0sVersion(status.DefaultSocketPath) if err != nil { logger.Info("Unable to determine current verion of k0s; requeuing") return cr.Result{}, fmt.Errorf("unable to get k0s version: %w", err) @@ -128,7 +128,7 @@ func (r *restart) Reconcile(ctx context.Context, req cr.Request) (cr.Result, err logger.Info("Preparing to restart k0s") - k0sPid, err := getK0sPid(DefaultK0sStatusSocketPath) + k0sPid, err := getK0sPid(status.DefaultSocketPath) if err != nil { logger.Info("Unable to determine current k0s pid; requeuing") return cr.Result{RequeueAfter: restartRequeueDuration}, fmt.Errorf("unable to get k0s pid: %w", err) diff --git a/pkg/autopilot/controller/signal/k0s/restarted_unix.go b/pkg/autopilot/controller/signal/k0s/restarted_unix.go index ead71b5f3414..82cae8a4fe83 100644 --- a/pkg/autopilot/controller/signal/k0s/restarted_unix.go +++ b/pkg/autopilot/controller/signal/k0s/restarted_unix.go @@ -14,6 +14,7 @@ import ( apdel "github.com/k0sproject/k0s/pkg/autopilot/controller/delegate" apsigpred "github.com/k0sproject/k0s/pkg/autopilot/controller/signal/common/predicate" apsigv2 "github.com/k0sproject/k0s/pkg/autopilot/signaling/v2" + "github.com/k0sproject/k0s/pkg/component/status" "github.com/sirupsen/logrus" cr "sigs.k8s.io/controller-runtime" @@ -86,7 +87,7 @@ func (r *restarted) Reconcile(ctx context.Context, req cr.Request) (cr.Result, e // Get the current version of k0s logger.Info("Determining the current version of k0s") - k0sVersion, err := getK0sVersion(DefaultK0sStatusSocketPath) + k0sVersion, err := getK0sVersion(status.DefaultSocketPath) if err != nil { logger.Info("Unable to determine current verion of k0s; requeuing") return cr.Result{}, fmt.Errorf("unable to get k0s version: %w", err) diff --git a/pkg/autopilot/controller/signal/k0s/signal.go b/pkg/autopilot/controller/signal/k0s/signal.go index 8ce9f6039abb..3f895d991de3 100644 --- a/pkg/autopilot/controller/signal/k0s/signal.go +++ b/pkg/autopilot/controller/signal/k0s/signal.go @@ -27,7 +27,6 @@ import ( const ( SignalResponseProcessingTimeout = 1 * time.Minute - DefaultK0sStatusSocketPath = "/run/k0s/status.sock" ) type k0sVersionHandlerFunc func() (string, error) diff --git a/pkg/autopilot/controller/updates/updater.go b/pkg/autopilot/controller/updates/updater.go index f1b64eded9c9..ca9798614e06 100644 --- a/pkg/autopilot/controller/updates/updater.go +++ b/pkg/autopilot/controller/updates/updater.go @@ -20,7 +20,6 @@ import ( apv1beta2 "github.com/k0sproject/k0s/pkg/apis/autopilot/v1beta2" apcli "github.com/k0sproject/k0s/pkg/autopilot/client" appc "github.com/k0sproject/k0s/pkg/autopilot/controller/plans/core" - "github.com/k0sproject/k0s/pkg/autopilot/controller/signal/k0s" uc "github.com/k0sproject/k0s/pkg/autopilot/updater" "github.com/k0sproject/k0s/pkg/build" "github.com/k0sproject/k0s/pkg/component/status" @@ -77,7 +76,7 @@ func newCronUpdater(parentCtx context.Context, updateConfig apv1beta2.UpdateConf schedule = defaultCronSchedule } - status, err := status.GetStatusInfo(k0s.DefaultK0sStatusSocketPath) + status, err := status.GetStatusInfo(status.DefaultSocketPath) if err != nil { return nil, err } diff --git a/pkg/component/status/client.go b/pkg/component/status/client.go index 57be267b2656..282ec01d3f17 100644 --- a/pkg/component/status/client.go +++ b/pkg/component/status/client.go @@ -1,5 +1,3 @@ -//go:build unix - // SPDX-FileCopyrightText: 2022 k0s authors // SPDX-License-Identifier: Apache-2.0 @@ -40,7 +38,7 @@ type ProbeStatus struct { // GetStatus returns the status of the k0s process using the status socket func GetStatusInfo(socketPath string) (*K0sStatus, error) { status := &K0sStatus{} - if err := doHTTPRequestViaUnixSocket(socketPath, "status", status); err != nil { + if err := doStatusHTTPRequest(socketPath, "status", status); err != nil { return nil, err } return status, nil @@ -49,7 +47,7 @@ func GetStatusInfo(socketPath string) (*K0sStatus, error) { // GetComponentStatus returns the per-component events and health-checks func GetComponentStatus(socketPath string, maxCount int) (*prober.State, error) { status := &prober.State{} - if err := doHTTPRequestViaUnixSocket(socketPath, + if err := doStatusHTTPRequest(socketPath, fmt.Sprintf("components?maxCount=%d", maxCount), status); err != nil { return nil, err @@ -57,12 +55,12 @@ func GetComponentStatus(socketPath string, maxCount int) (*prober.State, error) return status, nil } -func doHTTPRequestViaUnixSocket(socketPath string, path string, tgt any) error { - httpc := http.Client{ +func doStatusHTTPRequest(socketPath string, path string, tgt any) error { + httpc := &http.Client{ Transport: &http.Transport{ + DisableKeepAlives: true, DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - var d net.Dialer - return d.DialContext(ctx, "unix", socketPath) + return dialSocket(ctx, socketPath) }, }, } diff --git a/pkg/component/status/defaults.go b/pkg/component/status/defaults.go new file mode 100644 index 000000000000..1fdc32aa706e --- /dev/null +++ b/pkg/component/status/defaults.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 k0s authors +// SPDX-License-Identifier: Apache-2.0 + +package status + +import "github.com/k0sproject/k0s/pkg/constant" + +// DefaultSocketPath is the platform-specific default socket path used by k0s +// when no explicit status socket is provided. +// +// Deprecated: This constant is defined to avoid a circular import in autopilot +// and will be refactored out. +const DefaultSocketPath = constant.StatusSocketPathDefault diff --git a/pkg/component/status/status.go b/pkg/component/status/status.go index 7bd81bff7a9e..9b4e0ef8c808 100644 --- a/pkg/component/status/status.go +++ b/pkg/component/status/status.go @@ -1,5 +1,3 @@ -//go:build unix - // SPDX-FileCopyrightText: 2021 k0s authors // SPDX-License-Identifier: Apache-2.0 @@ -9,9 +7,7 @@ import ( "context" "encoding/json" "errors" - "net" "net/http" - "os" "strconv" "time" @@ -35,7 +31,6 @@ type Status struct { Socket string L *logrus.Entry httpserver http.Server - listener net.Listener CertManager certManager } @@ -62,34 +57,23 @@ func (s *Status) Init(_ context.Context) error { w.WriteHeader(http.StatusInternalServerError) } }) - var err error s.httpserver = http.Server{ Handler: mux, } - removeLeftovers(s.Socket) - s.listener, err = net.Listen("unix", s.Socket) - if err != nil { - s.L.Errorf("failed to create listener %s", err) - return err - } - s.L.Infof("Listening address %s", s.Socket) - return nil } -// removeLeftovers tries to remove leftover sockets that nothing is listening on -func removeLeftovers(socket string) { - _, err := net.Dial("unix", socket) - if err != nil { - _ = os.Remove(socket) - } -} - // Start runs the component func (s *Status) Start(_ context.Context) error { + listener, err := newStatusListener(s.Socket) + if err != nil { + s.L.Errorf("failed to create listener %s", err) + return err + } + s.L.Infof("Listening address %s", s.Socket) go func() { - if err := s.httpserver.Serve(s.listener); err != nil && !errors.Is(err, http.ErrServerClosed) { + if err := s.httpserver.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { s.L.Errorf("failed to start status server at %s: %s", s.Socket, err) } }() @@ -103,8 +87,7 @@ func (s *Status) Stop() error { if err := s.httpserver.Shutdown(ctx); err != nil && !errors.Is(err, context.Canceled) { return err } - // Unix socket doesn't need to be explicitly removed because it's handled - // by httpserver.Shutdown + cleanupStatusListener(s.Socket) return nil } diff --git a/pkg/component/status/transport_unix.go b/pkg/component/status/transport_unix.go new file mode 100644 index 000000000000..08b729ee0fe7 --- /dev/null +++ b/pkg/component/status/transport_unix.go @@ -0,0 +1,33 @@ +//go:build unix + +// SPDX-FileCopyrightText: 2025 k0s authors +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "context" + "net" + "os" +) + +func newStatusListener(socketPath string) (net.Listener, error) { + removeLeftovers(socketPath) + return net.Listen("unix", socketPath) +} + +func cleanupStatusListener(socketPath string) { + _ = os.Remove(socketPath) +} + +func removeLeftovers(socket string) { + _, err := net.Dial("unix", socket) + if err != nil { + _ = os.Remove(socket) + } +} + +func dialSocket(ctx context.Context, socketPath string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, "unix", socketPath) +} diff --git a/pkg/component/status/transport_windows.go b/pkg/component/status/transport_windows.go new file mode 100644 index 000000000000..a2509a44af6d --- /dev/null +++ b/pkg/component/status/transport_windows.go @@ -0,0 +1,23 @@ +//go:build windows + +// SPDX-FileCopyrightText: 2025 k0s authors +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "context" + "net" + + "github.com/Microsoft/go-winio" +) + +func newStatusListener(pipePath string) (net.Listener, error) { + return winio.ListenPipe(pipePath, nil) +} + +func cleanupStatusListener(string) {} + +func dialSocket(ctx context.Context, socketPath string) (net.Conn, error) { + return winio.DialPipeContext(ctx, socketPath) +} diff --git a/pkg/config/cfgvars.go b/pkg/config/cfgvars.go index 43d5de43d6d9..4f49304dfc2c 100644 --- a/pkg/config/cfgvars.go +++ b/pkg/config/cfgvars.go @@ -11,6 +11,7 @@ import ( "io" "os" "path/filepath" + "runtime" "github.com/spf13/pflag" @@ -138,6 +139,20 @@ func NewCfgVars(cobraCmd command, dirs ...string) (*CfgVars, error) { runDir = filepath.Join(dataDir, "run") } + var statusSocketPath string + if cobraCmd != nil { + if val, err := cobraCmd.Flags().GetString("status-socket"); err == nil { + statusSocketPath = val + } + } + if statusSocketPath == "" { + if runtime.GOOS == "windows" { + statusSocketPath = constant.StatusSocketPathDefault + } else { + statusSocketPath = filepath.Join(runDir, "status.sock") + } + } + certDir := filepath.Join(dataDir, "pki") helmHome := filepath.Join(dataDir, "helmhome") @@ -163,7 +178,7 @@ func NewCfgVars(cobraCmd command, dirs ...string) (*CfgVars, error) { RunDir: runDir, KonnectivityKubeConfigPath: filepath.Join(certDir, "konnectivity.conf"), RuntimeConfigPath: filepath.Join(runDir, "k0s.yaml"), - StatusSocketPath: filepath.Join(runDir, "status.sock"), + StatusSocketPath: statusSocketPath, StartupConfigPath: constant.K0sConfigPathDefault, // Helm Config diff --git a/pkg/constant/constant_unix.go b/pkg/constant/constant_unix.go index ef3c9996487a..616b1b865d75 100644 --- a/pkg/constant/constant_unix.go +++ b/pkg/constant/constant_unix.go @@ -9,8 +9,9 @@ const ( // DataDirDefault is the default directory containing k0s state. DataDirDefault = "/var/lib/k0s" - KineSocket = "kine/kine.sock:2379" - K0sConfigPathDefault = "/etc/k0s/k0s.yaml" + KineSocket = "kine/kine.sock:2379" + K0sConfigPathDefault = "/etc/k0s/k0s.yaml" + StatusSocketPathDefault = "/run/k0s/status.sock" ExecutableSuffix = "" ) diff --git a/pkg/constant/constant_windows.go b/pkg/constant/constant_windows.go index 124405cd2968..0255a384d981 100644 --- a/pkg/constant/constant_windows.go +++ b/pkg/constant/constant_windows.go @@ -7,8 +7,9 @@ const ( // DataDirDefault is the default directory containing k0s state. DataDirDefault = "C:\\var\\lib\\k0s" - KineSocket = "kine\\kine.sock:2379" - K0sConfigPathDefault = "C:\\etc\\k0s\\k0s.yaml" + KineSocket = "kine\\kine.sock:2379" + K0sConfigPathDefault = "C:\\etc\\k0s\\k0s.yaml" + StatusSocketPathDefault = `\\.\pipe\k0s-status` ExecutableSuffix = ".exe" )