Skip to content

Commit 5533447

Browse files
johnathanabsideup
andauthored
K3sContainer: expose kubeConfig for docker network access (#5097)
Co-authored-by: Sergei Egorov <[email protected]>
1 parent 0073001 commit 5533447

File tree

2 files changed

+93
-15
lines changed

2 files changed

+93
-15
lines changed

modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,29 @@
88
import com.github.dockerjava.api.command.InspectContainerResponse;
99
import com.github.dockerjava.api.model.DockerObjectAccessor;
1010
import lombok.SneakyThrows;
11+
import org.apache.commons.io.IOUtils;
1112
import org.testcontainers.containers.BindMode;
1213
import org.testcontainers.containers.GenericContainer;
1314
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
1415
import org.testcontainers.utility.DockerImageName;
1516

17+
import java.nio.charset.StandardCharsets;
1618
import java.util.HashMap;
1719
import java.util.Map;
1820

1921
public class K3sContainer extends GenericContainer<K3sContainer> {
2022

23+
public static int KUBE_SECURE_PORT = 6443;
24+
25+
public static int RANCHER_WEBHOOK_PORT = 8443;
26+
2127
private String kubeConfigYaml;
2228

2329
public K3sContainer(DockerImageName dockerImageName) {
2430
super(dockerImageName);
2531
dockerImageName.assertCompatibleWith(DockerImageName.parse("rancher/k3s"));
2632

27-
addExposedPorts(6443, 8443);
33+
addExposedPorts(KUBE_SECURE_PORT, RANCHER_WEBHOOK_PORT);
2834
setPrivilegedMode(true);
2935
withCreateContainerCmdModifier(it -> {
3036
it.getHostConfig().withCgroupnsMode("host");
@@ -44,30 +50,57 @@ public K3sContainer(DockerImageName dockerImageName) {
4450
setWaitStrategy(new LogMessageWaitStrategy().withRegEx(".*Node controller sync successful.*"));
4551
}
4652

47-
@SneakyThrows
4853
@Override
4954
protected void containerIsStarted(InspectContainerResponse containerInfo) {
55+
String rawKubeConfig = copyFileFromContainer("/etc/rancher/k3s/k3s.yaml",
56+
is -> IOUtils.toString(is, StandardCharsets.UTF_8)
57+
);
58+
String serverUrl = "https://" + this.getHost() + ":" + this.getMappedPort(KUBE_SECURE_PORT);
59+
kubeConfigYaml = kubeConfigWithServerUrl(rawKubeConfig, serverUrl);
60+
}
61+
62+
/**
63+
* Return the kubernetes client configuration to access k3s from the host machine.
64+
*
65+
* @return the kubeConfig yaml.
66+
*/
67+
public String getKubeConfigYaml() {
68+
return kubeConfigYaml;
69+
}
70+
71+
/**
72+
* Generate a kubernetes client configuration for use on a docker internal network. The kubeConfig can be used by
73+
* another docker container running in the same network as the k3s container. For access from the host, use
74+
* the {@link #getKubeConfigYaml()} method instead.
75+
*
76+
* @param networkAlias a valid network alias of the k3s container.
77+
* @return the kubeConfig yaml.
78+
*/
79+
public String generateInternalKubeConfigYaml(String networkAlias) {
80+
if (this.getNetworkAliases().contains(networkAlias)) {
81+
String serverUrl = "https://" + networkAlias + ":" + KUBE_SECURE_PORT;
82+
return kubeConfigWithServerUrl(kubeConfigYaml, serverUrl);
83+
} else {
84+
throw new IllegalArgumentException(networkAlias + " is not a network alias for k3s container");
85+
}
86+
}
87+
88+
@SneakyThrows
89+
private String kubeConfigWithServerUrl(String kubeConfigYaml, String serverUrl) {
5090
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
5191

52-
ObjectNode rawKubeConfig = copyFileFromContainer(
53-
"/etc/rancher/k3s/k3s.yaml",
54-
is -> objectMapper.readValue(is, ObjectNode.class)
55-
);
92+
ObjectNode kubeConfigObjectNode = objectMapper.readValue(kubeConfigYaml, ObjectNode.class);
5693

57-
JsonNode clusterNode = rawKubeConfig.at("/clusters/0/cluster");
94+
JsonNode clusterNode = kubeConfigObjectNode.at("/clusters/0/cluster");
5895
if (!clusterNode.isObject()) {
5996
throw new IllegalStateException("'/clusters/0/cluster' expected to be an object");
6097
}
6198
ObjectNode clusterConfig = (ObjectNode) clusterNode;
99+
clusterConfig.replace("server", new TextNode(serverUrl));
62100

63-
clusterConfig.replace("server", new TextNode("https://" + this.getHost() + ":" + this.getMappedPort(6443)));
101+
kubeConfigObjectNode.set("current-context", new TextNode("default"));
64102

65-
rawKubeConfig.set("current-context", new TextNode("default"));
66-
67-
kubeConfigYaml = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rawKubeConfig);
68-
}
69-
70-
public String getKubeConfigYaml() {
71-
return kubeConfigYaml;
103+
return objectMapper.writerWithDefaultPrettyPrinter()
104+
.writeValueAsString(kubeConfigObjectNode);
72105
}
73106
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.testcontainers.k3s;
2+
3+
import org.junit.ClassRule;
4+
import org.junit.Test;
5+
import org.testcontainers.containers.GenericContainer;
6+
import org.testcontainers.containers.Network;
7+
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
8+
import org.testcontainers.images.builder.Transferable;
9+
import org.testcontainers.utility.DockerImageName;
10+
11+
import java.time.Duration;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
public class KubectlContainerTest {
16+
17+
public static Network network = Network.SHARED;
18+
19+
@ClassRule
20+
public static K3sContainer k3s = new K3sContainer(DockerImageName.parse("rancher/k3s:v1.21.3-k3s1"))
21+
.withNetwork(network)
22+
.withNetworkAliases("k3s");
23+
24+
@Test
25+
public void shouldExposeKubeConfigForNetworkAlias() throws Exception {
26+
String kubeConfigYaml = k3s.generateInternalKubeConfigYaml("k3s");
27+
28+
try (
29+
GenericContainer<?> kubectlContainer = new GenericContainer<>("rancher/kubectl:v1.23.3")
30+
.withNetwork(network)
31+
.withCopyToContainer(Transferable.of(kubeConfigYaml), "/.kube/config")
32+
.withCommand("get namespaces")
33+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(30)))
34+
) {
35+
kubectlContainer.start();
36+
37+
assertThat(kubectlContainer.getLogs()).contains("kube-system");
38+
}
39+
}
40+
41+
@Test(expected = IllegalArgumentException.class)
42+
public void shouldThrowAnExceptionForUnknownNetworkAlias() {
43+
k3s.generateInternalKubeConfigYaml("not-set-network-alias");
44+
}
45+
}

0 commit comments

Comments
 (0)