From e5a21e4a486f5e0076e0e8a831e300e8096b8507 Mon Sep 17 00:00:00 2001 From: Vincent Sourin Date: Sun, 27 Oct 2024 13:31:27 +0100 Subject: [PATCH] Add nodeSelect capability to kubernetes extension. Fix https://github.com/quarkusio/quarkus/issues/44122 Signed-off-by: Vinche --- .../asciidoc/deploying-to-kubernetes.adoc | 21 +++++++ .../deployment/AddNodeSelectorDecorator.java | 24 ++++++++ .../deployment/KnativeProcessor.java | 2 + .../deployment/KubernetesCommonHelper.java | 4 ++ .../deployment/NodeSelectorConfig.java | 13 +++++ .../deployment/PlatformConfiguration.java | 5 ++ .../deployment/KubernetesConfigTest.java | 4 ++ .../application-kubernetes.properties | 3 + .../KubernetesWithNodeSelectorTest.java | 55 +++++++++++++++++++ .../kubernetes-with-nodeselector.properties | 2 + 10 files changed, 133 insertions(+) create mode 100644 extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddNodeSelectorDecorator.java create mode 100644 extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/NodeSelectorConfig.java create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithNodeSelectorTest.java create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-nodeselector.properties diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc index 40f358744db22..8ab61ce549e68 100644 --- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc +++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc @@ -716,6 +716,27 @@ spec: ip: 10.0.0.0 ---- +=== Add nodeSelector +To add a nodeSelector in the generated `Deployment` (more information can be found in https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes[Kubernetes documentation]), just apply the following configuration: + +[source,properties] +---- +quarkus.kubernetes.node-selector.key=diskType +quarkus.kubernetes.node-selector.value=ssd +---- + +This would generate the following `nodeSelector` section in the `deployment` definition: + +[source,yaml] +---- +kind: Deployment +spec: + template: + spec: + nodeSelector: + diskType: ssd +---- + === Container Resources Management CPU & Memory limits and requests can be applied to a `Container` (more info in https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/[Kubernetes documentation]) using the following configuration: diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddNodeSelectorDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddNodeSelectorDecorator.java new file mode 100644 index 0000000000000..c646ac9525ef9 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddNodeSelectorDecorator.java @@ -0,0 +1,24 @@ +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.decorator.NamedResourceDecorator; +import io.dekorate.utils.Strings; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.PodSpecFluent; + +public class AddNodeSelectorDecorator extends NamedResourceDecorator> { + private final String nodeSelectorKey; + private final String nodeSelectorValue; + + public AddNodeSelectorDecorator(String deploymentName, String nodeSelectorKey, String nodeSelectorValue) { + super(deploymentName); + this.nodeSelectorKey = nodeSelectorKey; + this.nodeSelectorValue = nodeSelectorValue; + } + + public void andThenVisit(PodSpecFluent podSpec, ObjectMeta resourceMeta) { + if (Strings.isNotNullOrEmpty(nodeSelectorKey) && Strings.isNotNullOrEmpty(nodeSelectorValue)) { + podSpec.removeFromNodeSelector(nodeSelectorKey); + podSpec.addToNodeSelector(nodeSelectorKey, nodeSelectorValue); + } + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java index 03aa9446595b1..a19726c00c5da 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java @@ -290,6 +290,8 @@ public List createDecorators(ApplicationInfoBuildItem applic result.addAll(createAppConfigVolumeAndEnvDecorators(name, config)); config.hostAliases().entrySet().forEach(e -> result.add(new DecoratorBuildItem(KNATIVE, new AddHostAliasesToRevisionDecorator(name, HostAliasConverter.convert(e))))); + config.nodeSelector().ifPresent(n -> result.add(new DecoratorBuildItem(KNATIVE, + new AddNodeSelectorDecorator(name, n.key(), n.value())))); config.sidecars().entrySet().forEach(e -> result .add(new DecoratorBuildItem(KNATIVE, new AddSidecarToRevisionDecorator(name, ContainerConverter.convert(e))))); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 68160092bc238..6c1f43f2e3d21 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -852,6 +852,10 @@ private static List createPodDecorators(String target, Strin config.hostAliases().entrySet().forEach(e -> result .add(new DecoratorBuildItem(target, new AddHostAliasesDecorator(name, HostAliasConverter.convert(e))))); + config.nodeSelector() + .ifPresent(n -> result.add( + new DecoratorBuildItem(target, new AddNodeSelectorDecorator(name, n.key(), n.value())))); + config.initContainers().entrySet().forEach(e -> result .add(new DecoratorBuildItem(target, new AddInitContainerDecorator(name, ContainerConverter.convert(e))))); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/NodeSelectorConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/NodeSelectorConfig.java new file mode 100644 index 0000000000000..681e7de03043e --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/NodeSelectorConfig.java @@ -0,0 +1,13 @@ +package io.quarkus.kubernetes.deployment; + +public interface NodeSelectorConfig { + /** + * The key of the nodeSelector. + */ + String key(); + + /** + * The value of the nodeSelector. + */ + String value(); +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/PlatformConfiguration.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/PlatformConfiguration.java index 11c78c45cc24f..51a8e40ab7fc3 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/PlatformConfiguration.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/PlatformConfiguration.java @@ -204,6 +204,11 @@ public interface PlatformConfiguration extends EnvVarHolder { @WithName("hostaliases") Map hostAliases(); + /** + * The nodeSelector. + */ + Optional nodeSelector(); + /** * Resources requirements. */ diff --git a/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigTest.java b/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigTest.java index 4e75fe48d3e1f..1ddc093509987 100644 --- a/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigTest.java +++ b/extensions/kubernetes/vanilla/deployment/src/test/java/io/quarkus/kubernetes/deployment/KubernetesConfigTest.java @@ -228,6 +228,10 @@ void kubernetes() throws Exception { assertEquals("konoha", hostAliases.get("ip")); assertIterableEquals(List.of("dev", "qly", "prod"), (Iterable) hostAliases.get("hostnames")); + Map nodeSelector = deployment().map("spec").map("template").map("spec").asMap("nodeSelector"); + assertTrue(nodeSelector.containsKey("jutsu")); + assertEquals("katon", nodeSelector.get("jutsu")); + Map limits = container().map("resources").asMap("limits"); assertEquals("fuuton", limits.get("cpu")); assertEquals("raiton", limits.get("memory")); diff --git a/extensions/kubernetes/vanilla/deployment/src/test/resources/application-kubernetes.properties b/extensions/kubernetes/vanilla/deployment/src/test/resources/application-kubernetes.properties index 467f9ad9fcb82..bdf4d8a914544 100644 --- a/extensions/kubernetes/vanilla/deployment/src/test/resources/application-kubernetes.properties +++ b/extensions/kubernetes/vanilla/deployment/src/test/resources/application-kubernetes.properties @@ -132,6 +132,9 @@ quarkus.kubernetes.deployment-target=kubernetes,openshift,knative,minikube quarkus.kubernetes.hostaliases.konoha.ip=0.0.0.0 quarkus.kubernetes.hostaliases.konoha.hostnames=dev,qly,prod +quarkus.kubernetes.node-selector.key=jutsu +quarkus.kubernetes.node-selector.value=katon + quarkus.kubernetes.resources.limits.cpu=fuuton quarkus.kubernetes.resources.limits.memory=raiton quarkus.kubernetes.resources.requests.cpu=katon diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithNodeSelectorTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithNodeSelectorTest.java new file mode 100644 index 0000000000000..950eb433c088d --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithNodeSelectorTest.java @@ -0,0 +1,55 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KubernetesWithNodeSelectorTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) + .setApplicationName("nodeselector") + .setApplicationVersion("0.1-SNAPSHOT") + .withConfigurationResource("kubernetes-with-nodeselector.properties"); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + Map expectedNodeSelector = Map.of("diskType", "ssd"); + + Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml")); + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + assertThat(kubernetesList.get(0)).isInstanceOfSatisfying(Deployment.class, d -> { + assertThat(d.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo("nodeselector"); + }); + + assertThat(d.getSpec()).satisfies(deploymentSpec -> { + assertThat(deploymentSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getNodeSelector()).containsExactlyEntriesOf(expectedNodeSelector); + }); + }); + }); + }); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-nodeselector.properties b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-nodeselector.properties new file mode 100644 index 0000000000000..b14b91278ff67 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-nodeselector.properties @@ -0,0 +1,2 @@ +quarkus.kubernetes.node-selector.key=diskType +quarkus.kubernetes.node-selector.value=ssd \ No newline at end of file