技术探索与实践踩坑记录:一个试用期萌新的 Spring Boot + K8s 云原生历险记
大家好,我是小张,刚入职上海这家做 SaaS 平台的创业公司不到两个月,目前还在试用期,每天战战兢兢如履薄冰。坐标浦东张江,租了个老小区的一居室,走路十分钟到公司——主要是为了省打车钱,毕竟试用期工资还没转正呢 😅。
我之前在上一家公司主要搞后端开发,技术栈偏传统,Spring Boot + MySQL + Redis 那一套玩得挺熟。但新公司一上来就给我整了个“惊喜”:微服务架构、Kubernetes 编排、CI/CD 全自动化、服务网格……好家伙,直接给我干懵了。虽然简历里写了“熟悉云原生”,但实际上也就是本地 minikube 跑过几个 demo,真正上生产?不存在的。
上周五晚上,团队临时接到需求:要在双11前上线一个实时库存预警模块,要求高可用、低延迟、能快速扩缩容。Leader 直接点名:“小张,你不是说对 K8s 熟吗?这模块你来搭,Spring Boot 写服务,部署上 K8s,下周一演示。”
我当时内心 OS:我熟的是 Hello World on K8s 啊!
但没办法,试用期嘛,领导让你上,你就得冲。于是,一场充满 Bug、报错、重启和深夜泡面的技术探索之旅,正式开启。
从“Hello World”到“Why World”
项目需求其实不复杂:用户下单时,调用库存服务检查商品余量,如果低于阈值(比如 10 件),就发一条消息到 Kafka,触发预警通知。典型的 CRUD + 异步解耦场景。
我第一反应:这还不简单?Spring Boot + JPA + Kafka Producer,本地跑通,打包成 JAR,扔进 Dockerfile,推到镜像仓库,写个 Deployment 和 Service,apply 到 K8s,搞定!
结果第一天就翻车了。
坑一:本地能跑,K8s 上连数据库都连不上?
我把服务打包成镜像,部署到测试集群,Pod 一启动就 CrashLoopBackOff。查日志:
Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException:
Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago.
The driver has not received any packets from the server.
??? 我本地连得好好的啊!MySQL 在另一个 Pod,Service 名叫 mysql-service,我在 application.yml 里写的也是这个地址。
后来问了运维大哥(他看我的眼神充满了怜悯),才知道:K8s 里 Pod 之间通信默认走 ClusterIP,但如果你没配 NetworkPolicy,或者 DNS 解析有问题,就会连不上。
更关键的是,我本地测试用的是 localhost:3306,而 K8s 里要用 mysql-service.default.svc.cluster.local:3306。虽然理论上 mysql-service 就够了,但有些网络插件(比如我们用的 Calico)对短域名解析有点脾气。
解决方案:
- 确保 MySQL Pod 正常运行,Service 类型为
ClusterIP - 在 Spring Boot 的
application-prod.yml中明确写全限定域名:spring: datasource: url: jdbc:mysql://mysql-service.default.svc.cluster.local:3306/inventory_db?useSSL=false&serverTimezone=UTC - 加个 initContainer 测试连通性(后面会讲)
💡 实战经验:别信“本地能跑线上就能跑”这种鬼话。K8s 是另一个世界,网络、存储、权限全是新规则。
工具链拉满:从手搓 YAML 到 Helm + ArgoCD
一开始我傻乎乎地手写 Deployment、Service、ConfigMap、Secret,每次改个配置就要 kubectl apply -f 一堆文件。有次改错了一个缩进,Pod 起不来,排查半小时。
直到隔壁工位的老王(入职三年的老油条)看不下去了:“兄弟,现在谁还手写 YAML 啊?用 Helm 啊!再配个 ArgoCD,GitOps 搞起来,改代码自动部署,多香。”
我:GitOps?那是什么?能吃吗?
被科普后才知道,工具链现代化是云原生的基本修养。于是我花了一天时间重构部署流程:
- 用 Helm Chart 管理应用模板
- ConfigMap 存放非敏感配置(比如 Kafka 地址)
- Secret 存放数据库密码(Base6 wrap 过)
- ArgoCD 监听 Git 仓库的
manifests/目录,自动同步到集群
Helm values.yaml 示例:
replicaCount: 2
image:
repository: registry.company.com/inventory-service
tag: "v1.2.0"
pullPolicy: IfNotPresent
env:
SPRING_PROFILES_ACTIVE: prod
KAFKA_BOOTSTRAP_SERVERS: kafka-service:9092
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "200m"
memory: "256Mi"
Deployment 模板里引用:
env:
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.env.SPRING_PROFILES_ACTIVE }}
- name: KAFKA_BOOTSTRAP_SERVERS
value: {{ .Values.env.KAFKA_BOOTSTRAP_SERVERS }}
效果立竿见影:改完代码 → 提交 → CI 自动 build 镜像 → 推送 → ArgoCD 检测到 manifest 更新 → 自动 rollout 新版本。再也不用手动 kubectl 了!
🤯 感悟:工具不是炫技,而是解放生产力。试用期程序员的时间很宝贵,别把青春浪费在重复劳动上。
健康检查:Liveness vs Readiness,别再搞混了!
服务终于能起来了,但 QA 同学反馈:“你们服务有时候请求超时,但 Pod 明明是 Running 状态!”
我一脸懵,查 K8s 事件,发现 Pod 被频繁重启。再看日志,发现应用启动要 30 秒(因为要初始化缓存、连接池等),但我的 Liveness Probe 设置得太激进:
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
问题来了:应用还没完全启动,健康检查就失败,K8s 认为 Pod 不健康,直接 kill 重建。恶性循环!
后来查文档才明白:
- Readiness Probe:决定 Pod 是否准备好接收流量。没 ready 就不会加入 Service 的 endpoints。
- Liveness Probe:决定 Pod 是否还活着。失败就重启。
正确姿势应该是:
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20 # 给足启动时间
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60 # 等应用稳定后再检查
periodSeconds: 30
同时,在 Spring Boot 2.3+ 中,需要开启 Probes 支持:
management:
endpoint:
health:
probes:
enabled: true
endpoints:
web:
exposure:
include: health,info
这样,/actuator/health/readiness 会检查外部依赖(DB、Kafka),而 /actuator/health/liveness 只检查 JVM 是否存活。
✅ 最佳实践:永远不要用同一个 endpoint 做 liveness 和 readiness。否则,外部依赖抖动(比如 DB 瞬间不可用)会导致整个 Pod 被误杀。
日志、监控、告警:别等线上炸了才想起它们
上线前一天,Leader 问我:“日志怎么查?CPU 飙高怎么定位?有没有告警?”
我:……(冷汗)
赶紧补课。我们公司用的是 EFK(Elasticsearch + Fluentd + Kibana)栈,但我的服务日志格式还是默认的 console 格式,没法结构化查询。
改造日志输出:
- 引入
logstash-logback-encoder - 输出 JSON 格式日志
<!-- pom.xml -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<!-- logback-spring.xml -->
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<mdc/> <!-- 支持 traceId 注入 -->
<stackTrace/>
</providers>
</encoder>
</appender>
这样,每条日志都会带 traceId、level、logger 等字段,Kibana 里可以直接过滤、聚合。
监控方面,Spring Boot Actuator + Micrometer + Prometheus 几乎是标配:
management:
metrics:
tags:
application: inventory-service
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
然后在 K8s Service 上加 annotation,让 Prometheus 自动发现:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
最后配个 Grafana 面板,CPU、内存、HTTP 请求数、JVM GC 时间……一目了然。
🔔 血泪教训:可观测性不是可选项,是保命符。试用期程序员最怕线上事故,有了日志+监控,至少能快速定位,而不是背锅。
性能调优:从 OOM 到丝滑运行
服务上线后,压力测试一来,Pod 内存飙升,直接 OOMKilled。
查 heap dump 发现:Kafka Producer 没关闭,连接池泄漏;另外,Spring Boot 默认的 Tomcat 线程池太大(200),并发一高就吃光内存。
优化措施:
合理设置 JVM 参数(通过环境变量注入):
env: - name: JAVA_OPTS value: "-Xmx384m -Xms256m -XX:+UseG1GC"限制 Tomcat 线程数:
server: tomcat: max-threads: 50 min-spare-threads: 10Kafka Producer 复用(别每次 new):
@Bean public KafkaTemplate<String, String> kafkaTemplate() { return new KafkaTemplate<>(producerFactory()); }加资源 limit/request(前面 Helm 已配置)
优化前后对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 平均内存 | 600Mi | 320Mi |
| P99 延迟 | 1200ms | 180ms |
| Pod 稳定性 | 频繁 OOM | 7 天无重启 |
总结:试用期程序员的云原生生存指南
折腾了两周,这个库存预警服务终于稳稳跑在 K8s 上,双11 期间扛住了流量高峰,Leader 还夸我“上手快”。虽然我知道他只是客套,但心里还是美滋滋的。
回顾这段经历,踩过的坑、熬过的夜、查过的文档,都变成了实战经验。分享几点心得:
- 别怕问:运维、测试、老员工都是宝藏。一句“这个我不太熟,能请教下吗?”比自己死磕三小时强。
- 工具提效:Helm、ArgoCD、Prometheus 这些不是摆设,用好了能让你从“救火队员”变成“架构师”。
- 健康检查要分清:Liveness 和 Readiness 的区别,值得每个 K8s 开发者刻在 DNA 里。
- 可观测性先行:日志、监控、告警,上线前必须配齐。别等事故发生了才后悔。
- 资源要克制:Java 应用在容器里别乱设 Xmx,留点余量给 OS 和其他进程。
最后,作为试用期员工,我深刻体会到:技术深度重要,但工程化思维更重要。能写出优雅代码是本事,能让服务稳定跑在生产环境,才是真本事。
这篇文章写完,我也该去改下一个需求了——听说要上 Service Mesh?Istio?救命……
(完)
P.S. 如果你也正在试用期,别慌。每个大佬都曾是菜鸟,只要肯学、肯问、肯踩坑,终会发光。共勉!

评论 0