Recent Posts
Archives

Posts Tagged ‘Docker’

PostHeaderIcon [DevoxxFR 2018] Java in Docker: Best Practices for Production

The practice of running Java applications within Docker containers has become widely adopted in modern software deployment, yet it is not devoid of potential challenges, particularly when transitioning to production environments. Charles Sabourdin, a freelance architect, and Jean-Christophe Sirot, an engineer at Docker, collaborated at DevoxxFR2018 to share their valuable experiences and disseminate best practices for optimizing Java applications inside Docker containers. Their insightful talk directly addressed common and often frustrating issues, such as containers crashing unexpectedly, applications consuming excessive RAM leading to node instability, and encountering CPU throttling. They offered practical solutions and configurations aimed at ensuring smoother and more reliable production deployments for Java workloads.

The presenters initiated their session with a touch of humor, explaining why operations teams might exhibit a degree of apprehension when tasked with deploying a containerized Java application into a production setting. It’s a common scenario: containers that perform flawlessly on a developer’s local machine can begin to behave erratically or fail outright in production. This discrepancy often stems from a fundamental misunderstanding of how the Java Virtual Machine (JVM) interacts with the resource limits imposed by the container’s control groups (cgroups). Several key problems frequently surface in this context. Perhaps the most common is memory mismanagement; the JVM, particularly older versions, might not be inherently aware of the memory limits defined for its container by the cgroup. This lack of awareness can lead the JVM to attempt to allocate and use more memory than has been allocated to the container by the orchestrator or runtime. Such overconsumption inevitably results in the container being abruptly terminated by the operating system’s Out-Of-Memory (OOM) killer, a situation that can be difficult to diagnose without understanding this interaction.

Similarly, CPU resource allocation can present challenges. The JVM might not accurately perceive the CPU resources available to it within the container, such as CPU shares or quotas defined by cgroups. This can lead to suboptimal decisions in sizing internal thread pools (like the common ForkJoinPool or garbage collection threads) or can cause the application to experience unexpected CPU throttling, impacting performance. Another frequent issue is Docker image bloat. Overly large Docker images not only increase deployment times across the infrastructure but also expand the potential attack surface by including unnecessary libraries or tools, thereby posing security vulnerabilities. The talk aimed to equip developers and operations personnel with the knowledge to anticipate and mitigate these common pitfalls. During the presentation, a demonstration application, humorously named “ressources-munger,” was used to simulate these problems, clearly showing how an application could consume excessive memory leading to an OOM kill by Docker, or how it might trigger excessive swapping if not configured correctly, severely degrading performance.

JVM Memory Management and CPU Considerations within Containers

A significant portion of the discussion was dedicated to the intricacies of JVM memory management within the containerized environment. Charles and Jean-Christophe elaborated that older JVM versions, specifically those prior to Java 8 update 131 and Java 9, were not inherently “cgroup-aware”. This lack of awareness meant that the JVM’s default heap sizing heuristics—for example, typically allocating up to one-quarter of the physical host’s memory for the heap—would be based on the total resources of the host machine rather than the specific limits imposed on the container by its cgroup. This behavior is a primary contributor to unexpected OOM kills when the container’s actual memory limit is much lower than what the JVM assumes based on the host.

Several best practices were shared to address these memory-related issues effectively. The foremost recommendation is to use cgroup-aware JVM versions. Modern Java releases, particularly Java 8 update 191 and later, and Java 10 and newer, incorporate significantly improved cgroup awareness. For older Java 8 updates (specifically 8u131 to 8u190), experimental flags such as -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap can be employed to enable the JVM to better respect container memory limits. In Java 10 and subsequent versions, this behavior became standard and often requires no special flags. However, even with cgroup-aware JVMs, explicitly setting the heap size using parameters like -Xms for the initial heap size and -Xmx for the maximum heap size is frequently a recommended practice for predictability and control. Newer JVMs also offer options like -XX:MaxRAMPercentage, allowing for more dynamic heap sizing relative to the container’s allocated memory. It’s crucial to understand that the JVM’s total memory footprint extends beyond just the heap; it also requires memory for metaspace (which replaced PermGen in Java 8+), thread stacks, native libraries, and direct memory buffers. Therefore, when allocating memory to a container, it is essential to account for this total footprint, not merely the -Xmx value. A common guideline suggests that the Java heap might constitute around 50-75% of the total memory allocated to the container, with the remainder reserved for these other essential JVM components and any other processes running within the container. Tuning metaspace parameters, such as -XX:MetaspaceSize and -XX:MaxMetaspaceSize, can also prevent excessive native memory consumption, particularly in applications that dynamically load many classes.

