Monday, May 25, 2026

Redis Topologies

Topology Best Used For Complexity Writes Scalable?
Standalone (e.g. apt setup) Local development / testing. Very Low No
Leader-Follower (Our next step) Small/Medium apps needing read-scalability. Medium No (Only 1 Leader)
Sentinel High Availability (Auto-failover) for Leader-Follower. High No (Only 1 Leader)
Cluster Mode Massive datasets that don't fit on one machine. High Yes (Shards data across masters)
Active-Passive Disaster Recovery across different cloud regions. Very High No (Only Main Region)
Active-Active Global apps requiring ultra-low latency worldwide. Extremely High Yes (Write anywhere globally)

Understanding the React ESLint Warning: “Avoid Calling setState() Directly Within an Effect”

While working with React, I encountered an interesting ESLint warning related to useEffect.

The application itself was running perfectly fine, but ESLint still flagged the code with an error.

At first glance, the warning looked confusing because useEffect is supposed to handle side effects like fetching data.

This article explains:

  • what the warning actually means,
  • why ESLint complained,
  • why the corrected version passed,
  • and the deeper React concepts involved.

The Original Code


// Helper function to fetch messages from the backend API
const fetchEntries = async () => {
  try {
    const res = await fetch(API_URL);

    if (!res.ok) {
      throw new Error('Backend failed to respond');
    }

    const data = await res.json();

    setEntries(data);
    setError(null);

  } catch (err) {
    setError('Could not fetch guestbook entries. Is the backend running?');
    console.error(err);
  }
};

// Fetch entries automatically when the page loads
useEffect(() => {
  fetchEntries();
}, []);

  

The ESLint Error

ESLint produced the following warning:

Error: Calling setState synchronously within an effect can trigger cascading renders

Avoid calling setState() directly within an effect

This warning came from:

react-hooks/set-state-in-effect

The Corrected Version

The corrected version moved the async function inside the effect.


useEffect(() => {

  const fetchEntries = async () => {
    try {
      const res = await fetch(API_URL);

      if (!res.ok) {
        throw new Error('Backend failed to respond');
      }

      const data = await res.json();

      setEntries(data);
      setError(null);

    } catch (err) {
      setError('Could not fetch guestbook entries. Is the backend running?');
      console.error(err);
    }
  };

  fetchEntries();

}, []);

  

Interestingly, this version passed ESLint successfully.

Was the Original Code Actually Wrong?

Not really.

Both versions are almost identical in runtime behavior.

The application worked because the state update was not actually synchronous.

The important line is:

await fetch(API_URL);

That await creates an asynchronous boundary.

So the sequence is actually:

useEffect runs

fetch request starts

effect completes

network response arrives later

setState executes

This is very different from:


useEffect(() => {
  setCount(count + 1);
}, []);

  

where the state update happens immediately during the effect execution.

Then Why Did ESLint Complain?

The answer lies in static analysis.

ESLint does not execute the program like a browser does. Instead, it analyzes the source code structure and tries to detect potentially dangerous patterns.

In the original code, ESLint saw:


useEffect(() => {
  fetchEntries();
}, []);

  

and noticed that:

fetchEntries()

contains:

  • setEntries(...)
  • setError(...)

From ESLint’s perspective:

Effect → function call → setState

So it assumed:

“This effect may be synchronously updating state.”

The linter could not safely prove that the state updates only happen after an asynchronous operation.


The complete error as received was: 

error  Error: Calling setState synchronously within an effect can trigger cascading renders


Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:

* Update external systems with the latest state from React.

* Subscribe for updates from some external system, calling setState in a callback function when external state changes.


Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).

npm vs npx

The primary difference is that npm (Node Package Manager) is used to install and manage packages, while npx (Node Package Execute) is used to run packages without permanently installing them.

Think of npm as your project's storage manager and npx as a "one-off" execution tool.

Key Comparison

Feature npm npx
Primary Goal Installs and manages project dependencies. Executes Node packages on the fly.
Storage Packages are installed locally or globally on your machine. Runs packages without cluttering local or global storage.
Workflow You typically install a package before you can use it. You can run a package directly from the registry without prior installation.
Version Control Manages specific versions via the package.json file. Often used to ensure you are running the latest version of a tool.

When to Use Each

Use npm: For long-term project dependencies that you need to use repeatedly, like a web framework (React) or a utility library (Lodash).

Use npx: For one-time tasks or scaffolding new projects. For example, create-react-app is commonly run with npx so you don't have to keep an outdated version of the installer globally on your system.

Practical Example

If you want to create a new React application:

Using npm

You would first run npm install -g create-react-app to install it globally, then run create-react-app my-app .

Using npx

You simply run npx create-react-app my-app . It downloads what it needs, runs the setup, and finishes without leaving the tool installed on your computer

Sunday, May 24, 2026

SSoT for the K8s Project

Architectural Design Specifications

