DEV Community

Cover image for Kubernetes and Helm: Managing Containers at Scale
Rhuturaj Takle
Rhuturaj Takle

Posted on

Kubernetes and Helm: Managing Containers at Scale

Kubernetes and Helm: Managing Containers at Scale

A practical guide to Kubernetes — the container orchestration platform for deploying, scaling, and managing containerized applications — and Helm, its package manager, covering core objects, networking, configuration, scaling, and packaging applications for reuse.


Table of Contents

  1. Introduction
  2. Why Kubernetes Exists
  3. Core Objects
  4. Deployments and ReplicaSets
  5. Services and Networking
  6. Ingress
  7. ConfigMaps and Secrets
  8. Storage: Volumes and Persistent Volume Claims
  9. Scaling
  10. Health Probes
  11. Namespaces and RBAC
  12. Helm: Packaging Kubernetes Applications
  13. Helm Templating in Depth
  14. Deployment Strategies
  15. Quick Reference Table
  16. Conclusion

Introduction

Kubernetes (often "k8s") is a container orchestration platform — it takes the containers covered in this series' Docker guide and manages many of them, across many machines, handling scheduling, scaling, networking, self-healing, and rolling updates so that running dozens or hundreds of containerized services doesn't mean manually tracking which container runs on which server. Helm sits on top of Kubernetes as its de facto package manager, letting a complex, multi-object Kubernetes application be packaged, versioned, and installed with a single command, the way apt or NuGet manages packages for an OS or a codebase.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-api
spec:
  replicas: 3
  selector:
    matchLabels: { app: product-api }
  template:
    metadata:
      labels: { app: product-api }
    spec:
      containers:
        - name: product-api
          image: myregistry.azurecr.io/product-api:1.0
          ports: [{ containerPort: 8080 }]
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f deployment.yaml
Enter fullscreen mode Exit fullscreen mode

That YAML declares a desired state — "3 replicas of this container image should be running" — and Kubernetes continuously works to make reality match it, restarting failed containers, rescheduling them onto healthy nodes, and rolling out updates without downtime.


1. Why Kubernetes Exists

The problem: running containers at scale is more than docker run

A single docker run command works fine for one container on one machine. Real production systems need:

  • Scheduling — deciding which of many available machines (nodes) should run which containers, based on available capacity.
  • Self-healing — automatically restarting a crashed container, or rescheduling it onto a different node if the original node fails entirely.
  • Scaling — running more or fewer copies of a container based on load, without manual intervention.
  • Service discovery — letting containers find and communicate with each other reliably, even as individual container instances come and go.
  • Rolling updates — deploying a new version without downtime, and rolling back automatically if something goes wrong.
  • Load balancing — distributing traffic across multiple instances of the same service.

Kubernetes provides all of this as a coherent, declarative system — you describe the desired end state (as YAML manifests), and a set of control loops continuously reconcile the actual cluster state toward that description, the same declarative philosophy covered in this series' Terraform/Bicep guide, applied specifically to running containers.

Declarative, not imperative

# Imperative: "do these specific steps"
docker run -d --name api1 my-api:1.0
docker run -d --name api2 my-api:1.0
docker run -d --name api3 my-api:1.0

# Declarative: "this is what should exist"
kubectl apply -f deployment.yaml   # "3 replicas of my-api:1.0 should be running" — Kubernetes figures out how
Enter fullscreen mode Exit fullscreen mode

If a node crashes and takes api2 down with it, the imperative approach requires someone (or some script) to notice and manually restart it. The declarative approach means Kubernetes' control loop notices the actual state (2 running replicas) no longer matches the desired state (3 replicas) and schedules a replacement automatically, with no human intervention.


2. Core Objects

Kubernetes resources are defined as YAML manifests and applied to a cluster — every object shares a common structure:

apiVersion: apps/v1      # which Kubernetes API version/group this object belongs to
kind: Deployment          # the type of object being defined
metadata:
  name: product-api       # this object's name within its namespace
  labels:
    app: product-api       # arbitrary key-value tags used for selection/grouping
spec:                      # the desired state, specific to this object's kind
  # ...
Enter fullscreen mode Exit fullscreen mode

Pods: the smallest deployable unit

apiVersion: v1
kind: Pod
metadata:
  name: product-api-pod
spec:
  containers:
    - name: product-api
      image: myregistry.azurecr.io/product-api:1.0
      ports:
        - containerPort: 8080
