Skip to content

Commit 0e45373

Browse files
K3s
1 parent dfa1693 commit 0e45373

File tree

2 files changed

+64
-70
lines changed

2 files changed

+64
-70
lines changed

docs/modules/k3s.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# K3s Module
1+
# K3s
22

3-
[K3s](https://k3s.io/) is a highly available, certified Kubernetes distribution designed for production workloads in unattended, resource-constrained, remote locations or inside IoT appliances.
3+
!!! warning
4+
This container runs privileged, as it spawns its own containers. For this reason, this container will not work in certain rootless Docker, Docker-in-Docker, or other environments that disallow privileged containers.
45

56
## Install
67

@@ -10,17 +11,28 @@ npm install @testcontainers/k3s --save-dev
1011

1112
## Examples
1213

14+
These examples use the following libraries:
15+
16+
- [@kubernetes/client-node](https://www.npmjs.com/package/@kubernetes/client-node)
17+
18+
npm install @kubernetes/client-node
19+
20+
Choose an image from the [container registry](https://hub.docker.com/r/rancher/k3s) and substitute `IMAGE`.
21+
22+
### List nodes
23+
1324
<!--codeinclude-->
14-
[Starting a K3s server:](../../packages/modules/k3s/src/k3s-container.test.ts) inside_block:starting_k3s
25+
[](../../packages/modules/k3s/src/k3s-container.test.ts) inside_block:k3sListNodes
1526
<!--/codeinclude-->
27+
28+
### Start a pod
1629

1730
<!--codeinclude-->
18-
[Connecting to the server using the Kubernetes JavaScript client:](../../packages/modules/k3s/src/k3s-container.test.ts) inside_block:connecting_with_client
31+
[](../../packages/modules/k3s/src/k3s-container.test.ts) inside_block:k3sStartPod
1932
<!--/codeinclude-->
2033

21-
## Known limitations
34+
### Aliased kubeconfig
2235

23-
!!! warning
24-
* K3sContainer runs as a privileged container and needs to be able to spawn its own containers. For these reasons,
25-
K3sContainer will not work in certain rootless Docker, Docker-in-Docker, or other environments where privileged
26-
containers are disallowed.
36+
<!--codeinclude-->
37+
[](../../packages/modules/k3s/src/k3s-container.test.ts) inside_block:k3sAliasedKubeConfig
38+
<!--/codeinclude-->
Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,33 @@
11
import * as k8s from "@kubernetes/client-node";
2-
import { setTimeout } from "node:timers/promises";
32
import { GenericContainer, Network, Wait } from "testcontainers";
3+
import { getImage } from "../../../testcontainers/src/utils/test-helper";
44
import { K3sContainer } from "./k3s-container";
55

6-
describe("K3s", { timeout: 120_000 }, () => {
7-
it("should construct", () => {
8-
new K3sContainer("rancher/k3s:v1.31.2-k3s1");
9-
});
6+
const IMAGE = getImage(__dirname);
107

8+
describe("K3s", { timeout: 120_000 }, () => {
119
// K3sContainer runs as a privileged container
1210
if (!process.env["CI_ROOTLESS"]) {
1311
it("should start and have listable node", async () => {
14-
// starting_k3s {
15-
await using container = await new K3sContainer("rancher/k3s:v1.31.2-k3s1").start();
16-
// }
17-
18-
// connecting_with_client {
19-
// obtain a kubeconfig file that allows us to connect to k3s
20-
const kubeConfig = container.getKubeConfig();
21-
22-
const kc = new k8s.KubeConfig();
23-
kc.loadFromString(kubeConfig);
12+
// k3sListNodes {
13+
await using container = await new K3sContainer(IMAGE).start();
2414

25-
const client = kc.makeApiClient(k8s.CoreV1Api);
15+
const kubeConfig = new k8s.KubeConfig();
16+
kubeConfig.loadFromString(container.getKubeConfig());
2617

27-
// interact with the running K3s server, e.g.:
18+
const client = kubeConfig.makeApiClient(k8s.CoreV1Api);
2819
const nodeList = await client.listNode();
29-
// }
3020

3121
expect(nodeList.items).toHaveLength(1);
32-
});
33-
34-
it("should expose kubeconfig for a network alias", async () => {
35-
await using network = await new Network().start();
36-
await using container = await new K3sContainer("rancher/k3s:v1.31.2-k3s1")
37-
.withNetwork(network)
38-
.withNetworkAliases("k3s")
39-
.start();
40-
41-
// obtain a kubeconfig that allows us to connect on the custom network
42-
const kubeConfig = container.getAliasedKubeConfig("k3s");
43-
44-
await using kubectlContainer = await new GenericContainer("rancher/kubectl:v1.31.2")
45-
.withNetwork(network)
46-
.withCopyContentToContainer([{ content: kubeConfig, target: "/home/kubectl/.kube/config" }])
47-
.withCommand(["get", "namespaces"])
48-
.withWaitStrategy(Wait.forOneShotStartup())
49-
.withStartupTimeout(30_000)
50-
.start();
51-
52-
const chunks = [];
53-
for await (const chunk of await kubectlContainer.logs()) {
54-
chunks.push(chunk);
55-
}
56-
expect(chunks).toEqual(expect.arrayContaining([expect.stringContaining("kube-system")]));
22+
// }
5723
});
5824

5925
it("should start a pod", async () => {
60-
await using container = await new K3sContainer("rancher/k3s:v1.31.2-k3s1").start();
61-
const kc = new k8s.KubeConfig();
62-
kc.loadFromString(container.getKubeConfig());
26+
// k3sStartPod {
27+
await using container = await new K3sContainer(IMAGE).start();
28+
29+
const kubeConfig = new k8s.KubeConfig();
30+
kubeConfig.loadFromString(container.getKubeConfig());
6331

6432
const pod = {
6533
metadata: {
@@ -85,23 +53,37 @@ describe("K3s", { timeout: 120_000 }, () => {
8553
},
8654
};
8755

88-
const client = kc.makeApiClient(k8s.CoreV1Api);
56+
const client = kubeConfig.makeApiClient(k8s.CoreV1Api);
8957
await client.createNamespacedPod({ namespace: "default", body: pod });
9058

91-
// wait for pod to be ready
92-
expect(await podIsReady(client, "default", "helloworld", 60_000)).toBe(true);
59+
await vi.waitFor(async () => {
60+
const { status } = await client.readNamespacedPodStatus({ namespace: "default", name: "helloworld" });
61+
62+
return (
63+
status?.phase === "Running" &&
64+
status?.conditions?.some((cond) => cond.type === "Ready" && cond.status === "True")
65+
);
66+
}, 60_000);
67+
// }
9368
});
9469
}
95-
});
9670

97-
async function podIsReady(client: k8s.CoreV1Api, namespace: string, name: string, timeout: number): Promise<boolean> {
98-
for (const startTime = Date.now(); Date.now() - startTime < timeout; ) {
99-
const res = await client.readNamespacedPodStatus({ namespace, name });
100-
const ready =
101-
res.status?.phase === "Running" &&
102-
!!res.status?.conditions?.some((cond) => cond.type === "Ready" && cond.status === "True");
103-
if (ready) return true;
104-
await setTimeout(3_000);
105-
}
106-
return false;
107-
}
71+
it("should expose kubeconfig for a network alias", async () => {
72+
// k3sAliasedKubeConfig {
73+
await using network = await new Network().start();
74+
await using container = await new K3sContainer(IMAGE).withNetwork(network).withNetworkAliases("k3s").start();
75+
76+
const kubeConfig = container.getAliasedKubeConfig("k3s");
77+
78+
await using kubectlContainer = await new GenericContainer("rancher/kubectl:v1.31.2")
79+
.withNetwork(network)
80+
.withCopyContentToContainer([{ content: kubeConfig, target: "/home/kubectl/.kube/config" }])
81+
.withCommand(["get", "namespaces"])
82+
.withWaitStrategy(Wait.forOneShotStartup())
83+
.start();
84+
85+
const chunks = await (await kubectlContainer.logs()).toArray();
86+
expect(chunks).toEqual(expect.arrayContaining([expect.stringContaining("kube-system")]));
87+
// }
88+
});
89+
});

0 commit comments

Comments
 (0)