Regarding CPU resources, the presenters noted that the JVM’s perception of available processors is also influenced by its cgroup awareness. In environments where CPU resources are constrained, using flags like -XX:ActiveProcessorCount can be beneficial to explicitly inform the JVM about the number of CPUs it should consider for sizing its internal thread pools, such as the common ForkJoinPool or the threads used for garbage collection. Optimizing the Docker image itself is another critical aspect of preparing Java applications for production. This involves choosing a minimal base image, such as alpine-jre, distroless, or official “slim” JRE images, instead of a full operating system distribution, to reduce the image size and potential attack surface. Utilizing multi-stage builds in the Dockerfile is a highly recommended technique; this allows developers to use a larger image containing build tools like Maven or Gradle and a full JDK in an initial stage, and then copy only the necessary application artifacts (like the JAR file) and a minimal JRE into a final, much smaller runtime image. Furthermore, being mindful of Docker image layering by combining related commands in the Dockerfile where possible can help reduce the number of layers and optimize image size. For applications on Java 9 and later, tools like jlink can be used to create custom, minimal JVM runtimes that include only the Java modules specifically required by the application, further reducing the image footprint. The session strongly emphasized that a collaborative approach between development and operations teams, combined with a thorough understanding of both JVM internals and Docker containerization principles, is paramount for successfully and reliably running Java applications in production environments.

Links:

Hashtags: #Java #Docker #JVM #Containerization #DevOps #Performance #MemoryManagement #DevoxxFR2018 #CharlesSabourdin #JeanChristopheSirot #BestPractices #ProductionReady #CloudNative

PostHeaderIcon [DevoxxUS2017] Mobycraft: Manage Docker Containers Using Minecraft by Arun Gupta

At DevoxxUS2017, Arun Gupta, Vice President of Developer Advocacy at Couchbase, presented an innovative project called Mobycraft, a Minecraft client-side mod designed to manage Docker containers. Collaborating with his son, Aditya Gupta, Arun showcased how this mod transforms container management into an engaging, game-based experience, particularly for younger audiences learning Java and Docker fundamentals. This post delves into the key aspects of Arun’s session, highlighting how Mobycraft bridges gaming and technology education.

Engaging Young Minds with Docker

Arun Gupta introduced Mobycraft as a creative fusion of Minecraft’s interactive environment and Docker’s container management capabilities. Developed as a father-son project, the mod allows users to execute Docker commands like /docker ps and /docker run within Minecraft. Arun explained how containers are visualized as color-coded boxes, with interactive elements like start/stop buttons and status indicators. This approach, rooted in Aditya’s passion for Minecraft modding, makes complex Docker concepts accessible and fun, fostering early exposure to DevOps principles.

Technical Implementation and Community Contribution

Arun detailed Mobycraft’s technical foundation, built on Minecraft Forge for Minecraft 1.8, using the docker-java library to interface with Docker hosts. The mod supports multiple providers, including local Docker hosts, Docker for Mac, and Netflix’s Titus, leveraging Guice for dependency injection to ensure flexibility. Arun encouraged community contributions through code reviews and pull requests on the GitHub repository, emphasizing its educational potential and inviting developers to enhance features like Swarm visualization.

Links:

PostHeaderIcon [DevoxxUS2017] Lessons Learned from Building Hyper-Scale Cloud Services Using Docker by Boris Scholl

At DevoxxUS2017, Boris Scholl, Vice President of Development for Microservices at Oracle, shared valuable lessons from building hyper-scale cloud services using Docker. With a background in Microsoft’s Service Fabric and Container Service, Boris discussed Oracle’s adoption of Docker, Mesos/Marathon, and Kubernetes for resource-efficient, multi-tenant services. His session offered insights into architecture choices and DevOps best practices, providing a roadmap for scalable cloud development. This post examines the key themes of Boris’s presentation, highlighting practical strategies for modern cloud services.

Adopting Docker for Scalability

Boris Scholl began by outlining Oracle’s shift toward cloud services, leveraging Docker to build scalable, multi-tenant applications. He explained how Docker containers optimize resource consumption, enabling rapid service deployment. Drawing from his experience at Oracle, Boris highlighted the pros of containerization, such as portability, and cons, like the need for robust orchestration, setting the stage for discussing advanced DevOps practices.

Orchestration with Mesos and Kubernetes

Delving into orchestration, Boris discussed Oracle’s use of Mesos/Marathon and Kubernetes to manage containerized services. He shared lessons learned, such as the importance of abstracting container management to avoid platform lock-in. Boris’s examples illustrated how orchestration tools ensure resilience and scalability, enabling Oracle to handle hyper-scale workloads while maintaining service reliability.

DevOps Best Practices for Resilience

Boris emphasized the critical role of DevOps in running “always-on” services. He advocated for governance to manage diverse team contributions, preventing architectural chaos. His insights included automating CI/CD pipelines and prioritizing diagnostics for monitoring. Boris shared a lesson on avoiding over-reliance on specific orchestrators, suggesting abstraction layers to ease transitions between platforms like Mesos and Kubernetes.

Governance and Future-Proofing

Concluding, Boris stressed the importance of governance in distributed systems, drawing from Oracle’s experience in maintaining component versioning and compatibility. He recommended blogging as a way to share microservices insights, referencing his own posts. His practical advice inspired developers to adopt disciplined DevOps practices, ensuring cloud services remain scalable, resilient, and adaptable to future needs.

Links:

