K3S实战:SpringBoot+Vue前后端分离项目容器化部署指南 📅 发布时间:2026/8/26 10:53:10 👁 浏览次数: 1. 从单体到容器化为什么选择K3S来部署前后端服务最近在折腾一个SpringBootVue的前后端分离项目从本地开发到最终上线部署环节总是绕不开的一环。相信很多朋友都经历过本地跑得好好的一上服务器就各种环境问题Node版本不对、JDK版本冲突、端口被占、依赖缺失…… 传统的部署方式比如直接在物理机或虚拟机上安装Java、Nginx、MySQL虽然直接但环境隔离性差迁移和扩展都相当麻烦。后来大家普遍转向了Docker用容器把应用和它的运行环境打包在一起确实解决了“在我这能跑”的问题。但当你需要管理多个容器处理它们之间的网络、存储、调度时单靠Docker Compose就显得力不从心了这时候就需要一个容器编排平台。KubernetesK8s无疑是这个领域的王者功能强大但同时也以“重”和“复杂”著称。对于中小型项目、个人开发者或者边缘计算场景全套K8s的学习和维护成本有点过高。这正是轻量级Kubernetes发行版K3S的用武之地。K3S由Rancher Labs现为SUSE开发它保留了K8s的核心API和功能但通过移除旧的、非必须的代码和外部依赖将二进制文件大小控制在100MB左右内存占用极低。它默认使用containerd作为容器运行时内置了SQLite作为默认的存储后端也支持etcd并且将所有的K8s组件打包进了一个单一的二进制文件中安装和启动变得异常简单。对于部署我们这种典型的SpringBoot后端Vue前端的前后端分离应用K3S提供了一个近乎完美的平衡点它具备了服务发现、负载均衡、配置管理、滚动更新等生产级能力同时又足够轻巧可以在从云端虚拟机到树莓派的任何地方快速拉起一个集群。所以这次我们就来实战一下如何将一个标准的SpringBoot Vue前后端分离项目完整地部署到K3S集群中。整个过程会涵盖从应用容器化Docker镜像制作到K8s资源定义Deployment, Service, Ingress再到最终通过域名访问的完整链路。你会发现借助K3S部署和维护一个现代化应用可以如此清晰和高效。2. 项目准备与容器化构建可部署的Docker镜像在将应用扔进K3S之前我们必须先把它“装进盒子”也就是制作成Docker镜像。这是至关重要的一步镜像的质量直接决定了部署的稳定性和可重复性。我们的项目结构通常如下my-app/ ├── backend/ # SpringBoot项目目录 │ ├── src/ │ ├── pom.xml # 或 build.gradle │ └── Dockerfile ├── frontend/ # Vue项目目录 │ ├── src/ │ ├── package.json │ ├── vue.config.js │ └── Dockerfile └── k8s-manifests/ # K8s资源定义文件后续使用2.1 SpringBoot后端镜像构建SpringBoot应用的容器化已经非常成熟。关键在于构建一个分层Layered的镜像以充分利用Docker的镜像缓存机制加快构建和推送速度。这里以Maven项目为例使用多阶段构建。backend/Dockerfile:# 第一阶段构建 FROM maven:3.8.6-eclipse-temurin-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-jammy WORKDIR /app # 复制构建产物使用分层JAR结构 COPY --frombuilder /app/target/*.jar app.jar # 创建一个非root用户运行增强安全性 RUN useradd -m -u 1000 appuser chown -R appuser:appuser /app USER appuser EXPOSE 8080 ENTRYPOINT [java, -jar, /app/app.jar]关键点解析多阶段构建第一阶段使用完整的JDK镜像进行编译打包第二阶段只包含轻量的JRE运行环境这能显著减小最终镜像的体积通常能从600MB降到200MB左右。依赖缓存先单独复制pom.xml并执行mvn dependency:go-offline这样只要依赖不变后续构建就可以复用这一层缓存极大加速构建过程。非Root用户在生产环境中以root权限运行容器是高风险行为。我们创建一个名为appuser的普通用户来运行应用这是安全最佳实践。分层JARSpring Boot 2.3支持在打包时创建分层索引spring-boot-maven-plugin配置layerstrue/layers可以进一步优化镜像层。上述Dockerfile是通用写法如果项目启用了分层可以更精细地复制spring-boot-loader、dependencies、snapshot-dependencies、application各层最大化缓存利用率。在backend目录下执行构建docker build -t my-springboot-app:latest .2.2 Vue前端镜像构建Vue项目是静态资源我们需要一个Web服务器来托管它。Nginx是首选因为它轻量、高效并且配置灵活。同样采用多阶段构建第一阶段用Node环境进行构建npm run build第二阶段将生成的dist目录复制到Nginx镜像中。frontend/Dockerfile:# 第一阶段构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . RUN npm run build # 第二阶段托管 FROM nginx:alpine # 将构建好的静态文件复制到Nginx的默认发布目录 COPY --frombuilder /app/dist /usr/share/nginx/html # 复制自定义的Nginx配置文件可选用于处理Vue Router的history模式 COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80关键点解析npm civsnpm install在CI/CD环境中推荐使用npm ci。它严格根据package-lock.json安装依赖能确保依赖树的一致性并且速度更快。Nginx配置默认配置可能无法正确处理Vue Router的history模式访问非根路径返回404。我们需要一个自定义配置来解决这个问题。frontend/nginx.conf:server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; # 核心配置当请求的文件不存在时重定向到index.html由前端路由处理 try_files $uri $uri/ /index.html; } # 可选代理后端API请求避免跨域。如果前后端通过K8s Service通信则不需要此配置。 # location /api/ { # proxy_pass http://backend-service:8080/; # } }这个配置中的try_files指令是支持Vue Routerhistory模式的关键。它告诉Nginx如果请求的URI对应的文件不存在就返回index.html让前端应用Vue Router去处理这个路由。在frontend目录下执行构建docker build -t my-vue-app:latest .实操心得镜像标签Tag管理很重要。在生产实践中不要总是使用:latest。建议使用Git提交哈希、构建编号或语义化版本作为标签的一部分如my-app:git-${COMMIT_SHA}这样可以实现精确的版本回滚和追踪。3. K3S集群搭建与核心概念快速上手有了镜像接下来就需要一个K3S集群来运行它们。K3S的安装简单到令人发指。3.1 单节点集群安装All-in-One对于学习和测试单节点模式足够了。在一台干净的Linux服务器如Ubuntu 22.04上只需一行命令curl -sfL https://get.k3s.io | sh -执行完成后K3S服务会自动启动。你可以通过以下命令检查状态sudo systemctl status k3s获取集群配置以便用kubectlK8s命令行工具管理sudo cat /etc/rancher/k3s/k3s.yaml将输出内容保存到本地~/.kube/config并修改其中的server地址为你的服务器IP如果从远程访问。然后安装kubectl就可以操作集群了。3.2 多节点集群安装可选如果需要多节点首先在主节点上安装并获取tokensudo cat /var/lib/rancher/k3s/server/node-token然后在工作节点上使用以下命令加入集群将MASTER_IP和TOKEN替换为实际值curl -sfL https://get.k3s.io | K3S_URLhttps://MASTER_IP:6443 K3S_TOKENTOKEN sh -3.3 部署前必须理解的几个K8s资源对象在编写部署文件之前需要理解几个核心的K8s资源它们是我们应用的“乐高积木”。PodK8s中最小的可部署单元。一个Pod包含一个或多个容器通常是一个共享网络和存储空间。我们一般不直接创建Pod。Deployment这是管理Pod的“控制器”。你定义一个Deployment它来确保指定数量的Pod副本Replicas始终运行。它负责滚动更新、回滚等。我们的应用无论是前端还是后端都会通过Deployment来部署。ServicePod是短暂的IP会变。Service提供了一个稳定的网络端点一个固定的集群内部IP和DNS名称来访问一组Pod。后端服务需要被前端访问所以后端需要一个Service。前端如果只需要被外部访问可能不需要独立的Service可通过Ingress直接指向Pod。IngressService提供的是L4TCP/UDP访问。Ingress是L7HTTP/HTTPS流量管理器它可以根据域名、路径将外部请求路由到集群内部不同的Service。我们需要一个Ingress来将公网流量路由到前端Nginx并可能将/api路径的请求代理到后端Service。K3S默认安装了Traefik作为Ingress Controller。理解了这些我们就可以像搭积木一样用YAML文件描述出我们应用的完整运行蓝图。4. 编写K8s部署清单定义应用运行蓝图现在我们在项目根目录创建k8s-manifests文件夹并开始编写YAML文件。这些文件描述了我们的应用在K3S集群中应该如何运行。4.1 部署后端SpringBoot应用首先定义后端的Deployment和Service。k8s-manifests/backend-deployment.yaml:apiVersion: apps/v1 kind: Deployment metadata: name: springboot-backend labels: app: springboot-backend spec: replicas: 2 # 运行2个副本提高可用性 selector: matchLabels: app: springboot-backend template: # 这是Pod的模板 metadata: labels: app: springboot-backend spec: containers: - name: backend image: my-springboot-app:latest # 替换为你的实际镜像名 ports: - containerPort: 8080 resources: requests: # 容器启动所需的最小资源 memory: 512Mi cpu: 250m limits: # 容器所能使用的最大资源 memory: 1Gi cpu: 500m env: # 环境变量可用于传递配置 - name: SPRING_PROFILES_ACTIVE value: prod - name: DB_HOST valueFrom: configMapKeyRef: name: app-config key: database.host # 健康检查探针对Spring Boot Actuator应用非常有用 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 # 容器启动后60秒开始探测 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: backend-service spec: selector: app: springboot-backend ports: - port: 80 # Service对外暴露的端口 targetPort: 8080 # 容器内端口 type: ClusterIP # 默认类型仅在集群内部可访问关键配置解读replicas: 2启动两个相同的Pod实例。当一个实例故障时另一个仍可提供服务Deployment会自动创建新的Pod维持数量。resources为容器设置资源请求和限制是生产环境必备操作。它防止某个应用耗尽节点资源影响其他应用。250m代表0.25个CPU核心。env将配置从代码中分离。这里演示了直接写死和引用ConfigMap两种方式。敏感信息如密码应使用Secret。livenessProbereadinessProbe就绪探针Readiness告诉K8s什么时候Pod可以开始接收流量。如果检查失败Pod会从Service的负载均衡池中移除。存活探针Liveness告诉K8s什么时候Pod需要重启。如果检查失败K8s会杀死并重启容器。Spring Boot Actuator提供了现成的健康端点。这能确保流量只会被发送到真正准备好的应用实例并在应用死锁时自动恢复。Service类型为ClusterIP这意味着后端服务只能在K3S集群内部通过backend-service这个DNS名称访问例如前端应用可以通过http://backend-service/api/users来调用后端API。这实现了前后端在网络层面的解耦和安全隔离。4.2 部署前端Vue应用前端是静态文件我们同样用Deployment来管理Nginx Pod并用一个Service暴露它。但前端的Service最终需要被外部访问我们有两种选择1) 使用NodePort类型的Service2) 使用ClusterIPIngress。生产环境推荐第二种。k8s-manifests/frontend-deployment.yaml:apiVersion: apps/v1 kind: Deployment metadata: name: vue-frontend labels: app: vue-frontend spec: replicas: 2 selector: matchLabels: app: vue-frontend template: metadata: labels: app: vue-frontend spec: containers: - name: frontend image: my-vue-app:latest # 替换为你的实际镜像名 ports: - containerPort: 80 resources: requests: memory: 128Mi cpu: 100m limits: memory: 256Mi cpu: 200m # 前端通常不需要复杂的健康检查一个简单的HTTP GET即可 livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 30 periodSeconds: 10 --- apiVersion: v1 kind: Service metadata: name: frontend-service spec: selector: app: vue-frontend ports: - port: 80 targetPort: 80 type: ClusterIP # 先使用ClusterIP通过Ingress对外暴露4.3 配置Ingress路由规则这是将外部流量引入集群的关键。我们创建一个Ingress资源定义路由规则。假设我们的域名是app.my-domain.com。k8s-manifests/ingress.yaml:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress annotations: # 以下注解针对TraefikK3S默认Ingress Controller traefik.ingress.kubernetes.io/router.entrypoints: web # 如果启用HTTPS还需要配置websecure入口点和证书 spec: rules: - host: app.my-domain.com # 你的域名 http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 80 - path: /api pathType: Prefix backend: service: name: backend-service port: number: 80路由规则解读当用户访问http://app.my-domain.com/时流量会被路由到frontend-service即我们的Vue应用。当Vue前端发起API请求到/api/users时这个请求会被Ingress捕获并路由到backend-service。注意这里后端Service的端口是80Service端口它会转发到Pod的8080端口targetPort。这样就实现了前后端在同一个域名下的统一访问入口完美解决了前后端分离项目在部署时的跨域问题CORS因为现在它们在同源相同域名、端口下了。踩坑提醒如果你的SpringBoot后端有配置上下文路径server.servlet.context-path例如/api/v1那么Ingress中的path应该与之匹配比如设置为/api/v1。同时前端调用后端API的基地址Base URL也应该是/api/v1而不是简单的/api。路径不匹配是导致404错误的常见原因。5. 部署实战与运维要点编写好YAML文件后就可以开始部署了。5.1 应用部署与状态检查应用配置首先确保你的Docker镜像已经推送到一个K3S节点能够访问的镜像仓库如Docker Hub、私有Harbor或者直接构建在本地节点上。如果镜像在本地K3S可以直接使用。部署资源在Master节点上使用kubectl apply命令部署所有资源。kubectl apply -f k8s-manifests/这个命令会读取目录下所有.yaml文件并创建资源。检查部署状态# 查看所有Pod的状态 kubectl get pods -o wide # 查看Deployment状态 kubectl get deployments # 查看Service kubectl get svc # 查看Ingress kubectl get ingress等待所有Pod的状态变为Running并且READY列为2/2表示2个副本都就绪。5.2 访问应用与问题排查获取访问地址由于我们使用了Ingress需要知道TraefikIngress Controller的外部IP。在云服务器上这个IP可能就是节点的公网IP。执行以下命令查看kubectl get svc -n kube-system traefik如果EXTERNAL-IP是pending在非云环境中你可能需要将节点的IP地址作为访问地址。或者你可以修改frontend-service的类型为NodePort然后通过节点IP:NodePort直接访问前端。配置DNS/本地Hosts将你的域名app.my-domain.com解析到上一步获得的IP地址。在测试环境可以直接修改本地的hosts文件。访问在浏览器中打开http://app.my-domain.com应该能看到Vue前端页面。前端发起的API请求如/api/users应该能正确到达后端并返回数据。5.3 常见问题与排查命令Pod一直处于Pending状态通常是资源不足CPU/内存或节点选择问题。使用kubectl describe pod pod-name查看事件详情。Pod处于CrashLoopBackOff状态容器反复启动失败。首先查看日志kubectl logs pod-name。如果容器有多个用-c container-name指定。查看前一次崩溃的日志kubectl logs pod-name --previous。服务无法访问检查Service的Selector是否与Pod的Label匹配kubectl describe svc service-name。进入一个Pod内部尝试用Service的DNS名称如curl http://backend-service/actuator/health测试连通性。检查Ingress Controller日志kubectl logs -n kube-system deployment/traefik。应用配置问题确保环境变量、ConfigMap、Secret等配置正确挂载到容器中。使用kubectl exec -it pod-name -- /bin/sh进入容器内部检查环境。5.4 配置管理与敏感信息处理在实际项目中数据库连接字符串、API密钥等敏感信息绝不能硬编码在镜像或YAML文件中。K8s提供了ConfigMap和Secret来管理配置和敏感数据。示例使用ConfigMap管理应用配置# k8s-manifests/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config data: application.properties: | spring.datasource.urljdbc:mysql://${DB_HOST}:3306/mydb app.feature.enabledtrue示例使用Secret管理数据库密码# k8s-manifests/secret.yaml (密码需要base64编码) apiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: password: c3VwZXJzZWNyZXQ # supersecret的base64编码然后在Deployment中通过环境变量引用env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password6. 进阶持久化存储、滚动更新与监控初探6.1 为SpringBoot应用添加数据库持久化我们的SpringBoot应用通常需要数据库。在K8s中数据库如MySQL也建议以有状态工作负载StatefulSet部署并需要持久化存储PersistentVolume, PV来保存数据。简化示例使用K3S内置的Local Path ProvisionerK3S默认安装了一个本地路径存储类StorageClass可以动态创建PV。我们可以为MySQL创建一个PersistentVolumeClaimPVC。# k8s-manifests/mysql-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pvc spec: accessModes: - ReadWriteOnce storageClassName: local-path # K3S默认的存储类 resources: requests: storage: 5Gi然后在MySQL的Deployment中挂载这个PVC到容器内的数据目录如/var/lib/mysql。这样即使Pod重启或迁移数据也不会丢失。6.2 实现零停机滚动更新当我们发布新版本的镜像时Deployment的滚动更新策略可以确保服务不中断。这是我们在Deployment定义中spec.strategy字段控制的默认就是RollingUpdate。触发更新只需修改Deployment YAML文件中的镜像标签然后重新apply。kubectl set image deployment/springboot-backend backendmy-springboot-app:v2.0 # 或者 kubectl apply -f k8s-manifests/backend-deployment.yaml # 如果文件里镜像tag已改K8s会逐步用新Pod替换旧Pod期间始终有Pod在提供服务。你可以通过kubectl rollout status deployment/springboot-backend来观察更新状态。如果新版本有问题可以快速回滚kubectl rollout undo deployment/springboot-backend。6.3 基础监控与日志收集对于生产环境监控和日志必不可少。K3S集群监控可以部署Prometheus Grafana。社区有成熟的Helm Chart可以一键部署用于监控节点、Pod的资源使用情况CPU、内存、网络。应用日志所有容器的标准输出和错误输出都可以通过kubectl logs查看。对于集中式日志收集可以考虑部署EFKElasticsearch, Fluentd, Kibana或Loki栈。在K3S上由于资源限制Loki是更轻量级的选择。应用性能监控APM对于Java应用可以集成SkyWalking、Pinpoint等APM工具通过Sidecar模式或Java Agent注入到应用容器中来监控接口性能、调用链路和JVM状态。7. 总结与个人实践建议走完这一整套流程你会发现用K3S部署SpringBootVue项目虽然前期需要学习一些K8s的概念和YAML语法但一旦流程固化下来其带来的收益是巨大的环境标准化、一键部署、弹性伸缩、故障自愈。整个过程就像是为你的应用编写了一份详细的“运行说明书”YAML文件集群会严格按照说明书来执行和维护。从我个人的实践经验来看有几点建议值得分享第一基础设施即代码IaC。把你所有的K8s YAML文件、Dockerfile、甚至安装脚本都纳入Git版本控制。这能保证部署过程的可重复性和可审计性。你可以使用Kustomize或Helm来管理更复杂的配置和多个环境开发、测试、生产。第二重视健康检查。为你的SpringBoot应用集成Actuator并配置好livenessProbe和readinessProbe。这是K8s管理你应用生命周期的“眼睛”没有它K8s就不知道你的应用是死是活滚动更新和自愈能力会大打折扣。第三镜像标签策略。永远不要依赖:latest标签进行生产部署。使用有意义的标签如${gitTag}-${buildNumber}。这能让你在出问题时精确地知道线上运行的是哪个版本的代码并快速回滚。第四从小处着手。如果你和你的团队是K8s新手不要试图一次性把所有微服务、所有中间件都搬上去。可以从一个简单的、无状态的应用就像我们这个前后端项目开始熟悉整个流程和排查问题的方法。等有了信心再逐步迁移更复杂的组件。最后K3S的轻量化特性使得它成为个人项目、初创公司或边缘场景拥抱K8s生态的绝佳跳板。它降低了你学习和试错的硬件和心智门槛。当你用几行命令就搭建起一个具备完整容器编排能力的集群并看着你的应用在其中稳定运行时那种一切尽在掌控的感觉正是DevOps文化的魅力所在。