从 Docker 到 Kubernetes:一次“工程视角”的 K8s 核心概念深度梳理

引言:为什么 Docker 熟练工也会在 K8s 面前感到困惑?

在当今的云原生时代,Docker 已经成为了容器技术的代名词。很多开发者通过 Docker 入门容器世界:学会了编写 Dockerfile 将应用打包成镜像,掌握了 docker run命令启动容器,能够熟练部署 Redis、MySQL 等中间件,也理解了 --restart参数如何让容器在退出后自动重启。从单机应用的角度看,这些技能已经足够让应用在本地或单台服务器上可靠运行。

然而,当这些技能被带入真实的线上环境、面对生产系统的复杂性时,一个令人困惑的现象出现了:明明 Docker 用得滚瓜烂熟,为什么一到线上就处处碰壁?

这个问题的答案并不在于个人技术能力,而在于技术本身的定位边界。就像精通驾驶汽车并不意味着能管理整个城市的交通系统一样,精通 Docker 容器也不等于能管理分布式的容器化应用系统。这种认知的跃迁,正是从 Docker 到 Kubernetes 必须跨越的关键鸿沟。

一、Docker 的设计边界:它从未承诺解决"系统级"问题

1.1 Docker 的核心价值:单机容器化

Docker 在技术史上的革命性贡献可以用一句话概括:"它标准化了应用的打包、分发和运行方式"。在 Docker 之前,开发与运维之间最大的矛盾之一就是环境不一致问题------"在我本地是好的,怎么到服务器上就不行了?" Docker 通过容器镜像技术,将应用及其所有依赖(包括运行时、系统工具、库文件等)打包在一起,形成了一个可移植、自包含的交付单元。

从工程实现角度看,Docker 主要解决了以下几个关键问题:

  • 环境一致性:镜像包含了应用运行所需的一切,消除了"环境差异"导致的问题

  • 资源隔离:通过 Linux 内核的命名空间和控制组(cgroups)技术,实现进程、网络、文件系统等层面的隔离

  • 便携性:一次构建,到处运行(前提是内核兼容)

  • 轻量快速:相比虚拟机,容器启动更快、资源开销更小

这些特性让 Docker 在开发、测试和单机部署场景中表现卓越。然而,当我们把这些优势放到多机、多服务的生产环境中审视时,就会发现 Docker 的能力边界。

1.2 当单机遇到集群:Docker 无法回答的系统级问题

在实际的生产环境中,你会很快遇到一系列 Docker 自身无法回答的问题:

问题一:镜像的分发与同步

在一台机器上,docker pull可以拉取镜像。但当你需要在一百台机器上部署同一个服务时,你需要手动或编写脚本在每台机器上执行拉取命令。更复杂的是镜像版本管理:如何确保所有机器上的镜像版本一致?如何高效地分发大型镜像?如何避免因网络问题导致的部分节点镜像拉取失败?

问题二:故障域与高可用

Docker 的 --restart策略确实可以在容器异常退出时自动重启,但这只解决了"容器级"的故障恢复。如果容器所在的宿主机(物理机或虚拟机)本身宕机了呢?容器会随着宿主机的宕机而全部消失,此时再多的重启策略也无济于事。这就是典型的"单点故障"问题。

问题三:弹性伸缩的人工成本

当流量高峰来临时,你需要快速扩容服务实例。使用 Docker,这意味着需要在更多机器上手动启动容器,配置网络,挂载存储。高峰过后,又需要手动清理这些资源。这个过程不仅耗时耗力,而且极易出错。

问题四:服务发现的脆弱性

在微服务架构中,服务之间需要相互调用。使用 Docker 时,常见的做法是通过 IP 地址和端口进行通信。但容器的 IP 地址是动态分配的,每次重启都可能变化。即使使用链接(link)功能,也仅限于单机环境。跨主机的服务发现成为了一个需要自行解决的难题。

问题五:发布更新的服务中断

