mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
tests: Prepull images commonly used test images
Some tests have a short timeout for starting the pods (1 minute), but if those tests happen to be the first ones to run, and the images have to be pulled, then the test could timeout, especially with larger images. This commit will allow us to prepull commonly used E2E test images, so this issue can be avoided.
This commit is contained in:
parent
37efc5feec
commit
35e23afa50
@ -189,6 +189,7 @@ fi
|
|||||||
--master-tag="${MASTER_TAG:-}" \
|
--master-tag="${MASTER_TAG:-}" \
|
||||||
--docker-config-file="${DOCKER_CONFIG_FILE:-}" \
|
--docker-config-file="${DOCKER_CONFIG_FILE:-}" \
|
||||||
--dns-domain="${KUBE_DNS_DOMAIN:-cluster.local}" \
|
--dns-domain="${KUBE_DNS_DOMAIN:-cluster.local}" \
|
||||||
|
--prepull-images="${PREPULL_IMAGES:-false}" \
|
||||||
--ginkgo.slowSpecThreshold="${GINKGO_SLOW_SPEC_THRESHOLD:-300}" \
|
--ginkgo.slowSpecThreshold="${GINKGO_SLOW_SPEC_THRESHOLD:-300}" \
|
||||||
${CONTAINER_RUNTIME:+"--container-runtime=${CONTAINER_RUNTIME}"} \
|
${CONTAINER_RUNTIME:+"--container-runtime=${CONTAINER_RUNTIME}"} \
|
||||||
${MASTER_OS_DISTRIBUTION:+"--master-os-distro=${MASTER_OS_DISTRIBUTION}"} \
|
${MASTER_OS_DISTRIBUTION:+"--master-os-distro=${MASTER_OS_DISTRIBUTION}"} \
|
||||||
|
@ -53,7 +53,7 @@ var CurrentSuite Suite
|
|||||||
|
|
||||||
// PrePulledImages are a list of images used in e2e/common tests. These images should be prepulled
|
// PrePulledImages are a list of images used in e2e/common tests. These images should be prepulled
|
||||||
// before tests starts, so that the tests won't fail due image pulling flakes.
|
// before tests starts, so that the tests won't fail due image pulling flakes.
|
||||||
// Currently, this is only used by node e2e test.
|
// Currently, this is only used by node e2e test and E2E tests.
|
||||||
// See also updateImageAllowList() in ../../e2e_node/image_list.go
|
// See also updateImageAllowList() in ../../e2e_node/image_list.go
|
||||||
// TODO(random-liu): Change the image puller pod to use similar mechanism.
|
// TODO(random-liu): Change the image puller pod to use similar mechanism.
|
||||||
var PrePulledImages = sets.NewString(
|
var PrePulledImages = sets.NewString(
|
||||||
@ -67,6 +67,16 @@ var PrePulledImages = sets.NewString(
|
|||||||
imageutils.GetE2EImage(imageutils.NonRoot),
|
imageutils.GetE2EImage(imageutils.NonRoot),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WindowsPrePulledImages are a list of images used in e2e/common tests. These images should be prepulled
|
||||||
|
// before tests starts, so that the tests won't fail due image pulling flakes. These images also have
|
||||||
|
// Windows support. Currently, this is only used by E2E tests.
|
||||||
|
var WindowsPrePulledImages = sets.NewString(
|
||||||
|
imageutils.GetE2EImage(imageutils.Agnhost),
|
||||||
|
imageutils.GetE2EImage(imageutils.BusyBox),
|
||||||
|
imageutils.GetE2EImage(imageutils.Nginx),
|
||||||
|
imageutils.GetE2EImage(imageutils.Httpd),
|
||||||
|
)
|
||||||
|
|
||||||
type testImagesStruct struct {
|
type testImagesStruct struct {
|
||||||
AgnhostImage string
|
AgnhostImage string
|
||||||
BusyBoxImage string
|
BusyBoxImage string
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/onsi/ginkgo/reporters"
|
"github.com/onsi/ginkgo/reporters"
|
||||||
"github.com/onsi/gomega"
|
"github.com/onsi/gomega"
|
||||||
|
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtimeutils "k8s.io/apimachinery/pkg/util/runtime"
|
runtimeutils "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
@ -44,6 +45,7 @@ import (
|
|||||||
"k8s.io/component-base/version"
|
"k8s.io/component-base/version"
|
||||||
commontest "k8s.io/kubernetes/test/e2e/common"
|
commontest "k8s.io/kubernetes/test/e2e/common"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework/daemonset"
|
||||||
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
|
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
|
||||||
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
||||||
@ -257,6 +259,11 @@ func setupSuite() {
|
|||||||
framework.Logf("WARNING: Waiting for all daemonsets to be ready failed: %v", err)
|
framework.Logf("WARNING: Waiting for all daemonsets to be ready failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if framework.TestContext.PrepullImages {
|
||||||
|
framework.Logf("Pre-pulling images so that they are cached for the tests.")
|
||||||
|
prepullImages(c)
|
||||||
|
}
|
||||||
|
|
||||||
// Log the version of the server and this client.
|
// Log the version of the server and this client.
|
||||||
framework.Logf("e2e test version: %s", version.Get().GitVersion)
|
framework.Logf("e2e test version: %s", version.Get().GitVersion)
|
||||||
|
|
||||||
@ -400,3 +407,42 @@ func setupSuitePerGinkgoNode() {
|
|||||||
framework.TestContext.IPFamily = getDefaultClusterIPFamily(c)
|
framework.TestContext.IPFamily = getDefaultClusterIPFamily(c)
|
||||||
framework.Logf("Cluster IP family: %s", framework.TestContext.IPFamily)
|
framework.Logf("Cluster IP family: %s", framework.TestContext.IPFamily)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepullImages(c clientset.Interface) {
|
||||||
|
namespace, err := framework.CreateTestingNS("img-puller", c, map[string]string{
|
||||||
|
"e2e-framework": "img-puller",
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
ns := namespace.Name
|
||||||
|
defer c.CoreV1().Namespaces().Delete(context.TODO(), ns, metav1.DeleteOptions{})
|
||||||
|
|
||||||
|
images := commontest.PrePulledImages
|
||||||
|
if framework.NodeOSDistroIs("windows") {
|
||||||
|
images = commontest.WindowsPrePulledImages
|
||||||
|
}
|
||||||
|
|
||||||
|
label := map[string]string{"app": "prepull-daemonset"}
|
||||||
|
var imgPullers []*appsv1.DaemonSet
|
||||||
|
for _, img := range images.List() {
|
||||||
|
dsName := fmt.Sprintf("img-pull-%s", strings.ReplaceAll(strings.ReplaceAll(img, "/", "-"), ":", "-"))
|
||||||
|
|
||||||
|
dsSpec := daemonset.NewDaemonSet(dsName, img, label, nil, nil, nil)
|
||||||
|
ds, err := c.AppsV1().DaemonSets(ns).Create(context.TODO(), dsSpec, metav1.CreateOptions{})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
imgPullers = append(imgPullers, ds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should not be a multiple of 5, because node status updates
|
||||||
|
// every 5 seconds. See https://github.com/kubernetes/kubernetes/pull/14915.
|
||||||
|
dsRetryPeriod := 9 * time.Second
|
||||||
|
dsRetryTimeout := 5 * time.Minute
|
||||||
|
|
||||||
|
for _, imgPuller := range imgPullers {
|
||||||
|
checkDaemonset := func() (bool, error) {
|
||||||
|
return daemonset.CheckPresentOnNodes(c, imgPuller, ns, framework.TestContext.CloudConfig.NumNodes)
|
||||||
|
}
|
||||||
|
framework.Logf("Waiting for %s", imgPuller.Name)
|
||||||
|
err := wait.PollImmediate(dsRetryPeriod, dsRetryTimeout, checkDaemonset)
|
||||||
|
framework.ExpectNoError(err, "error waiting for image to be pulled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -67,6 +67,18 @@ func CheckRunningOnAllNodes(f *framework.Framework, ds *appsv1.DaemonSet) (bool,
|
|||||||
return CheckDaemonPodOnNodes(f, ds, nodeNames)()
|
return CheckDaemonPodOnNodes(f, ds, nodeNames)()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckPresentOnNodes will check that the daemonset will be present on at least the given number of
|
||||||
|
// schedulable nodes.
|
||||||
|
func CheckPresentOnNodes(c clientset.Interface, ds *appsv1.DaemonSet, ns string, numNodes int) (bool, error) {
|
||||||
|
nodeNames := SchedulableNodes(c, ds)
|
||||||
|
if len(nodeNames) < numNodes {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return checkDaemonPodStateOnNodes(c, ds, ns, nodeNames, func(pod *v1.Pod) bool {
|
||||||
|
return pod.Status.Phase != v1.PodPending
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func SchedulableNodes(c clientset.Interface, ds *appsv1.DaemonSet) []string {
|
func SchedulableNodes(c clientset.Interface, ds *appsv1.DaemonSet) []string {
|
||||||
nodeList, err := c.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
|
nodeList, err := c.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
@ -90,7 +102,14 @@ func canScheduleOnNode(node v1.Node, ds *appsv1.DaemonSet) bool {
|
|||||||
|
|
||||||
func CheckDaemonPodOnNodes(f *framework.Framework, ds *appsv1.DaemonSet, nodeNames []string) func() (bool, error) {
|
func CheckDaemonPodOnNodes(f *framework.Framework, ds *appsv1.DaemonSet, nodeNames []string) func() (bool, error) {
|
||||||
return func() (bool, error) {
|
return func() (bool, error) {
|
||||||
podList, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{})
|
return checkDaemonPodStateOnNodes(f.ClientSet, ds, f.Namespace.Name, nodeNames, func(pod *v1.Pod) bool {
|
||||||
|
return podutil.IsPodAvailable(pod, ds.Spec.MinReadySeconds, metav1.Now())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDaemonPodStateOnNodes(c clientset.Interface, ds *appsv1.DaemonSet, ns string, nodeNames []string, stateChecker func(*v1.Pod) bool) (bool, error) {
|
||||||
|
podList, err := c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
framework.Logf("could not get the pod list: %v", err)
|
framework.Logf("could not get the pod list: %v", err)
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -105,11 +124,11 @@ func CheckDaemonPodOnNodes(f *framework.Framework, ds *appsv1.DaemonSet, nodeNam
|
|||||||
if pod.DeletionTimestamp != nil {
|
if pod.DeletionTimestamp != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if podutil.IsPodAvailable(&pod, ds.Spec.MinReadySeconds, metav1.Now()) {
|
if stateChecker(&pod) {
|
||||||
nodesToPodCount[pod.Spec.NodeName]++
|
nodesToPodCount[pod.Spec.NodeName]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
framework.Logf("Number of nodes with available pods: %d", len(nodesToPodCount))
|
framework.Logf("Number of nodes with available pods controlled by daemonset %s: %d", ds.Name, len(nodesToPodCount))
|
||||||
|
|
||||||
// Ensure that exactly 1 pod is running on all nodes in nodeNames.
|
// Ensure that exactly 1 pod is running on all nodes in nodeNames.
|
||||||
for _, nodeName := range nodeNames {
|
for _, nodeName := range nodeNames {
|
||||||
@ -119,12 +138,11 @@ func CheckDaemonPodOnNodes(f *framework.Framework, ds *appsv1.DaemonSet, nodeNam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
framework.Logf("Number of running nodes: %d, number of available pods: %d", len(nodeNames), len(nodesToPodCount))
|
framework.Logf("Number of running nodes: %d, number of available pods: %d in daemonset %s", len(nodeNames), len(nodesToPodCount), ds.Name)
|
||||||
// Ensure that sizes of the lists are the same. We've verified that every element of nodeNames is in
|
// Ensure that sizes of the lists are the same. We've verified that every element of nodeNames is in
|
||||||
// nodesToPodCount, so verifying the lengths are equal ensures that there aren't pods running on any
|
// nodesToPodCount, so verifying the lengths are equal ensures that there aren't pods running on any
|
||||||
// other nodes.
|
// other nodes.
|
||||||
return len(nodesToPodCount) == len(nodeNames), nil
|
return len(nodesToPodCount) == len(nodeNames), nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckDaemonStatus(f *framework.Framework, dsName string) error {
|
func CheckDaemonStatus(f *framework.Framework, dsName string) error {
|
||||||
|
@ -340,6 +340,9 @@ func RegisterClusterFlags(flags *flag.FlagSet) {
|
|||||||
flags.StringVar(&TestContext.KubeVolumeDir, "volume-dir", "/var/lib/kubelet", "Path to the directory containing the kubelet volumes.")
|
flags.StringVar(&TestContext.KubeVolumeDir, "volume-dir", "/var/lib/kubelet", "Path to the directory containing the kubelet volumes.")
|
||||||
flags.StringVar(&TestContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.")
|
flags.StringVar(&TestContext.CertDir, "cert-dir", "", "Path to the directory containing the certs. Default is empty, which doesn't use certs.")
|
||||||
flags.StringVar(&TestContext.RepoRoot, "repo-root", "../../", "Root directory of kubernetes repository, for finding test files.")
|
flags.StringVar(&TestContext.RepoRoot, "repo-root", "../../", "Root directory of kubernetes repository, for finding test files.")
|
||||||
|
// NOTE: Node E2E tests have this flag defined as well, but true by default.
|
||||||
|
// If this becomes true as well, they should be refactored into RegisterCommonFlags.
|
||||||
|
flags.BoolVar(&TestContext.PrepullImages, "prepull-images", false, "If true, prepull images so image pull failures do not cause test failures.")
|
||||||
flags.StringVar(&TestContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, skeleton (the fallback if not set), etc.)")
|
flags.StringVar(&TestContext.Provider, "provider", "", "The name of the Kubernetes provider (gce, gke, local, skeleton (the fallback if not set), etc.)")
|
||||||
flags.StringVar(&TestContext.Tooling, "tooling", "", "The tooling in use (kops, gke, etc.)")
|
flags.StringVar(&TestContext.Tooling, "tooling", "", "The tooling in use (kops, gke, etc.)")
|
||||||
flags.StringVar(&TestContext.OutputDir, "e2e-output-dir", "/tmp", "Output directory for interesting/useful test data, like performance data, benchmarks, and other metrics.")
|
flags.StringVar(&TestContext.OutputDir, "e2e-output-dir", "/tmp", "Output directory for interesting/useful test data, like performance data, benchmarks, and other metrics.")
|
||||||
|
Loading…
Reference in New Issue
Block a user