Skip to content

Commit

Permalink
Merge pull request #558 from groldan/build/appcds
Browse files Browse the repository at this point in the history
Speed up application startup times with AppCDS
  • Loading branch information
groldan authored Oct 22, 2024
2 parents 9edb290 + 2684f12 commit 9c7002b
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 18 deletions.
2 changes: 1 addition & 1 deletion compose/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ services:
environment:
JAVA_OPTS: "${JAVA_OPTS_CONFIG}"
SPRING_PROFILES_ACTIVE: "${CONFIG_SERVER_DEFAULT_PROFILES}"
restart: unless-stopped
# restart: unless-stopped
volumes:
# override with the local copy to test config changes during development
- config:/etc/geoserver
Expand Down
15 changes: 14 additions & 1 deletion src/apps/base-images/jre/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ FROM eclipse-temurin:21-jre

LABEL maintainer="GeoServer PSC <[email protected]>"

ENV JAVA_TOOL_OPTIONS=
# default JVM parameters https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html
# to add JVM parameters use the JAVA_OPTS env variable preferrably
ENV DEFAULT_JAVA_TOOL_OPTIONS="\
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.text=ALL-UNNAMED \
--add-opens=java.desktop/java.awt.font=ALL-UNNAMED \
--add-opens=java.desktop/sun.awt.image=ALL-UNNAMED \
--add-opens=java.desktop/sun.java2d.pipe=ALL-UNNAMED \
--add-opens=java.naming/com.sun.jndi.ldap=ALL-UNNAMED \
-Djava.awt.headless=true"

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS}"
ENV JAVA_OPTS=

# Install the system CA certificates for the JVM :wqnow that we're root
Expand Down
14 changes: 0 additions & 14 deletions src/apps/base-images/spring-boot/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,6 @@ RUN mkdir -p /opt/app/bin

WORKDIR /opt/app/bin

# default JVM parameters https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html
# to add JVM parameters use the JAVA_OPTS env variable preferrably
ENV JAVA_TOOL_OPTIONS="\
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.text=ALL-UNNAMED \
--add-opens=java.desktop/java.awt.font=ALL-UNNAMED \
--add-opens=java.desktop/sun.awt.image=ALL-UNNAMED \
--add-opens=java.desktop/sun.java2d.pipe=ALL-UNNAMED \
--add-opens=java.naming/com.sun.jndi.ldap=ALL-UNNAMED \
-Djava.awt.headless=true"

ENV JAVA_OPTS=
EXPOSE 8080
EXPOSE 8081

Expand Down
12 changes: 12 additions & 0 deletions src/apps/geoserver/gwc/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
12 changes: 12 additions & 0 deletions src/apps/geoserver/restconfig/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
12 changes: 12 additions & 0 deletions src/apps/geoserver/wcs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
12 changes: 12 additions & 0 deletions src/apps/geoserver/webui/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
12 changes: 12 additions & 0 deletions src/apps/geoserver/wfs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
12 changes: 12 additions & 0 deletions src/apps/geoserver/wms/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
12 changes: 12 additions & 0 deletions src/apps/geoserver/wps/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN mkdir /tmp/tmpdatadir
RUN java \
-XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,datadir,offline \
-Dgeosever.backend.data-directory.location=/tmp/tmpdatadir \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
9 changes: 9 additions & 0 deletions src/apps/infrastructure/admin/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@ HEALTHCHECK \
CMD curl -f -s -o /dev/null localhost:8080/actuator/health || exit 1

CMD exec env USER_ID="$(id -u)" USER_GID="$(id -g)" java $JAVA_OPTS org.springframework.boot.loader.JarLauncher

# Execute the CDS training run
RUN java -XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone,offline \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
6 changes: 5 additions & 1 deletion src/apps/infrastructure/config/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ RUN true
COPY --from=builder application/ ./

# Either 'git' or 'native'.
ENV SPRING_PROFILES_ACTIVE=native
ENV SPRING_PROFILES_ACTIVE=native,standalone
# 'native' profile config, use the default config embedded in the Docker image.
# Feel free to override with a mounted volume
ENV CONFIG_NATIVE_PATH=/etc/geoserver
Expand All @@ -36,4 +36,8 @@ ENV CONFIG_GIT_BASEDIR: /tmp/git_config
# avoid stack trace due to jgit not being able of creating a .config dir at $HOME
ENV XDG_CONFIG_HOME: /tmp

# Execute the CDS training run
RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefreshed org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
9 changes: 9 additions & 0 deletions src/apps/infrastructure/discovery/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN java -XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
9 changes: 9 additions & 0 deletions src/apps/infrastructure/gateway/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ COPY --from=builder spring-boot-loader/ ./
#see https://github.com/moby/moby/issues/37965
RUN true
COPY --from=builder application/ ./

