Skip to content

Commit

Permalink
Replace pid with flock for runtime config loading
Browse files Browse the repository at this point in the history
Use lock file and flock(2) to ensure there is only a single instance of
k0s running. This is more reliable than storing the pid in the runtime
config.

This also solves false positives with k0s runtime config leftovers.

Fixes: #5399
Signed-off-by: Natanael Copa <[email protected]>
  • Loading branch information
ncopa committed Jan 14, 2025
1 parent d71ff7b commit 9f1369f
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 100 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/go-logr/logr v1.4.2
github.com/go-openapi/jsonpointer v0.21.0
github.com/go-playground/validator/v10 v10.24.0
github.com/gofrs/flock v0.8.1
github.com/google/go-cmp v0.6.0
github.com/k0sproject/bootloose v0.9.0
github.com/k0sproject/version v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
Expand Down
41 changes: 22 additions & 19 deletions pkg/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"
"path/filepath"

"github.com/gofrs/flock"
"github.com/k0sproject/k0s/internal/pkg/dir"
"github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
"github.com/k0sproject/k0s/pkg/constant"
Expand Down Expand Up @@ -56,7 +57,7 @@ type RuntimeConfig struct {
type RuntimeConfigSpec struct {
NodeConfig *v1beta1.ClusterConfig `json:"nodeConfig"`
K0sVars *CfgVars `json:"k0sVars"`
Pid int `json:"pid"`
lock *flock.Flock
}

func LoadRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
Expand All @@ -83,22 +84,20 @@ func LoadRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
return nil, fmt.Errorf("%w: spec is nil", ErrInvalidRuntimeConfig)
}

// If a pid is defined but there's no process found, the instance of k0s is
// expected to have died, in which case the existing config is removed and
// an error is returned, which allows the controller startup to proceed to
// initialize a new runtime config.
if spec.Pid != 0 {
if err := checkPid(spec.Pid); err != nil {
defer func() { _ = spec.Cleanup() }()
return nil, errors.Join(ErrK0sNotRunning, err)
}
}

return spec, nil
}

func NewRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
if _, err := LoadRuntimeConfig(k0sVars); err == nil {
if err := dir.Init(filepath.Dir(k0sVars.RuntimeConfigPath), constant.RunDirMode); err != nil {
logrus.Warnf("failed to initialize runtime config dir: %v", err)
}

lock := flock.New(k0sVars.RuntimeConfigPath + ".lock")
locked, err := lock.TryLock()
if err != nil {
return nil, fmt.Errorf("failed to aquire lock on runtime config: %w", err)
}
if !locked {
return nil, ErrK0sAlreadyRunning
}

Expand All @@ -123,7 +122,7 @@ func NewRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
Spec: &RuntimeConfigSpec{
NodeConfig: nodeConfig,
K0sVars: k0sVars,
Pid: os.Getpid(),
lock: lock,
},
}

Expand All @@ -132,10 +131,6 @@ func NewRuntimeConfig(k0sVars *CfgVars) (*RuntimeConfigSpec, error) {
return nil, err
}

if err := dir.Init(filepath.Dir(k0sVars.RuntimeConfigPath), constant.RunDirMode); err != nil {
logrus.Warnf("failed to initialize runtime config dir: %v", err)
}

if err := os.WriteFile(k0sVars.RuntimeConfigPath, content, 0600); err != nil {
return nil, fmt.Errorf("failed to write runtime config: %w", err)
}
Expand All @@ -149,7 +144,15 @@ func (r *RuntimeConfigSpec) Cleanup() error {
}

if err := os.Remove(r.K0sVars.RuntimeConfigPath); err != nil {
return fmt.Errorf("failed to clean up runtime config file: %w", err)
logrus.Warnf("failed to clean up runtime config file: %v", err)
}

if err := r.lock.Close(); err != nil {
return fmt.Errorf("failed to unlock runtime config: %w", err)
}

if err := os.Remove(r.lock.Path()); err != nil {
return fmt.Errorf("failed to delete %s: %w", r.lock.Path(), err)
}
return nil
}
23 changes: 13 additions & 10 deletions pkg/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ import (

"github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLoadRuntimeConfig_K0sNotRunning(t *testing.T) {
func TestLoadRuntimeConfig(t *testing.T) {
// create a temporary file for runtime config
tmpfile, err := os.CreateTemp("", "runtime-config")
assert.NoError(t, err)
require.NoError(t, err)
defer os.Remove(tmpfile.Name())

// prepare k0sVars
Expand All @@ -43,26 +44,29 @@ spec:
nodeConfig:
metadata:
name: k0s
pid: 9999999
`)
err = os.WriteFile(k0sVars.RuntimeConfigPath, content, 0644)
assert.NoError(t, err)
require.NoError(t, err)

// try to load runtime config and check if it returns an error
spec, err := LoadRuntimeConfig(k0sVars)
assert.Nil(t, spec)
assert.ErrorIs(t, err, ErrK0sNotRunning)
require.NoError(t, err)
assert.NotNil(t, spec)
}

func TestNewRuntimeConfig(t *testing.T) {
// create a temporary directory for k0s files
tempDir, err := os.MkdirTemp("", "k0s")
assert.NoError(t, err)
require.NoError(t, err)
defer os.RemoveAll(tempDir)

tempRunDir, err := os.MkdirTemp("", "k0s-rundir")
require.NoError(t, err)
defer os.RemoveAll(tempRunDir)

// create a temporary file for the runtime config
tmpfile, err := os.CreateTemp("", "runtime-config")
assert.NoError(t, err)
tmpfile, err := os.CreateTemp(tempRunDir, "runtime-config")
require.NoError(t, err)
tmpfile.Close()
defer os.Remove(tmpfile.Name())

Expand All @@ -82,7 +86,6 @@ func TestNewRuntimeConfig(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, spec)
assert.Equal(t, tempDir, spec.K0sVars.DataDir)
assert.Equal(t, os.Getpid(), spec.Pid)
assert.NotNil(t, spec.NodeConfig)
cfg, err := spec.K0sVars.NodeConfig()
assert.NoError(t, err)
Expand Down
38 changes: 0 additions & 38 deletions pkg/config/runtime_unix.go

This file was deleted.

33 changes: 0 additions & 33 deletions pkg/config/runtime_windows.go

This file was deleted.

0 comments on commit 9f1369f

Please sign in to comment.