引言:容器化部署为何成为 Java 开发的必选项
在 Java 开发领域,部署方式正经历着从传统虚拟机到容器化的革命性转变。根据 2024 年 JetBrains 开发者调查,超过 78% 的企业级 Java 项目已采用容器化部署,其中 Kubernetes 成为容器编排的绝对主导者,市场占有率高达 83%。
这种转变并非偶然。传统 Java 应用部署面临着环境一致性差、资源利用率低、扩展能力弱等痛点:开发环境运行正常的代码,到了测试环境却频频报错;为应对流量峰值不得不长期维持大量闲置服务器;系统出现故障时,手动恢复耗时费力。
Docker 容器化技术与 Kubernetes 集群管理的结合,为这些问题提供了完美解决方案:
- 环境一致性:容器封装了应用及其所有依赖,确保 "一次构建,到处运行"
- 资源高效利用:容器比虚拟机更轻量,能在相同硬件上运行更多应用实例
- 弹性伸缩:根据负载自动调整实例数量,平衡性能与成本
- 故障自愈:自动检测并替换故障实例,提高系统可用性
- 简化部署流程:实现从代码提交到生产部署的自动化流水线
本文将带领你完成 Java 项目的容器化之旅,从基础的 Docker 镜像构建,到复杂的 Kubernetes 集群部署,再到高级的自动伸缩和故障自愈配置,全方位掌握云原生部署技术栈。
一、Docker 容器化基础:从理论到实践
1.1 Docker 核心概念与工作原理
Docker 采用了操作系统级虚拟化技术,通过隔离用户空间实现应用的独立运行。其核心组件包括:

