Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions cmd/airgap/bundleartifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import (
"errors"
"fmt"
"io"
"os"
"os/signal"
"strconv"
"syscall"

"github.com/k0sproject/k0s/cmd/internal"
"github.com/k0sproject/k0s/internal/pkg/file"
Expand Down Expand Up @@ -51,9 +48,6 @@ instead of in an arbitrary order based on when they finish downloading.
`,
PersistentPreRun: debugFlags.Run,
RunE: func(cmd *cobra.Command, args []string) (err error) {
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
defer cancel()

cmd.SilenceUsage = true

if bundler.Concurrency == 0 {
Expand Down Expand Up @@ -94,7 +88,7 @@ instead of in an arbitrary order based on when they finish downloading.
}

buffered := bufio.NewWriter(out)
if err := bundler.Run(ctx, refs, out); err != nil {
if err := bundler.Run(cmd.Context(), refs, out); err != nil {
return err
}
return buffered.Flush()
Expand Down
25 changes: 9 additions & 16 deletions cmd/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import (
"io/fs"
"net"
"os"
"os/signal"
"path/filepath"
"slices"
"syscall"
"time"

"github.com/k0sproject/k0s/cmd/internal"
Expand All @@ -24,6 +22,7 @@ import (
"github.com/k0sproject/k0s/internal/pkg/file"
"github.com/k0sproject/k0s/internal/pkg/stringmap"
"github.com/k0sproject/k0s/internal/pkg/sysinfo"
"github.com/k0sproject/k0s/internal/supervised"
"github.com/k0sproject/k0s/internal/sync/value"
"github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
"github.com/k0sproject/k0s/pkg/applier"
Expand Down Expand Up @@ -104,9 +103,7 @@ func NewControllerCmd() *cobra.Command {
return err
}

ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
defer cancel()
return c.start(ctx, &controllerFlags, debugFlags.IsDebug())
return c.start(cmd.Context(), &controllerFlags, debugFlags.IsDebug())
},
}

Expand Down Expand Up @@ -593,23 +590,19 @@ func (c *command) start(ctx context.Context, flags *config.ControllerOptions, de
}
}()

perfTimer.Output()

if controllerMode.WorkloadsEnabled() {
perfTimer.Checkpoint("starting-worker")
if err := c.startWorker(ctx, nodeName, kubeletExtraArgs, flags); err != nil {
logrus.WithError(err).Error("Failed to start controller worker")
} else {
perfTimer.Checkpoint("started-worker")
}
return c.startWorker(ctx, nodeName, kubeletExtraArgs, flags)
}

perfTimer.Output()
if supervised := supervised.Get(ctx); supervised != nil {
supervised.MarkReady()
}

// Wait for k0s process termination
<-ctx.Done()
logrus.Debug("Context done in main")
logrus.Info("Shutting down k0s controller")

perfTimer.Output()
logrus.Info("Shutting down k0s: ", context.Cause(ctx))

return nil
}
Expand Down
8 changes: 3 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
package cmd

import (
"context"
"errors"
"os"

"github.com/k0sproject/k0s/cmd/airgap"
"github.com/k0sproject/k0s/cmd/api"
Expand Down Expand Up @@ -146,8 +146,6 @@ $ k0s completion fish > ~/.config/fish/completions/k0s.fish
}
}

func Execute() {
if err := NewRootCmd().Execute(); err != nil {
os.Exit(1)
}
func Execute(ctx context.Context) error {
return NewRootCmd().ExecuteContext(ctx)
}
20 changes: 11 additions & 9 deletions cmd/worker/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import (
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime"
"syscall"

"github.com/k0sproject/k0s/cmd/internal"
"github.com/k0sproject/k0s/internal/pkg/dir"
"github.com/k0sproject/k0s/internal/pkg/file"
"github.com/k0sproject/k0s/internal/pkg/flags"
"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/component/iptables"
"github.com/k0sproject/k0s/pkg/component/manager"
"github.com/k0sproject/k0s/pkg/component/prober"
Expand Down Expand Up @@ -94,10 +93,6 @@ func NewWorkerCmd() *cobra.Command {
return err
}

// Set up signal handling
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
defer cancel()

// Check for legacy CA file (unused on worker-only nodes since 1.33)
if legacyCAFile := filepath.Join(c.K0sVars.CertRootDir, "ca.crt"); file.Exists(legacyCAFile) {
// Keep the file to allow interop between 1.32 and 1.33.
Expand All @@ -116,7 +111,7 @@ func NewWorkerCmd() *cobra.Command {
return err
}

return c.Start(ctx, nodeName, kubeletExtraArgs, getBootstrapKubeconfig, nil)
return c.Start(cmd.Context(), nodeName, kubeletExtraArgs, getBootstrapKubeconfig, nil)
},
}

Expand Down Expand Up @@ -313,13 +308,20 @@ func (c *Command) Start(ctx context.Context, nodeName apitypes.NodeName, kubelet
if err != nil {
return fmt.Errorf("failed to start worker components: %w", err)
}

if supervised := supervised.Get(ctx); supervised != nil {
supervised.MarkReady()
}

// Wait for k0s process termination
<-ctx.Done()
logrus.Info("Shutting down k0s worker")
logrus.Info("Shutting down k0s: ", context.Cause(ctx))

// Stop components
if err := componentManager.Stop(); err != nil {
logrus.WithError(err).Error("error while stopping component manager")
logrus.WithError(err).Error("Failed to stop worker components")
} else {
logrus.Info("All worker components stopped")
}
return nil
}
40 changes: 40 additions & 0 deletions internal/supervised/supervised.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 k0s authors
// SPDX-License-Identifier: Apache-2.0

// Package supervised helps integrating k0s with process supervisors.
package supervised

import (
"context"

"github.com/k0sproject/k0s/pkg/k0scontext"
)

// Interaction points with the process supervisor.
type Interface interface {
// Signals to the supervisor that k0s is ready. Can only be called once.
// Subsequent calls to this method are no-ops.
MarkReady()
}

// Gets this process's interface to its supervisor, if any.
func Get(ctx context.Context) Interface {
return k0scontext.Value[Interface](ctx)
}

// The main function to run in a supervised fashion.
type MainFunc func(context.Context) error

// Runs the main function in a supervisor-aware manner. The main function can
// interact with the supervisor by obtaining a supervision interface via [Get].
// Whenever the supervisor deems that k0s should exit, the context passed to
// main is canceled.
func Run(ctx context.Context, main MainFunc) error {
// This is not doing anything special yet. Explicitly store a nil interface.
ctx = set(ctx, nil)
return main(ctx)
}

func set(ctx context.Context, supervised Interface) context.Context {
return k0scontext.WithValue(ctx, supervised)
}
23 changes: 2 additions & 21 deletions inttest/common/bootloosesuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import (
"net/url"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"testing"
"text/template"
"time"
Expand Down Expand Up @@ -196,7 +194,7 @@ func (s *BootlooseSuite) SetupSuite() {
t.Logf("Cleaning up")

// Get a fresh context for the cleanup tasks.
ctx, cancel := signalAwareCtx(t.Context())
ctx, cancel := k0scontext.ShutdownContext(t.Context())
defer cancel(nil)
s.cleanupSuite(ctx, t)
}()
Expand All @@ -212,23 +210,6 @@ func (s *BootlooseSuite) SetupSuite() {
}
}

func signalAwareCtx(parent context.Context) (context.Context, context.CancelCauseFunc) {
ctx, cancel := context.WithCancelCause(parent)

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
go func() {
defer signal.Stop(sigs)
select {
case <-ctx.Done():
case sig := <-sigs:
cancel(fmt.Errorf("signal received: %s", sig))
}
}()

return ctx, cancel
}

// waitForSSH waits to get a SSH connection to all bootloose machines defined as part of the test suite.
// Each node is tried in parallel for ~30secs max
func (s *BootlooseSuite) waitForSSH(ctx context.Context) {
Expand Down Expand Up @@ -1313,7 +1294,7 @@ func cleanupClusterDir(t *testing.T, dir string) {
}

func newSuiteContext(t *testing.T) (context.Context, context.CancelCauseFunc) {
signalCtx, cancel := signalAwareCtx(t.Context())
signalCtx, cancel := k0scontext.ShutdownContext(t.Context())

// We need to reserve some time to conduct a proper teardown of the suite before the test timeout kicks in.
deadline, hasDeadline := t.Deadline()
Expand Down
9 changes: 8 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
package main

import (
"context"
"os"
"path"
"strings"

"github.com/k0sproject/k0s/cmd"
internallog "github.com/k0sproject/k0s/internal/pkg/log"
"github.com/k0sproject/k0s/internal/supervised"
"github.com/k0sproject/k0s/pkg/k0scontext"
"github.com/k0sproject/k0s/pkg/supervisor"
)

Expand All @@ -22,12 +25,16 @@ func init() {
func main() {
supervisor.TerminationHelperHook()

ctx, _ := k0scontext.ShutdownContext(context.Background())

// Make embedded commands work through symlinks such as /usr/local/bin/kubectl (or k0s-kubectl)
progN := strings.TrimPrefix(path.Base(os.Args[0]), "k0s-")
switch progN {
case "kubectl", "ctr":
os.Args = append([]string{"k0s", progN}, os.Args[1:]...)
}

cmd.Execute()
if err := supervised.Run(ctx, cmd.Execute); err != nil {
os.Exit(1)
}
}
39 changes: 39 additions & 0 deletions pkg/k0scontext/signal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2025 k0s authors
// SPDX-License-Identifier: Apache-2.0

package k0scontext

import (
"context"
"errors"
"os"
"os/signal"
"syscall"
)

// Returns a context that gets canceled as soon as k0s receives a signal to
// which it should respond with a clean shutdown.
func ShutdownContext(parent context.Context) (context.Context, context.CancelCauseFunc) {
ctx, cancel := context.WithCancelCause(parent)

sigs := make(chan os.Signal, 1)
signal.Notify(sigs,
syscall.SIGINT, // Windows: CTRL_C_EVENT, CTRL_BREAK_EVENT
syscall.SIGTERM, // Windows: CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT

// Windows behavior:
// https://learn.microsoft.com/en-us/windows/console/console-control-handlers
// https://github.com/golang/go/commit/5d1a95175e693f5be0bc31ae9e6a7873318925eb#diff-fc175f04ebb256c1d34c14d27b8915f38928b71df55a35bfbd86fcb4618ff5a9
)

go func() {
defer signal.Stop(sigs)
select {
case <-ctx.Done():
case sig := <-sigs:
cancel(errors.New("signal received: " + sig.String()))
}
}()

return ctx, cancel
}