diff --git a/pkg/component/worker/containerd/component.go b/pkg/component/worker/containerd/component.go index 53c1cbb3a07f..ae0343562987 100644 --- a/pkg/component/worker/containerd/component.go +++ b/pkg/component/worker/containerd/component.go @@ -28,17 +28,16 @@ import ( "os" "path/filepath" "runtime" - "strings" "syscall" "time" + "github.com/BurntSushi/toml" "github.com/fsnotify/fsnotify" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "github.com/k0sproject/k0s/internal/pkg/dir" "github.com/k0sproject/k0s/internal/pkg/file" - "github.com/k0sproject/k0s/internal/pkg/templatewriter" "github.com/k0sproject/k0s/pkg/assets" "github.com/k0sproject/k0s/pkg/component/manager" workerconfig "github.com/k0sproject/k0s/pkg/component/worker/config" @@ -48,16 +47,10 @@ import ( "github.com/k0sproject/k0s/pkg/supervisor" ) -const confTmpl = `# k0s_managed=true +const containerdTomlHeader = `# k0s_managed=true # This is a placeholder configuration for k0s managed containerd. # If you wish to override the config, remove the first line and replace this file with your custom configuration. # For reference see https://github.com/containerd/containerd/blob/main/docs/man/containerd-config.toml.5.md -version = 2 -imports = [ - {{- range $i := .Imports }} - "{{ $i }}", - {{- end }} -] ` const confPathPosix = "/etc/k0s/containerd.toml" const confPathWindows = "C:\\Program Files\\containerd\\config.toml" @@ -205,33 +198,32 @@ func (c *Component) setupConfig() error { } criConfigPath := filepath.Join(c.K0sVars.RunDir, "containerd-cri.toml") - err = file.WriteContentAtomically(criConfigPath, []byte(config.CRIConfig), 0644) - if err != nil { - return fmt.Errorf("can't create containerd CRI config: %w", err) - } - var data struct{ Imports []string } - data.Imports = append(config.ImportPaths, criConfigPath) - - // double escape for windows because containerd expects - // double backslash in the configuration but golang templates - // unescape double slash to a single slash - if runtime.GOOS == "windows" { - for i := range data.Imports { - data.Imports[i] = strings.ReplaceAll(data.Imports[i], "\\", "\\\\") - } + if err = file.AtomicWithTarget(criConfigPath). + WithPermissions(0644). + WriteString(config.CRIConfig); err != nil { + return fmt.Errorf("can't create containerd CRI config: %w", err) } - output := bytes.NewBuffer([]byte{}) - tw := templatewriter.TemplateWriter{ - Name: "containerdconfig", - Template: confTmpl, - Data: data, - } - if err := tw.WriteToBuffer(output); err != nil { + if err := file.AtomicWithTarget(c.confPath). + WithPermissions(0644). + Do(func(f file.AtomicWriter) error { + w := bufio.NewWriter(f) + if _, err := w.WriteString(containerdTomlHeader); err != nil { + return err + } + if err := toml.NewEncoder(w).Encode(map[string]any{ + "version": 2, + "imports": append(config.ImportPaths, criConfigPath), + }); err != nil { + return err + } + return w.Flush() + }); err != nil { return fmt.Errorf("can't create containerd config: %w", err) } - return file.WriteContentAtomically(c.confPath, output.Bytes(), 0644) + + return nil } func (c *Component) watchDropinConfigs(ctx context.Context) { diff --git a/pkg/component/worker/containerd/configurer.go b/pkg/component/worker/containerd/configurer.go index 56b4edb32271..dfbea6a969c5 100644 --- a/pkg/component/worker/containerd/configurer.go +++ b/pkg/component/worker/containerd/configurer.go @@ -109,21 +109,18 @@ func generateDefaultCRIConfig(sandboxContainerImage string) ([]byte, error) { // Set pause image criPluginConfig.SandboxImage = sandboxContainerImage if runtime.GOOS == "windows" { - criPluginConfig.CniConfig.NetworkPluginBinDir = "c:\\opt\\cni\\bin" - criPluginConfig.CniConfig.NetworkPluginConfDir = "c:\\opt\\cni\\conf" + // The default config for Windows uses %ProgramFiles%/containerd/cni/{bin,conf}. + // Maybe k0s can use the default in the future, so there's no need for this override. + criPluginConfig.CniConfig.NetworkPluginBinDir = `c:\opt\cni\bin` + criPluginConfig.CniConfig.NetworkPluginConfDir = `c:\opt\cni\conf` } - // We need to use custom struct so we can unmarshal the CRI plugin config only - containerdConfig := struct { - Version int `toml:"version"` - Plugins map[string]interface{} `toml:"plugins"` - }{ - Version: 2, - Plugins: map[string]interface{}{ + + return toml.Marshal(map[string]any{ + "version": 2, + "plugins": map[string]any{ "io.containerd.grpc.v1.cri": criPluginConfig, }, - } - - return toml.Marshal(containerdConfig) + }) } func hasCRIPluginConfig(data []byte) (bool, error) { diff --git a/pkg/component/worker/containerd/configurer_test.go b/pkg/component/worker/containerd/configurer_test.go index b45fa65e7dd7..05b1da1554fb 100644 --- a/pkg/component/worker/containerd/configurer_test.go +++ b/pkg/component/worker/containerd/configurer_test.go @@ -39,8 +39,9 @@ func TestConfigurer_HandleImports(t *testing.T) { err := os.WriteFile(filepath.Join(importsPath, "foo.toml"), []byte(criRuntimeConfig), 0644) require.NoError(t, err) c := configurer{ - loadPath: filepath.Join(importsPath, "*.toml"), - log: logrus.New().WithField("test", t.Name()), + loadPath: filepath.Join(importsPath, "*.toml"), + pauseImage: "pause:42", + log: logrus.New().WithField("test", t.Name()), } criConfig, err := c.handleImports() assert.NoError(t, err) @@ -60,8 +61,10 @@ func TestConfigurer_HandleImports(t *testing.T) { assert.Equal(t, 2, containerdConfig.Version) criPluginConfig := containerdConfig.Plugins["io.containerd.grpc.v1.cri"] require.NotNil(t, criPluginConfig, "No CRI plugin configuration section found") + sandboxImage := criPluginConfig.Get("sandbox_image") + assert.Equal(t, "pause:42", sandboxImage, "Custom pause image not found in CRI configuration") snapshotter := criPluginConfig.GetPath([]string{"containerd", "snapshotter"}) - require.Equal(t, "zfs", snapshotter) + assert.Equal(t, "zfs", snapshotter, "Overridden snapshotter not found in CRI configuration") }) t.Run("should have no imports if imports dir is empty", func(t *testing.T) {