K3S实战:SpringBoot+Vue全栈应用容器化部署与Kubernetes编排指南
1. 项目概述与核心价值
最近在整理技术栈,发现很多朋友在从单体应用转向微服务或云原生架构时,面对容器编排这一关总有些发怵。尤其是中小团队或个人开发者,既想享受Kubernetes带来的声明式部署、服务发现、弹性伸缩等现代化能力,又担心它那庞大的体量和复杂的运维成本。这不,我手头正好有一个典型的SpringBoot后端+Vue前端组成的全栈项目需要部署上线,这次我决定不直接用庞大的K8s,而是选用它的轻量级发行版——K3S,来一次从零到一的完整实践。
K3S可以理解为Kubernetes的“精简优化版”,它保留了K8s的核心API和功能,但通过去除历史包袱、集成轻量级组件(如用SQLite替代etcd)、将二进制文件打包为单个小于100MB的包等方式,极大地降低了资源消耗和部署复杂度。对于资源有限的边缘计算场景、开发测试环境,或者像我这样只想快速搭建一个稳定可用的生产级容器编排平台的人来说,K3S简直是“福音”。本次部署的目标,就是将一套标准的SpringBoot后端服务(提供RESTful API)和一个Vue.js构建的前端静态应用,通过K3S集群进行容器化部署,实现服务的高可用、便捷管理和自动化运维。整个流程涉及Docker镜像制作、K3S集群搭建、Kubernetes资源配置文件(YAML)编写、服务暴露等关键环节,我会把每一步的原理、操作和踩过的坑都详细记录下来。
2. 环境与工具准备:构建可复现的基础
工欲善其事,必先利其器。在开始动手之前,我们需要准备好一个干净、一致的实验环境。我选择在一台配置为2核4GB的云服务器(CentOS 7.9)上进行,这模拟了大多数个人项目或初创团队的最小化生产环境。当然,你也可以在本地虚拟机(如VirtualBox + Ubuntu)或多台服务器上搭建集群,原理相通。
2.1 基础系统配置
首先,确保系统环境干净。更新系统包并安装一些基础工具:
# 更新系统包
yum update -y
# 安装常用工具(wget用于下载,vim用于编辑,net-tools用于网络诊断)
yum install -y wget vim net-tools
接下来是关键一步:关闭Swap。Kubernetes及其衍生品(包括K3S)为了确保调度和运行的稳定性,默认要求禁用Swap内存。这是因为Swap的I/O操作会引入不可预测的延迟,影响Pod(容器组)的调度和性能。
# 临时关闭Swap
swapoff -a
# 永久关闭Swap:注释掉/etc/fstab中swap相关的行
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
然后,配置Linux内核参数,以允许iptables正确处理桥接网络流量,这是容器网络正常工作的基础。
# 加载br_netfilter模块
modprobe br_netfilter
# 确保重启后依然加载
echo 'br_netfilter' > /etc/modules-load.d/k8s.conf
# 设置sysctl参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# 使配置生效
sysctl --system
2.2 Docker安装与配置
虽然K3S默认集成了containerd作为容器运行时,且从1.21版本开始甚至可以直接通过
--docker
参数使用Docker,但考虑到我们后续镜像构建和调试的便利性(Docker CLI工具链更丰富),我选择先安装Docker,并配置K3S使用Docker作为运行时。
# 1. 卸载旧版本(如有)
yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
# 2. 安装yum工具集并添加Docker仓库
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 3. 安装Docker引擎
yum install -y docker-ce docker-ce-cli containerd.io
# 4. 启动并设置开机自启
systemctl start docker
systemctl enable docker
# 5. 验证安装
docker run hello-world
安装成功后,需要配置Docker的镜像加速器(国内环境必备),并修改Cgroup驱动为
systemd
,以与K3S保持一致,避免潜在的兼容性问题。
# 配置daemon.json
cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": ["https://your-mirror.mirror.aliyuncs.com"], # 替换为你的加速器地址
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
# 重启Docker
systemctl daemon-reload
systemctl restart docker
2.3 K3S Server节点安装
K3S的安装简单到令人发指。官方提供了一键安装脚本。我们将当前服务器作为集群的Server节点(即Master节点)。
# 使用国内镜像加速安装,避免网络问题
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -s - --docker
这个命令做了几件事:下载K3S安装脚本、指定使用中国镜像源、并通过
--docker
参数告诉K3S使用我们刚安装的Docker作为容器运行时。安装完成后,K3S服务会自动启动。
关键验证与文件定位:
# 检查K3S服务状态
systemctl status k3s
# 获取node节点信息(此时应该只有一个节点,角色为control-plane,master)
k3s kubectl get node
# 获取集群所有Pod状态,确认核心组件(coredns, metrics-server等)运行正常
k3s kubectl get pods -A
安装成功后,K3S的kubeconfig文件(集群管理员凭证)位于
/etc/rancher/k3s/k3s.yaml
。我们需要将其权限设置正确,并复制到本地
~/.kube/config
,以便使用常规的
kubectl
命令。
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
# 验证kubectl
kubectl cluster-info
3. 应用容器化:从代码到镜像
有了K3S集群,接下来就需要将我们的SpringBoot和Vue应用打包成Docker镜像。这是将任何应用交付到Kubernetes生态的第一步,也是至关重要的一步。
3.1 SpringBoot后端应用Docker化
对于SpringBoot应用,通常我们采用多阶段构建(Multi-stage Build)来制作镜像,目的是得到一个仅包含运行所需JRE和JAR包的、体积尽可能小的生产镜像。
假设你的SpringBoot项目使用Maven构建,项目根目录结构如下:
springboot-app/
├── src/
├── pom.xml
└── Dockerfile
对应的
Dockerfile
内容如下:
# 第一阶段:构建阶段,使用Maven镜像
FROM maven:3.8.6-openjdk-11-slim AS builder
WORKDIR /app
# 复制pom文件,利用Docker缓存层,避免依赖重复下载
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 复制源码并打包
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行阶段,使用轻量级JRE镜像
FROM openjdk:11-jre-slim
# 设置时区(避免容器内日志时间不对)
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
# 创建一个非root用户运行应用,增强安全性
RUN useradd -m -u 1000 appuser
USER appuser
WORKDIR /app
# 从构建阶段复制打好的jar包
COPY --from=builder /app/target/*.jar app.jar
# 暴露应用端口(与SpringBoot配置文件中的server.port一致)
EXPOSE 8080
# 使用exec形式启动,确保能接收SIGTERM等信号,实现优雅关闭
ENTRYPOINT ["java", "-jar", "app.jar"]
构建与推送镜像:
# 在项目根目录执行构建,并打上标签
docker build -t your-dockerhub-username/springboot-app:1.0.0 .
# 登录Docker Hub(或其他镜像仓库)
docker login
# 推送镜像到仓库
docker push your-dockerhub-username/springboot-app:1.0.0
注意: 如果你的应用需要连接数据库、Redis等,不要在Dockerfile里写死配置。应该通过环境变量(在K8s的YAML中配置)或外部的配置文件(通过ConfigMap挂载)来注入,保证镜像的通用性。
3.2 Vue前端应用Docker化
Vue项目是纯静态资源(HTML, CSS, JS)。标准的做法是使用Node.js环境进行构建(
npm run build
),然后将生成的
dist
目录放到一个Nginx或Apache镜像中提供服务。
假设Vue项目结构如下:
vue-app/
├── src/
├── public/
├── package.json
├── vue.config.js
└── Dockerfile
对应的
Dockerfile
也采用多阶段构建:
# 第一阶段:构建阶段,使用Node镜像
FROM node:16-alpine AS builder
WORKDIR /app
# 复制依赖文件并安装
COPY package*.json ./
RUN npm install --registry=https://registry.npmmirror.com
# 复制源码并构建
COPY . .
RUN npm run build
# 第二阶段:运行阶段,使用Nginx镜像
FROM nginx:alpine
# 将构建好的静态文件复制到Nginx的默认服务目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 如果需要,可以复制自定义的Nginx配置文件
# COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露80端口
EXPOSE 80
# Nginx镜像默认已启动Nginx,无需额外ENTRYPOINT
构建与推送:
docker build -t your-dockerhub-username/vue-app:1.0.0 .
docker push your-dockerhub-username/vue-app:1.0.0
实操心得: Vue项目构建时,经常需要配置生产环境API地址。绝对不要写死在
vue.config.js里。正确做法是:在Dockerfile构建阶段,通过构建参数(--build-arg)传入;或者更常见的,在Nginx配置中设置一个变量,让前端JS在运行时从window.env或通过请求特定接口来获取。在K8s中,我们通常采用后者,结合ConfigMap来管理Nginx配置。
4. K3S集群部署实战:编写Kubernetes清单
镜像准备就绪后,就到了最核心的环节:编写Kubernetes的资源清单文件(YAML)。这些文件描述了我们的应用最终在集群中应该以何种形态运行。我会为前后端分别创建Deployment(定义Pod副本)和Service(定义网络访问)。
4.1 部署SpringBoot后端服务
创建文件
springboot-deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: springboot-backend
namespace: default # 不指定则默认为default
spec:
replicas: 2 # 启动2个Pod副本,实现高可用
selector:
matchLabels:
app: springboot-backend
template:
metadata:
labels:
app: springboot-backend
spec:
containers:
- name: app
image: your-dockerhub-username/springboot-app:1.0.0 # 替换为你的镜像
ports:
- containerPort: 8080 # 容器内端口
env: # 通过环境变量注入配置,这是12-Factor App的最佳实践
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DB_HOST
valueFrom:
configMapKeyRef: # 假设数据库地址通过ConfigMap管理
name: app-config
key: database.host
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: redis.host
resources: # 资源请求与限制,防止单个Pod占用过多资源,也帮助调度器决策
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe: # 存活探针,检查应用是否“活着”
httpGet:
path: /actuator/health/liveness # Spring Boot Actuator端点
port: 8080
initialDelaySeconds: 60 # 容器启动后60秒开始探测
periodSeconds: 10 # 每10秒探测一次
readinessProbe: # 就绪探针,检查应用是否“准备好”接收流量
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: springboot-backend-service
spec:
selector:
app: springboot-backend
ports:
- port: 80 # Service对集群内暴露的端口
targetPort: 8080 # 转发到Pod的哪个端口
type: ClusterIP # 默认类型,仅在集群内部可访问
关键点解析:
-
Deployment
: 定义了Pod的副本数、更新策略等。
replicas: 2意味着任何时候都至少有两个Pod在运行,一个挂了,另一个还能服务。 - 环境变量(env) : 将配置信息(如数据库连接串)从代码中分离。这里演示了从ConfigMap引用,实际还可以用Secret管理密码。
-
资源(resources)
:
requests是调度依据,limits是硬性限制。设置合理值能提高集群稳定性。 -
探针(Probe)
: 这是生产部署的
灵魂
。
livenessProbe失败,K8s会重启Pod;readinessProbe失败,K8s会将该Pod从Service的负载均衡池中移除,直到它恢复。这实现了应用的自我修复和零停机部署。 -
Service
: 类型为
ClusterIP,为后端Pod集合提供了一个稳定的内部域名(springboot-backend-service.default.svc.cluster.local)和IP,前端应用在集群内通过这个域名访问后端。
4.2 部署Vue前端服务
创建文件
vue-frontend-deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vue-frontend
spec:
replicas: 2
selector:
matchLabels:
app: vue-frontend
template:
metadata:
labels:
app: vue-frontend
spec:
containers:
- name: nginx
image: your-dockerhub-username/vue-app:1.0.0
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
# 静态资源服务通常不需要复杂的探针,一个简单的HTTP GET即可
livenessProbe:
httpGet:
path: /index.html
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /index.html
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: vue-frontend-service
spec:
selector:
app: vue-frontend
ports:
- port: 80
targetPort: 80
type: NodePort # 修改为NodePort,便于从集群外部访问
关键点解析:
-
NodePort Service
: 这是与后端Service最大的不同。
type: NodePort会在集群每个节点的某个固定端口(范围30000-32767)上暴露服务。这样,用户就可以通过<节点IP>:<NodePort>来访问前端页面。这是最简单的外部访问方式,适合演示和测试。生产环境通常会使用LoadBalancer(如果云厂商支持)或更常用的Ingress控制器。
4.3 应用配置管理(ConfigMap示例)
将可变配置与镜像分离。创建
app-configmap.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# 后端应用配置
database.host: "mysql-service.default.svc.cluster.local"
redis.host: "redis-service.default.svc.cluster.local"
# 前端API基础地址(可供前端运行时获取)
api.base.url: "http://springboot-backend-service"
这个ConfigMap被上面的SpringBoot Deployment通过
env.valueFrom.configMapKeyRef
引用。
4.4 执行部署与验证
将以上YAML文件保存到服务器,然后使用
kubectl apply
命令部署:
# 应用所有配置
kubectl apply -f app-configmap.yaml
kubectl apply -f springboot-deployment.yaml
kubectl apply -f vue-frontend-deployment.yaml
# 查看部署状态
kubectl get deployments
kubectl get pods -o wide # 查看Pod详情及所在节点
kubectl get services # 查看Service,注意vue-frontend-service的NodePort端口
# 查看Pod日志,排查问题
kubectl logs -f <pod-name>
如果一切顺利,你会看到两个
springboot-backend-xxxxx
和两个
vue-frontend-xxxxx
的Pod状态都是
Running
。通过
kubectl get svc
命令,找到
vue-frontend-service
对应的
NodePort
(例如
32345
),然后在浏览器访问
http://你的服务器IP:32345
,应该就能看到Vue前端页面了。前端页面发起的API请求,会通过配置的
api.base.url
(指向
springboot-backend-service
)访问到后端服务。
5. 进阶配置与生产级考量
基础的部署完成后,我们可以进一步优化,让这个部署更接近生产环境的要求。
5.1 使用Ingress暴露服务(替代NodePort)
NodePort不适合生产,端口范围有限且不安全。更优雅的方式是使用Ingress,它类似于一个7层(HTTP/HTTPS)负载均衡器,可以根据域名和路径将流量路由到不同的后端Service。
首先,在K3S上安装一个Ingress Controller。K3S默认集成了Traefik作为Ingress Controller,但也可以安装更流行的Nginx Ingress Controller。这里以安装Nginx Ingress为例:
# 使用Helm安装(K3S默认安装了Helm)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=NodePort # 为Ingress Controller自身创建一个NodePort Service
安装后,创建一个Ingress资源定义文件
ingress.yaml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fullstack-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # 可能需要根据前端路由配置重写规则
spec:
rules:
- host: app.yourdomain.com # 你的域名,需要配置DNS指向集群节点IP或负载均衡器IP
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vue-frontend-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: springboot-backend-service
port:
number: 80
应用这个Ingress:
kubectl apply -f ingress.yaml
现在,访问
http://app.yourdomain.com
会看到前端,而前端所有以
/api
开头的请求会被转发到后端SpringBoot服务。你需要将域名
app.yourdomain.com
解析到安装了Ingress Controller的节点IP(或云负载均衡器IP)。
5.2 持久化存储与数据库部署
有状态服务(如MySQL、Redis)需要持久化存储。在K3S中,可以方便地使用
Local Path Provisioner
(K3S默认安装)来提供动态的本地存储,或者对接云存储。
以部署MySQL为例,需要创建一个PersistentVolumeClaim (PVC)来申请存储,然后在Deployment中挂载。这里是一个简化的示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-path # 使用K3S自带的local-path存储类
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef: # 密码等敏感信息务必使用Secret
name: mysql-secret
key: rootPassword
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc # 挂载PVC
注意: 生产环境强烈建议将数据库部署在集群外,或者使用云托管的数据库服务(RDS),以获得更好的可靠性、可维护性和备份能力。
5.3 日志与监控
K3S默认集成了CoreDNS和Metrics Server。Metrics Server为Kubernetes Dashboard或HPA(Horizontal Pod Autoscaler,自动水平扩缩容)提供资源指标。
对于应用日志,标准做法是让应用将日志输出到标准输出(stdout)和标准错误(stderr),Kubernetes会自动捕获并可以通过
kubectl logs
查看。对于集中式日志收集,可以部署EFK(Elasticsearch, Fluentd, Kibana)或Loki栈。
对于监控,可以部署Prometheus + Grafana。K3S社区有相关的Helm Chart可以一键部署,用于监控集群节点、Pod、Service等各项指标。
6. 常见问题与故障排查实录
在实际操作中,你几乎一定会遇到一些问题。下面是我在部署过程中遇到的一些典型问题及解决方法。
6.1 镜像拉取失败
现象:
Pod状态一直为
ImagePullBackOff
或
ErrImagePull
。
排查:
-
kubectl describe pod <pod-name>查看Pod详情,在Events部分会有详细错误信息。 -
常见原因:
-
镜像名错误或不存在
:检查YAML文件中的
image字段,确保拼写正确,且镜像已推送到仓库。 -
私有仓库无权限
:如果使用私有仓库(如阿里云容器镜像服务),需要创建
docker-registry类型的Secret,并在Deployment的spec.template.spec中添加imagePullSecrets字段引用它。
然后在Deployment YAML中添加:# 创建docker-registry secret kubectl create secret docker-registry regcred \ --docker-server=<your-registry-server> \ --docker-username=<your-name> \ --docker-password=<your-password> \ --docker-email=<your-email>spec: template: spec: imagePullSecrets: - name: regcred containers: - ... -
镜像名错误或不存在
:检查YAML文件中的
6.2 Pod启动后立即CrashLoopBackOff
现象:
Pod反复重启,状态为
CrashLoopBackOff
。
排查:
-
kubectl logs <pod-name> --previous查看上一个容器的日志,这通常能直接定位到应用启动失败的原因(如数据库连接不上、配置文件错误、端口冲突等)。 -
检查应用的健康检查(liveness/readiness probe)配置是否合理。如果探针检查的路径不对或启动延迟(
initialDelaySeconds)设置太短,可能导致K8s认为应用不健康而不断重启。可以临时将探针注释掉,看应用是否能正常启动并运行。
6.3 Service无法访问
现象: 前端页面能打开,但所有API请求都失败(网络错误)。 排查:
-
在集群内部进行调试。进入一个运行中的Pod(比如前端Pod),使用
curl命令测试后端Service的域名。kubectl exec -it <vue-pod-name> -- sh # 在Pod内部执行 curl -v http://springboot-backend-service:80/actuator/health -
如果内部能通,说明Service配置和网络策略没问题,问题可能出在前端代码配置的API地址上。检查前端构建时或运行时配置的API地址是否正确指向了后端Service的
集群内域名
(
springboot-backend-service或带命名空间的springboot-backend-service.default.svc.cluster.local)。 -
如果内部也不通,检查:
-
Service的selector
是否与Pod的
labels匹配。 -
Pod的containerPort
是否与Service的
targetPort一致。 -
后端Pod是否真的在运行且就绪(
kubectl get pods查看READY列)。
-
Service的selector
是否与Pod的
6.4 NodePort无法从外部访问
现象:
浏览器访问
<节点IP>:<NodePort>
无法打开页面。
排查:
-
确认NodePort端口号:
kubectl get svc vue-frontend-service,查看PORT(S)列,例如80:32345/TCP,则NodePort是32345。 - 检查服务器安全组/防火墙规则,是否放行了该NodePort端口(30000-32767范围)。
-
在服务器本机用
curl测试:curl http://localhost:32345,如果本机能通,外部不通,基本就是防火墙或安全组的问题。
6.5 K3S特定问题:coredns Pod pending
现象:
kubectl get pods -A
发现
coredns
Pod状态为
Pending
。
排查:
这通常是因为节点资源(特别是CPU/内存)不足,或者
local-path
存储类的默认存储路径权限问题。
-
查看Pod详情:
kubectl describe pod -n kube-system coredns-xxxx。 - 如果是资源不足,考虑增加节点资源或优化其他Pod的资源限制。
-
如果是存储问题,可以尝试手动创建存储目录并赋予权限(谨慎操作):
然后删除Pending的Pod让其重建:# 查看local-path的配置路径 kubectl get storageclass local-path -o yaml | grep -A 5 -B 5 path # 通常默认路径在/var/lib/rancher/k3s/storage下 sudo mkdir -p /var/lib/rancher/k3s/storage sudo chmod 777 /var/lib/rancher/k3s/storage # 仅为示例,生产环境应设置更严格的权限kubectl delete pod -n kube-system coredns-xxxx。
整个部署过程,从环境准备到应用上线,再到问题排查,其实是一个不断加深对Kubernetes对象模型理解的过程。K3S极大地降低了入门门槛,但它背后运行的,是完整的、生产就绪的Kubernetes能力。把SpringBoot和Vue这样的经典组合放上去,不仅仅是完成一次部署,更是为应用拥抱弹性伸缩、蓝绿部署、服务网格等更高级的云原生特性铺平了道路。当你熟悉了这些YAML文件和
kubectl
命令之后,你会发现管理应用的生命周期,变得前所未有的清晰和高效。
更多推荐
所有评论(0)