- 镜像 (Image):包含运行应用所需的代码、运行时、库、环境变量和配置文件的不可变模板
- 容器 (Container):镜像的运行实例,是一个独立的可执行软件包
- Docker 引擎:创建和管理容器的核心软件,包括 Docker 守护进程和命令行客户端
- 仓库 (Registry):存储和分发 Docker 镜像的仓库,如 Docker Hub、阿里云容器仓库等
- 网络 (Network):实现容器间通信的网络模型
- 卷 (Volume):用于持久化存储容器数据的机制
Docker 的工作流程可概括为:
1.2 准备一个可容器化的 Java 项目
我们将以一个 Spring Boot 应用为例,演示容器化过程。首先创建项目结构并添加核心依赖:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>docker-k8s-demo</artifactId>
<version>1.0.0</version>
<name>docker-k8s-demo</name>
<description>A demo project for Docker and Kubernetes deployment</description>
<properties>
<java.version>17</java.version>
<lombok.version>1.18.30</lombok.version>
<springdoc.version>2.1.0</springdoc.version>
<fastjson2.version>2.0.45</fastjson2.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Actuator (用于健康检查和监控) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- Swagger3 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- FastJSON2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
<!-- 构建分层jar,优化Docker镜像构建 -->
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
应用主类
package com.example.dockerk8sdemo;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
/**
* 应用主类
*
* @author ken
*/
@SpringBootApplication
@MapperScan("com.example.dockerk8sdemo.mapper")
@OpenAPIDefinition(info = @Info(title = "Docker-K8s Demo API", version = "1.0", description = "演示API"))
@Slf4j
public class DockerK8sDemoApplication {
public static void main(String[] args) {
SpringApplication.run(DockerK8sDemoApplication.class, args);
}
/**
* 应用启动完成事件
*/
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
log.info("Application started successfully");
// 打印环境信息,用于验证容器环境
String javaVersion = System.getProperty("java.version");
String userName = System.getenv("USER_NAME");
log.info("Running with Java version: {}", javaVersion);
log.info("USER_NAME environment variable: {}", userName);
}
}
配置文件
# application.yml
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE:default}
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo}?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:root}
driver-class-name: com.mysql.cj.jdbc.Driver
# 应用配置
server:
port: ${PORT:8080}
servlet:
context-path: /api
# Actuator配置(暴露健康检查和监控端点)
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true
metrics:
export:
prometheus:
enabled: true
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
db-config:
id-type: auto
健康检查控制器
package com.example.dockerk8sdemo.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* 健康检查和演示控制器
*
* @author ken
*/
@RestController
@RequestMapping("/health")
@RequiredArgsConstructor
@Tag(name = "健康检查", description = "应用健康状态检查接口")
@Slf4j
public class HealthController implements HealthIndicator {
private final Random random = new Random();
/**
* 自定义健康检查端点
*/
@Override
public Health health() {
// 实际应用中可以检查数据库连接、缓存等资源
int errorCode = check();
if (errorCode != 0) {
return Health.down()
.withDetail("Error Code", errorCode)
.withDetail("Message", "Some resources are not available")
.build();
}
return Health.up()
.withDetail("Message", "Application is running normally")
.withDetail("Version", "1.0.0")
.build();
}
/**
* 简单的健康检查逻辑
*/
private int check() {
// 模拟健康检查,99%概率返回健康
return random.nextInt(100) < 99 ? 0 : 1;
}
/**
* 测试接口
*/
@GetMapping("/test")
@Operation(summary = "测试接口", description = "用于验证服务是否正常运行")
public Map<String, Object> test() {
Map<String, Object> result = new HashMap<>(4);
result.put("status", "success");
result.put("message", "Service is running");
result.put("instance", System.getenv("HOSTNAME")); // 在容器中会显示容器ID
result.put("timestamp", System.currentTimeMillis());
log.info("Test endpoint called");
return result;
}
/**
* 模拟负载的接口
*/
@GetMapping("/load/{seconds}")
@Operation(summary = "模拟负载", description = "用于测试自动伸缩功能")
public Map<String, Object> simulateLoad(
@Parameter(description = "负载持续时间(秒)") @PathVariable int seconds) {
long endTime = System.currentTimeMillis() + seconds * 1000L;
log.info("Simulating load for {} seconds", seconds);
// 消耗CPU
while (System.currentTimeMillis() < endTime) {
Math.sqrt(random.nextDouble());
}
Map<String, Object> result = new HashMap<>(3);
result.put("status", "success");
result.put("message", "Load simulation completed");
result.put("durationSeconds", seconds);
return result;
}
}
1.3 编写高效的 Dockerfile
Dockerfile 是构建 Docker 镜像的蓝图,一个优化的 Dockerfile 能显著减小镜像体积并提高构建速度。以下是针对 Java 应用的优化 Dockerfile:
# 多阶段构建:第一阶段编译打包
FROM maven:3.9.6-eclipse-temurin-17 AS builder
# 设置工作目录
WORKDIR /app
# 复制pom.xml和依赖文件,利用缓存
COPY pom.xml .
COPY src ./src
# 编译打包,跳过测试以加快构建
RUN mvn clean package -DskipTests
# 提取Spring Boot分层
RUN java -Djarmode=layertools -jar target/*.jar extract
# 第二阶段:运行环境
FROM eclipse-temurin:17-jre-alpine
# 添加非root用户,增强安全性
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 设置工作目录
WORKDIR /app
# 复制分层文件
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
# 切换到非root用户
USER appuser
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget -q --spider http://localhost:8080/api/health || exit 1
# 启动命令
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
# 元数据标签
LABEL maintainer="ken"
LABEL version="1.0.0"
LABEL description="Docker-K8s Demo Application"
这个 Dockerfile 采用了多项优化技术:
- 多阶段构建:将编译环境和运行环境分离,最终镜像只包含运行所需文件
- 分层构建:利用 Spring Boot 的分层功能,将不常变化的依赖与频繁变化的应用代码分离,提高缓存利用率
- 使用轻量基础镜像:采用 Alpine 版本的 JRE,比完整版小很多
- 非 root 用户运行:增强容器安全性,避免潜在的权限问题
- 健康检查:内置健康检查命令,便于容器平台监控应用状态
- 适当的元数据:添加标签信息,提高镜像可维护性
1.4 构建并测试 Docker 镜像
构建镜像命令:
# 构建镜像,指定标签
docker build -t docker-k8s-demo:1.0.0 .
# 查看构建的镜像
docker images | grep docker-k8s-demo
运行容器测试:
# 简单运行
docker run -d -p 8080:8080 --name demo-app docker-k8s-demo:1.0.0
# 带环境变量运行
docker run -d \
-p 8080:8080 \
--name demo-app \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_HOST=mysql \
-e DB_USERNAME=root \
-e DB_PASSWORD=secret \
docker-k8s-demo:1.0.0
# 查看容器日志
docker logs -f demo-app
# 测试接口
curl http://localhost:8080/api/health/test
# 停止并删除容器
docker stop demo-app
docker rm demo-app
1.5 推送镜像到仓库
为了在 Kubernetes 集群中使用镜像,需要将其推送到容器仓库:
# 登录Docker Hub(如果使用其他仓库,如阿里云容器仓库,替换为相应地址)
docker login
# 标记镜像(格式:仓库地址/用户名/镜像名:标签)
docker tag docker-k8s-demo:1.0.0 yourusername/docker-k8s-demo:1.0.0
# 推送镜像
docker push yourusername/docker-k8s-demo:1.0.0
# 如果使用私有仓库
docker tag docker-k8s-demo:1.0.0 private-registry.example.com/demo/docker-k8s-demo:1.0.0
docker push private-registry.example.com/demo/docker-k8s-demo:1.0.0
二、Kubernetes 核心概念与集群搭建
2.1 Kubernetes 核心组件与架构
Kubernetes(简称 K8s)是一个开源的容器编排平台,能够自动化容器的部署、扩展和管理。其核心架构如下:

核心组件说明:
-
控制平面 (Control Plane):
- API Server:所有操作的统一入口,提供 RESTful API
- etcd:分布式键值存储,保存集群的所有状态
- Scheduler:负责 Pod 的调度,决定将 Pod 部署到哪个节点
- Controller Manager:运行各种控制器进程,如节点控制器、副本控制器等
- Cloud Controller Manager:与云服务提供商集成的控制器
-
节点 (Node):
- Kubelet:在每个节点上运行,确保容器按照 Pod 规范运行
- Kube-proxy:网络代理,维护节点网络规则
- 容器运行时:负责运行容器的软件,如 containerd
Kubernetes 的核心对象包括:
- Pod:最小部署单元,包含一个或多个容器
- Service:定义 Pod 的访问方式,提供固定访问点
- Deployment:管理 Pod 和 ReplicaSet,支持滚动更新
- StatefulSet:用于管理有状态应用
- ConfigMap/Secret:配置管理
- Namespace:提供资源隔离
- Ingress:管理外部访问
2.2 搭建本地 Kubernetes 集群
对于开发和测试,我们可以使用 Minikube 搭建本地单节点集群:
# 安装Minikube(Windows使用Chocolatey,macOS使用Homebrew)
# macOS:
brew install minikube
# 启动集群(指定容器运行时为containerd,内存2GB)
minikube start --driver=docker --container-runtime=containerd --memory=2048
# 检查集群状态
minikube status
# 安装kubectl(Kubernetes命令行工具)
# macOS:
brew install kubectl
# 验证kubectl配置
kubectl config view
# 查看节点信息
kubectl get nodes
# 查看集群组件
kubectl get pods -n kube-system
对于生产环境,建议使用 kubeadm 搭建多节点集群,或使用云服务商提供的托管 Kubernetes 服务(如 EKS、GKE、AKS、阿里云 ACK 等)。
2.3 kubectl 命令行工具基础
kubectl 是与 Kubernetes 集群交互的主要工具,常用命令:
# 查看资源
kubectl get pods # 查看所有Pod
kubectl get services # 查看所有Service
kubectl get deployments # 查看所有Deployment
kubectl get namespaces # 查看所有Namespace
# 查看详细信息
kubectl describe pod <pod-name>
kubectl describe deployment <deployment-name>
# 查看日志
kubectl logs <pod-name>
kubectl logs -f <pod-name> # 实时查看日志
# 执行命令
kubectl exec -it <pod-name> -- /bin/sh
# 创建资源
kubectl create -f <yaml-file>
# 应用配置(创建或更新资源)
kubectl apply -f <yaml-file>
# 删除资源
kubectl delete pod <pod-name>
kubectl delete -f <yaml-file>
# 查看集群信息
kubectl cluster-info
# 切换命名空间
kubectl config set-context --current --namespace=<namespace-name>
三、在 Kubernetes 部署 Java 应用
3.1 编写 Kubernetes 配置文件
Kubernetes 使用 YAML 或 JSON 格式的配置文件定义资源。我们需要创建以下配置文件来部署 Java 应用:
1. 命名空间配置(namespace.yaml)
apiVersion: v1
kind: Namespace
metadata:
name: demo-app
labels:
name: demo-app
2. 配置文件(configmap.yaml)
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-app-config
namespace: demo-app
data:
# 应用配置
SPRING_PROFILES_ACTIVE: "prod"
SERVER_PORT: "8080"
# 日志配置
LOG_LEVEL: "INFO"
LOG_PATTERN: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
3. 密钥配置(secret.yaml)
apiVersion: v1
kind: Secret
metadata:
name: demo-app-secret
namespace: demo-app
type: Opaque
data:
# 注意:这里的值需要Base64编码
DB_USERNAME: cm9vdA== # root的Base64编码
DB_PASSWORD: c2VjcmV0 # secret的Base64编码
生成 Base64 编码的命令:
echo -n "value" | base64
4. 部署配置(deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo-app
labels:
app: demo-app
spec:
# 初始副本数
replicas: 2
# 选择器,用于匹配Pod模板
selector:
matchLabels:
app: demo-app
# 策略配置
strategy:
# 滚动更新策略
rollingUpdate:
maxSurge: 1 # 可以超出期望副本数的最大数量
maxUnavailable: 0 # 更新过程中允许不可用的最大副本数
type: RollingUpdate
# Pod模板
template:
metadata:
labels:
app: demo-app
spec:
# 容器配置
containers:
- name: demo-app
# 镜像地址(替换为你的镜像仓库地址)
image: yourusername/docker-k8s-demo:1.0.0
imagePullPolicy: Always
# 端口配置
ports:
- containerPort: 8080
name: http
protocol: TCP
# 环境变量配置
env:
# 从ConfigMap获取环境变量
- name: SPRING_PROFILES_ACTIVE
valueFrom:
configMapKeyRef:
name: demo-app-config
key: SPRING_PROFILES_ACTIVE
- name: SERVER_PORT
valueFrom:
configMapKeyRef:
name: demo-app-config
key: SERVER_PORT
# 从Secret获取环境变量
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: demo-app-secret
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: demo-app-secret
key: DB_PASSWORD
# 直接指定环境变量
- name: DB_HOST
value: mysql-service
- name: DB_PORT
value: "3306"
- name: DB_NAME
value: demo
# 资源限制
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
# 健康检查
livenessProbe:
httpGet:
path: /api/health
port: 8080
initialDelaySeconds: 60 # 启动后多久开始检查
periodSeconds: 30 # 检查间隔
timeoutSeconds: 3 # 超时时间
failureThreshold: 3 # 失败多少次视为不健康
readinessProbe:
httpGet:
path: /api/health/test
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# 启动探针(确保应用完全启动)
startupProbe:
httpGet:
path: /api/health
port: 8080
failureThreshold: 30
periodSeconds: 10
5. 服务配置(service.yaml)
apiVersion: v1
kind: Service
metadata:
name: demo-app-service
namespace: demo-app
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
type: ClusterIP # 集群内部访问
6. ingress 配置(ingress.yaml)- 可选
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-app-ingress
namespace: demo-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: demo-app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-app-service
port:
number: 80
3.2 部署应用到 Kubernetes 集群
执行以下命令部署应用:
# 创建命名空间
kubectl apply -f namespace.yaml
# 创建配置和密钥
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
# 部署应用
kubectl apply -f deployment.yaml
# 创建服务
kubectl apply -f service.yaml
# (可选)创建Ingress
# 首先需要安装Ingress控制器:minikube addons enable ingress
kubectl apply -f ingress.yaml
# 查看部署状态
kubectl get deployments -n demo-app
kubectl get pods -n demo-app
kubectl get services -n demo-app
# 查看部署详情
kubectl describe deployment demo-app -n demo-app
# 查看Pod日志
kubectl logs -f <pod-name> -n demo-app
3.3 访问 Kubernetes 中的应用
有多种方式可以访问 Kubernetes 集群中的应用:
-
端口转发:
将本地端口8080转发到集群中的服务
kubectl port-forward -n demo-app service/demo-app-service 8080:80
现在可以通过http://localhost:8080访问应用
-
使用 Minikube 隧道(针对 NodePort 或 LoadBalancer 类型服务):
在另一个终端启动隧道
minikube tunnel
获取服务地址
kubectl get service demo-app-service -n demo-app
-
通过 Ingress 访问:
添加主机映射(Windows在C:\Windows\System32\drivers\etc\hosts,Linux/macOS在/etc/hosts)
echo "$(minikube ip) demo-app.local" | sudo tee -a /etc/hosts
访问应用
3.4 部署 MySQL 数据库
为了让我们的 Java 应用能够正常工作,需要部署一个 MySQL 数据库:
mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
namespace: demo-app
data:
MYSQL_DATABASE: "demo"
MYSQL_ROOT_HOST: "%"
mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
namespace: demo-app
type: Opaque
data:
MYSQL_ROOT_PASSWORD: c2VjcmV0 # secret的Base64编码
mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: demo-app
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.3.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
name: mysql-config
key: MYSQL_DATABASE
- name: MYSQL_ROOT_HOST
valueFrom:
configMapKeyRef:
name: mysql-config
key: MYSQL_ROOT_HOST
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: demo-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: demo-app
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
clusterIP: None # Headless Service
部署 MySQL:
kubectl apply -f mysql-configmap.yaml
kubectl apply -f mysql-secret.yaml
kubectl apply -f mysql-pvc.yaml
kubectl apply -f mysql-deployment.yaml
kubectl apply -f mysql-service.yaml
# 查看MySQL部署状态
kubectl get pods -n demo-app | grep mysql
四、实现服务的自动部署与滚动更新
4.1 CI/CD 流水线集成
实现自动部署的关键是建立 CI/CD 流水线。我们以 GitHub Actions 为例,演示如何配置自动构建 Docker 镜像并部署到 Kubernetes:
创建.github/workflows/deploy.yml 文件
name: Build and Deploy to Kubernetes
# 触发条件:main分支有push或pull request合并
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 检出代码
- uses: actions/checkout@v4
# 设置JDK 17
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
# 构建项目
- name: Build with Maven
run: mvn clean package -DskipTests
# 设置Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 登录到Docker Hub
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 构建并推送Docker镜像
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/docker-k8s-demo:latest,${{ secrets.DOCKERHUB_USERNAME }}/docker-k8s-demo:${{ github.sha }}
# 只有在main分支push时才部署到K8s
- name: Deploy to Kubernetes
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: steebchen/kubectl@v4
with:
config: ${{ secrets.KUBE_CONFIG_DATA }} # 存储在GitHub Secrets中的kubeconfig
command: apply -f k8s/
配置说明:
-
首先需要在 GitHub 仓库的 Secrets 中添加:
- DOCKERHUB_USERNAME:Docker Hub 用户名
- DOCKERHUB_TOKEN:Docker Hub 访问令牌
- KUBE_CONFIG_DATA:base64 编码的 kubeconfig 文件内容
-
生成 KUBE_CONFIG_DATA 的命令:
cat ~/.kube/config | base64
-
这个流水线会:
- 当代码推送到 main 分支或有 PR 合并到 main 分支时触发
- 构建 Java 项目
- 构建 Docker 镜像并推送到 Docker Hub
- 只有在 main 分支的 push 事件才会部署到 Kubernetes
4.2 Kubernetes 滚动更新策略
Kubernetes 的 Deployment 资源提供了滚动更新功能,确保应用更新过程中不中断服务。我们在前面的 deployment.yaml 中已经配置了滚动更新策略:
strategy:
rollingUpdate:
maxSurge: 1 # 可以超出期望副本数的最大数量(绝对值或百分比)
maxUnavailable: 0 # 更新过程中允许不可用的最大副本数
type: RollingUpdate
手动触发滚动更新的方法:
# 方法1:修改镜像版本
kubectl set image deployment/demo-app demo-app=yourusername/docker-k8s-demo:1.0.1 -n demo-app
# 方法2:使用kubectl apply更新配置
kubectl apply -f deployment.yaml
# 查看更新状态
kubectl rollout status deployment/demo-app -n demo-app
# 查看更新历史
kubectl rollout history deployment/demo-app -n demo-app
# 回滚到上一版本
kubectl rollout undo deployment/demo-app -n demo-app
# 回滚到指定版本
kubectl rollout undo deployment/demo-app --to-revision=2 -n demo-app
滚动更新的工作流程:
4.3 蓝绿部署与金丝雀发布
除了滚动更新,Kubernetes 还支持蓝绿部署和金丝雀发布等高级部署策略。
蓝绿部署:同时维护两个相同的环境(蓝环境和绿环境),新版本部署到非生产环境,测试通过后切换流量:
# 创建新版本的Deployment(绿环境)
kubectl apply -f deployment-v2.yaml
# 测试新版本
kubectl port-forward -n demo-app deployment/demo-app-v2 8081:8080
# 切换Service指向新版本
kubectl patch service demo-app-service -n demo-app -p '{"spec":{"selector":{"app":"demo-app-v2"}}}'
# 如果出现问题,切换回旧版本
kubectl patch service demo-app-service -n demo-app -p '{"spec":{"selector":{"app":"demo-app"}}}'
金丝雀发布:先将少量流量导向新版本,验证无误后逐步增加流量比例:
# 部署新版本,副本数较少
kubectl apply -f deployment-canary.yaml
# 通过Ingress控制流量分配(需要支持权重的Ingress控制器,如NGINX Ingress)
kubectl apply -f ingress-canary.yaml
ingress-canary.yaml 示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-app-primary
namespace: demo-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: demo-app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-app-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-app-canary
namespace: demo-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10%流量到金丝雀版本
spec:
rules:
- host: demo-app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-app-canary-service
port:
number: 80
五、实现服务的弹性伸缩
5.1 Kubernetes HPA(Horizontal Pod Autoscaler)
Horizontal Pod Autoscaler(HPA)可以根据 CPU 利用率、内存使用率或自定义指标自动调整 Pod 的数量。
1. 首先确保已部署 metrics-server:
# 安装metrics-server
minikube addons enable metrics-server
# 验证metrics-server是否运行
kubectl get pods -n kube-system | grep metrics-server
2. 创建 HPA 配置(hpa.yaml):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: demo-app-hpa
namespace: demo-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-app
minReplicas: 2 # 最小副本数
maxReplicas: 10 # 最大副本数
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU利用率目标值
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存利用率目标值
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 扩容稳定窗口
policies:
- type: Percent
value: 50 # 每次扩容50%
periodSeconds: 60 # 扩容间隔
scaleDown:
stabilizationWindowSeconds: 300 # 缩容稳定窗口(更长,避免频繁波动)
policies:
- type: Percent
value: 30 # 每次缩容30%
periodSeconds: 120 # 缩容间隔
3. 部署 HPA:
kubectl apply -f hpa.yaml
# 查看HPA状态
kubectl get hpa -n demo-app
kubectl describe hpa demo-app-hpa -n demo-app
5.2 基于自定义指标的弹性伸缩
除了 CPU 和内存,Kubernetes 还支持基于自定义指标的弹性伸缩,如请求数、队列长度等。
1. 部署 Prometheus 和 Prometheus Adapter:
# 添加Helm仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 安装Prometheus
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
2. 配置 Prometheus Adapter:创建 custom-metrics-config.yaml:
rules:
default: false
external:
- seriesQuery: 'http_server_requests_seconds_count{job!=""}'
resources:
overrides:
kubernetes_namespace:
resource: namespace
kubernetes_pod_name:
resource: pod
name:
matches: "^(.*)_count"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>)'
使用 Helm 安装 Prometheus Adapter:
helm install prometheus-adapter prometheus-community/prometheus-adapter \
-n monitoring \
--set configMap.create=true \
--set configMap.content=$(cat custom-metrics-config.yaml)
3. 创建基于自定义指标的 HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: demo-app-hpa-custom
namespace: demo-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: http_server_requests_seconds_per_second
target:
type: AverageValue
averageValue: "100" # 每个Pod平均每秒100个请求
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 30
periodSeconds: 120
5.3 测试弹性伸缩功能
使用以下方法测试弹性伸缩功能:
-
增加负载测试 CPU 触发扩容:
在一个终端中持续发送请求
while true; do curl http://localhost:8080/api/health/load/1; sleep 0.1; done
在另一个终端监控HPA和Pod
watch -n 5 "kubectl get hpa -n demo-app; echo; kubectl get pods -n demo-app"
-
观察扩容过程:当 CPU 利用率超过目标值(70%)时,HPA 会自动增加 Pod 数量。
-
停止负载测试观察缩容:
停止发送请求后,等待缩容稳定窗口(300秒)
watch -n 5 "kubectl get hpa -n demo-app; echo; kubectl get pods -n demo-app"
当负载降低后,HPA 会在稳定窗口后逐渐减少 Pod 数量到最小副本数。
六、实现服务的故障自愈
6.1 Kubernetes 的自愈机制
Kubernetes 提供了多种机制确保服务的高可用性和故障自愈:
- Pod 健康检查:通过 livenessProbe、readinessProbe 和 startupProbe 检测 Pod 状态
- 自动重启:当容器崩溃时,Kubelet 会根据 restartPolicy 自动重启容器
- 节点故障处理:当节点故障时,控制器会在健康节点上重新创建 Pod
- 副本集维护:ReplicaSet 控制器确保实际副本数与期望副本数一致
6.2 配置 Pod 的重启策略和健康检查
在 deployment.yaml 中配置重启策略和健康检查:
spec:
template:
spec:
restartPolicy: Always # 总是重启失败的容器
containers:
- name: demo-app
# ...其他配置...
# 存活探针:检测容器是否运行正常,失败则重启
livenessProbe:
httpGet:
path: /api/health
port: 8080
initialDelaySeconds: 60 # 应用启动时间较长,延迟开始检查
periodSeconds: 30 # 检查间隔
timeoutSeconds: 3 # 超时时间
failureThreshold: 3 # 连续3次失败视为不健康
# 就绪探针:检测容器是否可以接收请求,失败则从Service移除
readinessProbe:
httpGet:
path: /api/health/test
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
# 启动探针:确保应用完全启动
startupProbe:
httpGet:
path: /api/health
port: 8080
failureThreshold: 30 # 最多检查30次
periodSeconds: 10 # 每10秒检查一次
重启策略(restartPolicy)可选值:
- Always:总是重启失败的容器(默认值)
- OnFailure:只有当容器以非零退出码终止时才重启
- Never:从不重启容器
6.3 配置 Pod 的反亲和性与节点亲和性
为了提高服务的可用性,可以配置 Pod 的反亲和性,确保 Pod 分散在不同节点上:
spec:
template:
spec:
affinity:
# 反亲和性:避免同一应用的Pod调度到同一节点
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- demo-app
topologyKey: "kubernetes.io/hostname"
# 节点亲和性:优先调度到具有特定标签的节点
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: environment
operator: In
values:
- production
- weight: 50
preference:
matchExpressions:
- key: hardware
operator: In
values:
- high-performance
6.4 测试故障自愈功能
测试 Kubernetes 的故障自愈能力:
-
手动删除 Pod:
查看当前Pod
kubectl get pods -n demo-app
删除一个Pod
kubectl delete pod <pod-name> -n demo-app
观察是否会自动创建新的Pod
kubectl get pods -n demo-app
-
模拟容器崩溃:
进入容器
kubectl exec -it <pod-name> -n demo-app -- /bin/sh
在容器内杀死应用进程
ps aux | grep java
kill -9 <java-pid>退出容器,观察Pod状态
exit
kubectl get pods -n demo-app -
模拟节点故障(仅在多节点集群中可行):
标记节点不可调度
kubectl cordon <node-name>
排空节点
kubectl drain <node-name> --ignore-daemonsets
观察Pod是否迁移到其他节点
kubectl get pods -n demo-app -o wide
恢复节点
kubectl uncordon <node-name>
在以上测试中,Kubernetes 都会自动恢复 Pod 到期望状态,体现了其强大的故障自愈能力。
七、监控与日志管理
7.1 部署 Prometheus 和 Grafana 监控
使用 Helm 部署 Prometheus 和 Grafana:
# 添加Helm仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 创建命名空间
kubectl create namespace monitoring
# 安装kube-prometheus-stack
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--set grafana.service.type=NodePort \
--set prometheus.service.type=NodePort
# 查看部署状态
kubectl get pods -n monitoring
# 获取Grafana访问地址
minikube service prometheus-grafana -n monitoring --url
配置 Java 应用暴露 Prometheus 指标:
我们的 Spring Boot 应用已经添加了 actuator 和 prometheus 依赖,只需在 application.yml 中配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
在 Grafana 中导入 Spring Boot 应用监控面板(ID:12856),可以查看 JVM、内存、CPU、请求数等指标。
7.2 集中式日志管理
部署 ELK(Elasticsearch, Logstash, Kibana)栈或 EFK(Elasticsearch, Fluentd, Kibana)栈进行日志集中管理:
# 安装EFK
helm repo add elastic https://helm.elastic.co
helm repo update
# 安装Elasticsearch
helm install elasticsearch elastic/elasticsearch \
--namespace logging \
--create-namespace \
--set replicas=1 \
--set minimumMasterNodes=1 \
--set resources.requests.cpu=1 \
--set resources.requests.memory=2Gi \
--set resources.limits.cpu=1 \
--set resources.limits.memory=2Gi
# 安装Kibana
helm install kibana elastic/kibana --namespace logging
# 安装Fluentd
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install fluentd bitnami/fluentd --namespace logging \
--set elasticsearch.host=elasticsearch-master \
--set elasticsearch.port=9200
配置 Java 应用输出 JSON 格式日志,在 logback-spring.xml 中:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
<fieldNames>
<timestamp>timestamp</timestamp>
<message>message</message>
<logger>logger</logger>
<thread>thread</thread>
<level>level</level>
</fieldNames>
<timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
添加 logstash-logback-encoder 依赖:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
八、生产环境最佳实践与优化
8.1 资源限制与请求配置
为所有容器设置合理的资源限制和请求是生产环境的基本要求:
resources:
requests:
memory: "256Mi" # 容器需要的最小内存
cpu: "200m" # 容器需要的最小CPU(1000m = 1核)
limits:
memory: "512Mi" # 容器允许使用的最大内存
cpu: "500m" # 容器允许使用的最大CPU
设置资源限制的好处:
- 防止单个容器耗尽节点资源
- 帮助调度器做出更好的调度决策
- 提高集群资源利用率
- 避免节点级别的资源竞争
8.2 安全最佳实践
-
使用非 root 用户运行容器:在 Dockerfile 中创建并使用普通用户
-
限制容器权限:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"] -
使用 PodSecurityContext:
podSecurityContext:
seccompProfile:
type: RuntimeDefault -
配置网络策略:限制 Pod 间通信
-
定期更新基础镜像:修复已知漏洞
-
使用镜像拉取密钥:限制对私有仓库的访问
-
加密敏感数据:使用 Secret 存储敏感信息,并考虑使用 SealedSecrets 或 Vault
8.3 备份与灾难恢复
-
备份 etcd 数据:
创建备份
kubectl -n kube-system exec -it <etcd-pod-name> -- etcdctl
--endpoints=https://127.0.0.1:2379
--cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/apiserver-etcd-client.crt
--key=/etc/kubernetes/pki/apiserver-etcd-client.key
snapshot save /backup/etcd-snapshot.db复制备份到本地
kubectl cp -n kube-system <etcd-pod-name>:/backup/etcd-snapshot.db ./etcd-snapshot.db
-
使用 Velero 进行集群备份:
安装Velero
velero install
--provider aws
--plugins velero/velero-plugin-for-aws:v1.6.0
--bucket my-backup-bucket
--secret-file ./credentials-velero
--use-volume-snapshots=false
--backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio:9000创建备份
velero backup create demo-app-backup --include-namespaces demo-app
查看备份
velero backup get
恢复备份
velero restore create --from-backup demo-app-backup
-
多区域部署:对于关键业务,考虑跨区域部署以提高可用性
8.4 性能优化建议
-
使用适当的容器运行时:containerd 比 Docker 更轻量,性能更好
-
优化节点配置:
- 关闭 Swap
- 配置适当的内核参数
- 使用高性能网络插件(如 Calico)
-
优化 Pod 调度:
- 使用节点亲和性将 Pod 调度到合适的节点
- 使用 Pod 反亲和性避免单点故障
- 考虑使用 PriorityClass 确保关键 Pod 优先调度
-
使用本地存储:对于有状态应用,考虑使用本地 SSD 提高性能
-
配置资源自动伸缩:根据实际负载调整资源分配
-
使用 Service Mesh:如 Istio,提供更精细的流量控制和监控
九、总结与展望
通过容器化和 Kubernetes,我们不仅解决了传统部署的诸多痛点,更构建了一个弹性、可靠、可扩展的应用平台,为业务的快速迭代和持续创新提供了坚实基础。