news 2026/10/5 7:44:04

Spring Boot项目Kubernetes化:从镜像构建到生产级部署实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot项目Kubernetes化:从镜像构建到生产级部署实践

我接手过不少Spring Boot项目,早期基本都是打jar包扔到一台服务器上,用nohup java -jar xxx.jar &这种原始方式跑起来。单机部署确实省事,但服务一多、流量一大就开始难受:日志分散在各台机器上,扩容要手动加机器,发布一个新版本搞得全组人如临大敌。后来我把项目迁到Kubernetes(K8s)上,用声明式的方式统一管理部署,整个过程才真正变得有序可控。

这篇文章就把我从Docker镜像构建、Deployment编写到探针配置、自动扩缩容、监控排障的完整经验整理出来。适合三类人看:第一类是Spring Boot开发写了不少,但对容器编排一知半解,想搞明白部署到底怎么落地;第二类是团队刚引入K8s,正准备把老项目容器化的运维或架构同学;第三类是想学习云原生部署路径,把Deployment、Service、ConfigMap这些概念串起来的入门者。如果是老手,可以直接跳到第3章的实操部分,里面有我在生产环境里踩过的坑和调参经验。

1. 为什么要把Spring Boot项目搬上Kubernetes

1.1 传统部署和K8s部署的差别

传统方式部署Spring Boot项目,流程一般是:CI拉代码,Maven打包,把jar包传到服务器,杀掉旧进程,启动新进程,再用Nginx或SLB把流量切过去。这套流程在单机场景没问题,但放到多实例、微服务化的场景里,账就不太好算了。

  • 服务增多后,每台服务器的资源利用率很难均衡,有的机器CPU飘红,有的机器内存长期闲置。
  • 水平扩容需要手动加机器、改负载均衡配置、再部署服务,整个过程全靠人,出错概率高。
  • 机器宕机后恢复流程依赖运维个人经验,没有统一的自愈机制。
  • 服务之间的调用地址写IP,IP一变全都得跟着改。

K8s把这些问题抽象成了几个核心对象:Pod是最小的调度单元,Deployment负责声明期望的实例数和滚动更新策略,Service提供稳定的访问入口,ConfigMap和Secret做配置与敏感信息分离。Spring Boot应用在这些对象的配合下,从一个需要被细心照看的进程,变成一个可以被调度、被观测、被自动恢复的云原生服务。

1.2 哪些Spring Boot项目适合上K8s

我的建议是别盲目跟风,不是所有项目都非要上K8s。我判断一个项目该不该容器化,主要看几个条件。

第一,服务需要多副本或者需要弹性伸缩。如果永远是单实例,一台虚拟机跑jar包反而更省事,引入K8s是给自己找麻烦。第二,团队有容器化基础,至少Docker用得熟练,因为K8s虽然是编排层,但底层所有应用都得跑在容器里,镜像构建不熟练,后面全卡在镜像环节。第三,有配套的日志、监控、CI/CD诉求,K8s的滚动发布和回滚能力很强,但没有自动化流水线,手工敲命令发布,效率照样上不去。

Spring Boot在这个场景里有天然优势:它打包出来的可执行jar可以直接用java命令启动,Actuator开箱即用地提供健康检查端点,Spring Boot 2.3之后还针对容器环境做了内存感知优化。所以Spring Boot加K8s的组合,几乎是我能想到的最顺滑的云原生落地路径。

2. 镜像构建:准备阶段的核心动作

在K8s上部署Spring Boot,第一步是把应用打包成镜像。这一步看起来简单,但实际优化空间相当大,里面有不少细节决定你后面部署是顺风顺水还是处处踩坑。

2.1 用多阶段构建减小镜像体积

我见过不少团队直接把jar包扔进openjdk:8-jdk-alpine里跑,一个镜像动辄四五百MB,推到私有仓库慢,拉取也慢。其实Spring Boot项目完全可以做到100MB左右。