1. Core Architecture & Local Runtime Environment

The entire platform is built as a distributed microservice system running inside an enterprise-grade local sandbox. The underlying infrastructure uses k3d to spin up a multi-node, lightweight K3s (Kubernetes) cluster directly inside Docker on your laptop. To eliminate external internet dependencies and network bottlenecks, a built-in local container registry is bound directly to the k3d cluster nodes. The local development life cycle is orchestrated entirely by Skaffold, which watches local source directories, coordinates container builds, injects live code updates via hot-reloading directly into running containers, and automates local manifest application.

2. Service Breakdown & Polyglot Storage Strategy

The business logic is decoupled into distinct, containerized microservices to ensure individual scalability and fault isolation:

  • Frontend Service (React): The client-side user interface responsible for store browsing, user authentication, and shopping cart interactions.
  • Product Catalog Service: A read-intensive microservice managing item definitions, technical specifications, and merchant inventory counts. It relies on a document-oriented MongoDB database cluster due to its highly flexible, schema-less properties.
  • Order Processing Service: A highly transactional microservice handling secure checkout states, billing, and customer invoices. It relies on PostgreSQL to enforce strict ACID compliance and relational integrity.

3. Data Sovereignty & Geographic Infrastructure Sharding

To strictly comply with global data protection regulations (such as GDPR, CCPA, and DPDP), the system uses Geographic Sharding to ensure sensitive user data is physically stored within its region of origin.

  • Implementation: The system leverages a composite shard key consisting of [tenant_id, country_code].
  • Database Layers: MongoDB automates horizontal scaling using its native mongos routing query routers and config servers. PostgreSQL implements true multi-node horizontal sharding across independent physical cloud clusters by using the Citus open-source database extension.

4. Complex Multi-Tenancy Strategy

The platform supports multiple commercial merchants (tenants) sharing a unified, cost-efficient infrastructure via a Hybrid Multi-Tenancy Architecture:

  • Compute Tier: All tenants share the same running microservice container pods inside the Kubernetes cluster. The system dynamically identifies the active tenant by decoding a tenant_id claim baked directly into the user’s cryptographically signed identity token.
  • Product Storage Tier (Shared Catalogue, Private Overlay): To prevent duplicate entries of universal products, the platform separates data. The core products collection holds global, shared item details (SKU, brand, images). A separate merchant_listings collection stores tenant-specific commercial overrides (price, current stock, active flag) tied to a unique tenant_id.
  • Order Storage Tier (Isolated Shards): Financial and customer transaction histories are isolated across independent database shards managed by Citus and MongoDB clustering based on the tenant's risk profile and scale.

5. Application-Level Geo-Fencing (Country Restrictions)

When compliance or licensing restricts specific products from being sold in certain countries, the system avoids infrastructural sharding for public catalog data and handles rules at the application layer.

  • An explicit geo_restrictions schema matrix containing inclusion or exclusion lists (e.g., whitelist or blacklist arrays of ISO country codes) is attached directly to the global product or merchant listing document.
  • At runtime, the cluster edge (API Ingress) captures the user's origin country via GeoIP or profile data and passes an X-User-Country header down to the Product Service. The backend appends a dynamic mongo filter (e.g., $ne: userCountry) to eliminate blocked items before delivering responses to the client.

6. Scalable Redis Caching Strategy

To protect the primary databases from intensive read operations, an ultra-fast Redis in-memory data cache sits directly in front of MongoDB.

  • Key Namespacing: To prevent data leaks across distinct merchants and countries, Redis uses strict composite composite keys following a colon-separated namespace: tenant:<tenant_id>:country:<country_code>:catalog
  • Cache Patterns: The system enforces a Cache-Aside (Lazy Loading) pattern with a mandatory Time-To-Live (TTL) expiration limit of 1 hour.
  • Day-2 Operational Safety: The Product Service implements a Mutex Lock mechanism to block "Cache Stampedes" by ensuring only one backend worker can re-query MongoDB on a cache miss while parallel concurrent requests wait. Whenever a merchant alters an item or a country rule, the backend executes targeted cache eviction using background scans matching tenant:<tenant_id>:country:*:catalog.

7. Eventual Data Synchronization & Change Data Capture (CDC)

  • Instead of forcing application code to double-write to multiple networks, the system implements a modern Change Data Capture (CDC) model.
  • The Debezium framework hooks directly into the database transaction logs (the Postgres Write-Ahead Log [WAL] and the MongoDB Oplog) in real-time, translating row modifications into structured JSON event streams. These streams are safely fed into an Apache Kafka cluster, allowing reporting data warehouses or regional caches to sync automatically.

8. API Gateway Management & Versioning Strategy

  • Ingress & Validation: Traefik acts as the API Gateway. It validates incoming JSON Web Tokens (JWT) signatures at the cluster edge, dropping unauthorized traffic instantly.
  • API Evolution: The platform enforces strict URL Path Versioning (e.g., /api/v1/orders vs. /api/v2/orders). Breaking structural API payload changes require incrementing the major path version, allowing Traefik to route legacy and modern variants cleanly to distinct Kubernetes deployment groups.

