--- tags: [k8s, devops, container-orchestration] create time: 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)。 ### 架构总览 ```mermaid graph TB subgraph ControlPlane["Control Plane(控制平面)"] API["API Server
集群的唯一入口"] etcd["etcd
分布式 KV 存储"] sched["Scheduler
Pod 调度决策"] cm["Controller Manager
维持期望状态"] end subgraph Worker1["Worker Node 1"] kubelet1["kubelet
管理 Pod 生命周期"] proxy1["kube-proxy
网络规则管理"] cr1["Container Runtime
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 将这些紧密耦合的容器打包在一起,共享网络和存储,比独立容器更优雅。 ```yaml # 一个包含 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 信息: ```bash # 查看集群所有节点 kubectl get nodes -o wide # 查看某个节点的详细信息(资源、条件、运行的 Pod) kubectl describe node ``` Node 有几个重要的**条件(Conditions)**: | Condition | 说明 | |-----------|------| | Ready | 节点健康且准备好接受 Pod | | MemoryPressure | 节点内存不足 | | DiskPressure | 节点磁盘空间不足 | | PIDPressure | 节点进程数过多 | | NetworkUnavailable | 节点网络未正确配置 | ### Namespace Namespace 是 K8s 中的**逻辑隔离机制**,用于在同一物理集群中划分多个虚拟集群。适用于: - 多团队共享集群(`team-a`、`team-b`) - 环境隔离(`dev`、`staging`、`production`) - 资源配额管理(每个 Namespace 设置独立的 ResourceQuota) ```yaml # 创建一个 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 对象 | ```bash # 在指定 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 的灵活性远超树形结构。 ```yaml # 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 的三种用法**: ```bash # 等值选择器 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 为例): ```yaml 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 内容: ```yaml 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 标准镜像,与构建工具无关 ### 容器运行时的演进 ```mermaid 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** ```yaml # 反面教材: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` 标签** ```yaml # 反面教材 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 反亲和性确保高可用**: ```yaml 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)**: ```yaml spec: terminationGracePeriodSeconds: 30 containers: - name: app lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 5"] ``` `preStop` 中的 `sleep 5` 给 kube-proxy 更新规则留出时间,避免请求被发送到正在终止的 Pod。 6. **LimitRange 设置默认值**: ```yaml 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 ``` 防止单个未配置资源限制的容器拖垮整个节点。 ## 延伸阅读 - [Kubernetes 官方文档](https://kubernetes.io/zh-cn/docs/home/) — 最权威的参考 - [Kubernetes 官方教程](https://kubernetes.io/zh-cn/docs/tutorials/) — 动手入门 - [CNCF 技术雷达](https://www.cncf.io/tech-radars/) — 云原生技术选型参考 - [Kelsey Hightower - Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way) — 手动从零搭建 K8s 集群,深入理解每个组件 - 《Kubernetes in Action (第二版)》 — Marko Lukša 著,深入原理的经典教材 ## 关联笔记 - [[k8s/k8s-workload-resources]] — Deployment、StatefulSet、DaemonSet 等工作负载详解 - [[k8s/k8s-networking]] — Service、Ingress、NetworkPolicy 网络模型 - [[k8s/k8s-storage]] — PV、PVC、StorageClass 存储体系