推荐的方式是多阶段构建。第一阶段用Maven镜像编译,第二阶段只复制jar包到运行时镜像。我常用的Dockerfile大概长这样:

FROM maven:3.8-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"]

这里有几个细节值得说明。第一,dependency:go-offline先把依赖缓存到镜像层,只要pom文件不变,后续构建会快很多。第二,运行时镜像用JRE而不是JDK,体积会小很多。第三,别在Dockerfile里直接写死JVM参数,尽量通过环境变量传入,这是为后面在K8s的Deployment里统一管理资源做准备。

另外,项目里一定要加.dockerignore文件,把.git、target、.idea这些目录排除掉,否则COPY的时候会把一堆没用的文件带进构建上下文,拖慢构建速度。如果你用Spring Boot 3以上版本,JDK版本至少要17,基础镜像可以考虑eclipse-temurin或bellsoft,这两个在安全更新和维护上相对靠谱。

2.2 镜像仓库与版本管理

镜像构建出来必须推到仓库,K8s节点才能拉取。私有仓库我推荐用Harbor或阿里云ACR,功能上都够用,区别不大。但有几个习惯建议养成。

镜像tag坚决不要用latest,生产环境一定要带上版本号,比如1.2.3或git-abc1234。latest有个很坑的地方:你拉下来的时候根本不知道是哪次构建的产物,线上出了问题想回滚,发现tag对不上之前版本,直接抓瞎。镜像仓库建议开启漏洞扫描,Spring Boot依赖多,第三方库容易出CVE,扫描能在上线前发现明显问题。

还有一个真实踩坑记录:有段时间我们镜像push到Harbor后,K8s节点一直拉取失败,后来排查了半天,发现是节点上的容器运行时没配置Harbor的insecure-registry,HTTP请求被拒。这类问题不在代码层面,但会让你部署进度直接卡死。提前配好仓库的访问能力和认证信息,比什么都重要。

如果节点需要从私有仓库拉取镜像,记得在K8s里创建imagePullSecret并配置到Deployment中,否则ImagePullBackOff会一直缠着你。

2.3 配置外部化:让环境变量接管

Spring Boot本身支持多套配置源的优先级覆盖:命令行参数大于Java系统属性,Java系统属性大于环境变量,环境变量大于application.yml。在K8s里,环境变量的注入非常方便,所以我强烈建议把数据库、Redis、MQ等连接信息全部从配置文件中抽离,通过ConfigMap或Secret注入。

举个例子,原来的application.yml里可能直接写着:

spring: datasource: url: jdbc:mysql://localhost:3306/demo username: root password: 123456

迁到K8s后应该改成:

spring: datasource: url: ${DB_URL} username: ${DB_USERNAME} password: ${DB_PASSWORD}

然后在Deployment里引用ConfigMap管理普通配置、Secret管理敏感信息。连接信息这种敏感数据建议放在Secret而不是ConfigMap,虽然K8s自带Secret的加密能力相对有限,但至少做到了配置与代码分离,权限控制也好做。

3. 核心部署实战:从YAML到上线

前两章是把地基打牢,这一步是重头戏。我会用一个典型的订单服务为例,从命名空间、Deployment、Service、配置注入到探针和自动伸缩,全部串起来讲一遍。

3.1 命名空间与资源隔离

很多团队一上来就把所有资源全塞进default命名空间,短期用着方便,但项目一多就乱了。我建议至少按环境分:dev、staging、prod。多业务线的还可以再按业务分。

命名空间做的是逻辑隔离,不是安全隔离。它的主要价值有三个:资源配额管理、权限控制、对象组织。可以给每个命名空间设置ResourceQuota,防止某个环境把整个集群资源吃光:

apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: "8" requests.memory: 16Gi limits.cpu: "16" limits.memory: 32Gi

多团队共用集群的时候,这个配置能少吵很多架。没有配额限制的环境,一旦有人把副本数调到几十个,整个集群都可能被拖垮。

