From 7dbbfe38e1ff5f655ecaf333d39710c51d631c14 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Tue, 5 Oct 2021 14:50:59 +0200 Subject: [PATCH 1/7] e2e_node: remote runner: junitFilePrefix -> junitFileName --- test/e2e_node/remote/remote.go | 5 ++--- test/e2e_node/runner/remote/run_remote.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/e2e_node/remote/remote.go b/test/e2e_node/remote/remote.go index 6a114ea28da..fd2f509eeea 100644 --- a/test/e2e_node/remote/remote.go +++ b/test/e2e_node/remote/remote.go @@ -65,8 +65,7 @@ func CreateTestArchive(suite TestSuite, systemSpecName string) (string, error) { } // RunRemote returns the command output, whether the exit was ok, and any errors -// TODO(random-liu): junitFilePrefix is not prefix actually, the file name is junit-junitFilePrefix.xml. Change the variable name. -func RunRemote(suite TestSuite, archive string, host string, cleanup bool, imageDesc, junitFilePrefix, testArgs, ginkgoArgs, systemSpecName, extraEnvs, runtimeConfig string) (string, bool, error) { +func RunRemote(suite TestSuite, archive string, host string, cleanup bool, imageDesc, junitFileName, testArgs, ginkgoArgs, systemSpecName, extraEnvs, runtimeConfig string) (string, bool, error) { // Create the temp staging directory klog.V(2).Infof("Staging test binaries on %q", host) workspace := newWorkspaceDir() @@ -111,7 +110,7 @@ func RunRemote(suite TestSuite, archive string, host string, cleanup bool, image } klog.V(2).Infof("Running test on %q", host) - output, err := suite.RunTest(host, workspace, resultDir, imageDesc, junitFilePrefix, testArgs, ginkgoArgs, systemSpecName, extraEnvs, runtimeConfig, *testTimeout) + output, err := suite.RunTest(host, workspace, resultDir, imageDesc, junitFileName, testArgs, ginkgoArgs, systemSpecName, extraEnvs, runtimeConfig, *testTimeout) aggErrs := []error{} // Do not log the output here, let the caller deal with the test output. diff --git a/test/e2e_node/runner/remote/run_remote.go b/test/e2e_node/runner/remote/run_remote.go index d2699a97832..c5ac93c651a 100644 --- a/test/e2e_node/runner/remote/run_remote.go +++ b/test/e2e_node/runner/remote/run_remote.go @@ -340,16 +340,16 @@ func main() { imageConfig := gceImages.images[shortName] fmt.Printf("Initializing e2e tests using image %s/%s/%s.\n", shortName, imageConfig.project, imageConfig.image) running++ - go func(image *internalGCEImage, junitFilePrefix string) { - results <- testImage(image, junitFilePrefix) + go func(image *internalGCEImage, junitFileName string) { + results <- testImage(image, junitFileName) }(&imageConfig, shortName) } if *hosts != "" { for _, host := range strings.Split(*hosts, ",") { fmt.Printf("Initializing e2e tests using host %s.\n", host) running++ - go func(host string, junitFilePrefix string) { - results <- testHost(host, *cleanup, "", junitFilePrefix, *ginkgoFlags) + go func(host string, junitFileName string) { + results <- testHost(host, *cleanup, "", junitFileName, *ginkgoFlags) }(host, host) } } @@ -436,7 +436,7 @@ func getImageMetadata(input string) *compute.Metadata { } // Run tests in archive against host -func testHost(host string, deleteFiles bool, imageDesc, junitFilePrefix, ginkgoFlagsStr string) *TestResult { +func testHost(host string, deleteFiles bool, imageDesc, junitFileName, ginkgoFlagsStr string) *TestResult { instance, err := computeService.Instances.Get(*project, *zone, host).Do() if err != nil { return &TestResult{ @@ -466,7 +466,7 @@ func testHost(host string, deleteFiles bool, imageDesc, junitFilePrefix, ginkgoF } } - output, exitOk, err := remote.RunRemote(suite, path, host, deleteFiles, imageDesc, junitFilePrefix, *testArgs, ginkgoFlagsStr, *systemSpecName, *extraEnvs, *runtimeConfig) + output, exitOk, err := remote.RunRemote(suite, path, host, deleteFiles, imageDesc, junitFileName, *testArgs, ginkgoFlagsStr, *systemSpecName, *extraEnvs, *runtimeConfig) return &TestResult{ output: output, err: err, @@ -526,7 +526,7 @@ func getGCEImage(imageRegex, imageFamily string, project string) (string, error) // Provision a gce instance using image and run the tests in archive against the instance. // Delete the instance afterward. -func testImage(imageConfig *internalGCEImage, junitFilePrefix string) *TestResult { +func testImage(imageConfig *internalGCEImage, junitFileName string) *TestResult { ginkgoFlagsStr := *ginkgoFlags // Check whether the test is for benchmark. if len(imageConfig.tests) > 0 { @@ -552,7 +552,7 @@ func testImage(imageConfig *internalGCEImage, junitFilePrefix string) *TestResul // If we are going to delete the instance, don't bother with cleaning up the files deleteFiles := !*deleteInstances && *cleanup - result := testHost(host, deleteFiles, imageConfig.imageDesc, junitFilePrefix, ginkgoFlagsStr) + result := testHost(host, deleteFiles, imageConfig.imageDesc, junitFileName, ginkgoFlagsStr) // This is a temporary solution to collect serial node serial log. Only port 1 contains useful information. // TODO(random-liu): Extract out and unify log collection logic with cluste e2e. serialPortOutput, err := computeService.Instances.GetSerialPortOutput(*project, *zone, host).Port(1).Do() From 26980cf701206f596b4fa18004ead814fdfb2866 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Wed, 6 Oct 2021 13:35:43 +0200 Subject: [PATCH 2/7] e2e_node: cleanup entrypoint --- test/e2e_node/e2e_node_suite_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/e2e_node/e2e_node_suite_test.go b/test/e2e_node/e2e_node_suite_test.go index 81da69d0b78..848d3af4b72 100644 --- a/test/e2e_node/e2e_node_suite_test.go +++ b/test/e2e_node/e2e_node_suite_test.go @@ -168,12 +168,13 @@ func TestE2eNode(t *testing.T) { } return } - // If run-services-mode is not specified, run test. + + // We're not running in a special mode so lets run tests. gomega.RegisterFailHandler(ginkgo.Fail) reporters := []ginkgo.Reporter{} reportDir := framework.TestContext.ReportDir if reportDir != "" { - // Create the directory if it doesn't already exists + // Create the directory if it doesn't already exist if err := os.MkdirAll(reportDir, 0755); err != nil { klog.Errorf("Failed creating report directory: %v", err) } else { @@ -294,8 +295,6 @@ func waitForNodeReady() { } // updateTestContext updates the test context with the node name. -// TODO(random-liu): Using dynamic kubelet configuration feature to -// update test context with node configuration. func updateTestContext() error { setExtraEnvs() updateImageAllowList() From f1deb0ba2e212212dc847ccf744ef1b6b0b1dd1c Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Thu, 7 Oct 2021 13:20:35 +0200 Subject: [PATCH 3/7] e2e_node: remote: add kubeletconfig to archive This commit enables the remote runner to provide a KubeletConfiguration file to the test suite when uploading it to a remote host, thet test runner will then use this configuration to run the Kubelet with the provided config. --- test/e2e_node/remote/remote.go | 43 ++++++++++++++++++++++- test/e2e_node/runner/remote/run_remote.go | 3 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/test/e2e_node/remote/remote.go b/test/e2e_node/remote/remote.go index fd2f509eeea..86bafbd14a7 100644 --- a/test/e2e_node/remote/remote.go +++ b/test/e2e_node/remote/remote.go @@ -19,6 +19,7 @@ package remote import ( "flag" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -36,8 +37,43 @@ var resultsDir = flag.String("results-dir", "/tmp/", "Directory to scp test resu const archiveName = "e2e_node_test.tar.gz" +func copyKubeletConfigIfExists(kubeletConfigFile, dstDir string) error { + srcStat, err := os.Stat(kubeletConfigFile) + if err != nil { + if os.IsNotExist(err) { + return nil + } else { + return err + } + } + + if !srcStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", kubeletConfigFile) + } + + source, err := os.Open(kubeletConfigFile) + if err != nil { + return err + } + defer source.Close() + + dst := filepath.Join(dstDir, "kubeletconfig.yaml") + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + + _, err = io.Copy(destination, source) + if err != nil { + return err + } + + return os.Chmod(dst, 0x644) +} + // CreateTestArchive creates the archive package for the node e2e test. -func CreateTestArchive(suite TestSuite, systemSpecName string) (string, error) { +func CreateTestArchive(suite TestSuite, systemSpecName, kubeletConfigFile string) (string, error) { klog.V(2).Infof("Building archive...") tardir, err := ioutil.TempDir("", "node-e2e-archive") if err != nil { @@ -45,6 +81,11 @@ func CreateTestArchive(suite TestSuite, systemSpecName string) (string, error) { } defer os.RemoveAll(tardir) + err = copyKubeletConfigIfExists(kubeletConfigFile, tardir) + if err != nil { + return "", fmt.Errorf("failed to copy kubelet config: %v", err) + } + // Call the suite function to setup the test package. err = suite.SetupTestPackage(tardir, systemSpecName) if err != nil { diff --git a/test/e2e_node/runner/remote/run_remote.go b/test/e2e_node/runner/remote/run_remote.go index c5ac93c651a..b13e0c18589 100644 --- a/test/e2e_node/runner/remote/run_remote.go +++ b/test/e2e_node/runner/remote/run_remote.go @@ -69,6 +69,7 @@ var ginkgoFlags = flag.String("ginkgo-flags", "", "Passed to ginkgo to specify a var systemSpecName = flag.String("system-spec-name", "", fmt.Sprintf("The name of the system spec used for validating the image in the node conformance test. The specs are at %s. If unspecified, the default built-in spec (system.DefaultSpec) will be used.", system.SystemSpecPath)) var extraEnvs = flag.String("extra-envs", "", "The extra environment variables needed for node e2e tests. Format: a list of key=value pairs, e.g., env1=val1,env2=val2") var runtimeConfig = flag.String("runtime-config", "", "The runtime configuration for the API server on the node e2e tests.. Format: a list of key=value pairs, e.g., env1=val1,env2=val2") +var kubeletConfigFile = flag.String("kubelet-config-file", "", "The KubeletConfiguration file that should be applied to the kubelet") // envs is the type used to collect all node envs. The key is the env name, // and the value is the env value @@ -218,7 +219,7 @@ func main() { rand.Seed(time.Now().UnixNano()) if *buildOnly { // Build the archive and exit - remote.CreateTestArchive(suite, *systemSpecName) + remote.CreateTestArchive(suite, *systemSpecName, *kubeletConfigFile) return } From 4097a3d472c0163f1dc4851e59c0da1869056eba Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Thu, 7 Oct 2021 15:38:02 +0200 Subject: [PATCH 4/7] e2e_node: allow customizing the base kubeletconfig This commit forces Kubelet Configuration files to always be generated and when possible will use the kubeletconfig file that has been provided by the test orchestrator --- hack/make-rules/test-e2e-node.sh | 5 +- test/e2e_node/remote/remote.go | 6 +- test/e2e_node/runner/remote/run_remote.go | 2 +- test/e2e_node/services/kubelet.go | 94 +++++++++++++---------- 4 files changed, 59 insertions(+), 48 deletions(-) diff --git a/hack/make-rules/test-e2e-node.sh b/hack/make-rules/test-e2e-node.sh index 24b25c35920..7ee66c14ef8 100755 --- a/hack/make-rules/test-e2e-node.sh +++ b/hack/make-rules/test-e2e-node.sh @@ -49,6 +49,7 @@ extra_envs=${EXTRA_ENVS:-} runtime_config=${RUNTIME_CONFIG:-} ssh_user=${SSH_USER:-"${USER}"} ssh_key=${SSH_KEY:-} +kubelet_config_file=${KUBELET_CONFIG_FILE:-""} # Parse the flags to pass to ginkgo ginkgoflags="" @@ -164,6 +165,8 @@ if [ "${remote}" = true ] ; then echo "Ginkgo Flags: ${ginkgoflags}" echo "Instance Metadata: ${metadata}" echo "Image Config File: ${image_config_file}" + echo "Kubelet Config File: ${kubelet_config_file}" + # Invoke the runner go run test/e2e_node/runner/remote/run_remote.go --logtostderr --vmodule=*=4 --ssh-env="gce" \ --zone="${zone}" --project="${project}" --gubernator="${gubernator}" \ @@ -174,7 +177,7 @@ if [ "${remote}" = true ] ; then --image-config-file="${image_config_file}" --system-spec-name="${system_spec_name}" \ --runtime-config="${runtime_config}" --preemptible-instances="${preemptible_instances}" \ --ssh-user="${ssh_user}" --ssh-key="${ssh_key}" --image-config-dir="${image_config_dir}" \ - --extra-envs="${extra_envs}" --test-suite="${test_suite}" \ + --extra-envs="${extra_envs}" --kubelet-config-file="${kubelet_config_file}" --test-suite="${test_suite}" \ "${timeout_arg}" \ 2>&1 | tee -i "${artifacts}/build-log.txt" exit $? diff --git a/test/e2e_node/remote/remote.go b/test/e2e_node/remote/remote.go index 86bafbd14a7..b6aa5496551 100644 --- a/test/e2e_node/remote/remote.go +++ b/test/e2e_node/remote/remote.go @@ -65,11 +65,7 @@ func copyKubeletConfigIfExists(kubeletConfigFile, dstDir string) error { defer destination.Close() _, err = io.Copy(destination, source) - if err != nil { - return err - } - - return os.Chmod(dst, 0x644) + return err } // CreateTestArchive creates the archive package for the node e2e test. diff --git a/test/e2e_node/runner/remote/run_remote.go b/test/e2e_node/runner/remote/run_remote.go index b13e0c18589..5f3285120ed 100644 --- a/test/e2e_node/runner/remote/run_remote.go +++ b/test/e2e_node/runner/remote/run_remote.go @@ -405,7 +405,7 @@ func callGubernator(gubernator bool) { } func (a *Archive) getArchive() (string, error) { - a.Do(func() { a.path, a.err = remote.CreateTestArchive(suite, *systemSpecName) }) + a.Do(func() { a.path, a.err = remote.CreateTestArchive(suite, *systemSpecName, *kubeletConfigFile) }) return a.path, a.err } diff --git a/test/e2e_node/services/kubelet.go b/test/e2e_node/services/kubelet.go index 19344dce658..34d63089520 100644 --- a/test/e2e_node/services/kubelet.go +++ b/test/e2e_node/services/kubelet.go @@ -26,7 +26,6 @@ import ( "strings" "time" - "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" cliflag "k8s.io/component-base/cli/flag" @@ -34,9 +33,12 @@ import ( kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" "k8s.io/kubernetes/cmd/kubelet/app/options" + "k8s.io/kubernetes/pkg/cluster/ports" "k8s.io/kubernetes/pkg/features" kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" + "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/configfiles" kubeletconfigcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e_node/builder" "k8s.io/kubernetes/test/e2e_node/remote" @@ -62,11 +64,9 @@ func (a *args) Set(value string) error { // kubeletArgs is the override kubelet args specified by the test runner. var kubeletArgs args -var genKubeletConfigFile bool func init() { flag.Var(&kubeletArgs, "kubelet-flags", "Kubelet flags passed to kubelet, this will override default kubelet flags in the test. Flags specified in multiple kubelet-flags will be concatenate.") - flag.BoolVar(&genKubeletConfigFile, "generate-kubelet-config-file", true, "The test runner will generate a Kubelet config file containing test defaults instead of passing default flags to the Kubelet.") } // RunKubelet starts kubelet and waits for termination signal. Once receives the @@ -94,6 +94,35 @@ const ( kubeletHealthCheckURL = "http://127.0.0.1:" + kubeletReadOnlyPort + "/healthz" ) +// TODO(endocrimes): Refactor to take a path to the kubeletconfig +func baseKubeConfiguration() (*kubeletconfig.KubeletConfiguration, error) { + cwd, _ := os.Getwd() + cfgPath, err := filepath.Abs(filepath.Join(cwd, "kubeletconfig.yaml")) + if err != nil { + return nil, err + } + + _, err = os.Stat(cfgPath) + if err != nil { + // If the kubeletconfig exists, but for some reason we can't read it, then + // return an error to avoid silently skipping it. + if !os.IsNotExist(err) { + return nil, err + } + + // If the kubeletconfig file doesn't exist, then use a default configuration + // as the base. + return options.NewKubeletConfiguration() + } + + loader, err := configfiles.NewFsLoader(&utilfs.DefaultFs{}, cfgPath) + if err != nil { + return nil, err + } + + return loader.Load() +} + // startKubelet starts the Kubelet in a separate process or returns an error // if the Kubelet fails to start. func (e *E2EServices) startKubelet() (*server, error) { @@ -127,53 +156,54 @@ func (e *E2EServices) startKubelet() (*server, error) { return nil, err } - // PLEASE NOTE: If you set new KubeletConfiguration values or stop setting values here, - // you must also update the flag names in kubeletConfigFlags! - kubeletConfigFlags := []string{} - - // set up the default kubeletconfiguration - kc, err := options.NewKubeletConfiguration() + kc, err := baseKubeConfiguration() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to load base kubelet configuration: %v", err) } + // Apply overrides to allow access to the Kubelet API from the test suite. + // These are insecure and should generally not be used outside of test infra. + + // --anonymous-auth + kc.Authentication.Anonymous.Enabled = true + // --authentication-token-webhook + kc.Authentication.Webhook.Enabled = false + // --authorization-mode + kc.Authorization.Mode = kubeletconfig.KubeletAuthorizationModeAlwaysAllow + // --read-only-port + kc.ReadOnlyPort = ports.KubeletReadOnlyPort + + // Setup general overrides for the kubelet. + // TODO(endocrimes): Move the following to a `default` configuration file + kc.CgroupRoot = "/" - kubeletConfigFlags = append(kubeletConfigFlags, "cgroup-root") kc.VolumeStatsAggPeriod = metav1.Duration{Duration: 10 * time.Second} // Aggregate volumes frequently so tests don't need to wait as long - kubeletConfigFlags = append(kubeletConfigFlags, "volume-stats-agg-period") kc.SerializeImagePulls = false - kubeletConfigFlags = append(kubeletConfigFlags, "serialize-image-pulls") kc.StaticPodPath = podPath - kubeletConfigFlags = append(kubeletConfigFlags, "pod-manifest-path") kc.FileCheckFrequency = metav1.Duration{Duration: 10 * time.Second} // Check file frequently so tests won't wait too long - kubeletConfigFlags = append(kubeletConfigFlags, "file-check-frequency") // Assign a fixed CIDR to the node because there is no node controller. // Note: this MUST be in sync with the IP in // - cluster/gce/config-test.sh and // - test/e2e_node/conformance/run_test.sh. kc.PodCIDR = "10.100.0.0/24" - kubeletConfigFlags = append(kubeletConfigFlags, "pod-cidr") kc.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 30 * time.Second} - kubeletConfigFlags = append(kubeletConfigFlags, "eviction-pressure-transition-period") kc.EvictionHard = map[string]string{ "memory.available": "250Mi", "nodefs.available": "10%", "nodefs.inodesFree": "5%", } - kubeletConfigFlags = append(kubeletConfigFlags, "eviction-hard") kc.EvictionMinimumReclaim = map[string]string{ "nodefs.available": "5%", "nodefs.inodesFree": "5%", } - kubeletConfigFlags = append(kubeletConfigFlags, "eviction-minimum-reclaim") var killCommand, restartCommand *exec.Cmd var isSystemd bool @@ -204,17 +234,14 @@ func (e *E2EServices) startKubelet() (*server, error) { restartCommand = exec.Command("systemctl", "restart", unitName) kc.KubeletCgroups = "/kubelet.slice" - kubeletConfigFlags = append(kubeletConfigFlags, "kubelet-cgroups") } else { cmdArgs = append(cmdArgs, builder.GetKubeletServerBin()) // TODO(random-liu): Get rid of this docker specific thing. cmdArgs = append(cmdArgs, "--runtime-cgroups=/docker-daemon") kc.KubeletCgroups = "/kubelet" - kubeletConfigFlags = append(kubeletConfigFlags, "kubelet-cgroups") kc.SystemCgroups = "/system" - kubeletConfigFlags = append(kubeletConfigFlags, "system-cgroups") } cmdArgs = append(cmdArgs, "--kubeconfig", kubeconfigPath, @@ -277,17 +304,11 @@ func (e *E2EServices) startKubelet() (*server, error) { cmdArgs = append(cmdArgs, "--image-service-endpoint", framework.TestContext.ImageServiceEndpoint) } - // Write config file or flags, depending on whether --generate-kubelet-config-file was provided - if genKubeletConfigFile { - if err := writeKubeletConfigFile(kc, kubeletConfigPath); err != nil { - return nil, err - } - // add the flag to load config from a file - cmdArgs = append(cmdArgs, "--config", kubeletConfigPath) - } else { - // generate command line flags from the default config, since --generate-kubelet-config-file was not provided - addKubeletConfigFlags(&cmdArgs, kc, kubeletConfigFlags) + if err := writeKubeletConfigFile(kc, kubeletConfigPath); err != nil { + return nil, err } + // add the flag to load config from a file + cmdArgs = append(cmdArgs, "--config", kubeletConfigPath) // Override the default kubelet flags. cmdArgs = append(cmdArgs, kubeletArgs...) @@ -311,15 +332,6 @@ func (e *E2EServices) startKubelet() (*server, error) { return server, server.start() } -// addKubeletConfigFlags adds the flags we care about from the provided kubelet configuration object -func addKubeletConfigFlags(cmdArgs *[]string, kc *kubeletconfig.KubeletConfiguration, flags []string) { - fs := pflag.NewFlagSet("kubelet", pflag.ExitOnError) - options.AddKubeletConfigFlags(fs, kc) - for _, name := range flags { - *cmdArgs = append(*cmdArgs, fmt.Sprintf("--%s=%s", name, fs.Lookup(name).Value.String())) - } -} - // writeKubeletConfigFile writes the kubelet config file based on the args and returns the filename func writeKubeletConfigFile(internal *kubeletconfig.KubeletConfiguration, path string) error { data, err := kubeletconfigcodec.EncodeKubeletConfig(internal, kubeletconfigv1beta1.SchemeGroupVersion) From 6e9e4360260913623a8dc7a50424cb9cf8dfd83a Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Thu, 7 Oct 2021 20:46:03 +0200 Subject: [PATCH 5/7] e2e_node: kubelet config: move to file where possible --- hack/make-rules/test-e2e-node.sh | 2 +- .../jenkins/default-kubelet-config.yaml | 27 +++++++++++++++ test/e2e_node/services/kubelet.go | 33 +------------------ 3 files changed, 29 insertions(+), 33 deletions(-) create mode 100644 test/e2e_node/jenkins/default-kubelet-config.yaml diff --git a/hack/make-rules/test-e2e-node.sh b/hack/make-rules/test-e2e-node.sh index 7ee66c14ef8..3778fef5382 100755 --- a/hack/make-rules/test-e2e-node.sh +++ b/hack/make-rules/test-e2e-node.sh @@ -49,7 +49,7 @@ extra_envs=${EXTRA_ENVS:-} runtime_config=${RUNTIME_CONFIG:-} ssh_user=${SSH_USER:-"${USER}"} ssh_key=${SSH_KEY:-} -kubelet_config_file=${KUBELET_CONFIG_FILE:-""} +kubelet_config_file=${KUBELET_CONFIG_FILE:-"test/e2e_node/jenkins/default-kubelet-config.yaml"} # Parse the flags to pass to ginkgo ginkgoflags="" diff --git a/test/e2e_node/jenkins/default-kubelet-config.yaml b/test/e2e_node/jenkins/default-kubelet-config.yaml new file mode 100644 index 00000000000..36acf31218b --- /dev/null +++ b/test/e2e_node/jenkins/default-kubelet-config.yaml @@ -0,0 +1,27 @@ +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +cgroupDriver: cgroupfs +cgroupRoot: / + +# Assign a fixed CIDR to the node because we do not run a node controller +# This MUST be in sync with IPs in: +# - cluster/gce/config-test.sh and +# - test/e2e_node/conformance/run_test.sh +podCIDR: "10.100.0.0/24" + +# Aggregate volumes frequently to reduce test wait times +volumeStatsAggPeriod: 10s +# Check files frequently to reduce test wait times +fileCheckFrequency: 10s + +evictionPressureTransitionPeriod: 30s +evictionHard: + memory.available: 250Mi + nodefs.available: 10% + nodefs.inodesFree: 5% +evictionMinimumReclaim: + nodefs.available: 5% + nodefs.inodesFree: 5% + +serializeImagePulls: false + diff --git a/test/e2e_node/services/kubelet.go b/test/e2e_node/services/kubelet.go index 34d63089520..dff1da3ce99 100644 --- a/test/e2e_node/services/kubelet.go +++ b/test/e2e_node/services/kubelet.go @@ -24,9 +24,7 @@ import ( "os/exec" "path/filepath" "strings" - "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" cliflag "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" @@ -173,38 +171,9 @@ func (e *E2EServices) startKubelet() (*server, error) { // --read-only-port kc.ReadOnlyPort = ports.KubeletReadOnlyPort - // Setup general overrides for the kubelet. - // TODO(endocrimes): Move the following to a `default` configuration file - - kc.CgroupRoot = "/" - - kc.VolumeStatsAggPeriod = metav1.Duration{Duration: 10 * time.Second} // Aggregate volumes frequently so tests don't need to wait as long - - kc.SerializeImagePulls = false - + // Static Pods are in a per-test location, so we override them for tests. kc.StaticPodPath = podPath - kc.FileCheckFrequency = metav1.Duration{Duration: 10 * time.Second} // Check file frequently so tests won't wait too long - - // Assign a fixed CIDR to the node because there is no node controller. - // Note: this MUST be in sync with the IP in - // - cluster/gce/config-test.sh and - // - test/e2e_node/conformance/run_test.sh. - kc.PodCIDR = "10.100.0.0/24" - - kc.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 30 * time.Second} - - kc.EvictionHard = map[string]string{ - "memory.available": "250Mi", - "nodefs.available": "10%", - "nodefs.inodesFree": "5%", - } - - kc.EvictionMinimumReclaim = map[string]string{ - "nodefs.available": "5%", - "nodefs.inodesFree": "5%", - } - var killCommand, restartCommand *exec.Cmd var isSystemd bool // Apply default kubelet flags. From a4cf3a90a26226bb1eed3b73965309fe872c1a80 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Fri, 8 Oct 2021 13:45:37 +0200 Subject: [PATCH 6/7] e2e_node: support passing kubelet-config-file to local runs --- hack/make-rules/test-e2e-node.sh | 1 + test/e2e_node/runner/local/run_local.go | 4 ++++ test/e2e_node/services/kubelet.go | 12 ++++++------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/hack/make-rules/test-e2e-node.sh b/hack/make-rules/test-e2e-node.sh index 3778fef5382..7d06162ef80 100755 --- a/hack/make-rules/test-e2e-node.sh +++ b/hack/make-rules/test-e2e-node.sh @@ -213,6 +213,7 @@ else --ginkgo-flags="${ginkgoflags}" --test-flags="--container-runtime=${runtime} \ --alsologtostderr --v 4 --report-dir=${artifacts} --node-name $(hostname) \ ${test_args}" --runtime-config="${runtime_config}" \ + --kubelet-config-file="${kubelet_config_file}" \ --build-dependencies=true 2>&1 | tee -i "${artifacts}/build-log.txt" exit $? fi diff --git a/test/e2e_node/runner/local/run_local.go b/test/e2e_node/runner/local/run_local.go index 66c260116da..5787fe1de88 100644 --- a/test/e2e_node/runner/local/run_local.go +++ b/test/e2e_node/runner/local/run_local.go @@ -37,6 +37,7 @@ var testFlags = flag.String("test-flags", "", "Space-separated list of arguments var systemSpecName = flag.String("system-spec-name", "", fmt.Sprintf("The name of the system spec used for validating the image in the node conformance test. The specs are at %s. If unspecified, the default built-in spec (system.DefaultSpec) will be used.", system.SystemSpecPath)) var extraEnvs = flag.String("extra-envs", "", "The extra environment variables needed for node e2e tests. Format: a list of key=value pairs, e.g., env1=val1,env2=val2") var runtimeConfig = flag.String("runtime-config", "", "The runtime configuration for the API server on the node e2e tests. Format: a list of key=value pairs, e.g., env1=val1,env2=val2") +var kubeletConfigFile = flag.String("kubelet-config-file", "", "The KubeletConfiguration file that should be applied to the kubelet") func main() { klog.InitFlags(nil) @@ -67,6 +68,9 @@ func main() { systemSpecFile := filepath.Join(rootDir, system.SystemSpecPath, *systemSpecName+".yaml") args = append(args, fmt.Sprintf("--system-spec-name=%s --system-spec-file=%s --extra-envs=%s", *systemSpecName, systemSpecFile, *extraEnvs)) } + if *kubeletConfigFile != "" { + args = append(args, fmt.Sprintf("--kubelet-config-file=\"%s\"", *kubeletConfigFile)) + } if err := runCommand(ginkgo, args...); err != nil { klog.Exitf("Test failed: %v", err) } diff --git a/test/e2e_node/services/kubelet.go b/test/e2e_node/services/kubelet.go index dff1da3ce99..6442a2ee588 100644 --- a/test/e2e_node/services/kubelet.go +++ b/test/e2e_node/services/kubelet.go @@ -62,9 +62,11 @@ func (a *args) Set(value string) error { // kubeletArgs is the override kubelet args specified by the test runner. var kubeletArgs args +var kubeletConfigFile string func init() { - flag.Var(&kubeletArgs, "kubelet-flags", "Kubelet flags passed to kubelet, this will override default kubelet flags in the test. Flags specified in multiple kubelet-flags will be concatenate.") + flag.Var(&kubeletArgs, "kubelet-flags", "Kubelet flags passed to kubelet, this will override default kubelet flags in the test. Flags specified in multiple kubelet-flags will be concatenate. Deprecated, see: --kubelet-config-file.") + flag.StringVar(&kubeletConfigFile, "kubelet-config-file", "./kubeletconfig.yaml", "The base KubeletConfiguration to use when setting up the kubelet. This configuration will then be minimially modified to support requirements from the test suite.") } // RunKubelet starts kubelet and waits for termination signal. Once receives the @@ -92,10 +94,8 @@ const ( kubeletHealthCheckURL = "http://127.0.0.1:" + kubeletReadOnlyPort + "/healthz" ) -// TODO(endocrimes): Refactor to take a path to the kubeletconfig -func baseKubeConfiguration() (*kubeletconfig.KubeletConfiguration, error) { - cwd, _ := os.Getwd() - cfgPath, err := filepath.Abs(filepath.Join(cwd, "kubeletconfig.yaml")) +func baseKubeConfiguration(cfgPath string) (*kubeletconfig.KubeletConfiguration, error) { + cfgPath, err := filepath.Abs(cfgPath) if err != nil { return nil, err } @@ -154,7 +154,7 @@ func (e *E2EServices) startKubelet() (*server, error) { return nil, err } - kc, err := baseKubeConfiguration() + kc, err := baseKubeConfiguration(kubeletConfigFile) if err != nil { return nil, fmt.Errorf("failed to load base kubelet configuration: %v", err) } From 4ae64bd799d214e1a8065727b5d7561e8bccc085 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Fri, 8 Oct 2021 14:52:34 +0200 Subject: [PATCH 7/7] e2e_node: add a default kubeletconfig fallback --- test/e2e_node/services/kubelet.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/e2e_node/services/kubelet.go b/test/e2e_node/services/kubelet.go index 6442a2ee588..76e2096bb46 100644 --- a/test/e2e_node/services/kubelet.go +++ b/test/e2e_node/services/kubelet.go @@ -24,12 +24,14 @@ import ( "os/exec" "path/filepath" "strings" + "time" utilfeature "k8s.io/apiserver/pkg/util/feature" cliflag "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/cmd/kubelet/app/options" "k8s.io/kubernetes/pkg/cluster/ports" "k8s.io/kubernetes/pkg/features" @@ -110,7 +112,34 @@ func baseKubeConfiguration(cfgPath string) (*kubeletconfig.KubeletConfiguration, // If the kubeletconfig file doesn't exist, then use a default configuration // as the base. - return options.NewKubeletConfiguration() + kc, err := options.NewKubeletConfiguration() + if err != nil { + return nil, err + } + + // The following values should match the contents of + // test/e2e_node/jenkins/default-kubelet-config.yaml. We can't use go embed + // here to fallback as default config lives in a parallel directory. + // TODO(endocrimes): Remove fallback for lack of kubelet config when all + // uses of e2e_node switch to providing one (or move to + // kubetest2 and pick up the default). + kc.CgroupRoot = "/" + kc.VolumeStatsAggPeriod = metav1.Duration{Duration: 10 * time.Second} + kc.SerializeImagePulls = false + kc.FileCheckFrequency = metav1.Duration{Duration: 10 * time.Second} + kc.PodCIDR = "10.100.0.0/24" + kc.EvictionPressureTransitionPeriod = metav1.Duration{Duration: 30 * time.Second} + kc.EvictionHard = map[string]string{ + "memory.available": "250Mi", + "nodefs.available": "10%", + "nodefs.inodesFree": "5%", + } + kc.EvictionMinimumReclaim = map[string]string{ + "nodefs.available": "5%", + "nodefs.inodesFree": "5%", + } + + return kc, nil } loader, err := configfiles.NewFsLoader(&utilfs.DefaultFs{}, cfgPath)