K8s Architecture for Python Apps

Understand Kubernetes architecture for Python apps in this first lesson of the Kubernetes for Python Developers track. Learn the core concepts, hands-on steps, troubleshooting, and what to study next.

Focus: understand kubernetes architecture for python apps

Sponsored

You've built a solid Python app — it works on your laptop, your tests pass, and your CI pipeline is happy. Then production hits: traffic spikes, a node crashes, and your single python app.py process dies with it. Users see 502s, you're paged at 3 AM, and you realize that running a service on one machine is a gamble. Kubernetes exists to solve exactly this — it gives you a platform to deploy, scale, and heal your Python applications automatically. But before you can kubectl apply anything, you need to understand the architecture underneath. This lesson breaks down the core components of a Kubernetes cluster and how they work together, specifically from the perspective of a Python developer who wants to run services like FastAPI, Django, or Celery workers in production.

The problem this lesson solves

When you first look at Kubernetes, the jargon alone is overwhelming: nodes, pods, controllers, schedulers, etcd, kubelet, kube-proxy — it feels like a distributed systems course dropped on your desk. The real problem isn't the technology; it's that you don't have a mental map of how these pieces fit together. Without that map, you'll write YAML that "works" but you won't know why it breaks under load, why a Pod restarts, or why your service is unreachable from outside the cluster.

More specifically, Python developers often hit these walls: - Your app runs fine in a container, but you don't know how Kubernetes decides where to run it. - You see CrashLoopBackOff and have no idea why the control plane keeps killing your process. - You hear “stateless” and “stateful” but aren't sure which of your Python services need special handling (like a Celery broker vs. a Postgres database). - You can't reason about scaling because you don't understand the ReplicaSet and Deployment layers.

This lesson closes that gap. You'll walk away with a clear architectural picture — the same one a production Kubernetes engineer uses — and the vocabulary to find answers in official docs, Stack Overflow, and team discussions.

Core concept / mental model

Think of Kubernetes as a hotel manager for your microservices. You have a building with many rooms (nodes), each room can host several guests (pods), and the manager's job is to place guests, clean up after them, and make sure they have what they need. You never talk to the housekeeper directly about a specific guest; you talk to the front desk, and the manager handles the rest.

In Kubernetes terms: - Cluster — the whole hotel, your entire set of machines working as one unit. - Node — a single machine (physical or virtual) in the cluster, like a room. Each node runs your workloads. - Pod — the smallest deployable unit, one or more containers that share storage and network, like one or more guests sharing a hotel room. In practice, a Pod usually wraps a single Python container (e.g., your Gunicorn process). - Control plane — the management staff. It includes the API server (front desk), scheduler (decides which room a new guest goes to), etcd (the reservation book), and controller managers (housekeeping supervisors). - Worker nodes — the rooms themselves. Each runs a kubelet (the room's phone to the front desk), a container runtime (like Docker), and kube-proxy (the building's internal messenger that ensures network rules).

To understand Kubernetes architecture for Python apps, remember this core flow: you submit a desired state (via kubectl or the API), the control plane stores it in etcd, the scheduler picks nodes, and kubelet agents on those nodes start or stop containers to match that desired state. The controllers continuously watch for differences between what you asked for and what's running, and they take action to make reality match your intent.

For your Python app, the key takeaway: you define the desired state — the image, replicas, environment variables — and Kubernetes handles the "how".

How it works step by step

Let's trace what happens when you run kubectl apply -f deployment.yaml for a FastAPI service. This is the heartbeat of everything you'll do with Kubernetes.

  1. You send the request. kubectl talks to the API server (port 6443). It authenticates you and validates your YAML against the schema.
  2. etcd stores the desired state. The API server writes your Deployment object into etcd, the cluster's source of truth. If the API server crashes, etcd still has your configuration.
  3. The Deployment controller notices a change. It watches the API server and sees a new Deployment object. It creates a ReplicaSet, which declares "I want 3 replicas of this Pod template."
  4. The ReplicaSet controller creates Pods. For each replica, it asks the scheduler to place a Pod on a suitable node.
  5. The scheduler finds a home. It reads node metrics, resource requests/limits, taints, and affinities to pick the best node. It updates the Pod object with the node name.
  6. The kubelet on that node launches containers. Your kubelet sees the Pod assigned to its node, pulls your Python image, and starts the container with your specified environment variables and commands.
  7. kube-proxy updates network rules. So that services can route traffic to the Pod's IP. Meanwhile, your controller keeps watching — if a Pod dies, it recreates one to match the desired count.