更新应用版本时,通常需要停止旧容器,启动新容器。这个过程中服务会出现短暂不可用。虽然可以通过一些技巧实现蓝绿部署,但这些都需要额外的工具和复杂的脚本,而且缺乏统一的管理和监控。

1.3 问题的本质:Docker 的设计哲学边界

这些问题的共同特征是:它们都发生在"多容器、多机器"的系统层面,而不是单个容器的运行层面

Docker 的官方定位是"容器运行时和打包工具",它的设计边界非常清晰:单机 + 单容器生命周期管理。Docker 从未承诺,也从未试图解决分布式系统中的调度、编排、服务发现、弹性伸缩等问题。

用一个类比来理解:Docker 就像是一个优秀的"集装箱"技术,它标准化了货物的包装和装卸方式。但管理一个港口,需要的是集装箱的调度系统、吊车的协调、堆场规划、船只安排等更高级别的系统。Kubernetes 正是这个"港口管理系统"。

二、Kubernetes 的本质:集群级的 Docker 管理与调度系统

2.1 一句话定义 Kubernetes

如果要用一句话概括 Kubernetes 是什么,最准确的描述是:Kubernetes 是一个"集群级的 Docker 管理与调度系统"

这个定义包含了几个关键点:

  1. 集群级:Kubernetes 的视野是整个集群(多台机器),而不是单台机器

  2. 管理:包括部署、更新、回滚、扩缩容等全生命周期管理

  3. 调度:决定容器应该在集群的哪台机器上运行

  4. 系统:它是一个完整的、自动化的系统,而不仅仅是一个工具

Kubernetes 不是 Docker 的替代品,而是 Docker 的"管理者"。在 Kubernetes 中,Docker 仍然是实际的容器运行时,负责在单个节点上创建和运行容器。Kubernetes 则站在更高维度,决定哪些容器应该在哪些节点上运行,如何管理它们之间的网络通信,如何确保它们的高可用性。

2.2 核心理念:从"人肉运维"到"系统自治"

Kubernetes 最根本的设计哲学是:把需要人工决策的运维操作,转化为系统自动执行的过程

在传统的运维模式中,当服务需要扩容时,工程师需要:

  1. 查看监控,判断是否需要扩容

  2. 选择在哪些机器上启动新实例

  3. 登录这些机器,执行启动命令

  4. 配置负载均衡,将流量导入新实例

  5. 验证新实例是否正常工作

在 Kubernetes 中,你只需要声明:"我希望这个服务始终保持5个运行实例"。系统会自动监控当前状态,如果发现只有3个实例,会自动创建2个新实例;如果发现太多负载,会自动调整实例分布。这种转变是从"怎么做"(命令式)到"要什么"(声明式)的根本性转变。

三、声明式 vs 命令式:两种完全不同的工程思维

3.1 命令式思维:关注过程与控制

Docker 的使用方式是典型的命令式(Imperative)思维。命令式思维关注"如何做"(How),你需要给出具体的执行步骤:

bash 复制代码
# 启动一个 Redis 容器 
docker run -d --name redis -p 6379:6379 redis:6.0 
# 停止这个容器 
docker stop redis 
# 重启这个容器 
docker restart redis

在这种模式下,你是系统的"操作者",直接对系统发出具体指令。系统执行你的指令,但不知道你的最终目标是什么。如果中间任何一步出错,你需要手动处理异常情况。

3.2 声明式思维:关注目标与状态

Kubernetes 采用的是声明式(Declarative)思维。声明式思维关注"要什么"(What),你只需要描述期望的最终状态:

bash 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:6.0
        ports:
        - containerPort: 6379

在这份配置中,你声明了:

  • 我需要一个名为 redis-deployment 的部署

  • 它应该始终保持3个副本

  • 使用 redis:6.0 镜像

  • 容器暴露6379端口

你不需要告诉 Kubernetes:

  • 在哪些机器上启动容器

  • 如何拉取镜像

  • 如何配置容器网络

  • 如何监控容器状态

  • 容器挂了怎么恢复

3.3 控制论视角下的 Kubernetes