9. Unified Identity Provider (IdP) & Application RBAC

  • Authentication (AuthN): Handled completely outside the custom microservices by deploying Keycloak (OpenID Connect / OIDC provider). Users register, authenticate, and manage passwords against Keycloak, which hands back a secure JWT.
  • Authorization (AuthZ): Inside our custom application code, granular Role-Based Access Control (RBAC) is evaluated by decoding the roles array contained within the token (e.g., separating standard customer reading routes from inventory-manager mutation actions).

10. Left-Shift Security & Automated Quality Gates

  • Pre-Commit / Early CI: ESLint/Ruff/Golangci-lint checks syntax, while TruffleHog/Gitleaks blocks commits if high-entropy cloud secrets are exposed in plain text.
  • Static Security & Dependencies: SonarQube/Semgrep executes Static Application Security Testing (SAST), while Snyk/Dependabot reviews open-source application dependency trees for Software Composition Analysis (SCA).
  • Container Security: During image creation, Trivy audits the operating system container base layers for vulnerabilities (CVEs).
  • Manifest Auditing: Before manifests are committed to Git, Datree/Kube-linter scans the Kubernetes YAML files to block infrastructure misconfigurations (e.g., missing resource limits, privilege escalation vectors).
  • Runtime Security: Automated OWASP ZAP runs Dynamic Application Security Testing (DAST) web vulnerability injection attacks against running staging namespaces.

11. Tiered Testing Strategy

  • Unit Tests: Handled natively via modern test runners (Vitest/PyTest/Go Test) to check decoupled pure business logic and algorithms with zero dependencies.
  • Integration Tests: Enabled by Testcontainers. During test execution, Testcontainers invokes the local Docker API to spin up real, ephemeral, completely pristine instances of Redis, MongoDB, and PostgreSQL. The microservice test suite runs its queries against these short-lived native databases, ensuring integration testing accurately mirrors true cloud environments before disposing of the containers.

12. GitOps Delivery & Infrastructure Control

  • Environment Orchestration: Managing differing requirements across development, staging, and production environments is isolated using Kustomize. Kustomize maintains a single source base/ configuration file structure and maps changes (replicas, ingress domains, configuration values) through declarative, non-destructive overlays/.
  • Continuous Deployment (CD): The platform rejects manual push actions (kubectl apply) in favor of a pull-based GitOps engine powered by Argo CD running inside the cluster. Argo CD constantly checks the state of your infrastructure Git repository against the active state of the cluster, pulling, validating, and auto-correcting drift instantly.

Architectural Design Matrix

Category Component Layer Chosen Technology Architectural Function & Single Source of Truth Rules
Runtime Local Cluster Infrastructure k3d (K3s in Docker) + Local Registry Provisions a local multi-node cluster and secure loopback repository to eliminate external cloud network lag.
Workflow Developer Inner Loop Skaffold Automates container tracking, handles image orchestration, and triggers live hot-reloading into k3d.
Microservices Frontend Core React Client browser rendering engine managing secure sessions and UI visual flows.
Microservices Catalog Backend Node.js / Go / Python Controls item inventories and references. Pairs strictly with unstructured storage.
Microservices Transactional Backend Node.js / Go / Python Processes state-driven consumer purchase checkouts and compliance-driven invoicing.
Data Layer Product Storage MongoDB (Cloud-Ready) Document database housing global reference products alongside multi-tenant listings.
Data Layer Order Ledger PostgreSQL + Citus Sharded relational engine executing horizontal table partitioning and row routing across servers.
Data Layer Distributed Fast Cache Redis Accelerates data reads via lazy-loading. Resolves cache stampedes via Mutex locks.
Architecture Data Sovereignty Geo-Sharding via [tenant_id, country_code] Restricts sensitive relational transaction logs to country boundaries via database-level shards.
Architecture Multi-Tenancy Composite JWT Token Mapping Leverages a single shared compute container layer while using logical document IDs and Postgres tables to segregate clients.
Architecture Geo-Fencing API X-User-Country Header Matrix Filters catalog displays by querying a blacklist/whitelist array inside item metadata to meet geographic constraints.
Data Flow Asynchronous Sync Debezium + Apache Kafka Executes infrastructure-level Change Data Capture (CDC) via live log tracking to sync remote data analytics.
Traffic Gate API Gateway & Routing Traefik Performs URL path validation, checks edge JWT signatures, and separates /v1 and /v2 microservice endpoints.
Identity AuthN & AuthZ Keycloak (OIDC) Decouples core systems from user credentials by centralizing authentication and providing cryptographically signed JWT tokens.
Quality/QA Code Linters ESLint / Ruff / Golangci-lint Enforces structural code patterns and identifies structural code smells before code review.
Quality/QA Secret Interception TruffleHog / Gitleaks Evaluates local git histories and blocks commits if unencrypted security tokens are caught.
Quality/QA Logic Testing Vitest / PyTest / Go Test Executes ultra-fast, local testing of functions without firing external infrastructure networks.
Quality/QA Environment Testing Testcontainers Spins up live Docker containers of real databases (Mongo/Postgres/Redis) for clean integration tests.
Quality/QA Static Code Security SonarQube / Semgrep (SAST) Audits codebase syntax trees for OWASP top-10 programming vulnerabilities during pull requests.
Quality/QA Dependency Audit Snyk / Dependabot (SCA) Discovers and alerts developers to security issues in open-source third-party application modules.
Quality/QA Container Integrity Trivy Reviews target container base filesystems for known CVE vulnerability patches during the build process.
Quality/QA Runtime Testing OWASP ZAP (DAST) Fires automated pen-testing payload injections against live staging application endpoints.
Quality/QA Manifest Evaluation Datree / Kube-linter Evaluates local Kubernetes configuration YAML layouts for misconfigurations before application.
Deployment Environment Variant Overlay Kustomize Declares a standard base configurations directory and merges targeted environment patches natively.
Deployment GitOps Delivery Argo CD Automates the complete delivery loop by monitoring Git changes and auto-correcting active cluster drift.

