Rolling Deployments for Python Apps
Learn to update Python apps with rolling deployments in Kubernetes. Step-by-step guide with hands-on exercises, troubleshooting, and next steps.
Focus: update python apps with rolling deployments
Your Python API is serving thousands of requests when your team asks you to roll out a critical bug fix. You push the new image, run kubectl set image, and watch in horror as 30% of users start seeing 500 errors. What went wrong? You updated all replicas at once, creating a brief but painful service outage. Rolling deployments exist to solve this exact problem — they update your Python apps incrementally, keeping every pod available throughout the release. In this lesson, you'll master rolling deployments in Kubernetes, from the mental model to hands-on kubectl commands, so your next update goes unnoticed by your users.
The problem this lesson solves
Imagine you run a Flask or FastAPI service with 10 replicas behind a Kubernetes Service. When you release a new version, the naive approach — deleting all pods and starting new ones — drops your service to zero availability for several seconds or minutes. During that window, every request fails. Even a short outage can cost you money, reputation, and user trust.
The need to update Python apps with rolling deployments arises because modern software changes constantly. Bug fixes, dependency upgrades, config changes, and feature releases happen weekly or daily. You need a release mechanism that:
- Keeps your service available during the update
- Allows you to catch problems early
- Lets you roll back quickly if something goes wrong
- Minimizes risk without requiring complex orchestration
Kubernetes answers this with rolling updates — a strategy where new pods are created gradually while old pods are terminated one by one. By the time the update is complete, every pod runs the new version, and no traffic is lost because at least one old pod is always serving until a new pod is ready. This is the default update strategy for Deployment objects, and every Python developer deploying to Kubernetes should understand it.
You've likely already used kubectl apply to create deployments. Without a solid grasp of rolling updates, you risk either full downtime or a release that looks successful but actually deployed a broken version to all users. This lesson eliminates that risk.
Core concept / mental model
What is a rolling deployment?
A rolling deployment (or rolling update) is a strategy for updating a Deployment's pods incrementally. It ensures that the number of available pods never drops below a threshold you define, and the number of pods above the desired count never exceeds a limit. New pods are created before old ones are terminated, so the service stays available.
Think of it like a conveyor belt
Picture a bakery line with 10 workers (pods) baking cookies (serving requests). You need to replace one worker with a new version. A rolling deployment doesn't fire everyone at once. It hires one new worker, waits until they're fully trained and producing cookies at the same rate (ready), then lets one old worker go. It repeats this until all 10 are the new version. Traffic never stops because there's always someone taking orders.
Key terminology
- ReplicaSet: The group of pods running a specific version. A deployment manages one or more ReplicaSets.
- MaxUnavailable: The maximum number of pods that can be unavailable during the update. Default is 25%.
- MaxSurge: The maximum number of pods above the desired count during the update. Default is 25%.
- Readiness probe: A check that tells Kubernetes when a pod is ready to receive traffic. Rolling updates rely on this to avoid sending requests to a broken new pod.
How it works step by step
Step 1 — Trigger an update
You trigger a rolling update by changing the deployment's pod template — typically the container image tag or a config value. This can be done with:
kubectl set image deployment/my-app my-app=myregistry/my-python-app:v2kubectl edit deployment/my-appkubectl apply -f deployment.yamlwith a modified manifest
Step 2 — Kubernetes creates a new ReplicaSet
The Deployment controller sees the spec change and creates a new ReplicaSet with the desired number of replicas (e.g., 3). It doesn't touch the old ReplicaSet yet.
Step 3 — Scale up the new, scale down the old
The controller alternates between scaling up the new ReplicaSet and scaling down the old one, respecting maxSurge and maxUnavailable. For example, with maxSurge: 25% and maxUnavailable: 25%, on a 4-replica deployment, it might add one new pod (surge), then remove one old pod (keeping 3 available), and repeat.
Step 4 — Wait for readiness
Before Kubernetes terminates an old pod, it ensures the new pod is ready (passing its readiness probe). If the new pod never becomes ready, the update pauses, and the old pods remain running.
Step 5 — Completion and rollback
When all new pods are ready and old pods are scaled to zero, the update is complete. The old ReplicaSet remains (scaled to zero) for potential rollback, which you can trigger with kubectl rollout undo.
Hands-on walkthrough
Prerequisite setup
Make sure you have a running cluster (e.g., minikube or kind) and kubectl configured. Clone a simple Python app or use the following example.
Create a simple Python deployment
Create a file app.py with a minimal Flask-like server (or just use a placeholder). Here's a complete example using a small Python HTTP server:
# app.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import os
VERSION = os.getenv("APP_VERSION", "v1")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(f"Hello from {VERSION}!\n".encode())
if __name__ == "__main__":
server = HTTPServer(("0.0.0.0", 8000), Handler)
print(f"Starting {VERSION} on port 8000")
server.serve_forever()
Build two images: my-python-app:v1 and v2, where v2 sets the environment variable to v2. Push them to a registry or load them into your cluster (e.g., with minikube image load).
Create a deployment manifest deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-python-app
spec:
replicas: 4
selector:
matchLabels:
app: my-python-app
template:
metadata:
labels:
app: my-python-app
spec:
containers:
- name: app
image: myregistry/my-python-app:v1
ports:
- containerPort: 8000
# Readiness probe so Kubernetes knows when the pod is ready
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 2
periodSeconds: 3
Apply it:
kubectl apply -f deployment.yaml
kubectl get pods -l app=my-python-app
Expected output: four pods running, all with Running status.
Trigger a rolling update
Update the image to v2:
kubectl set image deployment/my-python-app app=myregistry/my-python-app:v2
kubectl rollout status deployment/my-python-app
Watch the pods change:
kubectl get pods -l app=my-python-app -w
You'll see new pods being created (with names ending in a different ReplicaSet hash) and old ones terminating. The rollout completes when the status shows deployment "my-python-app" successfully rolled out.
Verify the update
kubectl get rs -l app=my-python-app
kubectl get pods -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image
kubectl rollout history deployment/my-python-app
Expected: two ReplicaSets (old scaled to 0, new at 4), all pods using v2, and history shows revision 1 and 2.
Compare options / when to choose what
Kubernetes offers several update strategies. Here's how they compare:
| Strategy | When to use | Pros | Cons |
|---|---|---|---|
| Recreate | When the app cannot run multiple versions simultaneously (e.g., uses exclusive locks) | Simplest, no surge | Full downtime during update |
| RollingUpdate (default) | Most Python web services | Zero downtime, gradual rollout, automatic rollback | Slightly more complex, uses extra resources for surge |
| Blue/Green (via manual) | When you need instant switch and full version separation | Zero downtime, instant rollback | Requires extra infrastructure (2x resources) |
| Canary (manual) | When you want to test with a small percentage of traffic | Controlled exposure, metrics-based | Requires additional tooling (e.g., Flagger) |
For typical Python microservices, RollingUpdate is the default and best choice. It's built into Deployment and requires zero extra configuration. If your app uses a database with incompatible schema changes, you might need blue/green or canary to avoid breaking old pods. But if you can keep backward compatibility, rolling is safer and simpler.
Troubleshooting & edge cases
Issue — New pods never become ready
Your readiness probe might be failing. Check with kubectl get pods and inspect logs:
kubectl logs <new-pod-name>
kubectl describe pod <new-pod-name>
Common causes: wrong health check path, image crash on startup, or missing environment variables. Fix the issue and the rollout will resume automatically (or you can restart the deployment).
Issue — Rollout stuck
Run kubectl rollout status to see progress. If it hangs, inspect with kubectl describe deployment. Look for events like FailedCreate or Unhealthy. Often it's a resource constraint (insufficient CPU/memory) or a misconfigured probe.
Issue — Rollback fails
If you need to undo a bad rollout:
kubectl rollout undo deployment/my-python-app
This reverts to the previous revision. If that revision also fails, you can specify a revision number:
kubectl rollout undo deployment/my-python-app --to-revision=1
Edge case — Multiple containers changing at once
If your pod has multiple containers, the deployment updates them simultaneously within each pod. Make sure all containers are compatible; otherwise, the readiness probe might fail for the whole pod.
What you learned & what's next
You now understand the core problem of updating Python apps, the mental model of rolling deployments, and the step-by-step mechanism. You also completed a hands-on exercise where you built a minimal Python app, deployed it, updated it with kubectl set image, and verified the rollout. You learned about strategies like Recreate and Blue/Green and when to choose them, and you know how to troubleshoot common issues.
Specifically, you can now:
- Explain why rolling deployments are essential for zero-downtime updates
- Manage
maxUnavailableandmaxSurgesettings to control the rollout pace - Use
kubectl rolloutcommands to monitor, pause, resume, and undo updates - Diagnose and fix readiness probe failures
You've mastered the default Deployment update path. But what about strategies that give you even more control, like relying on the next lesson in this track — canary deployments — where you can send a small percentage of traffic to the new version and observe metrics before fully rolling out? That's exactly what you'll tackle next. Practice the rolling update with your own Python service until the process feels automatic.
Practice recap
Try updating your own Python deployment with a readiness probe that intentionally fails (e.g., a wrong path) and watch the rollout hang. Then fix the probe and resume the update with kubectl rollout resume. This hands-on exercise will cement your understanding of readiness checks and rollout control.
Common mistakes
- Forgetting to define a readiness probe — without it, Kubernetes sends traffic to new pods before they're ready, causing intermittent 500s.
- Using
maxUnavailable: 0withoutmaxSurge: 1— the rollout can't proceed because no old pod can be taken down and no new pod can be created. - Updating the image tag but forgetting to push the new image to the registry the cluster can access — pods will pull the image and fail with
ImagePullBackOff. - Assuming the rollout is complete just because
kubectl get podsshows all pods running — always runkubectl rollout statusto confirm. - Scaling up replicas during a rolling update manually — this can conflict with
maxSurgeand cause the rollout to behave unexpectedly.
Variations
- Use
kubectl rollout pauseto pause the rollout after a few pods are replaced, inspect logs, thenresumeto continue. - Manually implement a canary deployment by creating a second deployment with a small replica count and adjusting a Service selector — useful when you need finer control.
- Use a GitOps approach with Argo Rollouts or Flux to automate rolling updates and automate rollbacks based on metrics.
Real-world use cases
- Deploy a bug fix to a Flask API serving production traffic without dropping a single request, ensuring users stay connected.
- Update a Celery worker deployment to a new code version while jobs are still processing — rolling updates keep the queue draining continuously.
- Upgrade a Django app's dependencies or image tag across a large replica set in a multi-zone cluster, minimizing resource use and downtime.
Key takeaways
- Rolling deployments update pods incrementally, keeping your Python service available throughout the release.
- The deployment controller creates a new ReplicaSet and scales it up while scaling down the old one, respecting
maxUnavailableandmaxSurge. - Readiness probes are critical — without them, Kubernetes may send traffic to pods that aren't actually ready.
- You can monitor, pause, resume, and rollback using
kubectl rolloutcommands. - Rolling is the default strategy and usually the best choice for Python web services; use Recreate for apps that can't run multiple versions, and consider Canary/Blue-Green for extra control.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.