Kubernetes 的声明式模型深受控制论(Cybernetic)思想影响。在控制论中,一个典型的控制系统包括:

  1. 期望状态(Desired State):你希望系统达到的状态

  2. 当前状态(Current State):系统当前的实际状态

  3. 控制回路(Control Loop):不断比较期望状态和当前状态,并采取措施减少两者差异

Kubernetes 的各个控制器就是这样的控制回路。以 Deployment 控制器为例,它持续运行一个循环:

  1. 观察:读取 Deployment 对象中声明的期望状态(如副本数=3)

  2. 比较:检查当前集群中匹配的 Pod 数量

  3. 执行:如果当前只有2个 Pod,就创建1个新的;如果有4个,就删除1个

  4. 等待:短暂等待后,回到第1步

这个过程无限循环,确保系统状态始终向期望状态收敛。这种设计使得系统具有自我修复(Self-healing)能力,能够自动应对各种异常情况。

四、Node:重新理解 Kubernetes 中的"机器"

4.1 Node 的本质:资源的提供者与故障边界

在 Kubernetes 中,Node 的概念看似简单,实则包含深刻的设计考量。Node 的本质是:提供计算资源的实体,通常是物理机或虚拟机

但"机器"这个概念在 Kubernetes 中被重新定义和抽象。Node 的核心职责不是"管理",而是:

  1. 提供资源:向集群提供 CPU、内存、存储和网络资源

  2. 运行容器:为 Pod 提供实际的运行环境

  3. 定义故障域:Node 的故障意味着运行其上的所有 Pod 都需要迁移

  4. 资源边界:Node 的物理限制(如 CPU 核心数、内存大小)定义了资源分配的上限

4.2 澄清常见误解:Node 不是最小资源单位

一个常见的误解是将 Node 视为 Kubernetes 中的"最小资源单位",认为调度器总是以整个 Node 为单位进行调度。这种理解是不准确的。

正确的理解是:Node 是资源的提供边界,Pod 是资源的请求单位,而容器是资源的使用实体

当你在 Pod 中定义:

复制代码
复制代码

resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m"

你是在向集群请求资源,而不是向特定 Node 请求。Kubernetes 调度器会查看所有 Node 的剩余可用资源,选择能够满足这个请求的 Node 来运行 Pod。Node 只是资源的"提供者",而不是"分配单位"。

4.3 Node 作为故障边界的设计含义

将 Node 设计为故障边界,是 Kubernetes 高可用设计的基础。这意味着:

  1. 故障隔离:一个 Node 的故障不应该影响其他 Node

  2. 故障恢复:Node 故障时,其上的 Pod 会被重新调度到其他健康 Node

  3. 滚动更新:可以通过逐个更新 Node 来实现集群的无中断升级

这种设计使得集群可以容忍单点(甚至多点)故障,实现真正的高可用性。

五、Pod:为什么 Kubernetes 不直接调度容器?

5.1 Pod 的设计动机:解决容器间的"亲密关系"问题

这是理解 Kubernetes 架构的第一道门槛,也是最重要的概念之一。一个很自然的问题是:既然最终运行的是容器,为什么 Kubernetes 不直接调度容器,而要引入 Pod 这个额外的抽象层?

答案是:因为现实世界中的应用很少是单一容器独立运行的

考虑以下常见场景:

  1. 日志收集:应用容器需要与日志收集容器(如 Fluentd)共享日志文件

  2. 服务网格:应用容器需要与边车容器(Sidecar,如 Envoy)共享网络栈

  3. 文件同步:Web 服务器容器需要与内容同步容器共享网站文件

  4. 本地缓存:多个容器需要访问同一个内存缓存

这些场景中的容器对具有以下特征:

  • 需要部署在同一台机器上

  • 需要共享本地存储

  • 需要通过 localhost 或共享内存通信

  • 具有紧密耦合的生命周期

Docker 虽然提供了容器间通信的机制(如 --link、共享卷),但这些机制是命令式的、临时性的,缺乏统一的管理抽象。Pod 正是为了解决这个问题而设计的。

5.2 Pod 的工程定义:共享命运的一组容器

从工程角度,Pod 可以定义为:一组生命周期强耦合、必须运行在同一 Node 上、共享部分命名空间的容器集合

理解 Pod 的关键在于理解它的共享机制:

  1. 共享网络命名空间:Pod 中的所有容器共享同一个 IP 地址和端口空间,它们可以通过 localhost 相互访问

  2. 共享存储卷:Pod 可以定义存储卷(Volume),这些卷可以被挂载到多个容器中

  3. 共享 IPC 命名空间:容器可以通过 System V IPC 或 POSIX 消息队列通信

  4. 共享 UTS 命名空间:容器共享同一个主机名和域名

  5. 共享 PID 命名空间(可选):容器可以相互看到进程

这些共享机制使得 Pod 内的容器可以像同一台机器上的进程一样紧密协作。

5.3 Pod 作为最小调度单位的意义

将 Pod 作为最小调度单位,而不是单个容器,具有重要的工程意义:

  1. 原子性调度:确保紧密耦合的容器总是被一起调度,避免了分散部署导致的协调问题

  2. 资源保证:Kubernetes 可以保证 Pod 所需的所有资源在同一个 Node 上得到满足

  3. 简化网络:Pod 有唯一的 IP 地址,外部通过这个 IP 访问 Pod,不需要关心内部有多少容器

  4. 统一生命周期:Pod 的启动、停止、重启是原子操作,内部容器一起被管理

这种设计体现了计算机科学中一个经典原则:将经常一起变化的东西放在一起(Common Closure Principle)。Pod 内的容器因为紧密耦合,所以一起调度、一起管理;而 Pod 之间相对独立,可以独立调度和管理。

六、Node 与 Pod 的约束关系:为什么 Pod 不能跨 Node?

6.1 一个基本原则:Pod 的边界就是单台 Node

这是 Kubernetes 设计中一个基本但至关重要的约束:一个 Pod 的所有容器必须运行在同一个 Node 上,不能跨 Node 分布

这个约束不是技术限制(理论上可以通过分布式技术实现容器跨节点协同),而是经过深思熟虑的设计选择。理解这个选择背后的原因,是理解 Kubernetes 调度和故障模型的关键。

6.2 约束背后的工程考量

网络层面的原因:localhost 语义的崩溃

Pod 内的容器共享网络命名空间,它们通过 localhost 相互访问。localhost 的语义是"本机",如果容器分布在不同的物理机器上,localhost 的语义就崩溃了。虽然可以通过虚拟网络技术模拟 localhost,但这会引入巨大的复杂性和性能开销。

存储层面的原因:本地存储的语义问题

Pod 内的容器可以共享存储卷(Volume)。当这些卷是本地存储(如 hostPath、emptyDir)时,它们实际上对应着 Node 上的目录或文件。如果容器跨 Node 分布,共享本地存储就变得不可能,或者需要引入复杂的分布式文件系统。

调度层面的原因:资源协调的复杂性

Kubernetes 调度器需要为 Pod 找到满足其所有资源需求的 Node。如果 Pod 可以跨 Node,调度器需要同时找到多个 Node,这些 Node 的组合需要满足 Pod 的总需求。这会将调度问题从一维(单个 Node 的选择)提升到多维(多个 Node 的组合选择),大大增加调度算法的复杂性。

故障处理层面的原因:部分故障难以定义和处理

如果 Pod 跨 Node 运行,当其中一个 Node 故障时,Pod 处于"部分存活"状态。这种状态难以定义和处理:Pod 是算存活还是故障?存活的容器应该继续运行还是终止?如何重新调度?

6.3 这个约束带来的设计简洁性

Pod 不能跨 Node 的约束实际上带来了系统设计的简洁性:

  1. 清晰的故障边界:Node 故障意味着其上的所有 Pod 都故障,故障处理逻辑简单清晰

  2. 简单的调度模型:调度器只需要为 Pod 找到一台合适的 Node,而不是多台

  3. 直观的网络模型:Pod IP 对应实际 Node 上的网络端点

  4. 高效的本地通信:容器间通过本地 IPC 或共享内存通信,无需经过网络