每个Deployment我都会在metadata里加namespace字段,防止不小心部署错环境。这一步看似不起眼,但能避免不少生产事故。

3.2 Deployment:用声明式描述期望状态

Deployment是K8s里和Spring Boot关系最紧密的对象,它声明了期望的副本数、Pod模板和更新策略。下面是一个相对完整的参考:

apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: prod labels: app: order-service spec: replicas: 3 revisionHistoryLimit: 10 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: order-service template: metadata: labels: app: order-service spec: containers: - name: order-service image: harbor.example.com/ecommerce/order-service:1.2.3 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 name: http envFrom: - configMapRef: name: order-service-config - secretRef: name: order-service-secret resources: requests: cpu: 500m memory: 512Mi limits: cpu: "1" memory: 1Gi livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 2 failureThreshold: 3

几个关键参数说说怎么定。

replicas副本数不是拍脑袋定的,取决于QPS、单实例处理能力和期望的冗余。假设单实例能扛200QPS,业务峰值在1000QPS左右,那5个副本是起步,再留20%左右的buffer,所以5到6个比较合理。

resources里的requests是调度依据,limits是运行约束。我习惯把requests设成业务实际常常占用的量,把limits设成requests的1.5到2倍。比如一个Spring Boot服务刚启动占300MB内存,正常运行后500MB,requests给512Mi,limits给1Gi。CPU单位m是毫核,500m就是0.5核。limits不要设太高,否则某个Pod异常吃CPU时可能把整个节点拖垮。

探针配置是整个Deployment里最需要调的地方。readiness负责流量接入,liveness负责容器存活。initialDelaySeconds要根据Spring Boot实际启动时间调整,我这个例子假设启动10到20秒。如果你的服务启动要40秒,探针就得等一等,否则刚起来还没监听端口就被探针打死,形成CrashLoopBackOff。

3.3 Service与Ingress:让流量稳定地进来

Pod的IP在K8s里是随时变化的,不能直接依赖。Service提供了一组Pod的稳定访问入口,通过标签选择器关联Pod,并把流量负载均衡到每个Pod。

Service类型有四种,按使用场景区分:

Service类型适用场景说明
ClusterIP内部服务调用只在集群内可达,服务间调用首选
NodePort测试环境把端口暴露在每个节点上,外部可用节点IP访问
LoadBalancer公有云云平台自动创建负载均衡器,把公网流量接到Service
ExternalName服务映射给外部域名做别名,偶尔有用

Spring Boot微服务之间的调用,一般直接用ClusterIP。比如订单服务要调用户服务,只需要通过http://user-service:8080这个地址访问,K8s会自动做负载均衡,应用层面不需要感知对端Pod的具体IP。

下面是订单服务的Service定义:

apiVersion: v1 kind: Service metadata: name: order-service namespace: prod spec: type: ClusterIP selector: app: order-service ports: - name: http port: 8080 targetPort: http

port是Service暴露的端口,targetPort指向Pod里的容器端口。把端口命名成http,方便后面Ingress配置时引用,可读性也好。

如果要把服务暴露到公网,我会在前面加一层Ingress域名路由。下面的配置把api.example.com/order路径转发到order-service,/user路径转发到user-service:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api-ingress namespace: prod spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: /order pathType: Prefix backend: service: name: order-service port: number: 8080 - path: /user pathType: Prefix backend: service: name: user-service port: number: 8080

Ingress Controller我用得最多的是Nginx Ingress和Traefik,Nginx稳定性更强,Traefik配置更简洁。具体选型看团队熟悉度,功能上差别不大。

3.4 ConfigMap与Secret:配置解耦的完整实践

我在第2章提到要把配置外部化,这里展示具体怎么做。

ConfigMap管理非敏感配置:

apiVersion: v1 kind: ConfigMap metadata: name: order-service-config namespace: prod data: SPRING_PROFILES_ACTIVE: "prod" SERVER_PORT: "8080" LOG_LEVEL: "INFO" REDIS_HOST: "redis-master.default.svc.cluster.local" REDIS_PORT: "6379"