Enter fullscreen mode Exit fullscreen mode

A Pod wraps one or more containers that are always scheduled together, on the same node, sharing a network namespace (they can reach each other via localhost) and optionally storage. Most pods run a single container; multiple containers in one pod are typically used for sidecar patterns (a logging agent, a service mesh proxy running alongside the main application container). In practice, you rarely create bare Pods directly — a Deployment (Section 3) manages them for you.


3. Deployments and ReplicaSets

Deployment: the standard way to run a stateless application

apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: product-api
  template:
    metadata:
      labels:
        app: product-api
    spec:
      containers:
        - name: product-api
          image: myregistry.azurecr.io/product-api:1.0
          ports:
            - containerPort: 8080
          resources:
            requests: { cpu: "250m", memory: "256Mi" }
            limits: { cpu: "500m", memory: "512Mi" }
Enter fullscreen mode Exit fullscreen mode

A Deployment manages a ReplicaSet (which in turn manages the actual Pods), and adds rolling-update behavior on top — updating the image tag and reapplying the manifest triggers a controlled rollout, replacing old pods with new ones incrementally rather than all at once (Section 13 covers this rollout behavior in detail).

Resource requests and limits

resources:
  requests: { cpu: "250m", memory: "256Mi" }   # guaranteed minimum, used for scheduling decisions
  limits: { cpu: "500m", memory: "512Mi" }      # hard ceiling, enforced at runtime
Enter fullscreen mode Exit fullscreen mode

Requests tell the Kubernetes scheduler how much capacity to reserve for this pod when deciding which node to place it on; limits are the hard ceiling the container is never allowed to exceed (a memory limit breach results in the container being killed and restarted — an "OOMKilled" event, one of the most common things to check when a pod is unexpectedly restarting). Setting these accurately (not too generous, not too tight) is directly connected to the cost-optimization guidance in this series' Cloud Cost Optimization guide — over-requested resources reserve cluster capacity that then sits idle.

Rolling updates in action

kubectl set image deployment/product-api product-api=myregistry.azurecr.io/product-api:2.0
kubectl rollout status deployment/product-api
kubectl rollout undo deployment/product-api   # roll back to the previous revision if something's wrong
Enter fullscreen mode Exit fullscreen mode

By default, a Deployment replaces pods incrementally — bringing up new pods (running the new image) while gradually terminating old ones, governed by maxSurge (how many extra pods can exist temporarily above the desired replica count) and maxUnavailable (how many pods can be unavailable during the rollout) settings, aiming for zero-downtime updates for a properly health-checked application (Section 9).


4. Services and Networking

The problem Services solve

Pods are ephemeral — they get created, destroyed, and rescheduled, each time potentially with a new internal IP address. Something that needs to reliably reach "the product API" can't hardcode a specific pod's IP, since that IP might not exist a minute later.

apiVersion: v1
kind: Service
metadata:
  name: product-api-service
spec:
  selector:
    app: product-api   # matches pods with this label, regardless of which specific pods currently exist
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP
Enter fullscreen mode Exit fullscreen mode

