欢迎光临
我们一直在努力

Kubernetes - 从 Docker 到 K8s,容器技术的进阶与协同逻辑

在这里插入图片描述

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Kubernetes这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

  • Kubernetes — 从 Docker 到 K8s,容器技术的进阶与协同逻辑 🚀
    • 一、起点:Docker 是什么?为什么 Java 工程师必须懂它? 🐳
      • ✅ 示例:用 Dockerfile 构建一个 Spring Boot Web 应用
    • 二、跃迁:为什么 Docker 不足以支撑生产级微服务? 📉
    • 三、基石:Pod —— Kubernetes 的原子调度单元 🧱
      • ✅ 示例:Sidecar 模式注入日志采集与指标暴露
    • 四、规模化:Deployment —— 声明式副本控制器 🔄
      • ✅ 示例:定义一个高可用 Spring Boot Deployment
      • 🔁 滚动升级实战:从 v1.0 升级到 v1.1
    • 五、通信:Service —— 集群内服务发现与负载均衡 🌐
      • ✅ 示例:为 demo-app 创建 ClusterIP Service
    • 六、入口:Ingress —— 统一七层流量网关 🌐➡️📦
      • ✅ 示例:用 Nginx Ingress Controller 暴露 demo-app
    • 七、协同逻辑全景图:从 Docker 到 K8s 的分层抽象 🧩
    • 八、Java 工程师的 K8s 实战 Checklist ✅
    • 九、结语:容器技术的终局不是取代,而是升维 🌈

Kubernetes — 从 Docker 到 K8s,容器技术的进阶与协同逻辑 🚀

“Docker 让容器可运行,Kubernetes 让容器可信赖。” —— 这不是一句口号,而是一场分布式系统范式的静默革命 🔁

在微服务架构席卷全球的今天,Java 工程师早已不再满足于 java -jar app.jar 的单机部署时代。我们写完 Spring Boot 应用、打好 JAR 包、配置好 application.yml,却常卡在最后一公里:如何让服务在 3 台机器上自动负载均衡?如何在节点宕机时 5 秒内自愈?如何灰度发布新版本而不影响老用户?如何按 CPU 使用率动态扩缩容?

这些问题的答案,不在 Nginx 配置里,不在 Ansible 脚本中,也不在 Jenkins 流水线深处——它们统一指向一个名字:Kubernetes(简称 K8s) 🌐。

但 Kubernetes 并非凭空而降。它生长于 Docker 的土壤之上,承袭其“一次构建、处处运行”的哲学,又以声明式编排、自治控制平面和面向终态的设计,将容器技术推向工程化、生产级的新高度。本文将带你从 Java 开发者的视角出发,以真实可运行的代码为锚点,层层解构 Docker → Containerd → Pod → Deployment → Service → Ingress 的演进链条,揭示容器技术从“能跑”到“稳跑”、“智跑”的底层协同逻辑。全程无概念堆砌,只有可验证的逻辑、可调试的代码、可落地的 YAML 和可渲染的 Mermaid 图表 🧩。


一、起点:Docker 是什么?为什么 Java 工程师必须懂它? 🐳

Docker 不是虚拟机,也不是编程语言,而是一个标准化的软件交付单元封装与运行时平台。它通过 Linux 内核的 Namespaces(隔离) 和 Cgroups(限制) 技术,在宿主机上创建轻量、独立、可移植的运行环境——即容器(Container)。

对 Java 开发者而言,Docker 的核心价值在于:终结“在我机器上能跑”(It works on my machine)的千年魔咒 ✨。

设想这样一个场景:你的 Spring Boot 项目依赖 JDK 17 + MySQL 8.0.33 + Redis 7.2,本地开发用的是 macOS,测试环境是 CentOS 7,生产是 Ubuntu 22.04。每换一个环境,你都要手动安装 JDK、配置 JAVA_HOME、下载 MySQL RPM 包、处理 SELinux 策略……直到某天运维告诉你:“线上不能装 Oracle JDK,必须用 OpenJDK 17.0.2”。崩溃?不,是时候拥抱 Docker 了。

✅ 示例:用 Dockerfile 构建一个 Spring Boot Web 应用

我们从一个极简的 Spring Boot REST API 开始(基于 Spring Boot 3.2.x + Jakarta EE 9+):

// src/main/java/com/example/demo/HelloController.java
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@GetMapping("/api/hello")
public String sayHello() {
return "Hello from Spring Boot in Docker! 🐳✨";
}
}

对应的 pom.xml(关键片段):

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.7</version>
<relativePath/>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

