从 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 为什么这样设计,以及如何利用这些设计构建更可靠、更易管理的应用系统。

相关推荐
oMcLin6 小时前
如何在 Ubuntu 22.10 上通过 Kubernetes 和 Helm 管理微服务应用,简化跨平台电商平台的自动化部署?
ubuntu·微服务·kubernetes
JadenOliver10 小时前
Docker 守护进程核心配置入口:daemon.json
docker·daemon.json
原神启动111 小时前
K8S(九)—— Kubernetes 集群调度全面解析
云原生·容器·kubernetes
用户938169125536011 小时前
Ubuntu系统docker错误,failed to do request: Head "https://registry-1.docker.io/v2/...
docker
m0_7482459211 小时前
Docker 容器基本操作
运维·docker·容器
咋吃都不胖lyh12 小时前
Docker 是什么?全面解析容器化技术
运维·docker·容器
阿杰 AJie12 小时前
Docker 常用镜像启动参数对照表
运维·docker·容器
db_cy_206213 小时前
Docker+Kubernetes企业级容器化部署解决方案(阶段一)
docker·容器·kubernetes·云计算·负载均衡·运维开发
王同学 学出来14 小时前
vue+nodejs项目在服务器实现docker部署
服务器·前端·vue.js·docker·node.js
last demo14 小时前
docker容器
运维·docker·容器