Demystifying Kubernetes: Scaling, NodePorts, and the Myth of the "Chosen Pod"

Kubernetes has become the de facto orchestration platform for modern cloud-native applications. While containerization platforms like Docker solve packaging and deployment problems, Kubernetes solves the far more difficult challenges of scalability, resiliency, orchestration, and traffic management.

However, once developers move beyond basic tutorials and begin working with production-grade workloads, several practical questions emerge:

  • How can an application automatically handle sudden spikes in traffic?
  • How does Kubernetes distribute traffic among pods?
  • Can a request be routed to one exact pod instance?
  • What architectural patterns should be used for stateful applications?

This article provides a detailed and practical explanation of how Kubernetes scaling and networking work internally, including Horizontal Pod Autoscaling (HPA), NodePort services, StatefulSets, sticky sessions, and direct pod routing architectures.

Quick Reference Guide

Component / Concept Primary Function Traffic Control Capability Best Used For
Deployment Manages application pod lifecycles and rolling updates. None. Focuses on maintaining desired replica count. Stateless web applications, APIs, microservices.
Service (NodePort) Exposes an application port externally on cluster nodes. Round-robin or random load balancing. External access during development and testing.
StatefulSet Provides stable pod identities and persistent naming. Supports exact pod targeting with headless services. Databases, Kafka clusters, distributed systems.
HPA Automatically scales pods using metrics. Scales based on CPU, memory, or custom metrics. Traffic spikes and dynamic workloads.

Part 1: Understanding Kubernetes Scaling

Consider a web application that normally operates with 3 pods handling approximately 100 requests per second. During high traffic periods, such as marketing campaigns or viral events, traffic may surge beyond 200 requests per second. In such scenarios, Kubernetes can automatically scale the application to additional pods using the Horizontal Pod Autoscaler (HPA).

The Default HPA Behavior

By default, HPA monitors CPU and memory consumption. When average resource utilization crosses configured thresholds, Kubernetes increases or decreases the number of pod replicas accordingly.

Important: Real-world traffic patterns do not always correlate directly with CPU usage. Lightweight API requests may overwhelm network bandwidth or databases while CPU usage remains relatively low.

To scale based on request volume or other business metrics, Kubernetes supports Custom Metrics. This is commonly implemented using:

  • Prometheus
  • Prometheus Adapter
  • KEDA (Kubernetes Event-Driven Autoscaling)

The HPA Scaling Formula

HPA periodically evaluates workload metrics and calculates the required replica count using the following formula:

Desired Replicas = Ceiling(Current Replicas × (Current Metric Value ÷ Target Metric Value))

Practical Example

Suppose:

  • 3 pods comfortably handle 100 requests per second
  • Target per-pod throughput is approximately 33 requests per second
  • Traffic suddenly increases to 210 requests per second

Kubernetes computes:

Desired Replicas = Ceiling(3 × (70 ÷ 33)) = Ceiling(6.363) = 7 Pods

Kubernetes therefore increases the deployment replica count from 3 to 7 pods in order to distribute the increased workload safely.

Example HPA Manifest

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-awesome-app-scaler
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app-deployment

  minReplicas: 3
  maxReplicas: 10

  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: 33m

Part 2: Understanding NodePort Services

A common misconception among Kubernetes beginners is the belief that a specific pod can be targeted directly through a NodePort service.

Key Principle: A Kubernetes Service abstracts pods behind a stable endpoint. The service owns the port — not the pods themselves.

How NodePort Works

