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
|
package rootfs
|
||||||
|
|
||||||
import (
|
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/phase"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
|
"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/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"
|
"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.
|
// MountOverlay represents the MountOverlay task.
|
||||||
@ -37,56 +26,22 @@ func NewMountOverlayTask() phase.Task {
|
|||||||
func (task *MountOverlay) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
func (task *MountOverlay) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
|
||||||
switch mode {
|
switch mode {
|
||||||
case runtime.Container:
|
case runtime.Container:
|
||||||
return task.container
|
return nil
|
||||||
default:
|
default:
|
||||||
return task.standard
|
return task.standard
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *MountOverlay) standard(platform platform.Platform, data *userdata.UserData) (err error) {
|
func (task *MountOverlay) standard(platform platform.Platform, data *userdata.UserData) (err error) {
|
||||||
overlays := []string{
|
var mountpoints *mount.Points
|
||||||
"/etc/kubernetes",
|
mountpoints, err = overlay.MountPoints()
|
||||||
"/etc/cni",
|
if err != nil {
|
||||||
"/usr/libexec/kubernetes",
|
|
||||||
"/usr/etc/udev",
|
|
||||||
"/opt",
|
|
||||||
}
|
|
||||||
// Create all overlay mounts.
|
|
||||||
for _, o := range overlays {
|
|
||||||
if err = overlay(o); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
m := manager.NewManager(mountpoints)
|
||||||
return nil
|
if err = m.MountAll(); err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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(
|
phase.NewPhase(
|
||||||
"overlay mounts",
|
"overlay mounts",
|
||||||
rootfs.NewMountOverlayTask(),
|
rootfs.NewMountOverlayTask(),
|
||||||
|
rootfs.NewMountSharedTask(),
|
||||||
),
|
),
|
||||||
phase.NewPhase(
|
phase.NewPhase(
|
||||||
"setup /var",
|
"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
|
package mount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -18,7 +21,27 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"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 {
|
type Point struct {
|
||||||
source string
|
source string
|
||||||
target 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
|
// Mount attempts to retry a mount on EBUSY. It will attempt a retry
|
||||||
// every 100 milliseconds over the course of 5 seconds.
|
// every 100 milliseconds over the course of 5 seconds.
|
||||||
func (p *Point) Mount() (err error) {
|
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 {
|
if p.ReadOnly {
|
||||||
p.flags |= unix.MS_RDONLY
|
p.flags |= unix.MS_RDONLY
|
||||||
}
|
}
|
||||||
|
|
||||||
target := path.Join(p.Prefix, p.target)
|
switch {
|
||||||
|
case p.Overlay:
|
||||||
if err = os.MkdirAll(target, os.ModeDir); err != nil {
|
err = retry(overlay, p)
|
||||||
return errors.Errorf("error creating mount point directory %s: %v", target, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
default:
|
||||||
return err
|
err = retry(mount, p)
|
||||||
}
|
}
|
||||||
}
|
if err != nil {
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Errorf("mount timeout: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = retry(p.source, target, p.fstype, p.flags, p.data); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Shared {
|
if p.Shared {
|
||||||
if err = retry("", target, "", unix.MS_SHARED, ""); err != nil {
|
if err = retry(share, p); err != nil {
|
||||||
return errors.Errorf("error mounting shared mount point %s: %v", target, err)
|
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
|
// Unmount attempts to retry an unmount on EBUSY. It will attempt a
|
||||||
// retry every 100 milliseconds over the course of 5 seconds.
|
// retry every 100 milliseconds over the course of 5 seconds.
|
||||||
func (p *Point) Unmount() (err error) {
|
func (p *Point) Unmount() (err error) {
|
||||||
retry := func(target string, flags int) error {
|
p.target = path.Join(p.Prefix, p.target)
|
||||||
for i := 0; i < 50; i++ {
|
if err := retry(unmount, p); err != nil {
|
||||||
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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,3 +206,45 @@ func (p *Point) GrowFilesystem() (err error) {
|
|||||||
|
|
||||||
return nil
|
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
|
ReadOnly bool
|
||||||
Shared bool
|
Shared bool
|
||||||
Resize bool
|
Resize bool
|
||||||
|
Overlay bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is the functional option func.
|
// 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.
|
// NewDefaultOptions initializes a Options struct with default values.
|
||||||
func NewDefaultOptions(setters ...Option) *Options {
|
func NewDefaultOptions(setters ...Option) *Options {
|
||||||
opts := &Options{
|
opts := &Options{
|
||||||
@ -53,6 +62,7 @@ func NewDefaultOptions(setters ...Option) *Options {
|
|||||||
ReadOnly: false,
|
ReadOnly: false,
|
||||||
Shared: false,
|
Shared: false,
|
||||||
Resize: false,
|
Resize: false,
|
||||||
|
Overlay: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, setter := range setters {
|
for _, setter := range setters {
|
||||||
|
@ -168,6 +168,10 @@ const (
|
|||||||
|
|
||||||
// NodeCertRenewalInterval is the default interval at which Talos Node Certifications should be renewed
|
// NodeCertRenewalInterval is the default interval at which Talos Node Certifications should be renewed
|
||||||
NodeCertRenewalInterval = 24 * time.Hour
|
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
|
// See https://linux.die.net/man/3/klogctl
|
||||||
|
Loading…
x
Reference in New Issue
Block a user