From 1a9771cae5f6f02d865bbc62b6c3805dd736fcb7 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Fri, 28 Nov 2025 16:00:06 +0200 Subject: [PATCH 1/9] Add Windows pipe transport for status socket Named pipes are now used for the status component on Windows. Autopilot has been changed to use the constant from the status package instead of defining a new constant. Note that autopilot currently breaks if you don't use the default status socket path, this needs to be fixed elsewhere. Signed-off-by: Kimmo Lehto (cherry picked from commit d7cc716abf3816afab4f8582ebdbec0d0c6329ed) --- cmd/worker/worker_windows.go | 24 +++++++- pkg/autopilot/controller/setup.go | 5 +- pkg/autopilot/controller/signal/k0s/init.go | 2 +- .../controller/signal/k0s/restart_unix.go | 4 +- .../controller/signal/k0s/restarted_unix.go | 3 +- pkg/autopilot/controller/signal/k0s/signal.go | 1 - pkg/autopilot/controller/updates/updater.go | 3 +- pkg/component/status/client.go | 20 ++----- pkg/component/status/defaults.go | 10 ++++ pkg/component/status/status.go | 20 ++----- pkg/component/status/transport_unix.go | 40 +++++++++++++ pkg/component/status/transport_windows.go | 60 +++++++++++++++++++ pkg/config/cfgvars.go | 12 +++- pkg/constant/constant_unix.go | 5 +- pkg/constant/constant_windows.go | 5 +- 15 files changed, 166 insertions(+), 48 deletions(-) create mode 100644 pkg/component/status/defaults.go create mode 100644 pkg/component/status/transport_unix.go create mode 100644 pkg/component/status/transport_windows.go diff --git a/cmd/worker/worker_windows.go b/cmd/worker/worker_windows.go index 4e8f8f8900ec..3db50efae953 100644 --- a/cmd/worker/worker_windows.go +++ b/cmd/worker/worker_windows.go @@ -12,7 +12,10 @@ import ( "github.com/k0sproject/k0s/internal/pkg/dir" "github.com/k0sproject/k0s/internal/pkg/log" + "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" "github.com/k0sproject/k0s/pkg/constant" @@ -45,6 +48,23 @@ func initLogging(ctx context.Context, logDir string) error { return nil } -func addPlatformSpecificComponents(context.Context, *manager.Manager, *config.CfgVars, EmbeddingController, *worker.CertificateManager) { - // no-op +func addPlatformSpecificComponents(ctx context.Context, m *manager.Manager, k0sVars *config.CfgVars, controller EmbeddingController, certManager *worker.CertificateManager) { + if controller != nil { + return + } + + 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, + }, + CertManager: certManager, + Socket: k0sVars.StatusSocketPath, + }) } 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..a6ab527f83d4 100644 --- a/pkg/component/status/client.go +++ b/pkg/component/status/client.go @@ -1,15 +1,11 @@ -//go:build unix - // SPDX-FileCopyrightText: 2022 k0s authors // SPDX-License-Identifier: Apache-2.0 package status import ( - "context" "encoding/json" "fmt" - "net" "net/http" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" @@ -40,7 +36,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 +45,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,14 +53,10 @@ func GetComponentStatus(socketPath string, maxCount int) (*prober.State, error) return status, nil } -func doHTTPRequestViaUnixSocket(socketPath string, path string, tgt any) error { - httpc := http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - var d net.Dialer - return d.DialContext(ctx, "unix", socketPath) - }, - }, +func doStatusHTTPRequest(socketPath string, path string, tgt any) error { + httpc, err := newStatusHTTPClient(socketPath) + if err != nil { + return fmt.Errorf("status: can't prepare transport for %q: %w", socketPath, err) } response, err := httpc.Get("http://localhost/" + path) diff --git a/pkg/component/status/defaults.go b/pkg/component/status/defaults.go new file mode 100644 index 000000000000..cbe8bc6921c2 --- /dev/null +++ b/pkg/component/status/defaults.go @@ -0,0 +1,10 @@ +// 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. +const DefaultSocketPath = constant.StatusSocketPathDefault diff --git a/pkg/component/status/status.go b/pkg/component/status/status.go index 7bd81bff7a9e..4b86d021126e 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 @@ -11,7 +9,6 @@ import ( "errors" "net" "net/http" - "os" "strconv" "time" @@ -67,8 +64,7 @@ func (s *Status) Init(_ context.Context) error { Handler: mux, } - removeLeftovers(s.Socket) - s.listener, err = net.Listen("unix", s.Socket) + s.listener, err = newStatusListener(s.Socket) if err != nil { s.L.Errorf("failed to create listener %s", err) return err @@ -78,14 +74,6 @@ func (s *Status) Init(_ context.Context) error { 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 { go func() { @@ -103,8 +91,10 @@ 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 + if s.listener != nil { + _ = s.listener.Close() + } + 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..f37df924b639 --- /dev/null +++ b/pkg/component/status/transport_unix.go @@ -0,0 +1,40 @@ +//go:build unix + +// SPDX-FileCopyrightText: 2025 k0s authors +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "context" + "net" + "net/http" + "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 newStatusHTTPClient(socketPath string) (*http.Client, error) { + transport := &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, "unix", socketPath) + }, + } + + return &http.Client{Transport: transport}, nil +} diff --git a/pkg/component/status/transport_windows.go b/pkg/component/status/transport_windows.go new file mode 100644 index 000000000000..8ff9e8c3eb0e --- /dev/null +++ b/pkg/component/status/transport_windows.go @@ -0,0 +1,60 @@ +//go:build windows + +// SPDX-FileCopyrightText: 2025 k0s authors +// SPDX-License-Identifier: Apache-2.0 + +package status + +import ( + "context" + "net" + "net/http" + "path/filepath" + "strings" + + "github.com/Microsoft/go-winio" +) + +func newStatusListener(socketPath string) (net.Listener, error) { + return winio.ListenPipe(pipePath(socketPath), nil) +} + +func cleanupStatusListener(string) {} + +func newStatusHTTPClient(socketPath string) (*http.Client, error) { + path := pipePath(socketPath) + transport := &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return winio.DialPipeContext(ctx, path) + }, + } + return &http.Client{Transport: transport}, nil +} + +func pipePath(socketPath string) string { + if hasPipePrefix(socketPath) { + return socketPath + } + + cleaned := filepath.Clean(socketPath) + cleaned = strings.ReplaceAll(cleaned, "\\", "/") + cleaned = strings.TrimPrefix(cleaned, "/") + cleaned = strings.ReplaceAll(cleaned, "/", "-") + cleaned = strings.ReplaceAll(cleaned, ":", "") + + if cleaned == "" || cleaned == "." { + cleaned = "k0s-status" + } + + return `\\.\pipe\` + cleaned +} + +func hasPipePrefix(socketPath string) bool { + if socketPath == "" { + return false + } + lower := strings.ToLower(socketPath) + return strings.HasPrefix(lower, `\\.\pipe\`) || + strings.HasPrefix(lower, `//./pipe/`) || + strings.HasPrefix(lower, `\\?\pipe\`) +} diff --git a/pkg/config/cfgvars.go b/pkg/config/cfgvars.go index 43d5de43d6d9..ff3913439948 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" @@ -131,13 +132,20 @@ func NewCfgVars(cobraCmd command, dirs ...string) (*CfgVars, error) { return nil, err } + euid := os.Geteuid() + var runDir string - if os.Geteuid() == 0 { + if euid == 0 { runDir = "/run/k0s" } else { runDir = filepath.Join(dataDir, "run") } + statusSocketPath := filepath.Join(runDir, "status.sock") + if runtime.GOOS != "windows" && euid == 0 { + statusSocketPath = constant.StatusSocketPathDefault + } + certDir := filepath.Join(dataDir, "pki") helmHome := filepath.Join(dataDir, "helmhome") @@ -163,7 +171,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" ) From d56e501942cb32502263ad44a053b2d34545c268 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Fri, 28 Nov 2025 16:08:46 +0200 Subject: [PATCH 2/9] Expose "k0s status" sub-command on all platforms The "k0s status" sub-command was not built into Windows binaries as the status compoennt did not yet run on windows. Signed-off-by: Kimmo Lehto (cherry picked from commit 95fa26fd284049283e55faf160e96ca97a3fa2b5) --- cmd/root.go | 2 ++ cmd/root_linux.go | 2 -- cmd/status/status.go | 12 +++++++++--- pkg/config/cfgvars.go | 15 ++++++++++++--- 4 files changed, 23 insertions(+), 8 deletions(-) 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/pkg/config/cfgvars.go b/pkg/config/cfgvars.go index ff3913439948..7918fe0332f9 100644 --- a/pkg/config/cfgvars.go +++ b/pkg/config/cfgvars.go @@ -141,9 +141,18 @@ func NewCfgVars(cobraCmd command, dirs ...string) (*CfgVars, error) { runDir = filepath.Join(dataDir, "run") } - statusSocketPath := filepath.Join(runDir, "status.sock") - if runtime.GOOS != "windows" && euid == 0 { - statusSocketPath = constant.StatusSocketPathDefault + var statusSocketPath string + if cobraCmd != nil { + if val, err := cobraCmd.Flags().GetString("status-socket"); err == nil { + statusSocketPath = val + } + } + if statusSocketPath == "" { + if runtime.GOOS == "windows" || euid == 0 { + statusSocketPath = constant.StatusSocketPathDefault + } else { + statusSocketPath = filepath.Join(runDir, "status.sock") + } } certDir := filepath.Join(dataDir, "pki") From a628ecb9eebe797e4fc58cefbb6baf01b18d63c7 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 10:44:22 +0200 Subject: [PATCH 3/9] Add status component to component manager without platform separation Signed-off-by: Kimmo Lehto (cherry picked from commit fa499101ee53a579dbac7c6cdf1eabd9124bf5c5) --- cmd/worker/worker.go | 23 +++++++++++++++++++++++ cmd/worker/worker_unix.go | 25 ------------------------- cmd/worker/worker_windows.go | 22 +--------------------- 3 files changed, 24 insertions(+), 46 deletions(-) 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 3db50efae953..18e8f93b6757 100644 --- a/cmd/worker/worker_windows.go +++ b/cmd/worker/worker_windows.go @@ -12,10 +12,7 @@ import ( "github.com/k0sproject/k0s/internal/pkg/dir" "github.com/k0sproject/k0s/internal/pkg/log" - "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" "github.com/k0sproject/k0s/pkg/constant" @@ -49,22 +46,5 @@ func initLogging(ctx context.Context, logDir string) error { } func addPlatformSpecificComponents(ctx context.Context, m *manager.Manager, k0sVars *config.CfgVars, controller EmbeddingController, certManager *worker.CertificateManager) { - if controller != nil { - return - } - - 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, - }, - CertManager: certManager, - Socket: k0sVars.StatusSocketPath, - }) + // no-op } From 1e28401b39a11aca30ca4b686afadd4cb6a9e23a Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 10:49:01 +0200 Subject: [PATCH 4/9] Add deprecation notice to status.DefaultSocketPath Signed-off-by: Kimmo Lehto (cherry picked from commit 4fc21e340f3419cee352d62d1d9e9ef4f86c2259) --- pkg/component/status/defaults.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/component/status/defaults.go b/pkg/component/status/defaults.go index cbe8bc6921c2..1fdc32aa706e 100644 --- a/pkg/component/status/defaults.go +++ b/pkg/component/status/defaults.go @@ -7,4 +7,7 @@ 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 From 3d65dc21d9ee2cf37f45adb2564dd5a7b487cd95 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 10:55:20 +0200 Subject: [PATCH 5/9] Dont normalize pipe paths, let invalid paths fail downstream Signed-off-by: Kimmo Lehto (cherry picked from commit 7773d1f0cfcca3b6237d06db229369766cceee32) --- pkg/component/status/transport_windows.go | 39 +++-------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/pkg/component/status/transport_windows.go b/pkg/component/status/transport_windows.go index 8ff9e8c3eb0e..73ffe97f7a62 100644 --- a/pkg/component/status/transport_windows.go +++ b/pkg/component/status/transport_windows.go @@ -9,52 +9,21 @@ import ( "context" "net" "net/http" - "path/filepath" - "strings" "github.com/Microsoft/go-winio" ) -func newStatusListener(socketPath string) (net.Listener, error) { - return winio.ListenPipe(pipePath(socketPath), nil) +func newStatusListener(pipePath string) (net.Listener, error) { + return winio.ListenPipe(pipePath, nil) } func cleanupStatusListener(string) {} -func newStatusHTTPClient(socketPath string) (*http.Client, error) { - path := pipePath(socketPath) +func newStatusHTTPClient(pipePath string) (*http.Client, error) { transport := &http.Transport{ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return winio.DialPipeContext(ctx, path) + return winio.DialPipeContext(ctx, pipePath) }, } return &http.Client{Transport: transport}, nil } - -func pipePath(socketPath string) string { - if hasPipePrefix(socketPath) { - return socketPath - } - - cleaned := filepath.Clean(socketPath) - cleaned = strings.ReplaceAll(cleaned, "\\", "/") - cleaned = strings.TrimPrefix(cleaned, "/") - cleaned = strings.ReplaceAll(cleaned, "/", "-") - cleaned = strings.ReplaceAll(cleaned, ":", "") - - if cleaned == "" || cleaned == "." { - cleaned = "k0s-status" - } - - return `\\.\pipe\` + cleaned -} - -func hasPipePrefix(socketPath string) bool { - if socketPath == "" { - return false - } - lower := strings.ToLower(socketPath) - return strings.HasPrefix(lower, `\\.\pipe\`) || - strings.HasPrefix(lower, `//./pipe/`) || - strings.HasPrefix(lower, `\\?\pipe\`) -} From 9e6050d934a1c6bdd1dcd5a080eb15847eb9d3d0 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 11:01:55 +0200 Subject: [PATCH 6/9] Simplify status HTTP client building The client can be built universally, only the dialcontext needs to be different for linux/win. Co-authored-by: Tom Wieczorek Signed-off-by: Kimmo Lehto (cherry picked from commit bdcbfb7f3fcc27a862d586641bdee77e5bb273da) --- pkg/component/status/client.go | 12 +++++++++--- pkg/component/status/transport_unix.go | 13 +++---------- pkg/component/status/transport_windows.go | 10 ++-------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/pkg/component/status/client.go b/pkg/component/status/client.go index a6ab527f83d4..282ec01d3f17 100644 --- a/pkg/component/status/client.go +++ b/pkg/component/status/client.go @@ -4,8 +4,10 @@ package status import ( + "context" "encoding/json" "fmt" + "net" "net/http" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" @@ -54,9 +56,13 @@ func GetComponentStatus(socketPath string, maxCount int) (*prober.State, error) } func doStatusHTTPRequest(socketPath string, path string, tgt any) error { - httpc, err := newStatusHTTPClient(socketPath) - if err != nil { - return fmt.Errorf("status: can't prepare transport for %q: %w", socketPath, err) + httpc := &http.Client{ + Transport: &http.Transport{ + DisableKeepAlives: true, + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return dialSocket(ctx, socketPath) + }, + }, } response, err := httpc.Get("http://localhost/" + path) diff --git a/pkg/component/status/transport_unix.go b/pkg/component/status/transport_unix.go index f37df924b639..08b729ee0fe7 100644 --- a/pkg/component/status/transport_unix.go +++ b/pkg/component/status/transport_unix.go @@ -8,7 +8,6 @@ package status import ( "context" "net" - "net/http" "os" ) @@ -28,13 +27,7 @@ func removeLeftovers(socket string) { } } -func newStatusHTTPClient(socketPath string) (*http.Client, error) { - transport := &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - var d net.Dialer - return d.DialContext(ctx, "unix", socketPath) - }, - } - - return &http.Client{Transport: transport}, nil +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 index 73ffe97f7a62..a2509a44af6d 100644 --- a/pkg/component/status/transport_windows.go +++ b/pkg/component/status/transport_windows.go @@ -8,7 +8,6 @@ package status import ( "context" "net" - "net/http" "github.com/Microsoft/go-winio" ) @@ -19,11 +18,6 @@ func newStatusListener(pipePath string) (net.Listener, error) { func cleanupStatusListener(string) {} -func newStatusHTTPClient(pipePath string) (*http.Client, error) { - transport := &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return winio.DialPipeContext(ctx, pipePath) - }, - } - return &http.Client{Transport: transport}, nil +func dialSocket(ctx context.Context, socketPath string) (net.Conn, error) { + return winio.DialPipeContext(ctx, socketPath) } From 4475b3704da8282705dfac18fb6f54230bc078df Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 11:10:26 +0200 Subject: [PATCH 7/9] Delay status listener creation Move listener creation from Init function to Start as Init is not supposed to create resources that need clean-up. Let httpserver.Shutdown handle closing of the listener. Signed-off-by: Kimmo Lehto (cherry picked from commit f9dd33a1802bf38905bcce62d1444eda600d1d95) --- pkg/component/status/status.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/pkg/component/status/status.go b/pkg/component/status/status.go index 4b86d021126e..05d379a30e6a 100644 --- a/pkg/component/status/status.go +++ b/pkg/component/status/status.go @@ -32,7 +32,6 @@ type Status struct { Socket string L *logrus.Entry httpserver http.Server - listener net.Listener CertManager certManager } @@ -64,20 +63,19 @@ func (s *Status) Init(_ context.Context) error { Handler: mux, } - s.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) - return nil } // 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) } }() @@ -91,9 +89,6 @@ func (s *Status) Stop() error { if err := s.httpserver.Shutdown(ctx); err != nil && !errors.Is(err, context.Canceled) { return err } - if s.listener != nil { - _ = s.listener.Close() - } cleanupStatusListener(s.Socket) return nil } From 1b143e9637f9629b8de58232d473de8db2a7026b Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 11:22:54 +0200 Subject: [PATCH 8/9] Remove redundant euid comparison when building socket path The run-dir is forced to /run/k0s for root above so there's no need to re-check euid when building the socket path. Signed-off-by: Kimmo Lehto (cherry picked from commit 499291c9409788c71b3fe02efcf8080e8c86b97c) --- pkg/config/cfgvars.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/config/cfgvars.go b/pkg/config/cfgvars.go index 7918fe0332f9..4f49304dfc2c 100644 --- a/pkg/config/cfgvars.go +++ b/pkg/config/cfgvars.go @@ -132,10 +132,8 @@ func NewCfgVars(cobraCmd command, dirs ...string) (*CfgVars, error) { return nil, err } - euid := os.Geteuid() - var runDir string - if euid == 0 { + if os.Geteuid() == 0 { runDir = "/run/k0s" } else { runDir = filepath.Join(dataDir, "run") @@ -148,7 +146,7 @@ func NewCfgVars(cobraCmd command, dirs ...string) (*CfgVars, error) { } } if statusSocketPath == "" { - if runtime.GOOS == "windows" || euid == 0 { + if runtime.GOOS == "windows" { statusSocketPath = constant.StatusSocketPathDefault } else { statusSocketPath = filepath.Join(runDir, "status.sock") From 500f2bacf9e99dcca43f57a7ac9e1df9ccdd7317 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Wed, 3 Dec 2025 11:32:17 +0200 Subject: [PATCH 9/9] Lint/fmt fixes for status.go Signed-off-by: Kimmo Lehto (cherry picked from commit dd411dcf50d8abeac638a84d236affc23d8e0ee6) --- pkg/component/status/status.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/component/status/status.go b/pkg/component/status/status.go index 05d379a30e6a..9b4e0ef8c808 100644 --- a/pkg/component/status/status.go +++ b/pkg/component/status/status.go @@ -7,7 +7,6 @@ import ( "context" "encoding/json" "errors" - "net" "net/http" "strconv" "time" @@ -58,7 +57,6 @@ func (s *Status) Init(_ context.Context) error { w.WriteHeader(http.StatusInternalServerError) } }) - var err error s.httpserver = http.Server{ Handler: mux, }