When a NodePort service is created:

  • Kubernetes allocates a port from the NodePort range (typically 30000–32767).
  • The port becomes accessible on every node in the cluster.
  • kube-proxy intercepts incoming traffic.
  • Traffic is forwarded to one available backend pod using load balancing.
[ External User ]

        ↓

Traffic hits Node on Port 32500

        ↓

[kube-proxy Load Balancer]

    ↙        ↓        ↘

[Pod 1]    [Pod 2]    [Pod 3]

As a result, requests are distributed automatically, and clients cannot choose a specific pod through the NodePort alone.

Why Kubernetes Prevents Direct Pod Addressing

Pods are intentionally ephemeral. If a pod crashes, Kubernetes replaces it with a new pod having a different IP address. Directly exposing pod-specific networking to clients would make applications fragile and tightly coupled to infrastructure details.

The Service abstraction ensures:

  • Stable networking endpoints
  • Automatic failover
  • Simplified service discovery
  • Transparent load balancing

Part 3: Architectures for Targeting Specific Pods

Certain workloads require communication with a specific pod instance. Common examples include:

  • Multiplayer gaming servers
  • Sticky WebSocket sessions
  • Distributed databases
  • Kafka brokers
  • Partition-aware applications

Option A: StatefulSet + Headless Service

This is the recommended approach for stateful distributed systems.

  • Pods receive stable deterministic names.
  • Example names: my-app-0, my-app-1, my-app-2
  • A headless service exposes direct DNS entries for each pod.
  • Applications can directly communicate with specific pod identities.

Option B: Dedicated Service per Pod

In scenarios where external traffic must target exact pods, a dedicated Kubernetes Service can be created for each pod using unique label selectors.

  • Service A → NodePort 32501 → Pod 1
  • Service B → NodePort 32502 → Pod 2
  • Service C → NodePort 32503 → Pod 3

This bypasses generic load balancing and routes requests deterministically.

Option C: Ingress Controller with Session Affinity

For web applications requiring sticky sessions, an Ingress Controller such as NGINX Ingress can maintain affinity between a client and a backend pod using cookies.

apiVersion: networking.k8s.io/v1
kind: Ingress

metadata:
  name: my-app-ingress

  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "SERVERID"

Subsequent requests from the same browser are routed consistently to the same backend pod.

Frequently Asked Questions

What is the difference between a Pod, ReplicaSet, and Deployment?

Component Purpose
Pod Runs the actual application container.
ReplicaSet Maintains the required number of pod replicas.
Deployment Manages ReplicaSets and rolling updates.

How does Kubernetes prevent rapid scaling fluctuations?

Kubernetes prevents excessive scaling oscillations using stabilization windows and cooldown periods. After scaling up, the HPA intentionally waits before scaling back down, ensuring system stability during temporary traffic spikes.

Is NodePort obsolete?

No. NodePort remains useful for:

  • Local Kubernetes environments (Minikube, Kind)
  • Bare-metal clusters
  • Internal development environments
  • Testing and debugging

However, production systems typically use:

  • Ingress Controllers
  • Cloud LoadBalancers
  • Service Meshes
on top of their NodePorts to gain granular path routing, SSL termination, and secure access controls

Troubleshooting Checklist

  • Verify HPA Metrics: Run kubectl get hpa. If the TARGETS column shows <unknown>, the metrics pipeline is failing.
  • Check Service Endpoints: Run kubectl get endpoints <service-name>. Empty endpoints indicate label selector mismatches.
  • Inspect NodePort Allocation: Run kubectl describe service <service-name> to confirm valid NodePort allocation.

Conclusion

Kubernetes abstracts enormous operational complexity behind relatively simple APIs. However, understanding the internal mechanics of scaling, service routing, traffic distribution, and pod identity is essential for building reliable cloud-native systems.

Horizontal Pod Autoscaling allows applications to react dynamically to changing workloads. Services and NodePorts provide stable networking abstractions, while StatefulSets and advanced ingress configurations enable deterministic routing for stateful applications.

Selecting the correct Kubernetes architecture depends heavily on whether the workload is stateless or stateful, internal or internet-facing, and whether exact pod targeting is required.


Consider real-world production questions:
  • "How do I make my app handle a sudden viral spike in traffic without waking me up at 3:00 AM?"
  • "If I expose a port to the outside world, how on earth do I talk to one specific instance of my app?"
If you have ever felt personally attacked by Kubernetes networking or auto-scaling math, do not panic. Grab a coffee, mute your Slack notifications, and let’s break down exactly how Kubernetes handles scaling and traffic routing.

The Quick-Reference Guide

