Speed up VM create with work seeds
Beat VM create wall time without changing VM semantics. Generate a work-seed ext4 sidecar during image builds and rootfs rebuilds, then clone and resize that seed for each new VM instead of rebuilding /root from scratch. Plumb the new seed artifact through config, runtime metadata, store state, runtime-bundle defaults, doctor checks, and default-image reconciliation so older images still fall back cleanly. Add a daemon TAP pool to keep idle bridge-attached devices warm, expose stage timing in lifecycle logs, add a create/SSH benchmark script plus Make target, and teach verify.sh that tap-pool-* devices are reusable capacity rather than cleanup leaks. Validated with go test ./..., make build, ./verify.sh, and make bench-create ARGS="--runs 2".
This commit is contained in:
parent
a14a80fd6b
commit
c8d9a122f9
24 changed files with 695 additions and 44 deletions
|
|
@ -1,14 +1,22 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
minWorkSeedBytes int64 = 512 * 1024 * 1024
|
||||
workSeedSlackBytes int64 = 256 * 1024 * 1024
|
||||
workSeedRoundBytes int64 = 64 * 1024 * 1024
|
||||
)
|
||||
|
||||
func CopyFilePreferClone(sourcePath, targetPath string) error {
|
||||
source, err := os.Open(sourcePath)
|
||||
if err != nil {
|
||||
|
|
@ -48,6 +56,79 @@ func CopyFilePreferClone(sourcePath, targetPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func WorkSeedPath(rootfsPath string) string {
|
||||
rootfsPath = strings.TrimSpace(rootfsPath)
|
||||
if rootfsPath == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasSuffix(rootfsPath, ".ext4") {
|
||||
return strings.TrimSuffix(rootfsPath, ".ext4") + ".work-seed.ext4"
|
||||
}
|
||||
return rootfsPath + ".work-seed"
|
||||
}
|
||||
|
||||
func BuildWorkSeedImage(ctx context.Context, runner CommandRunner, rootfsPath, outPath string) error {
|
||||
rootMount, cleanupRoot, err := MountTempDir(ctx, runner, rootfsPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanupRoot()
|
||||
|
||||
rootHome := filepath.Join(rootMount, "root")
|
||||
sizeBytes, err := estimateWorkSeedSize(rootHome)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(outPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(outPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Truncate(outPath, sizeBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := runner.Run(ctx, "mkfs.ext4", "-F", outPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workMount, cleanupWork, err := MountTempDir(ctx, runner, outPath, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanupWork()
|
||||
|
||||
return CopyDirContents(ctx, runner, rootHome, workMount, true)
|
||||
}
|
||||
|
||||
func estimateWorkSeedSize(rootHome string) (int64, error) {
|
||||
var usedBytes int64
|
||||
err := filepath.Walk(rootHome, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Mode().IsRegular() {
|
||||
usedBytes += info.Size()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sizeBytes := usedBytes*2 + workSeedSlackBytes
|
||||
if sizeBytes < minWorkSeedBytes {
|
||||
sizeBytes = minWorkSeedBytes
|
||||
}
|
||||
if rem := sizeBytes % workSeedRoundBytes; rem != 0 {
|
||||
sizeBytes += workSeedRoundBytes - rem
|
||||
}
|
||||
return sizeBytes, nil
|
||||
}
|
||||
|
||||
func ReadNormalizedLines(path string) ([]string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue