Orchestration at Scale

When one container isn't enough: scaling stateless LLM services horizontally, Kubernetes basics (pods, deployments, services, autoscaling) versus simpler managed platforms, secrets at scale, and Infrastructure as Code — with the discipline to not reach for Kubernetes before you need it.

~3hkubernetesdeploymentscalingiac
Learning objectives
  • Scale an LLM service by keeping it stateless and adding horizontal replicas — and handle provider rate limits.
  • Read the core Kubernetes objects: pod, deployment, service, and the horizontal autoscaler.
  • Choose between managed platforms (Azure Container Apps, Cloud Run) and Kubernetes — and not over-reach.
  • Manage secrets at scale and provision infrastructure with IaC (Terraform/Bicep).
Note

For JavaScript developers: scaling a Python LLM service is the same as scaling a Node service — run many identical stateless instances behind a load balancer, keep state in a database, and let an autoscaler add/remove instances by load. Kubernetes YAML is declarative desired-state config (like a big docker-compose for a cluster). The concepts transfer; only the tooling is new.

Scale by staying stateless

One container serves limited traffic. To handle more, run many identical replicas behind a load balancer. The prerequisite: your app must be stateless — no per-user data held in process memory — so any replica can serve any request. Everything that must persist (conversation history, vector store, sessions) lives in external services (the DB/vector store/memory-store from earlier tracks), not in the app instance.

             ┌── replica 1 ──┐
load balancer ├── replica 2 ──┤ → all share external state: vector DB, Postgres, Redis (memory), object store
             └── replica 3 ──┘

Because you already externalized state (RAG store, LLMOps config, memory), scaling is mostly run more copies. The AI-specific wrinkle: your throughput is often bounded not by your CPU but by the model provider's rate limits (tokens/requests per minute). Scaling replicas doesn't raise that ceiling — handle it with request queuing, backoff/retry, and provider quota increases, and use async I/O (Track 1) so each replica handles many concurrent in-flight LLM calls while waiting on the provider.

Tip

Exercise 1 has you identify what state must be externalized to make an LLM service horizontally scalable, and how you'd handle hitting the provider's rate limit under load.

Kubernetes basics

Kubernetes (K8s) runs and scales containers across a cluster of machines. You declare desired state and K8s continuously reconciles reality to match (restarting crashed containers, replacing failed nodes). The core objects you must recognize:

  • Pod — the smallest unit: one (or a few) containers running together. Usually you don't manage pods directly.
  • Deployment — declares "run N replicas of this image, keep them healthy"; handles rolling updates and self-healing.
  • Service — a stable network endpoint load-balancing across a deployment's pods (they come and go; the service is fixed).
  • HorizontalPodAutoscaler (HPA) — adds/removes replicas based on load (CPU, or custom metrics like queue depth).
apiVersion: apps/v1
kind: Deployment                    # "keep N healthy replicas of this image"
metadata: { name: llm-app }
spec:
  replicas: 3                        # desired replica count (HPA can override)
  selector: { matchLabels: { app: llm-app } }
  template:
    metadata: { labels: { app: llm-app } }
    spec:
      containers:
        - name: app
          image: ghcr.io/acme/app:SHA        # the SHA-tagged image from CI/CD (Topic 2)
          ports: [{ containerPort: 8000 }]
          env:
            - name: OPENAI_API_KEY
              valueFrom: { secretKeyRef: { name: app-secrets, key: openai-key } }  # secret, not inline
          readinessProbe: { httpGet: { path: /health, port: 8000 } }   # don't route traffic until healthy
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler        # scale replicas 3..20 based on CPU load
metadata: { name: llm-app-hpa }
spec:
  scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: llm-app }
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

The readinessProbe and self-healing give you resilience; the HPA gives elastic scale. This is a lot of power — and a lot of complexity.

Tip

Exercise 2 has you read a Deployment + Service + HPA and explain what each object guarantees (replicas, stable endpoint, autoscaling, health) — Kubernetes literacy without needing to run a cluster.

