jongkwan.dev
Development · Essay №023

Containerization and Orchestration: An In-Depth Guide

Multi-stage builds, image security, advanced scheduling, service mesh, and other container operations in depth

Jongkwan Lee2025년 11월 22일7 min read
Contents

A container packages the runtime environment, and once the scale grows, orchestration automates deployment and recovery.

A container bundles an application together with its runtime environment into a single package so it behaves the same anywhere. Once the number of containers grows, handling deployment, networking, storage, and failure recovery by hand becomes impractical. Orchestration automates that work through declarative configuration.

Advanced container build strategies

Multi-stage builds

Keeping build tools and compilers out of the final image shrinks the runtime image. A build stage produces the binary, and only the artifact is copied into the runtime stage.

dockerfile
# build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o main .
 
# runtime stage
FROM alpine:3.18
COPY --from=builder /app/main /main
CMD ["/main"]

COPY --from=builder pulls in only the build stage's output, so the Go compiler and the source code never reach the final image.

Image layer optimization

Docker caches layer by layer, so the later a frequently changing file is copied, the longer the cache survives. Copying the dependency manifest first and installing from it, then copying source code, lets the dependency install layer stay cached when only code changes.

dockerfile
# Bad: poor cache efficiency
COPY . .
RUN npm install
 
# Good: good cache efficiency
COPY package*.json ./
RUN npm install
COPY . .

Image security

ToolPurpose
TrivyVulnerability scanning
cosign / NotationImage signing
distrolessMinimal base image

Image signing was once close to standardized on Notary (v1), but the trend has moved to Sigstore cosign and Notation. distroless is a base image with no shell and no package manager, which shrinks the attack surface.

bash
# scan for vulnerabilities with Trivy
trivy image my-app:latest

Resource isolation and tuning

cgroups and namespaces

Control groups (cgroups) isolate CPU, memory, I/O, and network usage at the Linux kernel level. Namespaces separate what a process can see of processes, networks, and filesystems.

yaml
# Kubernetes resource limits
resources:
  requests:
    memory: "64Mi"
    cpu: "250m"
  limits:
    memory: "128Mi"
    cpu: "500m"

requests is the minimum the scheduler guarantees when placing the Pod, and limits is the ceiling the container may not exceed. Exceeding the memory limit terminates the container (OOMKilled), while CPU is throttled at the ceiling instead.

Container runtimes

RuntimeCharacteristics
containerdLightweight, the Kubernetes default
CRI-OKubernetes-only
gVisorSecurity sandbox

Rootless containers

Running containers without host root privileges means a container escape does not hand the attacker root on the host.

bash
# Podman rootless mode
podman run --user 1000:1000 nginx

Advanced scheduling

Node affinity

Places Pods only on nodes matching a label condition. The example below forces scheduling onto nodes labeled gpu=true.

yaml
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: gpu
          operator: In
          values:
          - "true"

Pod anti-affinity

Spreads Pods of the same kind across different nodes so one node failing does not take everything down with it. topologyKey sets the unit of separation; kubernetes.io/hostname spreads them per node.

yaml
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchLabels:
          app: web
      topologyKey: kubernetes.io/hostname

Taints and tolerations

Tainting a node means only Pods that tolerate that taint land on it. Node affinity is the Pod choosing a node; a taint is the node pushing Pods away.

bash
# add a taint to a node
kubectl taint nodes gpu-node gpu=true:NoSchedule
yaml
# add a toleration to a Pod
tolerations:
- key: "gpu"
  operator: "Equal"
  value: "true"
  effect: "NoSchedule"

Service mesh

A service mesh handles communication between microservices in a separate infrastructure layer. A sidecar takes over traffic control, security, and observability instead of the application code.

CapabilityDescription
Traffic controlRouting, load balancing
SecurityAutomatic mutual TLS (mTLS)
ObservabilityDistributed tracing

Major implementations

ImplementationCharacteristics
IstioThe most features, the most complexity
LinkerdLightweight, easy to run
Consul ConnectThe HashiCorp ecosystem

The sidecar pattern

Attaching an Envoy proxy as a sidecar next to the application container adds networking capability without touching the code. All inbound and outbound traffic passes through the proxy, so mTLS and tracing can be applied uniformly.

Storage orchestration

CSI (Container Storage Interface)

The Container Storage Interface (CSI) exposes different storage backends such as AWS EBS, GCP PD, and NFS through one consistent interface.

yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

volumeBindingMode: WaitForFirstConsumer delays volume creation until the Pod is actually scheduled. The volume then lands in the same availability zone as the Pod, which prevents mount failures caused by zone mismatch.

Dynamic provisioning

Creating a PersistentVolumeClaim (PVC) makes the StorageClass provision and bind a matching PersistentVolume (PV) automatically. An administrator does not have to pre-create volumes.

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  storageClassName: fast
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

Autoscaling

HPA (Horizontal Pod Autoscaler)

Scales Pod count up or down to hold CPU or memory utilization at a target. The example below adjusts replicas between 2 and 10 against a 70% average CPU utilization target.

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

KEDA (Kubernetes Event-Driven Autoscaling)

Scales on external event metrics such as queue length or message backlog rather than CPU and memory. In the example below, lagThreshold: "100" means consumer Pods are added once a Kafka consumer has more than 100 unprocessed messages per partition.

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: kafka-scaler
spec:
  scaleTargetRef:
    name: consumer
  triggers:
  - type: kafka
    metadata:
      topic: orders
      lagThreshold: "100"

Observability

The monitoring stack

  • Metrics: Prometheus -> Grafana
  • Logs: Fluentd -> Elasticsearch -> Kibana
  • Tracing: Jaeger or Zipkin

GitOps

The Git repository is the single source of truth, and Argo CD or Flux continuously reconciles cluster state against it. Because changes happen only through Git commits, deployment history and rollbacks live at commit granularity.

Summary

AreaKey technologies
BuildMulti-stage builds, image scanning
SchedulingAffinity, taints and tolerations
NetworkingService mesh, Container Network Interface (CNI)
StorageCSI, dynamic provisioning
ScalingHPA, Vertical Pod Autoscaler (VPA), KEDA
OperationsGitOps, observability

Container orchestration automates deployment, recovery, and scaling through declarative configuration, then verifies the result through observability. At the image stage, multi-stage builds cut size while scanning and signing establish trust. Scheduling controls placement with affinity and taints/tolerations based on node characteristics, and storage attaches volumes automatically through CSI and dynamic provisioning. Scaling splits by signal: HPA for CPU and memory, KEDA for event-driven measures such as queue backlog. Operations records every change as a commit through GitOps and ties metrics, logs, and traces together to track failures.