构建完成后,生成 target/demo-0.0.1-SNAPSHOT.jar。接下来,编写 Dockerfile:

# 使用官方 OpenJDK 运行时作为基础镜像(多阶段构建推荐)
FROM registry.access.redhat.com/ubi8/openjdk-17:latest

# 设置工作目录
WORKDIR /app

# 复制 JAR 文件(注意:Spring Boot fat jar 自带嵌入式 Tomcat)
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar

# 暴露端口(仅声明,实际由 Spring Boot server.port 决定)
EXPOSE 8080

# 启动命令(使用 jvm args 优化容器内存感知)
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

💡 关键说明:

  • -XX:+UseContainerSupport 启用 JVM 对容器 cgroup 内存限制的感知(JDK 10+ 默认启用,但仍建议显式声明);
  • -XX:MaxRAMPercentage=75.0 让 JVM 自动根据容器内存 limit 分配堆大小,避免 OOMKill;
  • registry.access.redhat.com/ubi8/openjdk-17 是 Red Hat Universal Base Image,安全合规、生产就绪,官方文档 可查。

构建并运行:

# 构建镜像(tag 为 demo-app:1.0)
$ mvn clean package
$ docker build -t demo-app:1.0 .

# 启动容器(映射 8080→8080,后台运行)
$ docker run -d –name demo-ctr -p 8080:8080 demo-app:1.0

# 验证
$ curl http://localhost:8080/api/hello
# 输出:Hello from Spring Boot in Docker! 🐳✨

✅ 成功!你已将 Java 应用封装为不可变镜像(Immutable Image)——内容哈希固定、环境完全一致、启动秒级完成。

但请注意:这只是单机单容器。当业务增长,你需要:

  • 启动 3 个实例做负载均衡;
  • 某个实例 OOM 崩溃后自动重启;
  • 升级时先停 1 个、再启 1 个(滚动更新);
  • 所有实例共用同一个数据库连接串,且该串需安全注入;

👉 Docker CLI 或 docker-compose.yml 可以部分解决,但缺乏跨主机调度、健康探针、服务发现、策略驱动的自愈能力。这正是 Kubernetes 登场的时刻 ⏳。


二、跃迁:为什么 Docker 不足以支撑生产级微服务? 📉

Docker 是伟大的,但它本质是一个单机容器引擎(Container Runtime)。它的设计哲学是:“给我一个镜像,还你一个进程”。它不关心:

问题域Docker 的能力Kubernetes 的能力
多主机编排 ❌ 仅限本机 docker run ✅ 自动选择最优 Node 调度 Pod
服务发现 ❌ –link 已废弃,docker network 仅限单机 ✅ 内置 DNS(service-name.namespace.svc.cluster.local)
健康自愈 ✅ –restart=always(仅进程级) ✅ Liveness/Readiness 探针 + 控制器闭环
配置与密钥管理 ❌ docker run -e 明文暴露,–mount 无生命周期管理 ✅ ConfigMap / Secret 声明式挂载,热更新支持
滚动升级 & 回滚 ❌ 需手动 stop/rm/run ✅ kubectl rollout undo 一键回退

更关键的是:Docker daemon 本身已成为历史。自 Kubernetes v1.20 起,Dockershim 已被正式移除。K8s 不再直接调用 Docker Engine,而是通过 Container Runtime Interface (CRI) 与符合标准的运行时交互(如 containerd、CRI-O)。

这意味着: 🔹 你在 kubectl get nodes 看到的节点,底层可能运行的是 containerd(Docker Desktop 内置)、Podman(Red Hat)、或 CRI-O(OpenShift); 🔹 docker ps 看到的容器,只是 containerd 托管的其中一种 workload; 🔹 kubectl run nginx –image=nginx 创建的 Pod,其容器由 containerd 拉取、解压、运行,与 dockerd 无关。

所以,请放下执念:Kubernetes ≠ Docker 的上层封装,而是以容器为基本单元的分布式操作系统 🧠。它抽象出比“进程”更高一层的原语——Pod。


三、基石:Pod —— Kubernetes 的原子调度单元 🧱

在 K8s 中,最小、最不可分割的可调度对象是 Pod,而非容器。一个 Pod 封装了一个或多个紧密耦合、共享网络与存储的容器。它们总是被调度到同一台 Node 上,共享 IP、端口空间、IPC、UTS 和网络命名空间。

🌟 类比理解:

  • Docker 容器 ≈ Linux 进程(独立 PID namespace)
  • Kubernetes Pod ≈ 一个“逻辑主机”(共享 network namespace + volumes)

为什么 Java 微服务需要 Pod?看这个典型场景:

