mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-20 06:01:13 +02:00
This PR introduces a new strategy for upgrades. Instead of attempting to zap the partition table, create a new one, and then format the partitions, this change will only update the `vmlinuz`, and `initramfs.xz` being used to boot. It introduces an A/B style upgrade process, which will allow for easy rollbacks. One deviation from our original intention with upgrades is that this change does not completely reset a node. It falls just short of that and does not reset the partition table. This forces us to keep the current partition scheme in mind as we make changes in the future, because an upgrade assumes a specific partition scheme. We can improve upgrades further in the future, but this will at least make them more dependable. Finally, one more feature in this PR is the ability to keep state. This enables single node clusters to upgrade since we keep the etcd data around. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package install
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/containerd/containerd/cio"
|
|
"github.com/containerd/containerd/namespaces"
|
|
"github.com/containerd/containerd/oci"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
|
|
"github.com/talos-systems/go-procfs/procfs"
|
|
|
|
"github.com/talos-systems/talos/internal/pkg/containers/image"
|
|
"github.com/talos-systems/talos/internal/pkg/runtime"
|
|
"github.com/talos-systems/talos/pkg/constants"
|
|
)
|
|
|
|
// RunInstallerContainer performs an installation via the installer container.
|
|
//nolint: gocyclo
|
|
func RunInstallerContainer(r runtime.Runtime, opts ...Option) error {
|
|
options := DefaultInstallOptions()
|
|
|
|
for _, opt := range opts {
|
|
if err := opt(&options); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ctx := namespaces.WithNamespace(context.Background(), constants.SystemContainerdNamespace)
|
|
|
|
client, err := containerd.New(constants.SystemContainerdAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("pulling installer container image: %q", r.Config().Machine().Install().Image())
|
|
|
|
var img containerd.Image
|
|
|
|
if options.ImagePull {
|
|
img, err = image.Pull(ctx, r.Config().Machine().Registries(), client, r.Config().Machine().Install().Image())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
img, err = client.GetImage(ctx, r.Config().Machine().Install().Image())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mounts := []specs.Mount{
|
|
{Type: "bind", Destination: "/dev", Source: "/dev", Options: []string{"rbind", "rshared", "rw"}},
|
|
}
|
|
|
|
// TODO(andrewrynhard): To handle cases when the newer version changes the
|
|
// platform name, this should be determined in the installer container.
|
|
var config *string
|
|
if config = procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); config == nil {
|
|
return fmt.Errorf("no config option was found")
|
|
}
|
|
|
|
upgrade := "false"
|
|
if r.Sequence() == runtime.Upgrade {
|
|
upgrade = "true"
|
|
}
|
|
|
|
args := []string{
|
|
"/bin/installer",
|
|
"install",
|
|
"--disk=" + r.Config().Machine().Install().Disk(),
|
|
"--platform=" + r.Platform().Name(),
|
|
"--config=" + *config,
|
|
"--upgrade=" + upgrade,
|
|
"--force=" + strconv.FormatBool(!options.Preserve),
|
|
}
|
|
|
|
for _, arg := range r.Config().Machine().Install().ExtraKernelArgs() {
|
|
args = append(args, []string{"--extra-kernel-arg", arg}...)
|
|
}
|
|
|
|
specOpts := []oci.SpecOpts{
|
|
oci.WithImageConfig(img),
|
|
oci.WithProcessArgs(args...),
|
|
oci.WithHostNamespace(specs.NetworkNamespace),
|
|
oci.WithHostNamespace(specs.PIDNamespace),
|
|
oci.WithMounts(mounts),
|
|
oci.WithHostHostsFile,
|
|
oci.WithHostResolvconf,
|
|
oci.WithParentCgroupDevices,
|
|
oci.WithPrivileged,
|
|
}
|
|
containerOpts := []containerd.NewContainerOpts{
|
|
containerd.WithImage(img),
|
|
containerd.WithNewSnapshot("upgrade", img),
|
|
containerd.WithNewSpec(specOpts...),
|
|
}
|
|
|
|
container, err := client.NewContainer(ctx, "upgrade", containerOpts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t, err := container.NewTask(ctx, cio.LogFile("/dev/kmsg"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = t.Start(ctx); err != nil {
|
|
return fmt.Errorf("failed to start %q task: %w", "upgrade", err)
|
|
}
|
|
|
|
statusC, err := t.Wait(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed waiting for %q task: %w", "upgrade", err)
|
|
}
|
|
|
|
status := <-statusC
|
|
|
|
code := status.ExitCode()
|
|
if code != 0 {
|
|
return fmt.Errorf("task %q failed: exit code %d", "upgrade", code)
|
|
}
|
|
|
|
return nil
|
|
}
|