2. K8s 基础回顾:Pod、Deployment、Service、Ingress 核心概念
好,咱们正式开始第二讲。在动手调优 vLLM 之前,我强烈建议咱们先把 K8s 的这几个核心概念捋一遍。说白了,你后面所有的推理服务、弹性伸缩、流量接入,都是围绕着这几个东西转的。我见过不少同学,YAML 文件能写,但一问 Pod 和 Deployment 到底啥区别,就含糊了。嗯,这不行,咱们得把地基打牢。
2.1 Pod:K8s 里最小的“计算单元”
Pod 是什么?你可以把它想象成一个“逻辑主机”。这个主机里可以跑一个或多个容器。但在 vLLM 推理的场景下,我强烈建议你一个 Pod 只跑一个推理容器。为什么?因为方便管理资源,也方便排查问题。
每个 Pod 都有自己独立的 IP 地址、端口空间和存储卷。Pod 里的容器共享这些资源。举个例子,你可以在同一个 Pod 里放一个 vLLM 主容器和一个日志采集的 sidecar 容器,它们共享网络栈,通过 localhost 就能通信。
核心要点:Pod 是“临时”的。它可能会因为节点故障、资源不足、或者你手动更新而被销毁重建。所以,永远不要直接依赖 Pod 的 IP 地址。
我在项目中遇到过一个问题:有个同事把推理服务的客户端写死了 Pod IP,结果 Pod 一重启,服务就断了。排查了半天,最后发现是 IP 变了。所以,记住:Pod 是“牲口”,不是“宠物”。
2.2 Deployment:你的“应用管家”
Deployment 是干嘛的?说白了,它就是帮你管理 Pod 副本集的。你告诉它:“我要跑 3 个 vLLM 推理 Pod”,它就会帮你维持这 3 个 Pod 一直运行。如果某个 Pod 挂了,它会自动再拉起一个。
Deployment 最核心的能力是“声明式更新”。你只需要更新 YAML 文件里的镜像版本,Deployment 就会自动执行滚动更新,逐个替换旧 Pod,保证服务不中断。
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-inference
spec:
replicas: 3
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
ports:
- containerPort: 8000
resources:
requests:
memory: "16Gi"
cpu: "4"
limits:
memory: "32Gi"
cpu: "8"
你看,这个 YAML 里我指定了副本数是 3,每个 Pod 请求 16G 内存、4 核 CPU,限制是 32G 内存、8 核 CPU。这里有个坑:vLLM 推理对显存要求很高,但 K8s 默认不管理 GPU 显存。我曾经因为没配 GPU 资源限制,导致一个 Pod 把整张 A100 的 80G 显存全占了,其他 Pod 直接 OOM。所以,记得加上 GPU 的 resource 声明。
我的习惯:在 Deployment 的 annotations 里加上版本号和 commit ID,这样回滚时一眼就能看出哪个版本有问题。
2.3 Service:稳定的“流量入口”
好,现在 Pod 有了,Deployment 也管着它们。但问题来了:Pod 的 IP 是动态的,客户端怎么访问?这时候 Service 就登场了。
Service 为一组 Pod 提供一个稳定的虚拟 IP(ClusterIP)和 DNS 名称。客户端只需要访问这个 Service 的名字,K8s 就会自动把请求转发到后端的某个 Pod 上。说白了,Service 就是一个“负载均衡器”。
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP
这个 Service 会把 80 端口的流量转发到所有带 app: vllm 标签的 Pod 的 8000 端口上。默认类型是 ClusterIP,只能在集群内部访问。如果你想让外部也能访问,可以改成 NodePort 或者 LoadBalancer。
我记得有一次,线上推理服务的延迟突然飙升。查了半天,发现是 Service 的 sessionAffinity 没配,导致同一个用户的请求被分发到了不同的 Pod 上,缓存命中率极低。后来我加上了 sessionAffinity: ClientIP,问题就解决了。
2.4 Ingress:集群的“大门”
Service 解决了内部访问的问题,但如果你想让外部的 HTTP/HTTPS 请求进来,还得靠 Ingress。Ingress 是 K8s 里的“七层网关”,它可以根据域名、路径等规则,把请求路由到不同的 Service 上。
举个例子,你可以让 api.example.com/v1 指向 vLLM 推理服务,api.example.com/v2 指向另一个版本的推理服务。Ingress 还能帮你做 TLS 终止、限流、白名单等操作。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vllm-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: inference.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: vllm-service
port:
number: 80
注意:Ingress 本身只是一个 API 对象,真正干活的是 Ingress Controller(比如 Nginx Ingress Controller、Traefik)。你得先在集群里部署好 Ingress Controller,否则 Ingress 规则不会生效。
我曾经踩过一个坑:部署了 Ingress 规则,但访问一直 404。查了半天,发现是 Ingress Controller 的 Pod 没有暴露端口。嗯,这种低级错误,犯过一次就记住了。
2.5 它们之间的关系
说了这么多,咱们用一张图来总结一下这几个概念的关系:
你看,整个链路很清晰:用户请求先到 Ingress,Ingress 根据域名和路径把请求转发给对应的 Service,Service 再负载均衡到后端的 Pod 上,而 Pod 的副本数由 Deployment 来管理。
2.6 避坑指南
- Pod 资源限制:vLLM 推理对显存和内存需求很大。我曾经因为没设置 limits,导致一个 Pod 把节点内存吃光,其他 Pod 全被 OOMKill。记住:
requests是保证,limits是上限。 - Service 的 sessionAffinity:如果你的推理服务有状态(比如缓存),记得开启
sessionAffinity: ClientIP,否则同一个用户的请求会被分散到不同 Pod,缓存命中率会很低。 - Ingress 的 pathType:注意
Prefix和Exact的区别。我遇到过因为用了Exact导致路径匹配不上,返回 404 的情况。 - Deployment 的更新策略:默认是滚动更新,但如果你有多个推理 Pod,建议把
maxSurge和maxUnavailable调小一点,避免更新期间服务抖动。
我的小技巧:在调试阶段,可以用 kubectl port-forward 直接把本地端口映射到 Pod 上,这样不用经过 Service 和 Ingress,排查问题更快。
好了,这一章的内容就到这里。这几个概念是 K8s 的基石,你后面做 vLLM 推理的弹性伸缩、蓝绿部署、流量管理,都离不开它们。下一章咱们会深入聊聊 K8s 的资源调度和 GPU 管理,那才是真正考验功底的地方。