Every step is about reconciliation: the cluster constantly compares actual state with desired state and acts to close the gap. If your Python process exits (say, unhandled exception), kubelet restarts it according to your restartPolicy. If a node fails, controllers reschedule the Pods elsewhere.

This architecture is declarative: you don't tell Kubernetes how to run your app, you tell it what you want, and it persists that intent and enforces it.

Hands-on walkthrough

Let's ground this in practice. You'll use minikube (or any local cluster) to see the architecture in action. These commands work on Linux/macOS with Docker installed.

First, start a local cluster:

# Check if you have it; otherwise install minikube and kubectl
minikube start --cpus=2 --memory=2048
kubectl cluster-info

You'll see the control plane URL and CoreDNS info. Now create a simple Python deployment. Save this as python-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastapi-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: fastapi-demo
  template:
    metadata:
      labels:
        app: fastapi-demo
    spec:
      containers:
      - name: api
        image: python:3.11-slim
        command: ["sh", "-c", "pip install fastapi uvicorn && uvicorn main:app --host 0.0.0.0 --port 8000"]
        ports:
        - containerPort: 8000

Apply it, then inspect what the cluster does:

kubectl apply -f python-deployment.yaml
# Watch the rollout
kubectl get events --watch
# See the Pods and which node they're on
kubectl get pods -o wide

You should see output like:

NAME                            READY   STATUS    RESTARTS   AGE   IP           NODE
fastapi-demo-7d9f5d6b8f-abcde   1/1     Running   0          45s   10.244.0.3   minikube
fastapi-demo-7d9f5d6b8f-ghijk   1/1     Running   0          45s   10.244.0.4   minikube

Now take a live look at the control plane components (in minikube, they run as containers):

kubectl -n kube-system get pods

You'll see etcd-minikube, kube-apiserver-minikube, kube-scheduler-minikube, and kube-controller-manager-minikube — the control plane in action. On the worker side, kube-proxy-xxxx runs on the same node.

Test the resilience: delete one Pod and watch it come back.

kubectl delete pod fastapi-demo-7d9f5d6b8f-abcde
kubectl get pods

Within seconds, a new Pod will appear. That's the ReplicaSet controller reconciling state. You're seeing architecture, not magic.

Expected outcome: You can identify each control plane component, explain the scheduler's role, and demonstrate self-healing. This is exactly the understand Kubernetes architecture for Python apps foundation you need.

Pro tip: If your Python image is large, pull times can make the rollout slow. Use a slim image or cache layers in CI to speed up pod startup.

Compare options / when to choose what

When working with Kubernetes, you'll choose between deployment and management patterns. Here's a quick comparison relevant to Python developers:

Approach What it gives you Best for Watch out for
Plain Pods Direct container launches Debugging / one-off jobs No self-healing, no scaling
Deployments Rolling updates, replicas, rollback Stateless Python web apps (FastAPI, Flask, Django) Extra layer to reason about
StatefulSets Stable network identity, persistent storage Stateful Python services (Celery with DB, distributed lock) More complex, needs PVs
DaemonSets Run one pod per node Logging agents, metrics collectors in Python Not for general app scaling
Jobs Runs-to-completion workloads Batch Python scripts, one-off migrations Not for long-running services

For most Python web services, Deployments are your default. If your app holds state (like a local cache or queue), consider StatefulSets — but only after you've decoupled state into an external store (like Redis or Postgres) where possible.

Also decide how you interact with the cluster: - kubectl — quick, imperative, good for learning and debugging. - Helm — package your Python app as a chart, reusable with templating. - Python client (kubernetes pip package) — automate deployments from Python scripts or CI, which you'll explore later in this track.

For learning, kubectl first. For production automation, Helm or the Python client.

Troubleshooting & edge cases

