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.
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:v1Multi‑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
HomeTech
HomeTech tech sharing
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.