Giter Site home page Giter Site logo

java-mod-10-building-spring-boot-images's Introduction

Building Spring Boot Images

Learning Goals

  • Understanding Spring Boot native Maven container build process
  • Understanding simple case Dockerfile construction
  • Experiencing iterative development process with container construction

Instructions

Throughout the previous labs, we've been using pre-built Docker images to setup all our environments. While Docker definitely has been helping us in those cases, so far we have yet to create an image from scratch ourselves. Not only is this necessary to do when creating images around custom applications, it is also necessary when needing to modify current applications that may already have their own images provided. As you may be able to recall from the previous labs, there were frequently environmental variables being passed along with the container run to get the desired behavior. This may not be available at all times to do, so customizing application images is frequently needed.

We will start approaching this task by using the built-in image build process available with Maven. After working through that process, we'll dive into using Dockerfile to achieve the same end.

Spring Boot Native Maven Image Build

Open up a terminal window in our previous single node Cassandra Spring Boot project and run the following:

./mvnw spring-boot:build-image
...

[INFO] Successfully built image 'docker.io/library/rest-service-complete:0.0.1-SNAPSHOT'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  53.191 s
[INFO] Finished at: 2022-07-24T17:12:24-05:00
[INFO] ------------------------------------------------------------------------

It's worth noting that the spring-boot:build-image phase does run through the testing phase by default. The -DskipTests flag can be used to disable this if needed.

This same process can be done directly in IntelliJ by enabling this plugin in the Maven pane to execute after build.

You can now take a look at the local Docker images on your workstation, and see that these are now ready for your use

docker images
REPOSITORY                  TAG                    IMAGE ID       CREATED         SIZE
...
rest-service-complete       0.0.1-SNAPSHOT         ceae6ec762be   42 years ago    245MB
paketobuildpacks/builder    base                   303a611351e3   42 years ago    1.18GB

But, if you try to use them as is, they will fail due to Database connectivity errors. We haven't been considering the case of Dockerized applications up until this point, and our workflow of using services on localhost(127.0.0.1) for development work will no longer work once migrated to Docker.

Let's hardcode some new values for now in order to get this up and running quickly.

docker inspect --format '{{ .Name }} {{ .NetworkSettings.Networks.labnetwork.IPAddress }}' cassandra-lab
/cassandra-lab 172.17.0.2

Update application.properties

...
spring.data.cassandra.contact-points=172.17.0.2:9042
...

Rebuild the application and image, and we can now see it running correctly from inside a container

docker run --rm --name spring-boot-lab --network labnetwork -d -p 8080:8080 rest-service-complete:0.0.1-SNAPSHOT
curl "http://localhost:8080/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8080/persistent_greeting"
{"id":9,"content":"Hello, World!"}% 

Hardcoding environment values is a fairly bad practice though, so let us parameterize these to enable very easy reuse in any environment. In the case of Spring Boot, this is an absolutely trivial process as Spring Boot supports overriding arbitrary properties with Environmental Variables. E.g. we can override spring.data.cassandra.contact-points by passing into the container the environmental variable SPRING_DATA_CASSANDRA_CONTACT_POINTS. We can also expand variables in the properties files using the syntax ${ENV_VAR} if anything more complex is needed.

Let's test this out by reverting the connection string to localhost(127.0.0.1) again, and then running a container with the correct environmental variables specified.

# update connection to localhost and rebuild
docker run --rm --name spring-boot-lab --network labnetwork -p 8080:8080 rest-service-complete:0.0.1-SNAPSHOT

# see container crash with connection errors

docker run --rm --name spring-boot-lab --network labnetwork -p 8080:8080 -e SPRING_DATA_CASSANDRA_CONTACT_POINTS="172.17.0.2:9042" rest-service-complete:0.0.1-SNAPSHOT

# see container persisting, and serving traffic correctly

curl "http://localhost:8080/persistent_greeting"
{"id":10,"content":"Hello, World!"}%

At this point, it is now easily possible to launch as many instances of the application as we want, all connecting to the same backend Database system.

