How to run Kubernetes (Node) e2e tests locally?
TL;DR:
-
To run Node e2e tests:
> KUBE_ROOT=$(pwd) make test-e2e-node FOCUS="just the scheduled condition set"
-
To run rest of the e2e tests:
> ./_output/bin/e2e.test \ --provider=local \ --kubeconfig=$HOME/.kube/config \ --ginkgo.focus="PodReadyToStartContainerCondition"
(logging for my future self!)
Below are a bunch of my raw running notes as I try to figure out how to run Kubernetes e2e tests locally.
I’m trying to add a new e2e test in the Kubernetes codebase (as part of the work required for the GA promotion of KEP-3085 (aka the feature-gate, PodReadyToStartContainerCondition
).
And I want to be able to run this locally.
So, starting with the following steps to set up my environment:
-
clone the kubernetes repository
> git clone git@github.com:kubernetes/kubernetes.git > cd kubernetes > git checkout -b some-new-branch-name # build all the binaries you'll need for running e2e locally > make WHAT=test/e2e/e2e.test
This will produce the
e2e.test
binary in./output/bin/e2e.test
path. -
Set up a local kubernetes cluster (I’ll use
kind
)> kind create cluster --name local-e2e-test # to make sure my kubeconfig is pointing to the intended cluster > kubectl cluster-info
-
Run the e2e test binary (the newly built
./output/bin/e2e.test
binary).The e2e framework runs Ginkgo tests.
I can target the test I want to run, by its
--ginkgo.focus
flag.Example:
> ./_output/bin/e2e.test \ --provider=local \ --kubeconfig=$HOME/.kube/config \ --ginkgo.focus="PodReadyToStartContainerCondition"
I also learnt, that there’s a
--help
flag also available, if I want to understand the full length of the capablities offered by thise2e.test
binary.I tried a few of them below.
For example, If I want to get a dump of the entire list of e2e tests from the binary:
> ./_output/bin/e2e.test --list-tests
That prints all 7k+ test names.
So, if I want to check for a specific test, I can grep for some keyword or string used in the test definition (I learnt this needs to some keyword or string from the
ginkgo.It(...)
block where the test is invoked):> ./_output/bin/e2e.test --list-tests | grep -i "some-string-in-the-test-definition"
Also, please note, if I have added a new test after above steps, then IIUC
e2e.test --list-tests
won’t show it right away.
I’ll need to rebuild the binary to make the new test visible.
You know what? I realised what I did above, doesn’t really help me to run the Node e2e tests (aka the tests defined in the path test/e2d_node/*
of the Kubernetes repository).
(And for the KEP-3085, that is exactly what I need - to be able to write and run Node e2e tests locally)
But before I move ahead - How did I realise that I need something else?
Well, because - e2e.test
binary wasn’t able to list my intended test (which is defined in the test/e2e_node/pod_conditions_test.go
path in the Kubernetes repo).
Ok, moving forward.
I figured out, there’s a different way to run these Node e2e tests locally.
I came across this handy document – Node End-To-End (e2e) tests.
This very thoroughly describes the process for running Node e2e tests locally (as well as, remotely, but that’s for some other day. I want to try the local execution first).
(Thank you, to the good folks in the SIG Node, this truly is a gem of a resource!)
Ok, let’s see if this helps me with my usecase!
There’s a bit of work needed (the pre-requisities) before I can start following the instructions in the document to actually run the tests.
-
I need to install etcd, and make sure it’s available in the path.
(I’m on an openSUSE Tumbleweed machine, so I didzypper install etcd
and that did the job. Otherwise, follow the instructions from Etcd documentation here.)> which etcd /usr/sbin/etcd > etcd --version etcd Version: 3.6.4 Git SHA: Not provided (use ./build instead of go build) Go Version: go1.24.5 Go OS/Arch: linux/amd64
-
Next, I need to install Containerd and configure cgroup driver to cgroupfs.
(once again, I didzypper install containerd
. Otherwise, follow the instructions from Containerd documentation here.)> sudo mkdir -p /etc/containerd > sudo containerd config default | sudo tee /etc/containerd/config.toml > sudo sed -i 's/SystemdCgroup = true/SystemdCgroup = false/' /etc/containerd/config.toml > sudo systemctl restart containerd > containerd --version containerd github.com/containerd/containerd v1.7.27 05044ec0a9a75232cad458027ca83437aae3f4da
-
And then finally, I need to ensure that there’s a valid CNI configuration in
/etc/cni/net.d/
.
For that I ran the following steps.> sudo mkdir -p /opt/cni/bin > sudo curl -L https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz | sudo tar -C /opt/cni/bin -xz > sudo mkdir -p /etc/cni/net.d > sudo tee /etc/cni/net.d/10-bridge.conf <<EOF { "cniVersion": "0.4.0", "name": "bridge", "type": "bridge", "bridge": "cni0", "isGateway": true, "ipMasq": true, "ipam": { "type": "host-local", "subnet": "10.22.0.0/16", "routes": [{"dst": "0.0.0.0/0"}] } } EOF
With these pre-requisties out of my way, for running the Node e2e tests (i.e., to run the ginkgo binary against the subdirectory test/e2e_node), I will use the following make
target:
make test-e2e-node
And this also has a handy HELP
option:
make test-e2e-node PRINT_HELP=y
Right now, I want to try running a single test, so, I will use the FOCUS
arugment to target it.
(I want to test this block invoked via this ginkgo.It(...)
description)
So, my make target call would look something like:
(Please note, I also had to set the KUBE_ROOT
variable to point to the correct path to my local kubernetes repo clone.)
KUBE_ROOT=$(pwd) make test-e2e-node FOCUS="just the scheduled condition set"
Wait! It ran, but not successfully. I got the following error:
<< Captured StdOut/StdErr Output
Timeline >>
I0920 14:50:41.511206 22650 server.go:105] Starting server "services" with command "/home/psaggu/work-upstream/kep-3085-add-condition-for-sandbox-creation/kubernetes/_output/local/go/bin/e2e_node.test --run-services-mode --bearer-token=Ai-zpfsimz4_NvH2 --test.timeout=0 --ginkgo.seed=1758360041 --ginkgo.timeout=23h59m59.999947072s --ginkgo.grace-period=30s --ginkgo.focus=just the scheduled condition set --ginkgo.skip=\\[Flaky\\]|\\[Slow\\]|\\[Serial\\] --ginkgo.parallel.process=1 --ginkgo.parallel.total=8 --ginkgo.parallel.host=127.0.0.1:33109 --v 4 --report-dir=/tmp/_artifacts/250920T145006 --node-name alpha --kubelet-flags=--cluster-domain=cluster.local --dns-domain=cluster.local --prepull-images=false --container-runtime-endpoint=unix:///run/containerd/containerd.sock --runtime-config= --kubelet-config-file=test/e2e_node/jenkins/default-kubelet-config.yaml"
I0920 14:50:41.511254 22650 util.go:48] Running readiness check for service "services"
I0920 14:50:41.511354 22650 server.go:133] Output file for server "services": /tmp/_artifacts/250920T145006/services.log
I0920 14:50:41.511706 22650 server.go:163] Waiting for server "services" start command to complete
W0920 14:50:43.029782 22650 util.go:106] Health check on "https://127.0.0.1:6443/healthz" failed, status=500
I0920 14:50:44.032240 22650 services.go:69] Node services started.
I0920 14:50:44.032254 22650 kubelet.go:157] Starting kubelet
I0920 14:50:44.032259 22650 kubelet.go:159] Standalone mode: false
I0920 14:50:44.033031 22650 feature_gate.go:385] feature gates: {map[]}
I0920 14:50:44.042420 22650 server.go:105] Starting server "kubelet" with command "/usr/bin/systemd-run -p Delegate=true -p StandardError=append:/tmp/_artifacts/250920T145006/kubelet.log --unit=kubelet-20250920T145044.service --slice=runtime.slice --remain-after-exit /home/psaggu/work-upstream/kep-3085-add-condition-for-sandbox-creation/kubernetes/_output/local/go/bin/kubelet --kubeconfig /home/psaggu/work-upstream/kep-3085-add-condition-for-sandbox-creation/kubernetes/_output/local/go/bin/kubeconfig --root-dir /var/lib/kubelet --v 4 --config-dir /home/psaggu/work-upstream/kep-3085-add-condition-for-sandbox-creation/kubernetes/_output/local/go/bin/kubelet.conf.d --hostname-override alpha --container-runtime-endpoint unix:///run/containerd/containerd.sock --config /home/psaggu/work-upstream/kep-3085-add-condition-for-sandbox-creation/kubernetes/_output/local/go/bin/kubelet-config --cluster-domain=cluster.local"
I0920 14:50:44.042464 22650 util.go:48] Running readiness check for service "kubelet"
I0920 14:50:44.042571 22650 server.go:133] Output file for server "kubelet": /tmp/_artifacts/250920T145006/kubelet.log
I0920 14:50:44.043021 22650 server.go:163] Waiting for server "kubelet" start command to complete
W0920 14:50:45.058069 22650 util.go:104] Health check on "http://127.0.0.1:10248/healthz" failed, error=Head "http://127.0.0.1:10248/healthz": dial tcp 127.0.0.1:10248: connect: connection refused
...
...
In [SynchronizedBeforeSuite] at: k8s.io/kubernetes/test/e2e_node/e2e_node_suite_test.go:235 @ 09/20/25 14:52:44.204
------------------------------
[SynchronizedBeforeSuite] [FAILED] [122.848 seconds]
[SynchronizedBeforeSuite]
k8s.io/kubernetes/test/e2e_node/e2e_node_suite_test.go:235
[FAILED] SynchronizedBeforeSuite failed on Ginkgo parallel process #1
The first SynchronizedBeforeSuite function running on Ginkgo parallel process
#1 failed. This suite will now abort.
In [SynchronizedBeforeSuite] at: k8s.io/kubernetes/test/e2e_node/e2e_node_suite_test.go:235 @ 09/20/25 14:52:44.211
------------------------------
Summarizing 8 Failures:
[FAIL] [SynchronizedBeforeSuite]
k8s.io/kubernetes/test/e2e_node/e2e_node_suite_test.go:235
...
...
Ran 0 of 876 Specs in 130.799 seconds
FAIL! -- A BeforeSuite node failed so all tests were skipped.
Ginkgo ran 1 suite in 2m11.106353619s
Test Suite Failed
F0920 14:52:52.136374 21331 run_local.go:101] Test failed: exit status 1
exit status 1
make: *** [Makefile:292: test-e2e-node] Error 1
I did a little bit looking around and realised that my test is not able to start kubelet
properly.
But I still couldn’t understand why exactly! Because the Kubelet binary is built as part of the test run and is available in the path - _output/local/go/bin/kubelet
.
(I didn’t have to install kubelet binary by myself, it was built as part of the above make target run along with other components like kube-apiserver, etc.)
Some more digging (well, actually reading the above error message once again, properly).
I realised, I can find out the actually issue in the kubelet logs.
Below log helped:
I0920 14:50:44.042571 22650 server.go:133] Output file for server “kubelet”: /tmp/_artifacts/250920T145006/kubelet.log
So, checking the kubelet logs and it gave me the problem right away.
> tail -f /tmp/_artifacts/250920T145006/kubelet.log
I0920 14:50:44.151457 22917 server.go:579] "Sending events to api server"
I0920 14:50:44.151587 22917 swap_util.go:119] "Swap is on" /proc/swaps contents=<
Filename Type Size Used Priority
/dev/nvme0n1p3 partition 2097472 0 -2
>
E0920 14:50:44.151610 22917 run.go:72] "command failed" err="failed to run Kubelet: running with swap on is not supported, please disable swap or set --fail-swap-on flag to false"
The swap
is ON, on my machine.
The test setup expects me to to turn it OFF atleast for the duration of the execution of the test.
sudo swapoff -a
I turned off the swap and reran the test.
> KUBE_ROOT=$(pwd) make test-e2e-node FOCUS="just the scheduled condition set"
<< Captured StdOut/StdErr Output
------------------------------
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
Ran 1 of 876 Specs in 37.704 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 875 Skipped
Ginkgo ran 1 suite in 37.997204389s
Test Suite Passed
It worked! The test ran successfully! Hurrah!
But where to check the logs now?
I can’t see any test execution logs, information like Pod status (pending -> initializing -> running, etc).
Figured out - the make test-e2e-node
run captures a bunch of artifacts from the execution of the test.
I can find them in the /tmp/_artifacts
path.
❯ ls /tmp/_artifacts/250920T153001
build-log.txt containerd-installation.log containerd.log junit_01.xml kern.log kubelet.log services.log
And in my case, for the Pod status related information, I can check those from the Kubelet logs.
❯ cat /tmp/_artifacts/250920T153001/kubelet.log
So, here we go. I have a solution for running my new node e2e tests locally.
(Off I go to implement the test! o/)