Skip to main content

Python Kubernetes Pods: Create & Manage Containers

A Kubernetes pod is the smallest deployable unit that wraps one or more containers with shared networking and storage. For Python applications, a pod typically contains a single container running your application process, though advanced patterns use init containers or sidecar containers to handle logging, monitoring, or secrets injection. Understanding how to write a pod specification is fundamental to every Kubernetes deployment.

How Do You Define a Python Pod Specification?

A pod specification is a YAML or JSON declaration that tells Kubernetes what container image to run, what ports to expose, how much CPU and memory to allocate, and how to handle lifecycle events. The specification lives in the spec section of a Pod or Deployment manifest.

I recently debugged a Python data-processing pod that was getting killed by the Linux OOM (out-of-memory) killer because the pod spec had no memory limit. Once I added a limits.memory: 2Gi field, the issue stopped occurring. This taught me that pod specs are not optional details—they directly control how reliably your Python code runs.

Crafting a Complete Pod Specification for Python

A minimal pod spec requires a container name and image; a production spec includes resource requests, readiness probes, and environment variables. Here's a realistic example running a Python FastAPI application:

apiVersion: v1
kind: Pod
metadata:
name: python-api-pod
labels:
app: python-api
spec:
containers:
- name: fastapi-app
image: my-registry.azurecr.io/python-fastapi:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
- name: PYTHONUNBUFFERED
value: "1"
- name: APP_ENV
value: "production"
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5

This pod declares a FastAPI container with explicit resource limits, health checks, and environment variables. The imagePullPolicy: IfNotPresent avoids unnecessary image pulls if the image already exists on the node. The PYTHONUNBUFFERED=1 environment variable ensures Python output is sent directly to stdout (essential for Kubernetes log streaming).

Understanding Container Images and Registries

The image field specifies which container image Kubernetes should pull. Images are stored in registries (Docker Hub, Amazon ECR, Azure Container Registry, Google Container Registry). The format is registry-url/repository/image:tag. If you omit the tag, Kubernetes defaults to :latest, which is generally not recommended for production because latest is ambiguous.

Here's a Dockerfile that builds a Python image suitable for Kubernetes:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV PYTHONUNBUFFERED=1
EXPOSE 8000

CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

This Dockerfile builds a lightweight Python 3.11 image, installs dependencies, and runs your FastAPI application. The --host 0.0.0.0 ensures the app listens on all network interfaces (required so Kubernetes can route traffic into the container).

Resource Requests and Limits: Preventing Pod Starvation

requests tell Kubernetes the minimum resources a pod needs; limits cap the maximum. The scheduler uses requests to decide which node to place a pod on. If a pod exceeds its limit, Kubernetes terminates it. For a Python application serving a typical web workload, 100m CPU and 256Mi memory requests with 500m CPU and 512Mi memory limits is a reasonable starting point.

  • cpu: 100m means 0.1 of a CPU core (1000m = 1 full core).
  • memory: 256Mi means 256 mebibytes of RAM.

Exposing Ports and Container Networking

The ports section declares which ports the container listens on. The containerPort is the port inside the container (typically 8000 for Python web apps). You do not define external ports here; that is handled by Services (covered in the next article). Naming ports (like name: http) is optional but recommended—it allows Services to reference ports by name.

How Do Health Checks Keep Your Pod Alive?

Kubernetes provides two types of health checks:

  • Liveness probe: Checks if a pod is alive. If it fails, Kubernetes restarts the pod.
  • Readiness probe: Checks if a pod is ready to accept traffic. If it fails, Kubernetes removes the pod from service endpoints.

Both probe types support httpGet (HTTP request), exec (run a command), and tcpSocket (TCP connection). For a Python web app, httpGet is simplest:

livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

This probe makes an HTTP GET request to /health every 10 seconds, starting 30 seconds after the pod starts. If it fails 3 times consecutively, Kubernetes restarts the pod. Your Python application should implement a lightweight /health endpoint that returns HTTP 200 if the app is running correctly.

Understanding Pod Lifecycle and Init Containers

Pods have a lifecycle: pending (scheduling), running (container started), succeeded or failed (terminal state). Sometimes you need to run setup tasks before the main application starts—that's where init containers come in. Init containers run sequentially and must complete successfully before the main container starts.

Here's an example with an init container that waits for a database:

apiVersion: v1
kind: Pod
metadata:
name: python-app-with-init
spec:
initContainers:
- name: wait-for-db
image: busybox:latest
command: ['sh', '-c', 'until nc -z postgres-svc 5432; do echo waiting for db; sleep 2; done;']
containers:
- name: app
image: my-registry/python-app:1.0.0
ports:
- containerPort: 8000

The init container uses nc (netcat) to verify the database is reachable on port 5432 before the main application starts. This prevents startup errors when the database is not yet available.

Key Takeaways

  • A pod specification declares the container image, ports, resource requests/limits, and lifecycle probes.
  • Always set requests and limits to prevent pod starvation and unexpected terminations.
  • Use PYTHONUNBUFFERED=1 to ensure Python logging reaches Kubernetes logs immediately.
  • Health checks (liveness and readiness probes) keep pods running and ensure traffic flows only to healthy pods.
  • Init containers handle setup tasks before the main application starts.

Frequently Asked Questions

What happens if I don't set resource requests?

Kubernetes schedules the pod on any node with available capacity. If the node becomes overcommitted, pods may be evicted without warning. Setting requests allows the scheduler to make informed decisions and protects your application from resource contention.

Can one pod run multiple containers?

Yes, but it is uncommon in practice. Multiple containers in one pod share the same IP and network namespace but run separate processes. This pattern is useful for sidecar containers (logging agents, monitoring proxies) that tightly couple with the main application.

How do I pass environment variables to a Python container in Kubernetes?

Use the env field in the pod spec:

env:
- name: DATABASE_URL
value: "postgres://user:pass@host/db"

For sensitive data like credentials, use Secrets instead of literal values (covered in a later article).

What is the difference between imagePullPolicy: Always and IfNotPresent?

Always pulls the image from the registry on every pod creation, ensuring you get the latest version. IfNotPresent uses a cached image if it already exists on the node, reducing bandwidth and startup time. For development, Always is safer; for production, IfNotPresent is faster unless you use strict version tags.

How do I debug a pod that is stuck in pending state?

Run kubectl describe pod <pod-name> to see the pod's status and events. Pending usually means the scheduler cannot find a node with sufficient resources. Check node availability and adjust resource requests if needed.

Further Reading