Kubernetes 部署 Spring Boot 全指南
引言
隨著容器化技術和云原生架構的普及,將Spring Boot應用部署到K8s已成為企業級應用的標準實踐。這種部署方式不僅能實現應用的快速擴展和高可用,還能簡化配置管理和版本迭代流程。
本文將詳細介紹如何在K8s環境中完整部署一個Spring Boot應用,涵蓋從項目準備、鏡像構建到K8s資源配置、部署驗證及日常運維的全流程。
前置條件
開發環境:
- JDK 11+(與Spring Boot版本匹配)
- Maven 3.6+或Gradle 7.0+
- Docker 20.10+
- Kubernetes集群(1.27+,可使用Minikube、Kind或生產級集群)
- kubectl命令行工具(與集群版本兼容)
基礎知識:
- 了解Spring Boot應用的打包和運行機制
- 熟悉Docker基本命令和鏡像構建
- 了解Kubernetes核心概念(Pod、Deployment、Service、ConfigMap等)
項目準備與容器化
首先確保Spring Boot項目能正常構建為可執行JAR包。在項目根目錄執行打包命令:
# Maven項目
mvn clean package -DskipTests
# Gradle項目
gradle clean build -x test
編寫 Dockerfile
在項目根目錄創建Dockerfile,用于將Spring Boot應用容器化:
# 基礎鏡像選擇(推薦使用官方精簡版JDK)
FROM openjdk:11-jdk-slim
# 維護者信息
LABEL maintainer="yian@example.com"
# 設置工作目錄
WORKDIR /app
# 復制JAR包到容器(注意替換實際JAR文件名)
COPY target/vpdnapi-0.0.1-SNAPSHOT.jar app.jar
# 暴露應用端口(與Spring Boot配置的server.port一致)
EXPOSE 8080
# 啟動命令(使用exec格式確保容器能正確接收信號)
ENTRYPOINT ["java", "-jar", "app.jar"]
# 可選:添加JVM參數優化
# ENTRYPOINT ["java", "-Xms512m", "-Xmx1g", "-jar", "app.jar"]
最佳實踐:
- 選擇合適的JDK版本,與項目編譯版本一致
- 使用精簡基礎鏡像(如slim或alpine版本)減小鏡像體積
- 固定鏡像標簽,避免使用latest
- 合理配置JVM參數,避免資源浪費
構建 Docker 鏡像
# 格式:docker build -t [鏡像倉庫地址]/[鏡像名]:[版本] .
docker build -t your-registry.example.com/vpdnapi:v1.0.0 .
推送鏡像到倉庫:
# 登錄鏡像倉庫(如需)
docker login your-registry.example.com
# 推送鏡像
docker push your-registry.example.com/vpdnapi:v1.0.0
資源配置
配置管理:ConfigMap 與 Secret
Spring Boot應用通常需要外部配置文件(如application.yml)和敏感信息(如數據庫密碼)。在K8s中,推薦使用ConfigMap存儲普通配置,使用Secret存儲敏感信息。
apiVersion: v1
kind: ConfigMap
metadata:
name: vpdnapi-config
namespace: vpdn-system
labels:
app: vpdnapi
data:
application.yml: |
spring:
profiles:
active: prod
application:
name: vpdnapi
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
probes:
enabled: true# 啟用Spring Boot的探針支持
application-prod.yml: |
spring:
datasource:
url: jdbc:mysql://mysql-service:3306/vpdn_db
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
---
# conf目錄資源文件
apiVersion: v1
kind: ConfigMap
metadata:
name: vpdnapi-conf-files
namespace: vpdn-system
labels:
app: vpdnapi
data:
clients.xml: |
<?xml version="1.0" encoding="UTF-8"?>
<clients>
<client id="web" name="Web Application"/>
</clients>
permissions.properties: |
admin=*
operator=read,write
viewer=read
apiVersion: v1
kind: Secret
metadata:
name: vpdnapi-secret
namespace: your-namespace
type: Opaque
data:
# 注意:值必須使用Base64編碼(echo -n "實際值" | base64)
db-username: YWRtaW4= # 編碼前:admin
db-password: cGFzc3dvcmQxMjM= # 編碼前:password123
api-key: SldJVFNlY3JldEtleQ== # 編碼前:JWTSecretKey
部署應用:Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: vpdnapi-deployment
namespace: vpdn-system
labels:
app: vpdnapi
spec:
replicas: 3
selector:
matchLabels:
app: vpdnapi
# 滾動更新策略優化
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
# 最小就緒時間(K8s 1.27+推薦設置)
minReadySeconds: 10
template:
metadata:
labels:
app: vpdnapi
spec:
containers:
- name: vpdnapi
image: your-registry.example.com/vpdnapi:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
protocol: TCP
# 資源管理
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
# 環境變量
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: vpdnapi-secret
key: db-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: vpdnapi-secret
key: db-password
# 配置文件掛載
volumeMounts:
- name: main-config
mountPath: /app/application.yml
subPath: application.yml
- name: main-config
mountPath: /app/application-prod.yml
subPath: application-prod.yml
- name: conf-files
mountPath: /app/conf
# 健康探針(K8s 1.27推薦配置)
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
# 啟動探針(處理慢啟動場景)
startupProbe:
httpGet:
path: /actuator/health
port: http
failureThreshold: 30
periodSeconds: 10
volumes:
- name: main-config
configMap:
name: vpdnapi-config
defaultMode: 0644
- name: conf-files
configMap:
name: vpdnapi-conf-files
defaultMode: 0644
關鍵配置說明:
- replicas: 設置多個副本實現高可用
- resources: 配置資源請求和限制,避免資源競爭
- livenessProbe/readinessProbe: 健康檢查確保K8s能正確識別應用狀態
- volumeMounts: 將ConfigMap掛載到容器內,實現配置外部化
- imagePullPolicy: 建議設置為IfNotPresent或Always(生產環境)
暴露服務:Service
apiVersion: v1
kind: Service
metadata:
name: vpdnapi-service
namespace: vpdn-system
labels:
app: vpdnapi
spec:
selector:
app: vpdnapi
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
type: ClusterIP
Service類型說明:
- ClusterIP: 僅集群內部可訪問(默認)
- NodePort: 通過節點端口暴露服務(適合測試環境)
- LoadBalancer: 結合云廠商負載均衡器使用(適合生產環境)
外部訪問:Ingress(可選)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vpdnapi-ingress
namespace: vpdn-system
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
# 啟用會話保持(如需)
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "VPdnApiSession"
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
ingressClassName: nginx
rules:
- host: vpdnapi.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vpdnapi-service
port:
name: http
tls:
- hosts:
- vpdnapi.example.com
secretName: vpdnapi-tls-cert
部署與驗證
# 創建命名空間(如不存在)
kubectl create namespace your-namespace
# 部署配置資源
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
# 部署應用
kubectl apply -f deployment.yaml
# 部署服務
kubectl apply -f service.yaml
# 部署Ingress(如需要)
kubectl apply -f ingress.yaml
驗證部署狀態
#查看 Pod 狀態
kubectl get pods -n your-namespace -l app=vpdnapi
NAME READY STATUS RESTARTS AGE
vpdnapi-deployment-7f9d658b45-2xq8k 1/1 Running 0 5m
vpdnapi-deployment-7f9d658b45-zm7p3 1/1 Running 0 5m
#查看 Deployment 狀態
kubectl get deployment vpdnapi-deployment -n your-namespace
#查看 Service 狀態
kubectl get service vpdnapi-service -n your-namespace
#查看應用日志
# 查看指定Pod日志
kubectl logs -f <pod-name> -n your-namespace
# 查看最近100行日志
kubectl logs --tail=100 <pod-name> -n your-namespace
測試訪問:
# 通過Service ClusterIP訪問
curl http://<service-ip>:80/actuator/health
# 通過Ingress訪問
curl https://vpdnapi.example.com/actuator/health