一准备镜像image

1.1 maven编译程序

root@rocky9-2 tmp]# cd /data
[root@rocky9-2 data]# cd java-app/
[root@rocky9-2 java-app]# mvn clean package -DskipTests
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.example:k8s-demo >------------------------
[INFO] Building k8s-demo 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[WARNING] The artifact mysql:mysql-connector-java:jar:8.0.33 has been relocated to com.mysql:mysql-connector-j:jar:8.0.33
[INFO] 
[INFO] --- maven-clean-plugin:3.2.0:clean (default-clean) @ k8s-demo ---
[INFO] Deleting /data/java-app/target
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ k8s-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ k8s-demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /data/java-app/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ k8s-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory /data/java-app/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.10.1:testCompile (default-testCompile) @ k8s-demo ---
[INFO] No sources to compile
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ k8s-demo ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ k8s-demo ---
[INFO] Building jar: /data/java-app/target/k8s-demo-1.0.0.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.7.18:repackage (repackage) @ k8s-demo ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.445 s
[INFO] Finished at: 2026-02-13T12:45:20+08:00
[INFO] ------------------------------------------------------------------------

1.2 docker构建

命令:

docker build -t k8s-demo-app:v2.0 .
root@rocky9-2 java-app]# docker build -t k8s-demo-app:v2.0 .
[+] Building 60.5s (8/8) FINISHED                                                                                                                            docker:default
 => [internal] load build definition from Dockerfile                                                                                                                   0.0s
 => => transferring dockerfile: 209B                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/openjdk:11-jre-slim                                                                                                59.9s
 => [internal] load .dockerignore                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                        0.0s
 => [1/3] FROM docker.io/library/openjdk:11-jre-slim@sha256:93af7df2308c5141a751c4830e6b6c5717db102b3b31f012ea29d842dc4f2b02                                           0.0s
 => [internal] load build context                                                                                                                                      0.3s
 => => transferring context: 21.23MB                                                                                                                                   0.3s
 => CACHED [2/3] WORKDIR /app                                                                                                                                          0.0s
 => [3/3] COPY target/*.jar app.jar                                                                                                                                    0.1s
 => exporting to image                                                                                                                                                 0.1s
 => => exporting layers                                                                                                                                                0.1s
 => => writing image sha256:3584deb1306899a00c7e56c5172c17fd8616eb6617873d5b5fdfba9ae6f2bd8e                                                                           0.0s
 => => naming to docker.io/library/k8s-demo-app:v2.0                                                                                                                   0.0s

这条命令是 Docker 最核心的操作之一:构建镜像。你看到的日志显示构建已经成功完成了。

简单来说,你刚刚把你的 Java 程序(target/*.jar)打包成了一个能在任何安装了容器环境的地方运行的“背囊”(镜像)。

1. 命令拆解:docker build -t k8s-demo-app:v2.0 .

  • docker build: 告诉 Docker 引擎,“照着 Dockerfile 里的说明书给我做一个镜像”。

  • -t k8s-demo-app:v2.0: -t 代表 tag(打标签)。

  • k8s-demo-app 是镜像的名字。

  • v2.0 是版本号。如果不写,默认会是 latest

  • .: 这个代表 上下文路径 (Context)。它告诉 Docker:“就在当前文件夹找 Dockerfile 和代码文件”。


2. 日志里的“细节”

  • [internal] load build context (21.23MB): ava jar 包和相关文件大小约为 21MB,已经成功传送给 Docker 守护进程。
  • CACHED [2/3] WORKDIR /app: 这一步显示了 缓存(CACHED)。说明之前构建过 v1.0 或者是类似的镜像,Docker 发现 /app 这个目录没变,直接用了旧的层,节省了时间。
  • [3/3] COPY target/*.jar app.jar: 这是关键一步,把本地编译好的 Java 包塞进了镜像里。
  • writing image sha256:3584...: 镜像生成了唯一的“身份证号”。

二 手动镜像分发

2.1 验证镜像

docker images | grep k8s-demo-app

检查存在后再导出

docker save plan-system:v1.0 -o /tmp/plan-system-v1.0.tar

检查大小

[root@rocky9-2 target]# ls -lh /tmp/plan-system-v1.0.tar
-rw-------. 1 root root 238M  216 12:44 /tmp/plan-system-v1.0.tar
[root@rocky9-2 target]# 

选择导进containd

ctr -n k8s.io images import /tmp/plan-system-v1.0.tar

[root@rocky9-2 target]# ctr -n k8s.io images import /tmp/plan-system-v1.0.tar
unpacking docker.io/library/plan-system:v1.0 (sha256:acec55989f9684280865ce699768c9cfc5fd207d75e8682c4f6625949ba79afe)...done

2.2 同步镜像

K8s 调度 Pod 时,如果调度到了 rocky9-1rocky9-3,它们本地没有这个镜像就会报错 ImagePullBackOff。你有两个选择:

root@rocky9-2 java-app]# ctr -n k8s.io images import /tmp/k8s-demo-app-v2.tar
unpacking docker.io/library/k8s-demo-app:v2.0 (sha256:065cdb9ce9121e4f2e52f625b05c0bec174a66184555d921e8118733a99db74a)...done
[root@rocky9-2 java-app]# scp /tmp/k8s-demo-app-v2.tar root@192.168.80.173:/tmp/
k8s-demo-app-v2.tar                                                                                                                       100%  237MB  55.9MB/s   00:04    
[root@rocky9-2 java-app]# ssh root@192.168.80.173 "ctr -n k8s.io images import /tmp/k8s-demo-app-v2.tar"
unpacking docker.io/library/k8s-demo-app:v2.0 (sha256:065cdb9ce9121e4f2e52f625b05c0bec174a66184555d921e8118733a99db74a)...done
[root@rocky9-2 java-app]# crictl images | grep k8s-demo
docker.io/library/k8s-demo-app                       v1.0                c07fedfedf5ed       249MB
docker.io/library/k8s-demo-app                       v2.0                d0bfc15264084       249MB

1. 为什么用 ctr -n k8s.io

因为 Kubernetes 运行时使用的是 containerd

  • ctr 是 containerd 的原生命令行工具。
  • -n k8s.io 是关键:Kubernetes 只会去名为 k8s.io 的命名空间(Namespace)下寻找镜像。如果你直接导入到默认空间,crictl 和 K8s 是看不见这个镜像的。

2. ctrcrictl 的配合

  • ctr 导入镜像(写操作)。
  • 你用 crictl 检查镜像(读操作)。
    crictl 是专门为 Kubernetes 设计的容器运行时接口(CRI)交互工具。
    v2.0 出现在 crictl images 的列表中,意味着 Kubernetes 已经准备好使用这个镜像了

3. 跨节点分发成功

通过 scp 将 237MB 的镜像包拷贝到 192.168.80.173 (rocky9-3),执行了导入。这样无论 Pod 漂移到哪台机器,都不会出现 ImagePullBackOff 错误。


三 更新

3.1 滚动更新

[root@rocky9-2 java-app]# kubectl set image deployment/k8s-demo-app k8s-demo-app=docker.io/library/k8s-demo-app:v2.0 -n java-web --record
Flag --record has been deprecated, --record will be removed in the future
[root@rocky9-2 java-app]# kubectl rollout status deployment/k8s-demo-app -n java-web
deployment "k8s-demo-app" successfully rolled out

3.2 滚动更新失败的排查

发现running的age还是这个48min,但是我这个pod应该是上一秒推上去更新的,所以说时间是不对的

[root@rocky9-2 java-app]# kubectl get pods -n java-web -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
k8s-demo-app-7666646bd4-fmh8z   1/1     Running   0          48m   10.244.1.9   rocky9-2   <none>           <none>
k8s-demo-app-7666646bd4-v8m8w   1/1     Running   0          49m   10.244.2.8   rocky9-3   <none>           <none>
tomcat-6c48f7bd6-s84xn          1/1     Running   0          12h   10.244.1.7   rocky9-2   <none>           <none>
tomcat-6c48f7bd6-xvkdq          1/1     Running   0          12h   10.244.2.5   rocky9-3   <none>           <none>

直接删除pod

[root@rocky9-2 java-app]# kubectl -n java-web delete pod -l app=k8s-demo-app
pod "k8s-demo-app-7666646bd4-fmh8z" deleted
pod "k8s-demo-app-7666646bd4-v8m8w" deleted
[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS   AGE
k8s-demo-app-7666646bd4-b62lw   0/1     Running   0          8s
k8s-demo-app-7666646bd4-tk4jk   0/1     Running   0          8s
tomcat-6c48f7bd6-s84xn          1/1     Running   0          12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0          12h

开始滚动更新了,但是也会重启,说明yaml文件写的有问题

[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS      AGE
k8s-demo-app-7666646bd4-b62lw   0/1     Running   1 (81s ago)   2m52s
k8s-demo-app-7666646bd4-tk4jk   0/1     Running   1 (81s ago)   2m52s
tomcat-6c48f7bd6-s84xn          1/1     Running   0             12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0             12h

删除也解决不了问题,直接分析日志

^C[root@rocky9-2 java-app]# kubectl -n java-web describe pod k8s-demo-app-7666646bd4-b62lw
Name:             k8s-demo-app-7666646bd4-b62lw
Namespace:        java-web
Priority:         0
Service Account:  default
Node:             rocky9-2/192.168.80.172
Start Time:       Fri, 13 Feb 2026 12:53:29 +0800
Labels:           app=k8s-demo-app
                  pod-template-hash=7666646bd4
                  version=v2.0
Annotations:      <none>
Status:           Running
IP:               10.244.1.10
IPs:
  IP:           10.244.1.10
Controlled By:  ReplicaSet/k8s-demo-app-7666646bd4
Containers:
  k8s-demo-app:
    Container ID:   containerd://144d7f1573c7e1ea72e2eaf271f16a89315f735cf5459f75efb61750d8354252
    Image:          docker.io/library/k8s-demo-app:v2.0
    Image ID:       sha256:d0bfc152640842377f449c421b65d6c07fbec712c53f8510651287da5e6af17a
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Fri, 13 Feb 2026 12:55:01 +0800
    Last State:     Terminated
      Reason:       Error
      Exit Code:    143
      Started:      Fri, 13 Feb 2026 12:53:31 +0800
      Finished:     Fri, 13 Feb 2026 12:55:00 +0800
    Ready:          False
    Restart Count:  1
    Limits:
      cpu:     500m
      memory:  512Mi
    Requests:
      cpu:        250m
      memory:     256Mi
    Liveness:     http-get http://:8080/health delay=60s timeout=1s period=10s #success=1 #failure=3
    Readiness:    http-get http://:8080/health delay=30s timeout=1s period=5s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-p6bpr (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             False 
  ContainersReady   False 
  PodScheduled      True 
Volumes:
  kube-api-access-p6bpr:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  3m                   default-scheduler  Successfully assigned java-web/k8s-demo-app-7666646bd4-b62lw to rocky9-2
  Warning  Unhealthy  2m13s                kubelet            Readiness probe failed: Get "http://10.244.1.10:8080/health": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
  Normal   Pulled     89s (x2 over 2m58s)  kubelet            Container image "docker.io/library/k8s-demo-app:v2.0" already present on machine
  Normal   Created    89s (x2 over 2m58s)  kubelet            Created container k8s-demo-app
  Warning  Unhealthy  89s (x10 over 2m9s)  kubelet            Readiness probe failed: HTTP probe failed with statuscode: 404
  Warning  Unhealthy  89s (x3 over 109s)   kubelet            Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing    89s                  kubelet            Container k8s-demo-app failed liveness probe, will be restarted
  Normal   Started    88s (x2 over 2m58s)  kubelet            Started container k8s-demo-app
  Warning  Unhealthy  49s (x4 over 2m24s)  kubelet            Readiness probe failed: Get "http://10.244.1.10:8080/health": dial tcp 10.244.1.10:8080: connect: connection refused

从这份 describe 日志中,我们可以像医生看 X 光片一样,一眼看出你的 v2.0 应用出问题了

最核心的结论是:新镜像 v2.0 虽然启动了,但它的健康检查(Liveness/Readiness Probe)失败了,导致容器不断重启且无法对外提供服务。

以下是详细的“病例分析”:

1. 致命错误:HTTP 404

  • 日志证据Liveness probe failed: HTTP probe failed with statuscode: 404
  • 分析:Kubelet 尝试访问 Pod 内部的 http://:8080/health 来检查它是否活着,但你的程序返回了 404 Not Found
  • 原因
  • 接口变了:v2.0 是不是把 /health 路径删除了,或者改成了 /actuator/health
  • 代码没写:v2.0 的代码里可能根本没有定义这个 API 接口。

2. 容器正在经历“无限重启”

  • 日志证据Restart Count: 1 并且 Reason: Error, Exit Code: 143
  • 分析:因为 Liveness Probe(存活探针)连续 3 次失败,Kubernetes 认为这个容器已经“坏死”,所以执行了 Killing 操作并尝试重启它。
  • Exit Code 143:这是典型的 SIGTERM 信号,表示 Kubernetes 正常通知容器退出。

3. 就绪检查失败(流量进不来)

  • 日志证据Ready: FalseReadiness probe failed: ... connect: connection refused
  • 分析:因为探测失败,Pod 被标记为 NotReady。这意味着即便你外面有 Service,流量也不会转发给这个 Pod,用户访问会报错。

4. 资源限制(Limits)

  • 日志证据cpu: 500m, memory: 512Mi
  • 分析:这是你设置的资源上限。如果你的 Java 应用启动瞬间需要消耗大量 CPU 或内存(超过 512Mi),也可能导致启动极慢,进而触发 context deadline exceeded(超时)。

四 更新yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8s-demo-app
  namespace: java-web
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: k8s-demo-app
  template:
    metadata:
      labels:
        app: k8s-demo-app
        version: v2.0
    spec:
      containers:
      - name: k8s-demo-app
        image: docker.io/library/k8s-demo-app:v2.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

        # ✅ 改成 TCP 探针
        livenessProbe:
          tcpSocket:
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10

        readinessProbe:
          tcpSocket:
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5

五 更新成功

root@rocky9-2 java-app]# kubectl apply -f k8s-demo-app-v2.yaml
deployment.apps/k8s-demo-app configured
[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS      AGE
k8s-demo-app-759779f4fb-4gtcc   0/1     Running   0             11s
k8s-demo-app-7666646bd4-b62lw   0/1     Running   5 (12s ago)   7m33s
k8s-demo-app-7666646bd4-tk4jk   0/1     Running   5 (2s ago)    7m33s
tomcat-6c48f7bd6-s84xn          1/1     Running   0             12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0             12h
^C[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS      AGE
k8s-demo-app-759779f4fb-4gtcc   0/1     Running   0             17s
k8s-demo-app-7666646bd4-b62lw   0/1     Running   5 (18s ago)   7m39s
k8s-demo-app-7666646bd4-tk4jk   0/1     Running   5 (8s ago)    7m39s
tomcat-6c48f7bd6-s84xn          1/1     Running   0             12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0             12h
^C[root@rocky9-2 java-app]# 
[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS      AGE
k8s-demo-app-759779f4fb-4gtcc   0/1     Running   0             28s
k8s-demo-app-7666646bd4-b62lw   0/1     Running   5 (29s ago)   7m50s
k8s-demo-app-7666646bd4-tk4jk   0/1     Running   5 (19s ago)   7m50s
tomcat-6c48f7bd6-s84xn          1/1     Running   0             12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0             12h
^[[A^C[root@rocky9-2 java-app]# 
[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS      AGE
k8s-demo-app-759779f4fb-4gtcc   0/1     Running   0             33s
k8s-demo-app-7666646bd4-b62lw   0/1     Running   5 (34s ago)   7m55s
k8s-demo-app-7666646bd4-tk4jk   0/1     Running   5 (24s ago)   7m55s
tomcat-6c48f7bd6-s84xn          1/1     Running   0             12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0             12h
k8s-demo-app-759779f4fb-4gtcc   1/1     Running   0             45s
k8s-demo-app-7666646bd4-tk4jk   0/1     Terminating   5 (36s ago)   8m7s
k8s-demo-app-759779f4fb-hf9k2   0/1     Pending       0             0s
k8s-demo-app-759779f4fb-hf9k2   0/1     Pending       0             0s
k8s-demo-app-759779f4fb-hf9k2   0/1     ContainerCreating   0             0s
k8s-demo-app-759779f4fb-hf9k2   0/1     Running             0             2s
k8s-demo-app-7666646bd4-tk4jk   0/1     Terminating         5             8m16s
k8s-demo-app-7666646bd4-tk4jk   0/1     Terminating         5             8m17s
k8s-demo-app-7666646bd4-tk4jk   0/1     Terminating         5             8m17s
k8s-demo-app-7666646bd4-tk4jk   0/1     Terminating         5             8m17s
k8s-demo-app-759779f4fb-hf9k2   1/1     Running             0             41s
k8s-demo-app-7666646bd4-b62lw   0/1     Terminating         5 (87s ago)   8m48s
k8s-demo-app-7666646bd4-b62lw   0/1     Terminating         5             8m48s
k8s-demo-app-7666646bd4-b62lw   0/1     Terminating         5             8m49s
k8s-demo-app-7666646bd4-b62lw   0/1     Terminating         5             8m49s
k8s-demo-app-7666646bd4-b62lw   0/1     Terminating         5             8m49s
^C[root@rocky9-2 java-app]# kubectl -n java-web get pods -w
NAME                            READY   STATUS    RESTARTS   AGE
k8s-demo-app-759779f4fb-4gtcc   1/1     Running   0          3m
k8s-demo-app-759779f4fb-hf9k2   1/1     Running   0          2m15s
tomcat-6c48f7bd6-s84xn          1/1     Running   0          12h
tomcat-6c48f7bd6-xvkdq          1/1     Running   0          12h

非常好,这一段 -w 输出已经说明问题被修复了,而且滚动升级是健康的 👍
我们来“复盘”你刚刚看到的全过程:


真实滚动升级过程

1️⃣ 新 ReplicaSet 创建

k8s-demo-app-759779f4fb-4gtcc   0/1  Running

2️⃣ 新 Pod 就绪

k8s-demo-app-759779f4fb-4gtcc   1/1  Running

3️⃣ 老 Pod 被优雅下线

k8s-demo-app-7666646bd4-tk4jk   Terminating
k8s-demo-app-7666646bd4-b62lw   Terminating

4️⃣ 补齐第 2 个新副本

k8s-demo-app-759779f4fb-hf9k2   1/1  Running

最终状态(几秒后)应该是:

kubectl -n java-web get pods

老的 7666646bd4-* 全部消失。


  • 探针误杀 → 定位 → 修复
  • 滚动发布不中断服务
  • 观察 Pod 生命周期状态流转
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