# Execute the CDS training run
RUN java -XX:ArchiveClassesAtExit=application.jsa \
-Dspring.context.exit=onRefreshed \
-Dspring.profiles.active=standalone \
org.springframework.boot.loader.JarLauncher
RUN rm -rf /tmp/*

ENV JAVA_TOOL_OPTIONS="${DEFAULT_JAVA_TOOL_OPTIONS} -XX:SharedArchiveFile=application.jsa"
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.app;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;

/**
* Allows to pass a JVM argument to exit the application upon specific {@link
* ApplicationContextEvent application events}, mostly useful to start up an application during the
* Docker image build process to create the AppCDS archive.
*
* <p>Usage: run the application with {@code -Dspring.context.exit=<event>}, where {@code <event>}
* is one of
*
* <ul>
* <li>{@link ExitOn#onPrepared onPrepared}
* <li>{@link ExitOn#onRefreshed onRefreshed}
* <li>{@link ExitOn#onStarted onStarted}
* <li>{@link ExitOn#onReady onReady}
* </ul>
*
* <p>Note Spring Boot 3.2 supports {@code spring.context.exit=onRefresh} as of <a
* href="https://github.com/spring-projects/spring-framework/commit/eb3982b6c25d6c3dd49f6c4cc000c40364916a83">this
* commit</a>, and when we migrate from Spring Boot 2.7 to 3.2+ this will not be necessary most
* probably, although we've added additional events because some applications may fail to start
* without all the machinery in place at different stages. Nonetheless, the new {@code offline}
* embedded spring profile should allow them all to start without spring cloud bus, ACL, etc.
*
* @since 1.9.0
*/
@AutoConfiguration
@ConditionalOnProperty("spring.context.exit")
@Slf4j
public class ExitOnApplicationEventAutoConfiguration {

public enum ExitOn {
/**
* The {@link SpringApplication} is starting up and the {@link ApplicationContext} is fully
* prepared but not refreshed. The bean definitions will be loaded and the {@link
* Environment} is ready for use at this stage.
*
* @see ApplicationPreparedEvent
*/
onPrepared,
/**
* {@code ApplicationContext} gets initialized or refreshed
*
* @see ContextRefreshedEvent
*/
onRefreshed,
/**
* {@code ApplicationContext} has been refreshed but before any {@link ApplicationRunner
* application} and {@link CommandLineRunner command line} runners have been called.
*
* @see ApplicationStartedEvent
*/
onStarted,
/**
* Published as late as conceivably possible to indicate that the application is ready to
* service requests. The source of the event is the {@link SpringApplication} itself, but
* beware all initialization steps will have been completed by then.
*
* @see ApplicationReadyEvent
*/
onReady
}

@Autowired private ApplicationContext appContext;

@Value("${spring.context.exit}")
ExitOn exitOn;

@EventListener(ApplicationPreparedEvent.class)
void exitOnApplicationPreparedEvent(ApplicationPreparedEvent event) {
exit(ExitOn.onStarted, event.getApplicationContext());
}

@EventListener(ContextRefreshedEvent.class)
void exitOnContextRefreshedEvent(ContextRefreshedEvent event) {
exit(ExitOn.onRefreshed, event.getApplicationContext());
}

@EventListener(ApplicationStartedEvent.class)
void exitOnApplicationStartedEvent(ApplicationStartedEvent event) {
exit(ExitOn.onStarted, event.getApplicationContext());
}

@EventListener(ApplicationReadyEvent.class)
void exitOnApplicationReadyEvent(ApplicationReadyEvent event) {
exit(ExitOn.onStarted, event.getApplicationContext());
}

private void exit(ExitOn ifGiven, ApplicationContext applicationContext) {
if (this.exitOn == ifGiven && applicationContext == this.appContext) {
log.warn("Exiting application, spring.context.exit={}", ifGiven);
try {
((ConfigurableApplicationContext) applicationContext).close();
} finally {
System.exit(0);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.geoserver.cloud.app.StartupLoggerAutoConfiguration,\
org.geoserver.cloud.app.ServiceIdFilterAutoConfiguration
org.geoserver.cloud.app.ServiceIdFilterAutoConfiguration,\
org.geoserver.cloud.app.ExitOnApplicationEventAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ eureka:
defaultZone: ${eureka.server.url}
healthcheck:
enabled: false # must only be set to true in application.yml, not bootstrap

---
spring.config.activate.on-profile: offline
spring:
cloud.config.enabled: false
cloud.config.discovery.enabled: false
cloud.discovery.enabled: false
cloud.bus.enabled: false
eureka.client.enabled: false

geoserver.acl.enabled: false
---
spring.config.activate.on-profile: local
# Profile used for local development, so an app launched from the IDE can participate in the cluster.
Expand Down

0 comments on commit 9c7002b

Please sign in to comment.