nixos/rke2: make tests work in test driver sandbox (#395775)

This commit is contained in:
rorosen 2025-04-04 08:25:24 +02:00 committed by GitHub
parent b222bbdb7e
commit cb60a01188
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 198 additions and 167 deletions

View file

@ -6,26 +6,32 @@ import ../make-test-python.nix (
... ...
}: }:
let let
pauseImage = pkgs.dockerTools.streamLayeredImage { throwSystem = throw "RKE2: Unsupported system: ${pkgs.stdenv.hostPlatform.system}";
name = "test.local/pause"; coreImages =
{
aarch64-linux = rke2.images-core-linux-arm64-tar-zst;
x86_64-linux = rke2.images-core-linux-amd64-tar-zst;
}
.${pkgs.stdenv.hostPlatform.system} or throwSystem;
canalImages =
{
aarch64-linux = rke2.images-canal-linux-arm64-tar-zst;
x86_64-linux = rke2.images-canal-linux-amd64-tar-zst;
}
.${pkgs.stdenv.hostPlatform.system} or throwSystem;
helloImage = pkgs.dockerTools.buildImage {
name = "test.local/hello";
tag = "local"; tag = "local";
contents = pkgs.buildEnv { compressor = "zstd";
name = "rke2-pause-image-env"; copyToRoot = pkgs.buildEnv {
name = "rke2-hello-image-env";
paths = with pkgs; [ paths = with pkgs; [
tini
bashInteractive
coreutils coreutils
socat socat
]; ];
}; };
config.Entrypoint = [
"/bin/tini"
"--"
"/bin/sleep"
"inf"
];
}; };
# A daemonset that responds 'server' on port 8000 # A daemonset that responds 'hello' on port 8000
networkTestDaemonset = pkgs.writeText "test.yml" '' networkTestDaemonset = pkgs.writeText "test.yml" ''
apiVersion: apps/v1 apiVersion: apps/v1
kind: DaemonSet kind: DaemonSet
@ -44,113 +50,133 @@ import ../make-test-python.nix (
spec: spec:
containers: containers:
- name: test - name: test
image: test.local/pause:local image: test.local/hello:local
imagePullPolicy: Never imagePullPolicy: Never
resources: resources:
limits: limits:
memory: 20Mi memory: 20Mi
command: ["socat", "TCP4-LISTEN:8000,fork", "EXEC:echo server"] command: ["socat", "TCP4-LISTEN:8000,fork", "EXEC:echo hello"]
''; '';
tokenFile = pkgs.writeText "token" "p@s$w0rd"; tokenFile = pkgs.writeText "token" "p@s$w0rd";
agentTokenFile = pkgs.writeText "agent-token" "p@s$w0rd"; agentTokenFile = pkgs.writeText "agent-token" "agentP@s$w0rd";
# Let flannel use eth1 to enable inter-node communication in tests
canalConfig = pkgs.writeText "rke2-canal-config.yaml" ''
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: rke2-canal
namespace: kube-system
spec:
valuesContent: |-
flannel:
iface: "eth1"
'';
in in
{ {
name = "${rke2.name}-multi-node"; name = "${rke2.name}-multi-node";
meta.maintainers = rke2.meta.maintainers; meta.maintainers = rke2.meta.maintainers;
nodes = { nodes = {
server1 = server =
{ pkgs, ... }:
{ {
networking.firewall.enable = false; config,
networking.useDHCP = false; nodes,
networking.defaultGateway = "192.168.1.1"; pkgs,
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [ ...
}:
{ {
address = "192.168.1.1"; # Setup image archives to be imported by rke2
prefixLength = 24; systemd.tmpfiles.settings."10-rke2" = {
} "/var/lib/rancher/rke2/agent/images/rke2-images-core.tar.zst" = {
"L+".argument = "${coreImages}";
};
"/var/lib/rancher/rke2/agent/images/rke2-images-canal.tar.zst" = {
"L+".argument = "${canalImages}";
};
"/var/lib/rancher/rke2/agent/images/hello.tar.zst" = {
"L+".argument = "${helloImage}";
};
# Copy the canal config so that rke2 can write the remaining default values to it
"/var/lib/rancher/rke2/server/manifests/rke2-canal-config.yaml" = {
"C".argument = "${canalConfig}";
};
};
# Canal CNI with VXLAN
networking.firewall.allowedUDPPorts = [ 8472 ];
networking.firewall.allowedTCPPorts = [
# Kubernetes API
6443
# Canal CNI health checks
9099
# RKE2 supervisor API
9345
]; ];
virtualisation.memorySize = 1536; # RKE2 needs more resources than the default
virtualisation.diskSize = 4096; virtualisation.cores = 4;
virtualisation.memorySize = 4096;
virtualisation.diskSize = 8092;
services.rke2 = { services.rke2 = {
enable = true; enable = true;
role = "server"; role = "server";
package = rke2;
inherit tokenFile; inherit tokenFile;
inherit agentTokenFile; inherit agentTokenFile;
nodeName = "${rke2.name}-server1"; # Without nodeIP the apiserver starts with the wrong service IP family
package = rke2; nodeIP = config.networking.primaryIPAddress;
nodeIP = "192.168.1.1";
disable = [ disable = [
"rke2-coredns" "rke2-coredns"
"rke2-metrics-server" "rke2-metrics-server"
"rke2-ingress-nginx" "rke2-ingress-nginx"
]; "rke2-snapshot-controller"
extraFlags = [ "rke2-snapshot-controller-crd"
"--cluster-reset" "rke2-snapshot-validation-webhook"
]; ];
}; };
}; };
server2 = agent =
{ pkgs, ... }:
{ {
networking.firewall.enable = false; config,
networking.useDHCP = false; nodes,
networking.defaultGateway = "192.168.1.2"; pkgs,
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [ ...
}:
{ {
address = "192.168.1.2"; # Setup image archives to be imported by rke2
prefixLength = 24; systemd.tmpfiles.settings."10-rke2" = {
} "/var/lib/rancher/rke2/agent/images/rke2-images-core.linux-amd64.tar.zst" = {
]; "L+".argument = "${coreImages}";
};
virtualisation.memorySize = 1536; "/var/lib/rancher/rke2/agent/images/rke2-images-canal.linux-amd64.tar.zst" = {
virtualisation.diskSize = 4096; "L+".argument = "${canalImages}";
};
services.rke2 = { "/var/lib/rancher/rke2/agent/images/hello.tar.zst" = {
enable = true; "L+".argument = "${helloImage}";
role = "server"; };
serverAddr = "https://192.168.1.1:6443"; "/var/lib/rancher/rke2/server/manifests/rke2-canal-config.yaml" = {
inherit tokenFile; "C".argument = "${canalConfig}";
inherit agentTokenFile;
nodeName = "${rke2.name}-server2";
package = rke2;
nodeIP = "192.168.1.2";
disable = [
"rke2-coredns"
"rke2-metrics-server"
"rke2-ingress-nginx"
];
}; };
}; };
agent1 = # Canal CNI health checks
{ pkgs, ... }: networking.firewall.allowedTCPPorts = [ 9099 ];
{ # Canal CNI with VXLAN
networking.firewall.enable = false; networking.firewall.allowedUDPPorts = [ 8472 ];
networking.useDHCP = false;
networking.defaultGateway = "192.168.1.3";
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
{
address = "192.168.1.3";
prefixLength = 24;
}
];
virtualisation.memorySize = 1536; # The agent node can work with less resources
virtualisation.diskSize = 4096; virtualisation.memorySize = 2048;
virtualisation.diskSize = 8092;
services.rke2 = { services.rke2 = {
enable = true; enable = true;
role = "agent"; role = "agent";
tokenFile = agentTokenFile;
serverAddr = "https://192.168.1.2:6443";
nodeName = "${rke2.name}-agent1";
package = rke2; package = rke2;
nodeIP = "192.168.1.3"; tokenFile = agentTokenFile;
serverAddr = "https://${nodes.server.networking.primaryIPAddress}:9345";
nodeIP = config.networking.primaryIPAddress;
}; };
}; };
}; };
@ -158,53 +184,42 @@ import ../make-test-python.nix (
testScript = testScript =
let let
kubectl = "${pkgs.kubectl}/bin/kubectl --kubeconfig=/etc/rancher/rke2/rke2.yaml"; kubectl = "${pkgs.kubectl}/bin/kubectl --kubeconfig=/etc/rancher/rke2/rke2.yaml";
ctr = "${pkgs.containerd}/bin/ctr -a /run/k3s/containerd/containerd.sock";
jq = "${pkgs.jq}/bin/jq"; jq = "${pkgs.jq}/bin/jq";
ping = "${pkgs.iputils}/bin/ping";
in in
# python
'' ''
machines = [server1, server2, agent1] start_all()
for machine in machines: server.wait_for_unit("rke2-server")
machine.start() agent.wait_for_unit("rke2-agent")
machine.wait_for_unit("rke2")
# wait for the agent to show up # Wait for the agent to be ready
server1.succeed("${kubectl} get node ${rke2.name}-agent1") server.wait_until_succeeds(r"""${kubectl} wait --for='jsonpath={.status.conditions[?(@.type=="Ready")].status}=True' nodes/agent""")
for machine in machines: server.succeed("${kubectl} cluster-info")
machine.succeed("${pauseImage} | ${ctr} image import -") server.wait_until_succeeds("${kubectl} get serviceaccount default")
server1.succeed("${kubectl} cluster-info")
server1.wait_until_succeeds("${kubectl} get serviceaccount default")
# Now create a pod on each node via a daemonset and verify they can talk to each other. # Now create a pod on each node via a daemonset and verify they can talk to each other.
server1.succeed("${kubectl} apply -f ${networkTestDaemonset}") server.succeed("${kubectl} apply -f ${networkTestDaemonset}")
server1.wait_until_succeeds( server.wait_until_succeeds(
f'[ "$(${kubectl} get ds test -o json | ${jq} .status.numberReady)" -eq {len(machines)} ]' f'[ "$(${kubectl} get ds test -o json | ${jq} .status.numberReady)" -eq {len(machines)} ]'
) )
# Get pod IPs # Get pod IPs
pods = server1.succeed("${kubectl} get po -o json | ${jq} '.items[].metadata.name' -r").splitlines() pods = server.succeed("${kubectl} get po -o json | ${jq} '.items[].metadata.name' -r").splitlines()
pod_ips = [ pod_ips = [
server1.succeed(f"${kubectl} get po {n} -o json | ${jq} '.status.podIP' -cr").strip() for n in pods server.succeed(f"${kubectl} get po {n} -o json | ${jq} '.status.podIP' -cr").strip() for n in pods
] ]
# Verify each server can ping each pod ip # Verify each node can ping each pod ip
for pod_ip in pod_ips: for pod_ip in pod_ips:
server1.succeed(f"${ping} -c 1 {pod_ip}") # The CNI sometimes needs a little time
agent1.succeed(f"${ping} -c 1 {pod_ip}") server.wait_until_succeeds(f"ping -c 1 {pod_ip}", timeout=5)
agent.wait_until_succeeds(f"ping -c 1 {pod_ip}", timeout=5)
# Verify the pods can talk to each other # Verify the server can exec into the pod
resp = server1.wait_until_succeeds(f"${kubectl} exec {pods[0]} -- socat TCP:{pod_ips[1]}:8000 -") # for pod in pods:
assert resp.strip() == "server" # resp = server.succeed(f"${kubectl} exec {pod} -- socat TCP:{pod_ip}:8000 -")
resp = server1.wait_until_succeeds(f"${kubectl} exec {pods[1]} -- socat TCP:{pod_ips[0]}:8000 -") # assert resp.strip() == "hello", f"Unexpected response from hello daemonset: {resp.strip()}"
assert resp.strip() == "server"
# Cleanup
server1.succeed("${kubectl} delete -f ${networkTestDaemonset}")
for machine in machines:
machine.shutdown()
''; '';
} }
) )

