Skip to content

Commit

Permalink
rootless: fail early if prerequiresites are not satisfied
Browse files Browse the repository at this point in the history
`kind create cluster` now prints errors before running the containers.

Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Mar 15, 2021
1 parent 5b79090 commit e21174c
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 7 deletions.
21 changes: 21 additions & 0 deletions pkg/cluster/internal/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ type ClusterOptions struct {

// Cluster creates a cluster
func Cluster(logger log.Logger, p providers.Provider, opts *ClusterOptions) error {
// validate provider first
if err := validateProvider(p); err != nil {
return err
}

// default / process options (namely config)
if err := fixupOptions(opts); err != nil {
return err
Expand Down Expand Up @@ -234,3 +239,19 @@ func fixupOptions(opts *ClusterOptions) error {

return nil
}

func validateProvider(p providers.Provider) error {
info, err := p.Info()
if err != nil {
return err
}
if info.Rootless {
if !info.Cgroup2 {
return errors.New("running kind with rootless provider requires cgroup v2, see https://kind.sigs.k8s.io/docs/user/rootless/")
}
if !info.SupportsMemoryLimit || !info.SupportsPidsLimit || !info.SupportsCPUShares {
return errors.New("running kind with rootless provider requires setting systemd property \"Delegate=yes\", see https://kind.sigs.k8s.io/docs/user/rootless/")
}
}
return nil
}
33 changes: 28 additions & 5 deletions pkg/cluster/internal/providers/docker/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func NewProvider(logger log.Logger) providers.Provider {
// see NewProvider
type provider struct {
logger log.Logger
info *providers.ProviderInfo
}

// String implements fmt.Stringer
Expand Down Expand Up @@ -286,17 +287,39 @@ func (p *provider) CollectLogs(dir string, nodes []nodes.Node) error {

// Info returns the provider info.
func (p *provider) Info() (*providers.ProviderInfo, error) {
cmd := exec.Command("docker", "info", "--format", "{{json .SecurityOptions}}")
var err error
if p.info == nil {
p.info, err = info()
}
return p.info, err
}

// dockerInfo corresponds to `docker info --format '{{json .}}'`
type dockerInfo struct {
CgroupVersion string `json:"CgroupVersion"` // e.g. "2"
MemoryLimit bool `json:"MemoryLimit"`
PidsLimit bool `json:"PidsLimit"`
CPUShares bool `json:"CPUShares"`
SecurityOptions []string `json:"SecurityOptions"`
}

func info() (*providers.ProviderInfo, error) {
cmd := exec.Command("docker", "info", "--format", "{{json .}}")
out, err := exec.Output(cmd)
if err != nil {
return nil, errors.Wrap(err, "failed to get docker info")
}
var securityOptions []string
if err := json.Unmarshal(out, &securityOptions); err != nil {
var dInfo dockerInfo
if err := json.Unmarshal(out, &dInfo); err != nil {
return nil, err
}
var info providers.ProviderInfo
for _, o := range securityOptions {
info := providers.ProviderInfo{
Cgroup2: dInfo.CgroupVersion == "2",
SupportsMemoryLimit: dInfo.MemoryLimit,
SupportsPidsLimit: dInfo.PidsLimit,
SupportsCPUShares: dInfo.CPUShares,
}
for _, o := range dInfo.SecurityOptions {
// o is like "name=seccomp,profile=default", or "name=rootless",
csvReader := csv.NewReader(strings.NewReader(o))
sliceSlice, err := csvReader.ReadAll()
Expand Down
38 changes: 37 additions & 1 deletion pkg/cluster/internal/providers/podman/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package podman
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -53,6 +54,7 @@ func NewProvider(logger log.Logger) providers.Provider {
// see NewProvider
type provider struct {
logger log.Logger
info *providers.ProviderInfo
}

// String implements fmt.Stringer
Expand Down Expand Up @@ -347,8 +349,42 @@ func (p *provider) CollectLogs(dir string, nodes []nodes.Node) error {

// Info returns the provider info.
func (p *provider) Info() (*providers.ProviderInfo, error) {
var err error
if p.info == nil {
p.info, err = info(p.logger)
}
return p.info, err
}

func info(logger log.Logger) (*providers.ProviderInfo, error) {
euid := os.Geteuid()
info := &providers.ProviderInfo{
Rootless: os.Geteuid() != 0,
Rootless: euid != 0,
}
if _, err := os.Stat("/sys/fs/cgroup/cgroup.controllers"); err == nil {
info.Cgroup2 = true
// Unlike `docker info`, `podman info` does not print available cgroup controllers.
// So we parse "cgroup.subtree_control" file by ourselves.
subtreeControl := "/sys/fs/cgroup/cgroup.subtree_control"
if info.Rootless {
// Change subtreeControl to the path of the systemd user-instance.
// Non-systemd hosts are not supported.
subtreeControl = fmt.Sprintf("/sys/fs/cgroup/user.slice/user-%d.slice/user@%d.service/cgroup.subtree_control", euid, euid)
}
if subtreeControlBytes, err := ioutil.ReadFile(subtreeControl); err != nil {
logger.Warnf("failed to read %q: %+v", subtreeControl, err)
} else {
for _, controllerName := range strings.Fields(string(subtreeControlBytes)) {
switch controllerName {
case "cpu":
info.SupportsCPUShares = true
case "memory":
info.SupportsMemoryLimit = true
case "pids":
info.SupportsPidsLimit = true
}
}
}
}
return info, nil
}
6 changes: 5 additions & 1 deletion pkg/cluster/internal/providers/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,9 @@ type Provider interface {

// ProviderInfo is the info of the provider
type ProviderInfo struct {
Rootless bool
Rootless bool
Cgroup2 bool
SupportsMemoryLimit bool
SupportsPidsLimit bool
SupportsCPUShares bool
}

0 comments on commit e21174c

Please sign in to comment.