Multi-Pod K8s Session Management with Redis
Running Claude Control in a Kubernetes environment requires sharing session state across multiple pods. On a single server, storing sessions in memory is straightforward, but when K8s scales out, state becomes inconsistent between pods. This post documents how we solved that problem using Redis as a central store.
The Problem
[Pod A] Create session → Save to Redis ✅
[Pod B] Send prompt to that session → No local process found ❌
The session's metadata exists in Redis, but the actual Claude process only lives on the pod that created it. That mismatch needed to be addressed.
Solution: Session-Pod Affinity
Store the originating pod's information in Redis alongside the session, then have the routing layer forward requests to the correct pod.
import socket
from datetime import datetime
class K8sSessionManager:
def __init__(self, redis_store: RedisSessionStore):
self.store = redis_store
self.pod_name = os.getenv("POD_NAME", socket.gethostname())
self.pod_ip = os.getenv("POD_IP", "127.0.0.1")
async def create_session(self, session_id: str, workdir: str):
# Store pod info together with the session on creation
await self.store.save_session(session_id, {
"status": "active",
"workdir": workdir,
"pod_name": self.pod_name,
"pod_ip": self.pod_ip,
"created_at": datetime.now().isoformat(),
})
# Maintain a per-pod active session list
await self.store.redis.sadd(
f"claude:pod:{self.pod_name}:sessions", session_id
)
return await self._start_local_process(session_id, workdir)
async def route_request(self, session_id: str, prompt: str):
session = await self.store.get_session(session_id)
if not session:
raise ValueError("Session not found")
target_pod = session["pod_name"]
if target_pod == self.pod_name:
return await self._execute_local(session_id, prompt)
else:
# Forward request to the target pod
return await self._forward_to_pod(
session["pod_ip"], session_id, prompt
)
Inter-Pod Communication
Requests forwarded to another pod use internal HTTP calls.
import httpx
async def _forward_to_pod(self, pod_ip: str, session_id: str, prompt: str):
async with httpx.AsyncClient() as client:
response = await client.post(
f"http://{pod_ip}:8000/internal/sessions/{session_id}/prompt",
json={"prompt": prompt},
timeout=120.0,
)
return response.json()
Health Checks and Session Migration
When a pod goes down, its sessions need to be migrated to another pod.
async def health_check_loop(self):
while True:
pods = await self.store.redis.smembers("claude:pods:active")
for pod_name in pods:
pod_ip = await self.store.redis.hget(f"claude:pod:{pod_name}", "ip")
if not await self._is_pod_alive(pod_ip):
await self._migrate_sessions(pod_name)
await self.store.redis.srem("claude:pods:active", pod_name)
await asyncio.sleep(30)
Conclusion
Multi-pod K8s support significantly improved the stability and scalability of Claude Control. The pattern of using Redis as a central hub with inter-pod HTTP communication as a secondary channel worked well. That said, fully restoring the state of a Claude process during session migration remains an open challenge.