Files
Qiniu/technical/k8s/k8s-01-overview.md
T

21 KiB
Raw Blame History

tags, create time
tags create time
k8s
devops
container-orchestration
2026-07-07 15:48

Kubernetes 概述

概述

Kubernetes(简称 K8s)是一个开源的容器编排平台,由 Google 内部的 Borg 系统演化而来,于 2014 年开源,目前由 CNCF(Cloud Native Computing Foundation)托管。它的核心使命是:让部署、扩展和管理容器化应用变得自动化且可预测。简单来说,Docker 解决了「如何打包一个应用」的问题,而 K8s 解决的是「如何在成百上千台机器上高效地运行和管理这些应用」的问题。

为什么需要 K8s

从 Docker 单机到集群编排

让我们先回顾一下容器技术的演进路径:

  1. 裸机/虚拟机时代:应用直接部署在物理机或 VM 上,手动管理依赖、端口和资源分配。
  2. Docker 单机时代:容器解决了环境一致性和依赖打包的问题,但单机资源有限,且面临应用故障恢复、滚动更新等挑战。
  3. 集群编排时代:当你的服务需要跨多台机器运行时,你需要一个「大脑」来决定:把容器调度到哪台机器?挂了怎么重启?流量怎么分配?——这就是 K8s 存在的意义。

[!tip] 一个类比 如果 Docker 集装箱标准化了「货物打包」,那 K8s 就是自动化码头——它决定集装箱该放哪艘船、航线怎么规划、遇到风暴如何重新调度。

Docker Compose vs Docker Swarm vs Kubernetes

特性 Docker Compose Docker Swarm Kubernetes
定位 单机多容器编排 轻量级集群编排 企业级容器编排平台
规模 单主机 中小规模集群 大规模生产环境
学习曲线 低 中 高
自动扩缩容 不支持 基础支持 HPA / VPA / Cluster Autoscaler
自愈能力 无 基础(重启策略) 强(Liveness/Readiness Probe + 控制器)
滚动更新 不支持 支持 细粒度控制(maxUnavailable / maxSurge)
生态 仅 Docker 生态 仅 Docker 生态 CNCF 生态,插件丰富
服务发现 容器名 DNS 内置 DNS CoreDNS + Service 资源
适用场景 本地开发、测试 快速上手小集群 生产环境首选

[!info] 为什么 Swarm 没能打败 K8s? Docker Swarm 的设计哲学是「简单优先」,但现实世界的生产需求远比想象中复杂。K8s 的声明式 API、丰富的控制器模型、以及 CNCF 生态的飞轮效应,最终让它成为事实标准。

核心架构

K8s 采用经典的 Master-Worker 架构(官方文档中 Master 节点现在更常称为 Control Plane)。

架构总览

graph TB
    subgraph ControlPlane["Control Plane(控制平面)"]
        API["API Server<br/>集群的唯一入口"]
        etcd["etcd<br/>分布式 KV 存储"]
        sched["Scheduler<br/>Pod 调度决策"]
        cm["Controller Manager<br/>维持期望状态"]
    end

    subgraph Worker1["Worker Node 1"]
        kubelet1["kubelet<br/>管理 Pod 生命周期"]
        proxy1["kube-proxy<br/>网络规则管理"]
        cr1["Container Runtime<br/>containerd / CRI-O"]
        P1a["Pod A"]
        P1b["Pod B"]
    end

    subgraph Worker2["Worker Node 2"]
        kubelet2["kubelet"]
        proxy2["kube-proxy"]
        cr2["Container Runtime"]
        P2a["Pod C"]
        P2b["Pod D"]
    end

    subgraph Client["用户 / CI/CD"]
        kubectl["kubectl / SDK"]
    end

    kubectl -->|"REST API"| API
    API <-->|"读写"| etcd
    API -->|"监听事件"| sched
    API -->|"监听事件"| cm
    kubelet1 -->|"汇报状态"| API
    kubelet2 -->|"汇报状态"| API
    kubelet1 --> cr1
    kubelet2 --> cr2
    cr1 --> P1a & P1b
    cr2 --> P2a & P2b
    proxy1 -.->|"Service 流量"| P1a & P1b
    proxy2 -.->|"Service 流量"| P2a & P2b