这个约束也促使开发者合理设计应用架构:紧密耦合的组件放在同一个 Pod 中,通过本地通信;相对独立的组件放在不同 Pod 中,通过服务发现和网络通信。这正是微服务架构的实践原则。

七、ReplicaSet:单一职责的数量守护者

7.1 ReplicaSet 的精准定位

在 Kubernetes 的架构中,每个组件都有明确的单一职责。ReplicaSet 的职责可以用一句话概括:确保指定标签选择器匹配的 Pod 副本数量始终符合预期

这个定义包含了几个关键点:

  1. 指定标签选择器:ReplicaSet 通过标签选择器(Label Selector)识别它要管理的 Pod

  2. 副本数量:ReplicaSet 只关心数量,不关心 Pod 的具体内容

  3. 始终符合预期:这是一个持续的过程,不是一次性的动作

7.2 ReplicaSet 的工作机制:持续的控制回路

ReplicaSet 控制器的工作机制是一个典型的控制回路:

  1. 观察:查询 API Server,获取当前集群中匹配其标签选择器的所有 Pod

  2. 比较:将实际 Pod 数量与期望副本数(spec.replicas)比较

  3. 行动

    • 如果实际数量少于期望数量,创建新的 Pod

    • 如果实际数量多于期望数量,删除多余的 Pod

  4. 等待:短暂间隔后,重新开始观察

这个过程无限循环,确保系统状态始终向期望状态收敛。这种机制使得 ReplicaSet 具有自我修复能力:如果它管理的 Pod 被意外删除(如 Node 故障、人为误操作),ReplicaSet 会自动创建新的 Pod 来替代。

7.3 为什么需要 ReplicaSet?而不让 Deployment 直接管理 Pod?

这是一个很好的设计问题。从表面看,Deployment 可以直接创建和管理 Pod,为什么要引入 ReplicaSet 这个中间层?

答案是:为了职责分离和版本管理

考虑一个场景:应用从 v1 版本升级到 v2 版本。如果没有 ReplicaSet:

  1. Deployment 需要直接删除 v1 的 Pod,创建 v2 的 Pod

  2. 如果升级过程中出现问题,需要回滚

  3. 回滚时,Deployment 需要删除 v2 的 Pod,重新创建 v1 的 Pod

  4. Deployment 需要自己记录历史版本信息

这种设计会让 Deployment 变得复杂,因为它需要同时处理:

  • 期望状态的定义

  • Pod 的生命周期管理

  • 版本历史管理

  • 滚动升级逻辑

引入 ReplicaSet 后,职责被清晰分离:

  1. Deployment​ 只关心"期望状态":使用哪个镜像、需要多少副本、如何升级

  2. ReplicaSet​ 只关心"数量保证":确保特定版本的 Pod 数量符合预期

  3. Pod​ 只关心"运行应用":实际运行容器化应用

这种分层设计使得每个组件都简单、专注,易于理解和维护。

八、Deployment:期望状态的描述者

8.1 Deployment 的误解与正解

一个常见的误解是:"Deployment 直接管理 Pod"。这个误解源于我们通常通过 Deployment 来创建和管理应用,但实际上,Deployment 并不直接与 Pod 交互。

正确的理解是:Deployment 是应用期望状态的描述者,而不是 Pod 的直接管理者

Deployment 的主要职责包括:

  1. 描述期望状态:应用应该使用哪个镜像、运行多少副本、使用什么配置

  2. 管理发布策略:如何从当前版本升级到新版本(滚动更新、蓝绿部署等)

  3. 管理版本历史:保留历史版本信息,支持快速回滚

  4. 协调 ReplicaSet:创建和管理代表不同版本的 ReplicaSet

8.2 Deployment 与 ReplicaSet 的协作关系

Deployment 和 ReplicaSet 的协作关系可以用以下流程理解:

bash 复制代码
Deployment (描述期望状态)
    |
    | 创建并管理
    v
ReplicaSet-v1 (保证 v1 版本有3个副本)
    |
    | 创建和管理
    v