docker run --rm --name spring-boot-lab-2 --network labnetwork -d -p 8081:8080 -e SPRING_DATA_CASSANDRA_CONTACT_POINTS="172.17.0.2:9042" rest-service-complete:0.0.1-SNAPSHOT
docker run --rm --name spring-boot-lab-3 --network labnetwork -d -p 8082:8080 -e SPRING_DATA_CASSANDRA_CONTACT_POINTS="172.17.0.2:9042" rest-service-complete:0.0.1-SNAPSHOT
docker run --rm --name spring-boot-lab-4 --network labnetwork -d -p 8083:8080 -e SPRING_DATA_CASSANDRA_CONTACT_POINTS="172.17.0.2:9042" rest-service-complete:0.0.1-SNAPSHOT
docker run --rm --name spring-boot-lab-5 --network labnetwork -d -p 8084:8080 -e SPRING_DATA_CASSANDRA_CONTACT_POINTS="172.17.0.2:9042" rest-service-complete:0.0.1-SNAPSHOT
docker ps
CONTAINER ID   IMAGE                                  COMMAND                  CREATED          STATUS          PORTS                                                                          NAMES
8591e3c8662d   rest-service-complete:0.0.1-SNAPSHOT   "/cnb/process/web"       3 seconds ago    Up 2 seconds    0.0.0.0:8084->8080/tcp, :::8084->8080/tcp                                      spring-boot-lab-5
a765a276e7ed   rest-service-complete:0.0.1-SNAPSHOT   "/cnb/process/web"       13 seconds ago   Up 13 seconds   0.0.0.0:8083->8080/tcp, :::8083->8080/tcp                                      spring-boot-lab-4
742629b00477   rest-service-complete:0.0.1-SNAPSHOT   "/cnb/process/web"       22 seconds ago   Up 21 seconds   0.0.0.0:8082->8080/tcp, :::8082->8080/tcp                                      spring-boot-lab-3
f8fb16fdc9c6   rest-service-complete:0.0.1-SNAPSHOT   "/cnb/process/web"       29 seconds ago   Up 29 seconds   0.0.0.0:8081->8080/tcp, :::8081->8080/tcp                                      spring-boot-lab-2
33b63f4e4c5d   rest-service-complete:0.0.1-SNAPSHOT   "/cnb/process/web"       37 minutes ago   Up 37 minutes   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp                                      spring-boot-lab
a03e783ff4fe   cassandra:4.0.4                        "docker-entrypoint.s…"   2 days ago       Up 7 hours      7000-7001/tcp, 7199/tcp, 9160/tcp, 0.0.0.0:9042->9042/tcp, :::9042->9042/tcp   cassandra-lab
curl "http://localhost:8080/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8081/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8082/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8083/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8084/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8080/persistent_greeting"
{"id":11,"content":"Hello, World!"}%
curl "http://localhost:8081/persistent_greeting"
{"id":12,"content":"Hello, World!"}%
curl "http://localhost:8082/persistent_greeting"
{"id":13,"content":"Hello, World!"}%
curl "http://localhost:8083/persistent_greeting"
{"id":14,"content":"Hello, World!"}%
curl "http://localhost:8084/persistent_greeting"
{"id":15,"content":"Hello, World!"}%

Simple Case Dockerfile

Dockerfile can be considered its own configuration management language, and can get quite complex. So for the sake of this lab, we will be approaching this using a pre-written Dockerfile that follows best practices for Spring Boot images.

While what we have accomplished with generating images directly in Maven is the most simple way to get started, the functionality provided there is only going to cover a specific predefined subset of what Dockfiles are capable of building. If you utilize the full extent of a Dockerfile, you can essentially build an entire self-contained OS image configured specifically for your needs, whatever they may be. It does require a much larger Sysadmin knowledge base though to make the full use of this. Some basic Linux knowledge and Googling abilities should get you to resolving most common problems though.

Take a look at the included Dockerfile, and try to understand what all the steps are accomplishing. The Docker primer lesson should have covered all these items from the Dockerfile command side. And, there is a good overview on the best-practices approach of layered Spring Boot containers being used here.

Once you've reviewed those, copy the Dockerfile into the project directory, to the same level as the pom.xml file. Then, run the following command from a terminal at that location:

docker build -t spring-boot-lab-build .
Sending build context to Docker daemon  33.43MB
Step 1/19 : FROM eclipse-temurin:11-jdk as builder
...
Successfully built 4e75576a93d4
Successfully tagged spring-boot-lab-build:latest

If everything builds successfully, you should now see this image available for use on your workstation

docker images
REPOSITORY                  TAG                    IMAGE ID       CREATED              SIZE
spring-boot-lab-build       latest                 4e75576a93d4   45 seconds ago       273MB
...

You can launch an instance using this image, and see that it runs identically to what was built via the Maven plugin

docker run --rm --name spring-boot-lab-6 --network labnetwork -d -p 8085:8080 -e SPRING_DATA_CASSANDRA_CONTACT_POINTS="172.17.0.2:9042" spring-boot-lab-build
curl "http://localhost:8085/greeting"
{"id":1,"content":"Hello, World!"}%
curl "http://localhost:8085/persistent_greeting"
{"id":16,"content":"Hello, World!"}%