✅ 示例:Sidecar 模式注入日志采集与指标暴露

你的 Spring Boot 应用需要:

  • 输出结构化 JSON 日志(供 ELK 收集);
  • 暴露 /actuator/prometheus 指标(供 Prometheus 抓取);
  • 但你不希望在业务代码里集成 Logstash 或 Prometheus Client 的复杂配置。

解决方案:用 Sidecar 容器解耦关注点。

# pod-with-sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
name: springbootpod
labels:
app: demospringboot
spec:
containers:
# 主应用容器(Java)
name: app
image: demoapp:1.0
ports:
containerPort: 8080
env:
name: SPRING_PROFILES_ACTIVE
value: "prod"
# Readiness 探针:HTTP GET /actuator/health/readiness
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
# Liveness 探针:HTTP GET /actuator/health/liveness
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 15

# Sidecar:日志转发(Fluent Bit)
name: fluentbit
image: cr.fluentbit.io/fluent/fluentbit:2.2.0
volumeMounts:
name: varlog
mountPath: /var/log
name: fluentbitconfig
mountPath: /fluentbit/etc/
resources:
limits:
memory: "64Mi"
cpu: "100m"

# Sidecar:Prometheus Exporter(可选)
name: jmxexporter
image: bitnami/jmxexporter:0.18.0
args:
config=/config/jmxexporter.yaml
volumeMounts:
name: jmxconfig
mountPath: /config
ports:
containerPort: 5556

# 共享卷:业务容器写日志,Fluent Bit 读取
volumes:
name: varlog
emptyDir: {}
name: fluentbitconfig
configMap:
name: fluentbitconfigmap
name: jmxconfig
configMap:
name: jmxexporterconfig

# DNS 策略:启用集群内服务发现
dnsPolicy: ClusterFirst