Before we dive deep into the technical weeds, here is a foundational cheat sheet summarizing how Kubernetes components handle traffic, state, and scaling.
Component / ConceptPrimary FunctionTraffic Control CapabilityBest Used For
DeploymentManages application pod lifecycles and rolling updates.None. It focuses entirely on keeping the specified number of containers alive.Stateless web apps, APIs, microservices.
Service (NodePort)Exposes an application port on every cluster server (Node).Random/Round-robin load balancing. You cannot pick a specific pod.Basic external traffic entry points, development, testing.
StatefulSetManages pods with unique, permanent identities.Highly specific. Paired with a Headless Service, you can target exact pods.Databases (Mongo, Postgres), Kafka, distributed storage.
HPA (Horizontal Pod Autoscaler)Dynamically scales pods up and down based on resource metrics.Triggers scaling based on thresholds (CPU or request volume via Prometheus).Handling unpredictable traffic surges automatically.

Part 1: The Magic of Scaling (Or, How to Math Your Way to 6 Pods)

Let’s tackle your first scenario. Suppose you have a web application. Under normal conditions (around 100 requests per second), you want exactly 3 pods running. But if your traffic surges past 200 requests per second, you want Kubernetes to automatically scale your application up to 6 pods.
How does Kubernetes know how to do this? Does it look into a crystal ball? Not quite. It uses something called the Horizontal Pod Autoscaler (HPA).

The Default Problem: CPU vs. The Real World

Out of the box, the HPA is a bit basic. It loves reading CPU and Memory consumption. If a pod hits 80% CPU usage, the HPA spins up another pod.
However, in the real world, network traffic doesn’t always correlate perfectly with CPU. A massive spike in lightweight API requests might swamp your network bandwidth or database connections while your CPU sits casually at 15%.
To scale based on request count, you must break free from default metrics and implement Custom Metrics. This involves setting up a monitoring pipeline, usually using Prometheus to track the requests and a Prometheus Adapter (or a tool like KEDA) to feed those numbers directly to Kubernetes.

The Math Behind the Curtain

The HPA operates on a continuous loop (checking metrics roughly every 15 seconds). Every time it loops, it runs a specific algebraic formula to figure out how many pods you actually need:
$$\text{Desired Replicas} = \left\lceil \text{Current Replicas} \times \left( \frac{\text{Current Metric Value}}{\text{Target Metric Value}} \right) \right\rceil$$
(Note: The $\lceil \dots \rceil$ brackets mean "ceiling"—always round up to the next whole number. You can’t run 3.2 pods; a 0.2 pod is just a sad, broken container.)
Let’s apply your exact numbers to this formula.

Step 1: Establish Your Baseline Target

If you want 3 pods running comfortably when total traffic is at 100 requests, you need to calculate what a single, healthy pod is expected to handle.
$$\frac{100 \text{ total requests}}{3 \text{ pods}} \approx 33.33 \text{ requests per pod}$$
To keep our YAML clean, let’s tell Kubernetes that our target metric threshold is 33 requests per second per pod.

Step 2: The Traffic Spike (Hitting 210 Requests)

Suddenly, an influencer links to your site. Traffic jumps from 100 requests to 210 requests.
The HPA wakes up, looks at the traffic, and runs the math using our target value of 33:
  1. Current Replicas: 3
  2. Current Total Value: 210 requests (which averages to 70 requests per pod across the current 3 pods)
  3. Target Metric Value: 33 requests per pod
$$\text{Desired Replicas} = \left\lceil 3 \times \left( \frac{70}{33} \right) \right\rceil = \left\lceil 3 \times 2.121 \right\rceil = \left\lceil 6.363 \right\rceil = 7 \text{ Pods}$$
The HPA notices that 6.363 rounds up to 7 pods. It will immediately command your Deployment to scale up, spinning up 4 new pods to distribute the heavy load. Once traffic drops back down below 100, the HPA will safely scale you back down to your minimum of 3 pods.

The Configuration Blueprint

To make this happen in your cluster, you would deploy an HPA manifest that looks like this:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-awesome-app-scaler
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app-deployment
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: 33m # 'm' stands for milli-units in K8s; 33m represents our targeted average value

