Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Latest commit

 

History

History
287 lines (229 loc) · 12 KB

README_demo.md

File metadata and controls

287 lines (229 loc) · 12 KB

This repo demonstrates how a Sprint Boot app can easily be turned in to a container image and tested in a local development environment. This is what is intended to be coverd in this repo:

  • Run a sample Spring Boot application with Maven
  • Containerize and run the app without a Dockerfile using Maven plugins
  • Containerize the app by handcrafting a Dockerfile and executing the app using Docker CLI
  • Start a stack which consist of the sample application and a database using Docker Compose
  • If time allows, deploy the app in a Kubernetes env
  • A brief demo on how to package the sample app via GitHub actions as part of CI/CD

Prerequisite

Instructions

  1. Clone and run a Spring Boot application with Maven in a local development environment
git clone https://github.com/shazChaudhry/spring-petclinic.git
cd spring-petclinic

# Test and package the application with a maven plugin
./mvnw clean package
ls -lar ./target/*.jar

./mvnw spring-boot:run
# The sample app should be available at http://localhost:8080
# ctrl + c to get the terminal back. This will terminate the application too

Execute the application without a maven plugin

java -jar target/*.jar
# The sample app will be available at http://localhost:8080
# ctrl + c to get the terminal back. This will stop terminate the application too
# NOTE: You would normally store production candidate packages in a binary management system such as Jfrog artifactory
  1. There is no Dockerfile in this project. You can build a container image using the Spring Boot maven plugin
# Create a container image with maven spring-boot plugin. Image name and tag will be picked up from pom.xml
docker system prune -a --volumes
./mvnw spring-boot:build-image
docker image ls

# Or give the image a specific path
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=shazchaudhry/spring-petclinic-spring-boot
docker image ls
docker inspect spring-petclinic:3.1.0-SNAPSHOT

# Run the container which will then be available at http://localhost:8080
docker container run --rm --name spring-petclinic -d -p 8080:8080 spring-petclinic:3.1.0-SNAPSHOT
docker container ls
docker logs -f spring-petclinic

# If you want to poke around inside the image, you can open a shell in it or inspect it by running the following command
docker container exec -it spring-petclinic /bin/bash

Or you can use jib plugin from Google which is an alternative maven plugin for building container images from pom.xml. No Dockerfile is required. Documentation for the this plugin can be found here.

This is a quick way of generating the image for petclinic project

# This command required docker be installed locally
./mvnw compile com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=shazchaudhry/spring-petclinic-jib
docker image ls

# This builds and pushes a container image for your application to a container registry. So, you require push permissions
./mvnw compile com.google.cloud.tools:jib-maven-plugin:build -Dimage=shazchaudhry/spring-petclinic-jib
docker image ls
  1. Create a new Dockerfile which contains instructions required to build a Java image

In step #1 above, it was demonstrated that 'spring-boot' maven plugin could be used to start the sample application. In the Dockerfile written in this step, the same plugin goal will be used

Create a file called Dockerfile and place the following content in the file

# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jdk-jammy
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:resolve
COPY src ./src
CMD ["./mvnw", "spring-boot:run"]

Create a file called .dockerignore and place the following content in the file

target

Execute the following commands to build, list and tag container images

docker image build --tag java-docker .
docker image ls

# The image size is much bigger than the once created earlier with maven plugins
docker image history java-docker

docker tag java-docker:latest java-docker:v1.0.0
docker image ls
  1. Run the newly built image as a container

Start the container and expose port 8080 to port 8080 on the host

docker run --publish 8080:8080 java-docker

# browse to http://localhost:8080 or use this command in a new terminal
curl --request GET --url http://localhost:8080/actuator/health --header 'content-type: application/json'

# Press ctrl-c to stop the container and run it in detached mode and with a given name
docker container run --rm --name java-docker -d -p 8080:8080 java-docker

# browse to http://localhost:8080 or use this command to see the application is up and running
curl --request GET --url http://localhost:8080/actuator/health --header 'content-type: application/json'
docker container ls
  1. Use single-stage Dockerfile (craeted above) for petclinic and MySQL containers for development

we will take a look at running a database in a container and how we use volumes and networking to persist our data and allow our application to talk with the database.

Instead of downloading MySQL, installing, configuring, and then running the MySQL database as a service, we can use the Docker Official Image for MySQL and run it in a container.

# create a couple of volumes that Docker can manage to store our persistent data and configuration
docker volume create mysql_data
docker volume create mysql_config

# Now we’ll create a network that our application and database will use to talk to each other
docker network create mysqlnet

# Now, let's run MySQL in a container and attach to the volumes and network we created above. Docker pulls the image from Hub and runs it locally
docker run -it --rm -d \
-v mysql_data:/var/lib/mysql \
-v mysql_config:/etc/mysql/conf.d \
--network mysqlnet \
--name mysqlserver \
-e MYSQL_USER=petclinic \
-e MYSQL_PASSWORD=petclinic \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=petclinic \
mysql:8.0

Now that we have a running MySQL, let’s 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.

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 and let’s run our container. This time, we need to set the MYSQL_URL environment variable so that our application knows what connection string to use to access the database. We’ll do this using the docker run command.

# Rebuild the container image as the Dockerfile has been updated
docker build --tag java-docker .

# Run the application in detached mode
docker run --rm -d \
--name springboot-server \
--network mysqlnet \
-e MYSQL_URL=jdbc:mysql://mysqlserver/petclinic \
-p 8080:8080 java-docker

# Check the application logs
docker container logs -f springboot-server

# browse to `http://localhost:8080/vets.html` to ensure the application is up and running
  1. Multi-stage Dockerfile for development

Let’s 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.

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.

# 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"]

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 could now target building an image to a specific point such as:

  • docker image build --tag java-docker --target development . Or
  • docker image build --tag java-docker --target production .
  1. Use Compose to develop locally

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.

version: '3.8'
services:
  petclinic:
    build:
      context: .
      target: development
    ports:
      - "8080:8080"
    environment:
      - SERVER_PORT=8080
      - MYSQL_URL=jdbc:mysql://mysqlserver/petclinic
    networks: 
      - mysqlnet
    volumes:
      - ./:/app
    depends_on:
      - mysqlserver

  mysqlserver:
    image: mysql:8.0
    networks: 
      - mysqlnet
    environment: 
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_ALLOW_EMPTY_PASSWORD=true
      - MYSQL_USER=petclinic
      - MYSQL_PASSWORD=petclinic
      - MYSQL_DATABASE=petclinic
    volumes:
      - mysql_data:/var/lib/mysql
      - mysql_config:/etc/mysql/conf.d
volumes:
  mysql_data:
  mysql_config:
networks:
  mysqlnet:

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.

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.

# We pass the --build flag so Docker will compile our image and then starts the containers
docker compose -f docker-compose.dev.yml up --build -d
docker compose -f docker-compose.dev.yml ps

# Check logs for petclinic service
docker compose -f docker-compose.dev.yml logs petclinic -f

# Stop the stack
docker compose -f docker-compose.dev.yml down
  • Briefly look at GitHub actions for this repo

Stretch goal

Deploy the pet clinic app in a Kubernetes cluster (Docker Desktop). Kubectl referece docs can be found here

We will mainly be working with pods. Please see the kubernetes docs here

  • kubectl config get-contexts (Display the current-context)
  • kubectl get ns (get all namespaces)
  • kubectl get all -n default (get all resources in the default namespace)
  • ./mvnw compile com.google.cloud.tools:jib-maven-plugin:build -Dimage=shazchaudhry/spring-petclinic-jib (build and push an image to doker hub. No Dockerfile required)
  • kubectl run spring-petclinic --image=shazchaudhry/spring-petclinic-jib (Create and run a the image in a pod)
  • kubectl get pods (Display pods in the default namespace)
  • kubectl describe pod spring-petclinic (Show details of a specific resource)
  • kubectl logs -f spring-petclinic (Print the logs for a container in a pod)
  • kubectl exec -it spring-petclinic -- bash (Execute a command in a container)
  • kubectl port-forward pod/spring-petclinic 8080:8080 (Forward a local ports to the pod)
  • kubectl delete pod spring-petclinic (Delete resources pod names)

Conclusion

  • For spring boot applications, I would not bother writing a Dockerfile as container images could very conviniently be produced using maven plugins
  • These image can then be deployed in a local development environment such as Docker Compose / Docker Desktop or in a production environment such as AWS Elastic Kubernetes Service (EKS)

Reference

  1. Source code for this sample application is forked from spring-petclinic repo
  2. Language-specific (Java) guide for containerizing this sample application is taken from Docker Hub documentation