Secret管理敏感配置:

apiVersion: v1 kind: Secret metadata: name: order-service-secret namespace: prod type: Opaque stringData: DB_URL: jdbc:mysql://mysql-db:3306/order?useSSL=false&serverTimezone=UTC DB_USERNAME: order_app DB_PASSWORD: ChangeMe123

在Deployment的envFrom里引用,Pod启动后环境变量就会被注入成这些配置值。注意Secret用stringData写的是明文,在YAML文件里就能直接看到。如果对安全性要求比较高,建议用外部Secret存储方案或者把Secret做加密,这个按团队安全制度来。

还有一个很实际的坑:ConfigMap和Secret如果改了,线上Pod不会自动更新环境变量。想让配置生效,要么重启Deployment让Pod重建重新注入,要么用Reloader这类工具监听变化自动滚动重启。我个人习惯是:配置变更也算发布动作,直接改完ConfigMap后重启Deployment,保持发布流程的一致性。

3.5 水平自动扩缩容(HPA)

Spring Boot应用上K8s后的一个重要红利就是弹性。HPA可以根据CPU、内存等指标自动调整副本数。

我的订单服务早年配置过这样的HPA:

apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-service-hpa namespace: prod spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: order-service minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60

解释一下平均利用率的意思。averageUtilization: 60表示所有Pod的CPU平均使用量达到requests值的60%时,HPA开始扩容。例如requests是500m,当所有副本平均实际使用到300m,也就是60%的时候,就会增加副本。反过来,持续一段时间低于设定值会缩容。

HPA每5秒采集一次指标,但缩容有默认冷却时间,5分钟内不会立刻缩下去,这个机制是为了防止指标抖动导致频繁扩缩。如果你的业务流量有明显的潮汐特征,还可以用KEDA做定时伸缩或基于消息队列长度的伸缩,这个看具体业务形态再决定。

4. 可观测性:健康检查、监控与日志

部署只是开始,真正的运维难点在观测。应用写得再稳,线上出了问题也必须快速定位,没有可观测性的K8s集群光靠kubectl logs是很低效的。

4.1 把Actuator探针和K8s探针联动起来

Spring Boot 2.3之后,Actuator增加了liveness和readiness两个独立端点,分别对应K8s的存活探针和就绪探针。这是官方面向云原生场景独立设计的,比直接用原始的/actuator/health更合理。

为什么这样说?因为原始的health会把数据库、Redis等组件状态全部汇总。如果数据库短暂抖动,health返回DOWN,K8s会误以为Pod挂了,把它重启,但重启并不能恢复数据库,反而可能造成雪崩。liveness和readiness的拆分思路是:

  • readiness探针检查应用是否具备接收流量的能力,可以包含下游依赖状态。下游未就绪,就先别把流量打进来。
  • liveness探针只检查JVM进程本身是否存活。进程还活着就不轻易重启,避免无意义的反复拉起。

实践配置里,application.yml开启探针端点的写法是:

management: endpoints: web: exposure: include: health,info,prometheus,metrics health: probes: enabled: true endpoint: health: show-details: always

这样配置后,/actuator/health/liveness和/actuator/health/readiness就能正常访问了。再配合Deployment里的readinessProbe和livenessProbe配置,流量接入和进程存活就被区分开了。

4.2 接Prometheus监控Spring Boot指标

Spring Boot的指标监控我一般配合Micrometer使用,加上micrometer-registry-prometheus依赖后,Spring Boot会自动暴露Prometheus格式的/actuator/prometheus端点。Prometheus通过服务发现拉取指标,Grafana做可视化展示。

这个链路能拿到的指标非常全:JVM堆内存、GC次数、线程池状态、Tomcat活跃线程数、HTTP请求耗时,全部都是标准化格式。指标有了之后,告警规则也要配上,比如堆内存超过85%,持续5分钟就告警:

groups: - name: springboot-alerts rules: - alert: HighHeapMemoryUsage expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85 for: 5m labels: severity: warning

如果你不想自己搭一套Prometheus,先用K8s自带的metrics-server看CPU和内存也可以,但JVM层面的指标看不到,排查Java服务问题很容易抓瞎。做Java服务的生产环境,Prometheus加Grafana基本是标配。

4.3 日志收集:JSON化与采集方案

K8s里的Pod是随时变化的,登录Pod用kubectl logs只能做临时排查。要持久化日志,我推荐两条路线:轻量方案是Pod日志以stdout输出,通过Fluent Bit收集到Loki;标准方案是如果公司已有ELK,直接把日志打到Kafka或Logstash,再入ES。

对Spring Boot应用,日志格式建议统一为JSON,这一点很容易被忽略。非结构化日志只能全文搜索,而JSON格式可以根据level、traceId、serviceName直接过滤,排障效率完全是两个级别。数一下你每次排障时在日志里翻了多少页,就知道结构化日志有多重要。

logback配置里加一个JSON编码器,用logstash-logback-encoder这个库:

<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"/> </appender>

日志以JSON格式打到stdout后,K8s采集时不需要额外解析,后续接入任何日志平台都能直接处理。

5. 常见问题与排查实战记录

这一章我整理了迁移过程中真实遇到过的几个问题,每个都附排查思路,而不是只给一个现象查询表。

5.1 Pod一直CrashLoopBackOff

现象:kubectl get pods看到Pod反复重启。

排查分三步。第一步,kubectl logs <pod-name>看应用日志。Spring Boot应用启动失败,日志里通常会抛出异常堆栈,最常见的是端口被占用、数据库连接不上、配置缺失。

第二步,kubectl describe pod <pod-name>看Events。有时日志是正常的,但容器被K8s主动杀掉,Events里会写原因,比如Liveness probe failed,或者OOMKilled。

第三步,如果是探针失败,调整探针参数。启动慢就加大initialDelaySeconds,探针太频繁就调大periodSeconds。Spring Boot启动阶段有个容易忽略的细节:它启动时会在控制台打印Banner,如果日志刷屏,老版本应用在10秒内还没监听端口,readiness探针就开始打这个端口,一打失败就重启。所以启动慢的应用一定要给足够的启动等待时间。

5.2 ImagePullBackOff镜像拉不下来

现象:Pod状态显示ImagePullBackOff,Events里是Failed to pull image。

原因基本就这么几类:镜像tag对不上、私有仓库未配置认证、节点到仓库网络不通、insecure-registry配置缺失。

我的定位方法是:先在节点上手动执行一次docker pull。如果节点上能拉下来,说明K8s侧的imagePullSecret或权限配置有问题;如果节点上也拉不下来,那就是仓库或网络的问题。这个二分法定位非常高效。

另外提一句,如果你push镜像用了latest标签,偶尔会出现拉下来的镜像还是旧缓存。加个imagePullPolicy: Always可以强制每次拉取。当然最好还是回到我在第2.2节说过的建议:从一开始就使用带明确版本号的tag。

5.3 滚动更新卡住或流量中断

现象:更新Deployment镜像后,新版本一直起不来,旧版本也退不掉,或者更新期间请求出现502。

核心原因通常是readiness探针配置不当。滚动更新的逻辑是:新Pod完全就绪,才开始终止旧Pod。如果新Pod永远不就绪,比如探针路径配错、应用启动失败,滚动更新就会一直卡着。

另一个坑是maxUnavailable为0、maxSurge为1的组合:它保证没有Pod同时下线,但要求集群先能腾出资源启动新Pod。如果集群资源不足,新Pod会一直Pending,更新同样被卡住。资源紧张的集群,要么提前预留资源,要么调整maxSurge和maxUnavailable的数值。

