A simple web API in various languages and deployable to Kubernetes (Java)

Continuing this short series of writing a simple echo service web API along with the docker and k8s requirements, we’re now going to turn our attention to a Java implementation.

Implementation

I’m going to be using JetBrains IntelliJ.

  • Create a new Java Project, selecting Maven as the build system
  • We’re going to use Sprint Boot, so add the following to the pom.xml
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.5.5</version>
      </dependency>
    </dependencies>
    
  • We’re also going to want to use the Spring Boot Maven plugin to generate our JAR and Manifest
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>3.5.5</version>
          <executions>
            <execution>
              <goals>
                <goal>repackage</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
    
  • Let’s delete the supplied Main.java file and replace with one named EchoServiceApplication.java which looks like this
    package com.putridparrot;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @SpringBootApplication
    public class EchoServiceApplication {
        public static void main(String[] args) {
            SpringApplication app = new SpringApplication(EchoServiceApplication.class);
            Map<String, Object> props = new HashMap<>();
            props.put("server.port", System.getenv("PORT"));
            app.setDefaultProperties(props);
            app.run(args);
        }
    }
    

    We’re setting the PORT here from the environment variable as this will be supplied via the Dockerfile

  • Next add a new file named EchoController.java which will look like this
    package com.putridparrot;
    
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class EchoController {
    
        @GetMapping("/echo")
        public String echo(@RequestParam(name = "text", defaultValue = "") String text) {
            return String.format("Java Echo: %s", text);
        }
    
        @GetMapping("/readyz")
        public String readyz() {
            return "OK";
        }
    
        @GetMapping("/livez")
        public String livez() {
            return "OK";
        }
    }
    

Dockerfile

Next up we need to create our Dockerfile

FROM maven:3.9.11-eclipse-temurin-21 AS builder

WORKDIR /app

COPY . .
RUN mvn clean package -DskipTests

FROM eclipse-temurin:21-jre AS runtime

WORKDIR /app

COPY --from=builder /app/target/echo_service-1.0-SNAPSHOT.jar ./echo-service.jar

ENV PORT=8080
EXPOSE 8080

ENTRYPOINT ["java", "-jar", "echo-service.jar"]

Note: In Linux port 80 might be locked down, hence we use port 8080 – to override the default port in phx we also set the environment variable PORT.

To build this, run

docker build -t putridparrot.echo_service:v1 .

Don’t forget to change the name to your preferred name.

To test this, run

docker run -p 8080:8080 putridparrot.echo_service:v1

Kubernetes

If all wen well we’ve not tested our application and see it working from a docker image, so now we need to create the deployment etc. for Kubernete’s. Let’s assume you’ve pushed you image to Docker or another container registry such as Azure – I’m call my container registry putridparrotreg.

I’m also not going to use helm at this point as I just want a (relatively) simple yaml file to run from kubectl, so create a deployment.yaml file, we’ll store all the configurations, deployment, service and ingress in this one file jus for simplicity.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  namespace: dev
  labels:
    app: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
  template:
    metadata:
      labels:
        app: echo
    spec:
      containers:
      - name: echo
        image: putridparrotreg/putridparrot.echo_service:v1
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "100Mi"
            cpu: "100m"
          limits:
            memory: "200Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /livez
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: echo_service
  namespace: dev
  labels:
    app: echo
spec:
  type: ClusterIP
  selector:
    app: echo 
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
  namespace: dev
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: mydomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: echo_service
            port:
              number: 80

Don’t forget to change the “host” and image to suit, also this assume you created a namespace “dev” for your app. See Creating a local container registry for information on setting up your own container registry.