Recent Posts
Archives

Posts Tagged ‘Docker’

PostHeaderIcon Understanding Kubernetes for Docker and Docker Compose Users

TL;DR

Kubernetes may look like an overly complicated version of Docker Compose, but it operates on a different level entirely. Where Compose excels at quick, local orchestration of containers, Kubernetes is a robust, distributed platform designed for automated scaling, fault-tolerance, and production-grade deployments across multi-node clusters. This article provides a comprehensive comparison and shows how ArgoCD enhances GitOps-based Kubernetes workflows.


Docker Compose vs Kubernetes – Similarities and First Impressions

At a high level, Docker Compose and Kubernetes share similar concepts: containers, services, configuration, and volumes. This often leads to the assumption that Kubernetes is just a verbose, harder-to-write Compose replacement. However, Kubernetes is more than a runtime. It’s a control plane, a state manager, and a policy enforcer.

Concept Docker Compose Kubernetes
Service definition docker-compose.yml Deployment, Service, etc. YAML manifests
Networking Shared bridge network, service discovery by name DNS, internal IPs, ClusterIP, NodePort, Ingress
Volume management volumes: PersistentVolume, PersistentVolumeClaim, StorageClass
Secrets and configs .env, environment: ConfigMap, Secret, ServiceAccount
Dependency management depends_on initContainers, readinessProbe, livenessProbe
Scaling Manual (scale flag or duplicate services) Declarative (replicas), automatic via HPA

Real-Life Use Cases – Docker Compose vs Kubernetes Examples

Tomcat + Oracle + MongoDB + NGINX Stack

Docker Compose


version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    depends_on:
      - tomcat

  tomcat:
    image: tomcat:9
    ports:
      - "8080:8080"
    environment:
      DB_URL: jdbc:oracle:thin:@oracle:1521:orcl

  oracle:
    image: oracle/database:19.3.0-ee
    environment:
      ORACLE_PWD: secretpass
    volumes:
      - oracle-data:/opt/oracle/oradata

  mongo:
    image: mongo:5
    volumes:
      - mongo-data:/data/db

volumes:
  oracle-data:
  mongo-data:

Kubernetes Equivalent

  • Each service becomes a Deployment and a Service.
  • Environment variables and passwords are stored in Secrets.
  • Volumes are defined with PVC and StorageClass.

apiVersion: v1
kind: Secret
metadata:
  name: oracle-secret
type: Opaque
data:
  ORACLE_PWD: c2VjcmV0cGFzcw==

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat
spec:
  replicas: 2
  selector:
    matchLabels:
      app: tomcat
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
      - name: tomcat
        image: tomcat:9
        ports:
        - containerPort: 8080
        env:
        - name: DB_URL
          value: jdbc:oracle:thin:@oracle:1521:orcl

NodeJS + Express + MySQL + NGINX

Docker Compose


services:
  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
    volumes:
      - mysql-data:/var/lib/mysql

  api:
    build: ./api
    environment:
      DB_USER: root
      DB_PASS: rootpass
      DB_HOST: mysql

  nginx:
    image: nginx:latest
    ports:
      - "80:80"

Kubernetes Equivalent


apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
data:
  MYSQL_ROOT_PASSWORD: cm9vdHBhc3M=
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: api
        image: node-app:latest
        env:
        - name: DB_PASS
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: MYSQL_ROOT_PASSWORD

⚙️ Docker Compose vs kubectl – Command Mapping

Task Docker Compose Kubernetes
Start services docker-compose up -d kubectl apply -f .
Stop/cleanup docker-compose down kubectl delete -f .
View logs docker-compose logs -f kubectl logs -f pod-name
Scale a service docker-compose up --scale web=3 kubectl scale deployment web --replicas=3
Shell into container docker-compose exec app sh kubectl exec -it pod-name -- /bin/sh

ArgoCD – GitOps Made Practical

ArgoCD is a Kubernetes-native continuous deployment tool. It uses Git as the single source of truth, enabling declarative infrastructure and GitOps workflows.

✨ Key Features

  • Declarative sync of Git and cluster state
  • Drift detection and automatic repair
  • Multi-environment and multi-namespace support
  • CLI and Web UI available

Example ArgoCD Commands


argocd login argocd.myorg.com
argocd app create my-app \
  --repo https://github.com/org/app.git \
  --path k8s \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace production