Testing

The following command will run the tests to validate that this environment was setup correctly. A screenshot of the successful tests can be uploaded as a submission.

docker run --network labnetwork -it --rm -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd)/test:/test inspec-lab exec docker.rb
...
Profile:   tests from docker.rb (tests from docker.rb)
Version:   (not specified)
Target:    local://
Target ID: 

  ✔  Maven Image Build: Container image has been built via Maven
     ✔  #<Inspec::Resources::DockerImageFilter:0x0000563d46595770> with repository == "rest-service-complete" tag == "0.0.1-SNAPSHOT" is expected to exist
  ✔  Cassandra Running: Cassandra Docker instance is running
     ✔  #<Inspec::Resources::DockerImageFilter:0x0000563d4500eb68> with repository == "cassandra" tag == "4.0.4" is expected to exist#<Inspec::Resources::DockerContainerFilter:0x0000563d4511c280> with names == "cassandra-lab" image == "cassandra:4.0.4" status is expected to match [/Up/]
     ✔  Cassandra query: SELECT cluster_name FROM system.local output is expected to match /Test Cluster/
  ✔  Maven Container: Maven Spring Boot Container running
     ✔  #<Inspec::Resources::DockerContainerFilter:0x0000563d46c6a000> with names == "spring-boot-lab" image == "rest-service-complete:0.0.1-SNAPSHOT" ports =~ /0.0.0.0:8080/ status is expected to match [/Up/]
     ✔  HTTP GET on http://spring-boot-lab:8080/ status is expected to eq 404
  ✔  Maven Container Replicas: Maven Spring Boot Container Replicas running
     ✔  #<Inspec::Resources::DockerContainerFilter:0x0000563d43703f10> with names == "spring-boot-lab-2" image == "rest-service-complete:0.0.1-SNAPSHOT" ports =~ /0.0.0.0:8081/ status is expected to match [/Up/]
     ✔  HTTP GET on http://spring-boot-lab-2:8080/ status is expected to eq 404
     ✔  #<Inspec::Resources::DockerContainerFilter:0x0000563d467f3288> with names == "spring-boot-lab-3" image == "rest-service-complete:0.0.1-SNAPSHOT" ports =~ /0.0.0.0:8082/ status is expected to match [/Up/]
     ✔  HTTP GET on http://spring-boot-lab-3:8080/ status is expected to eq 404
     ✔  #<Inspec::Resources::DockerContainerFilter:0x0000563d4675b460> with names == "spring-boot-lab-4" image == "rest-service-complete:0.0.1-SNAPSHOT" ports =~ /0.0.0.0:8083/ status is expected to match [/Up/]
     ✔  HTTP GET on http://spring-boot-lab-4:8080/ status is expected to eq 404
     ✔  #<Inspec::Resources::DockerContainerFilter:0x0000563d44f70300> with names == "spring-boot-lab-5" image == "rest-service-complete:0.0.1-SNAPSHOT" ports =~ /0.0.0.0:8084/ status is expected to match [/Up/]
     ✔  HTTP GET on http://spring-boot-lab-5:8080/ status is expected to eq 404
  ✔  Dockerfile Image Build: Container image has been built via Dockerfile
     ✔  #<Inspec::Resources::DockerImageFilter:0x0000563d45114a30> with repository == "spring-boot-lab-build" tag == "latest" is expected to exist#<Inspec::Resources::DockerImageFilter:0x0000563d46c3fcb0> with repository == "eclipse-temurin" tag == "11-jre" is expected to exist
  ✔  Dockerfile Build Container: Custom Dockerfile Container running
     ✔  #<Inspec::Resources::DockerContainerFilter:0x0000563d45009ac8> with names == "spring-boot-lab-6" image == "spring-boot-lab-build" ports =~ /0.0.0.0:8085/ status is expected to match [/Up/]
     ✔  HTTP GET on http://spring-boot-lab-6:8080/ status is expected to eq 404


Profile Summary: 6 successful controls, 0 control failures, 0 controls skipped
Test Summary: 18 successful, 0 failures, 0 skipped

Advanced Lab

Take a look at all the containers we have used so far in this class. Their docker images are available from hub.docker.com, and you should be able to look up the associated Github repositories to browse to the Dockerfiles. Look through these Dockerfiles, and see just how complex these environments can get. The Bitnami containers have a complex nest of shell scripts that may be worth looking through. Always try to keep your own Dockerfiles/images as simple as possible when you can, but sometimes there is no getting around this complexity.

java-mod-10-building-spring-boot-images's People

Contributors

brianmurphyfis avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.