Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow generation of Deployment resource for Openshift #20901

Merged
merged 1 commit into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/src/main/asciidoc/deploying-to-openshift.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,23 @@ It's also possible to use the value from another field to add a new environment
quarkus.openshift.env.fields.foo=metadata.name
----

===== Using Deployment instead of DeploymentConfig
Out of the box the extension will generate a `DeploymentConfig` resource. Often users, prefer to use `Deployment` as the main deployment resource, but still make use of Openshift specific resources like `Route`, `BuildConfig` etc.
This feature is enabled by setting `quarkus.openshift.deployment-kind` to `Deployment`.

[source,properties]
----
quarkus.openshift.deployment-kind=Deployment
----

Since `Deployment` is a Kubernetes resource and not Openshift specific, it can't possibly leverage `ImageStream` resources, as is the case with `DeploymentConfig`. This means that the image references need to include the container image registry that hosts the image.
When the image is built, using Openshift builds (s2i binary and docker strategy) the Openshift internral image registry `image-registry.openshift-image-registry.svc:5000` will be used, unless an other registry has been explicitly specified by the user. Please note, that in the internal registry the project/namespace name is added as part of the image repository: `image-registry.openshift-image-registry.svc:5000/<project name>/<name>:<tag>`, so users will need to make sure that the target project/namespace name is aligned with the `quarkus.container-image.group`.

[source,properties]
----
quarkus.container-image.group=<project/namespace name>
----

===== Validation

A conflict between two definitions, e.g. mistakenly assigning both a value and specifying that a variable is derived from a field, will result in an error being thrown at build time so that you get the opportunity to fix the issue before you deploy your application to your cluster where it might be more difficult to diagnose the source of the issue.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.jboss.logging.Logger;