Pod-v1-xxxxx (实际运行 v1 版本应用)
Pod-v1-yyyyy
Pod-v1-zzzzz

当需要升级到 v2 版本时:

bash 复制代码
Deployment (更新期望状态:使用 v2 镜像)
    |
    | 创建新的 ReplicaSet
    v
ReplicaSet-v2 (逐步创建 v2 版本的 Pod)
    |               |
    | 逐步扩缩容    | 逐步缩容
    v               v
Pod-v2-aaaaa    Pod-v1-xxxxx (逐步减少)
Pod-v2-bbbbb    Pod-v1-yyyyy
Pod-v2-ccccc    Pod-v1-zzzzz

最终,v1 版本的 Pod 全部被 v2 版本替代,但 ReplicaSet-v1 仍然被保留(用于可能的回滚)。

8.3 Deployment 的声明式升级策略

Deployment 的核心价值之一是为应用升级提供了声明式的策略。你不需要编写复杂的脚本来控制升级过程,只需要声明升级策略,Deployment 会自动执行。

以滚动更新(RollingUpdate)为例,你只需要在 Deployment 中声明:

bash 复制代码
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

这表示:

  • 升级策略是滚动更新

  • 最多可以比期望副本数多1个 Pod(maxSurge)

  • 升级过程中始终有100%的 Pod 可用(maxUnavailable: 0)

基于这个声明,Deployment 会自动执行以下步骤:

  1. 创建新的 ReplicaSet(对应新版本)

  2. 逐步创建新版本的 Pod(每次最多新增1个)

  3. 等待新 Pod 就绪

  4. 逐步删除旧版本的 Pod

  5. 重复2-4,直到所有 Pod 都替换为新版本

  6. 如果新 Pod 就绪检查失败,自动暂停升级

整个过程完全自动化,无需人工干预。这体现了 Kubernetes 声明式模型的强大之处:你只需要告诉系统"要什么",系统自动解决"怎么做"。

九、为什么需要分层:从单一组件到清晰职责

9.1 分层的设计哲学

Kubernetes 的分层设计(Deployment -> ReplicaSet -> Pod -> Container)体现了软件工程中的重要原则:单一职责原则 ​ 和 关注点分离

每个层级都有明确的职责边界:

  1. Deployment 层:应用生命周期管理

    • 关注:应用的整体状态、发布策略、版本历史

    • 不关注:具体的 Pod 实例、调度细节

  2. ReplicaSet 层:副本数量管理

    • 关注:确保指定版本的 Pod 数量符合预期

    • 不关注:Pod 的具体内容、升级策略

  3. Pod 层:容器编排单元

    • 关注:一组容器的协同运行

    • 不关注:副本数量、版本管理

  4. Container 层:应用运行环境

    • 关注:单个应用的运行

    • 不关注:与其他容器的关系、资源调度

这种分层设计使得系统具有很好的可扩展性和可维护性。每层只需要关注自己的职责,通过清晰的接口与上下层交互。

9.2 分层的实际价值

价值一:支持复杂的发布策略

因为有 ReplicaSet 这一层,Deployment 可以同时管理多个版本的 ReplicaSet。这使得复杂的发布策略(如金丝雀发布、蓝绿部署)成为可能。Deployment 可以控制新旧版本的比例,逐步将流量切换到新版本。

价值二:简化回滚操作

当新版本有问题时,回滚只需要将 Deployment 指向旧版本的 ReplicaSet。因为旧版本的 ReplicaSet 仍然存在(只是副本数为0),回滚可以瞬间完成。如果没有 ReplicaSet 这一层,Deployment 需要从历史记录中重新创建旧版本的 Pod,过程会更复杂、更耗时。

价值三:清晰的版本管理

每个 ReplicaSet 对应一个具体的应用版本(通过镜像标签识别)。通过查看集群中的 ReplicaSet,可以清楚地知道当前和历史版本。这种清晰的版本管理是系统可观测性的重要部分。

价值四:独立的伸缩能力

