diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java index a0987cf2d24e8..f6b2e79a0052d 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java @@ -9,6 +9,8 @@ import static io.quarkus.kubernetes.deployment.Constants.MIN_PORT_NUMBER; import static io.quarkus.kubernetes.deployment.Constants.READINESS_PROBE; import static io.quarkus.kubernetes.deployment.Constants.STARTUP_PROBE; +import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.MANAGEMENT_PORT_NAME; +import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.managementPortIsEnabled; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -149,6 +151,14 @@ public static List createDecorators(String clusterKind, // Handle init Containers result.addAll(KubernetesCommonHelper.createInitContainerDecorators(clusterKind, name, initContainers, result)); result.addAll(KubernetesCommonHelper.createInitJobDecorators(clusterKind, name, jobs, result)); + + // Do not bind the Management port to the Service resource unless it's explicitly used by the user. + if (managementPortIsEnabled() + && (config.ingress == null + || !config.ingress.expose + || !config.ingress.targetPort.equals(MANAGEMENT_PORT_NAME))) { + result.add(new DecoratorBuildItem(clusterKind, new RemovePortFromServiceDecorator(name, MANAGEMENT_PORT_NAME))); + } return result; } 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 5883a3eb6da4f..297b5ac9bb4e1 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 @@ -217,13 +217,13 @@ public static List createDecorators(Optional projec result.addAll(createCommandDecorator(project, target, name, config, command)); result.addAll(createArgsDecorator(project, target, name, config, command)); - //Handle Probes + // Handle Probes if (!port.isEmpty()) { result.addAll(createProbeDecorators(name, target, config.getLivenessProbe(), config.getReadinessProbe(), config.getStartupProbe(), livenessProbePath, readinessProbePath, startupPath)); } - //Handle RBAC + // Handle RBAC result.addAll(createRbacDecorators(name, target, config, kubernetesClientConfiguration, roles, clusterRoles, serviceAccounts, roleBindings)); return result; diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigUtil.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigUtil.java index 2631260c9c3ce..66d3ab0600efa 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigUtil.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfigUtil.java @@ -22,6 +22,11 @@ public class KubernetesConfigUtil { + /** + * It should be the same name as in VertxHttpProcessor.kubernetesForManagement. + */ + public static final String MANAGEMENT_PORT_NAME = "management"; + private static final String DEKORATE_PREFIX = "dekorate."; private static final Pattern QUARKUS_DEPLOY_PATTERN = Pattern.compile("quarkus\\.([^\\.]+)\\.deploy"); @@ -111,6 +116,10 @@ public static Map toMap(PlatformConfiguration... platformConfigu return result; } + public static boolean managementPortIsEnabled() { + return ConfigProvider.getConfig().getOptionalValue("quarkus.management.enabled", Boolean.class).orElse(false); + } + private static Map toS2iProperties(Map map) { Map result = new HashMap<>(); map.forEach((k, v) -> { diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java index 0469ba7a445f5..50827d91246d6 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java @@ -9,6 +9,8 @@ import static io.quarkus.kubernetes.deployment.Constants.READINESS_PROBE; import static io.quarkus.kubernetes.deployment.Constants.ROUTE; import static io.quarkus.kubernetes.deployment.Constants.STARTUP_PROBE; +import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.MANAGEMENT_PORT_NAME; +import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.managementPortIsEnabled; import static io.quarkus.kubernetes.deployment.OpenshiftConfig.OpenshiftFlavor.v3; import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.DEFAULT_PRIORITY; @@ -368,6 +370,14 @@ public List createDecorators(ApplicationInfoBuildItem applic // Handle init Containers and Jobs result.addAll(KubernetesCommonHelper.createInitContainerDecorators(OPENSHIFT, name, initContainers, result)); result.addAll(KubernetesCommonHelper.createInitJobDecorators(OPENSHIFT, name, jobs, result)); + + // Do not bind the Management port to the Service resource unless it's explicitly used by the user. + if (managementPortIsEnabled() + && (config.route == null + || !config.route.expose + || !config.route.targetPort.equals(MANAGEMENT_PORT_NAME))) { + result.add(new DecoratorBuildItem(OPENSHIFT, new RemovePortFromServiceDecorator(name, MANAGEMENT_PORT_NAME))); + } return result; } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RemovePortFromServiceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RemovePortFromServiceDecorator.java new file mode 100644 index 0000000000000..f332a6933d83e --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/RemovePortFromServiceDecorator.java @@ -0,0 +1,29 @@ +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.decorator.AddServiceResourceDecorator; +import io.dekorate.kubernetes.decorator.Decorator; +import io.dekorate.kubernetes.decorator.NamedResourceDecorator; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.ServicePortBuilder; +import io.fabric8.kubernetes.api.model.ServiceSpecFluent; + +public class RemovePortFromServiceDecorator extends NamedResourceDecorator { + + private final String portNameToRemove; + + public RemovePortFromServiceDecorator(String name, String portNameToRemove) { + super(name); + this.portNameToRemove = portNameToRemove; + } + + @Override + public void andThenVisit(ServiceSpecFluent service, ObjectMeta resourceMeta) { + service.removeMatchingFromPorts(p -> ((ServicePortBuilder) p).getName().equals(portNameToRemove)); + } + + @Override + public Class[] after() { + return new Class[] { AddServiceResourceDecorator.class }; + } + +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java index ac24e0ba5b893..aa52dfb0f5da3 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java @@ -8,6 +8,8 @@ import static io.quarkus.kubernetes.deployment.Constants.LIVENESS_PROBE; import static io.quarkus.kubernetes.deployment.Constants.READINESS_PROBE; import static io.quarkus.kubernetes.deployment.Constants.STARTUP_PROBE; +import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.MANAGEMENT_PORT_NAME; +import static io.quarkus.kubernetes.deployment.KubernetesConfigUtil.managementPortIsEnabled; import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.VANILLA_KUBERNETES_PRIORITY; import java.util.ArrayList; @@ -279,6 +281,15 @@ public List createDecorators(ApplicationInfoBuildItem applic // Handle init Containers and Jobs result.addAll(KubernetesCommonHelper.createInitContainerDecorators(KUBERNETES, name, initContainers, result)); result.addAll(KubernetesCommonHelper.createInitJobDecorators(KUBERNETES, name, jobs, result)); + + // Do not bind the Management port to the Service resource unless it's explicitly used by the user. + if (managementPortIsEnabled() + && (config.ingress == null + || !config.ingress.expose + || !config.ingress.targetPort.equals(MANAGEMENT_PORT_NAME))) { + result.add(new DecoratorBuildItem(KUBERNETES, new RemovePortFromServiceDecorator(name, MANAGEMENT_PORT_NAME))); + } + return result; } diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesExposingManagementInterfaceTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesExposingManagementInterfaceTest.java new file mode 100644 index 0000000000000..593a10524a9f0 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesExposingManagementInterfaceTest.java @@ -0,0 +1,56 @@ +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 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.Service; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KubernetesExposingManagementInterfaceTest { + + private static final String NAME = "kubernetes-exposing-management"; + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) + .setApplicationName(NAME) + .setApplicationVersion("0.1-SNAPSHOT") + .overrideConfigKey("quarkus.management.enabled", "true") + .overrideConfigKey("quarkus.kubernetes.ingress.expose", "true") + .overrideConfigKey("quarkus.kubernetes.ingress.target-port", "management"); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + final 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")); + + Service service = kubernetesList.stream().filter(Service.class::isInstance).map(Service.class::cast).findFirst().get(); + + assertThat(service.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo(NAME); + }); + + assertThat(service.getSpec()).satisfies(spec -> { + assertThat(spec.getPorts()).hasSize(3); + assertThat(spec.getPorts()).satisfiesOnlyOnce(port -> assertThat(port.getName()).isEqualTo("http")); + assertThat(spec.getPorts()).satisfiesOnlyOnce(port -> assertThat(port.getName()).isEqualTo("https")); + assertThat(spec.getPorts()).satisfiesOnlyOnce(port -> assertThat(port.getName()).isEqualTo("management")); + }); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthUsingManagementInterfaceTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthUsingManagementInterfaceTest.java index 11d6f0f4d3fb8..92bba8d176c47 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthUsingManagementInterfaceTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthUsingManagementInterfaceTest.java @@ -15,6 +15,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Probe; +import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.quarkus.builder.Version; import io.quarkus.maven.dependency.Dependency; @@ -94,6 +95,18 @@ public void assertGeneratedResources() throws IOException { }); }); }); + + assertThat(kubernetesList.get(1)).isInstanceOfSatisfying(Service.class, s -> { + assertThat(s.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo(NAME); + }); + + assertThat(s.getSpec()).satisfies(spec -> { + assertThat(spec.getPorts()).hasSize(2); + assertThat(spec.getPorts()).satisfiesOnlyOnce(port -> assertThat(port.getName()).isEqualTo("http")); + assertThat(spec.getPorts()).satisfiesOnlyOnce(port -> assertThat(port.getName()).isEqualTo("https")); + }); + }); } private void assertProbePath(Probe p, String expectedPath) {