Control Plane 组件详解

API Server(kube-apiserver)

API Server 是整个集群的唯一入口,所有组件之间的通信都通过它进行。它承担的核心职责:

  • RESTful API 网关:暴露 K8s API,所有 kubectl 操作最终都到达这里
  • 认证与授权:RBAC、ServiceAccount、Webhook Token 等
  • 准入控制(Admission Control):在资源持久化之前进行校验和修改(如 MutatingWebhook、ValidatingWebhook)
  • Watch 机制:其他组件通过 Watch API Server 获取资源变更事件

[!tip] 为什么所有组件都要通过 API Server 通信? 这是 K8s 的一个重要设计决策——中心化 API 网关保证了统一的认证、授权、审计和数据校验逻辑。如果各组件直接读写 etcd,安全策略将难以实施。

etcd

etcd 是一个高可用的分布式 KV 存储,是 K8s 的「唯一数据源」。所有集群状态(Pod、Service、ConfigMap 等资源对象)都持久化在 etcd 中。

  • 基于 Raft 共识算法保证数据一致性
  • 生产环境通常部署 3 或 5 个节点(奇数个,便于选举)
  • Watch 机制:支持高效地监听 key 的变更

[!warning] etcd 是整个集群的 Single Point of Truth 如果 etcd 数据丢失且没有备份,整个集群状态将不可恢复。生产环境务必做好 etcd 的定期备份。

Scheduler(kube-scheduler)

Scheduler 负责将新创建的 Pod 绑定到合适的 Node 上。调度过程分为两个阶段:

  1. 过滤(Filtering):排除不满足条件的 Node(如资源不足、Taint 不匹配、亲和性规则冲突)
  2. 打分(Scoring):对剩余 Node 进行评分,选择得分最高的

常见调度策略:

  • 资源请求与限制:基于 Pod 的 requests 和 limits
  • 亲和性与反亲和性:nodeAffinity、podAffinity、podAntiAffinity
  • Taint 和 Toleration:驱逐不兼容的 Pod
  • 拓扑分布约束:topologySpreadConstraints 跨可用区均匀分布

Controller Manager(kube-controller-manager)

Controller Manager 是一组控制器的集合,每个控制器负责一种资源类型的「调谐循环(Reconciliation Loop)」。核心思想:持续比较「期望状态」和「实际状态」,并采取行动使实际状态趋近期望状态。

常见控制器:

控制器 职责
Deployment Controller 管理 ReplicaSet 的创建/更新
ReplicaSet Controller 确保指定数量的 Pod 副本运行
Node Controller 监控 Node 心跳,处理节点故障
Job Controller 管理一次性任务的 Pod
ServiceAccount Controller 为新 Namespace 创建默认 ServiceAccount

Worker Node 组件详解

kubelet

kubelet 是运行在每个 Worker Node 上的代理进程,负责:

  • 从 API Server 获取分配给本节点的 Pod Spec
  • 调用 Container Runtime 拉取镜像、启动容器
  • 执行健康检查(Liveness Probe、Readiness Probe、Startup Probe)
  • 向 API Server 汇报节点和 Pod 状态
  • 管理 Volume 的挂载和卸载

[!info] kubelet 不是通过 API Server 启动容器的 kubelet 通过 CRI(Container Runtime Interface) 与容器运行时交互,而非直接调用 Docker CLI。

kube-proxy

kube-proxy 负责在每个节点上维护网络规则,实现 Service 的负载均衡。它有三种工作模式:

模式 实现方式 性能 推荐度
iptables Linux iptables 规则 中等(规则数线性增长) 默认模式
IPVS Linux IPVS(内核级 LB) 高(哈希表查找) 大规模集群推荐
nftables nftables 规则 高 K8s 1.29+ 实验性

Container Runtime