Horizontal Pod Autoscaler(HPA)可以直接与 ReplicaSet 交互,根据指标自动调整副本数。因为 ReplicaSet 的职责单一(只关心数量),所以与 HPA 的集成非常简单直接。

十、一次完整的发布过程:K8s 内部的视角

10.1 发布前的状态

假设我们有一个运行中的应用,通过以下 Deployment 管理:

bash 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:v1.0.0

此时,Kubernetes 内部的状态是:

  • 一个名为 myapp的 Deployment 对象,期望状态是3个副本,使用 myapp:v1.0.0镜像

  • 一个由 Deployment 创建的 ReplicaSet,假设名为 myapp-7fd5c5f5b4,确保有3个 Pod

  • 3个运行中的 Pod,每个 Pod 运行一个 myapp:v1.0.0的容器

10.2 触发发布:更新 Deployment

当我们更新 Deployment,将镜像版本改为 v1.0.1

bash 复制代码
kubectl set image deployment/myapp myapp=myapp:v1.0.1

或者通过更新 YAML 文件。这个操作修改了 Deployment 对象的期望状态。

10.3 Kubernetes 的响应:控制回路开始工作

  1. Deployment 控制器检测到变化

    Deployment 控制器定期检查 Deployment 对象的当前状态与期望状态。当发现镜像版本变化时,它知道需要进行一次更新。

  2. 创建新的 ReplicaSet

    Deployment 控制器创建一个新的 ReplicaSet,命名为类似 myapp-7c6b5c4d3b的名称。这个新的 ReplicaSet 的 Pod 模板使用新镜像 myapp:v1.0.1,但初始副本数可能为0。

  3. 执行滚动更新策略

    根据 Deployment 中定义的策略(默认是 RollingUpdate),开始逐步替换 Pod:

    • 新的 ReplicaSet 逐步增加副本数(比如从0增加到1)

    • 旧的 ReplicaSet 逐步减少副本数(比如从3减少到2)

    • 等待新 Pod 就绪(通过就绪探针检查)

    • 继续逐步替换,直到新 ReplicaSet 有3个副本,旧 ReplicaSet 有0个副本

  4. Pod 的创建与调度

    当新的 ReplicaSet 需要创建 Pod 时:

    • 向 API Server 提交 Pod 创建请求

    • 调度器(Scheduler)监听到新的 Pod,开始调度决策:

      a. 过滤(Filtering):排除不满足要求的 Node(资源不足、不满足节点选择器等)

      b. 打分(Scoring):对满足要求的 Node 进行评分

      c. 绑定(Binding):将 Pod 绑定到得分最高的 Node

    • 目标 Node 上的 kubelet 监听到绑定事件:

      a. 拉取镜像 myapp:v1.0.1

      b. 创建容器运行时(如 Docker)容器

      c. 启动容器

      d. 执行启动后钩子和就绪探针

  5. 流量切换

    如果使用了 Service,流量会自动切换到新 Pod。因为 Service 通过标签选择器选择 Pod,而新旧 Pod 都有 app: myapp标签,所以 Service 会将流量同时路由到新旧 Pod。随着旧 Pod 被逐步终止,流量自然迁移到新 Pod。

  6. 更新完成

    当新 ReplicaSet 的副本数达到3,旧 ReplicaSet 的副本数降到0时,更新完成。但旧 ReplicaSet 仍然被保留(用于可能的回滚)。

10.4 关键洞察:Deployment 不知道具体 Pod

在整个过程中,一个关键点是:Deployment 从不直接管理或知道具体的 Pod 实例

Deployment 只与 ReplicaSet 交互,告诉每个 ReplicaSet 应该有多少副本。ReplicaSet 负责创建和管理具体的 Pod。这种间接性提供了重要的抽象和灵活性。

十一、Kubernetes 的核心思想浓缩

11.1 重要的设计理念

理念一:声明式优于命令式

不要告诉系统"怎么做",告诉系统"要什么"。系统会自动计算当前状态与期望状态的差异,并采取措施缩小差异。这种控制回路模式使得系统具有自我修复能力和最终一致性。

