Add migration for older configuration versions
Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
parent
0f8b79b2af
commit
8f013bb717
@ -43,6 +43,13 @@ import (
|
|||||||
// CurrentConfigVersion is the max config version which is supported
|
// CurrentConfigVersion is the max config version which is supported
|
||||||
const CurrentConfigVersion = 3
|
const CurrentConfigVersion = 3
|
||||||
|
|
||||||
|
// migrations hold the migration functions for every prior containerd config version
|
||||||
|
var migrations = []func(context.Context, *Config) error{
|
||||||
|
nil, // Version 0 is not defined, treated at version 1
|
||||||
|
v1Migrate, // Version 1 plugins renamed to URI for version 2
|
||||||
|
nil, // Version 2 has only plugin changes to version 3
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Any new map fields added also need to be handled in mergeConfig.
|
// NOTE: Any new map fields added also need to be handled in mergeConfig.
|
||||||
|
|
||||||
// Config provides containerd configuration data for the server
|
// Config provides containerd configuration data for the server
|
||||||
@ -67,9 +74,11 @@ type Config struct {
|
|||||||
Metrics MetricsConfig `toml:"metrics"`
|
Metrics MetricsConfig `toml:"metrics"`
|
||||||
// DisabledPlugins are IDs of plugins to disable. Disabled plugins won't be
|
// DisabledPlugins are IDs of plugins to disable. Disabled plugins won't be
|
||||||
// initialized and started.
|
// initialized and started.
|
||||||
|
// DisabledPlugins must use a fully qualified plugin URI.
|
||||||
DisabledPlugins []string `toml:"disabled_plugins"`
|
DisabledPlugins []string `toml:"disabled_plugins"`
|
||||||
// RequiredPlugins are IDs of required plugins. Containerd exits if any
|
// RequiredPlugins are IDs of required plugins. Containerd exits if any
|
||||||
// required plugin doesn't exist or fails to be initialized or started.
|
// required plugin doesn't exist or fails to be initialized or started.
|
||||||
|
// RequiredPlugins must use a fully qualified plugin URI.
|
||||||
RequiredPlugins []string `toml:"required_plugins"`
|
RequiredPlugins []string `toml:"required_plugins"`
|
||||||
// Plugins provides plugin specific configuration for the initialization of a plugin
|
// Plugins provides plugin specific configuration for the initialization of a plugin
|
||||||
Plugins map[string]interface{} `toml:"plugins"`
|
Plugins map[string]interface{} `toml:"plugins"`
|
||||||
@ -101,41 +110,83 @@ type StreamProcessor struct {
|
|||||||
Env []string `toml:"env"`
|
Env []string `toml:"env"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVersion returns the config file's version
|
|
||||||
func (c *Config) GetVersion() int {
|
|
||||||
if c.Version == 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return c.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateVersion validates the config for a v2 file
|
// ValidateVersion validates the config for a v2 file
|
||||||
func (c *Config) ValidateVersion() error {
|
func (c *Config) ValidateVersion() error {
|
||||||
version := c.GetVersion()
|
if c.Version > CurrentConfigVersion {
|
||||||
if version == 1 {
|
return fmt.Errorf("expected containerd config version equal to or less than `%d`, got `%d`", CurrentConfigVersion, c.Version)
|
||||||
return errors.New("containerd config version `1` is no longer supported since containerd v2.0, please switch to version `2`, " +
|
|
||||||
"see https://github.com/containerd/containerd/blob/main/docs/PLUGINS.md#version-header")
|
|
||||||
}
|
|
||||||
|
|
||||||
if version > CurrentConfigVersion {
|
|
||||||
return fmt.Errorf("expected containerd config version equal to or less than `%d`, got `%d`", CurrentConfigVersion, version)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range c.DisabledPlugins {
|
for _, p := range c.DisabledPlugins {
|
||||||
if !strings.HasPrefix(p, "io.containerd.") || len(strings.SplitN(p, ".", 4)) < 4 {
|
if !strings.ContainsAny(p, ".") {
|
||||||
return fmt.Errorf("invalid disabled plugin URI %q expect io.containerd.x.vx", p)
|
return fmt.Errorf("invalid disabled plugin URI %q expect io.containerd.x.vx", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range c.RequiredPlugins {
|
for _, p := range c.RequiredPlugins {
|
||||||
if !strings.HasPrefix(p, "io.containerd.") || len(strings.SplitN(p, ".", 4)) < 4 {
|
if !strings.ContainsAny(p, ".") {
|
||||||
return fmt.Errorf("invalid required plugin URI %q expect io.containerd.x.vx", p)
|
return fmt.Errorf("invalid required plugin URI %q expect io.containerd.x.vx", p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for p := range c.Plugins {
|
|
||||||
if !strings.HasPrefix(p, "io.containerd.") || len(strings.SplitN(p, ".", 4)) < 4 {
|
return nil
|
||||||
return fmt.Errorf("invalid plugin key URI %q expect io.containerd.x.vx", p)
|
}
|
||||||
|
|
||||||
|
// MigrateConfig will convert the config to the latest version before using
|
||||||
|
func (c *Config) MigrateConfig(ctx context.Context) error {
|
||||||
|
for c.Version < CurrentConfigVersion {
|
||||||
|
if m := migrations[c.Version]; m != nil {
|
||||||
|
if err := m(ctx, c); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.Version++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func v1Migrate(ctx context.Context, c *Config) error {
|
||||||
|
plugins := make(map[string]interface{}, len(c.Plugins))
|
||||||
|
|
||||||
|
// corePlugins is the list of used plugins before v1 was deprecated
|
||||||
|
corePlugins := map[string]string{
|
||||||
|
"cri": "io.containerd.grpc.v1.cri",
|
||||||
|
"cgroups": "io.containerd.monitor.v1.cgroups",
|
||||||
|
"linux": "io.containerd.runtime.v1.linux",
|
||||||
|
"scheduler": "io.containerd.gc.v1.scheduler",
|
||||||
|
"bolt": "io.containerd.metadata.v1.bolt",
|
||||||
|
"task": "io.containerd.runtime.v2.task",
|
||||||
|
"opt": "io.containerd.internal.v1.opt",
|
||||||
|
"restart": "io.containerd.internal.v1.restart",
|
||||||
|
"tracing": "io.containerd.internal.v1.tracing",
|
||||||
|
"otlp": "io.containerd.tracing.processor.v1.otlp",
|
||||||
|
"aufs": "io.containerd.snapshotter.v1.aufs",
|
||||||
|
"btrfs": "io.containerd.snapshotter.v1.btrfs",
|
||||||
|
"devmapper": "io.containerd.snapshotter.v1.devmapper",
|
||||||
|
"native": "io.containerd.snapshotter.v1.native",
|
||||||
|
"overlayfs": "io.containerd.snapshotter.v1.overlayfs",
|
||||||
|
"zfs": "io.containerd.snapshotter.v1.zfs",
|
||||||
|
}
|
||||||
|
for plugin, value := range c.Plugins {
|
||||||
|
if !strings.ContainsAny(plugin, ".") {
|
||||||
|
var ambiguous string
|
||||||
|
if full, ok := corePlugins[plugin]; ok {
|
||||||
|
plugin = full
|
||||||
|
} else if strings.HasSuffix(plugin, "-service") {
|
||||||
|
plugin = "io.containerd.service.v1." + plugin
|
||||||
|
} else if plugin == "windows" || plugin == "windows-lcow" {
|
||||||
|
// runtime, differ, and snapshotter plugins do not have configs for v1
|
||||||
|
ambiguous = plugin
|
||||||
|
plugin = "io.containerd.snapshotter.v1." + plugin
|
||||||
|
} else {
|
||||||
|
ambiguous = plugin
|
||||||
|
plugin = "io.containerd.grpc.v1." + plugin
|
||||||
|
}
|
||||||
|
if ambiguous != "" {
|
||||||
|
log.G(ctx).Warnf("Ambiguous %s plugin in v1 config, treating as %s", ambiguous, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugins[plugin] = value
|
||||||
|
}
|
||||||
|
c.Plugins = plugins
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,12 @@ import (
|
|||||||
"github.com/containerd/log/logtest"
|
"github.com/containerd/log/logtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMigrations(t *testing.T) {
|
||||||
|
if len(migrations) != CurrentConfigVersion {
|
||||||
|
t.Fatalf("Migration missing, expected %d migrations, only %d defined", CurrentConfigVersion, len(migrations))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMergeConfigs(t *testing.T) {
|
func TestMergeConfigs(t *testing.T) {
|
||||||
a := &Config{
|
a := &Config{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
@ -215,9 +221,10 @@ version = 2
|
|||||||
assert.Equal(t, true, pluginConfig["shim_debug"])
|
assert.Equal(t, true, pluginConfig["shim_debug"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDecodePluginInV1Config tests decoding non-versioned
|
// TestDecodePluginInV1Config tests decoding non-versioned config
|
||||||
// config (should be parsed as V1 config).
|
// (should be parsed as V1 config) and migrated to latest.
|
||||||
func TestDecodePluginInV1Config(t *testing.T) {
|
func TestDecodePluginInV1Config(t *testing.T) {
|
||||||
|
ctx := logtest.WithT(context.Background(), t)
|
||||||
data := `
|
data := `
|
||||||
[plugins.linux]
|
[plugins.linux]
|
||||||
shim_debug = true
|
shim_debug = true
|
||||||
@ -229,5 +236,15 @@ func TestDecodePluginInV1Config(t *testing.T) {
|
|||||||
|
|
||||||
var out Config
|
var out Config
|
||||||
err = LoadConfig(context.Background(), path, &out)
|
err = LoadConfig(context.Background(), path, &out)
|
||||||
assert.ErrorContains(t, err, "config version `1` is no longer supported")
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, out.Version)
|
||||||
|
|
||||||
|
err = out.MigrateConfig(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3, out.Version)
|
||||||
|
|
||||||
|
pluginConfig := map[string]interface{}{}
|
||||||
|
_, err = out.Decode(ctx, &plugin.Registration{Type: "io.containerd.runtime.v1", ID: "linux", Config: &pluginConfig})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, pluginConfig["shim_debug"])
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,20 @@ func CreateTopLevelDirectories(config *srvconfig.Config) error {
|
|||||||
|
|
||||||
// New creates and initializes a new containerd server
|
// New creates and initializes a new containerd server
|
||||||
func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
||||||
|
var (
|
||||||
|
version = config.Version
|
||||||
|
migrationT time.Duration
|
||||||
|
)
|
||||||
|
if version < srvconfig.CurrentConfigVersion {
|
||||||
|
// Migrate config to latest version
|
||||||
|
t1 := time.Now()
|
||||||
|
err := config.MigrateConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
migrationT = time.Since(t1)
|
||||||
|
}
|
||||||
|
|
||||||
if err := apply(ctx, config); err != nil {
|
if err := apply(ctx, config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -203,10 +217,12 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
|||||||
required[r] = struct{}{}
|
required[r] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version < srvconfig.CurrentConfigVersion {
|
||||||
|
t1 := time.Now()
|
||||||
// Run migration for each configuration version
|
// Run migration for each configuration version
|
||||||
// Run each plugin migration for each version to ensure that migration logic is simple and
|
// Run each plugin migration for each version to ensure that migration logic is simple and
|
||||||
// focused on upgrading from one version at a time.
|
// focused on upgrading from one version at a time.
|
||||||
for v := config.Version; v < srvconfig.CurrentConfigVersion; v++ {
|
for v := version; v < srvconfig.CurrentConfigVersion; v++ {
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
if p.ConfigMigration != nil {
|
if p.ConfigMigration != nil {
|
||||||
if err := p.ConfigMigration(ctx, v, config.Plugins); err != nil {
|
if err := p.ConfigMigration(ctx, v, config.Plugins); err != nil {
|
||||||
@ -215,6 +231,11 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
migrationT = migrationT + time.Since(t1)
|
||||||
|
}
|
||||||
|
if migrationT > 0 {
|
||||||
|
log.G(ctx).WithField("t", migrationT).Warnf("Configuration migrated from version %d, use `containerd config migrate` to avoid migration", version)
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
id := p.URI()
|
id := p.URI()
|
||||||
|
Loading…
Reference in New Issue
Block a user