这套 SpringAI 智能审核项目,写到现在已经是第十八掌。前面我们已经折腾过模型接入、提示词调优、Redis 集群缓存,也解决过并发压测下各种玄学问题,但说实话,代码能跑和能稳定跑完全是两回事。这一掌取名“神龙摆尾”,意思很简单:前面十七掌攒下的功力,到最后要真正落到生产环境——用 K8s 把整套服务托上云。
神龙摆尾不是花架子,它是整套降龙掌法里最需要回头审视自己弱点的那一招。在这一章里,我会把 SpringAI 服务容器化、推上 Kubernetes 集群、接好配置中心、连上 Redis 集群,再把常见故障一个个拆开揉碎讲清楚。适合已经跑通 SpringAI、但还没正经把服务送上 K8s 的读者,照着做能少踩一大半我踩过的坑。
1. 关于这一章:神龙摆尾要解决什么
1.1 为什么把部署放到第十八掌
我先说一个很多人的误区:项目做到能跑,跟项目做到能上线,中间还隔着一条很宽的河。
我之前接触过不少 SpringAI 项目,很多朋友在本地跑得飞快,一说到部署就犯怵。要么是 Docker 镜像没做精简,几百 MB 的 jar 包塞进去,启动要一分多钟;要么是把 Redis 连接地址写死在 application.yml 里,换套环境就得改代码重新打包;还有更头大的,多副本一开,日志散得到处都是,排查问题两眼一抹黑。
这一掌之所以放在第十八掌,是因为它依赖前面所有内容:没有合理的提示词配置策略,上 K8s 之后就要频繁改镜像;没有 Redis 集群做缓存,线上突发流量一来,大模型接口被调用方限流,整个审核服务就会被打穿。所以我一直建议,部署不是最后一公里,部署是前面所有设计的总验收。神龙摆尾这一招,本质上是回头把自己的弱点全部补齐。
1.2 整条技术链路的全貌
为了保证后面操作不跑偏,我先把这一整套 SpringAI 智能审核服务的架构摆出来。
- 入口层:用户通过接口提交待审核内容,比如合同文本、工单描述、用户评论。
- 审核服务层:基于 SpringAI 的智能审核服务,负责调度大模型完成敏感信息识别、合规风险判断、内容打分等任务。
- 大模型层:这里以阿里云通义千问接入为例,SpringAI 官方也支持 OpenAI、Ollama 等,改配置就能切换。
- 缓存与限流层:Redis 集群,用来缓存审核结果、做接口限流和幂等控制。
- 部署层:K8s 集群,运行上面所有服务的容器副本,并提供滚动更新、自动扩缩容、故障自愈能力。
这个链路里,SpringAI 解决的是“怎么把大模型能力编排进业务代码”,Redis 集群解决的是“高并发下别把模型侧打出问题”,K8s 解决的是“整条链路上的服务如何稳定跑起来”。三者缺一不可。后面每一节,我都会围绕这三者展开。
2. 容器化:SpringAI 上 K8s 前必须想清楚的事
2.1 先别急着写 YAML,镜像得做得能打
我见过很多第一次上 K8s 的人,上来就是一把梭写 Deployment,结果 Pod 一直 CrashLoopBackOff,查了半天发现是镜像本身有问题。容器化这一步虽然不在 K8s 内部,但它决定了后面所有步骤能不能顺利。
先说镜像选型。SpringAI 应用本质是一个 Spring Boot 项目,我用的基础镜像是 eclipse-temurin:17-jre-alpine。为什么不用带 JDK 的?因为运行只需要 JRE,镜像体积能少三分之一。为什么用 alpine 而不是 ubuntu?因为它小,而且我们这个场景只做 JSON 解析和 HTTP 调用,没有复杂的 native 依赖。
但这里有一个坑,alpine 镜像默认没有时区数据和字体。如果审核服务要处理 PDF 或者生成图片验证码,缺 fontconfig 会直接报字体相关的 NoClassDefFoundError。所以我在 Dockerfile 里做了两步补充。
FROM eclipse-temurin:17-jre-alpine RUN apk add --no-cache tzdata fontconfig ttf-dejavu \ && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone WORKDIR /app COPY target/springai-review.jar app.jar ENV JAVA_OPTS="" EXPOSE 8080 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]解释一下几个关键点:第一,设置时区这一步不能省,否则容器内时间会是 UTC,审核记录的时间戳会跟业务系统对不上。第二,ttf-dejavu 是为了保证 Java 图形相关操作不因字体缺失而崩溃。第三,JAVA_OPTS 留空,方便后期在 K8s 的 env 里注入 JVM 参数。
再补一个 .dockerignore,这个很多人会漏:
target/ logs/ .idea/ *.iml漏掉 .dockerignore 的后果是,构建上下文会把本地几 GB 的 target 目录发到 Docker daemon,轻则构建慢,重则直接打爆镜像仓库。这个模板我用了很久,SpringAI 项目只要不引入特殊 native 库,都能直接套用。
2.2 Docker 和 K8s 到底差在哪
这个热搜词我每次写部署都要解释一遍,因为真的有人以为 K8s 是 Docker 的替代品。不是。Docker 解决的是“单台机器上怎么把应用装进容器并跑起来”,K8s 解决的是“很多台机器组成的集群里,怎么让容器自动分配到合适的节点、挂了自动重启、流量高峰自动扩展”。
打个生活化的比方:Docker 是集装箱,K8s 是港口调度系统。集装箱解决货物标准化装载,调度系统解决几十台吊车、几千个集装箱怎么协调。没有集装箱,调度系统无从谈起;只有集装箱没有调度系统,港口就只能靠人肉搬运。
具体到 SpringAI 项目,Docker 阶段你只需要保证 docker run -p 8080:8080 springai-review 能起服务。但线上不可能只跑一个实例,你至少需要两个副本做高可用,流量大了可能要扩到十个。这时候手工 docker run 已经失控,你需要告诉 K8s:我要跑 3 个副本,内存上限 2Gi,CPU 超过 60% 就再扩两个。K8s 负责把这些事变成声明式配置,持续执行。
有一点要注意,K8s 的容器运行时默认已经不是 Docker,而是 containerd。从用户视角看差别不大,但排查问题时你会发现 docker ps 看不到 K8s 创建的容器,要用 crictl ps。这个我在后面的问题排查里会专门说。
3. 环境准备:Rocky Linux 安装 K8s 集群
3.1 节点规划与系统初始化
这一节内容基于我在 Rocky Linux 上的实操,版本以 K8s 1.36 为例。版本迭代很快,大家操作时以实际安装的官方稳定版本为准,核心命令基本一致。
先说节点规划。我这边准备了 3 台机器:1 台 master 节点,2 台 worker 节点。生产环境建议 master 至少 3 台做 HA,但我们项目阶段用单 master 够用。硬件上 master 给 4C8G,worker 给 8C16G。SpringAI 服务本身不重,但 JVM 和 Redis 集群会吃内存,worker 内存给足一点不吃亏。
系统初始化这步不能省,下面是几个关键操作。
第一,关闭 swap。K8s 要求节点 swap 关闭,否则 kubelet 无法正常工作。用 swapoff -a 只是临时关闭,必须注释掉 /etc/fstab 里 swap 相关行。
第二,加载内核模块并调整系统参数。需要 br_netfilter 和 overlay 模块,还要把 net.bridge.bridge-nf-call-iptables 设为 1,否则集群内 Service 转发会出问题。
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF sudo sysctl --system第三,关闭防火墙。按说生产环境防火墙应该开着,但 K8s 组件之间的端口很杂,教学阶段为了不被各种端口问题折腾,我建议先 disable,等整个集群跑通再按需放行。真实生产建议用安全组规则替代本机防火墙,明确放行 6443、2379、2380、10250、10256 等重点端口。
容器运行时我选 containerd,这也是目前 K8s 默认推荐的运行时。安装方式很简单,yum 装好之后把 config.toml 里的 SystemdCgroup 改成 true,否则后面 kubelet 的 cgroup 驱动会和 containerd 冲突,Pod 怎么起都起不来。
3.2 用 kubeadm 初始化集群
镜像源这一步,我直接配的是国内镜像源,K8s 组件下载速度会快很多。安装 kubelet、kubeadm、kubectl 三件套,版本都固定在 1.36。
cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el9-x86_64/ enabled=1 gpgcheck=0 EOF sudo yum install -y kubelet kubeadm kubectl sudo systemctl enable --now kubelet初始化 master 节点时,我指定了 Pod 网段为 10.244.0.0/16,这个是给后面 Flannel 网络插件用的。如果你用 Calico,建议换成 192.168.0.0/16。
sudo kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --pod-network-cidr=10.244.0.0/16执行完之后,kubeadm 会输出一段 kubeadm join 命令,保存好,worker 节点加入集群就要用它。初始化完成后需要配置 kubectl 的 kubeconfig:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config网络插件我习惯用 Flannel,原因很简单:配置少、链路干净、对 SpringAI 这种普通业务没有额外性能要求。安装只要一条 manifest 应用:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml装完之后等几分钟就能看到所有节点 Ready。worker 节点上直接跑之前保存的 kubeadm join 命令就行,join 命令里带着 token,不用额外处理。
3.3 namespace 规划是部署前的第一次分治
好多人第一次用 K8s 会忽略 namespace,所有东西都丢到 default 里。一开始可能没感觉,等接入了 Redis 集群、ConfigMap、监控告警,就会发现 namespace 其实就是 K8s 里的“租户”概念,用来做资源隔离、权限隔离、网络策略隔离。
我在这个项目里规划了两个 namespace:prod-ai 和 test-ai。prod-ai 放生产审核服务和 Redis 集群,test-ai 放联调环境。这样两个环境之间的逻辑互不干扰,而且后面做 RBAC 授权时,可以做到只给测试同学分配 test-ai 的权限,生产环境他们连看都看不到。
创建 namespace 很简单:
kubectl create ns prod-ai kubectl create ns test-ai有一点要注意:很多新人部署 Redis 集群时,明明 yaml 都写对了,但 Spring 服务连接不上,查到最后发现是 Redis 的 Service 在另一个 namespace 里,DNS 域名没带 namespace 后缀。K8s 里跨 namespace 访问服务必须用完整域名:服务名.命名空间.svc.cluster.local,本 namespace 内访问才能用短名。这个坑我在排查实录里还会强调。
4. 配置中心与 Redis 集群联动
4.1 系统提示词配置怎么落到 K8s 里
“SpringAI 系统提示词怎么配置”这个搜索词能上热榜,说明大家确实被这个问题卡过。SpringAI 里配置系统提示词其实很灵活,但很多人第一反应是把提示词写死在 Java 代码里。我不建议这么做,因为提示词是要反复调的。今天审核规则变了,明天要加一条敏感词策略,如果每次改提示词都要重新打包镜像,用不了两周你就会疯。
我的做法是把系统提示词放到 K8s 的 ConfigMap 里,然后在 Deployment 中用环境变量注入到 Spring 容器。先定义一个 ConfigMap:
apiVersion: v1 kind: ConfigMap metadata: name: ai-config namespace: prod-ai data: system-prompt: | 你是一个专业的智能审核助手。请从以下维度审核用户提交的文本内容: 1. 敏感信息:识别身份证号、手机号、银行账号等。 2. 合规风险:判断是否存在虚假宣传、绝对化用语。 3. 内容安全:识别辱骂、暴力、违禁内容。 4. 格式规范:检查是否包含必要字段。 请输出 JSON,格式为:{"level":"safe/warn/danger","riskTags":[],"score":0}Java 侧只需用 Spring 的 @Value 注解读取环境变量:
@Service public class ReviewService { @Value("${ai.prompt.system}") private String systemPrompt; public ReviewResult review(String input) { PromptTemplate template = new PromptTemplate(""" {systemPrompt} 待审核内容:{input} """); Map<String, Object> params = Map.of( "systemPrompt", systemPrompt, "input", input ); // 调用大模型并解析结果 } }然后在 Deployment 的 env 里引用 ConfigMap,application.yml 里写ai.prompt.system: ${AI_PROMPT_SYSTEM}就能对上 Spring 占位符语法:
env: - name: AI_PROMPT_SYSTEM valueFrom: configMapKeyRef: name: ai-config key: system-prompt这个方式的优势很明显:改提示词只需要 kubectl edit configmap,然后重启 Pod 或触发滚动更新,镜像是完全不变的。线上调提示词十分钟内就能生效,这对运营和算法团队来说非常友好。
4.2 Redis 集群部署与 Spring 连接参数
SpringAI 智能审核服务里,Redis 集群承担两个任务:一是审核结果缓存,相同内容在短时间内重复提交,直接返回缓存结果,不给大模型重复计费;二是限流,每个调用方单位时间内的请求数控制在一个阈值,防止恶意刷接口把大模型调用额度打光。
Redis 集群我用了 3 主 3 从的标准架构,在 K8s 里通过 StatefulSet 部署,配合 Headless Service 来固定网络标识。如果嫌手动写 StatefulSet 太啰嗦,可以直接用 Helm chart 装 bitnami/redis-cluster,十分钟能起一套。不过为了讲清楚原理,我建议至少手动部一遍,理解 StatefulSet 为什么要用稳定网络标识。
Spring Boot 连接 Redis 集群的配置是这样:
spring: data: redis: cluster: nodes: - redis-cluster-0.redis-cluster-headless.prod-ai.svc.cluster.local:6379 - redis-cluster-1.redis-cluster-headless.prod-ai.svc.cluster.local:6379 - redis-cluster-2.redis-cluster-headless.prod-ai.svc.cluster.local:6379 password: ${REDIS_PASSWORD} timeout: 3s配置里的域名必须写完整,这个我前面说过,跨 namespace 或者通过 Headless Service 访问,短名在解析时可能有延迟,写完整最稳妥。还有一个容易被忽视的参数是 timeout,默认可能只有几秒,网络一抖动就报连接超时。我建议调整到 3 秒到 5 秒之间,太长会让限流功能失效。
Secret 别直接写在 yaml 里。用 kubectl create secret 生成,然后在 Deployment 里通过 secretKeyRef 注入。比如:
kubectl create secret generic redis-secret \ --from-literal=password=YourStrongPass \ -n prod-ai这个习惯从第一天就要养成,不然后面一条配置泄露可能牵出整个集群的访问权限。
5. 部署 SpringAI 智能审核服务
5.1 编写 Deployment 与 Service:探针和资源限制是重点
配置中心、Redis、镜像都已经就位,现在可以正式把 SpringAI 服务推到 K8s 里。我直接给一份我项目里在用的 Deployment 精简版,几乎没有多余字段,但该有的都有。
apiVersion: apps/v1 kind: Deployment metadata: name: springai-review namespace: prod-ai labels: app: springai-review spec: replicas: 3 selector: matchLabels: app: springai-review template: metadata: labels: app: springai-review spec: imagePullSecrets: - name: registry-secret containers: - name: app image: registry.example.com/springai-review:v1.0.0 ports: - containerPort: 8080 env: - name: AI_PROMPT_SYSTEM valueFrom: configMapKeyRef: name: ai-config key: system-prompt - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: redis-secret key: password - name: JAVA_OPTS value: "-Xms512m -Xmx1024m -XX:+UseG1GC" resources: requests: cpu: 500m memory: 512Mi limits: cpu: 2 memory: 2Gi readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 30 terminationGracePeriodSeconds: 60讲几个我踩过的关键点。
第一,resources 一定要写。不写 resources 的 Pod 在调度时会被当成 0 资源需求,多个 Pod 可能挤到一台机器上,直接导致整机内存超卖,OOM 之后 kubelet 开始无差别杀 Pod。requests 设 512Mi,limits 设 2Gi,是给 JVM 留余量。JAVA_OPTS 里 Xmx 写 1024m,就是防止 JVM 觉得宿主机内存很大然后无脑申请。
第二,探针分两种,readiness 告诉 K8s“这个 Pod 能不能接流量”,liveness 告诉 K8s“这个 Pod 还活着吗”。Spring 应用启动慢,类扫描加上 SpringAI 初始化模型客户端,三十秒起步很正常,所以 initialDelaySeconds 给大一点,避免启动阶段就被误杀。
第三,优雅停机。Spring Boot 要开优雅停机配置:
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s同时把 terminationGracePeriodSeconds 设成 60,这样 K8s 在滚动更新时,会给旧 Pod 一分钟时间处理完当前请求再退出,不会出现用户请求发到一半被断连。
Service 就简单了,内部服务直接 ClusterIP:
apiVersion: v1 kind: Service metadata: name: springai-review namespace: prod-ai spec: selector: app: springai-review ports: - port: 8080 targetPort: 8080如果要对外暴露,可以在上层加一层 Ingress,按域名把请求导到 springai-review Service。这个项目里因为审核服务是内部接口,我暂时没暴露公网,如果你想做成对外 API,再补一个 ingress-nginx 就行。
5.2 滚动更新与自动扩缩容:让服务自己长大
服务上了 K8s 之后,最爽的一点是发版变成了一个动作而不是一个工程。以前在虚拟机上线,先备份 jar,再停服务,再启动,出问题还要回滚。现在改镜像 tag 就行。
kubectl set image deployment/springai-review \ app=registry.example.com/springai-review:v1.0.1 \ -n prod-ai这行命令会触发一次滚动更新,K8s 会先起一个新 Pod,等 readiness 通过后再杀掉一个旧 Pod,以此类推,整个过程用户无感知。如果新版本启动失败,readiness 一直不通过,滚动更新会自动暂停,不会把流量全部切到坏版本上。
自动扩缩容这块,我给这个服务配了一个基于 CPU 的 HPA。SpringAI 服务在处理审核请求时,CPU 占用和内存通常同步上升,以 CPU 为目标比较直接。
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: springai-review-hpa namespace: prod-ai spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: springai-review minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60注意,HPA 要生效必须装 metrics-server,没有它,kubectl top pod 拿不到指标,HPA 会一直处于 Unknown 状态。安装非常简单:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml装完之后配好探针,HPA 才会“看到”Pod 的实际负载,流量一上来,Pod 数量会自动从 3 扩到 10。高峰过去,又会缓慢缩回 3。这个能力对 SpringAI 这种可能被突发请求打爆的服务,价值非常大。
6. 常见问题与排查实录
6.1 Pod 一直 CrashLoopBackOff,先别急着删
这是所有 K8s 新手遇到最多的报错。看到 CrashLoopBackOff 第一反应是重新部署,其实没用。正确做法是按顺序排查。
第一步 kubectl logs 看容器当前输出。SpringAI 应用最常见的原因是提示词配置没拿到,或者 Redis 密码错误。日志里会直接看到Could not resolve placeholder 'AI_PROMPT_SYSTEM'或者 Redis 连接异常。
第二步看 Events,kubectl describe pod 会显示 OOMKilled 或探针失败。如果是 OOMKilled,说明 limits.memory 设小了,把 JVM Xmx 调低或者 limits 调大。如果是 Liveness probe failed,说明健康检查路径不对,或者应用启动真的超过 60 秒,把 initialDelaySeconds 再调大。
我还遇到过一种情况,容器启动到一半就退出,日志里没有任何报错。最后发现是 Dockerfile 里的 ENTRYPOINT 写成了 exec 格式,变量没展开。这个比较坑,但把 ENTRYPOINT 换成我前面那种 sh -c 写法就能解决。
6.2 镜像拉取超时和私有仓库认证
ImagePullBackOff 是最容易解决的,但它背后有两个常见原因。
一个是镜像仓库地址不通或者镜像 tag 不存在。先 kubectl describe pod 看 Events 里的具体拉取错误,404 就是 tag 不存在,timeout 就检查网络。
另一个是私有仓库认证。从我的实践经验看,很多人是项目初期用 docker login 在本机登录了仓库,但 K8s 里的 Pod 并不会自动继承这个登录状态。你需要先创建一个 imagePullSecrets:
kubectl create secret docker-registry registry-secret \ --docker-server=registry.example.com \ --docker-username=deploy \ --docker-password=xxx \ -n prod-ai然后在 Deployment 的 spec 里加 imagePullSecrets。不加这个东西,私有镜像永远拉不下来。另外,如果你用的是 containerd 做运行时,某些节点上可能还要额外配置 containerd 的 registry 配置,否则 crictl 拉取时一样会认证失败。
6.3 Redis 集群连接超时:DNS 和 namespace 的恩恩怨怨
我在 4.2 节埋了个伏笔,这里展开讲。曾有一次我在 test-ai namespace 里部署了一个 SpringAI 服务,去连 prod-ai namespace 里的 Redis 集群,配置写得也没问题,但就是连不上,日志里报 UnknownHostException。
排查到最后发现,测试服务的 Deployment 里配置的 Redis 节点是短名:redis-cluster-0.redis-cluster-headless,这个短名只有在 prod-ai namespace 内部的 Pod 才能直接解析。跨 namespace 必须写完整 FQDN,也就是加上 .prod-ai.svc.cluster.local。
还有一个隐蔽问题,Spring Data Redis 的 cluster nodes 如果只写一个节点地址,即使这里配了集群模式,客户端也可能只连这一个节点。建议把 3 个 master 的地址都写进配置,让客户端自己去探测集群拓扑。
6.4 疑难杂症速查表
我把上面这些问题整理成一张表,方便大家现场照方抓药。
| 现象 | 常见原因 | 排查命令 | 解决动作 |
|---|---|---|---|
| CrashLoopBackOff | 启动参数/环境变量缺失 | kubectl logs | 检查 ConfigMap 引用和环境变量名 |
| CrashLoopBackOff | JVM 内存超限 | kubectl describe pod | 调大 limits 或调小 Xmx |
| ImagePullBackOff | tag 不存在/仓库认证失败 | kubectl describe pod | 修正 tag,添加 imagePullSecrets |
| 探针一直失败 | 路径写错/启动太慢 | kubectl describe pod | 改 actuator 路径,加大 initialDelaySeconds |
| Redis UnknownHostException | 跨 namespace 用短名 | kubectl exec -- nslookup 域名 | 改成完整 FQDN |
| Pod 调度不上去 | 资源 requests 超过节点剩余 | kubectl describe pod | 降低 requests 或加节点 |
| kubectl top 没数据 | metrics-server 没装 | kubectl get pods -n kube-system | apply metrics-server |
实用的小技巧,K8s 里排查问题不要只盯 kubectl logs,kubectl describe pod 里的 Events 往往比日志早一步暴露问题。两个命令配合使用,大部分问题五分钟内能定位。
7. 这一掌的收尾体会
写到这,第十八掌“神龙摆尾”的核心内容就全部落地了。从容器化开始,我们把 SpringAI 智能审核服务的镜像做小、做强,然后在 Rocky Linux 上搭起了 1.36 的 K8s 集群,规划好 namespace,把系统提示词配置从代码里解放出来,接上 Redis 集群,最后用 Deployment、探针、HPA 让整个服务具备生产级的高可用能力。
说点我自己的真实感受。这套流程我在不同项目里重复了很多次,每次最有价值的收获不是命令背得更熟,而是越来越清楚“为什么要这样设计”。比如给 Pod 写 resources,表面上是 K8s 的一个参数,实际上背后是对 SpringAI 服务内存画像的掌控。再比如把系统提示词放进 ConfigMap,表面上是部署方式的选择,实际上让运营和算法可以自助调优,不用再求着开发发版。
另外给准备复现的朋友一个建议:不要一上来就追求三 master 双 worker 的大集群。先在单 master 上把整条链路跑通,看明白每个组件之间的依赖关系,再考虑扩节点。过程中多记录自己踩过的坑,多积累 kubectl describe 和 logs 结合排查的习惯。K8s 这东西,门槛不在命令,在于你对整个系统运转逻辑的体感。神龙摆尾这一招练成之后,后面再上什么服务,都会顺很多。