理念二:控制器模式

Kubernetes 的核心是控制器模式。每个控制器都是一个独立的过程,监视一类资源的状态,并采取措施使实际状态向期望状态收敛。这种模式使得系统易于理解和扩展。

理念三:面向API的设计

Kubernetes 的所有功能都通过 API 暴露。无论是 kubectl 命令行工具,还是 Dashboard 界面,或是其他客户端,都通过相同的 API 与系统交互。这种一致性简化了工具开发和系统集成。

理念四:松耦合的架构

组件之间通过清晰的 API 接口交互,每个组件有明确的职责边界。这种松耦合设计使得系统易于理解、调试和扩展。

11.2 对开发者的启示

启示一:从"操作者"到"声明者"的思维转变

使用 Kubernetes 时,你需要从"如何部署应用"的命令式思维,转变为"应用应该是什么状态"的声明式思维。编写 YAML 文件不是编写执行脚本,而是描述期望状态。

启示二:理解抽象的价值

Pod、Service、Deployment 等都是抽象。这些抽象隐藏了底层复杂性,提供了统一的接口。理解每个抽象的职责和边界,是有效使用 Kubernetes 的关键。

启示三:拥抱最终一致性

Kubernetes 是最终一致的系统。当你修改一个配置时,系统不会立即达到新状态,而是逐步向新状态收敛。这种模式可能需要一些适应,但它提供了更好的弹性和可靠性。

启示四:关注分离的重要性

Kubernetes 的分层设计体现了关注分离的原则。作为开发者,你的应用也应该遵循这个原则:容器只关注应用运行,Pod 关注容器组合,Deployment 关注应用生命周期。每层只关注自己的职责。

结语:从 Docker 到 Kubernetes 的思维升级

从 Docker 到 Kubernetes 的过渡,本质上是思维模式的升级:

  1. 从单机思维到集群思维:不再关注单个容器如何运行,而是关注整个应用系统如何在集群中运行

  2. 从命令式思维到声明式思维:不再编写具体的操作步骤,而是描述期望的最终状态

  3. 从手动操作到自动运维:将重复的运维决策交给系统自动执行

  4. 从关注实例到关注模式:不再关心具体的容器实例,而是关心应用部署和管理的模式

这种思维升级是云原生时代的核心要求。Kubernetes 不仅仅是一个工具,更是一种新的应用管理和运维范式。掌握这种范式,意味着你不再仅仅是应用的开发者,更是系统的设计者。

Kubernetes 的学习曲线确实陡峭,但一旦理解了其核心思想和设计哲学,你会发现它提供了一种优雅、统一的方式来管理复杂的分布式系统。这种理解不是通过记忆命令和参数获得的,而是通过理解系统背后的设计原则和工程考量获得的。

希望这篇从工程视角的梳理,能够帮助你跨越从 Docker 到 Kubernetes 的理解鸿沟,不仅在工具使用层面,更在系统设计层面,理解 Kubernetes 为什么这样设计,以及如何利用这些设计构建更可靠、更易管理的应用系统。

相关推荐
liuxuzxx2 小时前
containerd的CPU过高的问题排查
容器·性能优化·kubernetes
看-清3 小时前
Docker离线安装部署xxl-job流程
运维·docker·容器
建群新人小猿3 小时前
陀螺匠企业助手-我的日程
android·大数据·运维·开发语言·容器
孤岛悬城3 小时前
47 Docker镜像编排
docker·容器·云计算
忙里偷闲学python4 小时前
ceph介绍和安装
linux·ceph·kubernetes
隔壁阿布都5 小时前
Docker 安装 MySQL 8.0
mysql·docker·容器
RustFS6 小时前
RustFS 如何实现对象存储的前端直传?
vue.js·docker·rust
摇滚侠6 小时前
40分钟的Docker实战攻略,一期视频精通Docker
运维·docker·容器
忍冬行者6 小时前
kubeadm安装的k8s集群涉及etcd数据库的参数优化
数据库·kubernetes·etcd