PostHeaderIcon [DevoxxFR2015] Evolving Infrastructure Without Downtime: CloudBees’ Journey

Nicolas De Loof, an Apache Maven committer and founder of BreizhJUG, delivered an engaging session at Devoxx France 2015, stepping in for his colleague Michael Neale. Representing CloudBees, Nicolas shared the company’s evolution from a fragmented startup to a robust, globally available system, focusing on seamless infrastructure migrations without interrupting service. His narrative, infused with humor and practical insights, highlighted transitions to multi-tenant architectures and Docker-based deployments.

From Startup Chaos to Structured Systems

Nicolas began by outlining CloudBees’ early days, marked by ad-hoc technical decisions that later demanded refinement. Initial choices, such as a custom LXC-based solution, became obsolete as the company scaled. He described the challenge of maintaining zero downtime across a global user base, necessitating careful planning to evolve infrastructure while keeping services operational.

This journey, Nicolas emphasized, required strategic foresight.

Migrating to Multi-Tenant Architecture

The shift to a multi-tenant build-on-demand system was a cornerstone of CloudBees’ transformation. Nicolas detailed how this migration, spanning months, consolidated resources to improve efficiency without impacting users. By gradually phasing in the new architecture, the team ensured continuity, addressing regrets from earlier single-tenant designs that strained scalability.

This transition, he noted, enhanced resource utilization.

Adopting Docker for Containerization

Replacing LXC with Docker marked another pivotal change. Nicolas explained how Docker’s containerization simplified deployment and management, offering greater flexibility than the bespoke LXC setup. The migration, executed incrementally, maintained service uptime, with Docker’s lightweight containers streamlining operations across CloudBees’ infrastructure.

This adoption, Nicolas highlighted, modernized their platform.

Operational Best Practices

Drawing from CloudBees’ experience, Nicolas stressed the importance of health checks, monitoring, and termination strategies to prevent service disruptions. His lighthearted “Salut les Geeks” conclusion, inspired by a YouTube series, underscored practical advice: robust monitoring prevents “blonde” moments where systems fail silently. He urged teams to integrate these practices early to avoid production chaos.

These strategies, he concluded, ensure resilient operations.

Links:

PostHeaderIcon [DevoxxFR2014] Runtime stage

FROM nginx:alpine
COPY –from=builder /app/dist /usr/share/nginx/html
EXPOSE 80


This pattern reduces final image size from hundreds of megabytes to tens of megabytes. **Layer caching** optimization requires careful instruction ordering:

COPY package.json package-lock.json ./
RUN npm ci
COPY . .


Copying dependency manifests first maximizes cache reuse during development.

## Networking Models and Service Discovery
Docker’s default bridge network isolates containers on a single host. Production environments demand multi-host communication. **Overlay networks** create virtual networks across swarm nodes:

docker network create –driver overlay –attachable prod-net
docker service create –network prod-net –name api myapp:latest


Docker’s built-in DNS enables service discovery by name. For external traffic, **ingress routing meshes** like Traefik or NGINX provide load balancing, TLS termination, and canary deployments.

## Persistent Storage for Stateful Applications
Stateless microservices dominate container use cases, but databases and queues require durable storage. **Docker volumes** offer the most flexible solution:

docker volume create postgres-data
docker run -d \
–name postgres \
-v postgres-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:13


For distributed environments, **CSI (Container Storage Interface)** plugins integrate with Ceph, GlusterFS, or cloud-native storage like AWS EBS.

## Orchestration and Automated Operations
Docker Swarm provides native clustering with zero external dependencies:

docker swarm init
docker stack deploy -c docker-compose.yml myapp
“`

For advanced workloads, Kubernetes offers:
Deployments for rolling updates and self-healing.
Horizontal Pod Autoscaling based on CPU/memory or custom metrics.
ConfigMaps and Secrets for configuration management.

Migration paths typically begin with stateless services in Swarm, then progress to Kubernetes for stateful and machine-learning workloads.

Security Hardening and Compliance

Production containers must follow security best practices:
– Run as non-root users: USER appuser in Dockerfile.
– Scan images with Trivy or Clair in CI/CD pipelines.
– Apply seccomp and AppArmor profiles to restrict system calls.
– Use RBAC and Network Policies in Kubernetes to enforce least privilege.

Production Case Studies and Operational Wisdom

Spotify manages thousands of microservices using Helm charts and custom operators. Airbnb leverages Kubernetes for dynamic scaling during peak booking periods. The New York Times uses Docker for CI/CD acceleration, reducing deployment time from hours to minutes.

Common lessons include:
– Monitor with Prometheus and Grafana.
– Centralize logs with ELK or Loki.
– Implement distributed tracing with Jaeger or Zipkin.
– Use chaos engineering to validate resilience.

Strategic Impact on DevOps Culture

Docker fundamentally accelerates the CI/CD pipeline and enables immutable infrastructure. Success requires cultural alignment: developers embrace infrastructure-as-code, operations teams adopt GitOps workflows, and security integrates into every stage. Orchestration platforms bridge the gap between development velocity and operational stability.

Links: