cri: write generated CNI config atomically on Unix

The 10-containerd-net.conflist file generated from the conf_template
should be written atomically so that partial writes are not visible to
CNI plugins. Use the new consistentfile package to ensure this on
Unix-like platforms such as Linux, FreeBSD, and Darwin.

Fixes https://github.com/containerd/containerd/issues/8607

Signed-off-by: Samuel Karp <samuelkarp@google.com>
This commit is contained in:
Samuel Karp 2023-05-30 23:43:54 -07:00
parent f3ba7c8a35
commit 3c4a1ab1cb
No known key found for this signature in database
GPG Key ID: 997C5A3CD3167CB5
2 changed files with 60 additions and 44 deletions

View File

@ -26,8 +26,10 @@ import (
"text/template"
"time"
"github.com/containerd/containerd/log"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/pkg/atomicfile"
)
// cniConfigTemplate contains the values containerd will overwrite
@ -88,27 +90,8 @@ func (c *criService) UpdateRuntimeConfig(ctx context.Context, r *runtime.UpdateR
log.G(ctx).Infof("CNI config is successfully loaded, skip generating cni config from template %q", confTemplate)
return &runtime.UpdateRuntimeConfigResponse{}, nil
}
log.G(ctx).Infof("Generating cni config from template %q", confTemplate)
// generate cni config file from the template with updated pod cidr.
t, err := template.ParseFiles(confTemplate)
if err != nil {
return nil, fmt.Errorf("failed to parse cni config template %q: %w", confTemplate, err)
}
if err := os.MkdirAll(c.config.NetworkPluginConfDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create cni config directory: %q: %w", c.config.NetworkPluginConfDir, err)
}
confFile := filepath.Join(c.config.NetworkPluginConfDir, cniConfigFileName)
f, err := os.OpenFile(confFile, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return nil, fmt.Errorf("failed to open cni config file %q: %w", confFile, err)
}
defer f.Close()
if err := t.Execute(f, cniConfigTemplate{
PodCIDR: cidrs[0],
PodCIDRRanges: cidrs,
Routes: routes,
}); err != nil {
return nil, fmt.Errorf("failed to generate cni config file %q: %w", confFile, err)
if err := writeCNIConfigFile(ctx, c.config.NetworkPluginConfDir, confTemplate, cidrs[0], cidrs, routes); err != nil {
return nil, err
}
return &runtime.UpdateRuntimeConfigResponse{}, nil
}
@ -138,3 +121,28 @@ func getRoutes(cidrs []string) ([]string, error) {
}
return routes, nil
}
func writeCNIConfigFile(ctx context.Context, confDir string, confTemplate string, podCIDR string, podCIDRRanges []string, routes []string) error {
log.G(ctx).Infof("Generating cni config from template %q", confTemplate)
// generate cni config file from the template with updated pod cidr.
t, err := template.ParseFiles(confTemplate)
if err != nil {
return fmt.Errorf("failed to parse cni config template %q: %w", confTemplate, err)
}
if err := os.MkdirAll(confDir, 0755); err != nil {
return fmt.Errorf("failed to create cni config directory: %q: %w", confDir, err)
}
confFile := filepath.Join(confDir, cniConfigFileName)
f, err := atomicfile.New(confFile, 0o644)
defer func() {
err = f.Close()
}()
if err := t.Execute(f, cniConfigTemplate{
PodCIDR: podCIDR,
PodCIDRRanges: podCIDRRanges,
Routes: routes,
}); err != nil {
return fmt.Errorf("failed to generate cni config file %q: %w", confFile, err)
}
return err
}

View File

@ -26,8 +26,10 @@ import (
"text/template"
"time"
"github.com/containerd/containerd/log"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/pkg/atomicfile"
)
// cniConfigTemplate contains the values containerd will overwrite
@ -89,27 +91,8 @@ func (c *criService) UpdateRuntimeConfig(ctx context.Context, r *runtime.UpdateR
log.G(ctx).Infof("CNI config is successfully loaded, skip generating cni config from template %q", confTemplate)
return &runtime.UpdateRuntimeConfigResponse{}, nil
}
log.G(ctx).Infof("Generating cni config from template %q", confTemplate)
// generate cni config file from the template with updated pod cidr.
t, err := template.ParseFiles(confTemplate)
if err != nil {
return nil, fmt.Errorf("failed to parse cni config template %q: %w", confTemplate, err)
}
if err := os.MkdirAll(c.config.NetworkPluginConfDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create cni config directory: %q: %w", c.config.NetworkPluginConfDir, err)
}
confFile := filepath.Join(c.config.NetworkPluginConfDir, cniConfigFileName)
f, err := os.OpenFile(confFile, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return nil, fmt.Errorf("failed to open cni config file %q: %w", confFile, err)
}
defer f.Close()
if err := t.Execute(f, cniConfigTemplate{
PodCIDR: cidrs[0],
PodCIDRRanges: cidrs,
Routes: routes,
}); err != nil {
return nil, fmt.Errorf("failed to generate cni config file %q: %w", confFile, err)
if err := writeCNIConfigFile(ctx, c.config.NetworkPluginConfDir, confTemplate, cidrs[0], cidrs, routes); err != nil {
return nil, err
}
return &runtime.UpdateRuntimeConfigResponse{}, nil
}
@ -139,3 +122,28 @@ func getRoutes(cidrs []string) ([]string, error) {
}
return routes, nil
}
func writeCNIConfigFile(ctx context.Context, confDir string, confTemplate string, podCIDR string, podCIDRRanges []string, routes []string) error {
log.G(ctx).Infof("Generating cni config from template %q", confTemplate)
// generate cni config file from the template with updated pod cidr.
t, err := template.ParseFiles(confTemplate)
if err != nil {
return fmt.Errorf("failed to parse cni config template %q: %w", confTemplate, err)
}
if err := os.MkdirAll(confDir, 0755); err != nil {
return fmt.Errorf("failed to create cni config directory: %q: %w", confDir, err)
}
confFile := filepath.Join(confDir, cniConfigFileName)
f, err := atomicfile.New(confFile, 0o644)
defer func() {
err = f.Close()
}()
if err := t.Execute(f, cniConfigTemplate{
PodCIDR: podCIDR,
PodCIDRRanges: podCIDRRanges,
Routes: routes,
}); err != nil {
return fmt.Errorf("failed to generate cni config file %q: %w", confFile, err)
}
return err
}