Microk8s下带暂停功能的独占式Pod会话亲和性调度问题
解决方案:基于状态跟踪的动态Pod路由方案
针对你这种有状态独占型服务的调度需求,核心要解决两个问题:用户会话与Pod的绑定关系管理、Pod占用状态的动态感知与路由调整,不能依赖单纯的Cookie亲和,以下是适合Microk8s环境的落地方案:
核心架构思路
引入轻量状态存储组件(比如Redis),维护两个核心数据集合:
- Pod状态表:记录每个Pod的ID、当前状态(空闲/已占用)、占用用户ID
- 用户绑定表:记录每个用户ID对应的历史绑定PodID(保留优先级)
当用户发起请求时,先通过状态存储查询应路由的目标Pod,再通过Ingress/服务网格实现动态路由,完全规避静态Cookie亲和的局限性。
具体实现步骤
1. 部署状态存储组件(Microk8s环境)
直接启用Microk8s内置的Redis插件,快速搭建状态存储:
microk8s enable redis
通过microk8s kubectl get pods -n kube-system确认Redis Pod运行状态。
2. 改造应用服务,添加状态交互逻辑
在卡尔曼滤波器服务中嵌入以下逻辑:
- Pod启动注册:Pod启动后,向Redis写入自身状态为
空闲,注册ID用Pod的hostname(K8s中Pod hostname默认等于Pod名称):# 伪代码示例(Python) import redis import os r = redis.Redis(host="redis-kube-system.svc.cluster.local", port=6379, db=0) pod_id = os.environ["HOSTNAME"] # 初始化Pod状态为空闲 r.hset("pod_status", pod_id, "idle") - 用户请求处理:
- 用户首次请求:查询所有
空闲状态的Pod,选择一个后更新其状态为occupied,同时记录用户与Pod的绑定关系:# 从请求头提取用户标识 user_id = request.headers.get("X-User-ID") # 查询空闲Pod列表 idle_pods = [pod for pod, status in r.hgetall("pod_status").items() if status == b"idle"] if idle_pods: assigned_pod = idle_pods[0] # 更新Pod状态、用户绑定关系、Pod占用者信息 r.hset("pod_status", assigned_pod, "occupied") r.hset("user_binding", user_id, assigned_pod) r.hset("pod_owner", assigned_pod, user_id) - 用户后续请求:先查询历史绑定Pod,根据Pod状态决定路由目标:
user_id = request.headers.get("X-User-ID") bound_pod = r.hget("user_binding", user_id) target_pod = None if bound_pod: pod_status = r.hget("pod_status", bound_pod) pod_owner = r.hget("pod_owner", bound_pod) # 绑定Pod仍被当前用户占用,继续使用 if pod_status == b"occupied" and pod_owner == user_id.encode(): target_pod = bound_pod # 绑定Pod已空闲,重新占用 elif pod_status == b"idle": r.hset("pod_status", bound_pod, "occupied") r.hset("pod_owner", bound_pod, user_id) target_pod = bound_pod # 绑定Pod被其他用户占用,重新分配空闲Pod else: idle_pods = [pod for pod, status in r.hgetall("pod_status").items() if status == b"idle"] if idle_pods: target_pod = idle_pods[0] r.hset("pod_status", target_pod, "occupied") r.hset("user_binding", user_id, target_pod) r.hset("pod_owner", target_pod, user_id)
- 用户首次请求:查询所有
- 会话结束/释放Pod:用户主动结束会话(或暂停超过自定义最长保留时间)时,将对应Pod状态改回
空闲:user_id = request.headers.get("X-User-ID") bound_pod = r.hget("user_binding", user_id) if bound_pod: r.hset("pod_status", bound_pod, "idle") r.delete("pod_owner", bound_pod) # 可选:保留用户绑定关系,下次优先分配该Pod(若空闲) # r.delete("user_binding", user_id)
3. 配置Ingress实现动态路由
启用Microk8s Ingress插件后,修改Nginx Ingress的ConfigMap,添加Lua脚本实现请求转发前的状态查询:
apiVersion: v1 kind: ConfigMap metadata: name: nginx-configuration namespace: ingress-nginx data: enable-underscores-in-headers: "true" lua_shared_dict: "redis_cache 10m;" server-snippet: | location / { access_by_lua_block { local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) local ok, err = red:connect("redis-kube-system.kube-system.svc.cluster.local", 6379) if not ok then ngx.log(ngx.ERR, "failed to connect to redis: ", err) return ngx.exit(500) end local user_id = ngx.req.get_headers()["X-User-ID"] local target_pod = nil if user_id then target_pod, err = red:hget("user_binding", user_id) if err then ngx.log(ngx.ERR, "failed to get user binding: ", err) end end if target_pod then # 转发到对应Pod的Headless Service地址 ngx.var.upstream = target_pod .. ".your-service-headless.default.svc.cluster.local" else # 无绑定则转发到默认服务,由应用层处理分配 ngx.var.upstream = "your-service.default.svc.cluster.local" end red:close() } proxy_pass http://$upstream; }
同时配置Headless Service,让每个Pod拥有独立DNS记录:
apiVersion: v1 kind: Service metadata: name: your-service-headless spec: clusterIP: None selector: app: your-app ports: - port: 80 targetPort: 8080
关键优势
- 完美支持暂停功能:用户暂停后返回,若绑定Pod未被占用则继续使用,被占用则自动切换到空闲Pod
- 资源利用率高:用户释放后Pod立即回到空闲状态,可被其他用户复用
- 故障自愈:Pod意外故障时,可通过Redis过期键或Pod存活探针清理状态,用户下次请求自动分配新Pod
内容的提问来源于stack exchange,提问作者Leo Chen
相关产品推荐
相关产品推荐