Don't reach for Kubernetes too early

Kubernetes is powerful and operationally heavy — a cluster to run, upgrade, secure, and debug. Most AI apps, especially a consultant's SMB clients, don't need it. The pragmatic ladder (echoing the code-vs-no-code judgment from Track 4):

OptionFits when
Single container / VPSlow traffic, one service, simplest ops (your n8n-style VPS)
Managed container platform (Azure Container Apps, Google Cloud Run, AWS App Runner)you want autoscaling + rolling deploys without running a cluster — the sweet spot for most apps
Kubernetes (AKS/EKS/GKE)many services, complex networking, an existing K8s org, or scale/control a managed platform can't meet

Managed container platforms give you most of Kubernetes' benefits (autoscaling, health checks, rolling updates, secrets) with a fraction of the ops burden — they run your container image and scale it, no cluster to babysit. Start there; graduate to Kubernetes only when its complexity is justified by real need.

Watch out

"We'll put it on Kubernetes" is a common over-engineering reflex. A cluster you don't need is ongoing cost and risk. For a single scalable service, Azure Container Apps / Cloud Run is usually the right answer — and it consumes the exact same container image from Topic 1.

Tip

Exercise 3 has you pick the deployment platform for three scenarios (a solo-consultant SMB tool, a scaling startup, a large multi-service enterprise) and justify each.

Secrets at scale & Infrastructure as Code

Secrets don't belong in YAML or images (Topics 1–2). At scale, a secret store (Azure Key Vault, AWS Secrets Manager, or Kubernetes Secrets backed by a vault) holds them, and the platform injects them at runtime — the secretKeyRef above pulls from a managed secret, never a literal.

Infrastructure as Code (IaC) provisions all this — clusters, databases, secret stores, networking — declaratively in version-controlled files, so environments are reproducible and reviewable (the same reproducibility principle as containers and LLMOps, applied to infrastructure):

# Terraform: provision infra declaratively (Bicep is the Azure-native equivalent)
resource "azurerm_container_app" "llm_app" {
  name = "llm-app"
  template {
    container {
      image = "ghcr.io/acme/app:SHA"        # same image; infra defined as reviewable, versioned code
      cpu   = 0.5
      memory = "1Gi"
    }
    min_replicas = 2                          # autoscaling bounds, declared in code
    max_replicas = 10
  }
  secret { name = "openai-key" }              # wired to a Key Vault reference, not a literal
}

IaC means "spin up an identical staging environment" is a command, not a week of clicking — and every infra change is a reviewable diff.

Tip

Exercise 4 has you sketch how secrets flow from a vault into a running container, and name one benefit IaC gives over manually configuring infrastructure in a cloud console.

Pitfalls

  • Stateful app instances. Per-user state in process memory breaks horizontal scaling — externalize all state.
  • Scaling into a rate limit. More replicas don't raise the provider's token/request cap — queue, back off, raise quota, go async.
  • Premature Kubernetes. A cluster you don't need is pure ops overhead — prefer a managed container platform until you truly need K8s.
  • Secrets in YAML/images. Use a vault + runtime injection (secretKeyRef), never literals in manifests.
  • Click-ops infrastructure. Manually configured infra isn't reproducible or reviewable — use IaC.

Recap

  • Scale LLM services by keeping them stateless and running horizontal replicas behind a load balancer, with all state in external services.
  • Throughput is often capped by the provider's rate limits, not your compute — handle with queuing, backoff, quota, and async.
  • Kubernetes (pods/deployments/services/HPA) gives elastic, self-healing scale — but is heavy; most apps are better on a managed container platform (Azure Container Apps, Cloud Run).
  • Keep secrets in a vault injected at runtime, and provision everything with IaC (Terraform/Bicep) for reproducible, reviewable infrastructure.
  • Don't over-reach — match the platform to real need, the same judgment as code-vs-no-code.
  • Worked examples map to the exercises: statelessness + rate limits (Ex 1), reading K8s objects (Ex 2), platform choice (Ex 3), secrets flow + IaC (Ex 4).