argocd app sync my-app
argocd app get my-app
argocd app diff my-app

Sample ArgoCD Application Manifest


apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-api
spec:
  destination:
    namespace: default
    server: https://kubernetes.default.svc
  project: default
  source:
    path: k8s/app
    repoURL: https://github.com/org/api.git
    targetRevision: HEAD
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

✅ Conclusion

Docker Compose is perfect for prototyping and local dev. Kubernetes is built for cloud-native workloads, distributed systems, and high availability. ArgoCD makes declarative, Git-based continuous deployment simple, scalable, and observable.

PostHeaderIcon Essential Security Considerations for Docker Networking

Having recently absorbed my esteemed colleague Danish Javed’s insightful piece on Docker Networking (https://www.linkedin.com/pulse/docker-networking-danish-javed-rzgyf) – a truly worthwhile read for anyone navigating the container landscape – I felt compelled to further explore a critical facet: the intricate security considerations surrounding Docker networking. While Danish laid a solid foundation, let’s delve deeper into how we can fortify our containerized environments at the network level.

Beyond the Walls: Understanding Default Docker Network Isolation

As Danish aptly described, Docker’s inherent isolation, primarily achieved through Linux network namespaces, provides a foundational layer of security. Each container operates within its own isolated network stack, preventing direct port conflicts and limiting immediate interference. Think of it as each container having its own virtual network interface card and routing table within the host’s kernel.

However, it’s crucial to recognize that this isolation is a boundary, not an impenetrable fortress. Containers residing on the *same* Docker network (especially the default bridge network) can often communicate freely. This unrestricted lateral movement poses a significant risk. If one container is compromised, an attacker could potentially pivot and gain access to other services within the same network segment.

Architecting for Security: Leveraging Custom Networks for Granular Control

The first crucial step towards enhanced security is strategically utilizing **custom bridge networks**. Instead of relying solely on the default bridge, design your deployments with network segmentation in mind. Group logically related containers that *need* to communicate on dedicated networks.

Scenario: Microservices Deployment

Consider a microservices architecture with a front-end service, an authentication service, a user data service, and a payment processing service. We can create distinct networks:


docker network create frontend-network
docker network create backend-network
docker network create payment-network
        

Then, we connect the relevant containers:


docker run --name frontend --network frontend-network -p 80:80 frontend-image
docker run --name auth --network backend-network -p 8081:8080 auth-image
docker run --name users --network backend-network -p 8082:8080 users-image
docker run --name payment --network payment-network -p 8083:8080 payment-image
docker network connect frontend-network auth
docker network connect frontend-network users
docker network connect backend-network users
docker network connect payment-network auth
        

In this simplified example, the frontend can communicate with auth and users, which can also communicate internally on the backend-network. The highly sensitive payment service is isolated on its own network, only allowing necessary communication (e.g., with the auth service for verification).

The Fine-Grained Firewall: Implementing Network Policies with CNI Plugins

For truly granular control over inter-container traffic, **Docker Network Policies**, facilitated by CNI (Container Network Interface) plugins like Calico, Weave Net, Cilium, and others, are essential. These policies act as a micro-firewall at the container level, allowing you to define precise rules for ingress (incoming) and egress (outgoing) traffic based on labels, network segments, and port protocols.

Important: Network Policies are not a built-in feature of the default Docker networking stack. You need to install and configure a compatible CNI plugin to leverage them.

Conceptual Network Policy Example (Calico):

Let’s say we have our web-app (label: app=web) and database (label: app=db) on a backend-network. We want to allow only the web-app to access the database on its PostgreSQL port (5432).


apiVersion: networking.k8s.io/v1 # (Calico often aligns with Kubernetes NetworkPolicy API)
kind: NetworkPolicy
metadata:
  name: allow-web-to-db
spec:
  podSelector:
    matchLabels:
      app: db
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: web
    ports:
    - protocol: TCP
      port: 5432
  policyTypes:
  - Ingress
        

This (simplified) Calico NetworkPolicy targets pods (in a Kubernetes context, but the concept applies to labeled Docker containers with Calico) labeled app=db and allows ingress traffic only from pods labeled app=web on TCP port 5432. All other ingress traffic to the database would be denied.

Essential Best Practices for a Secure Docker Network

Beyond network segmentation and policies, a holistic approach to Docker network security involves several key best practices:

  • Apply the Principle of Least Privilege Network Access: Just as you would with user permissions, grant containers only the necessary network connections required for their specific function. Avoid broad, unrestricted access.
  • Isolate Sensitive Workloads on Dedicated, Strictly Controlled Networks: Databases, secret management tools, and other critical components should reside on isolated networks with rigorously defined and enforced network policies.
  • Internal Port Obfuscation: While exposing standard ports externally might be necessary, consider using non-default ports for internal communication between services on the same network. This adds a minor layer of defense against casual scanning.
  • Exercise Extreme Caution with --network host: This mode bypasses all container network isolation, directly exposing the container’s network interfaces on the host. It should only be used in very specific, well-understood scenarios with significant security implications considered. Often, there are better alternatives.
  • Implement Regular Network Configuration Audits: Periodically review your Docker network configurations, custom networks, and network policies (if implemented) to ensure they still align with your security posture and haven’t been inadvertently misconfigured.
  • Harden Host Firewalls: Regardless of your internal Docker network configurations, ensure your host machine’s firewall (e.g., iptables, ufw) is properly configured to control all inbound and outbound traffic to the host and any exposed container ports.
  • Consider Network Segmentation Beyond Docker: For larger and more complex environments, explore network segmentation at the infrastructure level (e.g., using VLANs or security groups in cloud environments) to further isolate groups of Docker hosts or nodes.
  • Maintain Up-to-Date Docker Engine and CNI Plugins: Regularly update your Docker engine and any installed CNI plugins to benefit from the latest security patches and feature enhancements. Vulnerabilities in these core components can have significant security implications.
  • Implement Robust Network Monitoring and Logging: Monitor network traffic within your Docker environment for suspicious patterns or unauthorized connection attempts. Centralized logging of network events can be invaluable for security analysis and incident response.
  • Secure Service Discovery Mechanisms: If you’re using service discovery tools within your Docker environment, ensure they are properly secured to prevent unauthorized registration or discovery of sensitive services.

Conclusion: A Multi-Layered Approach to Docker Network Security

Securing Docker networking is not a one-time configuration but an ongoing process that requires a layered approach. By understanding the nuances of Docker’s default isolation, strategically leveraging custom networks, implementing granular network policies with CNI plugins, and adhering to comprehensive best practices, you can significantly strengthen the security posture of your containerized applications. Don’t underestimate the network as a critical control plane in your container security strategy. Proactive and thoughtful network design is paramount to building resilient and secure container environments.

 

PostHeaderIcon Running Docker Natively on WSL2 (Ubuntu 24.04) in Windows 11

For many developers, Docker Desktop has long been the default solution to run Docker on Windows. However, licensing changes and the desire for a leaner setup have pushed teams to look for alternatives. Fortunately, with the maturity of Windows Subsystem for Linux 2 (WSL2), it is now possible to run the full Docker Engine directly inside a Linux distribution such as Ubuntu 24.04, while still accessing containers seamlessly from both Linux and Windows.

In this guide, I’ll walk you through a clean, step-by-step setup for running Docker Engine inside WSL2 without Docker Desktop, explain how Windows and WSL2 communicate, and share best practices for maintaining a healthy development environment.


Why Run Docker Inside WSL2?

Running Docker natively inside WSL2 has several benefits:

  • No licensing issues – you avoid Docker Desktop’s commercial license requirements.
  • Lightweight – no heavy virtualization layer; containers run directly inside your WSL Linux distro.
  • Integrated networking – on Windows 11 with modern WSL versions,
    containers bound to localhost inside WSL are automatically reachable from Windows.
  • Familiar Linux workflow – you install and use Docker exactly as you would on a regular Ubuntu server.

Step 1 – Update Ubuntu

Open your Ubuntu 24.04 terminal and ensure your system is up to date:

sudo apt update && sudo apt upgrade -y

Step 2 – Install Docker Engine

Install Docker using the official Docker repository:

# Install prerequisites
sudo apt install -y ca-certificates curl gnupg lsb-release

# Add Docker’s GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Configure Docker repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Step 3 – Run Docker Without sudo

To avoid prefixing every command with sudo, add your user to the docker group:

sudo usermod -aG docker $USER

Restart your WSL terminal for the change to take effect, then verify:

docker --version
docker ps

Step 4 – Test Networking

One of the most common questions is:
“Will my containers be accessible from both Ubuntu and Windows?”
The answer is yes on modern Windows 11 with WSL2.
Let’s test it by running an Nginx container:

docker run -d -p 8080:80 --name webtest nginx
  • Inside Ubuntu (WSL): curl http://localhost:8080
  • From Windows (browser or PowerShell): http://localhost:8080

Thanks to WSL2’s localhost forwarding, Windows traffic to localhost is routed
into the WSL network, making containers instantly accessible without extra configuration.


Step 5 – Run Multi-Container Applications with Docker Compose

The Docker Compose plugin is already installed as part of the package above. Check the version:

docker compose version

Create a docker-compose.yml for a WordPress + MySQL stack:

version: "3.9"
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppass
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppass
      WORDPRESS_DB_NAME: wordpress

volumes:
  db_data:

Start the services:

docker compose up -d

Once the containers are running, open http://localhost:8080 in your Windows browser
to access WordPress. The containers are managed entirely inside WSL2,
but networking feels seamless.


Maintenance: Cleaning Up Docker Data

Over time, Docker accumulates images, stopped containers, volumes, and networks.
This can take up significant disk space inside your WSL distribution.
Here are safe maintenance commands to keep your environment clean:

Remove Unused Objects

docker system prune -a --volumes
  • -a: removes all unused images, not just dangling ones
  • --volumes: also removes unused volumes

Reset Everything (Dangerous)

If you need to wipe your Docker environment completely (images, containers, volumes, networks):

docker stop $(docker ps -aq) 2>/dev/null
docker rm -f $(docker ps -aq) 2>/dev/null
docker volume rm $(docker volume ls -q) 2>/dev/null
docker network rm $(docker network ls -q) 2>/dev/null
docker image rm -f $(docker image ls -q) 2>/dev/null

⚠️ Use this only if you want to start fresh. All data will be removed.


Conclusion

By running Docker Engine directly inside WSL2, you gain a powerful, lightweight, and license-free Docker environment that integrates seamlessly with Windows 11. Your containers are accessible from both Linux and Windows, Docker Compose works out of the box, and maintenance is straightforward with prune commands.

This approach is particularly well-suited for developers who want the flexibility of Docker without the overhead of Docker Desktop. With WSL2 and Ubuntu 24.04, you get the best of both worlds: Linux-native Docker with Windows accessibility.

PostHeaderIcon Secure Development with Docker: DockerCon 2023 Workshop

The DockerCon 2023 workshop, “Secure Development with Docker,” delivered by Yves Brissaud, James Carnegie, David Dooling, and Christian Dupuis from Docker, offered a comprehensive exploration of securing the software supply chain. Spanning over three hours, this session addressed the tension between developers’ need for speed and security teams’ focus on risk mitigation. Participants engaged in hands-on labs to identify and remediate common vulnerabilities, leverage Docker Scout for actionable insights, and implement provenance, software bills of materials (SBOMs), and policies. The workshop emphasized Docker’s developer-centric approach to security, empowering attendees to enhance their workflows without compromising safety. By integrating Docker Scout, attendees learned to secure every stage of the software development lifecycle, from code to deployment.

Tackling Common Vulnerabilities and Exposures (CVEs)

The workshop began with a focus on Common Vulnerabilities and Exposures (CVEs), a critical starting point for securing software. David Dooling introduced CVEs as publicly disclosed cybersecurity vulnerabilities in operating systems, dependencies like OpenSSL, or container images. Participants used Docker Desktop 4.24 and the Docker Scout CLI to scan images based on Alpine 3.14, identifying vulnerabilities in base images and added layers, such as npm packages (e.g., Express and its transitive dependency Qs). Hands-on exercises guided attendees to update base images to Alpine 3.18, using Docker Scout’s recommendations to select versions with fewer vulnerabilities. The CLI’s cve command and Desktop’s vulnerability view provided detailed insights, including severity filters and package details, enabling developers to remediate issues efficiently. This segment underscored that while scanning is essential, it’s only one part of a broader security strategy, setting the stage for a holistic approach.

Understanding Software Supply Chain Security

The second segment, led by Dooling, introduced the software supply chain as a framework encompassing source code, dependencies, build processes, and deployment. Drawing an analogy to brewing coffee—where beans, water, and equipment have their own supply chains—the workshop highlighted risks like supply chain attacks, as outlined by CISA’s open-source security roadmap. These attacks, such as poisoning repositories, differ from CVEs by involving intentional tampering. Participants explored Docker Scout’s role as a supply chain management tool, not just a CVE scanner. Using the workshop’s GitHub repository (dc23-secure-workshop), attendees set up environment variables and Docker Compose to build images, learning how Scout tracks components across the lifecycle. This segment emphasized the need to secure every stage, from code creation to deployment, to prevent vulnerabilities and malicious injections.

Leveraging Docker Scout for Actionable Insights

Docker Scout was the cornerstone of the workshop, offering a developer-friendly interface to manage security. Yves Brissaud guided participants through hands-on labs using Docker Desktop and the Scout CLI to analyze images. Attendees explored vulnerabilities in a front-end image (using Express) and a Go-based back-end image, applying filters to focus on critical CVEs or specific package types (e.g., npm). Scout’s compare command allowed participants to assess changes between image versions, such as updating from Alpine 3.14 to 3.18, revealing added or removed packages and their impact on vulnerabilities. Desktop’s visual interface displayed recommended fixes, like updating base images or dependencies, while the CLI provided detailed outputs, including quick views for rapid assessments. This segment demonstrated Scout’s ability to integrate into CI/CD pipelines, providing early feedback to developers without disrupting workflows.

Implementing Provenance and Software Bill of Materials (SBOM)

The third segment focused on provenance and SBOMs, critical for supply chain transparency. Provenance, aligned with the SALSA framework’s Build Level 1, documents how an image is built, including base image tags, digests, and build metadata. SBOMs list all packages and their versions, ensuring consistency across environments. Participants rebuilt images with the --provenance and --sbom flags using BuildKit, generating attestations stored in Docker Hub. Brissaud demonstrated using the imagetools command to inspect provenance and SBOMs, revealing details like build timestamps and package licenses. The workshop highlighted the importance of embedding this metadata at build time to enable reproducible builds and accurate recommendations. By integrating Scout’s custom SBOM indexer, attendees ensured consistent vulnerability reporting across Desktop, CLI, and scout.docker.com, enhancing trust in the software’s integrity.

Enforcing Developer-Centric Policies

The final segment introduced Docker Scout’s policy enforcement, designed with a developer mindset to avoid unnecessary build failures. Dooling explained Scout’s “first do no harm” philosophy, rooted in Kaizen’s continuous improvement principles. Unlike traditional policies that block builds for existing CVEs, Scout compares new builds to production images, allowing progress if vulnerabilities remain unchanged. Participants explored four out-of-the-box policies in Early Access: fixing critical/high CVEs, updating base images, and avoiding deprecated tags. Using the scout policy command, attendees evaluated images against these policies, viewing compliance status on Desktop and scout.docker.com. The workshop also previewed upcoming GitHub Action integrations for pull request policy checks, enabling developers to assess changes before merging. This approach ensures security without hindering development, aligning with Docker’s mission to empower developers.

Hashtags: #DockerCon2023 #SoftwareSupplyChain #DockerScout #SecureDevelopment #CVEs #Provenance #SBOM #Policy #YvesBrissaud #JamesCarnegie #DavidDooling #ChristianDupuis

PostHeaderIcon Navigating the Application Lifecycle in Kubernetes

At Devoxx France 2019, Charles Sabourdin and Jean-Christophe Sirot, seasoned professionals in cloud-native technologies, delivered an extensive exploration of managing application lifecycles within Kubernetes. Charles, an architect with over 15 years in Linux and Java, and Jean-Christophe, a Docker expert since 2002, combined their expertise to demystify Docker’s underpinnings, Kubernetes’ orchestration, and the practicalities of continuous integration and delivery (CI/CD). Through demos and real-world insights, they addressed security challenges across development and business-as-usual (BAU) phases, proposing organizational strategies to streamline containerized workflows. This post captures their comprehensive session, offering a roadmap for developers and operations teams navigating Kubernetes ecosystems.

Docker’s Foundations: Isolation and Layered Efficiency

Charles opened the session by revisiting Docker’s core principles, emphasizing its reliance on Linux kernel features like namespaces and control groups (cgroups). Unlike virtual machines (VMs), which bundle entire operating systems, Docker containers share the host kernel, isolating processes within lightweight environments. This design achieves hyper-density, allowing more containers to run on a single machine compared to VMs. Charles demonstrated launching a container, highlighting its process isolation using commands like ps within a containerized bash session, contrasting it with the host’s process list. He introduced Docker’s layer system, where images are built as immutable, stacked deltas, optimizing storage through shared base layers. Tools like Dive, he noted, help inspect these layers, revealing command histories and suggesting size optimizations. This foundation sets the stage for Kubernetes, enabling efficient, portable application delivery across environments.

Kubernetes: Orchestrating Scalable Deployments

Jean-Christophe transitioned to Kubernetes, describing it as a resource orchestrator that manages containerized applications across node pools. Kubernetes abstracts infrastructure complexities, using declarative configurations to maintain desired application states. Key components include pods—the smallest deployable units housing containers—replica sets for scaling, and deployments for managing updates. Charles demonstrated creating a namespace and deploying a sample application using kubectl run, which scaffolds deployments, replica sets, and pods. He showcased rolling updates, where Kubernetes progressively replaces pods to ensure zero downtime, configurable via parameters like maxSurge and maxUnavailable. The duo emphasized Kubernetes’ auto-scaling capabilities, which adjust pod counts based on load, and the importance of defining resource limits to prevent performance bottlenecks. Their demo underscored Kubernetes’ role in achieving resilient, scalable deployments, aligning with hyper-density goals.

CI/CD Pipelines: Propagating Versions Seamlessly

The session delved into CI/CD pipelines, illustrating how Docker tags facilitate version propagation across development, pre-production, and production environments. Charles outlined a standard process: developers build Docker images tagged with version numbers (e.g., 1.11.2) or environment labels (e.g., prodstaging). These images, stored in registries like Docker Hub or private repositories, are pulled by Kubernetes clusters for deployment. Jean-Christophe highlighted debates around tagging strategies, noting that version-based tags ensure traceability, while environment tags simplify environment-specific deployments. Their demo integrated tools like Jenkins and JFrog Artifactory, automating builds, tests, and deployments. They stressed the need for robust pipeline configurations to avoid resource overuse, citing Jenkins’ default manual build triggers for tagged releases as a safeguard. This pipeline approach ensures consistent, automated delivery, bridging development and production.

Security Across the Lifecycle: Development vs. BAU

Security emerged as a central theme, with Charles contrasting development and BAU phases. During development, teams rapidly address Common Vulnerabilities and Exposures (CVEs) with frequent releases, leveraging tools like JFrog Xray and Clair to scan images for vulnerabilities. Xray integrates with Artifactory, while Clair, an open-source solution, scans registry images for known CVEs. However, in BAU, where releases are less frequent, unpatched vulnerabilities pose greater risks. Charles shared an anecdote about a PHP project where a dependency switch broke builds after two years, underscoring the need for ongoing maintenance. They advocated for practices like running containers in read-only mode and using non-root users to minimize attack surfaces. Tools like OWASP Dependency-Track, they suggested, could enhance visibility into library vulnerabilities, though current scanners often miss non-package dependencies. This dichotomy highlights the need for automated, proactive security measures throughout the lifecycle.

Organizational Strategies: Balancing Complexity and Responsibility

Drawing from their experiences, Charles and Jean-Christophe proposed organizational solutions to manage Kubernetes complexity. They introduced a “1-2-3 model” for image management: Level 1 uses vendor-provided images (e.g., official MySQL images) managed by operations; Level 2 involves base images built by dedicated teams, incorporating standardized tooling; and Level 3 allows project-specific images, with teams assuming maintenance responsibilities. This model clarifies ownership, reducing risks like disappearing maintainers when projects transition to BAU. They emphasized cross-team collaboration, encouraging developers and operations to share knowledge and align on practices like Dockerfile authorship and resource allocation in YAML configurations. Charles reflected on historical DevOps silos, advocating for shared vocabularies and traceable decisions to navigate evolving best practices. Their return-of-experience underscored the importance of balancing automation with human oversight to maintain robust, secure Kubernetes environments.

Hashtags: #Kubernetes #Docker #DevOps #CICD #Security #DevoxxFR #CharlesSabourdin #JeanChristopheSirot #JFrog #Clair

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