对于绝大多数Java开发者而言,从传统的jar包部署、虚拟机运维,转向Kubernetes容器化编排,是绕不开的技术升级路径。但很多人对Kubernetes的认知停留在“复制粘贴yaml文件就能部署应用”的层面,对底层的部署流程、调度逻辑、运维原理一知半解,最终在生产环境踩中各种坑:JVM频繁OOM、Pod被无故误杀、发布时流量中断、资源利用率极低、故障排查无从下手。
一、Kubernetes核心架构与Java应用的适配逻辑
很多Java开发者刚接触Kubernetes时,会被一堆组件和资源对象劝退,其实只要抓住“Kubernetes是一个为容器化应用提供自动化部署、弹性伸缩、高可用保障的编排系统”这个核心,就能理清所有组件的作用——所有组件都是为了让你的Java应用更稳定、更高效、更易运维。
1.1 Kubernetes集群的核心架构
Kubernetes集群分为两大核心部分:控制平面(Control Plane) 和数据平面(Node),前者负责集群的全局管控和决策,后者负责真正运行你的Java应用容器。
1.1.1 控制平面核心组件
- kube-apiserver:整个集群的唯一入口,所有的操作(比如部署Java应用、扩容、查询Pod状态)都必须通过kube-apiserver的RESTful API完成,它同时负责认证、授权、准入控制,确保所有操作的合法性。你的Java应用的所有生命周期管理,本质上都是通过调用kube-apiserver的API实现的。
- etcd:集群的唯一数据存储,是一个强一致性的分布式键值数据库,集群中所有的资源对象(Deployment、Pod、Service等)的状态、配置信息都持久化存储在etcd中。你的Java应用的部署配置、副本数、Pod运行状态,最终都会同步到etcd中,它是集群的“大脑数据库”。
- kube-scheduler:集群的“调度器”,核心职责是为新创建的Java应用Pod,选择一个最合适的Node节点运行。它会执行一系列的过滤和打分规则,最终选出最优节点,这个过程就是Kubernetes的调度核心,后面会详细拆解。
- kube-controller-manager:集群的“控制器管理器”,运行了一系列的控制器,每个控制器都负责一个特定的自动化逻辑。比如Deployment控制器,会持续监控你的Java应用的Pod副本数,当有Pod故障退出时,会自动创建新的Pod,保证副本数和你配置的一致;还有StatefulSet控制器、EndpointSlice控制器等,所有的自动化能力,本质上都是控制器在驱动。
- cloud-controller-manager:集群的“云厂商适配层”,负责对接底层云厂商的基础设施,比如负载均衡、块存储、VPC网络等。当你的Java应用需要公网访问时,它会自动创建云厂商的负载均衡实例,把流量转发到集群内的Pod。
1.1.2 数据平面Node核心组件
每个运行应用的服务器(虚拟机/物理机)都是一个Node节点,上面运行三个核心组件:
- kubelet:Node节点的“代理”,和控制平面的kube-apiserver持续通信,负责管理本节点上的所有Pod。它会根据kube-apiserver下发的Pod配置,调用容器运行时拉取Java应用的镜像、启动/停止容器、执行健康检查、上报Pod的运行状态,是Java应用在节点上的“直接管理员”。
- kube-proxy:Node节点的“网络代理”,负责维护节点上的网络规则,实现Service的负载均衡能力。你的Java应用之间的集群内调用、外部流量的接入,本质上都是通过kube-proxy配置的iptables或ipvs规则实现的,它是集群网络的“流量管家”。
- 容器运行时(CRI):负责真正运行容器的组件,遵循CRI(容器运行时接口)规范,目前主流的是containerd。你的Java应用的镜像,最终会通过容器运行时,在节点上启动为容器,分配资源、隔离环境。
1.2 Java开发者必须掌握的核心资源对象
Kubernetes的所有操作,都是围绕资源对象展开的,对于Java应用开发和运维,你只需要掌握以下核心资源对象,就能覆盖99%的使用场景。
1.2.1 Pod
Pod是Kubernetes中最小的部署和调度单元,一个Pod对应一组共享网络、存储资源的容器,绝大多数场景下,一个Pod里只运行一个Java应用容器。 你可以把Pod理解为“逻辑主机”,里面的Java容器共享同一个IP地址、端口空间、存储卷,就像传统虚拟机里运行的Java进程一样。Pod是短暂的、临时的,故障后会被重建,IP地址会变化,所以绝对不能用Pod的IP来访问Java应用。
1.2.2 Deployment
Deployment是管理无状态Java应用的核心资源,它是对Pod的更高层封装,提供了副本管理、滚动更新、版本回滚、扩缩容等能力。 你不需要直接管理Pod,只需要在Deployment中定义Java应用的镜像、副本数、资源配置、健康检查等规则,Deployment会自动帮你创建和管理对应的Pod,保证应用的高可用。绝大多数Java微服务应用,都是用Deployment来部署的。
1.2.3 StatefulSet
StatefulSet是管理有状态Java应用的资源,比如MySQL、Redis、Kafka等中间件,或者需要固定标识、持久化存储的Java应用。 和Deployment的核心区别是:StatefulSet管理的Pod有固定的名称、固定的网络标识、稳定的持久化存储,重建后这些标识不会变化,而Deployment的Pod是无固定标识的。
1.2.4 Service
Service是解决Pod IP动态变化问题的核心资源,它为一组提供相同服务的Pod,提供一个固定的访问入口(Cluster IP),同时实现对Pod的负载均衡。 你可以把Service理解为Java应用的“负载均衡器”,不管后端的Pod怎么重建、IP怎么变化、副本数怎么增减,Service的访问地址永远不变,Java应用之间的调用,只需要访问Service的名称即可,不需要关心后端的Pod状态。
Service有4种核心类型,这里明确区分易混淆的点:
- ClusterIP:默认类型,分配一个集群内可访问的固定IP,只能在集群内部访问,适合Java微服务之间的内部调用。
- NodePort:在每个Node节点上开放一个固定的端口,通过节点IP+端口就能访问集群内的Java应用,适合测试环境,或者没有云厂商负载均衡的场景。
- LoadBalancer:对接云厂商的负载均衡服务,分配一个公网可访问的IP,把流量转发到后端的Pod,适合生产环境的公网Java应用接入。
- ExternalName:把集群内的Service映射到集群外的域名,比如Java应用需要访问外部的数据库,就可以用这个类型,不需要修改应用代码。
1.2.5 Ingress
Ingress是集群的“HTTP/HTTPS反向代理”,负责把集群外的HTTP/HTTPS流量,根据域名、路径转发到集群内的不同Service,实现七层路由能力。 你可以把它理解为Nginx反向代理,一个Ingress资源就能管理多个Java应用的公网访问,不需要为每个应用都创建一个LoadBalancer类型的Service,节省成本,同时提供SSL终止、路径重写、限流、灰度发布等能力。
1.2.6 ConfigMap & Secret
- ConfigMap:用来存储Java应用的非敏感配置信息,比如application.properties配置文件、环境变量、启动参数等,实现配置和代码的分离,修改配置不需要重新打包镜像。
- Secret:用来存储Java应用的敏感信息,比如数据库密码、API密钥、SSL证书等,数据是base64编码的,和ConfigMap的使用方式类似,但是针对敏感场景做了权限控制。
1.2.7 PersistentVolume(PV) & PersistentVolumeClaim(PVC)
这两个是Kubernetes的持久化存储资源,PV是集群中的一块存储资源,PVC是用户对存储的申请,Java应用需要持久化存储数据时(比如日志、附件、数据库数据),只需要声明PVC,Kubernetes会自动为它绑定合适的PV,实现存储和应用的解耦。
二、Java应用的Kubernetes部署原理与全流程实战
理解了核心架构和资源对象,我们就可以从0到1完成一个Java应用的容器化和Kubernetes部署,同时拆解整个部署流程的底层原理,让你不仅知道“怎么做”,还知道“为什么这么做”。
2.1 Java应用容器化的核心逻辑
Java应用的容器化,和其他语言有一个核心的区别:JVM的资源管理和容器的Cgroup资源隔离机制的适配。 传统的JVM会默认读取宿主机的CPU和内存资源,来设置默认的堆内存、GC线程数等参数,但是在容器环境中,宿主机的资源和容器分配的资源是不一样的,如果JVM不能识别容器的Cgroup限制,就会出现:容器设置了1G的内存限制,JVM默认设置了宿主机1/4的内存(比如宿主机32G,JVM堆内存设置了8G),最终导致容器被OOM kill,这是Java开发者最常踩的坑之一。 从JDK 8u191、JDK 10开始,JVM已经默认开启了UseContainerSupport参数,能够自动识别容器的Cgroup内存和CPU限制,自动设置合理的堆内存、GC线程数等参数,目前的JDK LTS版本都已经完美适配容器环境,不需要额外手动调整。
2.2 实战:从Java代码到Kubernetes部署全流程
我们以Spring Boot应用为例,完成从代码编写、容器化、镜像构建、Kubernetes部署的全流程,所有示例都可以直接使用。
2.2.1 步骤1:编写Spring Boot示例应用
首先创建一个Spring Boot应用,包含基础的REST接口、健康检查端点,用于后续的部署和验证。
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 https://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.4.4</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>k8s-java-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>k8s-java-demo</name>
<description>Kubernetes Java Demo Application</description>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
主启动类DemoApplication.java:
package com.example.k8sjavademo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
测试接口类TestController.java:
package com.example.k8sjavademo.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class TestController {
@Value("${spring.application.name:k8s-java-demo}")
private String applicationName;
@Value("${server.port:8080}")
private String port;
@GetMapping("/hello")
public Map<String, Object> hello() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "Hello Kubernetes!");
result.put("applicationName", applicationName);
result.put("port", port);
result.put("timestamp", System.currentTimeMillis());
return result;
}
}
application.properties配置文件:
spring.application.name=k8s-java-demo
server.port=8080
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
2.2.2 步骤2:编写多阶段构建Dockerfile
为了减小镜像体积,同时保证构建环境和运行环境的分离,我们采用Docker多阶段构建,第一阶段用JDK镜像完成应用的编译打包,第二阶段用JRE镜像运行应用,避免把源码、maven依赖都打包到运行镜像中。
Dockerfile:
# 构建阶段:使用JDK镜像完成编译打包
FROM eclipse-temurin:25-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn clean package -DskipTests
# 运行阶段:使用JRE镜像运行应用
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
# 设置时区为东八区
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
# 复制构建好的jar包
COPY --from=builder /app/target/*.jar app.jar
# 非root用户运行,提升安全性
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 容器启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]
2.2.3 步骤3:镜像构建与推送
执行以下命令构建镜像,替换为你自己的镜像仓库地址:
# 构建镜像
docker build -t your-registry/k8s-java-demo:v1.0.0 .
# 推送镜像到仓库
docker push your-registry/k8s-java-demo:v1.0.0
2.2.4 步骤4:编写Kubernetes部署资源文件
我们需要编写4个核心资源文件:Deployment(部署应用)、Service(暴露服务)、ConfigMap(管理配置)、Ingress(公网访问),所有配置都符合生产环境规范。
configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: k8s-java-demo-config
data:
application.properties: |
spring.application.name=k8s-java-demo
server.port=8080
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-java-demo
labels:
app: k8s-java-demo
spec:
replicas: 3
selector:
matchLabels:
app: k8s-java-demo
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: k8s-java-demo
spec:
containers:
- name: k8s-java-demo
image: your-registry/k8s-java-demo:v1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
startupProbe:
httpGet:
path: /actuator/health
port: http
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
failureThreshold: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
failureThreshold: 3
periodSeconds: 10
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: k8s-java-demo-config
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: k8s-java-demo
topologyKey: kubernetes.io/hostname
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: k8s-java-demo-svc
labels:
app: k8s-java-demo
spec:
selector:
app: k8s-java-demo
ports:
- port: 80
targetPort: http
protocol: TCP
type: ClusterIP
ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: k8s-java-demo-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- demo.example.com
secretName: demo-tls-secret
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: k8s-java-demo-svc
port:
number: 80
2.2.5 步骤5:部署到Kubernetes集群并验证
执行以下命令,把所有资源部署到集群中:
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
部署完成后,执行以下命令验证部署状态:
# 查看Deployment状态
kubectl get deployment k8s-java-demo
# 查看Pod状态
kubectl get pods -l app=k8s-java-demo
# 查看Service状态
kubectl get service k8s-java-demo-svc
# 查看Ingress状态
kubectl get ingress k8s-java-demo-ingress
# 查看Pod日志,验证应用启动正常
kubectl logs -f <pod-name>
验证应用访问:
# 集群内通过Service访问
kubectl run curl-test --image=curlimages/curl -it --rm -- curl http://k8s-java-demo-svc/api/hello
# 公网通过域名访问
curl https://demo.example.com/api/hello
如果返回了正确的JSON结果,说明Java应用已经成功部署到Kubernetes集群中。
2.3 Java应用部署的全流程底层原理
很多人只知道执行kubectl apply就能部署应用,但不知道背后的完整流程,理解这个流程,能帮你快速定位部署过程中的各种故障。当你执行kubectl apply -f deployment.yaml时,背后会执行以下10个核心步骤:
我们把每个步骤的核心逻辑讲透:
- 请求入口阶段:kubectl会把yaml文件转换成JSON格式,发送给kube-apiserver,同时完成客户端的身份认证。
- API处理阶段:kube-apiserver会对请求进行认证(验证用户身份)、授权(验证用户是否有权限执行这个操作)、准入控制(验证资源配置是否符合规则,比如资源限制、标签规范等),所有校验通过后,才会进入下一步。
- 资源持久化阶段:kube-apiserver把Deployment资源对象持久化存储到etcd中,此时etcd中已经有了这个Deployment的完整配置。
- Deployment控制器处理阶段:kube-controller-manager中的Deployment控制器,通过kube-apiserver监听到Deployment资源的创建事件,根据Deployment的配置,生成对应的ReplicaSet资源,写入etcd。ReplicaSet是用来管理Pod副本的资源,Deployment的滚动更新、版本回滚,本质上都是通过管理不同的ReplicaSet实现的。
- ReplicaSet控制器处理阶段:ReplicaSet控制器监听到ReplicaSet的创建事件,根据配置的副本数,生成对应数量的Pod资源对象,写入etcd。此时生成的Pod,nodeName字段是空的,属于“未调度”状态。
- 调度阶段:kube-scheduler通过kube-apiserver监听到有未调度的Pod,会执行完整的调度流程:先过滤掉不符合要求的Node节点,再对剩下的候选节点打分,最终选择得分最高的节点,把Pod的nodeName字段设置为这个节点的名称,更新Pod的状态,写入etcd。
- Pod绑定阶段:Pod已经被绑定到了具体的Node节点,接下来就是节点上的kubelet来处理。
- 容器启动阶段:对应Node的kubelet,通过kube-apiserver监听到有Pod被绑定到了自己的节点,会根据Pod的配置,调用容器运行时(CRI),拉取Java应用的镜像,创建容器的网络、存储资源,启动容器。
- 健康检查与状态上报阶段:kubelet会持续执行Pod中配置的启动探针、存活探针、就绪探针,根据探针的结果,更新Pod的状态(Running、Ready等),并把状态上报给kube-apiserver,写入etcd。
- 网络规则更新阶段:kube-proxy通过kube-apiserver监听到Pod和Service的状态变化,会更新节点上的iptables或ipvs规则,把Service的流量转发到健康的Pod上,实现负载均衡。
至此,整个Java应用的部署流程就完成了,你的应用已经可以正常接收和处理请求了。
三、Kubernetes调度底层原理与Java应用调度优化
上一部分我们提到,kube-scheduler负责为Java应用的Pod选择最合适的Node节点,这个调度过程是Kubernetes的核心能力之一,理解它的底层逻辑,你就能根据Java应用的特点,优化调度策略,提升应用的性能、稳定性和资源利用率。
3.1 kube-scheduler的核心调度流程
kube-scheduler的调度过程,分为三个核心阶段:调度队列阶段、过滤阶段、打分阶段,每个阶段都有明确的规则和逻辑,最终为Pod选出最优的运行节点。
我们把每个阶段的底层逻辑讲透:
3.1.1 调度队列阶段
所有新创建的未调度Pod,都会先进入调度队列,队列会根据Pod的优先级进行排序,高优先级的Pod会被优先调度。如果集群资源不足,高优先级的Pod还可以抢占低优先级Pod的资源,保证核心Java应用的正常运行。
3.1.2 过滤阶段(Predicate)
过滤阶段的目标是,把所有不符合Pod运行要求的Node节点过滤掉,剩下的节点就是“候选节点”。比如,一个Java应用的Pod申请了2核CPU、4G内存,那么所有剩余资源不足的节点,都会被过滤掉。
常用的核心过滤规则:
- NodeResourcesFit:检查Node节点的剩余可分配资源,是否满足Pod的requests配置,这是最基础的过滤规则。
- NodePorts:检查Pod需要的hostPort端口,是否在Node节点上已经被占用,避免端口冲突。
- NodeAffinity:检查Pod的节点亲和性规则,是否和Node节点的标签匹配,不符合的节点会被过滤。
- TaintToleration:检查Pod的容忍度,是否匹配Node节点的污点,没有对应容忍度的节点会被过滤。
- PodTopologySpreadConstraints:检查Pod的拓扑分布约束,是否符合配置的分布规则,不符合的节点会被过滤。
- VolumeBinding:检查Pod需要的PVC,是否能在Node节点所在的可用区绑定,有存储需求的Java应用会用到这个规则。
过滤完成后,如果候选节点的数量为0,说明没有节点能运行这个Pod,调度失败,Pod会进入不可调度队列,等待后续重试;如果有候选节点,就进入打分阶段。
3.1.3 打分阶段(Priority)
打分阶段的目标是,对过滤后的候选节点,按照一系列的打分规则,为每个节点计算一个0-100的得分,得分最高的节点,就是最终被选中的最优节点。
常用的核心打分规则:
- NodeResourcesBalancedAllocation:节点的CPU和内存利用率越均衡,得分越高,避免节点出现CPU用满、内存还有大量剩余,或者反过来的情况,提升资源利用率。
- NodeResourcesFit:节点的剩余资源和Pod的requests越匹配,得分越高,也就是“刚刚好”的节点得分更高,避免大节点跑小Pod,浪费资源。
- ImageLocality:节点上已经存在Pod需要的镜像,得分越高,Java应用的镜像一般比较大,这个规则可以减少镜像拉取的时间,加快Pod的启动速度。
- PodTopologySpreadConstraints:Pod在拓扑域中的分布越均匀,得分越高,保证Java应用的副本在不同节点、不同可用区均匀分布,提升高可用性。
- InterPodAffinity:Pod的亲和性规则匹配度越高,得分越高,比如把Java应用和它依赖的Redis调度到同一个节点,减少网络延迟。
打分完成后,得分最高的节点会被选中,kube-scheduler会执行预留校验,确认节点的资源足够,然后把Pod的nodeName字段设置为这个节点的名称,更新Pod的状态,写入etcd,完成整个调度流程。
3.2 Java应用的调度优化实战
理解了调度的底层逻辑,我们就可以根据Java应用的特点,针对性地优化调度策略,解决生产环境中的常见问题:比如所有Pod都调度到同一个节点,节点故障导致服务不可用;Java应用和高负载应用调度到同一个节点,导致性能下降;大镜像的Pod启动慢等问题。
下面是Java应用最常用的调度优化策略,每个都有示例。
3.2.1 合理设置资源requests,保证调度的准确性
Pod的resources.requests是调度阶段过滤规则的核心依据,如果你设置的requests太小,会导致Pod被调度到资源不足的节点,运行时出现性能问题;如果设置的太大,会导致集群资源利用率低,浪费成本。
对于Java应用,requests的设置原则:
- CPU requests:设置为Java应用平时运行的平均CPU使用率,比如平时用0.3核,就设置为300m,保证调度到有足够CPU资源的节点。
- 内存requests:设置为JVM的Xms(初始堆内存) + 非堆内存(元空间、直接内存等)的总和,比如Xms设置为512M,非堆内存预计128M,就设置为640Mi,保证节点有足够的内存运行应用。
示例:
resources:
requests:
cpu: 300m
memory: 640Mi
limits:
cpu: 1000m
memory: 1Gi
3.2.2 Pod反亲和性:避免单点故障,提升高可用
Java应用的多个副本,如果都调度到同一个Node节点,当这个节点故障时,所有副本都会宕机,服务完全不可用。通过Pod反亲和性配置,可以强制要求同一个应用的Pod,调度到不同的节点、不同的可用区,实现故障隔离。
示例1:强制要求Pod不能调度到同一个节点
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: k8s-java-demo
topologyKey: kubernetes.io/hostname
topologyKey是拓扑域的标识,这里用kubernetes.io/hostname,代表每个节点是一个拓扑域,强制要求同一个应用的Pod,不能在同一个拓扑域(同一个节点)中存在。
示例2:强制要求Pod分布在不同的可用区
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: k8s-java-demo
topologyKey: topology.kubernetes.io/zone
这里用topology.kubernetes.io/zone作为拓扑域,代表每个可用区是一个拓扑域,强制要求Pod分布在不同的可用区,即使整个可用区故障,其他可用区的Pod还能正常运行,实现跨可用区高可用。
3.2.3 Pod亲和性:降低网络延迟,提升性能
如果你的Java应用强依赖某个中间件(比如Redis、MySQL),可以通过Pod亲和性配置,把Java应用和它依赖的中间件调度到同一个节点、同一个可用区,减少网络延迟,提升接口性能。
示例:把Java应用和Redis调度到同一个节点
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: redis
topologyKey: kubernetes.io/hostname
这个配置会强制要求Java应用的Pod,必须调度到有标签app=redis的Pod所在的节点,减少Java应用和Redis之间的网络调用延迟。
3.2.4 节点亲和性:把Java应用调度到合适的节点
如果你的集群中有不同配置的节点,比如高性能的CPU节点、大内存节点、普通节点,你可以通过节点亲和性配置,把CPU密集型的Java应用调度到高性能CPU节点,把内存密集型的Java应用调度到大内存节点,提升应用性能。
示例:把Java应用调度到CPU型号为Intel Xeon的高性能节点
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-cpu-type
operator: In
values:
- intel-xeon
这个配置要求Java应用的Pod,必须调度到有标签node-cpu-type=intel-xeon的节点上,你需要提前给对应的节点打上这个标签。
3.2.5 拓扑分布约束:实现Pod的均匀分布
Pod反亲和性只能实现“不能在同一个拓扑域”,而拓扑分布约束(PodTopologySpreadConstraints)可以实现Pod在多个拓扑域中的均匀分布,比如你有10个节点,3个副本,拓扑分布约束会把3个副本分别调度到3个不同的节点,实现均匀分布,避免节点负载不均。
示例:保证Pod在节点和可用区均匀分布
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: k8s-java-demo
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: k8s-java-demo
maxSkew=1代表不同拓扑域中的Pod数量,最大差值不能超过1,实现完全均匀分布;whenUnsatisfiable=ScheduleAnyway代表如果不能满足均匀分布,还是要调度,不会阻塞调度流程。
3.2.6 污点与容忍:实现节点的专用隔离
如果你的集群中有一些专用的节点,专门用来运行核心Java应用,不想让其他应用占用这些节点的资源,可以给这些节点打上污点,只有配置了对应容忍度的Pod,才能调度到这些节点上,实现资源隔离。
首先给节点打上污点:
kubectl taint nodes node-01 app=core-java:NoSchedule
这个污点的效果是:没有对应容忍度的Pod,不能调度到这个节点上。
然后给Java应用的Pod配置容忍度:
tolerations:
- key: "app"
operator: "Equal"
value: "core-java"
effect: "NoSchedule"
配置了这个容忍度的Pod,就能调度到打上了对应污点的节点上,实现核心应用的专用节点隔离。
四、Java应用在Kubernetes中的运维全链路与核心能力
把Java应用部署到Kubernetes集群,只是第一步,生产环境中更重要的是应用的全生命周期运维:发布管理、弹性伸缩、监控告警、日志管理、故障排查、配置管理等,Kubernetes为这些运维场景提供了完整的能力支持,我们逐个拆解底层原理和实战用法。
4.1 Java应用的发布策略与实战
发布是Java应用运维中最高频的操作,发布策略的选择,直接决定了发布时服务的可用性、风险可控性。Kubernetes提供了多种发布策略,适配不同的场景,我们讲透最常用的4种发布策略的原理和实战示例。
4.1.1 滚动更新(RollingUpdate)
滚动更新是Deployment默认的发布策略,核心逻辑是:逐步用新版本的Pod替换旧版本的Pod,整个过程中,始终有可用的Pod在运行,保证服务不中断,是Java应用最常用的发布策略。
滚动更新的两个核心参数:
- maxSurge:发布过程中,最多可以超出预期副本数的Pod数量,可以是百分比,也可以是具体数值。比如副本数是3,maxSurge=1,那么发布过程中,最多可以有4个Pod同时运行。
- maxUnavailable:发布过程中,最多可以有多少个不可用的Pod,可以是百分比,也可以是具体数值。比如副本数是3,maxUnavailable=0,那么发布过程中,必须保证3个Pod始终可用,不允许有不可用的Pod。
示例:Java应用的零中断滚动更新配置
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
这个配置的效果是:发布时,先新增1个新版本的Pod,等待新版本Pod就绪后,再删除1个旧版本的Pod,循环这个过程,直到所有Pod都替换为新版本,整个过程中,始终有3个可用的Pod,服务完全不中断,适合核心Java应用的发布。
4.1.2 蓝绿发布
蓝绿发布的核心逻辑是:同时部署两套完整的环境,一套是当前正在运行的“蓝环境”(旧版本),一套是新部署的“绿环境”(新版本),新版本测试验证没问题后,把流量从蓝环境切换到绿环境,完成发布。如果新版本有问题,可以立即切换回蓝环境,实现零风险回滚。
蓝绿发布的实战步骤:
- 首先部署旧版本的蓝环境Deployment,名称为k8s-java-demo-blue,副本数3,标签app=k8s-java-demo, version=blue
- 部署Service,selector匹配app=k8s-java-demo, version=blue,所有流量都转发到蓝环境
- 部署新版本的绿环境Deployment,名称为k8s-java-demo-green,副本数3,标签app=k8s-java-demo, version=green
- 测试验证绿环境的新版本,确认没有问题
- 修改Service的selector,匹配app=k8s-java-demo, version=green,所有流量切换到绿环境,完成发布
- 如果新版本有问题,修改Service的selector切回blue,立即回滚
蓝绿发布的优点是发布和回滚速度快,零风险,缺点是需要占用两倍的资源,适合资源充足的核心Java应用。
4.1.3 金丝雀发布
金丝雀发布的核心逻辑是:先把一小部分流量切换到新版本,验证新版本的功能、性能、稳定性没有问题后,再逐步把全量流量切换到新版本,过程中如果发现问题,可以立即回滚,把风险控制在小范围内。
在Kubernetes中,我们可以通过Ingress-Nginx的canary注解,实现金丝雀发布,实战示例:
- 首先部署旧版本的Deployment,名称为k8s-java-demo-stable,副本数3,标签app=k8s-java-demo, version=stable
- 部署稳定版的Service和Ingress,所有流量都转发到stable版本
- 部署新版本的Deployment,名称为k8s-java-demo-canary,副本数1,标签app=k8s-java-demo, version=canary
- 部署金丝雀版本的Service,名称为k8s-java-demo-canary-svc,selector匹配version=canary
- 部署金丝雀Ingress,配置canary注解,把10%的流量转发到金丝雀版本:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: k8s-java-demo-canary-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: k8s-java-demo-canary-svc
port:
number: 80
- 验证10%流量的新版本没有问题后,逐步调整canary-weight的值,增加流量比例,直到100%,完成发布
- 如果新版本有问题,把canary-weight设置为0,立即回滚
金丝雀发布的优点是风险可控,逐步验证,适合生产环境的核心Java应用发布,是目前最主流的发布策略之一。
4.2 Java应用的弹性伸缩能力与实战
弹性伸缩是Kubernetes的核心优势之一,它可以根据应用的负载情况,自动调整Pod的副本数、资源配置,甚至集群的节点数量,既可以应对流量高峰,保证服务的稳定性,又可以在流量低峰时减少资源占用,降低成本。
对于Java应用,最常用的弹性伸缩能力是HPA(水平Pod自动伸缩),我们重点讲透它的底层原理和实战用法。
4.2.1 HPA的底层原理
HPA(Horizontal Pod Autoscaler)的核心逻辑是:持续采集Pod的监控指标,和你设置的目标值进行对比,自动计算出期望的Pod副本数,调整Deployment的replicas字段,实现Pod副本数的自动扩缩容。
HPA的核心流程:
- metrics-server持续采集集群中所有Pod的CPU、内存、自定义指标数据
- HPA控制器通过metrics API获取Pod的当前指标数据
- HPA控制器根据公式:期望副本数 = ceil(当前副本数 * (当前指标值 / 目标指标值)),计算出期望的副本数
- HPA控制器调整Deployment的replicas字段,更新副本数
- Deployment控制器根据新的replicas字段,自动创建或删除Pod,完成扩缩容
HPA支持多种指标类型:
- 资源指标:CPU利用率、内存利用率,最常用的指标
- 自定义指标:比如Java应用的QPS、接口平均响应时间、JVM堆内存使用率等业务指标
- 外部指标:比如消息队列的堆积长度、网关的QPS等外部系统的指标
4.2.2 HPA实战示例
首先,你的集群中必须已经安装了metrics-server,用来采集Pod的资源指标。
示例1:基于CPU利用率的HPA,自动扩缩容Java应用
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: k8s-java-demo-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: k8s-java-demo
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 120
这个配置的核心说明:
- 扩缩容的目标是Deployment k8s-java-demo
- 最小副本数3,最大副本数10,避免无限扩容或者缩容到0
- 目标CPU利用率是60%,当所有Pod的平均CPU利用率超过60%时,自动扩容,低于60%时,自动缩容
- 扩容行为:扩容的稳定窗口是60秒,避免指标波动导致的频繁扩容;每分钟最多扩容50%的副本数
- 缩容行为:缩容的稳定窗口是300秒,避免频繁缩容;每2分钟最多缩容20%的副本数,保证缩容过程的平稳
示例2:基于Java应用自定义指标的HPA 对于Java应用,CPU利用率有时候不能完全反映应用的负载情况,比如接口响应时间过长、QPS过高,这时候可以用自定义指标来做HPA。比如我们用Spring Boot Actuator暴露的QPS指标,实现基于QPS的自动扩缩容。
首先,Spring Boot应用需要添加Micrometer的Prometheus依赖,在pom.xml中添加:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
然后在application.properties中添加配置,暴露Prometheus指标端点:
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.tags.application=${spring.application.name}
启动应用后,访问/actuator/prometheus端点,就能看到Prometheus格式的指标数据,包括JVM的堆内存、非堆内存、GC次数、GC耗时、Spring MVC的接口QPS、响应时间、错误率等核心指标。
然后,配置Prometheus的ServiceMonitor,让Prometheus自动发现并采集Java应用的指标:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: k8s-java-demo-monitor
labels:
release: prometheus
spec:
selector:
matchLabels:
app: k8s-java-demo
endpoints:
- port: http
path: /actuator/prometheus
interval: 15s
namespaceSelector:
matchNames:
- default
配置完成后,Prometheus就会每隔15秒采集一次Java应用的指标数据,通过Prometheus Adapter把自定义指标暴露给Kubernetes的metrics API后,就可以配置HPA了:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: k8s-java-demo-hpa-custom
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: k8s-java-demo
minReplicas: 3
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: http_server_requests_seconds_count
target:
type: AverageValue
averageValue: 1000
这个配置的效果是:当每个Pod的平均QPS超过1000时,自动扩容,低于1000时,自动缩容,更贴合Java应用的实际业务负载。
4.3 Java应用的监控告警体系
生产环境中,监控告警是Java应用稳定性的核心保障,你需要同时监控Kubernetes集群的状态、Pod的运行状态、Java应用的业务指标、JVM的内部状态,才能及时发现和定位问题。
Kubernetes生态中,最主流的监控方案是Prometheus + Grafana,我们讲透Java应用的监控体系搭建和核心指标。
4.3.1 监控体系的核心架构
- Prometheus:时序数据库,负责采集和存储所有的监控指标,通过PromQL语句查询指标数据
- Grafana:可视化面板,把Prometheus的指标数据转换成直观的图表,做数据可视化
- Node Exporter:采集Node节点的主机指标,比如CPU、内存、磁盘、网络使用率
- kube-state-metrics:采集Kubernetes集群的资源状态指标,比如Deployment的副本数、Pod的运行状态、HPA的扩缩容情况
- Micrometer:Java应用的指标埋点库,Spring Boot默认集成,用来暴露JVM指标、Spring MVC接口指标、业务自定义指标,格式兼容Prometheus
- Alertmanager:告警管理组件,接收Prometheus的告警规则,做告警的去重、分组、路由,发送到邮件、钉钉、企业微信等渠道
4.3.2 核心告警规则
对于Java应用,你需要配置以下核心告警规则,及时发现问题:
- Pod不可用告警:Deployment的可用副本数低于预期,持续1分钟
- 容器OOM告警:容器因为OOM被kill,立即告警
- CPU利用率过高告警:Pod的CPU利用率持续5分钟超过80%
- 内存利用率过高告警:Pod的内存利用率持续5分钟超过80%
- JVM Full GC频繁告警:JVM每分钟Full GC次数超过2次,持续3分钟
- 接口错误率过高告警:接口5xx错误率超过5%,持续2分钟
- 接口响应时间过长告警:接口99分位响应时间超过1秒,持续3分钟
4.4 Java应用的故障排查思路与常用命令
生产环境中,Java应用在Kubernetes中会出现各种故障,掌握正确的故障排查思路和常用命令,能帮你快速定位和解决问题。
4.4.1 常见故障与排查思路
- Pod启动失败,状态为Pending原因:调度失败,没有符合要求的Node节点,比如资源不足、污点不匹配、亲和性不满足、PVC绑定失败等。 排查命令:
kubectl describe pod <pod-name>,查看Events事件,里面会明确显示调度失败的原因。 - Pod启动失败,状态为ImagePullBackOff原因:镜像拉取失败,比如镜像地址错误、镜像仓库无法访问、镜像标签不存在、私有镜像仓库的认证信息没有配置。 排查命令:
kubectl describe pod <pod-name>,查看Events中的镜像拉取错误信息;kubectl get secrets,检查是否配置了镜像仓库的拉取密钥。 - Pod启动后不断重启,状态为CrashLoopBackOff原因:容器启动后进程异常退出,比如Java应用启动失败、端口冲突、配置错误、JVM OOM退出、健康检查失败等。 排查命令:
kubectl logs <pod-name> --previous,查看上一次启动的日志,找到应用启动失败的原因;kubectl describe pod <pod-name>,查看容器的退出码和Events事件。 - Pod状态为Running,但是服务访问不通原因:就绪探针失败,Pod没有被加入Service的负载均衡;端口配置错误;网络策略限制了访问;Service的selector配置错误。 排查命令:
kubectl get endpoints <service-name>,查看Service对应的Endpoint列表,是否有Pod的IP;kubectl exec -it <pod-name> -- curl localhost:8080/api/hello,在容器内部访问接口,确认应用是否正常运行;kubectl describe service <service-name>,检查selector是否正确。 - Java应用频繁OOM,容器被kill原因:JVM的堆内存设置超过了容器的内存limits;Java应用存在内存泄漏;容器的内存limits设置太小,无法满足应用的需求。 排查命令:
kubectl logs <pod-name>,查看JVM的OOM错误日志;kubectl describe pod <pod-name>,查看容器是否因为OOM被kill;kubectl exec -it <pod-name> -- jcmd <pid> GC.heap_info,查看JVM的堆内存使用情况。
4.4.2 常用的kubectl命令
- 查看Pod列表:
kubectl get pods -o wide - 查看Pod的详细信息和事件:
kubectl describe pod <pod-name> - 查看Pod的日志:
kubectl logs -f <pod-name> - 进入容器内部:
kubectl exec -it <pod-name> -- /bin/sh - 查看Deployment的状态:
kubectl get deployment <deployment-name> -o wide - 查看集群的事件:
kubectl get events --sort-by='.lastTimestamp' - 查看Service的Endpoint:
kubectl get endpoints <service-name> - 查看节点的资源使用情况:
kubectl top nodes - 查看Pod的资源使用情况:
kubectl top pods
五、Java应用在Kubernetes中的高频坑点与最佳实践
最后,我们总结Java开发者在Kubernetes中部署应用最常踩的10个坑点,以及对应的最佳实践,帮你避开生产环境的雷区。
5.1 高频坑点与解决方案
5.1.1 JVM内存与容器Cgroup限制不匹配导致OOM
坑点:JVM的最大堆内存设置超过了容器的内存limits,或者JVM没有识别容器的Cgroup限制,导致容器被OOM kill。解决方案:
- 使用JDK 8u191以上的版本,默认开启UseContainerSupport,自动识别容器的Cgroup限制
- 容器的内存limits,至少要比JVM的Xmx大30%,给非堆内存、元空间、直接内存、Native内存留足空间
- 不要手动设置Xmx和Xms,让JVM自动根据容器的内存限制调整,或者设置Xmx为容器limits的70%
5.1.2 健康检查配置不合理导致Pod被误杀
坑点:Java应用启动慢,没有配置启动探针,存活探针的初始延迟太短,导致应用还没启动完成,就被存活探针判定为失败,不断重启。解决方案:
- 必须配置启动探针,给Java应用足够的启动时间,failureThreshold*periodSeconds要大于应用的最大启动时间
- 存活探针只检查应用是否正常运行,不要配置复杂的业务检查,避免误杀
- 就绪探针检查应用是否能接收流量,比如数据库连接、缓存连接是否正常,失败后从Service中摘除,不重启容器
5.1.3 容器以root用户运行,存在严重安全风险
坑点:Dockerfile中没有指定运行用户,容器默认以root用户运行,一旦应用出现漏洞,攻击者可以获取宿主机的root权限,风险极高。解决方案:
- 在Dockerfile中创建非root用户,用非root用户运行容器,示例:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
- 配置Pod的SecurityContext,限制容器的运行用户,禁止以root用户运行,示例:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
5.1.4 发布时没有优雅停机,导致请求中断
坑点:发布时,Kubernetes会给容器发送SIGTERM信号,Java应用没有处理这个信号,直接被杀死,正在处理的请求中断,出现业务错误。解决方案:
- 开启Spring Boot的优雅停机,在application.properties中配置:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
- Dockerfile中的ENTRYPOINT必须用exec格式,不要用shell格式,保证容器能正确接收SIGTERM信号
- 配置Pod的terminationGracePeriodSeconds,设置为30-60秒,给应用足够的时间处理完剩余的请求
5.1.5 没有配置Pod反亲和性,所有副本调度到同一个节点
坑点:同一个应用的所有Pod都调度到同一个Node节点,当这个节点故障时,所有副本都宕机,服务完全不可用。解决方案:
- 配置Pod反亲和性,强制要求Pod分布在不同的节点、不同的可用区,实现故障隔离
5.1.6 日志写文件,导致容器磁盘占用过高
坑点:Java应用的日志输出到容器内的文件,没有日志轮转,导致容器的磁盘占用越来越高,最终把节点的磁盘占满。解决方案:
- Java应用的日志直接输出到标准输出stdout和标准错误stderr,不要写文件
- 用专门的日志收集组件(比如Fluentd、Filebeat)采集容器的标准输出日志,存储到Elasticsearch等日志系统中
- 配置logback,输出JSON格式的日志,方便日志系统解析和检索
5.1.7 没有配置资源limits,导致应用占用节点所有资源
坑点:Deployment中没有配置resources.limits,Java应用出现死循环、内存泄漏时,会占用节点的所有CPU和内存资源,影响节点上的其他应用,甚至导致节点宕机。解决方案:
- 所有的Java应用容器,必须配置resources.requests和resources.limits,限制资源的使用
- CPU limits不要设置的太小,避免Java应用的GC线程、编译线程被限流,导致性能下降
- 内存limits要设置合理,既要避免OOM,又要避免浪费资源
5.1.8 容器时区不对,导致Java应用时间错误
坑点:容器的默认时区是UTC,比东八区晚8小时,导致Java应用的日志时间、业务时间错误,出现业务问题。解决方案:
- 在Dockerfile中设置时区为东八区,示例:
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
- 或者在Deployment中挂载宿主机的时区文件,实现时区同步
5.1.9 ConfigMap配置更新后,Java应用没有自动刷新
坑点:更新了ConfigMap中的配置,但是Java应用没有重新加载配置,还是用的旧配置,需要重启Pod才能生效。解决方案:
- 使用Spring Cloud Kubernetes组件,实现ConfigMap更新后,自动刷新应用的配置,不需要重启Pod
- 或者使用配置中心(比如Nacos、Apollo),替代ConfigMap,实现配置的动态刷新
5.1.10 Java镜像体积过大,导致Pod启动慢
坑点:Dockerfile没有用多阶段构建,把源码、maven依赖、JDK都打包到了运行镜像中,镜像体积达到1G以上,导致Pod启动时镜像拉取时间长,启动慢。解决方案:
- 使用Docker多阶段构建,构建阶段用JDK镜像,运行阶段用JRE镜像,只保留运行需要的文件
- 使用jlink裁剪JRE,只保留Java应用需要的模块,进一步减小镜像体积
- 选择Alpine版本的JDK/JRE镜像,比标准镜像体积小很多
5.2 核心最佳实践总结
- 配置分离:用ConfigMap和Secret管理配置和敏感信息,不要把配置写死在镜像中,实现配置和代码的分离。
- 资源管控:所有容器必须配置requests和limits,合理设置资源值,保证调度的准确性,避免资源争抢和浪费。
- 高可用保障:配置Pod反亲和性、拓扑分布约束,把Pod分布在不同的节点和可用区,避免单点故障。
- 健康检查:正确配置启动探针、存活探针、就绪探针,保证应用的生命周期管理正确,避免误杀和流量转发到不可用的Pod。
- 安全加固:用非root用户运行容器,配置SecurityContext,禁止特权容器,最小化容器的权限。
- 优雅启停:开启Spring Boot的优雅停机,正确处理系统信号,保证发布时请求不中断,数据不丢失。
- 可观测性:完善应用的日志、监控、链路追踪,暴露JVM和业务指标,配置合理的告警规则,实现问题的早发现、早定位。
- 发布策略:核心应用使用金丝雀发布或蓝绿发布,控制发布风险,保证发布过程的服务可用性。
- 弹性伸缩:配置HPA,根据应用的负载自动扩缩容,应对流量高峰,同时降低资源成本。
- 镜像优化:使用多阶段构建,减小镜像体积,提升Pod的启动速度,降低镜像拉取的带宽成本。
Kubernetes已经成为Java微服务应用的标准部署和运行平台,对于Java开发者而言,掌握Kubernetes的底层原理,不仅仅是会写yaml文件,更要理解背后的部署流程、调度逻辑、运维能力,才能在生产环境中稳定、高效地运行Java应用。