Cloud Native 22 min read

Practical Experience of Containerizing Java Applications: Docker, Kubernetes, and Cloud‑Native Practices

This article shares the 2019 container‑migration experience of an e‑commerce team, covering Docker and Kubernetes fundamentals, Java‑specific memory and CPU limits, JDK version tuning, log collection, multi‑stage image builds, health‑check strategies, common pitfalls, and best‑practice recommendations for cloud‑native deployments.

HomeTech
HomeTech
HomeTech
Practical Experience of Containerizing Java Applications: Docker, Kubernetes, and Cloud‑Native Practices

Since Docker and Kubernetes became open‑source, container products have gained popularity due to isolation, portability, low resource consumption, and fast startup. Containers use OS‑level virtualization, allowing many isolated instances on a single host, making them ideal for deploying micro‑service modules.

In 2019 the e‑commerce team at Autohome began a cloud‑native migration, completing the migration of all applications to a container platform within two months and documenting the lessons learned.

Java vs Docker

Older Java versions cannot automatically detect Docker‑imposed memory and CPU limits. Docker uses cgroups to set limits, e.g., docker run -m 1024M --cpuset-cpus="1,3" . Before Java 8u131, the JVM does not recognize these limits, so explicit -Xmx and parallel‑GC settings are required to avoid OOM kills.

CPU Limits

The JVM runtime queries the host CPU count; if it sees the host’s CPUs instead of Docker’s limited set, thread pools and parallel GC may behave unexpectedly.

Log Collection

Container logs disappear when the container is destroyed, so a persistent logging solution is needed. The platform provides two approaches: bind‑mount or volume the log directory to the host and collect with filebeat , or embed nxlog in the image to ship application logs to a central system.

Technical Preparation

After extensive research the team chose JDK settings:

For Java 8u131 and earlier, set heap size manually: -Xmx=2G -XX:ParallelGCThreads=cpus -XX:CICompilerCount=cpus .

From Java 8u131 onward, enable container awareness with -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap .

Java 8u191 adds -XX:InitialRAMPercentage , -XX:MinRAMPercentage , -XX:MaxRAMPercentage to adapt to dynamic resources.

Java 11 enables -XX:UseContainerSupport by default, allowing automatic detection of CPU and memory limits.

The team settled on Java 8u191 as a stable, long‑term version.

Image Building

A custom base image was created (CentOS 7 with JDK 8 and nxlog). Example Dockerfile snippets:

FROM xxxx.autohome.com.cn/project/centos7-jdk8:full-nxlog
ADD skywalking-agent/6.4.0/ /skywalking-agent/
ADD arthas-3.1.4/ /arthas/
RUN echo 'Asia/Shanghai' > /etc/timezone && chown -R root:root /var/run/nxlog /var/spool/nxlog && mkdir -p /usr/local/nxlog-config/data
MAINTAINER [email protected]

Build and push commands:

docker build -t xxxx.autohome.com.cn/project/centos7-jdk8:v1 .
docker push xxxx.autohome.com.cn/project/centos7-jdk8:v1

Multi‑stage builds were used to reduce image size, e.g.:

FROM maven:3.5-jdk-8 AS build
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package
FROM openjdk:8-jre
COPY --from=build /usr/src/app/target/BOOT-INF/lib /app/lib
COPY --from=build /usr/src/app/target/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

Jib Layered Builds

Jib (Google’s Maven/Gradle plugin) creates separate layers for dependencies, resources, and code, dramatically shrinking image size. However, direct pushes to the registry bypass CI/CD pipelines, which the team considered undesirable.

Health‑Check Strategies

Kubernetes provides Liveness and Readiness probes. Liveness ensures the container is still running; Readiness determines if the pod should receive traffic. The team recommends setting Readiness to fail for recoverable dependency issues and Liveness to fail for unrecoverable states.

Pitfalls and Solutions

Encoding issues: Set container locale with ENV LANG="zh_CN.UTF-8" .

Timezone misconfiguration: Add RUN echo 'Asia/Shanghai' > /etc/timezone to Dockerfile.

Log conflicts: Remove duplicate SLF4J bindings via Maven <exclusions>... .

Downloading files from containers: Use curl -T file ftp://user:pass@server/path or HTTP form upload.

Incorrect CMD usage: Prefer JSON array syntax, e.g. CMD ["java","$JAVA_OPTS","-jar","/app.jar"] , so PID 1 receives signals.

Graceful shutdown: Enable Spring Boot shutdown endpoint and use curl -X POST host:port/shutdown .

Off‑heap memory OOM: Monitor native memory usage for frameworks like Netty.

Embracing the Cloud Platform

The e‑commerce team thanks the cloud‑platform group for enabling reliable container deployments and plans to design future systems with cloud‑native principles in mind.

References

Container‑oriented logging practices

Techniques to shrink Docker images by 90%

Why large JARs should not be baked into images

Docker security considerations

Java application Dockerization pitfalls

Javacloud-nativeDockerkubernetescontainerizationHealth ChecksJib
HomeTech
Written by

HomeTech

HomeTech tech sharing

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.