从 0 到 1 掌控云原生部署:Java 项目的 Docker 容器化与 K8s 集群实战指南
引言:容器化部署为何成为 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=trueusername: ${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,prometheusendpoint:health:show-details: alwaysprobes:enabled: truemetrics:export:prometheus:enabled: true# MyBatis-Plus配置
mybatis-plus:mapper-locations: classpath*:mapper/**/*.xmlconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.slf4j.Slf4jImplglobal-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();/*** 自定义健康检查端点*/@Overridepublic 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")); // 在容器中会显示容器IDresult.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);// 消耗CPUwhile (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-applabels:name: demo-app
2. 配置文件(configmap.yaml)
apiVersion: v1
kind: ConfigMap
metadata:name: demo-app-confignamespace: 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-secretnamespace: 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-appnamespace: demo-applabels: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-appspec:# 容器配置containers:- name: demo-app# 镜像地址(替换为你的镜像仓库地址)image: yourusername/docker-k8s-demo:1.0.0imagePullPolicy: Always# 端口配置ports:- containerPort: 8080name: httpprotocol: TCP# 环境变量配置env:# 从ConfigMap获取环境变量- name: SPRING_PROFILES_ACTIVEvalueFrom:configMapKeyRef:name: demo-app-configkey: SPRING_PROFILES_ACTIVE- name: SERVER_PORTvalueFrom:configMapKeyRef:name: demo-app-configkey: SERVER_PORT# 从Secret获取环境变量- name: DB_USERNAMEvalueFrom:secretKeyRef:name: demo-app-secretkey: DB_USERNAME- name: DB_PASSWORDvalueFrom:secretKeyRef:name: demo-app-secretkey: DB_PASSWORD# 直接指定环境变量- name: DB_HOSTvalue: mysql-service- name: DB_PORTvalue: "3306"- name: DB_NAMEvalue: demo# 资源限制resources:requests:memory: "256Mi"cpu: "200m"limits:memory: "512Mi"cpu: "500m"# 健康检查livenessProbe:httpGet:path: /api/healthport: 8080initialDelaySeconds: 60 # 启动后多久开始检查periodSeconds: 30 # 检查间隔timeoutSeconds: 3 # 超时时间failureThreshold: 3 # 失败多少次视为不健康readinessProbe:httpGet:path: /api/health/testport: 8080initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 3failureThreshold: 3# 启动探针(确保应用完全启动)startupProbe:httpGet:path: /api/healthport: 8080failureThreshold: 30periodSeconds: 10
5. 服务配置(service.yaml)
apiVersion: v1
kind: Service
metadata:name: demo-app-servicenamespace: demo-app
spec:selector:app: demo-appports:- port: 80targetPort: 8080protocol: TCPname: httptype: ClusterIP # 集群内部访问
6. ingress 配置(ingress.yaml)- 可选
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: demo-app-ingressnamespace: demo-appannotations:nginx.ingress.kubernetes.io/rewrite-target: /nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:rules:- host: demo-app.localhttp:paths:- path: /pathType: Prefixbackend:service:name: demo-app-serviceport: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访问应用
curl http://localhost:8080/api/health/test
- 使用 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# 访问应用
curl http://demo-app.local/api/health/test
3.4 部署 MySQL 数据库
为了让我们的 Java 应用能够正常工作,需要部署一个 MySQL 数据库:
mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: mysql-confignamespace: demo-app
data:MYSQL_DATABASE: "demo"MYSQL_ROOT_HOST: "%"
mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:name: mysql-secretnamespace: demo-app
type: Opaque
data:MYSQL_ROOT_PASSWORD: c2VjcmV0 # secret的Base64编码
mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: mysqlnamespace: demo-app
spec:replicas: 1selector:matchLabels:app: mysqltemplate:metadata:labels:app: mysqlspec:containers:- name: mysqlimage: mysql:8.3.0ports:- containerPort: 3306env:- name: MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name: mysql-secretkey: MYSQL_ROOT_PASSWORD- name: MYSQL_DATABASEvalueFrom:configMapKeyRef:name: mysql-configkey: MYSQL_DATABASE- name: MYSQL_ROOT_HOSTvalueFrom:configMapKeyRef:name: mysql-configkey: MYSQL_ROOT_HOSTvolumeMounts:- name: mysql-datamountPath: /var/lib/mysqlresources:requests:memory: "256Mi"cpu: "200m"limits:memory: "512Mi"cpu: "500m"livenessProbe:exec:command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]initialDelaySeconds: 30periodSeconds: 10readinessProbe:exec:command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]initialDelaySeconds: 5periodSeconds: 5volumes:- name: mysql-datapersistentVolumeClaim:claimName: mysql-pvc
mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: mysql-pvcnamespace: demo-app
spec:accessModes:- ReadWriteOnceresources:requests:storage: 1Gi
mysql-service.yaml
apiVersion: v1
kind: Service
metadata:name: mysql-servicenamespace: demo-app
spec:selector:app: mysqlports:- port: 3306targetPort: 3306clusterIP: 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-lateststeps:# 检出代码- uses: actions/checkout@v4# 设置JDK 17- name: Set up JDK 17uses: actions/setup-java@v4with:java-version: '17'distribution: 'temurin'cache: maven# 构建项目- name: Build with Mavenrun: mvn clean package -DskipTests# 设置Docker Buildx- name: Set up Docker Buildxuses: docker/setup-buildx-action@v3# 登录到Docker Hub- name: Login to DockerHubuses: docker/login-action@v3with:username: ${{ secrets.DOCKERHUB_USERNAME }}password: ${{ secrets.DOCKERHUB_TOKEN }}# 构建并推送Docker镜像- name: Build and pushuses: docker/build-push-action@v5with:context: .push: truetags: ${{ secrets.DOCKERHUB_USERNAME }}/docker-k8s-demo:latest,${{ secrets.DOCKERHUB_USERNAME }}/docker-k8s-demo:${{ github.sha }}# 只有在main分支push时才部署到K8s- name: Deploy to Kubernetesif: github.ref == 'refs/heads/main' && github.event_name == 'push'uses: steebchen/kubectl@v4with:config: ${{ secrets.KUBE_CONFIG_DATA }} # 存储在GitHub Secrets中的kubeconfigcommand: 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-primarynamespace: demo-appannotations:nginx.ingress.kubernetes.io/rewrite-target: /nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:rules:- host: demo-app.localhttp:paths:- path: /pathType: Prefixbackend:service:name: demo-app-serviceport:number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: demo-app-canarynamespace: demo-appannotations: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.localhttp:paths:- path: /pathType: Prefixbackend:service:name: demo-app-canary-serviceport: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-hpanamespace: demo-app
spec:scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: demo-appminReplicas: 2 # 最小副本数maxReplicas: 10 # 最大副本数metrics:- type: Resourceresource:name: cputarget:type: UtilizationaverageUtilization: 70 # CPU利用率目标值- type: Resourceresource:name: memorytarget:type: UtilizationaverageUtilization: 80 # 内存利用率目标值behavior:scaleUp:stabilizationWindowSeconds: 60 # 扩容稳定窗口policies:- type: Percentvalue: 50 # 每次扩容50%periodSeconds: 60 # 扩容间隔scaleDown:stabilizationWindowSeconds: 300 # 缩容稳定窗口(更长,避免频繁波动)policies:- type: Percentvalue: 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: falseexternal:- seriesQuery: 'http_server_requests_seconds_count{job!=""}'resources:overrides:kubernetes_namespace:resource: namespacekubernetes_pod_name:resource: podname: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-customnamespace: demo-app
spec:scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: demo-appminReplicas: 2maxReplicas: 10metrics:- type: Podspods:metric:name: http_server_requests_seconds_per_secondtarget:type: AverageValueaverageValue: "100" # 每个Pod平均每秒100个请求behavior:scaleUp:stabilizationWindowSeconds: 30policies:- type: Percentvalue: 50periodSeconds: 60scaleDown:stabilizationWindowSeconds: 300policies:- type: Percentvalue: 30periodSeconds: 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/healthport: 8080initialDelaySeconds: 60 # 应用启动时间较长,延迟开始检查periodSeconds: 30 # 检查间隔timeoutSeconds: 3 # 超时时间failureThreshold: 3 # 连续3次失败视为不健康# 就绪探针:检测容器是否可以接收请求,失败则从Service移除readinessProbe:httpGet:path: /api/health/testport: 8080initialDelaySeconds: 30periodSeconds: 10timeoutSeconds: 3failureThreshold: 3# 启动探针:确保应用完全启动startupProbe:httpGet:path: /api/healthport: 8080failureThreshold: 30 # 最多检查30次periodSeconds: 10 # 每10秒检查一次
重启策略(restartPolicy)可选值:
- Always:总是重启失败的容器(默认值)
- OnFailure:只有当容器以非零退出码终止时才重启
- Never:从不重启容器
6.3 配置 Pod 的反亲和性与节点亲和性
为了提高服务的可用性,可以配置 Pod 的反亲和性,确保 Pod 分散在不同节点上:
spec:template:spec:affinity:# 反亲和性:避免同一应用的Pod调度到同一节点podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues:- demo-apptopologyKey: "kubernetes.io/hostname"# 节点亲和性:优先调度到具有特定标签的节点nodeAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 100preference:matchExpressions:- key: environmentoperator: Invalues:- production- weight: 50preference:matchExpressions:- key: hardwareoperator: Invalues:- 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,prometheusmetrics: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: truerunAsUser: 1000runAsGroup: 3000fsGroup: 2000allowPrivilegeEscalation: falsecapabilities: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,我们不仅解决了传统部署的诸多痛点,更构建了一个弹性、可靠、可扩展的应用平台,为业务的快速迭代和持续创新提供了坚实基础。