diff --git a/cmd/kubeadm/app/phases/kubelet/BUILD b/cmd/kubeadm/app/phases/kubelet/BUILD index fecacf2a1f9..9801028a8b7 100644 --- a/cmd/kubeadm/app/phases/kubelet/BUILD +++ b/cmd/kubeadm/app/phases/kubelet/BUILD @@ -20,12 +20,14 @@ go_library( "//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1beta1:go_default_library", "//pkg/util/version:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/kubelet/flags.go b/cmd/kubeadm/app/phases/kubelet/flags.go index 926c4b5022e..f4688554287 100644 --- a/cmd/kubeadm/app/phases/kubelet/flags.go +++ b/cmd/kubeadm/app/phases/kubelet/flags.go @@ -23,10 +23,12 @@ import ( "path/filepath" "strings" + "github.com/golang/glog" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2" "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + utilsexec "k8s.io/utils/exec" ) // WriteKubeletDynamicEnvFile writes a environment file with dynamic flags to the kubelet. @@ -49,6 +51,13 @@ func buildKubeletArgMap(nodeRegOpts *kubeadmapi.NodeRegistrationOptions, registe kubeletFlags["network-plugin"] = "cni" kubeletFlags["cni-conf-dir"] = "/etc/cni/net.d" kubeletFlags["cni-bin-dir"] = "/opt/cni/bin" + execer := utilsexec.New() + driver, err := kubeadmutil.GetCgroupDriverDocker(execer) + if err != nil { + glog.Warningf("cannot automatically assign a '--cgroup-driver' value when starting the Kubelet: %v\n", err) + } else { + kubeletFlags["cgroup-driver"] = driver + } } else { kubeletFlags["container-runtime"] = "remote" kubeletFlags["container-runtime-endpoint"] = nodeRegOpts.CRISocket @@ -65,7 +74,7 @@ func buildKubeletArgMap(nodeRegOpts *kubeadmapi.NodeRegistrationOptions, registe // TODO: Pass through --hostname-override if a custom name is used? // TODO: Check if `systemd-resolved` is running, and set `--resolv-conf` based on that - // TODO: Conditionally set `--cgroup-driver` to either `systemd` or `cgroupfs` + // TODO: Conditionally set `--cgroup-driver` to either `systemd` or `cgroupfs` for CRI other than Docker return kubeletFlags } diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index 245aac69737..9e04be1a2e6 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -10,6 +10,7 @@ go_library( name = "go_default_library", srcs = [ "arguments.go", + "cgroupdriver.go", "copy.go", "endpoint.go", "error.go", @@ -29,6 +30,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], ) @@ -36,6 +38,7 @@ go_test( name = "go_default_test", srcs = [ "arguments_test.go", + "cgroupdriver_test.go", "endpoint_test.go", "error_test.go", "marshal_test.go", diff --git a/cmd/kubeadm/app/util/cgroupdriver.go b/cmd/kubeadm/app/util/cgroupdriver.go new file mode 100644 index 00000000000..b03a299126e --- /dev/null +++ b/cmd/kubeadm/app/util/cgroupdriver.go @@ -0,0 +1,75 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "strings" + + utilsexec "k8s.io/utils/exec" +) + +// TODO: add support for detecting the cgroup driver for CRI other than +// Docker. Currently only Docker driver detection is supported: +// Discussion: +// https://github.com/kubernetes/kubeadm/issues/844 + +// GetCgroupDriverDocker runs 'docker info' to obtain the docker cgroup driver +func GetCgroupDriverDocker(execer utilsexec.Interface) (string, error) { + info, err := callDockerInfo(execer) + if err != nil { + return "", err + } + return getCgroupDriverFromDockerInfo(info) +} + +func validateCgroupDriver(driver string) error { + if driver != "cgroupfs" && driver != "systemd" { + return fmt.Errorf("unknown cgroup driver %q", driver) + } + return nil +} + +// TODO: Docker 1.13 has a new way to obatain the cgroup driver: +// docker info -f "{{.CgroupDriver}} +// If the minimum supported Docker version in K8s becomes 1.13, move to +// this syntax. +func callDockerInfo(execer utilsexec.Interface) (string, error) { + out, err := execer.Command("docker", "info").Output() + if err != nil { + return "", fmt.Errorf("cannot execute 'docker info': %v", err) + } + return string(out), nil +} + +func getCgroupDriverFromDockerInfo(info string) (string, error) { + lineSeparator := ": " + prefix := "Cgroup Driver" + for _, line := range strings.Split(info, "\n") { + if !strings.Contains(line, prefix+lineSeparator) { + continue + } + lineSplit := strings.Split(line, lineSeparator) + // At this point len(lineSplit) is always >= 2 + driver := lineSplit[1] + if err := validateCgroupDriver(driver); err != nil { + return "", err + } + return driver, nil + } + return "", fmt.Errorf("cgroup driver is not defined in 'docker info'") +} diff --git a/cmd/kubeadm/app/util/cgroupdriver_test.go b/cmd/kubeadm/app/util/cgroupdriver_test.go new file mode 100644 index 00000000000..ca1ba3a4545 --- /dev/null +++ b/cmd/kubeadm/app/util/cgroupdriver_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "testing" +) + +func TestGetCgroupDriverDocker(t *testing.T) { + testCases := []struct { + name string + info string + expectedError bool + }{ + { + name: "valid: value is 'cgroupfs'", + info: `Cgroup Driver: cgroupfs`, + expectedError: false, + }, + { + name: "valid: value is 'systemd'", + info: `Cgroup Driver: systemd`, + expectedError: false, + }, + { + name: "invalid: missing 'Cgroup Driver' key and value", + info: "", + expectedError: true, + }, + { + name: "invalid: only a 'Cgroup Driver' key is present", + info: `Cgroup Driver`, + expectedError: true, + }, + { + name: "invalid: empty 'Cgroup Driver' value", + info: `Cgroup Driver: `, + expectedError: true, + }, + { + name: "invalid: unknown 'Cgroup Driver' value", + info: `Cgroup Driver: invalid-value`, + expectedError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if _, err := getCgroupDriverFromDockerInfo(tc.info); (err != nil) != tc.expectedError { + t.Fatalf("expected error: %v, saw: %v, error: %v", tc.expectedError, (err != nil), err) + } + }) + } +}