mirror of
https://github.com/siderolabs/talos.git
synced 2025-11-29 06:31:14 +01:00
feat: reboot via kexec
This should save a lot of time on BIOS/POST time with bare metal hardware. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
parent
3de505c894
commit
d0585fb6b3
@ -392,6 +392,15 @@ func create(ctx context.Context) (err error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !bootloaderEnabled {
|
||||||
|
// disable kexec, as this would effectively use the bootloader
|
||||||
|
genOptions = append(genOptions,
|
||||||
|
generate.WithSysctls(map[string]string{
|
||||||
|
"kernel.kexec_load_disabled": "1",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
defaultInternalLB, defaultEndpoint := provisioner.GetLoadBalancers(request.Network)
|
defaultInternalLB, defaultEndpoint := provisioner.GetLoadBalancers(request.Network)
|
||||||
|
|
||||||
if defaultInternalLB == "" {
|
if defaultInternalLB == "" {
|
||||||
|
|||||||
@ -16,13 +16,29 @@ preface = """\
|
|||||||
[notes]
|
[notes]
|
||||||
|
|
||||||
[notes.clouds]
|
[notes.clouds]
|
||||||
title = "Hetzner, Scaleway and Upcloud"
|
title = "Hetzner, Scaleway, Upcloud and Vultr"
|
||||||
description = """\
|
description = """\
|
||||||
Talos now natively supports three new cloud platforms:
|
Talos now natively supports three new cloud platforms:
|
||||||
|
|
||||||
* [Hetzner](https://www.hetzner.com/)
|
* [Hetzner](https://www.hetzner.com/)
|
||||||
* [Scaleway](https://www.scaleway.com/en/)
|
* [Scaleway](https://www.scaleway.com/en/)
|
||||||
* [Upcloud](https://upcloud.com/)
|
* [Upcloud](https://upcloud.com/)
|
||||||
|
* [Vultr](https://www.vultr.com/)
|
||||||
|
"""
|
||||||
|
|
||||||
|
[notes.kexec]
|
||||||
|
title = "Reboots via kexec"
|
||||||
|
description = """\
|
||||||
|
Talos now reboots by default via kexec syscall which means BIOS POST process is skipped.
|
||||||
|
On bare-metal hardware BIOS POST process might take 10-15 minutes, so Talos reboots 10-15 minutes faster on bare-metal.
|
||||||
|
|
||||||
|
Kexec support can be disabled with the following change to the machine configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
machine:
|
||||||
|
sysctls:
|
||||||
|
kernel.kexec_load_disabled: "1"
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[notes.kubespan]
|
[notes.kubespan]
|
||||||
|
|||||||
@ -35,6 +35,8 @@ type MachineState interface {
|
|||||||
IsInstallStaged() bool
|
IsInstallStaged() bool
|
||||||
StagedInstallImageRef() string
|
StagedInstallImageRef() string
|
||||||
StagedInstallOptions() []byte
|
StagedInstallOptions() []byte
|
||||||
|
KexecPrepared(bool)
|
||||||
|
IsKexecPrepared() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClusterState defines the cluster state.
|
// ClusterState defines the cluster state.
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
package grub
|
package grub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -109,6 +110,83 @@ func (g *Grub) Labels() (current, next string, err error) {
|
|||||||
return current, next, err
|
return current, next, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BootEntry describes GRUB boot entry.
|
||||||
|
type BootEntry struct {
|
||||||
|
// Paths to kernel and initramfs image.
|
||||||
|
Linux, Initrd string
|
||||||
|
// Cmdline for the kernel.
|
||||||
|
Cmdline string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentEntry fetches current boot entry, vmlinuz/initrd path, boot args.
|
||||||
|
//
|
||||||
|
//nolint:gocyclo
|
||||||
|
func (g *Grub) GetCurrentEntry() (*BootEntry, error) {
|
||||||
|
f, err := os.Open(GrubConfig)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
|
||||||
|
entry := &BootEntry{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultEntry string
|
||||||
|
currentEntry string
|
||||||
|
)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "set default"):
|
||||||
|
matches := regexp.MustCompile(`set default="(.*)"`).FindStringSubmatch(line)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
return nil, fmt.Errorf("malformed default entry: %q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultEntry = matches[1]
|
||||||
|
case strings.HasPrefix(line, "menuentry"):
|
||||||
|
matches := regexp.MustCompile(`menuentry "(.*)"`).FindStringSubmatch(line)
|
||||||
|
if len(matches) != 2 {
|
||||||
|
return nil, fmt.Errorf("malformed menuentry: %q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEntry = matches[1]
|
||||||
|
case strings.HasPrefix(line, " linux "):
|
||||||
|
if currentEntry != defaultEntry {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line[8:], " ", 2)
|
||||||
|
|
||||||
|
entry.Linux = parts[0]
|
||||||
|
if len(parts) == 2 {
|
||||||
|
entry.Cmdline = parts[1]
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(line, " initrd "):
|
||||||
|
if currentEntry != defaultEntry {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Initrd = line[9:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Linux == "" || entry.Initrd == "" {
|
||||||
|
return nil, scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
// Install implements the Bootloader interface. It sets up grub with the
|
// Install implements the Bootloader interface. It sets up grub with the
|
||||||
// specified kernel parameters.
|
// specified kernel parameters.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -168,6 +168,15 @@ func (*Sequencer) Install(r runtime.Runtime) []runtime.Phase {
|
|||||||
).Append(
|
).Append(
|
||||||
"stopEverything",
|
"stopEverything",
|
||||||
StopAllServices,
|
StopAllServices,
|
||||||
|
).Append(
|
||||||
|
"mountBoot",
|
||||||
|
MountBootPartition,
|
||||||
|
).Append(
|
||||||
|
"kexec",
|
||||||
|
KexecPrepare,
|
||||||
|
).Append(
|
||||||
|
"unmountBoot",
|
||||||
|
UnmountBootPartition,
|
||||||
).Append(
|
).Append(
|
||||||
"reboot",
|
"reboot",
|
||||||
Reboot,
|
Reboot,
|
||||||
@ -423,6 +432,15 @@ func (*Sequencer) Upgrade(r runtime.Runtime, in *machineapi.UpgradeRequest) []ru
|
|||||||
).Append(
|
).Append(
|
||||||
"stopEverything",
|
"stopEverything",
|
||||||
StopAllServices,
|
StopAllServices,
|
||||||
|
).Append(
|
||||||
|
"mountBoot",
|
||||||
|
MountBootPartition,
|
||||||
|
).Append(
|
||||||
|
"kexec",
|
||||||
|
KexecPrepare,
|
||||||
|
).Append(
|
||||||
|
"unmountBoot",
|
||||||
|
UnmountBootPartition,
|
||||||
).Append(
|
).Append(
|
||||||
"reboot",
|
"reboot",
|
||||||
Reboot,
|
Reboot,
|
||||||
@ -459,6 +477,15 @@ func stopAllPhaselist(r runtime.Runtime) PhaseList {
|
|||||||
"unmountSystem",
|
"unmountSystem",
|
||||||
UnmountEphemeralPartition,
|
UnmountEphemeralPartition,
|
||||||
UnmountStatePartition,
|
UnmountStatePartition,
|
||||||
|
).Append(
|
||||||
|
"mountBoot",
|
||||||
|
MountBootPartition,
|
||||||
|
).Append(
|
||||||
|
"kexec",
|
||||||
|
KexecPrepare,
|
||||||
|
).Append(
|
||||||
|
"unmountBoot",
|
||||||
|
UnmountBootPartition,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1424,11 +1424,17 @@ func UpdateBootloader(seq runtime.Sequence, data interface{}) (runtime.TaskExecu
|
|||||||
// Reboot represents the Reboot task.
|
// Reboot represents the Reboot task.
|
||||||
func Reboot(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
func Reboot(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||||
|
rebootCmd := unix.LINUX_REBOOT_CMD_RESTART
|
||||||
|
|
||||||
|
if r.State().Machine().IsKexecPrepared() {
|
||||||
|
rebootCmd = unix.LINUX_REBOOT_CMD_KEXEC
|
||||||
|
}
|
||||||
|
|
||||||
r.Events().Publish(&machineapi.RestartEvent{
|
r.Events().Publish(&machineapi.RestartEvent{
|
||||||
Cmd: unix.LINUX_REBOOT_CMD_RESTART,
|
Cmd: int64(rebootCmd),
|
||||||
})
|
})
|
||||||
|
|
||||||
return runtime.RebootError{Cmd: unix.LINUX_REBOOT_CMD_RESTART}
|
return runtime.RebootError{Cmd: rebootCmd}
|
||||||
}, "reboot"
|
}, "reboot"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1710,3 +1716,70 @@ func ActivateLogicalVolumes(seq runtime.Sequence, data interface{}) (runtime.Tas
|
|||||||
return nil
|
return nil
|
||||||
}, "activateLogicalVolumes"
|
}, "activateLogicalVolumes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KexecPrepare loads next boot kernel via kexec_file_load.
|
||||||
|
func KexecPrepare(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||||
|
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error {
|
||||||
|
if r.Config() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
disk, err := r.Config().Machine().Install().Disk()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
grub := &grub.Grub{
|
||||||
|
BootDisk: disk,
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := grub.GetCurrentEntry()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kernelPath := filepath.Join(constants.BootMountPoint, entry.Linux)
|
||||||
|
initrdPath := filepath.Join(constants.BootMountPoint, entry.Initrd)
|
||||||
|
|
||||||
|
kernel, err := os.Open(kernelPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer kernel.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
initrd, err := os.Open(initrdPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer initrd.Close() //nolint:errcheck
|
||||||
|
|
||||||
|
cmdline := strings.TrimSpace(entry.Cmdline)
|
||||||
|
|
||||||
|
if err = unix.KexecFileLoad(int(kernel.Fd()), int(initrd.Fd()), cmdline, 0); err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, unix.ENOSYS):
|
||||||
|
log.Printf("kexec support is disabled in the kernel")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case errors.Is(err, unix.EPERM):
|
||||||
|
log.Printf("kexec support is disabled via sysctl")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("error loading kernel for kexec: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("prepared kexec environment kernel=%q initrd=%q cmdline=%q", kernelPath, initrdPath, cmdline)
|
||||||
|
|
||||||
|
r.State().Machine().KexecPrepared(true)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, "kexecPrepare"
|
||||||
|
}
|
||||||
|
|||||||
@ -37,6 +37,8 @@ type MachineState struct {
|
|||||||
stagedInstall bool
|
stagedInstall bool
|
||||||
stagedInstallImageRef string
|
stagedInstallImageRef string
|
||||||
stagedInstallOptions []byte
|
stagedInstallOptions []byte
|
||||||
|
|
||||||
|
kexecPrepared bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClusterState represents the cluster's state.
|
// ClusterState represents the cluster's state.
|
||||||
@ -182,13 +184,15 @@ func (s *MachineState) Disk(options ...disk.Option) *probe.ProbedBlockDevice {
|
|||||||
func (s *MachineState) Close() error {
|
func (s *MachineState) Close() error {
|
||||||
var result *multierror.Error
|
var result *multierror.Error
|
||||||
|
|
||||||
for _, disk := range s.disks {
|
for label, disk := range s.disks {
|
||||||
if err := disk.Close(); err != nil {
|
if err := disk.Close(); err != nil {
|
||||||
e := multierror.Append(result, err)
|
e := multierror.Append(result, err)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(s.disks, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ErrorOrNil()
|
return result.ErrorOrNil()
|
||||||
@ -215,3 +219,13 @@ func (s *MachineState) StagedInstallImageRef() string {
|
|||||||
func (s *MachineState) StagedInstallOptions() []byte {
|
func (s *MachineState) StagedInstallOptions() []byte {
|
||||||
return s.stagedInstallOptions
|
return s.stagedInstallOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KexecPrepared implements the machine state interface.
|
||||||
|
func (s *MachineState) KexecPrepared(prepared bool) {
|
||||||
|
s.kexecPrepared = prepared
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsKexecPrepared implements the machine state interface.
|
||||||
|
func (s *MachineState) IsKexecPrepared() bool {
|
||||||
|
return s.kexecPrepared
|
||||||
|
}
|
||||||
|
|||||||
@ -81,6 +81,7 @@ type Input struct {
|
|||||||
RegistryConfig map[string]*v1alpha1.RegistryConfig
|
RegistryConfig map[string]*v1alpha1.RegistryConfig
|
||||||
MachineDisks []*v1alpha1.MachineDisk
|
MachineDisks []*v1alpha1.MachineDisk
|
||||||
SystemDiskEncryptionConfig *v1alpha1.SystemDiskEncryptionConfig
|
SystemDiskEncryptionConfig *v1alpha1.SystemDiskEncryptionConfig
|
||||||
|
Sysctls map[string]string
|
||||||
|
|
||||||
Debug bool
|
Debug bool
|
||||||
Persist bool
|
Persist bool
|
||||||
@ -488,6 +489,7 @@ func NewInput(clustername, endpoint, kubernetesVersion string, secrets *SecretsB
|
|||||||
CNIConfig: options.CNIConfig,
|
CNIConfig: options.CNIConfig,
|
||||||
RegistryMirrors: options.RegistryMirrors,
|
RegistryMirrors: options.RegistryMirrors,
|
||||||
RegistryConfig: options.RegistryConfig,
|
RegistryConfig: options.RegistryConfig,
|
||||||
|
Sysctls: options.Sysctls,
|
||||||
Debug: options.Debug,
|
Debug: options.Debug,
|
||||||
Persist: options.Persist,
|
Persist: options.Persist,
|
||||||
AllowSchedulingOnMasters: options.AllowSchedulingOnMasters,
|
AllowSchedulingOnMasters: options.AllowSchedulingOnMasters,
|
||||||
|
|||||||
@ -51,6 +51,7 @@ func initUd(in *Input) (*v1alpha1.Config, error) {
|
|||||||
},
|
},
|
||||||
MachineDisks: in.MachineDisks,
|
MachineDisks: in.MachineDisks,
|
||||||
MachineSystemDiskEncryption: in.SystemDiskEncryptionConfig,
|
MachineSystemDiskEncryption: in.SystemDiskEncryptionConfig,
|
||||||
|
MachineSysctls: in.Sysctls,
|
||||||
MachineFeatures: &v1alpha1.FeaturesConfig{},
|
MachineFeatures: &v1alpha1.FeaturesConfig{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -212,6 +212,21 @@ func WithClusterDiscovery() GenOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSysctls merges list of sysctls with new values.
|
||||||
|
func WithSysctls(params map[string]string) GenOption {
|
||||||
|
return func(o *GenOptions) error {
|
||||||
|
if o.Sysctls == nil {
|
||||||
|
o.Sysctls = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
o.Sysctls[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GenOptions describes generate parameters.
|
// GenOptions describes generate parameters.
|
||||||
type GenOptions struct {
|
type GenOptions struct {
|
||||||
EndpointList []string
|
EndpointList []string
|
||||||
@ -223,6 +238,7 @@ type GenOptions struct {
|
|||||||
CNIConfig *v1alpha1.CNIConfig
|
CNIConfig *v1alpha1.CNIConfig
|
||||||
RegistryMirrors map[string]*v1alpha1.RegistryMirrorConfig
|
RegistryMirrors map[string]*v1alpha1.RegistryMirrorConfig
|
||||||
RegistryConfig map[string]*v1alpha1.RegistryConfig
|
RegistryConfig map[string]*v1alpha1.RegistryConfig
|
||||||
|
Sysctls map[string]string
|
||||||
DNSDomain string
|
DNSDomain string
|
||||||
Debug bool
|
Debug bool
|
||||||
Persist bool
|
Persist bool
|
||||||
|
|||||||
@ -51,6 +51,7 @@ func workerUd(in *Input) (*v1alpha1.Config, error) {
|
|||||||
},
|
},
|
||||||
MachineDisks: in.MachineDisks,
|
MachineDisks: in.MachineDisks,
|
||||||
MachineSystemDiskEncryption: in.SystemDiskEncryptionConfig,
|
MachineSystemDiskEncryption: in.SystemDiskEncryptionConfig,
|
||||||
|
MachineSysctls: in.Sysctls,
|
||||||
MachineFeatures: &v1alpha1.FeaturesConfig{},
|
MachineFeatures: &v1alpha1.FeaturesConfig{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ weight: 6
|
|||||||
| Kubernetes | 1.22, 1.21, 1.20 | 1.22, 1.21, 1.20 |
|
| Kubernetes | 1.22, 1.21, 1.20 | 1.22, 1.21, 1.20 |
|
||||||
| Architecture | amd64, arm64 |
|
| Architecture | amd64, arm64 |
|
||||||
| **Platforms** | | |
|
| **Platforms** | | |
|
||||||
| - cloud | AWS, GCP, Azure, Digital Ocean, OpenStack |
|
| - cloud | AWS, GCP, Azure, Digital Ocean, Hetzner, OpenStack, Scaleway, Vultr, Upcloud | AWS, GCP, Azure, Digital Ocean, OpenStack |
|
||||||
| - bare metal | x86: BIOS, UEFI; arm64: UEFI; boot: ISO, PXE, disk image |
|
| - bare metal | x86: BIOS, UEFI; arm64: UEFI; boot: ISO, PXE, disk image |
|
||||||
| - virtualized | VMware, Hyper-V, KVM, Proxmox, Xen |
|
| - virtualized | VMware, Hyper-V, KVM, Proxmox, Xen |
|
||||||
| - SBCs | Raspberry Pi4, Banana Pi M64, Pine64, and other |
|
| - SBCs | Raspberry Pi4, Banana Pi M64, Pine64, and other |
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user