diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index df83eecefe6081..e50c753d5daace 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -59,6 +59,8 @@ import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.MainClassBuildItem; import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.AppCDSContainerImageBuildItem; +import io.quarkus.deployment.pkg.builditem.AppCDSResultBuildItem; import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.pkg.builditem.JarBuildItem; @@ -85,6 +87,20 @@ public CapabilityBuildItem capability() { return new CapabilityBuildItem(Capability.CONTAINER_IMAGE_JIB); } + // when AppCDS are enabled and a container image build via Jib has been requested, + // we want the AppCDS generation process to use the same JVM as the base image + // in order to make the AppCDS usable by the runtime JVM + @BuildStep(onlyIf = JibBuild.class) + public void appCDS(ContainerImageConfig containerImageConfig, JibConfig jibConfig, + BuildProducer producer) { + + if (!containerImageConfig.build && !containerImageConfig.push) { + return; + } + + producer.produce(new AppCDSContainerImageBuildItem(jibConfig.baseJvmImage)); + } + @BuildStep(onlyIf = { IsNormalNotRemoteDev.class, JibBuild.class }, onlyIfNot = NativeBuild.class) public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig jibConfig, PackageConfig packageConfig, @@ -96,6 +112,7 @@ public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig ji Optional buildRequest, Optional pushRequest, List containerImageLabels, + Optional appCDSResult, BuildProducer artifactResultProducer) { if (!containerImageConfig.build && !containerImageConfig.push && !buildRequest.isPresent() @@ -109,7 +126,8 @@ public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig ji jibContainerBuilder = createContainerBuilderFromLegacyJar(jibConfig, sourceJar, outputTarget, mainClass, containerImageLabels); } else if (packageConfig.isFastJar()) { - jibContainerBuilder = createContainerBuilderFromFastJar(jibConfig, sourceJar, curateOutcome, containerImageLabels); + jibContainerBuilder = createContainerBuilderFromFastJar(jibConfig, sourceJar, curateOutcome, containerImageLabels, + appCDSResult); } else { throw new IllegalArgumentException( "Package type '" + packageType + "' is not supported by the container-image-jib extension"); @@ -240,7 +258,8 @@ private Logger.Level toJBossLoggingLevel(LogEvent.Level level) { */ private JibContainerBuilder createContainerBuilderFromFastJar(JibConfig jibConfig, JarBuildItem sourceJarBuildItem, - CurateOutcomeBuildItem curateOutcome, List containerImageLabels) { + CurateOutcomeBuildItem curateOutcome, List containerImageLabels, + Optional appCDSResult) { Path componentsPath = sourceJarBuildItem.getPath().getParent(); Path appLibDir = componentsPath.resolve(JarResultBuildStep.LIB).resolve(JarResultBuildStep.MAIN); @@ -250,9 +269,10 @@ private JibContainerBuilder createContainerBuilderFromFastJar(JibConfig jibConfi if (jibConfig.jvmEntrypoint.isPresent()) { entrypoint = jibConfig.jvmEntrypoint.get(); } else { - entrypoint = new ArrayList<>(3 + jibConfig.jvmArguments.size()); + List effectiveJvmArguments = determineEffectiveJvmArguments(jibConfig, appCDSResult); + entrypoint = new ArrayList<>(3 + effectiveJvmArguments.size()); entrypoint.add("java"); - entrypoint.addAll(jibConfig.jvmArguments); + entrypoint.addAll(effectiveJvmArguments); entrypoint.add("-jar"); entrypoint.add(JarResultBuildStep.QUARKUS_RUN_JAR); } @@ -325,18 +345,37 @@ private JibContainerBuilder createContainerBuilderFromFastJar(JibConfig jibConfi // we need to manually create each layer // the idea here is that the fast changing libraries are created in a later layer, thus when they do change, // docker doesn't have to create an entire layer with all dependencies - only change the fast ones - jibContainerBuilder.addLayer( - Collections.singletonList( - componentsPath.resolve(JarResultBuildStep.LIB).resolve(JarResultBuildStep.BOOT_LIB)), - workDirInContainer.resolve(JarResultBuildStep.LIB)); + + // the boot lib jars need to preserve the modification time because otherwise AppCDS won't work + FileEntriesLayer.Builder bootLibsLayerBuilder = FileEntriesLayer.builder(); + Path bootLibPath = componentsPath.resolve(JarResultBuildStep.LIB).resolve(JarResultBuildStep.BOOT_LIB); + Files.list(bootLibPath).forEach(lib -> { + try { + bootLibsLayerBuilder.addEntry(lib, + workDirInContainer.resolve(JarResultBuildStep.LIB).resolve(JarResultBuildStep.BOOT_LIB) + .resolve(lib.getFileName()), + Files.getLastModifiedTime(lib).toInstant()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + jibContainerBuilder.addFileEntriesLayer(bootLibsLayerBuilder.build()); + jibContainerBuilder.addLayer(nonFastChangingLibPaths, workDirInContainer.resolve(JarResultBuildStep.LIB).resolve(JarResultBuildStep.MAIN)); jibContainerBuilder.addLayer(new ArrayList<>(fastChangingLibPaths), workDirInContainer.resolve(JarResultBuildStep.LIB).resolve(JarResultBuildStep.MAIN)); } + FileEntriesLayer quarkusRunLayer = FileEntriesLayer.builder().addEntry( + componentsPath.resolve(JarResultBuildStep.QUARKUS_RUN_JAR), + workDirInContainer.resolve(JarResultBuildStep.QUARKUS_RUN_JAR), + Files.getLastModifiedTime(componentsPath.resolve(JarResultBuildStep.QUARKUS_RUN_JAR)).toInstant()).build(); + jibContainerBuilder.addFileEntriesLayer(quarkusRunLayer); + if (appCDSResult.isPresent()) { + jibContainerBuilder + .addLayer(Collections.singletonList(appCDSResult.get().getAppCDS()), workDirInContainer); + } jibContainerBuilder - .addLayer(Collections.singletonList(componentsPath.resolve(JarResultBuildStep.QUARKUS_RUN_JAR)), - workDirInContainer) .addLayer(Collections.singletonList(componentsPath.resolve(JarResultBuildStep.APP)), workDirInContainer) .addLayer(Collections.singletonList(componentsPath.resolve(JarResultBuildStep.QUARKUS)), workDirInContainer) .setWorkingDirectory(workDirInContainer) @@ -356,6 +395,23 @@ private JibContainerBuilder createContainerBuilderFromFastJar(JibConfig jibConfi } } + private List determineEffectiveJvmArguments(JibConfig jibConfig, Optional appCDSResult) { + List effectiveJvmArguments = new ArrayList<>(jibConfig.jvmArguments); + if (appCDSResult.isPresent()) { + boolean containsAppCDSOptions = false; + for (String effectiveJvmArgument : effectiveJvmArguments) { + if (effectiveJvmArgument.startsWith("-XX:SharedArchiveFile")) { + containsAppCDSOptions = true; + break; + } + } + if (!containsAppCDSOptions) { + effectiveJvmArguments.add("-XX:SharedArchiveFile=" + appCDSResult.get().getAppCDS().getFileName().toString()); + } + } + return effectiveJvmArguments; + } + private void setUser(JibConfig jibConfig, JibContainerBuilder jibContainerBuilder) { jibConfig.user.ifPresent(jibContainerBuilder::setUser); } diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/invoker.properties b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/invoker.properties new file mode 100644 index 00000000000000..5a1e6fd59b49cd --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/invoker.properties @@ -0,0 +1 @@ +invoker.goals=clean package -Dquarkus.container-image.build=true diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/pom.xml b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/pom.xml new file mode 100644 index 00000000000000..bb702916d617ff --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + org.acme + container-build-jib-appcds + 0.1-SNAPSHOT + + UTF-8 + 3.0.0-M5 + 1.8 + UTF-8 + 1.8 + + + + + io.quarkus + quarkus-bom + @project.version@ + pom + import + + + + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-container-image-jib + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + io.quarkus + quarkus-maven-plugin + @project.version@ + + + + build + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + native + + + native + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${native.surefire.skip} + + + + io.quarkus + quarkus-maven-plugin + @project.version@ + + + + native-image + + + true + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/src/main/java/org/acme/Hello.java b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/src/main/java/org/acme/Hello.java new file mode 100644 index 00000000000000..ad80766a17747a --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/src/main/java/org/acme/Hello.java @@ -0,0 +1,16 @@ +package org.acme; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/hello") +public class Hello { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } +} \ No newline at end of file diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/src/main/resources/application.properties b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/src/main/resources/application.properties new file mode 100644 index 00000000000000..8c13e8552019a5 --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/src/main/resources/application.properties @@ -0,0 +1,6 @@ +# Configuration file +# key = value +quarkus.package.type=fast-jar +quarkus.package.create-appcds=true +# -Xshare:on ensures that the AppCDS file will be used - if it can't, the JVM terminates +quarkus.jib.jvm-arguments=-Djava.util.logging.manager=org.jboss.logmanager.LogManager,-Xshare:on diff --git a/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/verify.groovy b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/verify.groovy new file mode 100644 index 00000000000000..6b7a0bf65df86c --- /dev/null +++ b/integration-tests/container-image/maven-invoker-way/src/it/container-build-jib-appcds/verify.groovy @@ -0,0 +1,28 @@ +import io.quarkus.deployment.util.ExecUtil + +try { + ExecUtil.exec("docker", "version", "--format", "'{{.Server.Version}}'") +} catch (Exception ignored) { + println "Docker not found" + return +} + +String image = "${System.getProperty("user.name")}/container-build-jib-appcds:0.1-SNAPSHOT" +assert ExecUtil.exec("docker", "images", image) +final maxTimesToCheck = 10 +def i = 0 +assert ExecUtil.exec("docker", "run", "-d", "-p", "8080:8080", image) +while (true) { + try { + def response = "http://localhost:8080/hello".toURL().text + assert response == "hello" + break + } catch (IOException ignored) { + assert (i++) < maxTimesToCheck + try { + Thread.sleep(2000) + } catch (InterruptedException ignored2) { + } + } +} +assert ExecUtil.exec("docker", "rmi", "-f", image)