A Service provides a stable network identity (a consistent internal DNS name and virtual IP) in front of a dynamically changing set of pods matched by label selector — other pods reach product-api-service (resolved automatically via Kubernetes' internal DNS) regardless of which actual pod instances are currently backing it, and traffic is load-balanced across all matching, healthy pods automatically.

Service types

Type Behavior
ClusterIP (default) Internal-only, reachable only from within the cluster
NodePort Exposes the service on a static port on every node's IP — a simple, if somewhat blunt, way to expose something externally
LoadBalancer Provisions an actual cloud load balancer (an Azure Load Balancer, an AWS ELB) pointing at the service — the standard way to expose a service to the public internet on a cloud-managed cluster
ExternalName Maps a Service name to an external DNS name, useful for referring to something outside the cluster (a managed database) via the same internal service-discovery mechanism
spec:
  type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

Setting type: LoadBalancer on a cloud-managed cluster (AKS, EKS) automatically provisions the corresponding cloud load balancer resource and wires it up — the same underlying mechanism as the load balancer integration described in this series' Azure Compute and AWS Compute guides, just triggered declaratively from within Kubernetes rather than configured directly against the cloud provider's API.


5. Ingress

Routing HTTP traffic by hostname/path, not just by port

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /products
            pathType: Prefix
            backend:
              service:
                name: product-api-service
                port: { number: 80 }
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: order-api-service
                port: { number: 80 }
Enter fullscreen mode Exit fullscreen mode

A LoadBalancer Service works fine for exposing a single service, but provisioning a separate cloud load balancer for every individual service in a cluster of dozens of microservices gets expensive and unwieldy quickly. Ingress provides HTTP(S)-aware routing — one entry point (backed by an Ingress Controller, like NGINX, Traefik, or a cloud-native equivalent) routing traffic to many different backend Services based on hostname and path, much closer to how a traditional reverse proxy or API gateway works.

Ingress needs an Ingress Controller

An Ingress manifest alone does nothing — it requires an Ingress Controller (a separate piece of software, typically deployed as its own set of pods) actually running in the cluster to read Ingress objects and configure the underlying proxy accordingly. Managed Kubernetes offerings (AKS, EKS) often provide an easy way to install a common controller (NGINX Ingress Controller being especially widespread), or a cloud-native alternative (AWS ALB Ingress Controller, Azure Application Gateway Ingress Controller) that provisions the cloud's own load balancer/gateway product directly from Ingress definitions.


6. ConfigMaps and Secrets

ConfigMaps: non-sensitive configuration

apiVersion: v1
kind: ConfigMap
metadata:
  name: product-api-config
data:
  ASPNETCORE_ENVIRONMENT: "Production"
  Logging__LogLevel__Default: "Information"
Enter fullscreen mode Exit fullscreen mode
spec:
  containers:
    - name: product-api
      envFrom:
        - configMapRef:
            name: product-api-config
Enter fullscreen mode Exit fullscreen mode

A ConfigMap externalizes non-sensitive configuration from the container image itself — the same image can run with different configuration in different environments (dev, staging, production) by supplying a different ConfigMap, without rebuilding the image, mirroring the environment-variable-based configuration pattern covered in this series' Docker and ASP.NET Core guides.

Secrets: sensitive configuration

apiVersion: v1
kind: Secret
metadata:
  name: product-api-secrets
type: Opaque
data:
  ConnectionStrings__Default: <base64-encoded-value>
Enter fullscreen mode Exit fullscreen mode
spec:
  containers:
    - name: product-api
      envFrom:
        - secretRef:
            name: product-api-secrets
Enter fullscreen mode Exit fullscreen mode

Kubernetes Secrets are conceptually similar to ConfigMaps but intended for sensitive values — importantly, by default they're only base64-encoded, not encrypted, which is easily misunderstood as a security guarantee it doesn't actually provide (anyone with access to read the Secret object, or the underlying etcd datastore, can trivially decode it). For genuine secret protection, pair Kubernetes Secrets with encryption at rest (a cluster-level configuration) and, more robustly, integrate with an external secrets manager — the Secrets Store CSI Driver pulling from Azure Key Vault or AWS Secrets Manager, as covered in this series' Azure Compute guide, so the actual secret value lives in a proper secrets management system rather than as a merely-encoded Kubernetes object.


7. Storage: Volumes and Persistent Volume Claims

The same ephemeral-container problem as Docker, at cluster scale

As covered in this series' Docker guide, a container's filesystem is ephemeral — Kubernetes' storage model extends the volume concept to work across a dynamic, multi-node cluster, where a pod (and its data) might be rescheduled to a completely different physical machine.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: product-data-pvc
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 10Gi
  storageClassName: managed-premium
Enter fullscreen mode Exit fullscreen mode
spec:
  containers:
    - name: product-api
      volumeMounts:
        - name: data
          mountPath: /app/data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: product-data-pvc
Enter fullscreen mode Exit fullscreen mode

A PersistentVolumeClaim (PVC) requests storage with specific characteristics (size, access mode, performance tier via storageClassName) without the pod needing to know exactly which physical disk or cloud storage resource actually backs it — a StorageClass (often provided automatically by the managed Kubernetes offering, dynamically provisioning an Azure Disk or AWS EBS volume behind the scenes) handles the actual provisioning, keeping the pod's manifest portable across environments and cloud providers.

Access modes

Mode Meaning
ReadWriteOnce Mounted read-write by a single node at a time — the common case for most application data
ReadOnlyMany Mounted read-only by many nodes simultaneously
ReadWriteMany Mounted read-write by many nodes simultaneously — requires a storage backend that supports this (not all do)

Most stateful application data (a database's files) uses ReadWriteOnce, since a single database process typically owns its data directory exclusively — ReadWriteMany is reserved for genuinely shared storage needs (a shared file upload directory accessed by multiple pod replicas), and requires a compatible storage backend (like Azure Files or NFS) rather than typical block storage.


8. Scaling

Horizontal Pod Autoscaler (HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: product-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: product-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
Enter fullscreen mode Exit fullscreen mode

The HPA automatically adjusts a Deployment's replica count based on observed metrics (CPU utilization, memory, or custom application metrics via an adapter) — scaling out under load and back in as demand drops, the same automatic-scaling principle covered throughout this series' cloud compute guides, expressed here as a native Kubernetes object rather than a cloud-provider-specific mechanism.

Cluster Autoscaler: scaling the nodes themselves

As covered in this series' Azure Compute guide, the HPA scales the number of pods, but if there isn't enough underlying node capacity to actually schedule those additional pods, they'll sit Pending indefinitely. The Cluster Autoscaler operates at the layer below the HPA, adding or removing actual VM nodes based on whether pods are waiting to be scheduled due to insufficient cluster capacity — the two work together, the HPA deciding "we need more pods" and the Cluster Autoscaler deciding "we need more machines to run them on."

Vertical Pod Autoscaler (VPA)

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: product-api-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: product-api
  updatePolicy:
    updateMode: "Off"   # recommendation-only mode — doesn't automatically apply changes
Enter fullscreen mode Exit fullscreen mode

Rather than adjusting replica count, the VPA recommends (or, in more aggressive modes, automatically applies) adjustments to a pod's resource requests/limits themselves, based on observed actual usage — directly addressing the right-sizing concern from this series' Cloud Cost Optimization guide, at the individual pod level. updateMode: "Off" (recommendation-only) is a common, lower-risk starting point before trusting the VPA to actually apply changes automatically.


9. Health Probes

Liveness probes: is this container still working?

livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 15
Enter fullscreen mode Exit fullscreen mode

If a liveness probe fails repeatedly, Kubernetes restarts the container — appropriate for detecting a genuinely stuck or deadlocked process that a simple restart would fix, connecting directly to the /health endpoint patterns covered in this series' Background Services and ASP.NET Core guides.

Readiness probes: is this container ready to receive traffic?

readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
Enter fullscreen mode Exit fullscreen mode

If a readiness probe fails, Kubernetes stops routing traffic to that pod via its Service (without restarting it) — appropriate for a temporary condition (still warming up a cache, a downstream dependency being briefly unavailable) where restarting would be unnecessary or even counterproductive, but the pod genuinely shouldn't receive traffic right now.

Why both matter for a clean rolling update

A rolling update (Section 3) relies heavily on readiness probes specifically — Kubernetes waits for a new pod's readiness probe to pass before considering it "up" and routing traffic to it, and correspondingly before terminating an old pod. A missing or naively-implemented readiness probe (one that just returns "OK" immediately, before the application has actually finished initializing) is a common, easy-to-miss cause of brief traffic errors during what should be a zero-downtime deployment.


10. Namespaces and RBAC

Namespaces: logical isolation within a cluster

kubectl create namespace staging
kubectl apply -f deployment.yaml -n staging
Enter fullscreen mode Exit fullscreen mode

Namespaces partition a single physical cluster into multiple logical, isolated environments — commonly used to separate dev/staging/production within one cluster (though many organizations also use fully separate physical clusters per environment for stronger isolation), or to separate different teams'/applications' resources within a shared cluster, each with their own resource quotas and access controls.

Role-Based Access Control (RBAC)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: staging
  name: deployment-manager
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "update"]
Enter fullscreen mode Exit fullscreen mode
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deploy-binding
  namespace: staging
subjects:
  - kind: ServiceAccount
    name: ci-deployer
roleRef:
  kind: Role
  name: deployment-manager
  apiGroup: rbac.authorization.k8s.io
Enter fullscreen mode Exit fullscreen mode

RBAC governs who (a user, or a ServiceAccount representing an automated process like a CI/CD pipeline) can perform which actions (get, list, update, delete) against which resource types, scoped to a specific namespace or cluster-wide — the same least-privilege principle covered throughout this series (IAM roles in AWS, task roles in ECS, database permissions in SQL Server/PostgreSQL) applied to the Kubernetes API itself, ensuring a CI pipeline's deploy credentials, for instance, can update Deployments in staging without also being able to delete Secrets in production.


11. Helm: Packaging Kubernetes Applications

The problem Helm solves

A real application often needs a Deployment, a Service, an Ingress, a ConfigMap, a Secret, an HPA, and more — several separate YAML files, often with values (image tags, replica counts, resource limits) that need to differ between environments. Manually managing and templating all of this with raw kubectl apply commands and hand-edited YAML doesn't scale well across environments or across many similar applications.

my-app-chart/
  Chart.yaml           # chart metadata (name, version)
  values.yaml           # default configuration values
  templates/
    deployment.yaml
    service.yaml
    ingress.yaml
    configmap.yaml
  charts/                # dependency charts (subcharts), if any
Enter fullscreen mode Exit fullscreen mode

A Helm chart packages a complete Kubernetes application — templated manifests plus a set of configurable values — into a single, versioned, installable unit, conceptually similar to a NuGet package or a Docker image, but for an entire multi-object Kubernetes application rather than a single library or container.

Installing and managing releases

helm install my-app ./my-app-chart --namespace production --values production-values.yaml
helm upgrade my-app ./my-app-chart --values production-values.yaml
helm rollback my-app 1   # roll back to a previous release revision
helm uninstall my-app
Enter fullscreen mode Exit fullscreen mode

Each helm install/upgrade creates a tracked release — Helm keeps a revision history, making helm rollback a simple, reliable way to revert an entire application (all its Kubernetes objects together, consistently) to a previous known-good configuration, not just a single Deployment's container image the way kubectl rollout undo does.

Public and private chart repositories

helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-redis bitnami/redis
Enter fullscreen mode Exit fullscreen mode

A huge ecosystem of publicly available charts (for Redis, PostgreSQL, Prometheus, and hundreds of other common pieces of infrastructure) means standing up a well-configured instance of common infrastructure is often a single helm install away, rather than hand-writing the equivalent Deployment/Service/PVC/ConfigMap manifests from scratch — directly analogous to reaching for a Terraform Registry module (covered in this series' Terraform/Bicep guide) instead of writing infrastructure definitions from first principles.


12. Helm Templating in Depth

values.yaml: configurable defaults

# values.yaml
replicaCount: 3
image:
  repository: myregistry.azurecr.io/product-api
  tag: "1.0"
resources:
  requests: { cpu: "250m", memory: "256Mi" }
  limits: { cpu: "500m", memory: "512Mi" }
Enter fullscreen mode Exit fullscreen mode

Templates reference values with Go template syntax

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-product-api
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}-product-api
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-product-api
    spec:
      containers:
        - name: product-api
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
Enter fullscreen mode Exit fullscreen mode

{{ .Values.* }} references pull from values.yaml (or an override file supplied at install time), and {{ .Release.Name }} is a built-in variable referring to the specific release name given at helm install — this is what lets the same chart be installed multiple times, side by side, with different names/namespaces/configuration, without manifest name collisions.

Environment-specific overrides

# production-values.yaml — overrides just the values that differ from values.yaml's defaults
replicaCount: 10
image:
  tag: "2.0"
resources:
  requests: { cpu: "500m", memory: "512Mi" }
Enter fullscreen mode Exit fullscreen mode
helm install my-app ./my-app-chart -f production-values.yaml
Enter fullscreen mode Exit fullscreen mode

Rather than maintaining entirely separate manifest sets per environment (as you might with raw YAML), a single chart with environment-specific values files keeps the actual Kubernetes object structure defined once, with only the environment-specific values varying — reducing duplication and the risk of environments' manifests silently drifting apart in structure, not just configuration.

Subcharts and dependencies

# Chart.yaml
dependencies:
  - name: postgresql
    version: "12.x"
    repository: "https://charts.bitnami.com/bitnami"
Enter fullscreen mode Exit fullscreen mode

A chart can declare dependencies on other charts (subcharts) — an application chart might depend on a postgresql subchart, letting helm install bring up both the application and a correctly configured database together as one coherent unit, similar in spirit to how Docker Compose brings up multiple related services together for local development, but as a reusable, versioned package rather than a single environment-specific file.


13. Deployment Strategies

Rolling update (the Kubernetes Deployment default)

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
Enter fullscreen mode Exit fullscreen mode

As covered in Section 3, this incrementally replaces old pods with new ones — maxUnavailable: 0 combined with maxSurge: 1 means Kubernetes always brings up one extra new pod before removing an old one, ensuring full capacity is maintained throughout the rollout at the cost of briefly running one more pod than the steady-state replica count.

Blue/green via two Deployments and a Service swap

# Deploy the "green" version alongside the existing "blue" one, under a different label
kubectl apply -f deployment-green.yaml

# Once verified, switch the Service's selector to point at "green" instead of "blue"
kubectl patch service product-api-service -p '{"spec":{"selector":{"version":"green"}}}'
Enter fullscreen mode Exit fullscreen mode

Unlike a rolling update, blue/green keeps the entire previous version's pods running and fully staffed alongside the new version, and cuts traffic over atomically by repointing the Service's label selector — a simpler mental model for verifying the new version fully before any traffic touches it, at the cost of running double the pod capacity during the transition.

Canary releases

# Two Deployments, same Service selector, different replica counts and pod labels
# product-api-stable: 9 replicas (90% of traffic, roughly, via even load balancing across all matching pods)
# product-api-canary: 1 replica (10% of traffic)
Enter fullscreen mode Exit fullscreen mode

A canary release routes a small percentage of live traffic to the new version while the majority continues hitting the stable version — implemented at a basic level via replica-count ratios under a shared Service selector (as sketched above), or more precisely via a service mesh (Istio, Linkerd) or an Ingress controller supporting weighted traffic splitting, letting you catch a problem with the new version while it's affecting only a small fraction of real users, then gradually increase its share of traffic as confidence grows.

Tooling for progressive delivery

For canary and blue/green strategies more sophisticated than manually juggling replica counts or Service selectors, dedicated tools — Argo Rollouts, Flagger — extend Kubernetes with purpose-built objects for progressive delivery, including automated analysis (querying metrics from Prometheus, say) to decide whether to proceed, pause, or automatically roll back a rollout based on real observed behavior, rather than a human manually watching dashboards and deciding.


Quick Reference Table

Concept Purpose
Pod Smallest deployable unit; one or more co-located containers
Deployment Manages ReplicaSets/Pods, handles rolling updates
Service Stable network identity and load balancing over a dynamic set of pods
Ingress HTTP(S)-aware routing by hostname/path, via an Ingress Controller
ConfigMap Non-sensitive externalized configuration
Secret Sensitive configuration — base64-encoded only, not encrypted, by default
PersistentVolumeClaim Requests durable storage independent of pod scheduling
HPA / Cluster Autoscaler Scale pod replica count / scale underlying node capacity
Liveness / Readiness probe Restart a stuck container / stop routing traffic to a not-yet-ready one
Namespace Logical isolation and resource scoping within a cluster
RBAC Least-privilege access control over the Kubernetes API
Helm chart Versioned, templated package of a complete Kubernetes application
helm install/upgrade/rollback Managing a tracked, revisioned application release

Conclusion

Kubernetes takes the container packaging model from this series' Docker guide and adds everything needed to run containers reliably at scale: scheduling across many machines, self-healing when things fail, automatic scaling in both directions (pods and nodes), stable networking despite constantly changing pod instances, and controlled, health-check-driven rollouts. Helm then addresses the natural next problem — a real application is rarely just one Kubernetes object, and packaging the whole set together, versioned and configurable per environment, turns what would otherwise be an unwieldy pile of hand-maintained YAML into a genuinely reusable, installable unit.

The concepts in this guide connect directly to nearly everything else in this series: the containers being orchestrated are built exactly as described in the Docker guide; the cluster itself is commonly AKS or EKS as covered in the Azure/AWS Compute guides; the CI/CD pipelines from the GitHub Actions and Azure DevOps guides are what actually run helm upgrade on every merge; and the right-sizing, autoscaling, and cleanup principles from the Cloud Cost Optimization guide apply directly to how you configure resource requests, HPAs, and the Cluster Autoscaler. Kubernetes and Helm are, in a real sense, the connective tissue tying the container and cloud-infrastructure threads of this series together into one running system.


Found this useful? Feel free to star the repo, open an issue with corrections, or share the readiness-probe bug that taught you why "returns 200 immediately" isn't the same as "actually ready."

Top comments (0)