[ENGDOCS-864] Update java language-specific guide (#15269)

* Updated java guide

* Update language/java/develop.md

Co-authored-by: Guillaume Lours <guillaume@lours.me>

* Update language/java/develop.md

Co-authored-by: Guillaume Lours <guillaume@lours.me>

* moved multi-stage Dockerfile to develop section

Co-authored-by: Guillaume Lours <guillaume@lours.me>
This commit is contained in:
Craig Osterhout 2022-08-03 13:55:57 -07:00 committed by GitHub
parent 185660afa9
commit 4231acfd40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 83 deletions

View File

@ -81,12 +81,10 @@ we would like to use for our application.
```dockerfile
# syntax=docker/dockerfile:1
FROM openjdk:16-alpine3.13
FROM eclipse-temurin:17-jdk-jammy
```
Docker images can be inherited from other images. For this guide, we use the
official `openjdk` image from Docker Hub with Java JDK that already has all the
tools and packages that we need to run a Java application.
Docker images can be inherited from other images. For this guide, we use Eclipse Termurin, one of the most popular official images with a build-worthy JDK.
To make things easier when running the rest of our commands, lets set the image's
working directory. This instructs Docker to use this path as the default location
@ -113,15 +111,15 @@ COPY mvnw pom.xml ./
```
Once we have our `pom.xml` file inside the image, we can use the `RUN` command
to execute the command `mvnw dependency:go-offline`. This works exactly the same
to execute the command `mvnw dependency:resolve`. This works exactly the same
way as if we were running `mvnw` (or `mvn`) dependency locally on our machine,
but this time the dependencies will be installed into the image.
```dockerfile
RUN ./mvnw dependency:go-offline
RUN ./mvnw dependency:resolve
```
At this point, we have an Alpine version 3.13 image that is based on OpenJDK version 16, and we have also installed our dependencies. The next thing we need to do is to add our source code into the image. Well use the `COPY` command just like we did with our `pom.xml` file above.
At this point, we have an Eclipse Termurin image that is based on OpenJDK version 17, and we have also installed our dependencies. The next thing we need to do is to add our source code into the image. Well use the `COPY` command just like we did with our `pom.xml` file above.
```dockerfile
COPY src ./src
@ -138,13 +136,13 @@ Here's the complete Dockerfile.
```dockerfile
# syntax=docker/dockerfile:1
FROM openjdk:16-alpine3.13
FROM eclipse-temurin:17-jdk-jammy
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
RUN ./mvnw dependency:resolve
COPY src ./src
@ -176,7 +174,7 @@ $ docker build --tag java-docker .
```console
Sending build context to Docker daemon 5.632kB
Step 1/7 : FROM java:3.7-alpine
Step 1/7 : FROM eclipse-temurin:17-jdk-jammy
Step 2/7 : WORKDIR /app
...
Successfully built a0bb458aabd0
@ -195,7 +193,7 @@ REPOSITORY TAG IMAGE ID CREATED SIZ
java-docker latest b1b5f29f74f0 47 minutes ago 567MB
```
You should see at least the we just built `java-docker:latest`.
You should see at least the image we just built `java-docker:latest`.
## Tag images

View File

@ -44,7 +44,7 @@ $ docker run -it --rm -d -v mysql_data:/var/lib/mysql \
--name mysqlserver \
-e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic \
-e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic \
-p 3306:3306 mysql:8.0.23
-p 3306:3306 mysql:8.0
```
Okay, now that we have a running MySQL, lets update our Dockerfile to activate the MySQL Spring profile defined in the application and switch from an in-memory H2 database to the MySQL server we just created.
@ -55,7 +55,7 @@ We only need to add the MySQL profile as an argument to the `CMD` definition.
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql"]
```
Let's build our image
Let's build our image.
```console
$ docker build --tag java-docker .
@ -85,30 +85,64 @@ You should receive the following json back from our service.
{"vetList":[{"id":1,"firstName":"James","lastName":"Carter","specialties":[],"nrOfSpecialties":0,"new":false},{"id":2,"firstName":"Helen","lastName":"Leary","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":3,"firstName":"Linda","lastName":"Douglas","specialties":[{"id":3,"name":"dentistry","new":false},{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":2,"new":false},{"id":4,"firstName":"Rafael","lastName":"Ortega","specialties":[{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":1,"new":false},{"id":5,"firstName":"Henry","lastName":"Stevens","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":6,"firstName":"Sharon","lastName":"Jenkins","specialties":[],"nrOfSpecialties":0,"new":false}]}
```
## Multi-stage Dockerfile for development
Lets take a look at updating our Dockerfile to produce a final image which is ready for production as well as a dedicated step to produce a development image.
Well also set up the Dockerfile to start the application in debug mode in the development container so that we can connect a debugger to the running Java process.
Below is a multi-stage Dockerfile that we will use to build our production image and our development image. Replace the contents of your Dockerfile with the following.
```dockerfile
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jdk-jammy as base
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:resolve
COPY src ./src
FROM base as development
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql", "-Dspring-boot.run.jvmArguments='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000'"]
FROM base as build
RUN ./mvnw package
FROM eclipse-temurin:17-jre-jammy as production
EXPOSE 8080
COPY --from=build /app/target/spring-petclinic-*.jar /spring-petclinic.jar
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/spring-petclinic.jar"]
```
We first add a label to the `FROM eclipse-temurin:17-jdk-jammy` statement. This allows us to refer to this build stage in other build stages. Next, we added a new build stage labeled `development`.
We expose port 8000 and declare the debug configuration for the JVM so that we can attach a debugger.
## Use Compose to develop locally
In this section, well create a Compose file to start our `java-docker` and the MySQL database using a single command. Well also set up the Compose file to start the `java-docker` application in debug mode so that we can connect a debugger to the running Java process.
We can now create a Compose file to start our development container and the MySQL database using a single command.
Open the `petclinic` in your IDE or a text editor and create a new file named `docker-compose.dev.yml`. Copy and paste the following commands into the file.
```yaml
version: '3.8'
services:
petclinic:
build:
context: .
ports:
- 8000:8000
- 8080:8080
environment:
- SERVER_PORT=8080
- MYSQL_URL=jdbc:mysql://mysqlserver/petclinic
volumes:
- ./:/app
command: ./mvnw spring-boot:run -Dspring-boot.run.profiles=mysql -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"
petclinic:
build:
context: .
target: development
ports:
- 8000:8000
- 8080:8080
environment:
- SERVER_PORT=8080
- MYSQL_URL=jdbc:mysql://mysqlserver/petclinic
volumes:
- ./:/app
mysqlserver:
image: mysql:8.0.23
mysqlserver:
image: mysql:8.0
ports:
- 3306:3306
environment:
@ -121,14 +155,12 @@ services:
- mysql_data:/var/lib/mysql
- mysql_config:/etc/mysql/conf.d
volumes:
mysql_data:
mysql_config:
mysql_data:
mysql_config:
```
This Compose file is super convenient as we do not have to type all the parameters to pass to the `docker run` command. We can declaratively do that using a Compose file.
We expose port 8000 and declare the debug configuration for the JVM so that we can attach a debugger.
Another really cool feature of using a Compose file is that we have service resolution set up to use the service names. Therefore, we are now able to use `mysqlserver` in our connection string. The reason we use `mysqlserver` is because that is what we've named our MySQL service as in the Compose file.
Now, to start our application and to confirm that it is running properly.

View File

@ -33,18 +33,16 @@ $ docker run -it --rm --name springboot-test java-docker ./mvnw test
### Multi-stage Dockerfile for testing
Lets take a look at pulling the testing commands into our Dockerfile. Below is a multi-stage Dockerfile that we will use to build our production image and our test image. Add the highlighted lines to your Dockerfile
Lets take a look at pulling the testing commands into our Dockerfile. Below is our updated multi-stage Dockerfile that we will use to build our test image. Replace the contents of your Dockerfile with the following.
```dockerfile
# syntax=docker/dockerfile:1
FROM openjdk:16-alpine3.13 as base
FROM eclipse-temurin:17-jdk-jammy as base
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
RUN ./mvnw dependency:resolve
COPY src ./src
FROM base as test
@ -56,15 +54,14 @@ CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql", "-Dspring-
FROM base as build
RUN ./mvnw package
FROM openjdk:11-jre-slim as production
FROM eclipse-temurin:17-jre-jammy as production
EXPOSE 8080
COPY --from=build /app/target/spring-petclinic-*.jar /spring-petclinic.jar
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/spring-petclinic.jar"]
```
We first add a label to the `FROM openjdk:16-alpine3.13` statement. This allows us to refer to this build stage in other build stages. Next, we added a new build stage labeled `test`. We'll use this stage for running our tests.
We added a new build stage labeled `test`. We'll use this stage for running our tests.
Now lets rebuild our image and run our tests. We will run the `docker build` command as above, but this time we will add the `--target test` flag so that we specifically run the test build stage.
@ -100,18 +97,16 @@ The build output is truncated, but you can see that the Maven test runner was su
This is great. However, we'll have to run two Docker commands to build and run our tests. We can improve this slightly by using a `RUN` statement instead of the `CMD` statement in the test stage. The `CMD` statement is not executed during the building of the image, but is executed when you run the image in a container. When using the `RUN` statement, our tests run when building the image, and stop the build when they fail.
Update your Dockerfile with the highlighted line below.
Update your Dockerfile with the following.
```dockerfile
# syntax=docker/dockerfile:1
FROM openjdk:16-alpine3.13 as base
FROM eclipse-temurin:17-jdk-jammy as base
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
RUN ./mvnw dependency:resolve
COPY src ./src
FROM base as test
@ -123,11 +118,9 @@ CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql", "-Dspring-
FROM base as build
RUN ./mvnw package
FROM openjdk:11-jre-slim as production
FROM eclipse-temurin:17-jre-jammy as production
EXPOSE 8080
COPY --from=build /app/target/spring-petclinic-*.jar /spring-petclinic.jar
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/spring-petclinic.jar"]
```
@ -138,7 +131,7 @@ $ docker build -t java-docker --target test .
[+] Building 27.6s (11/12)
=> CACHED [base 3/6] COPY .mvn/ .mvn
=> CACHED [base 4/6] COPY mvnw pom.xml ./
=> CACHED [base 5/6] RUN ./mvnw dependency:go-offline
=> CACHED [base 5/6] RUN ./mvnw dependency:resolve
=> CACHED [base 6/6] COPY src ./src
=> [test 1/1] RUN ["./mvnw", "test"]
=> exporting to image
@ -172,39 +165,6 @@ $ docker build -t java-docker --target test .
executor failed running [./mvnw test]: exit code: 1
```
### Multi-stage Dockerfile for development
The new version of the Dockerfile produces a final image which is ready for production, but as you can notice, you also have a dedicated step to produce a development container.
```dockerfile
FROM base as development
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql", "-Dspring-boot.run.jvmArguments='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000'"]
```
We can now update our `docker-compose.dev.yml` to use this specific target to build the `petclinic` service and remove the `command` definition as follows:
```dockerfile
services:
petclinic:
build:
context: .
target: development
ports:
- 8000:8000
- 8080:8080
environment:
- SERVER_PORT=8080
- MYSQL_URL=jdbc:mysql://mysqlserver/petclinic
volumes:
- ./:/app
```
Now, let's run the Compose application. You should now see that application behaves as previously and you can still debug it.
```console
$ docker-compose -f docker-compose.dev.yml up --build
```
## Next steps
In this module, we took a look at running tests as part of our Docker image build process.