import io.quarkus.container.spi.ContainerImageInfoBuildItem;
import io.quarkus.container.spi.FallbackContainerImageRegistryBuildItem;
import io.quarkus.container.spi.ImageReference;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
Expand Down Expand Up @@ -34,7 +35,9 @@ public void ignoreCredentialsChange(BuildProducer<SuppressNonRuntimeConfigChange

@BuildStep
public void publishImageInfo(ApplicationInfoBuildItem app,
ContainerImageConfig containerImageConfig, Capabilities capabilities,
ContainerImageConfig containerImageConfig,
Optional<FallbackContainerImageRegistryBuildItem> containerImageRegistry,
Capabilities capabilities,
BuildProducer<ContainerImageInfoBuildItem> containerImage) {

ensureSingleContainerImageExtension(capabilities);
Expand Down Expand Up @@ -66,7 +69,8 @@ public void publishImageInfo(ApplicationInfoBuildItem app,
return;
}

String registry = containerImageConfig.registry.orElse(null);
String registry = containerImageConfig.registry
.orElseGet(() -> containerImageRegistry.map(FallbackContainerImageRegistryBuildItem::getRegistry).orElse(null));
if ((registry != null) && !ImageReference.isValidRegistry(registry)) {
throw new IllegalArgumentException("The supplied container-image registry '" + registry + "' is invalid");
}
Expand All @@ -83,7 +87,7 @@ public void publishImageInfo(ApplicationInfoBuildItem app,
throw new IllegalArgumentException("The supplied container-image tag '" + effectiveTag + "' is invalid");
}

containerImage.produce(new ContainerImageInfoBuildItem(containerImageConfig.registry,
containerImage.produce(new ContainerImageInfoBuildItem(Optional.ofNullable(registry),
containerImageConfig.getEffectiveGroup(),
effectiveName, effectiveTag,
containerImageConfig.additionalTags.orElse(Collections.emptyList())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private void whenPublishImageInfo() {
Capabilities capabilities = new Capabilities(Collections.emptySet());
BuildProducer<ContainerImageInfoBuildItem> containerImage = actualImageConfig -> actualContainerImageInfo = actualImageConfig;
ContainerImageProcessor processor = new ContainerImageProcessor();
processor.publishImageInfo(app, containerImageConfig, capabilities, containerImage);
processor.publishImageInfo(app, containerImageConfig, Optional.empty(), capabilities, containerImage);
}

private void thenImageIs(String expectedImage) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

package io.quarkus.container.spi;

import io.quarkus.builder.item.SimpleBuildItem;

public final class FallbackContainerImageRegistryBuildItem extends SimpleBuildItem {

private final String registry;

public FallbackContainerImageRegistryBuildItem(String registry) {
this.registry = registry;
}

public String getRegistry() {
return registry;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package io.quarkus.openshift.deployment;

import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG_GROUP;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG_VERSION;
import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT;

import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -19,13 +16,18 @@ public class OpenshiftProcessor {
public void checkOpenshift(ApplicationInfoBuildItem applicationInfo, OpenshiftConfig config,
BuildProducer<KubernetesDeploymentTargetBuildItem> deploymentTargets,
BuildProducer<KubernetesResourceMetadataBuildItem> resourceMeta) {

String kind = config.getDepoymentResourceKind();
String group = config.getDepoymentResourceGroup();
String version = config.getDepoymentResourceVersion();

deploymentTargets
.produce(
new KubernetesDeploymentTargetBuildItem(OPENSHIFT, DEPLOYMENT_CONFIG, DEPLOYMENT_CONFIG_GROUP,
DEPLOYMENT_CONFIG_VERSION, true));
new KubernetesDeploymentTargetBuildItem(OPENSHIFT, kind, group,
version, true));

String name = ResourceNameUtil.getResourceName(config, applicationInfo);
resourceMeta.produce(new KubernetesResourceMetadataBuildItem(OPENSHIFT, DEPLOYMENT_CONFIG_GROUP,
DEPLOYMENT_CONFIG_VERSION, DEPLOYMENT_CONFIG, name));
resourceMeta.produce(new KubernetesResourceMetadataBuildItem(OPENSHIFT, group,
version, kind, name));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

package io.quarkus.kubernetes.deployment;

import java.util.HashMap;

import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.fabric8.kubernetes.api.model.KubernetesListFluent;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;

public class AddDeploymentResourceDecorator extends ResourceProvidingDecorator<KubernetesListFluent<?>> {

private final String name;
private final PlatformConfiguration config;

@Override
public void visit(KubernetesListFluent<?> list) {
list.addToItems(new DeploymentBuilder()
.withNewMetadata()
.withName(name)
.endMetadata()
.withNewSpec()
.withReplicas(1)
.withNewSelector()
.withMatchLabels(new HashMap<String, String>())
.endSelector()
.withNewTemplate()
.withNewSpec()
.addNewContainer()
.withName(name)
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build());
}

public AddDeploymentResourceDecorator(String name, PlatformConfiguration config) {
this.name = name;
this.config = config;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.Route;
Expand Down Expand Up @@ -121,6 +122,7 @@ private DeploymentTargetEntry determineDeploymentTarget(
final DeploymentTargetEntry selectedTarget;

boolean checkForMissingRegistry = true;
boolean checkForNamespaceGroupAlignment = true;
List<String> userSpecifiedDeploymentTargets = KubernetesConfigUtil.getUserSpecifiedDeploymentTargets();
if (userSpecifiedDeploymentTargets.isEmpty()) {
selectedTarget = targets.getEntriesSortedByPriority().get(0);
Expand All @@ -145,7 +147,12 @@ private DeploymentTargetEntry determineDeploymentTarget(
}

if (OPENSHIFT.equals(selectedTarget.getName())) {
checkForMissingRegistry = Capability.CONTAINER_IMAGE_S2I.equals(activeContainerImageCapability);
checkForMissingRegistry = Capability.CONTAINER_IMAGE_S2I.equals(activeContainerImageCapability)
|| Capability.CONTAINER_IMAGE_OPENSHIFT.equals(activeContainerImageCapability);
if (!targets.getEntriesSortedByPriority().get(0).getKind().equals("DeploymentConfig")) {
checkForNamespaceGroupAlignment = true;
}

} else if (MINIKUBE.equals(selectedTarget.getName())) {
checkForMissingRegistry = false;
}
Expand All @@ -156,6 +163,16 @@ private DeploymentTargetEntry determineDeploymentTarget(
+ " because \"quarkus.container-image.registry\" has not been set. The Kubernetes deployment will only work properly"
+ " if the cluster is using the local Docker daemon. For that reason 'ImagePullPolicy' is being force-set to 'IfNotPresent'.");
}

//This might also be applicable in other scenarios too (e.g. Knative on Openshift), so we might need to make it slightly more generic.
if (checkForNamespaceGroupAlignment) {
Config config = Config.autoConfigure(null);
if (config.getNamespace() != null && !config.getNamespace().equals(containerImageInfo.getGroup())) {
log.warn("An openshift deployment was requested, but the container image group:" + containerImageInfo.getGroup()
+ " is not aligned with the currently selected project:" + config.getNamespace() + "."
+ "it is strongly advised to align them, or else the image might not be reachable.");
}
}
return selectedTarget;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@

package io.quarkus.kubernetes.deployment;

import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG_GROUP;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG_VERSION;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_GROUP;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_VERSION;

import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -19,6 +26,11 @@ public static enum OpenshiftFlavor {
v4;
}

public static enum DeploymentResourceKind {
Deployment,
DeploymentConfig
}

/**
* The OpenShift flavor / version to use.
* Older versions of OpenShift have minor differrences in the labels and fields they support.
Expand All @@ -27,6 +39,13 @@ public static enum OpenshiftFlavor {
@ConfigItem(defaultValue = "v4")
OpenshiftFlavor flavor;

/**
* The kind of the deployment resource to use.
* Supported values are 'Deployment' and 'DeploymentConfig' defaulting to the later.
*/
@ConfigItem(defaultValue = "DeploymentConfig")
DeploymentResourceKind deploymentKind;

/**
* The name of the group this component belongs too
*/
Expand Down Expand Up @@ -473,4 +492,16 @@ public Optional<String> getAppConfigMap() {
public Optional<ExpositionConfig> getExposition() {
return Optional.of(route);
}

public String getDepoymentResourceGroup() {
return deploymentKind == DeploymentResourceKind.Deployment ? DEPLOYMENT_GROUP : DEPLOYMENT_CONFIG_GROUP;
}

public String getDepoymentResourceVersion() {
return deploymentKind == DeploymentResourceKind.Deployment ? DEPLOYMENT_VERSION : DEPLOYMENT_CONFIG_VERSION;
}

public String getDepoymentResourceKind() {
return deploymentKind == DeploymentResourceKind.Deployment ? DEPLOYMENT : DEPLOYMENT_CONFIG;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_HTTP_PORT;
import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_S2I_IMAGE_NAME;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG_GROUP;
import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG_VERSION;
import static io.quarkus.kubernetes.deployment.Constants.HTTP_PORT;
import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT;
import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT_APP_RUNTIME;
Expand All @@ -31,10 +28,13 @@
import io.dekorate.s2i.config.S2iBuildConfigBuilder;
import io.dekorate.s2i.decorator.AddBuilderImageStreamResourceDecorator;
import io.dekorate.utils.Labels;
import io.fabric8.kubernetes.client.Config;
import io.quarkus.container.image.deployment.ContainerImageConfig;
import io.quarkus.container.image.deployment.util.ImageUtil;
import io.quarkus.container.spi.BaseImageInfoBuildItem;
import io.quarkus.container.spi.ContainerImageInfoBuildItem;
import io.quarkus.container.spi.ContainerImageLabelBuildItem;
import io.quarkus.container.spi.FallbackContainerImageRegistryBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -43,6 +43,7 @@
import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem;
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.kubernetes.deployment.OpenshiftConfig.DeploymentResourceKind;
import io.quarkus.kubernetes.spi.ConfiguratorBuildItem;
import io.quarkus.kubernetes.spi.CustomProjectRootBuildItem;
import io.quarkus.kubernetes.spi.DecoratorBuildItem;
Expand All @@ -61,20 +62,38 @@
public class OpenshiftProcessor {

private static final int OPENSHIFT_PRIORITY = DEFAULT_PRIORITY;
private static final String OPENSHIFT_INTERNAL_REGISTRY = "image-registry.openshift-image-registry.svc:5000";

@BuildStep
public void checkOpenshift(ApplicationInfoBuildItem applicationInfo, OpenshiftConfig config,
BuildProducer<KubernetesDeploymentTargetBuildItem> deploymentTargets,
BuildProducer<KubernetesResourceMetadataBuildItem> resourceMeta) {
List<String> targets = KubernetesConfigUtil.getUserSpecifiedDeploymentTargets();
boolean openshiftEnabled = targets.contains(OPENSHIFT);
deploymentTargets.produce(new KubernetesDeploymentTargetBuildItem(OPENSHIFT, DEPLOYMENT_CONFIG,
DEPLOYMENT_CONFIG_GROUP, DEPLOYMENT_CONFIG_VERSION, OPENSHIFT_PRIORITY,
String kind = config.getDepoymentResourceKind();
String group = config.getDepoymentResourceGroup();
String version = config.getDepoymentResourceVersion();

deploymentTargets.produce(new KubernetesDeploymentTargetBuildItem(OPENSHIFT, kind, group, version, OPENSHIFT_PRIORITY,
openshiftEnabled));
if (openshiftEnabled) {
String name = ResourceNameUtil.getResourceName(config, applicationInfo);
resourceMeta.produce(new KubernetesResourceMetadataBuildItem(OPENSHIFT, DEPLOYMENT_CONFIG_GROUP,
DEPLOYMENT_CONFIG_VERSION, DEPLOYMENT_CONFIG, name));
resourceMeta.produce(new KubernetesResourceMetadataBuildItem(OPENSHIFT, group,
version, kind, name));
}
}

@BuildStep
public void populateInternalRegistry(OpenshiftConfig openshiftConfig, ContainerImageConfig containerImageConfig,
BuildProducer<FallbackContainerImageRegistryBuildItem> containerImageRegistry) {
if (openshiftConfig.deploymentKind == DeploymentResourceKind.Deployment && !containerImageConfig.registry.isPresent()) {
containerImageRegistry.produce(new FallbackContainerImageRegistryBuildItem(OPENSHIFT_INTERNAL_REGISTRY));

// Images stored in internal openshift registry use the following patttern:
// 'image-registry.openshift-image-registry.svc:5000/{{ project name}}/{{ image name }}: {{image version }}.
// So, we need warn users if group does not match currently selected project.
String group = containerImageConfig.group.orElse(null);
Config config = Config.autoConfigure(null);
}
}

Expand Down Expand Up @@ -166,8 +185,17 @@ public List<DecoratorBuildItem> createDecorators(ApplicationInfoBuildItem applic
result.add(new DecoratorBuildItem(new RemoveOptionalFromConfigMapKeySelectorDecorator()));
}

if (config.deploymentKind == DeploymentResourceKind.Deployment) {
result.add(new DecoratorBuildItem(OPENSHIFT, new RemoveDeploymentConfigResourceDecorator(name)));
result.add(new DecoratorBuildItem(OPENSHIFT, new AddDeploymentResourceDecorator(name, config)));
}

if (config.getReplicas() != 1) {
// This only affects DeploymentConfig
result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyReplicasDecorator(name, config.getReplicas())));
// This only affects Deployment
result.add(new DecoratorBuildItem(OPENSHIFT,
new io.dekorate.kubernetes.decorator.ApplyReplicasDecorator(name, config.getReplicas())));
}
image.ifPresent(i -> {
result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyContainerImageDecorator(name, i.getImage())));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

package io.quarkus.kubernetes.deployment;

import java.util.List;
import java.util.stream.Collectors;

import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesListBuilder;

public class RemoveDeploymentConfigResourceDecorator extends Decorator<KubernetesListBuilder> {
private String name;

public RemoveDeploymentConfigResourceDecorator(String name) {
this.name = name;
}

@Override
public void visit(KubernetesListBuilder builder) {
List<HasMetadata> imageStreams = builder.getItems().stream()
.filter(d -> d instanceof HasMetadata)
.map(d -> (HasMetadata) d)
.filter(i -> i.getKind().equals("DeploymentConfig") && i.getMetadata().getName().equalsIgnoreCase(name))
.collect(Collectors.toList());

builder.removeAllFromItems(imageStreams);
}

@Override
public Class<? extends Decorator>[] after() {
return new Class[] { ResourceProvidingDecorator.class };
}
}
Loading