Part 2: The NodePort Dilemma (And Why You Can't Talk to Pod 2)

Now let's dive into your second question, which highlights one of the most common points of confusion in cloud networking.
The Scenario: You have a NodePort service. You have 3 pods running under it. The service opens up a port (let's say port 32500 externally, which maps to port 5000 inside your pods). You want to send a request specifically to Pod #2. How do you do it using only the port number?
The Blunt Answer: You don’t.
By default, Kubernetes is designed explicitly to stop you from doing this. A Kubernetes Service is not a router that lets you pick a destination; it is an abstract shield built to hide individual pods from the outside world.

Understanding the NodePort Flow

When you create a NodePort service, Kubernetes allocates a port from a specific range (usually 30000–32767) across every single server (Node) in your cluster.
                  [ External Internet User ]
                             │
                             ▼
               Traffic hits Node 1 on Port 32500
                             │
                      [ Kube-Proxy ] <── (The invisible referee)
                             │
         ┌───────────────────┼───────────────────┐
         │ (Random)          │ (Random)          │ (Random)
         ▼                   ▼                   ▼
    [  Pod 1  ]         [  Pod 2  ]         [  Pod 3  ]
When an external request hits port 32500 on any node, an internal network component called kube-proxy intercepts the traffic. kube-proxy looks at the available endpoints (your 3 pods), randomly selects one (using a round-robin style load-balancing algorithm), and forwards the traffic.
You only have the port number, and that port number belongs to the Service, not the Pod. Therefore, you have absolutely zero say in where that request lands.

Why Does Kubernetes Do This?

Imagine if you could address Pod 2 directly using a port. What happens if Pod 2 runs out of memory and crashes? Kubernetes will instantly kill it and spin up a new pod to replace it.
This brand-new pod will have a completely different internal IP address. If your external client application was hardcoded to talk only to the old "Pod 2", your application is now broken. The Service abstraction exists precisely to ensure that your clients only ever have to know one static endpoint, regardless of pods dying and resurrecting behind the scenes.

Part 3: Architectures to Target Specific Pods

What if your application absolutely must talk to a specific pod? This is a common requirement for multiplayer game servers (where players need to connect to the exact instance hosting their match), chat systems using sticky WebSockets, or partitioned database clusters.
If you find yourself in this situation, you have to bypass traditional Deployments and standard Services. Here are the three most effective architectural patterns to solve this problem.

Option A: The StatefulSet + Headless Service Pattern (Recommended)

Instead of deploying your application as an anonymous Deployment, you deploy it as a StatefulSet.

How it works:

  1. Predictable Identity: Instead of generating random hashes for pod names (like my-app-7mz8x), a StatefulSet names its pods deterministically: my-app-0, my-app-1, and my-app-2. Your "Pod 2" will permanently be known as my-app-1 (zero-indexed).
  2. The Headless Service: You create a companion Service but set its cluster IP configuration field to clusterIP: None.
Because it is "headless", Kubernetes does not provide a single load-balancing IP address. Instead, it creates direct internal DNS records for every individual pod.
Inside the cluster, you can now bypass the load balancer entirely and target Pod 2 directly by formatting its unique DNS URL:
http://cluster.local

Option B: The "Dedicated Service per Pod" Pattern

If your traffic is coming from the outside world and you absolutely must use NodePorts to reach specific pods, you have to create a distinct Service for every single pod instance.

How it works:

  1. Unique Labels: When writing your pod templates, ensure each pod receives a distinct metadata label distinguishing it from its siblings (e.g., app: my-app, instance: pod-1, app: my-app, instance: pod-2).
  2. Multiple Services: You write separate service configuration manifests:
    • Service 1: Listens on NodePort 32501 and targets pods with the label instance: pod-1.
    • Service 2: Listens on NodePort 32502 and targets pods with the label instance: pod-2.
Now, if an external client targets port 32502, the request will bypass the generic pool and route directly to Pod 2.

Option C: The Ingress Controller with Cookie Affinity

If you are running a traditional web application and simply want a user's web browser to stick to the same pod they originally connected to (session affinity), you shouldn't use bare NodePorts. Instead, use an Ingress Controller (like NGINX Ingress).
Using simple annotations, you can instruct the Ingress Controller to inject a unique cookie into the user's browser during their first visit.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "SERVERID"
When the user clicks around your website, the Ingress Controller reads the SERVERID cookie from their browser headers and guarantees that their subsequent requests are consistently routed back to the exact same backend pod.

Technical Deep Dive: A Frequently Asked Questions Breakdown

To reinforce these architecture concepts, let's explore some common edge-case questions that cloud engineers encounter when configuring traffic and scaling.

Q: What is the difference between a Pod, a Deployment, and a ReplicaSet?

Think of a Pod as a physical lightbulb. It is the actual component that glows (runs your code).
A ReplicaSet is the supervisor whose sole job is to watch the lightbulbs. If you tell the ReplicaSet "I need 3 burning lightbulbs," it constantly counts them. If one burns out, it immediately screws in a new one.
A Deployment is the grand architect. It manages the ReplicaSets. When you want to upgrade your lightbulbs from an old version to a new energy-efficient version, the Deployment creates a new ReplicaSet for the new bulbs, slowly turns them on one by one, and dims the old ones out. You should almost always interact with Deployments rather than managing ReplicaSets or Pods directly.

Q: If my metric value fluctuates wildly every few seconds, won't Kubernetes constantly scale up and down like crazy?

This behavior is known as thrashing (or flapping), and Kubernetes includes built-in safeguards to prevent it. Within the HPA configuration, there is a behavior section where you can define stabilization windows.
By default, Kubernetes enforces a cooldown downscale stabilization period (often 5 minutes). This means that if a sudden traffic spike causes your cluster to scale up to 6 pods, it will maintain those 6 pods for at least 5 minutes even if traffic drops down to zero a moment later. This prevents your cluster from wasting massive amounts of CPU and memory constantly destroying and spinning up containers.

Q: Why would anyone use NodePort if it lacks advanced routing capabilities? Is it obsolete?

NodePort is not obsolete, but it is a primitive building block. It is highly valued for its simplicity in local development environments (like Minikube or Kind) or bare-metal internal data centers where you don't have access to cloud provider automated load balancers.
For production-grade internet applications, engineers almost always layer an Ingress Controller or a cloud-native LoadBalancer Service on top of their NodePorts to gain granular path routing, SSL termination, and secure access controls.

Summary Troubleshooting Checklist

If your application scaling rules or network routing paths are behaving unexpectedly, use this step-by-step checklist to isolate the issue:
  1. Verify Metric Flow: Run kubectl get hpa. If you see <unknown> under the TARGETS column, your HPA cannot communicate with your metrics server or custom adapter.
Check Endpoint Binding: Run kubectl get endpoints <service-name>. If the list is empty, your Service's label selector does not match the labels declared in your Deployment template.
Inspect Cluster Traffic: Use kubectl describe service <service-name> to verify that your NodePort is allocated cleanly within the valid 30000–32767 range and hasn't conflicted with another app.

Friday, May 22, 2026

kubernetes controllers and workloads

In Kubernetes, Workloads are the applications you run on the cluster, while Controllers are the background control loops that watch the state of your cluster and make changes to move the current state closer to the desired state.

Think of it this way: a Workload defines what you want to run, and a Controller is the mechanism that ensures it stays running exactly how you specified.


1. Kubernetes Workloads

A workload is an application running on Kubernetes. Whether it is a single component or several working together, you run it inside a set of Pods. A Pod represents a single instance of a running process in your cluster.

Because managing individual Pods manually is tedious and risky (if a Pod dies, it doesn't self-heal), Kubernetes uses higher-level workload resources to manage them for you.

Core Workload Types

  • Deployments: The most common workload type. Best suited for stateless applications (e.g., web servers, microservices). It allows you to define how many identical replicas of a Pod you want, handles rolling updates, and lets you roll back to previous versions smoothly.

  • StatefulSets: Tailored for stateful applications that require unique identities and persistent storage (e.g., databases like PostgreSQL, MySQL, or Kafka). Unlike Deployments, Pods in a StatefulSet have a sticky identity (e.g., pod-0, pod-1) and pair with specific persistent volumes that persist even if a Pod is rescheduled.

  • DaemonSets: Ensures that all (or some) Nodes run a copy of a Pod.   As nodes are added to the cluster, Pods are automatically started on them. This is ideal for infrastructure-level apps like log collectors (Fluentd), monitoring agents (Prometheus Node Exporter), or networking plugins.

  • Jobs & CronJobs: Designed for ephemeral, run-to-completion tasks.

    • A Job creates one or more Pods and ensures that a specified number of them successfully terminate (e.g., a database migration script).

    • A CronJob runs Jobs on a time-based schedule, exactly like a traditional Linux cron job (e.g., nightly backups).


2. Kubernetes Controllers

A Controller is a tracking process that watches the shared state of the cluster through the API server and makes changes attempting to move the current state toward the desired state. This is known as the Reconciliation Loop.

+-----------------------+       Watches   +-----------------------+
|  Desired State     | <------------------ |                            |
| (YAML Configs)   |                         |     Kubernetes     |
+----------------------+                         |  Controller Loop  |
                                                        |  (Reconciliation)  |
+---------------------+       Modifies      |                             |
|  Current State   | <--------------------|                             |
| (Actual Cluster) |                           |                             |
+---------------------+                          +---------------------- -+

How They Work Together

Every workload mentioned above is actually driven by its own corresponding controller inside the Kubernetes control plane:

Workload ResourceManaging ControllerWhat It Responsibly Watches
DeploymentDeploymentControllerWatches ReplicaSets and Pods; handles rolling out updates without downtime.
StatefulSetStatefulSetControllerMaintains strict ordering and unique network/storage identifiers for its Pods.
DaemonSetDaemonSetControllerListens for new nodes joining the cluster and ensures the daemon pod is scheduled onto them.
Job / CronJobJobControllerTracks Pod exits codes to ensure tasks complete successfully; cleans up old pods.

The Built-in Controller Manager

Most of these core loops are packed into a single binary running in the control plane called the kube-controller-manager. While it acts as a single process to reduce complexity, each controller loop runs independently inside it.


Summary of Use Cases

  • Choose a Deployment if your app is a stateless API backend or frontend website.

  • Choose a StatefulSet if you are deploying a database or distributed system that cares about index order and identity.

  • Choose a DaemonSet if you need a utility tool running on every single server blade in your data center.

  • Choose a Job / CronJob if you want to run a batch processing script once or every Sunday at midnight.


Redis Topologies

Topology Best Used For Complexity Writes Scal...