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
- Introduction
- Why Kubernetes Exists
- Core Objects
- Deployments and ReplicaSets
- Services and Networking
- Ingress
- ConfigMaps and Secrets
- Storage: Volumes and Persistent Volume Claims
- Scaling
- Health Probes
- Namespaces and RBAC
- Helm: Packaging Kubernetes Applications
- Helm Templating in Depth
- Deployment Strategies
- Quick Reference Table
- 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 }]
kubectl apply -f deployment.yaml
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
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
# ...
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
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" }
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
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
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
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
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 }
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"
spec:
containers:
- name: product-api
envFrom:
- configMapRef:
name: product-api-config
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>
spec:
containers:
- name: product-api
envFrom:
- secretRef:
name: product-api-secrets
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
spec:
containers:
- name: product-api
volumeMounts:
- name: data
mountPath: /app/data
volumes:
- name: data
persistentVolumeClaim:
claimName: product-data-pvc
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
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
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
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
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
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"]
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
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
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
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
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" }
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 }}
{{ .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" }
helm install my-app ./my-app-chart -f production-values.yaml
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"
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
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"}}}'
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)
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)