流量中断还有一个隐蔽原因:Spring Boot的优雅停机没配置。K8s终止Pod时先发SIGTERM,如果你的应用直接硬退出,存量请求就会断掉。正确做法是在Spring Boot 2.3以上配置server.shutdown: graceful,同时给Pod设置一个合理terminationGracePeriodSeconds,比如30秒,让应用处理完当前请求再退出。

对应的回滚命令是kubectl rollout undo deployment/order-service,操作要快,先把服务恢复,再慢慢查原因。

5.4 数据库连接池和副本数不匹配

这是一个非常容易踩到生产环境大坑的问题。Spring Boot默认用HikariCP,默认最大连接池大小是10。如果服务有10个副本,全都连同一个数据库,数据库要承受100个连接。副本扩到20,连接池瞬间变成200,数据库连接数被打满,其他服务也会跟着受牵连。

到K8s环境后,建议把数据库连接池上限和副本数一起评估。比如HPA最大10个副本,单副本连接池限制10,那数据库连接上限至少要大于100,还要再留一些给其他用途。反过来想也可以:连接池最大连接数等于数据库可用连接数除以预期副本数,再预留一部分冗余。

这种问题在单机部署时根本不会暴露,一旦容器化多副本化就立刻显现,属于上K8s后最容易忽视的连带问题。

5.5 时区和内存参数导致的奇怪问题

说两个看起来小、实际影响很大的问题。

第一个是时区。很多基础镜像默认是UTC,业务日志常用的是北京时间。不统一时区的话,日志时间和数据库时间可能差8小时,排障时时间线对不上非常难受。解决方式是在Deployment里加环境变量TZ=Asia/Shanghai,或在Dockerfile里安装tzdata并设置时区,二选一都行。

第二个是JVM内存感知问题。老版本JVM对容器内存限制的感知有限,可能出现JVM在limits之外申请内存,然后被K8s莫名杀掉。如果用的是Java 8,至少用8u191之后的版本,配合-XX:+UseContainerSupport。Java 17默认已经支持容器感知,但为了保险,我还是会在启动参数里显式设置-Xmx,取值不要超过limits的70%,给元空间和堆外内存留余地。

6. 一些提升部署效率的经验技巧

最后分享几个我日常使用很顺手的习惯,不一定每个团队都适用,但实践下来确实能省不少事。

6.1 用Helm统一管理部署模板

Deployment、Service、ConfigMap、Secret这些YAML如果每个服务都写一份,维护成本会很高。Helm把公共模板抽象出来,用values文件区分环境。我现在每个Spring Boot服务一个Chart,不同环境传不同values,部署就是一条命令:

helm upgrade --install order-service ./charts/order-service -f values-prod.yaml

这套做法的好处是:新人上手成本低,环境差异完全靠values隔离,回滚用helm rollback一条命令完成。如果嫌Helm太重,Kustomize也是不错的轻量替代,但Helm在模板能力和社区生态上更成熟。

6.2 把部署嵌入CI/CD流水线

K8s部署只解决了最终怎么跑起来,代码提交、构建镜像、推仓库、更新Deployment,还是要靠流水线串起来。我现在的流程是:代码push主分支,GitLab CI触发构建,Maven编译打包,构建镜像推到Harbor,最后更新K8s Deployment,整个流程10分钟左右。

流水线里一定要加一个生产环境的人工确认环节。见过有人把自动发布直接怼到生产,结果一个配置没改对,一堆Pod循环重启。生产发布前让人review一遍配置,能拦截大部分低级错误。

6.3 本地开发贴近K8s环境的方案

有团队问过我,本地开发怎么模拟K8s环境。两个方向可以试。

第一个是minikube或k3s,适合在单机跑个完整集群做体验。minikube对资源要求高,内存至少8G起步,k3s轻量很多,适合笔记本。这类环境适合验证,性能和真实集群差距还是明显的。

第二个是Telepresence或kt-connect这类工具,让本地进程直接接入集群网络。你可以在本地跑一个Spring Boot进程,让它直接调测试集群里的其他微服务。这个方案对微服务联调体验提升很大,特别是你只想改自己负责的那个服务,不需要把整套环境拉到本地的时候。