Container Runtime 是真正执行容器生命周期操作的组件。K8s 通过 CRI(Container Runtime Interface) 标准化了与运行时的交互方式:

  • containerd:目前最主流的选择,Docker 的核心运行时组件被独立出来
  • CRI-O:Red Hat 主导,专为 K8s 设计的轻量级运行时
  • Docker:自 K8s 1.24 起不再直接支持(dockershim 已移除),但 containerd 仍可使用

关键概念

Pod

Pod 是 K8s 中最小的可部署单元,而不是容器。一个 Pod 可以包含一个或多个容器,它们:

  • 共享同一个 Network Namespace(即共享 IP 和端口空间)
  • 可以通过 Volume 共享存储
  • 总是被调度到同一个 Node 上

[!tip] 为什么需要 Pod 而不是直接管理容器? 现实中有些应用需要「伴生容器(Sidecar)」——比如日志收集器、Service Mesh 代理。Pod 将这些紧密耦合的容器打包在一起,共享网络和存储,比独立容器更优雅。

# 一个包含 Sidecar 的 Pod 示例
apiVersion: v1
kind: Pod
metadata:
  name: app-with-logging
  labels:
    app: my-app        # 用于 Service 选择和筛选
    env: production
spec:
  containers:
  - name: app
    image: my-app:v1.2.3
    ports:
    - containerPort: 8080
    resources:
      requests:
        cpu: "100m"       # 0.1 核
        memory: "128Mi"
      limits:
        cpu: "500m"       # 最多 0.5 核
        memory: "512Mi"
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      periodSeconds: 3
  - name: log-collector
    image: fluentd:v1.16
    volumeMounts:
    - name: log-volume
      mountPath: /var/log/app
  volumes:
  - name: log-volume
    emptyDir: {}    # Pod 内共享的临时目录

Pod 生命周期中的几个关键阶段:

Phase 含义
Pending 已被 API Server 接受,但尚未调度或镜像未拉取
Running 至少一个容器正在运行
Succeeded 所有容器正常退出(退出码 0)
Failed 至少一个容器异常退出
Unknown 无法获取 Pod 状态(通常是 Node 通信故障)

Node

Node 是 K8s 集群中的工作机器(物理机或虚拟机)。每个 Node 包含运行 Pod 所需的服务(kubelet、kube-proxy、Container Runtime)。

查看 Node 信息:

# 查看集群所有节点
kubectl get nodes -o wide

# 查看某个节点的详细信息(资源、条件、运行的 Pod)
kubectl describe node <node-name>

Node 有几个重要的条件(Conditions):

Condition 说明
Ready 节点健康且准备好接受 Pod
MemoryPressure 节点内存不足
DiskPressure 节点磁盘空间不足
PIDPressure 节点进程数过多
NetworkUnavailable 节点网络未正确配置

Namespace

Namespace 是 K8s 中的逻辑隔离机制,用于在同一物理集群中划分多个虚拟集群。适用于:

  • 多团队共享集群(team-a、team-b)
  • 环境隔离(dev、staging、production)
  • 资源配额管理(每个 Namespace 设置独立的 ResourceQuota)
# 创建一个 Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: team-backend
  labels:
    team: backend
    env: production
---
# 在该 Namespace 下设置资源配额
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-backend-quota
  namespace: team-backend
spec:
  hard:
    requests.cpu: "10"         # 总 CPU 请求上限 10 核
    requests.memory: "20Gi"    # 总内存请求上限 20Gi
    limits.cpu: "20"
    limits.memory: "40Gi"
    pods: "50"                 # 最多 50 个 Pod

K8s 默认创建的 Namespace:

Namespace 用途
default 未指定 Namespace 时的默认值
kube-system K8s 系统组件(CoreDNS、kube-proxy 等)
kube-public 公共资源(集群信息等)
kube-node-lease 节点心跳 Lease 对象
# 在指定 Namespace 下操作
kubectl get pods -n team-backend

# 设置默认 Namespace(避免每次都输入 -n)
kubectl config set-context --current --namespace=team-backend

[!warning] Namespace 不提供网络隔离 默认情况下,不同 Namespace 的 Pod 可以互相通信。如需网络隔离,需要配合 NetworkPolicy 实现。

Label & Selector