这个 Pod 启动后,三个容器共享:

  • 同一个 IP 地址(kubectl get pod springboot-pod -o wide 查看);
  • 同一个 /var/log 目录(业务容器写日志到 /var/log/app.log,Fluent Bit 从中读取);
  • 同一个 127.0.0.1(app 容器可通过 http://localhost:5556/metrics 访问 JMX Exporter)。

这是 Docker 单容器永远无法实现的协作粒度。而这一切,由 K8s 的 Pod Controller(如 ReplicaSet) 自动保障:如果 Node 故障,整个 Pod 被驱逐,控制器会在另一台健康 Node 上重建它(含所有容器与卷)。

下面这张 Mermaid 图清晰展示了 Pod 内部容器间的协同关系:

#mermaid-svg-2DTwvN9lT6SwfC32{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2DTwvN9lT6SwfC32 .error-icon{fill:#552222;}#mermaid-svg-2DTwvN9lT6SwfC32 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2DTwvN9lT6SwfC32 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2DTwvN9lT6SwfC32 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2DTwvN9lT6SwfC32 .marker.cross{stroke:#333333;}#mermaid-svg-2DTwvN9lT6SwfC32 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2DTwvN9lT6SwfC32 p{margin:0;}#mermaid-svg-2DTwvN9lT6SwfC32 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 .cluster-label text{fill:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 .cluster-label span{color:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 .cluster-label span p{background-color:transparent;}#mermaid-svg-2DTwvN9lT6SwfC32 .label text,#mermaid-svg-2DTwvN9lT6SwfC32 span{fill:#333;color:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 .node rect,#mermaid-svg-2DTwvN9lT6SwfC32 .node circle,#mermaid-svg-2DTwvN9lT6SwfC32 .node ellipse,#mermaid-svg-2DTwvN9lT6SwfC32 .node polygon,#mermaid-svg-2DTwvN9lT6SwfC32 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2DTwvN9lT6SwfC32 .rough-node .label text,#mermaid-svg-2DTwvN9lT6SwfC32 .node .label text,#mermaid-svg-2DTwvN9lT6SwfC32 .image-shape .label,#mermaid-svg-2DTwvN9lT6SwfC32 .icon-shape .label{text-anchor:middle;}#mermaid-svg-2DTwvN9lT6SwfC32 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2DTwvN9lT6SwfC32 .rough-node .label,#mermaid-svg-2DTwvN9lT6SwfC32 .node .label,#mermaid-svg-2DTwvN9lT6SwfC32 .image-shape .label,#mermaid-svg-2DTwvN9lT6SwfC32 .icon-shape .label{text-align:center;}#mermaid-svg-2DTwvN9lT6SwfC32 .node.clickable{cursor:pointer;}#mermaid-svg-2DTwvN9lT6SwfC32 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2DTwvN9lT6SwfC32 .arrowheadPath{fill:#333333;}#mermaid-svg-2DTwvN9lT6SwfC32 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2DTwvN9lT6SwfC32 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2DTwvN9lT6SwfC32 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2DTwvN9lT6SwfC32 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2DTwvN9lT6SwfC32 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2DTwvN9lT6SwfC32 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2DTwvN9lT6SwfC32 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2DTwvN9lT6SwfC32 .cluster text{fill:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 .cluster span{color:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2DTwvN9lT6SwfC32 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2DTwvN9lT6SwfC32 rect.text{fill:none;stroke-width:0;}#mermaid-svg-2DTwvN9lT6SwfC32 .icon-shape,#mermaid-svg-2DTwvN9lT6SwfC32 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2DTwvN9lT6SwfC32 .icon-shape p,#mermaid-svg-2DTwvN9lT6SwfC32 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2DTwvN9lT6SwfC32 .icon-shape .label rect,#mermaid-svg-2DTwvN9lT6SwfC32 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2DTwvN9lT6SwfC32 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2DTwvN9lT6SwfC32 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2DTwvN9lT6SwfC32 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Pod: springboot-pod

Shared Network Namespace

Shared Volume /var/log

HTTP POST

HTTP GET

Container: app- Spring Boot- Port 8080

Container: fluent-bit- Reads /var/log- Exports to Loki

Container: jmx-exporter- Scrapes JVM metrics- Port 5556

Loki Logging

Prometheus

📌 注意:上述 fluent-bit 和 jmx-exporter 都是社区成熟镜像,无需自行构建。Fluent Bit 官网 提供详尽文档;JMX Exporter GitHub 仓库(虽不放 GitHub 地址,但此链接可正常访问并查看配置示例)。


四、规模化:Deployment —— 声明式副本控制器 🔄

Pod 是原子单元,但生产环境绝不允许只跑一个 Pod。你需要:

  • 至少 3 个副本抵御单点故障;
  • 升级时逐个替换,保证可用性 > 99.9%;
  • 版本回滚能力,应对上线事故。

这就是 Deployment 的使命:它是一个声明式控制器,负责确保任意时刻,集群中运行着指定数量、指定镜像版本的 Pod 副本。

✅ 示例:定义一个高可用 Spring Boot Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoappdeployment
labels:
app: demoapp
spec:
# 声明期望状态:3 个副本
replicas: 3

# Pod 模板(与前面 Pod YAML 的 spec.containers 部分一致)
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
spec:
containers:
name: app
image: demoapp:1.0 # ← 关键:镜像版本在此声明
ports:
containerPort: 8080
env:
name: SPRING_PROFILES_ACTIVE
value: "k8s"
name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: appconfig
key: databaseurl
name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: dbsecret
key: password
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 15
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10

# 安全上下文:非 root 用户运行(最佳实践)
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 2001

# 使用 ConfigMap 和 Secret 注入配置(解耦代码与环境)
volumes:
name: configvolume
configMap:
name: appconfig
name: secretvolume
secret:
secretName: dbsecret

配套的 ConfigMap 和 Secret:

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: appconfig
data:
database-url: "jdbc:postgresql://postgres-svc:5432/mydb"
redis-host: "redis-svc"

# secret.yaml(base64 编码后)
apiVersion: v1
kind: Secret
metadata:
name: dbsecret
type: Opaque
data:
password: cGFzc3dvcmQxMjM= # "password123" 的 base64

部署并验证:

$ kubectl apply -f configmap.yaml
$ kubectl apply -f secret.yaml
$ kubectl apply -f deployment.yaml

# 查看 Deployment 状态
$ kubectl get deploy
# NAME READY UP-TO-DATE AVAILABLE AGE
# demo-app-deployment 3/3 3 3 10s

# 查看底层 ReplicaSet 和 Pods
$ kubectl get rs,po -l app=demo-app
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/demo-app-deployment-7c8b9d5f4 3 3 3 20s
#
# NAME READY STATUS RESTARTS AGE
# pod/demo-app-deployment-7c8b9d5f4-2xq8w 1/1 Running 0 15s
# pod/demo-app-deployment-7c8b9d5f4-5vz9p 1/1 Running 0 15s
# pod/demo-app-deployment-7c8b9d5f4-wk7t4 1/1 Running 0 15s

🔁 滚动升级实战:从 v1.0 升级到 v1.1

假设你已构建新镜像 demo-app:1.1(含新功能),只需更新 Deployment 中的 image 字段:

# 方式1:直接 patch(推荐用于 CI/CD)
$ kubectl set image deploy/demo-app-deployment app=demo-app:1.1

# 方式2:修改 YAML 后 apply(适合 GitOps)
# 修改 deployment.yaml 中 image: demo-app:1.0 → demo-app:1.1
$ kubectl apply -f deployment.yaml

K8s 会自动执行滚动更新:

  • 创建新的 ReplicaSet(demo-app-deployment-8d9a2b1c3),启动 1 个新 Pod;
  • 等待新 Pod ReadinessProbe 成功(HTTP 200);
  • 删除 1 个旧 Pod;
  • 重复直到所有 3 个 Pod 均为新版本。

全过程零停机,且可通过 kubectl rollout status deploy/demo-app-deployment 实时跟踪。

若发现问题,立即回滚:

$ kubectl rollout undo deploy/demo-app-deployment
# 回滚到上一个 revision(v1.0)

这就是 声明式 API 的力量:你只说“我要 3 个 demo-app:1.1”,K8s 控制器负责计算差异、执行动作、报告状态——你无需写一行 Bash 脚本去 SSH 登录每台机器。


五、通信:Service —— 集群内服务发现与负载均衡 🌐

现在你有 3 个 demo-app Pod 在运行,每个都有独立 IP(如 10.244.1.12, 10.244.2.8, 10.244.1.15)。但这些 IP:

  • 由 K8s 动态分配,随时可能变化(Pod 重建);
  • 位于 Pod 网络,外部不可路由;
  • 无法被其他服务稳定引用。

解决方案:Service —— 一个抽象的、稳定的网络端点,为一组 Pod 提供统一 VIP(ClusterIP)和 DNS 名称。

✅ 示例:为 demo-app 创建 ClusterIP Service

# service.yaml
apiVersion: v1
kind: Service
metadata:
name: demoappsvc
labels:
app: demoapp
spec:
type: ClusterIP # 默认类型:仅集群内可访问
selector:
app: demoapp # 匹配 label 为 app=demo-app 的 Pod
ports:
protocol: TCP
port: 80 # Service 暴露的端口(集群内访问)
targetPort: 8080 # Pod 容器的实际端口
name: http

部署后,神奇的事情发生了:

$ kubectl apply -f service.yaml

# 查看 Service 信息
$ kubectl get svc demo-app-svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# demo-app-svc ClusterIP 10.96.123.45 <none> 80/TCP 10s

# 在任意 Pod 内访问(DNS 自动解析)
$ kubectl exec -it some-other-pod — curl http://demo-app-svc/api/hello
# Hello from Spring Boot in Docker! 🐳✨

# 或用 ClusterIP 直接访问
$ kubectl exec -it some-other-pod — curl http://10.96.123.45/api/hello

K8s 如何实现?答案是 iptables + kube-proxy(或新版 ipvs 模式)。kube-proxy 监听 Service 变更,为每个 Service 在 Node 上生成 iptables 规则,将发往 10.96.123.45:80 的流量,随机或轮询转发到后端某个 Pod 的 10.244.x.x:8080。

下图展示了 Service 的流量转发模型:

#mermaid-svg-xZ3acqn2iQ8tEO3T{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xZ3acqn2iQ8tEO3T .error-icon{fill:#552222;}#mermaid-svg-xZ3acqn2iQ8tEO3T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xZ3acqn2iQ8tEO3T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .marker.cross{stroke:#333333;}#mermaid-svg-xZ3acqn2iQ8tEO3T svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xZ3acqn2iQ8tEO3T p{margin:0;}#mermaid-svg-xZ3acqn2iQ8tEO3T .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .cluster-label text{fill:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .cluster-label span{color:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .cluster-label span p{background-color:transparent;}#mermaid-svg-xZ3acqn2iQ8tEO3T .label text,#mermaid-svg-xZ3acqn2iQ8tEO3T span{fill:#333;color:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .node rect,#mermaid-svg-xZ3acqn2iQ8tEO3T .node circle,#mermaid-svg-xZ3acqn2iQ8tEO3T .node ellipse,#mermaid-svg-xZ3acqn2iQ8tEO3T .node polygon,#mermaid-svg-xZ3acqn2iQ8tEO3T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .rough-node .label text,#mermaid-svg-xZ3acqn2iQ8tEO3T .node .label text,#mermaid-svg-xZ3acqn2iQ8tEO3T .image-shape .label,#mermaid-svg-xZ3acqn2iQ8tEO3T .icon-shape .label{text-anchor:middle;}#mermaid-svg-xZ3acqn2iQ8tEO3T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .rough-node .label,#mermaid-svg-xZ3acqn2iQ8tEO3T .node .label,#mermaid-svg-xZ3acqn2iQ8tEO3T .image-shape .label,#mermaid-svg-xZ3acqn2iQ8tEO3T .icon-shape .label{text-align:center;}#mermaid-svg-xZ3acqn2iQ8tEO3T .node.clickable{cursor:pointer;}#mermaid-svg-xZ3acqn2iQ8tEO3T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .arrowheadPath{fill:#333333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xZ3acqn2iQ8tEO3T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xZ3acqn2iQ8tEO3T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xZ3acqn2iQ8tEO3T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xZ3acqn2iQ8tEO3T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .cluster text{fill:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T .cluster span{color:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xZ3acqn2iQ8tEO3T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xZ3acqn2iQ8tEO3T rect.text{fill:none;stroke-width:0;}#mermaid-svg-xZ3acqn2iQ8tEO3T .icon-shape,#mermaid-svg-xZ3acqn2iQ8tEO3T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xZ3acqn2iQ8tEO3T .icon-shape p,#mermaid-svg-xZ3acqn2iQ8tEO3T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xZ3acqn2iQ8tEO3T .icon-shape .label rect,#mermaid-svg-xZ3acqn2iQ8tEO3T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xZ3acqn2iQ8tEO3T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xZ3acqn2iQ8tEO3T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xZ3acqn2iQ8tEO3T :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Kubernetes Cluster

Node 2

Node 1

iptables rules

iptables rules

iptables rules

curl http://demo-app-svc

Pod: demo-app-310.244.1.15:8080

Service: demo-app-svc10.96.123.45:80

Pod: demo-app-110.244.1.12:8080

Pod: demo-app-210.244.2.8:8080

Client Podin same cluster

💡 进阶提示:ClusterIP 仅限集群内。若需对外暴露,可选:

  • NodePort:在每个 Node 上开一个固定端口(如 30080),通过 http://node-ip:30080 访问;
  • LoadBalancer:云厂商自动创建 SLB(如 AWS ALB、阿里云 SLB),绑定弹性公网 IP;
  • Ingress:七层 HTTP/HTTPS 路由(下节详解)。

六、入口:Ingress —— 统一七层流量网关 🌐➡️📦

Service 解决了集群内部通信,但生产环境需要:

  • 用 https://api.mycompany.com 访问 Java 应用,而非 http://10.96.123.45;
  • 同一域名下,/api/* 路由到 demo-app-svc,/admin/* 路由到 admin-svc;
  • 自动 HTTPS(Let’s Encrypt 证书);
  • WAF、限流、灰度发布等高级能力。

这就轮到 Ingress 出场了。它是一个K8s 内置的 API 对象,定义集群外部 HTTP/HTTPS 流量如何路由到内部 Service。但注意:Ingress 本身不处理流量,它需要一个 Ingress Controller(如 Nginx、Traefik、ALB Controller)来实现。

✅ 示例:用 Nginx Ingress Controller 暴露 demo-app

首先,确保集群已安装 Nginx Ingress Controller(官方 Helm Chart 是最常用方式)。

然后,定义 Ingress 资源:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demoappingress
annotations:
# 启用 HTTPS 重定向
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# 启用 Let's Encrypt 自动签发(需配合 cert-manager)
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
# TLS 配置(若已有证书)
tls:
hosts:
api.mycompany.com
secretName: demoapptlssecret # 引用已创建的 TLS Secret

# 路由规则
rules:
host: api.mycompany.com
http:
paths:
path: /api/
pathType: Prefix
backend:
service:
name: demoappsvc # 目标 Service
port:
number: 80 # Service 端口
path: /actuator/
pathType: Prefix
backend:
service:
name: demoappsvc
port:
number: 80

创建 TLS Secret(假设有 tls.crt 和 tls.key):

$ kubectl create secret tls demo-app-tls-secret \\
–cert=tls.crt \\
–key=tls.key

部署后,外部用户即可通过 https://api.mycompany.com/api/hello 访问你的 Spring Boot 应用 🎉。

🔗 补充资源:

  • Nginx Ingress Controller 官方文档(权威、实时更新);
  • Cert-Manager 官网(自动化证书管理,与 Let’s Encrypt 无缝集成)。

七、协同逻辑全景图:从 Docker 到 K8s 的分层抽象 🧩

至此,我们已走过完整路径。现在,让我们站在高处,凝视这张贯穿全文的协同逻辑图——它不是技术栈罗列,而是职责边界与协作契约的可视化:

#mermaid-svg-rb03aADnAXxuHZvc{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rb03aADnAXxuHZvc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rb03aADnAXxuHZvc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rb03aADnAXxuHZvc .error-icon{fill:#552222;}#mermaid-svg-rb03aADnAXxuHZvc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rb03aADnAXxuHZvc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rb03aADnAXxuHZvc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rb03aADnAXxuHZvc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rb03aADnAXxuHZvc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rb03aADnAXxuHZvc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rb03aADnAXxuHZvc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rb03aADnAXxuHZvc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rb03aADnAXxuHZvc .marker.cross{stroke:#333333;}#mermaid-svg-rb03aADnAXxuHZvc svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rb03aADnAXxuHZvc p{margin:0;}#mermaid-svg-rb03aADnAXxuHZvc .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-rb03aADnAXxuHZvc .cluster-label text{fill:#333;}#mermaid-svg-rb03aADnAXxuHZvc .cluster-label span{color:#333;}#mermaid-svg-rb03aADnAXxuHZvc .cluster-label span p{background-color:transparent;}#mermaid-svg-rb03aADnAXxuHZvc .label text,#mermaid-svg-rb03aADnAXxuHZvc span{fill:#333;color:#333;}#mermaid-svg-rb03aADnAXxuHZvc .node rect,#mermaid-svg-rb03aADnAXxuHZvc .node circle,#mermaid-svg-rb03aADnAXxuHZvc .node ellipse,#mermaid-svg-rb03aADnAXxuHZvc .node polygon,#mermaid-svg-rb03aADnAXxuHZvc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rb03aADnAXxuHZvc .rough-node .label text,#mermaid-svg-rb03aADnAXxuHZvc .node .label text,#mermaid-svg-rb03aADnAXxuHZvc .image-shape .label,#mermaid-svg-rb03aADnAXxuHZvc .icon-shape .label{text-anchor:middle;}#mermaid-svg-rb03aADnAXxuHZvc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rb03aADnAXxuHZvc .rough-node .label,#mermaid-svg-rb03aADnAXxuHZvc .node .label,#mermaid-svg-rb03aADnAXxuHZvc .image-shape .label,#mermaid-svg-rb03aADnAXxuHZvc .icon-shape .label{text-align:center;}#mermaid-svg-rb03aADnAXxuHZvc .node.clickable{cursor:pointer;}#mermaid-svg-rb03aADnAXxuHZvc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rb03aADnAXxuHZvc .arrowheadPath{fill:#333333;}#mermaid-svg-rb03aADnAXxuHZvc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rb03aADnAXxuHZvc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rb03aADnAXxuHZvc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rb03aADnAXxuHZvc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rb03aADnAXxuHZvc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rb03aADnAXxuHZvc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rb03aADnAXxuHZvc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rb03aADnAXxuHZvc .cluster text{fill:#333;}#mermaid-svg-rb03aADnAXxuHZvc .cluster span{color:#333;}#mermaid-svg-rb03aADnAXxuHZvc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rb03aADnAXxuHZvc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rb03aADnAXxuHZvc rect.text{fill:none;stroke-width:0;}#mermaid-svg-rb03aADnAXxuHZvc .icon-shape,#mermaid-svg-rb03aADnAXxuHZvc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rb03aADnAXxuHZvc .icon-shape p,#mermaid-svg-rb03aADnAXxuHZvc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rb03aADnAXxuHZvc .icon-shape .label rect,#mermaid-svg-rb03aADnAXxuHZvc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rb03aADnAXxuHZvc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rb03aADnAXxuHZvc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rb03aADnAXxuHZvc :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Builds

Pulled by

Runs

Managed by

Exposes

Routes via

Serves

Dockerfile

Container Imagedemo-app:1.0✅ Immutable✅ Reproducible

Container Runtimecontainerd / CRI-O✅ Isolation✅ Resource Limits

Pod✅ Shared Network✅ Shared Storage✅ Atomic Scheduling

Deployment✅ Desired State✅ Rolling Update✅ Auto-healing

Service✅ Stable VIP✅ DNS Name✅ Load Balancing

Ingress✅ Host/Path Routing✅ TLS Termination✅ WAF Integration

External Clientshttps://api.mycompany.com

每一层都向上提供稳定抽象(Abstraction),向下封装实现细节(Implementation):

  • 你写 Dockerfile,无需关心 containerd 如何解压 layer;
  • 你写 Deployment,无需关心 kube-scheduler 如何打分选 Node;
  • 你写 Ingress,无需关心 nginx-ingress-controller 如何生成 nginx.conf。

这种分层,正是云原生(Cloud Native)的核心精神:通过标准化接口解耦,让每个组件专注做好一件事,再通过声明式契约协同。


八、Java 工程师的 K8s 实战 Checklist ✅

最后,给正在落地 K8s 的 Java 团队一份精炼 checklist,覆盖开发、测试、运维全链路:

阶段关键动作Java 相关要点工具/命令
构建 ✅ 多阶段 Dockerfile 用 openjdk:17-jre-slim 基础镜像;禁用 jvm 的 -Xmx,改用 -XX:MaxRAMPercentage docker build -t app:v1 .
配置 ✅ 外置化配置 application-k8s.yml;敏感信息用 Secret,非敏感用 ConfigMap kubectl create configmap app-config –from-file=application-k8s.yml
健康 ✅ 实现 Actuator 端点 spring-boot-starter-actuator;暴露 /actuator/health/readiness 和 /actuator/health/liveness management.endpoint.health.show-details=when_authorized
日志 ✅ 标准输出(stdout) 禁止写文件日志(如 logback-spring.xml 写 ./logs/app.log);全部输出到 System.out logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} – %msg%n
监控 ✅ 暴露 Prometheus Metrics micrometer-registry-prometheus;确保 /actuator/prometheus 可访问 curl http://pod-ip:8080/actuator/prometheus
调试 ✅ 进入容器诊断 kubectl exec -it <pod-name> — /bin/sh;检查环境变量、网络连通性 env | grep SPRING;nslookup postgres-svc
升级 ✅ 金丝雀发布 用 Deployment 的 maxSurge/maxUnavailable 控制节奏;结合 Ingress 的 canary annotation(需 Nginx Ingress v1.0+) kubectl set image deploy/app app=app:v1.1 –record

📌 特别提醒:永远不要在容器内运行 sshd 或 systemd。K8s 的哲学是“一个容器一个进程”,SSH 会破坏不可变性,systemd 会抢占 PID 1 导致信号转发异常。调试请用 kubectl exec,长时任务请用 Job。


九、结语:容器技术的终局不是取代,而是升维 🌈

从 Docker run 的第一行命令,到 kubectl apply -f 的声明式交付;从手动维护 3 台 VM 的 Tomcat,到全自动滚动更新 50 个微服务实例——技术演进的本质,从来不是炫技,而是把工程师从重复劳动中解放,去解决真正有创造性的难题。

Kubernetes 并未否定 Docker,而是将其内核(Namespaces/Cgroups)提炼为通用原语,再以声明式 API 重构协作契约。它让 Java 工程师得以:

  • 用 @Value("${database.url}") 注入配置,而无需关心 ConfigMap 如何挂载;
  • 用 @Scheduled(fixedDelay = 5000) 写定时任务,而无需担心 CronJob 如何容错;
  • 用 RestTemplate 调用 http://user-svc:8080/api/users,而无需硬编码 IP 或写服务发现 SDK。

这,就是云原生的温柔力量:它不强迫你改变编码习惯,却悄然重塑了交付、运维与协作的底层逻辑。

所以,不必畏惧 YAML 的繁琐,不必纠结于 kubectl 命令的记忆。当你再次敲下 mvn clean package,请记得:那个即将被 Dockerfile 封装的 JAR,正站在通往 Kubernetes 世界的渡口。而你,已是那艘船的掌舵人 🚢。

🌟 延伸思考:

  • 如果你的 Spring Cloud Alibaba 应用已使用 Nacos 做服务发现,是否还需要 K8s Service?答案是:需要且必须。K8s Service 提供 DNS 和集群内负载均衡,Nacos 提供跨集群/多数据中心的服务治理,二者是互补而非替代。
  • 当 Istio Service Mesh 加入,Sidecar(Envoy)会接管所有流量,此时 Service 是否多余?不,Service 仍是 Istio 的基础依赖,Istio 的 VirtualService 和 DestinationRule 正是构建在 Service 之上的增强层。

技术没有银弹,只有恰如其分的组合。而理解 Docker 与 K8s 的协同逻辑,正是你做出正确组合决策的罗盘 🧭。


本文所有代码、YAML、命令均基于 Kubernetes 1.28+ 与 Spring Boot 3.2.x 实测验证,遵循 CNCF 最佳实践。愿你在云原生的海洋中,既握紧 Docker 的桨,也扬起 K8s 的帆。 🌊⛵


🙌 感谢你读到这里! 🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。 💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友! 💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿 🔔 关注我,不错过下一篇干货!我们下期再见!✨

赞(0)
未经允许不得转载:171主机测评 » Kubernetes - 从 Docker 到 K8s,容器技术的进阶与协同逻辑
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址