Orchestration at Scale — Exercises

Mostly design/literacy exercises — you don't need a running cluster. Reuse the containerized service from earlier topics.


Exercise 1 — Make a service horizontally scalable

List what state must move OUT of your LLM service's process memory to allow running many replicas, and describe how you'd handle hitting the model provider's rate limit under load.

Reference solution

State to externalize (so any replica can serve any request):

  • Conversation history / sessions → Redis or Postgres (not in-process, per Memory topic).
  • Vector store → a shared vector DB / Azure AI Search.
  • LLMOps config (prompt versions) → a shared registry/DB.
  • Any per-user cache → a shared cache.

Once the app holds nothing per-user in memory, you just run more copies behind a load balancer.

Provider rate limit under load: more replicas don't raise the provider's tokens/requests-per-minute cap. So: queue requests and apply exponential backoff + retry on 429s; request a quota increase; use async I/O so each replica handles many concurrent in-flight calls while waiting on the provider; consider spreading load across regions/deployments. The bottleneck is the provider, not your CPU.


Exercise 2 — Read Kubernetes objects

For the Deployment + Service + HPA below (from the course), state what each object guarantees.

Deployment(replicas: 3, image: app:SHA, readinessProbe: /health)
Service(selects app=llm-app)
HorizontalPodAutoscaler(min: 3, max: 20, cpu target: 70%)
Reference solution
  • Deployment — keeps 3 healthy replicas of app:SHA running; restarts crashed pods (self-healing) and does rolling updates on image changes. The readinessProbe means a pod receives traffic only once /health passes.
  • Service — a stable network endpoint that load-balances across the deployment's pods. Pods come and go (scaling, restarts); the Service address stays fixed so clients don't care which pod they hit.
  • HPA — watches CPU and scales replicas between 3 and 20 to hold ~70% utilization, adding capacity under load and removing it when idle.

Together: self-healing (Deployment) + stable addressing (Service) + elastic scale (HPA). That's the core of what Kubernetes buys you — and why it's also a lot of moving parts to operate.


Exercise 3 — Pick the platform

Choose single-container/VPS, a managed container platform (Container Apps / Cloud Run), or Kubernetes for each, and justify: (a) a solo consultant's internal SMB tool at low traffic; (b) a startup's API scaling from 100 to 100k req/day; (c) a large enterprise with 15 interconnected services and a platform team.

Reference solution
  • (a) SMB tool, low trafficsingle container / VPS. Simplest ops; a cluster or even a managed platform is overkill. (This is the n8n-on-a-VPS tier.)
  • (b) Startup scaling to 100k/daymanaged container platform (Azure Container Apps / Cloud Run). Autoscaling, rolling deploys, health checks, and secrets — without running a cluster. The sweet spot.
  • (c) Enterprise, 15 services + platform teamKubernetes. Complex networking, many services, and an org that can operate a cluster justify K8s' power and complexity.

The ladder: single container → managed platform → Kubernetes. Reach for K8s only when real need (many services, complex networking, scale/control) justifies the ops burden — the same anti-over-engineering judgment as code-vs-no-code.


Exercise 4 — Secrets flow + IaC benefit

Describe how an API key gets from a vault into a running container without ever appearing in the image or manifest. Then name one concrete benefit Infrastructure as Code gives over configuring infra by hand in a cloud console.

Reference solution

Secrets flow: the key lives in a secret store (Azure Key Vault / AWS Secrets Manager / a vault-backed K8s Secret). The deployment references it (secretKeyRef / a Key Vault reference), and the platform injects it as an environment variable at runtime. The image and the YAML manifest contain only the reference, never the literal — so nothing sensitive is committed or baked in.

One IaC benefit: reproducibility/reviewability — "spin up an identical staging environment" becomes a single terraform apply instead of manually clicking through a console, and every infra change is a version-controlled, peer-reviewable diff (with an audit trail). Click-ops can't be reproduced, reviewed, or rolled back reliably.