feat: add overlay task
This adds a well defined task for handling all overlay mount points that are required by the system. Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
parent
1eb02875c2
commit
be8f58c15d
@ -5,24 +5,13 @@
|
||||
package rootfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager"
|
||||
"github.com/talos-systems/talos/internal/pkg/mount/manager/overlay"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// SystemVarPath is the path to write runtime system related files and
|
||||
// directories.
|
||||
SystemVarPath = "/var/system"
|
||||
)
|
||||
|
||||
// MountOverlay represents the MountOverlay task.
|
||||
@ -37,56 +26,22 @@ func NewMountOverlayTask() phase.Task {
|
||||
func (task *MountOverlay) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
switch mode {
|
||||
case runtime.Container:
|
||||
return task.container
|
||||
return nil
|
||||
default:
|
||||
return task.standard
|
||||
}
|
||||
}
|
||||
|
||||
func (task *MountOverlay) standard(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
overlays := []string{
|
||||
"/etc/kubernetes",
|
||||
"/etc/cni",
|
||||
"/usr/libexec/kubernetes",
|
||||
"/usr/etc/udev",
|
||||
"/opt",
|
||||
var mountpoints *mount.Points
|
||||
mountpoints, err = overlay.MountPoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Create all overlay mounts.
|
||||
for _, o := range overlays {
|
||||
if err = overlay(o); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (task *MountOverlay) container(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
targets := []string{"/", "/var/lib/kubelet", "/etc/cni"}
|
||||
for _, t := range targets {
|
||||
if err = unix.Mount("", t, "", unix.MS_SHARED, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func overlay(path string) error {
|
||||
parts := strings.Split(path, "/")
|
||||
prefix := strings.Join(parts[1:], "-")
|
||||
diff := fmt.Sprintf(filepath.Join(SystemVarPath, "%s-diff"), prefix)
|
||||
workdir := fmt.Sprintf(filepath.Join(SystemVarPath, "%s-workdir"), prefix)
|
||||
|
||||
for _, s := range []string{diff, workdir} {
|
||||
if err := os.MkdirAll(s, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", path, diff, workdir)
|
||||
if err := unix.Mount("overlay", path, "overlay", 0, opts); err != nil {
|
||||
return errors.Errorf("error creating overlay mount to %s: %v", path, err)
|
||||
|
||||
m := manager.NewManager(mountpoints)
|
||||
if err = m.MountAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
42
internal/app/machined/internal/phase/rootfs/mount_shared.go
Normal file
42
internal/app/machined/internal/phase/rootfs/mount_shared.go
Normal file
@ -0,0 +1,42 @@
|
||||
/* 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 rootfs
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// MountShared represents the MountShared task.
|
||||
type MountShared struct{}
|
||||
|
||||
// NewMountSharedTask initializes and returns an MountShared task.
|
||||
func NewMountSharedTask() phase.Task {
|
||||
return &MountShared{}
|
||||
}
|
||||
|
||||
// RuntimeFunc returns the runtime function.
|
||||
func (task *MountShared) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||
switch mode {
|
||||
case runtime.Container:
|
||||
return task.container
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (task *MountShared) container(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||
targets := []string{"/", "/var/lib/kubelet", "/etc/cni"}
|
||||
for _, t := range targets {
|
||||
if err = unix.Mount("", t, "", unix.MS_SHARED, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -91,6 +91,7 @@ func run() (err error) {
|
||||
phase.NewPhase(
|
||||
"overlay mounts",
|
||||
rootfs.NewMountOverlayTask(),
|
||||
rootfs.NewMountSharedTask(),
|
||||
),
|
||||
phase.NewPhase(
|
||||
"setup /var",
|
||||
|
30
internal/pkg/mount/manager/overlay/overlay.go
Normal file
30
internal/pkg/mount/manager/overlay/overlay.go
Normal file
@ -0,0 +1,30 @@
|
||||
/* 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 overlay
|
||||
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
||||
)
|
||||
|
||||
// MountPoints returns the mountpoints required to boot the system.
|
||||
// These moiuntpoints are used as overlays on top of the read only rootfs.
|
||||
func MountPoints() (mountpoints *mount.Points, err error) {
|
||||
mountpoints = mount.NewMountPoints()
|
||||
|
||||
overlays := []string{
|
||||
"/etc/kubernetes",
|
||||
"/etc/cni",
|
||||
"/usr/libexec/kubernetes",
|
||||
"/usr/etc/udev",
|
||||
"/opt",
|
||||
}
|
||||
|
||||
for _, target := range overlays {
|
||||
mountpoint := mount.NewMountPoint("", target, "", 0, "", mount.WithOverlay(true))
|
||||
mountpoints.Set(target, mountpoint)
|
||||
}
|
||||
|
||||
return mountpoints, nil
|
||||
}
|
14
internal/pkg/mount/manager/overlay/overlay_test.go
Normal file
14
internal/pkg/mount/manager/overlay/overlay_test.go
Normal file
@ -0,0 +1,14 @@
|
||||
/* 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 overlay_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
//
|
||||
// please remove it once any unit-test is added
|
||||
// for this package
|
||||
}
|
@ -5,8 +5,11 @@
|
||||
package mount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -18,7 +21,27 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Point represents a linux mount point.
|
||||
// RetryFunc defines the requirements for retrying a mount point operation.
|
||||
type RetryFunc func(*Point) error
|
||||
|
||||
func retry(f RetryFunc, p *Point) (err error) {
|
||||
for i := 0; i < 50; i++ {
|
||||
if err = f(p); err != nil {
|
||||
switch err {
|
||||
case unix.EBUSY:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Errorf("timeout: %+v", err)
|
||||
}
|
||||
|
||||
// Point represents a Linux mount point.
|
||||
type Point struct {
|
||||
source string
|
||||
target string
|
||||
@ -85,41 +108,29 @@ func (p *Point) Data() string {
|
||||
// Mount attempts to retry a mount on EBUSY. It will attempt a retry
|
||||
// every 100 milliseconds over the course of 5 seconds.
|
||||
func (p *Point) Mount() (err error) {
|
||||
p.target = path.Join(p.Prefix, p.target)
|
||||
|
||||
if err = ensureDirectory(p.target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.ReadOnly {
|
||||
p.flags |= unix.MS_RDONLY
|
||||
}
|
||||
|
||||
target := path.Join(p.Prefix, p.target)
|
||||
|
||||
if err = os.MkdirAll(target, os.ModeDir); err != nil {
|
||||
return errors.Errorf("error creating mount point directory %s: %v", target, err)
|
||||
switch {
|
||||
case p.Overlay:
|
||||
err = retry(overlay, p)
|
||||
default:
|
||||
err = retry(mount, p)
|
||||
}
|
||||
|
||||
retry := func(source string, target string, fstype string, flags uintptr, data string) (err error) {
|
||||
for i := 0; i < 50; i++ {
|
||||
if err = unix.Mount(source, target, fstype, flags, data); err != nil {
|
||||
switch err {
|
||||
case unix.EBUSY:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Errorf("mount timeout: %v", err)
|
||||
}
|
||||
|
||||
if err = retry(p.source, target, p.fstype, p.flags, p.data); err != nil {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Shared {
|
||||
if err = retry("", target, "", unix.MS_SHARED, ""); err != nil {
|
||||
return errors.Errorf("error mounting shared mount point %s: %v", target, err)
|
||||
if err = retry(share, p); err != nil {
|
||||
return errors.Errorf("error sharing mount point %s: %+v", p.target, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,24 +141,8 @@ func (p *Point) Mount() (err error) {
|
||||
// Unmount attempts to retry an unmount on EBUSY. It will attempt a
|
||||
// retry every 100 milliseconds over the course of 5 seconds.
|
||||
func (p *Point) Unmount() (err error) {
|
||||
retry := func(target string, flags int) error {
|
||||
for i := 0; i < 50; i++ {
|
||||
if err = unix.Unmount(target, flags); err != nil {
|
||||
switch err {
|
||||
case unix.EBUSY:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.Errorf("mount timeout: %v", err)
|
||||
}
|
||||
|
||||
target := path.Join(p.Prefix, p.target)
|
||||
if err := retry(target, 0); err != nil {
|
||||
p.target = path.Join(p.Prefix, p.target)
|
||||
if err := retry(unmount, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -211,3 +206,45 @@ func (p *Point) GrowFilesystem() (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mount(p *Point) (err error) {
|
||||
return unix.Mount(p.source, p.target, p.fstype, p.flags, p.data)
|
||||
}
|
||||
|
||||
func unmount(p *Point) error {
|
||||
return unix.Unmount(p.target, 0)
|
||||
}
|
||||
|
||||
func share(p *Point) error {
|
||||
return unix.Mount("", p.target, "", unix.MS_SHARED, "")
|
||||
}
|
||||
|
||||
func overlay(p *Point) error {
|
||||
parts := strings.Split(p.target, "/")
|
||||
prefix := strings.Join(parts[1:], "-")
|
||||
diff := fmt.Sprintf(filepath.Join(constants.SystemVarPath, "%s-diff"), prefix)
|
||||
workdir := fmt.Sprintf(filepath.Join(constants.SystemVarPath, "%s-workdir"), prefix)
|
||||
|
||||
for _, target := range []string{diff, workdir} {
|
||||
if err := ensureDirectory(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", p.target, diff, workdir)
|
||||
if err := unix.Mount("overlay", p.target, "overlay", 0, opts); err != nil {
|
||||
return errors.Errorf("error creating overlay mount to %s: %v", p.target, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureDirectory(target string) (err error) {
|
||||
if _, err := os.Stat(target); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(target, os.ModeDir); err != nil {
|
||||
return errors.Errorf("error creating mount point directory %s: %v", target, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ type Options struct {
|
||||
ReadOnly bool
|
||||
Shared bool
|
||||
Resize bool
|
||||
Overlay bool
|
||||
}
|
||||
|
||||
// Option is the functional option func.
|
||||
@ -45,6 +46,14 @@ func WithResize(o bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithOverlay indicates that a the partition for a given mount point should be
|
||||
// mounted using overlayfs.
|
||||
func WithOverlay(o bool) Option {
|
||||
return func(args *Options) {
|
||||
args.Overlay = o
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultOptions initializes a Options struct with default values.
|
||||
func NewDefaultOptions(setters ...Option) *Options {
|
||||
opts := &Options{
|
||||
@ -53,6 +62,7 @@ func NewDefaultOptions(setters ...Option) *Options {
|
||||
ReadOnly: false,
|
||||
Shared: false,
|
||||
Resize: false,
|
||||
Overlay: false,
|
||||
}
|
||||
|
||||
for _, setter := range setters {
|
||||
|
@ -168,6 +168,10 @@ const (
|
||||
|
||||
// NodeCertRenewalInterval is the default interval at which Talos Node Certifications should be renewed
|
||||
NodeCertRenewalInterval = 24 * time.Hour
|
||||
|
||||
// SystemVarPath is the path to write runtime system related files and
|
||||
// directories.
|
||||
SystemVarPath = "/var/system"
|
||||
)
|
||||
|
||||
// See https://linux.die.net/man/3/klogctl
|
||||
|
Loading…
Reference in New Issue
Block a user