View file

@ -6,69 +6,83 @@ import ../make-test-python.nix (
... ...
}: }:
let let
pauseImage = pkgs.dockerTools.streamLayeredImage { throwSystem = throw "RKE2: Unsupported system: ${pkgs.stdenv.hostPlatform.system}";
name = "test.local/pause"; coreImages =
{
aarch64-linux = rke2.images-core-linux-arm64-tar-zst;
x86_64-linux = rke2.images-core-linux-amd64-tar-zst;
}
.${pkgs.stdenv.hostPlatform.system} or throwSystem;
canalImages =
{
aarch64-linux = rke2.images-canal-linux-arm64-tar-zst;
x86_64-linux = rke2.images-canal-linux-amd64-tar-zst;
}
.${pkgs.stdenv.hostPlatform.system} or throwSystem;
helloImage = pkgs.dockerTools.buildImage {
name = "test.local/hello";
tag = "local"; tag = "local";
contents = pkgs.buildEnv { compressor = "zstd";
name = "rke2-pause-image-env"; copyToRoot = pkgs.hello;
paths = with pkgs; [ config.Entrypoint = [ "${pkgs.hello}/bin/hello" ];
tini
(hiPrio coreutils)
busybox
];
}; };
config.Entrypoint = [ testJobYaml = pkgs.writeText "test.yaml" ''
"/bin/tini" apiVersion: batch/v1
"--" kind: Job
"/bin/sleep"
"inf"
];
};
testPodYaml = pkgs.writeText "test.yaml" ''
apiVersion: v1
kind: Pod
metadata: metadata:
name: test name: test
spec:
template:
spec: spec:
containers: containers:
- name: test - name: test
image: test.local/pause:local image: "test.local/hello:local"
imagePullPolicy: Never restartPolicy: Never
command: ["sh", "-c", "sleep inf"]
''; '';
in in
{ {
name = "${rke2.name}-single-node"; name = "${rke2.name}-single-node";
meta.maintainers = rke2.meta.maintainers; meta.maintainers = rke2.meta.maintainers;
nodes.machine = nodes.machine =
{ pkgs, ... }:
{ {
networking.firewall.enable = false; config,
networking.useDHCP = false; nodes,
networking.defaultGateway = "192.168.1.1"; pkgs,
networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [ ...
}:
{ {
address = "192.168.1.1"; # Setup image archives to be imported by rke2
prefixLength = 24; systemd.tmpfiles.settings."10-rke2" = {
} "/var/lib/rancher/rke2/agent/images/rke2-images-core.tar.zst" = {
]; "L+".argument = "${coreImages}";
};
"/var/lib/rancher/rke2/agent/images/rke2-images-canal.tar.zst" = {
"L+".argument = "${canalImages}";
};
"/var/lib/rancher/rke2/agent/images/hello.tar.zst" = {
"L+".argument = "${helloImage}";
};
};
virtualisation.memorySize = 1536; # RKE2 needs more resources than the default
virtualisation.diskSize = 4096; virtualisation.cores = 4;
virtualisation.memorySize = 4096;
virtualisation.diskSize = 8092;
services.rke2 = { services.rke2 = {
enable = true; enable = true;
role = "server"; role = "server";
package = rke2; package = rke2;
nodeIP = "192.168.1.1"; # Without nodeIP the apiserver starts with the wrong service IP family
nodeIP = config.networking.primaryIPAddress;
# Slightly reduce resource consumption
disable = [ disable = [
"rke2-coredns" "rke2-coredns"
"rke2-metrics-server" "rke2-metrics-server"
"rke2-ingress-nginx" "rke2-ingress-nginx"
]; "rke2-snapshot-controller"
extraFlags = [ "rke2-snapshot-controller-crd"
"--cluster-reset" "rke2-snapshot-validation-webhook"
]; ];
}; };
}; };
@ -76,23 +90,19 @@ import ../make-test-python.nix (
testScript = testScript =
let let
kubectl = "${pkgs.kubectl}/bin/kubectl --kubeconfig=/etc/rancher/rke2/rke2.yaml"; kubectl = "${pkgs.kubectl}/bin/kubectl --kubeconfig=/etc/rancher/rke2/rke2.yaml";
ctr = "${pkgs.containerd}/bin/ctr -a /run/k3s/containerd/containerd.sock";
in in
# python
'' ''
start_all() start_all()
machine.wait_for_unit("rke2") machine.wait_for_unit("rke2-server")
machine.succeed("${kubectl} cluster-info") machine.succeed("${kubectl} cluster-info")
machine.wait_until_succeeds(
"${pauseImage} | ${ctr} -n k8s.io image import -"
)
machine.wait_until_succeeds("${kubectl} get serviceaccount default") machine.wait_until_succeeds("${kubectl} get serviceaccount default")
machine.succeed("${kubectl} apply -f ${testPodYaml}") machine.succeed("${kubectl} apply -f ${testJobYaml}")
machine.succeed("${kubectl} wait --for 'condition=Ready' pod/test") machine.wait_until_succeeds("${kubectl} wait --for 'condition=complete' job/test")
machine.succeed("${kubectl} delete -f ${testPodYaml}") output = machine.succeed("${kubectl} logs -l batch.kubernetes.io/job-name=test")
assert output.rstrip() == "Hello, world!", f"unexpected output of test job: {output}"
machine.shutdown()
''; '';
} }
) )

View file

@ -134,15 +134,21 @@ let
passthru = { passthru = {
inherit updateScript; inherit updateScript;
tests = tests =
let
moduleTests =
let
package_version =
"rke2_" + lib.replaceStrings [ "." ] [ "_" ] (lib.versions.majorMinor rke2Version);
in
lib.mapAttrs (name: value: nixosTests.rke2.${name}.${package_version}) nixosTests.rke2;
in
{ {
version = testers.testVersion { version = testers.testVersion {
package = rke2; package = rke2;
version = "v${version}"; version = "v${version}";
}; };
} }
// lib.optionalAttrs stdenv.hostPlatform.isLinux { // moduleTests;
inherit (nixosTests) rke2;
};
} // (lib.mapAttrs (_: value: fetchurl value) imagesVersions); } // (lib.mapAttrs (_: value: fetchurl value) imagesVersions);
meta = with lib; { meta = with lib; {