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:
Andrew Rynhard 2019-08-25 16:42:37 +00:00
parent 1eb02875c2
commit be8f58c15d
8 changed files with 195 additions and 102 deletions

View File

@ -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

View 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
}

View File

@ -91,6 +91,7 @@ func run() (err error) {
phase.NewPhase(
"overlay mounts",
rootfs.NewMountOverlayTask(),
rootfs.NewMountSharedTask(),
),
phase.NewPhase(
"setup /var",

View 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
}

View 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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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