Even with a solid mental model, things fail. Common symptoms you'll see as a Python developer:

  • CrashLoopBackOff — Your Python container starts then exits. Check logs: kubectl logs <pod-name>. Often it's a missing dependency, misconfigured environment variable, or your process daemonizes instead of staying in the foreground. Fix by running uvicorn main:app directly instead of nohup ... &.

  • Pod stuck in Pending — The scheduler can't place the Pod. Run kubectl describe pod <name>. Common causes: insufficient CPU/memory on nodes, image pull secrets missing, or a node selector that matches no nodes. For a two-replica FastAPI, ask for only as much CPU as you need (e.g., 100m).

  • ImagePullBackOff — The cluster can't pull your Python image. Check the image name for typos; if private, verify imagePullSecrets. Use a published image or a registry you can access from the node.

  • Pods restart but your app has no external IP — You used a Deployment but no Service. Remember: Pods are ephemeral; to reach your FastAPI app you need a Service (in a later lesson). This is a classic architecture confusion.

  • Kubelet is not happykubectl get nodes shows NotReady. On minikube, often the VM runs out of memory. Restart minikube with more resources.

  • You see a node down — If a node fails, Pods on it are evicted and rescheduled. If they were part of a StatefulSet, they might lose local storage; that's why you design for statelessness with Deployments.

Blockquote tip: Whenever something goes wrong, kubectl describe is your best friend. It gives you events and conditions, unlike logs which only shows app output.

What you learned & what's next

You now understand the Kubernetes architecture that runs your Python workloads: the control plane (API server, etcd, scheduler, controllers) and worker nodes (kubelet, kube-proxy), plus the core objects (Pods, ReplicaSets, Deployments) and how they reconcile to keep your desired state. You've seen this architecture live with a FastAPI deployment and you know how to debug common placement and runtime issues.

You've met the first learning objective — explain the core idea — and the second — complete a practical exercise. Next in this track, you'll learn how to containerize a Python app so it's ready to run on this architecture. That's where Dockerfiles and image best practices come in — the bridge from code to cluster.

Continue to the next lesson: Containerizing Python apps for Kubernetes.

Practice recap

Now that you've seen the architecture, create a second Deployment for a Python worker service (e.g., a background job that prints 'Hello'). Watch its logs to confirm it starts. Then simulate a failure by deleting the Pod and observe that a new one replaces it — you'll see the self-healing behavior in action. This hands-on exercise consolidates your understanding before we move to containerizing your Python app.

Common mistakes

  • Thinking a Pod is the same as a container — a Pod can hold multiple containers (e.g., a sidecar) that share network and storage, but for simple Python apps it's usually one container per Pod.
  • Not understanding the control plane: making changes directly on nodes instead of using the API server, or forgetting that etcd persists your desired state and must be backed up.
  • Using a Deployment for a batch Python script that should be a Job — Deployments keep running, so your script will be restarted after it exits, causing a CrashLoop.
  • Expecting self-healing without a controller: a plain Pod won't be rescheduled if its node dies; you need a Deployment (or similar) for that.
  • Forgetting that Pod IPs are ephemeral — if your Python app needs a stable address, you must create a Service to route traffic to Pods.
  • Assuming resources are unlimited: not setting CPU/memory requests and limits can get your Python pod evicted or cause the scheduler to overcommit nodes.

Variations

  1. Using kind instead of minikube for local clusters — kind runs a lightweight Kubernetes in Docker containers, which is faster for CI and some development setups.
  2. Choosing a managed Kubernetes offering like Amazon EKS, Google GKE, or Azure AKS — they handle the control plane for you, so you only manage worker nodes and workloads.
  3. Using the Kubernetes Python client to inspect cluster objects programmatically instead of only kubectl — great for automation scripts and dashboards.

Real-world use cases

  • Running a FastAPI microservice as a Deployment with 3 replicas behind a Service, so it scales to handle traffic spikes and heals from pod failures automatically.
  • Deploying a Celery worker pool as a Deployment with a fixed number of replicas, each consuming from the same Redis queue, to process background jobs in parallel.
  • Running a Python data migration job as a Kubernetes Job that runs once to update a database, without leaving orphaned pods running.

Key takeaways

  • Kubernetes is a declarative platform: you define desired state in YAML, and controllers enforce it continuously.
  • The control plane (API server, etcd, scheduler, controllers) manages the cluster; worker nodes run your Python containers via kubelet.
  • A Pod is the smallest unit; Deployments create ReplicaSets to keep the desired number of Python app replicas alive.
  • Reconciliation is the magic: if a Pod dies, the ReplicaSet controller sees the mismatch and creates a new one.
  • For stateless Python web services, use Deployments; reserve StatefulSets for services that need stable identity or storage.
  • Troubleshoot with kubectl describe and kubectl logs to distinguish scheduling vs runtime issues.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.