我个人的开发环境就是这样:本地起了几个Spring Boot微服务,通过Telepresence连到测试集群,随时可以热调试,不用反复打包部署,体验比纯本地跑整套环境舒服太多。

到这里,整套部署链路基本讲完了。最后说一个我自己的体会:K8s的文档、视频、资料特别多,但真正有价值的东西,往往是在生产环境里踩过坑之后总结出来的判断。上面写的探针参数、资源配比、优雅停机、连接池评估这些,都不是抄来的模板,而是从一个个线上问题里排出来的做法。如果你照着这套流程去部署,再结合自己业务的实际情况调整参数,大概率能比那些只跑通demo就开始生产的人少走很多弯路。K8s不是银弹,但对于Spring Boot这类Java应用的部署和运维,它确实是一套值得长期投入的方向。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 7:42:36

MySQL运维必备5款开源工具:慢查询、监控、高可用与自动化实战指南

做MySQL运维的人&#xff0c;谁没熬过几个大夜&#xff1f;半夜被监控短信吵醒&#xff0c;爬起来一看&#xff1a;主库磁盘满了、从库延迟飙到几千秒、慢查询把连接池打满&#xff0c;这种事我经历过太多次。后来我把日常运维里依赖的工具沉淀成一套固定组合&#xff0c;就是标…

作者头像 李华
网站建设 2026/10/5 7:42:25

微网储能优化实战:从MPC建模到工程落地的完整复盘

1. 微网能量管理到底难在哪&#xff1a;我接手储能优化项目时的第一课先说结论&#xff1a;微网能量管理这个事儿&#xff0c;表面上看就是一套"什么时候充电、什么时候放电"的逻辑&#xff0c;但真正上手做了之后才发现&#xff0c;这里面藏着的坑比想象中多得多。我…

作者头像 李华
网站建设 2026/10/5 7:42:11

C++20 Concepts 教程:用约束终结模板编程黑魔法

1. 为什么模板程序员需要 Concepts1.1 模板编程的“黑暗时代”说起来&#xff0c;做 C 模板编程的人&#xff0c;多半都经历过那种一编译报错&#xff0c;整屏刷过去几百行、里面全是std::enable_if、decltype、void_t这些“黑魔法”套娃的日子。我到现在都记得第一次看到某个模…

作者头像 李华
网站建设 2026/10/5 7:42:04

MySQL索引底层原理与慢查询优化实战:B+树、联合索引与失效场景全解析

写过太多慢查询优化&#xff0c;见过太多因为索引没建对导致全表扫描把数据库拖垮的案例。MySQL索引这个东西&#xff0c;说简单就一个B树&#xff0c;说复杂能牵扯出回表、覆盖索引、最左前缀、索引下推一堆概念。但实际开发中真正需要掌握的&#xff0c;无非就是搞清楚索引底…

作者头像 李华
网站建设 2026/10/5 7:41:43

sqfentity_gen鸿蒙适配实战:驱动替换与30表迁移全记录

做 Flutter 开发的老哥应该都听过 sqfentity 和它配套的代码生成器 sqfentity_gen。这玩意儿的定位很直白&#xff1a;把数据库表结构定义成 Dart 注解&#xff0c;然后跑一遍 build_runner&#xff0c;实体类、DAO、数据库初始化代码全给你生成好&#xff0c;省掉手写 SQL 和映…

作者头像 李华
网站建设 2026/10/5 7:41:43

用项目管理工具DooTask搭建学习驾驶舱:新学期多项目并行管理全攻略

1. 新学期的手忙脚乱&#xff0c;问题不在不够努力而在没有结构开学还没到两周&#xff0c;我身边已经有不少人进入"看起来每天都很忙&#xff0c;坐下来想想又不知道今天到底该推进哪件事"的状态了。课表、小组作业、考研单词、社团例会、招聘宣讲全叠在一起&#x…

作者头像 李华