Label 是附加在 K8s 对象上的键值对,是 K8s 组织和筛选资源的核心机制。你可能会问:「为什么 K8s 不用目录/文件夹的方式来组织资源?」——因为资源之间的关系是多对多的,一个 Pod 可能同时属于某个应用、某个环境、某个团队,Label 的灵活性远超树形结构。

# Label 的常见命名约定
metadata:
  labels:
    app.kubernetes.io/name: my-app          # 应用名称
    app.kubernetes.io/version: "1.2.3"      # 版本
    app.kubernetes.io/component: frontend   # 组件
    app.kubernetes.io/part-of: platform     # 所属系统
    app.kubernetes.io/managed-by: helm      # 管理工具
    env: production                          # 环境
    team: backend                            # 团队

Selector 的三种用法:

# 等值选择器
kubectl get pods -l app=my-app,env=production

# 集合选择器
kubectl get pods -l 'env in (production, staging)'
kubectl get pods -l 'env notin (dev)'

# 否定选择器
kubectl get pods -l 'app!=my-app'

在资源定义中使用 Selector(以 Service 为例):

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
spec:
  selector:
    app: my-app         # 匹配所有带 app=my-app 标签的 Pod
    env: production
  ports:
  - port: 80
    targetPort: 8080

Annotation

Annotation 用于存储非标识性的附加信息,这些信息不会被 K8s API 用于筛选,但对工具、库和运维人员很有用。典型的 Annotation 内容:

metadata:
  annotations:
    # 记录维护者信息
    owner: "backend-team@company.com"
    # Git 提交信息(CI/CD 注入)
    git-commit: "abc123f"
    git-branch: "main"
    # 构建时间
    build-time: "2026-07-07T15:00:00Z"
    # Ingress 配置(Nginx Ingress Controller 使用)
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    # Prometheus 自动发现
    prometheus.io/scrape: "true"
    prometheus.io/port: "9090"

[!tip] Label vs Annotation 如何选择? 问自己一个问题:「我需要根据这个信息来筛选或分组资源吗?」—— 如果是,用 Label;如果只是附加的描述性信息,用 Annotation。Label 有长度和字符限制,Annotation 则更宽松。

K8s 与 Docker 的关系

常见误解

很多初学者会问:「K8s 是不是要替代 Docker?」答案是:不完全是。准确地说:

  • K8s 替代的是 Docker Swarm(编排层面的竞争)
  • K8s 不再直接使用 Docker Engine 作为容器运行时(自 1.24 起)
  • 但你仍然可以用 Docker 构建镜像,K8s 运行的是 OCI 标准镜像,与构建工具无关

容器运行时的演进

timeline
    title K8s 容器运行时演进
    2014-2016 : Docker 是唯一选择
              : kubelet 直接调用 Docker API
    2017 : CRI 标准引入
         : dockershim 作为适配层
    2018 : containerd 从 Docker 拆分
         : CRI-O 项目成熟
    2020 : dockershim 弃用公告
    2022-04 : K8s 1.24 移除 dockershim
            : containerd 成为主流
    2024+ : CRI-O 广泛使用
          : Kata / gVisor 沙箱运行时兴起

CRI(Container Runtime Interface) 是 K8s 定义的一套 gRPC 接口规范,任何实现了 CRI 的运行时都可以被 kubelet 使用:

运行时 特点 适用场景
containerd Docker 核心组件独立版,功能全面 通用场景,最广泛使用
CRI-O 专为 K8s 设计,轻量级 Red Hat / OpenShift 生态
Kata Containers 轻量级 VM,安全隔离 多租户、不可信工作负载
gVisor (runsc) 用户态内核沙箱 安全敏感场景

[!info] 理解 CRI 的分层 kubelet → CRI (gRPC) → containerd → runc → Linux Kernel(namespaces, cgroups)

每一层都有明确的职责边界。containerd 负责镜像管理和容器生命周期,runc 负责实际创建 Linux 容器。

为什么移除 dockershim?

