Kubernetes Architecture Series – Part 2: Designing Scalable and Resilient Applications


This is the second part of our three-part Kubernetes architecture series. In Part 1, we explored Kubernetes from the infrastructure and orchestration perspective. We discussed the control plane, data plane, and core components that make Kubernetes the go-to orchestrator for cloud-native workloads.
In this part, we’ll shift gears and explore Kubernetes from the application perspective. Whether you’re a developer, architect, or platform engineer, your job doesn’t end with writing code—your applications need to run in a scalable, cost-effective, and secure manner. They should be resilient, self-healing, and capable of recovering from failures with minimal disruption.
That’s where Kubernetes really shines—and we’ll explore how using some of its built-in primitives.
Our focus in this article will be on scaling and resilience, two of the biggest challenges when running applications at scale. You’ll see how Kubernetes objects like Pods, ReplicaSets, Deployments, Services, and Ingress help us build robust application architectures.
Throughout the post, we’ll also show examples of Kubernetes YAML configurations to ground the theory in real-world usage.
As we discussed in Part 1, the Pod is the smallest deployable unit in Kubernetes. But what exactly is a Pod? And how does it differ from a container?
Think of a Pod as a wrapper around one or more containers that need to run together. While a container is a single isolated environment to run an application, a Pod groups containers that are tightly coupled and share the same network and storage namespace.
You might wonder why a Pod would contain multiple containers. One common reason is when you use sidecar containers. These are utility containers that support the main container and have a tightly linked lifecycle. For example, a sidecar might collect logs, proxy traffic, or sync configuration files. If the main container stops, the sidecar should stop too—and vice versa.
apiVersion: v1
kind: Pod
metadata:
name: music
spec:
containers:
- name: musicContainer
image: nginx
apiVersion: v1
kind: Pod
metadata:
name: music
spec:
containers:
- name: musicContainer
image: nginx
- name: log-sidecar
image: busybox
args: ['/bin/sh', '-c', 'tail -f /var/log/nginx/access.log']
A single Pod is not resilient. If it crashes, traffic is lost. If demand spikes, it can’t scale on its own. In early container deployments, we used to manually spin up additional containers—which worked at a small scale but quickly became unsustainable. Worse, because Pods are ephemeral, they're more likely to disappear than a traditional server—so manual scaling is actually riskier than before.
This is where ReplicaSets come in. A ReplicaSet ensures that a specified number of Pod replicas are always running. If a Pod crashes, the ReplicaSet spins up a replacement. If demand increases and we change the desired number of replicas, the ReplicaSet adjusts accordingly.
However, ReplicaSets don’t offer features like versioning, rolling updates, or rollback. They are powerful, but not application-aware.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: musicReplica
spec:
replicas: 3
selector:
matchLabels:
app: music
template:
metadata:
labels:
app: music
spec:
containers:
- name: musicContainer
image: nginx
To bridge the gap between operational automation and application lifecycle management, Kubernetes provides Deployments.
Deployments manage ReplicaSets under the hood but offer critical functionality like versioned updates, rolling deployments, and rollback. When you update a Deployment, Kubernetes creates a new ReplicaSet with the updated specification and gradually shifts traffic to the new Pods—ensuring zero downtime during the rollout.
apiVersion: apps/v1
kind: Deployment
metadata:
name: music-deployment
spec:
replicas: 3
selector:
matchLabels:
app: music
template:
metadata:
labels:
app: music
spec:
containers:
- name: musicContainer
image: nginx:1.25
In distributed systems, failure domains are critical. Kubernetes allows us to spread Pods across nodes, availability zones, or even regions using anti-affinity rules.
For example, you can ensure that Pods of the same deployment do not land on the same node using a topologyKey such as kubernetes.io/hostname.
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- music
topologyKey: 'kubernetes.io/hostname'
Kubernetes also supports other topology keys like:
topology.kubernetes.io/zonetopology.kubernetes.io/regionThis allows for even higher levels of fault tolerance across zones or cloud regions.
Kubernetes supports two built-in deployment strategies:
RollingUpdate gradually replaces old Pods with new ones, minimizing downtime.
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
Pros: No downtime, graceful transition Cons: If something breaks, users might get a mix of old and new versions before rollback
Recreate stops all old Pods before starting new ones.
strategy:
type: Recreate
Pros: Simpler, useful when app cannot handle multiple versions running concurrently Cons: Downtime during deployment
Your Deployment may be running multiple Pods—but how do clients (internal or external) find and connect to them?
Kubernetes Services act as stable frontends for Pods, abstracting away the ephemeral nature of Pods by load-balancing traffic to healthy instances.
There are several service types:
apiVersion: v1
kind: Service
metadata:
name: music-service
spec:
selector:
app: music
ports:
- port: 80
targetPort: 80
type: ClusterIP
This allows any other Pod in the cluster to access the service via music-service.
While Services expose individual Deployments, Ingress allows you to route external traffic to the appropriate Service using rules based on domain names or paths.
Ingress also handles TLS encryption, authentication, and centralized routing. Think of Ingress as the security guard and traffic cop combined.
An Ingress Controller like NGINX or Traefik implements the logic defined in your Ingress resource and ensures only the right traffic reaches your services.
As we saw earlier, Kubernetes supports RollingUpdate and Recreate, but Blue/Green and Canary deployments are more advanced strategies. Kubernetes doesn’t support them natively—but you can build them using Deployments + Services + Ingress or extend them with tools like Argo Rollouts or Flagger.
In a Blue/Green deployment, you have two versions of the application (blue = current, green = new). You deploy the new version (green), test it, and then switch the Service or Ingress to point to green once it's verified.
# Two deployments: music-blue and music-green
# Service initially routes to music-blue
# Once green is verified, update the selector in music-service to route to music-green
This allows zero-downtime deployments with easy rollback (just switch back to blue).
In Canary deployments, a small percentage of traffic is sent to the new version to monitor behavior before gradually increasing the traffic.
# Two deployments: music (stable) and music-canary (new)
# Use Ingress rules or service weight-based routing to control traffic flow
This approach is excellent for A/B testing and minimizing blast radius during rollouts.
In this post, we’ve taken a deep dive into how Kubernetes helps you scale, deploy, and expose your applications reliably and efficiently.
We explored how:
In the final part of this series, we’ll explore the configuration management side of Kubernetes. We’ll look at handling application Secrets, managing multi-tenancy with Namespaces, and storing data persistently using Volumes, Persistent Volumes, and Persistent Volume Claims.
Stay tuned—because running containers is just the beginning. Making them stateful, secure, and tenant-aware is what we’ll tackle next.