Appearance
第十六阶段:Kubernetes 部署
docker compose up -d 很好用,但它只管"单机跑起来"。容器挂了没人救、更新要停机、想扩几个副本分不了流量——Kubernetes(K8s)管的就是"跑起来之后的事":自愈、扩缩容、滚动更新。
本阶段在本机(Docker Desktop 自带的 K8s)上把项目搬进 K8s,是路线表第 8 行的最后一块拼图。
一、核心概念(用 Compose 类比)
| K8s | 类比 Compose | 一句话 |
|---|---|---|
| Pod | 一个容器实例 | 最小单位,里面跑容器 |
| Deployment | service(+ replicas) | "给我维持 N 个 Pod",挂了自动补 |
| Service | 服务名 DNS 的升级版 | 给一群 Pod 一个固定地址 + 负载均衡 |
| ConfigMap / Secret | environment / .env | 配置放 ConfigMap,LLM_API_KEY 放 Secret |
| PVC | volumes | 数据持久化 |
| kubectl | docker compose 命令 | 和集群对话的命令行工具 |
二、准备:打开本机 K8s
- Docker Desktop → Settings → Kubernetes → 勾选 Enable,点 Apply;
- 验证:
bash
kubectl get nodes
# 看到 docker-desktop Ready 即成功- LM Studio 开着(bge-m3 已加载,Start Server,局域网访问已开)。
三、文件总览:k8s/ 目录
text
k8s/
configmap.yaml # 非敏感配置:Qdrant/Redis/Embedding 地址
redis.yaml # Deployment + Service
qdrant.yaml # StatefulSet + Service(有状态数据要 PVC)
api.yaml # Deployment(2 副本)+ Service
worker.yaml # Deployment(1 副本)
README.md # 命令速查密钥(LLM_API_KEY)不进仓库,手动建一次:
bash
kubectl create secret generic llm-secret --from-literal=LLM_API_KEY='你的key'四、逐个看
ConfigMap:配置集中放
yaml
data:
QDRANT_URL: "http://qdrant:6333"
REDIS_URL: "redis://redis:6379/0"
CELERY_REDIS_URL: "redis://redis:6379/1"
EMBED_URL: "http://host.docker.internal:1234/v1"对应 compose 里的 environment。注意 EMBED_URL 还是 host.docker.internal——容器里 localhost 是自己,这个特殊域名才能穿透到宿主机的 LM Studio。
redis.yaml:Deployment + Service
yaml
kind: Deployment
spec:
replicas: 1
template:
spec:
containers:
- name: redis
image: redis:7
---
kind: Service
metadata:
name: redis
spec:
selector:
app: redis
ports:
- port: 6379Deployment 说"我要 1 个 redis Pod";Service 给它一个集群内固定地址,其他 Pod 用 redis:6379 就能找到它——不管 Pod 重启换多少次 IP。
qdrant.yaml:StatefulSet + PVC
向量数据不能丢,用 StatefulSet + volumeClaimTemplates 自动申请 5Gi 持久化存储,对应 compose 里的 qdrant_data 卷。
api.yaml:2 副本 + 健康检查
yaml
kind: Deployment
spec:
replicas: 2 # 跑 2 个 api 分担请求
template:
spec:
containers:
- name: api
image: agent-learning:latest
imagePullPolicy: IfNotPresent # 本地已有镜像,不去网上拉
envFrom:
- configMapRef:
name: agent-config
env:
- name: LLM_API_KEY
valueFrom:
secretKeyRef:
name: llm-secret
key: LLM_API_KEY
readinessProbe: # /health 不通就不给它分流量
httpGet: {path: /health, port: 8000}
periodSeconds: 10
livenessProbe: # /health 长期不通就重启容器
httpGet: {path: /health, port: 8000}
periodSeconds: 30Service 把 2 个副本包成一个地址,请求自动负载均衡。
worker.yaml:同镜像,不同启动命令
yaml
command: ["celery", "-A", "tasks", "worker", "--loglevel=info", "--concurrency=1"]和 compose 里一样。PDF 不用 hostPath 挂载(Windows 路径映射是坑),部署后用 kubectl cp 拷进去。
五、部署
bash
# 1. 建镜像(K8s 和 Docker Desktop 共用 daemon,直接可见)
docker build -t agent-learning:latest .
# 2. 一键部署
kubectl apply -f k8s/
# 3. 看状态,4 个 Pod 都 Running 才算好
kubectl get pods六、验证:port-forward + 老三样
K8s 里的 Service 默认只在集群内可访问,拿 port-forward 映射到本机:
bash
kubectl port-forward svc/api 8000:8000
# 新开终端:
curl http://localhost:8000/health然后跑 stage15 的老三样:POST /ingest → 轮询 /tasks/{id} 看 SUCCESS → /ask 问答正常。Worker 的 PDF 先拷进去:
bash
kubectl cp ./data/. $(kubectl get pod -l app=worker -o jsonpath='{.items[0].metadata.name}'):/app/data/七、K8s 的真本事:扩容与滚动更新
扩容(Compose 时代要手动 --scale,现在一句话):
bash
kubectl scale deployment/api --replicas=3
kubectl get pods # 3 个 api Pod,Service 自动分流量滚动更新(改完代码不断线发布):
bash
docker build -t agent-learning:latest .
kubectl rollout restart deployment/api
kubectl rollout status deployment/api # 看着它一个一个替换期间一直 curl /health,请求不断——旧 Pod 要等新 Pod 就绪才下线。
看日志:
bash
kubectl logs -l app=worker
kubectl logs -l app=api八、清理
bash
kubectl delete -f k8s/
kubectl delete secret llm-secret九、提交代码
bash
git add k8s/
git commit -m "16 Kubernetes 部署"
git push项目 4 小结(完结)
- 第十二阶段:FastAPI 把问答做成 HTTP 接口;
- 第十三阶段:Qdrant 专业向量数据库;
- 第十四阶段:Dockerfile + Compose 一键拉起;
- 第十五阶段:Redis 缓存 + Celery 异步入库;
- 第十六阶段:Kubernetes,自愈、扩缩容、滚动更新。
从 01_hello_llm.py 到 K8s 集群,四个项目走完,一个能真正上线的知识库问答服务已经在你手里了。接下来可以往深里走:Prometheus + Grafana 监控、KEDA 按队列长度自动扩 worker、或者把 embedding 也搬进集群。
👉 附录:踩坑记录