移除 dockershim 的主要原因:

  1. 维护成本:dockershim 是 kubelet 内部的一层额外抽象,增加代码复杂度
  2. 性能开销:kubelet → dockershim → Docker daemon → containerd,调用链过长
  3. Docker 的「额外功能」:Docker Engine 包含很多 K8s 不需要的组件(如 Docker Swarm、docker build 等),但作为运行时又必须加载整个 daemon
  4. CRI 标准成熟:containerd 和 CRI-O 直接实现 CRI,不再需要中间层

部署方式对比

方案 类型 节点数 生产就绪 学习成本 适用场景
minikube 单节点本地集群 1 否 低 本地开发和学习,支持 Addons
kind Docker 容器模拟 Node 1-N 否 低 CI/CD 测试、多节点本地模拟
kubeadm 生产级部署工具 N 是 中高 自建集群、对基础设施有完全控制
EKS AWS 托管 N 是 中 AWS 生态用户
AKS Azure 托管 N 是 中 Azure 生态用户
GKE Google Cloud 托管 N 是 中 GCP 生态用户,Autopilot 模式
ACK 阿里云托管 N 是 中 国内云生态用户

[!tip] 如何选择?

  • 学习阶段:minikube 或 kind(零成本,快速启动)
  • CI/CD:kind(轻量,测试完即销毁)
  • 小团队生产:托管服务(减少运维负担)
  • 大规模/定制需求:kubeadm 或 Cluster API

常见陷阱与最佳实践

陷阱

  1. 不设置 Resource Requests/Limits

    # 反面教材:Pod 没有资源声明
    containers:
    - name: app
      image: my-app:v1
    

    后果:调度器无法做出合理决策,某个 Pod 可能吃掉整个节点的资源,导致其他 Pod 被 OOM Kill。

  2. 把所有东西塞进一个 Pod Pod 中的容器应该有紧密的生命周期耦合。如果两个应用可以独立部署、独立扩缩容,它们应该在不同的 Pod 中。

  3. 忽略 Liveness/Readiness Probe 没有 Probe 的 Pod,K8s 无法知道它是否真的健康。一个「Running」状态的 Pod,内部应用可能已经死锁。

  4. 在生产环境使用 latest 标签

    # 反面教材
    image: my-app:latest
    

    你无法确定每次部署用的是哪个版本,回滚更是噩梦。始终使用明确的版本标签(如 v1.2.3 或 Git SHA)。

  5. 直接操作集群资源而不使用 GitOps 手动 kubectl apply 的变更不可追溯、不可审计。使用 Helm / Kustomize + ArgoCD / Flux 进行声明式管理。

最佳实践

  1. 声明式 > 命令式:始终用 YAML 定义资源,而不是 kubectl run/kubectl create 的命令式操作。声明式配置可版本控制、可审计、可复现。

  2. 合理使用 Namespace:按团队或环境划分 Namespace,配合 ResourceQuota 和 LimitRange 进行资源管控。

  3. Pod 反亲和性确保高可用:

    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: ["my-app"]
              topologyKey: kubernetes.io/hostname
    

    这确保同一应用的多个副本不会被调度到同一个 Node 上。

  4. 使用 imagePullPolicy 控制镜像拉取策略:

    • Always:总是拉取(适用于 latest 标签,但不推荐用 latest)
    • IfNotPresent:本地没有才拉取(推荐用于明确版本标签)
    • Never:从不拉取(仅用于本地开发)
  5. 优雅终止(Graceful Shutdown):

    spec:
      terminationGracePeriodSeconds: 30
    containers:
    - name: app
      lifecycle:
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 5"]
    

    preStop 中的 sleep 5 给 kube-proxy 更新规则留出时间,避免请求被发送到正在终止的 Pod。

  6. LimitRange 设置默认值:

    apiVersion: v1
    kind: LimitRange
    metadata:
      name: default-limits
      namespace: team-backend
    spec:
      limits:
      - default:
          cpu: "500m"
          memory: "256Mi"
        defaultRequest:
          cpu: "100m"
          memory: "128Mi"
        type: Container
    

    防止单个未配置资源限制的容器拖垮整个节点。

延伸阅读

关联笔记