Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2024-04-09 Devnexus - Spring Boot 3 Workshop

2024-04-09 Devnexus - Spring Boot 3 Workshop

Jonatan Ivanov

June 04, 2024
Tweet

More Decks by Jonatan Ivanov

Other Decks in Programming

Transcript

  1. Cover w/ Image Today's Schedule • Morning Break (30 min)

    • Lunch Break (60 min) • Afternoon Break (30 min) • Ends 5pm (ish)
  2. Cover w/ Image Today's Schedule • Machine Setup (Java +

    Docker) • Tour of the Spring Boot Applications • Upgrade to Spring Boot 3.2 • Error handling • Interface Clients
  3. Cover w/ Image Today's Schedule • Observability ◦ Spring and

    Observability ◦ JDBC observability ◦ Prometheus ◦ Grafana • Dev Services • Building Docker images • Questions and Answers
  4. Hands Up 󰢨 • You're a Java developer? • You've

    used Docker? • You've used Spring Boot? • You've used Observability? • You've used OpenTelemetry?
  5. Machine Setup $ git clone https://github.com/jonatan-ivanov/dn24-boot3-workshop $ cd dn24-boot3-workshop $

    java --version $ ./mvnw --version $ ./mvnw package $ cd dog-service $ docker compose up-d $ docker compose ps $ docker compose down 󰢨 Problems? Please let us know!
  6. About the Dog Service Sample • It's a very silly

    application 🤪 • Just enough code to demo what we want! • Spring Boot 2.7 • Java 17 • JPA (Hibernate) • Spring MVC • Spring Security
  7. Checkout the Code $ git clone https://github.com/jonatan-ivanov/dn24-boot3-workshop $ cd dn24-boot3-workshop

    $ git checkout main $ cd dog-service; docker compose up -d; cd .. $ ./mvnw package Import into your favorite IDE
  8. Check Out the Code - pom.xml • Open pom.xml •

    We're using spring-boot-starter-parent 2.7 • The java.version property is 17 • We're using starters for: ◦ actuator, web, data-jpa, and security • We're using PostgreSQL
  9. Check Out the Code - src/main/resources Open src/main/resources Look at

    schema.sql and data.sql files DOG OWNER • id (pk) • name • owner_id (fk) • id (pk) • name 1 *
  10. Check Out the Code - com.example.dogservice.domain • Open com.example.dogservice.domain package

    • Dog and Owner JPA classes map to the schema • DogRepository and OwnerRepository are Spring Data repositories ◦ findByNameIgnoringCase is converted to JQL automatically • InfoLogger is an ApplicationRunner to log info at startup
  11. Check Out the Code - com.example.dogservice.service • Open com.example.dogservice.service package

    • OwnerService uses constructor injection • Simple facade over repositories • Throws custom NoSuchDogOwnerException
  12. Check Out the Code - com.example.dogservice.web • Open com.example.dogservice.web package

    • DogsController ◦ Simple controller used for testing • OwnerController ◦ Delegates to the OwnerService ◦ Deals with NoSuchDogOwnerException ◦ Note: Meta-annotated @RestController and @GetMapping
  13. Check Out the Code - com.example.dogservice.security • Open com.example.dogservice.security package

    • SecurityConfiguration ◦ Defines our web security • SecurityProperties and UserProperties ◦ @ConfigurationProperties maps from values in src/main/resources/application.yml
  14. Check Out the Code - src/main/resources/ • Open src/main/resources/ •

    Inspect application.yml ◦ Defines the database connection ◦ Configures JPA ◦ Enables JMX ◦ Configures server errors ◦ Exposes all actuator endpoints ◦ Enables actuators over HTTP ◦ Customizes a metric name ◦ Defines the in-memory user details
  15. Run the code $ ./mvnw -pl dog-service clean spring-boot:run __

    , ," e`--o (( ( | __,' \\~----------------' \_;/ ( / /) ._______________. ) (( ( (( ( ``-' ``-' … 2024-04-09T10:50:22.372-05:00 INFO 151018 --- [dog-service] [ main] [ ] c.example.dogservice.domain.InfoLogger : Found owners [Scott, Jonatan] 2024-04-09T10:50:22.386-05:00 INFO 151018 --- [dog-service] [ main] [] c.example.dogservice.domain.InfoLogger : Found dogs [Snoopy owned by Scott, Goofy owned by Scott, Clifford owned by Jonatan] … 2024-04-09T10:50:28.260-05:00 INFO 151018 --- [dog-service] [nio-8080-exec-1] [ ] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms …
  16. Why Upgrade? • Micrometer Observability • Docker Compose and Testcontainers

    support • SSL Bundles • Performance improvement ◦ GraalVM Native Images ◦ Checkpoint and Restore (CRaC) ◦ Class Data Sharing (CDS) - coming soon! • Virtual threads (on Java 21)
  17. Why Upgrade? • Dependency upgrades • Other new features and

    fixes • Supported version ◦ Spring Boot 2.7 OSS support reached its end! ◦ Spring Boot 2.7 is the end of 2.x line
  18. Managed Dependencies Project Version Spring Boot 2.7.x 3.0.x 3.2.x Spring

    Framework 5.3.x 6.0.x 6.1.x Spring Security 5.7.x 6.0.x 6.2.x Spring Data JPA 2.7.x 3.0.x 3.2.x Micrometer 1.9.x 1.10.x 1.12.x https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html #appendix.dependency-versions
  19. Checkpoint - DogsController Works $ http "http://localhost:8080/dogs" Generates error JSON

    $ http "http://localhost:8080/dogs?aregood=true" $ http "http://localhost:8080/dogs?aregood=false" $ http "http://localhost:8080/dogs/?aregood=false" Note the trailing slash
  20. Checkpoint - OwnerController Works $ http "http://localhost:8080/owner/scott/dogs" Needs login $

    http -a user:password "http://localhost:8080/owner/scott/dogs" $ http -a user:password "http://localhost:8080/owner/jonatan/dogs" $ http -a user:password "http://localhost:8080/owner/dave/dogs" NoSuchOwnerException mapped to HTTP 404
  21. Checkpoint - Actuator Works $ http -a admin:secret "http://localhost:8080/actuator" Open

    the following in a web browser: http://localhost:8080/actuator http://localhost:8080/actuator/env (search for password) http://localhost:8080/actuator/metrics/ http://localhost:8080/actuator/metrics/http.server.in
  22. Checkpoint - Actuator Over JMX Works $ jconsole Select com.example.dogservice.DogServiceApplication

    Click Connect Select MBeans on the menu Expand org.springframework.boot, Endpoint
  23. Upgrade Spring Boot - Update pom.xml • Edit the pom.xml

    file and change the <version> tag in the parent to 3.2.4 • Things will break and we'll need to fix them!
  24. Upgrade Spring Boot - Fix javax.persistence • For legal reasons

    javax package namespace can't be used by Jakarta • So the Dog and Owner classes now have invalid imports • Replace javax.persistence with jakarta.persistence
  25. Upgrade Spring Boot - Fix Spring Data Repositories • PagingAndSortingRepository

    no longer extends CrudRepository • There are new convenient List... interfaces • Change DogRepository to extend ListCrudRepository and ListPagingAndSortingRepository • Change OwnerRepository to extend ListCrudRepository
  26. Upgrade Spring Boot - Improve InfoLogger • The List... interface

    means findAll now returns List not Iterable • Change InfoLogger log calls to no longer use StreamSupport ◦ this.dogRepository.findAll() ◦ this.ownerRepository.findAll()
  27. Upgrade Spring Boot - Fix SecurityConfiguration • We suppressed the

    WebSecurityConfigurerAdapter deprecation • That class has gone so we need to fix things • Remove extends WebSecurityConfigurerAdapter
  28. Upgrade Spring Boot - Fix SecurityConfiguration • Replace configure method

    with a SecurityFilterChain bean definition @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { [ body of existing configure method ] http.httpBasic(Customizer.withDefaults()); return http.build(); }
  29. Upgrade Spring Boot - Fix SecurityConfiguration • The mvcMatchers method

    has gone • Replace mvcMatchers with requestMatchers
  30. Upgrade Spring Boot - Fix SecurityConfiguration • The http.authorizeRequests method

    is deprecated • Replace http.authorizeRequests with http.authorizeHttpRequests
  31. Upgrade Spring Boot - Fix @ConstructorBinding imports • @ConstructorBinding has

    moved and we need to fix that • Update SecurityProperties and UserProperties ◦ Replace import org.springframework.boot.context.properties.Const ructorBinding ◦ With import org.springframework.boot.context.properties.bind. ConstructorBinding
  32. Upgrade Spring Boot - Remove @ConstructorBinding • Read the release

    notes on the wiki • Remove @ConstructorBinding from SecurityProperties and UserProperties
  33. Upgrade Spring Boot - Use Records • Since we're using

    Java 17 we can use records • Replace SecurityProperties and UserProperties with records • Fix SecurityConfiguration
  34. $ ./mvnw -pl dog-service clean spring-boot:run $ http "http://localhost:8080/dogs?aregood=true" $

    http -a user:password "http://localhost:8080/owner/scott/dogs" Checkpoint - The application works
  35. Upgrade Spring Boot - Fix Deprecated Properties • Depending on

    your IDE you might see warnings in your application.yml • To help fix those, we can use the Spring Boot property migrator
  36. Upgrade Spring Boot - Fix Deprecated Properties • Update pom.xml

    and add spring-boot-properties-migrator <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-properties-migrator</artifactId> <scope>runtime</scope> </dependency>
  37. Upgrade Spring Boot - Fix Deprecated Properties • Run the

    application and it will report deprecations and replacements $ ./mvnw -pl dog-service clean spring-boot:run … The use of configuration keys that have been renamed was found in the environment: Property source 'Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'': Key: management.metrics.web.server.request.metric-name Line: 25 Replacement: management.observations.http.server.requests.name
  38. Upgrade Spring Boot - Fix Deprecated Properties • Replace management.metrics.web.request.metric-name

    with management.observations.http.requests.name • Check the result in actuator
  39. Upgrade Spring Boot - Fix Deprecated Properties • The properties

    migrator has a startup cost, so once we're done we can remove it • Update pom.xml and remove spring-boot-properties-migrator
  40. Checkpoint - The application works $ ./mvnw -pl dog-service clean

    spring-boot:run • Check the actuator env endpoint ◦ Notice that all values are hidden with ****** • Try JMX actuator with jconsole ◦ Notice that only health is available
  41. Upgrade Spring Boot - Expose JMX • Actuator JMX configuration

    is now aligned with web and needs to be exposed • Add management.endpoints.jmx.exposure.include with a value of "*" • Run the app again and check JMX in jconsole
  42. Upgrade Spring Boot - Always Show Env Values • To

    show env values again we need to set a property • Add management.endpoint.env.show-values with a value of always
  43. Upgrade Spring Boot - Sanitize Env Values • Spring Boot

    3.0 now leaves all sanitization to you! • Add a SanitizingFunction to DogServiceApplication @Bean SanitizingFunction sanitizingFunction() { return (data) -> (data.getKey().toLowerCase().contains("password")) ? data.withValue(SanitizableData.SANITIZED_VALUE) : data; }
  44. Upgrade Spring Boot - Everything works as expected! $ http

    "http://localhost:8080/dogs" $ http -a user:password "http://localhost:8080/owner/dave/dogs" Notice the specifics of the error JSON
  45. New Features - Problem Details • Spring Boot 3.0 supports

    problem details (RFC 7807) • We need to enable it for errors handled by Spring MVC • Set spring.mvc.problemdetails.enabled to true
  46. New Features - Problem Details • We can use org.springframework.http.ProblemDetail

    in our own code • Edit OwnerController.onNoSuchDogOwner @ExceptionHandler ProblemDetail onNoSuchDogOwner(NoSuchDogOwnerException ex) { ProblemDetail details = ProblemDetail.forStatus(HttpStatus.NOT_FOUND); details.setProperty("ownername", ex.getName()); return details; }
  47. New Features - Problem Details $ http "http://localhost:8080/dogs" $ http

    -a user:password "http://localhost:8080/owner/dave/dogs" Notice the specifics of the error JSON
  48. At Scale - Automate Upgrades • Spring Health Assessment •

    OpenRewrite recipes • Spring Boot Migrator
  49. About the Dog Client Sample • It's another very silly

    application 🤪 • Just enough code to demo what we want! • API using HttpServiceProxy • Another MVC controller • Some configuration
  50. HTTP Client Interfaces: interfaces + metadata • Open com.example.dogclient.api.Api @GetExchange("/dogs")

    DogsResponse dogs( @RequestParam(name="aregood") boolean areGood); @GetExchange("/owner/{name}/dogs") List<String> ownedDogs(@PathVariable String name);
  51. HTTP Client Interfaces - Building the client • Open com.example.dogclient.DogClientApplication

    WebClient webClient = webClientBuilder.baseUrl("…").build(); WebClientAdapter adapter = WebClientAdapter.create(webClient); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); return factory.createClient(Api.class);
  52. HTTP Client Interfaces - Using the client // Signature DogsResponse

    dogs(boolean areGood); List<String> ownedDogs(String name); // Usage api.dogs(true); api.ownedDogs("Scott");
  53. ApplicationRunner • Open com.example.dogclient.DogClientApplication • Prints information when the application

    starts • Functional interface called when the app starts: void run(ApplicationArguments args) throws Exception
  54. Run the application - Check the startup output $ ./mvnw

    -pl dog-service clean spring-boot:run $ ./mvnw -pl dog-client clean spring-boot:run 2024-04-09T13:26:07.471-05:00 INFO 1618241 --- [dog-client] [ main] [ ] c.e.dogclient.DogClientApplication : Started DogClientApplication in 1.505 seconds (process running for 1.643) DogsResponse[message=We <3 dogs!!!] [Snoopy, Goofy]
  55. What is Observability? How well we can understand the internals

    of a system based on its outputs (Providing meaningful information about what happens inside) (Data about your app)
  56. Why do we need Observability? Today's systems are increasingly complex

    (cloud) (Death Star Architecture, Big Ball of Mud)
  57. Environments can be chaotic You turn a knob here a

    little and apps are going down there We need to deal with unknown unknowns We can’t know everything Things can be perceived differently by observers Everything is broken for the users but seems ok to you Why do we need Observability?
  58. Why do we need Observability? (business perspective) Reduce lost revenue

    from production incidents Lower mean time to recovery (MTTR) Require less specialized knowledge Shared method of investigating across system Quantify user experience Don't guess, measure!
  59. Logging What happened (why)? Emitting events Metrics What is the

    context? Aggregating data Distributed Tracing Why happened? Recording causal ordering of events Logging - Metrics - Distributed Tracing
  60. Examples Latency Logging Processing took 140ms Metrics P99.999: 140ms Max:

    150 ms Distributed Tracing DB was slow (lot of data was requested) Error Logging Processing failed (stacktrace?) Metrics The error rate is 0.001/sec 2 errors in the last 30 minutes Distributed Tracing DB call failed (invalid input)
  61. SLF4J with Logback comes pre-configured SLF4J (Simple Logging Façade for

    Java) Simple API for logging libraries Logback Natively implements the SLF4J API If you want Log4j2 instead of Logback: - spring-boot-starter-logging + spring-boot-starter-log4j2 Logging with JVM/Spring: SLF4J + Logback
  62. Setup Logging - Add org property • We will need

    something that we can use to query: ◦ All of our apps (spring.application.org) ◦ Only one app (spring.application.name) ◦ Only one instance (we only have one instance/app) spring: application: name: dog-service org: petclinic
  63. Setup Logging - Add Loki4J • Copy From: dog-client/src/main/resources/logback-spring.xml To:

    dog-service/src/main/resources/logback-spring.xml • Add dependency to pom.xml <dependency> <groupId>com.github.loki4j</groupId> <artifactId>loki-logback-appender</artifactId> <version>1.5.1</version> </dependency>
  64. Setup Logging - Do we have logs? • Got to

    Grafana: http://localhost:3000 • Choose Explore, then Loki from the drop down • Search for application = dog-service • Search for org = petclinic • We will get back to our logs later
  65. Metrics with JVM/Spring: Micrometer Dimensional Metrics library on the JVM

    Like SLF4J, but for metrics API is independent of the configured metrics backend Supports many backends Comes with spring-boot-actuator Spring projects are instrumented using Micrometer Many third-party libraries use Micrometer
  66. Supported metrics backends/formats/protocols Ganglia Graphite Humio InfluxDB JMX KairosDB New

    Relic (/actuator/metrics) OpenTSDB OTLP Prometheus SignalFx Stackdriver (GCP) StatsD Wavefront (VMware) AppOptics Atlas Azure Monitor CloudWatch (AWS) Datadog Dynatrace Elastic
  67. • Add logs (application logs) • Add metrics ◦ Increment

    Counters ◦ Start/Stop Timers • Add Distributed Tracing ◦ Start/Stop Spans ◦ Log Correlation ◦ Context Propagation You want to instrument your application…
  68. Observation API (since Micrometer 1.10) Observation observation = Observation.start("talk",registry); try

    { // TODO: scope Thread.sleep(1000); } catch (Exception exception) { observation.error(exception); throw exception; } finally { // TODO: attach tags (key-value) observation.stop(); }
  69. Observation API (since Micrometer 1.10) ObservationRegistry registry = ObservationRegistry.create(); registry.observationConfig()

    .observationHandler(new MeterHandler(...)) .observationHandler(new TracingHandler(...)) .observationHandler(new LoggingHandler(...)) .observationHandler(new AuditEventHandler(...));
  70. Setup Metrics - Add the org to Observations and Metrics

    application.yml management: observations: key-values: org: ${spring.application.org} metrics: tags: application: ${spring.application.name} org: ${spring.application.org}
  71. Setup Metrics - Let’s check Metrics • http://localhost:8080/actuator/prometheus • 401

    🧐 • Prometheus? http://localhost:9090/targets • Spring Security! 👀 • Let’s disable it, what could go wrong!? 😈 • Everything, please don’t do this in prod! • Except if you want everyone know about it. 😈
  72. Setup Metrics - Remove Observation name customization • We are

    going to depend on default behavior • So let’s remove the custom http observation naming • Remove/comment out # observations: # http: # server: # requests: # name: "http.server.in"
  73. Setup Metrics - Add histogram support for http metrics •

    We want to see the latency distributions on our dashboards • We want to calculate percentiles (P99?) management: metrics: distribution: percentiles-histogram: # all: true http.server.requests: true
  74. Setup Metrics - Let’s check the HTTP and JVM metrics

    • Let’s check /actuator/metrics /actuator/metrics/{metricName} /actuator/metrics/{metricName}?tag=key:value • Let’s write a Prometheus query (HELP.md) sum by (application) (rate(http_server_requests_seconds_count[5m])) • Let’s check the dashboards: go to Grafana, then Browse ◦ Spring Boot Statistics ◦ Dogs
  75. Distributed Tracing with JVM/Spring Boot 2.x: Spring Cloud Sleuth Boot

    3.x: Micrometer Tracing (Sleuth w/o Spring dependencies) Provide an abstraction layer on top of tracing libraries - Brave (OpenZipkin), default - OpenTelemetry (CNCF), experimental Instrumentation for Spring Projects, 3rd party libraries, your app Support for various backends
  76. Setup Distributed Tracing - Add Micrometer Tracing <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId>

    </dependency> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-brave</artifactId> </dependency>
  77. Setup Distributed Tracing - Setup log correlation • If you

    are on Spring Boot 3.1 or above, this is not needed • If you are on 3.0, you need to set logging.pattern.level • We are on 3.2! logging: level: org.springframework.web.servlet.DispatcherServlet: DEBUG
  78. Setup Distributed Tracing - Let’s look at correlated logs 2024-03-22T20:13:21.588Z

    DEBUG 2167 --- [dog-service] [http-nio-8090-exec-5] [65fde66134624d949e80e0d3241ed138-9e80e0d3241ed138] o.s.web.servlet.DispatcherServlet: Completed 200 OK
  79. Setup Distributed Tracing - Let’s look at some traces •

    Go to Grafana, then Explore and choose Tempo • Terminology ◦ Span ◦ Trace ◦ Tags ◦ Annotations
  80. Setup Observations - Disable Spring Security Observations SecurityConfiguration.java @Bean ObservationPredicate

    noSpringSecurityObservations() { return (name,ctx)-> !name.startsWith("spring.security."); }
  81. Setup Observations - Disable Actuator Observations ActuatorConfiguration.java if (name.equals("http.server.requests") &&

    ctx instanceof ServerRequestObservationContext sc) { return !sc.getCarrier() .getRequestURI() .startsWith("/actuator"); } else return true;
  82. Setup Observations - Enable JDBC Observations Tadaya Tsuyukubo 😎 net.ttddyy.observation:datasource-micrometer-spring-boot

    (1.0.3) jdbc: datasource-proxy: include-parameter-values: true query: enable-logging: true log-level: INFO
  83. Interoperability - How to check Exemplars • Exemplars are only

    available if you request the OpenMetrics format • Your browser does not do this http :8081/actuator/prometheus / 'Accept: application/openmetrics-text;version=1.0.0' | grep trace_id
  84. Setup Observations - Log error and signal it OwnerController.java ProblemDetail

    onNoSuchDogOwner( HttpServletRequest request, NoSuchDogOwnerException ex) { logger.error("Ooops!", ex); ServerHttpObservationFilter .findObservationContext(request) .ifPresent(context -> context.setError(ex));
  85. ObservationFilter tempoErrorFilter() { return context -> { if (context.getError() !=

    null) { context.addHighCardinalityKeyValue( KeyValue.of("error", "true") ); context.addHighCardinalityKeyValue( KeyValue.of( "errorMsg", context.getError().getMessage()) ); } return context; }; } Setup Observations - Hack error reporting for Tempo
  86. Setup Observations - Hack DB tags for ServiceGraph if(ctx instanceof

    DataSourceBaseContext dsCtx){ ctx.addHighCardinalityKeyValue( KeyValue.of( "db.name", dsCtx.getRemoteServiceName() ) ); }
  87. Connection Details Abstraction • Provide key details of a connection

    from an application to an external service ◦ JDBC ◦ R2DBC ◦ MongoDB ◦ Redis ◦ Elasticsearch ◦ Neo4j ◦ Kafka ◦ RabbitMQ ◦ etc
  88. Docker Compose Support • Automatically manages the Docker Compose lifecycle

    (up, down) when the Spring Boot application starts and stops • Inspects started containers and detects known service types ◦ MariaDB ◦ MySQL ◦ Oracle ◦ Postgres ◦ SQL Server ◦ MongoDB ◦ Redis ◦ Elasticsearch ◦ Neo4j ◦ Kafka ◦ RabbitMQ ◦ etc
  89. Docker Compose Support • In dog-service, update pom.xml and add

    dependency on spring-boot-docker-compose <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-docker-compose</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency>
  90. Running with Docker Compose Support $ cd dog-service ; docker

    compose down ; cd .. $ ./mvnw -pl dog-service clean spring-boot:run INFO 63485 --- [dog-service] o.s.b.d.c.l.DockerComposeLifecycleManager : Using Docker Compose file '/home/projects/dn24-boot3-workshop/dog-service/docker-compose.yml' INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container tempo Created INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container postgres Created INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container loki Created INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container prometheus Created INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container grafana Created INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container loki Starting INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container grafana Starting INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container prometheus Starting INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container tempo Starting INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container postgres Starting INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container grafana Started INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container postgres Started INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container prometheus Started INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container tempo Started INFO 63485 --- [dog-service] o.s.boot.docker.compose.core.DockerCli : Container loki Started
  91. Docker Compose Support Internals $ docker compose ps --format=json [

    … { "ID": "b0568074e2fc767d1777a23589ace22cfd38750eb3caa6e4099c833fc50d69d4", "Name": "postgres", "Image": "postgres:16.2-alpine3.19", "Command": "docker-entrypoint.sh postgres", "Project": "dog-service", "Service": "postgres", … }, … ]
  92. [ { … "Config": { "Env": [ "POSTGRES_USER=dog", "POSTGRES_PASSWORD=woof", "POSTGRES_DB=dog-db",

    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=en_US.utf8", "PG_MAJOR=16", "PG_VERSION=16.2", "PGDATA=/var/lib/postgresql/data" ], } … ] Docker Compose Support Internals $ docker inspect b0568… --format=json
  93. [ { … "Config": { "Env": [ "POSTGRES_USER=dog", "POSTGRES_PASSWORD=woof", "POSTGRES_DB=dog-db",

    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "LANG=en_US.utf8", "PG_MAJOR=16", "PG_VERSION=16.2", "PGDATA=/var/lib/postgresql/data" ], } … ] Docker Compose Connection Details
  94. Additional Docker Compose Support Features • Official and Bitnami images

    are supported automatically ◦ Additional images can be supported with a label in compose file • Docker compose lifecycle can be customized ◦ Start (up, start) and stop (down, stop) commands ◦ Start and Stop, Start only, and none lifecycle options • Docker compose profiles can be activated ◦ Distinct from Spring profiles, but the concepts can be combined • Container readiness checks can be customized https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.docker-compose
  95. Testcontainers • Allows containers to be created and started using

    Java APIs • Eases the development of integration tests • Modules are provided for many common services that Spring Boot provides auto-configuration for • Containers can be inspected once they are started to get necessary connection information https://java.testcontainers.org/ https://testcontainers.com/modules/
  96. Integration Testing with Testcontainers • Update pom.xml and add Testcontainers

    dependencies <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-testcontainers</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency>
  97. Integration Testing with Testcontainers • Add an integration test class

    RepositoryIntegrationTests @Testcontainers @SpringBootTest public class RepositoryIntegrationTests { @Container @ServiceConnection static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16.2-alpine3.19"); @Autowired OwnerRepository ownerRepository; @Autowired DogRepository dogRepository; @Test void getDogs() { Owner owner = this.ownerRepository.findByNameIgnoringCase("Jonatan"); assertThat(owner.getName()).isEqualTo("Jonatan"); List<Dog> dogs = this.dogRepository.findByOwner(owner); assertThat(dogs).hasSize(1); assertThat(dogs).extracting("name").containsExactly("Clifford"); } }
  98. Integration Testing with Testcontainers $ ./mvnw -pl dog-service clean package

    … [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.example.dogservice.domain.RepositoryIntegrationTests … [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.077 s -- in com.example.dogservice.domain.RepositoryIntegrationTests
  99. Integration Testing with Testcontainers • Add an integration test class

    DogServiceIntegrationTests @Testcontainers @SpringBootTest public class DogServiceIntegrationTests { @Container @ServiceConnection static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16.2-alpine3.19"); @Autowired OwnerService ownerService; @Test void getDogsByOwner() { List<String> dogNames = this.ownerService.getOwnedDogNames("Scott"); assertThat(dogNames).hasSize(2); assertThat(dogNames).containsExactlyInAnyOrder("Snoopy", "Goofy"); dogNames = this.ownerService.getOwnedDogNames("Jonatan"); assertThat(dogNames).hasSize(1); assertThat(dogNames).containsExactlyInAnyOrder("Clifford"); } }
  100. Integration Testing with Testcontainers $ ./mvnw -pl dog-service clean package

    … [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] [INFO] Running com.example.dogservice.service.DogServiceIntegrationTests … [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.251 s -- in com.example.dogservice.service.DogServiceIntegrationTests
  101. Cloud Native Buildpacks • Cloud Native Computing Foundation project •

    Specifications and APIs for building modular and composable buildpacks that contribute to building OCI images • Implementations provided by Paketo, Google, Heroku, and others • Alternative to Dockerfiles and Jib https://buildpacks.io/
  102. Why Cloud Native Buildpacks? • No Dockerfiles • Consistent image

    base layers (OS, JRE) • Rebasing of images without re-creating all layers • Automatic Software Bill-of-Materials (SBOM) generation • Broad community support https://www.youtube.com/watch?v=TX_UXuzqVGQ
  103. Spring Boot CNB Integration • Invokes a CNB builder, passing

    built artifacts • Maven ◦ ./mvnw spring-boot:build-image • Gradle ◦ ./gradlew bootBuildImage • Uses Paketo buildpacks by default • Requires a Docker-compatible daemon running locally
  104. Paketo Buildpacks • java ◦ Installs Bellsoft Liberica JRE by

    default ◦ Can be configured to install Adoptium, Dragonwell, Corretto, Zulu, OpenJ9, GraalVM, Oracle, Microsoft OpenJDK instead ◦ Can install a JRE or JDK • java-native ◦ Builds a GraalVM native image when detecting a Spring Boot AOT-enabled application https://paketo.io https://paketo.io/docs/reference/java-reference/ https://paketo.io/docs/howto/java/
  105. Paketo Buildpacks • executable-jar ◦ Understands Spring Boot runnable JAR

    format • spring-boot ◦ Uses classpath.idx to ensure consistent class ordering ◦ Uses layers.idx for customizable layer configuration
  106. Image Layers with Dockerfiles OS JDK Application OS JDK Application

    (old) Application OS JDK Application (old) Application Application (old) rebuild rebuild Dockerfile FROM openjdk:17-jdk-alpine COPY target/dog-service-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java","-jar","/app.jar"] https://docs.docker.com/build/guide/layers/
  107. Image Layers with Spring Boot and Paketo OS JRE Dependencies

    rebuild rebuild Loader Application OS JRE Dependencies Loader Application (old) Application OS JRE Dependencies Loader Application (old) Application Application (old) BOOT-INF/layers.idx - "dependencies": - "BOOT-INF/lib/" - "spring-boot-loader": - "org/" - "snapshot-dependencies": - "application": - "BOOT-INF/classes/" - "BOOT-INF/classpath.idx" - "BOOT-INF/layers.idx" - "META-INF/" Least likely to change https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ #container-images.efficient-images.layering Most likely to change
  108. $ Update pom.xml to configure the builder <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId>

    <configuration> <image> <builder>paketobuildpacks/builder-jammy-buildpackless-tiny</builder> <buildpacks> <buildpack>paketobuildpacks/java:beta</buildpack> </buildpacks> </image> </configuration> </plugin> Building an OCI Image on a Mac M1/M2
  109. Build an OCI image $ ./mvnw -pl dog-service spring-boot:build-image [INFO]

    Building image 'docker.io/library/dog-service:0.0.1-SNAPSHOT' … [INFO] > Running creator [INFO] [creator] ===> DETECTING [INFO] [creator] 6 of 26 buildpacks participating [INFO] [creator] paketo-buildpacks/ca-certificates 3.6.8 [INFO] [creator] paketo-buildpacks/bellsoft-liberica 10.5.3 [INFO] [creator] paketo-buildpacks/syft 1.45.0 [INFO] [creator] paketo-buildpacks/executable-jar 6.8.4 [INFO] [creator] paketo-buildpacks/dist-zip 5.6.9 [INFO] [creator] paketo-buildpacks/spring-boot 5.27.10 … [INFO] Successfully built image 'docker.io/library/dog-service:0.0.1-SNAPSHOT'
  110. Run the image $ docker run -p 8080 dog-service:0.0.1-SNAPSHOT org.postgresql.util.PSQLException:

    Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections. at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl (ConnectionFactoryImpl.java:342) at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:54) at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:263) at org.postgresql.Driver.makeConnection(Driver.java:443) at org.postgresql.Driver.connect(Driver.java:297)
  111. Run the image $ cd dog-service ; docker compose up

    -d $ docker run -p 8080 --network dog-service_default --env SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/ dog-db dog-service:0.0.1-SNAPSHOT 2024-04-09T10:50:22.372-05:00 INFO 151018 --- [dog-service] [ main] [ ] c.example.dogservice.domain.InfoLogger : Found owners [Scott, Jonatan] 2024-04-09T10:50:22.386-05:00 INFO 151018 --- [dog-service] [ main] [] c.example.dogservice.domain.InfoLogger : Found dogs [Snoopy owned by Scott, Goofy owned by Scott, Clifford owned by Jonatan]
  112. Run the image • Add dog-service to dog-service/docker-compose.yml dog-service: container_name:

    dog-service image: library/dog-service:0.0.1-SNAPSHOT ports: - 8080:8080 environment: - "SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/dog-db"
  113. Run the image $ cd dog-service ; docker compose up

    dog-service | 2024-04-09T22:37:08.493Z INFO 1 --- [dog-service] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '' dog-service | 2024-04-09T22:37:08.504Z INFO 1 --- [dog-service] [ main] c.e.dogservice.DogServiceApplication : Started DogServiceApplication in 3.718 seconds (process running for 3.953) dog-service | 2024-04-09T22:37:08.587Z INFO 1 --- [dog-service] [ main] c.example.dogservice.domain.InfoLogger : Found owners [Scott, Jonatan] dog-service | 2024-04-09T22:37:08.597Z INFO 1 --- [dog-service] [ main] c.example.dogservice.domain.InfoLogger : Found dogs [Snoopy owned by Scott, Goofy owned by Scott, Clifford owned by Jonatan]
  114. Cloud Native Buildpacks • CNB integration in Spring Boot plugins

    are useful for fast iteration and inner-loop development • Production images should be built in a CI platform that supports CNB ◦ Tekton ◦ Tanzu Build Service ◦ GitHub Actions ◦ AWS CodeBuild ◦ Google Cloud Build
  115. Q&A