1.1. Kubernetes 系统基础

容器解决的是「如何打包并隔离一个进程」,编排解决的是「成百上千个这样的进程如何被调度、连接、升级和自愈」。这一章不背概念,而是沿着一条请求的路径,把容器运行时、CRI、CNI、CSI 与控制平面串成一张可以上手验证的图。

层级:

L1 · 入门

难度:

入门

预计:

50 分钟

前置:

Linux 基础、Docker 或 Podman 使用经验

1.1.1. 你将学到

  • 容器隔离依赖的内核机制,以及单机 Docker 解决不了什么问题

  • OCI、CRI、CNI、CSI 各自约束哪一层,为什么它们能解耦

  • 控制平面与工作节点的组件边界,以及「一切经过 API Server」的含义

  • Pod 网络模型的四条通信路径

  • 声明式 API 与控制器协调循环的工作方式

  • Helm、Operator、Service Mesh、可观测性在生态中的位置

  • 用 crictl 与 kubectl 观察运行时与组件健康

1.1.2. 从容器到编排

容器本质上是「被内核限制起来的进程」:namespaces 负责让进程看不见别的进程、网络和挂载点,cgroups 负责限制它能用多少 CPU 和内存,联合文件系统负责把镜像分层叠加。它不是一个轻量虚拟机,也没有自己的内核。

于是单机 Docker 很快会撞到墙:

问题

单机 Docker 的做法

编排系统要回答的

跑在哪台机器

人工 -p 指定

调度器按资源、亲和、污点决策

进程挂了怎么办

--restart 只能重启同一个

控制器在别的节点重建副本

多副本如何被发现

手工维护 IP 列表

Service + DNS 提供稳定入口

滚动升级与回滚

脚本编排容器

ReplicaSet 历史 + 发布策略

存储跟着谁走

-v 绑死宿主机路径

PV/PVC 与 CSI 动态供给

Kubernetes 的答案不是「更强的 Docker」,而是把「期望状态」写成对象存进 etcd,再让一组控制器不断把现实拉向期望。

备注

Docker 仍然有用

Docker 依然是本地构建镜像的好工具,Kubernetes 也照样运行 Docker 构建的镜像。变化的是运行时接口:自 1.24 起 dockershim 被移除,kubelet 通过 CRI 直接对接 containerd 或 CRI-O,Docker Engine 不再作为集群运行时被支持。

1.1.3. 标准、接口与实现

理解 Kubernetes 的关键,是分清「标准」和「实现」:

标准

约束什么

典型实现

OCI Image Spec

镜像的 layer、manifest、config 格式

buildkit、Harbor、Docker Registry

OCI Runtime Spec

如何把镜像变成运行中的进程

runc、crun、gVisor、Kata

CRI

kubelet 与容器运行时之间的 gRPC 接口

containerd、CRI-O

CNI

创建/删除 Pod 网络与 IP 的调用约定

Calico、Cilium、Flannel

CSI

卷的创建、挂载、快照的调用约定

csi-driver-nfs、Ceph CSI、EBS CSI

架构图

图:组件只与 API Server 对话,kubelet 只认 CRI,运行时不认识 Kubernetes API。

这种分层带来一个很实际的排错结论:Pod 起不来时,先判断问题在 API 层、调度层还是运行时层。kubectl describe 能看到的错误,通常已经不在应用代码里了。

1.1.4. 集群架构

组件

位置

一句话职责

kube-apiserver

控制平面

唯一读写 etcd 的入口,负责认证、授权、准入

etcd

控制平面

保存全部集群状态,强一致 KV

kube-scheduler

控制平面

为未绑定的 Pod 选择节点,只写绑定结果

kube-controller-manager

控制平面

运行 Deployment、Node、Job 等协调循环

kubelet

每个节点

让本节点的 Pod 实际状态靠近 spec

容器运行时

每个节点

通过 CRI 拉镜像、建沙箱、跑容器

kube-proxy

每个节点

用 iptables/IPVS 实现 Service 虚拟 IP

CNI 插件

每个节点

分配 Pod IP、配置路由、执行 NetworkPolicy

控制平面通常为奇数个节点,因为 etcd 需要多数派;工作节点可以随时增减。小集群里控制平面也会承载业务 Pod,但生产环境建议用污点把它们隔开。

1.1.5. Pod 网络模型

Kubernetes 对网络只提出四条要求:

  1. 每个 Pod 拥有集群内唯一的 IP;

  2. 同一 Pod 内的容器共享网络命名空间,用 localhost 互通;

  3. 任意两个 Pod 之间可以直接通信,不需要 NAT;

  4. 节点上的进程(kubelet、监控 agent)能直接访问 Pod IP。

架构图

由此可以推出四条需要分别排查的路径:同 Pod 内容器之间、Pod 之间、Pod 到 Service、集群外到 Service。很多「网络不通」的问题,其实卡在第二条之外的 DNS 或 Service 选后端阶段。

1.1.6. 声明式 API 与控制器

你提交的 YAML 是期望状态,不是命令。API Server 把它存进 etcd 后,控制器会进入一个永不停止的循环:

  • 读取期望状态(spec);

  • 读取实际状态(status 与集群现实);

  • 若有差异,采取行动;若无差异,什么都不做。

这叫水平触发(level-triggered):控制器不关心「发生了什么事件」,只关心「现在差多少」。因此它天然能处理重启、漏事件和重复执行。三个直接推论:

  • 删除一个由 Deployment 管理的 Pod,它会被重建,因为期望副本数没变;

  • 手工 kubectl edit 改掉的字段,可能被控制器改回去;

  • 自定义资源(CRD)+ 自定义控制器 = Operator,把「人类运维知识」编码进协调循环。

1.1.7. 生态地图

方向

代表项目

解决的问题

包管理

Helm、Kustomize

模板化、分环境渲染、版本化发布

扩展 API

CRD + Operator

把数据库、中间件的运维流程自动化

服务网格

Istio、Linkerd、Cilium Mesh

mTLS、流量治理、灰度、可观测性下沉

可观测性

Prometheus、Loki、Tempo、OpenTelemetry

指标、日志、链路三件套

GitOps / 策略

Argo CD、Flux、Kyverno

Git 为事实源持续同步,准入阶段强制规范

初学阶段不必全学,但要记住一条主线:Kubernetes 只提供机制,不提供策略。副本数、探针、配额、网络策略这些「策略」需要你自己写进对象里。

1.1.8. 实验:用 crictl 与 kubectl 观察运行时

1.1.8.1. 1. 找到 CRI 端点

sudo crictl info | head -20
cat /etc/crictl.yaml 2>/dev/null
sudo cat /var/lib/kubelet/kubeadm-flags.env | tr ' ' '\n' | grep runtime
# 没有配置文件时显式指定端点
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps

1.1.8.2. 2. 从 Pod 反查到容器

kubectl run web --image=nginx:1.27 --port=80
kubectl get pod web -o wide

POD_ID=$(sudo crictl pods -q --name web)
sudo crictl ps --pod "$POD_ID"
sudo crictl inspect "$(sudo crictl ps -q --pod "$POD_ID")" \
  | jq -r '.status.labels["io.kubernetes.pod.name"]'

最后一条命令会输出 web。这说明 kubectl 与 crictl 看的是同一批对象,只是视角不同:

视角

看到的东西

适合回答

kubectl

API 对象与 status

调度、准入、控制器、Service

crictl

沙箱、容器、镜像、日志文件

拉镜像、CRI 报错、容器是否真的在跑

journalctl -u kubelet

kubelet 与 CRI 的交互日志

挂载失败、探针执行、CNI 调用

1.1.8.3. 3. 检查控制平面健康

kubectl get --raw '/readyz?verbose'
kubectl get --raw '/livez?verbose'
kubectl -n kube-system get pods -o wide
kubectl -n kube-system logs kube-apiserver-<控制平面节点名> --tail=20

readyz?verbose 会逐项列出 etcd、informers、poststarthooks 的状态,是判断「API 慢/不可用」的第一步。

1.1.8.4. 4. 观察一次 Pod 启动

kubectl describe pod web | sed -n '/Events/,$p'
kubectl get events -A --sort-by=.lastTimestamp | tail -10
sudo crictl images | grep -E 'pause|nginx'
kubectl delete pod web

pause 镜像就是 Pod 沙箱:它持有网络命名空间,业务容器加入其中,因此同 Pod 内容器共享 IP。

1.1.9. 验证清单

  • kubectl get nodes 全部为 Ready

  • kubectl get --raw '/readyz?verbose' 所有检查项为 ok

  • kubectl -n kube-system get pods 中 CNI、CoreDNS、kube-proxy 均 Running

  • crictl info 能连上运行时,crictl ps 能看到业务容器

  • 能通过 CRI label io.kubernetes.pod.name 把容器映射回 Pod

  • 跨节点 Pod 互 ping 通,且 kubectl exec 能解析 Service 名

1.1.10. 排错表

现象

可能原因

排查入口

crictl 报 connection refused

端点错误或运行时未启动

systemctl status containerd、/etc/crictl.yaml

crictl ps 看不到 Pod

连到了另一个运行时 socket

对比 kubelet 的 --container-runtime-endpoint

Node NotReady

kubelet 异常或 CNI 未就绪

systemctl status kubelet、journalctl -u kubelet -n 100

Pod Running 但跨节点不通

CNI 未安装、路由或防火墙阻断

kubectl get pod -n kube-system、节点路由表

kubectl 返回 503

API Server 连不上 etcd

/readyz?verbose、etcd 日志与磁盘延迟

镜像拉取失败

仓库不可达、凭据缺失、沙箱镜像缺失

crictl pull、imagePullSecrets

1.1.11. 进阶挑战

  1. 用 crictl inspectp 找到 Pod 沙箱的网络命名空间路径,nsenter 进去执行 ip addr,验证 Pod IP 就在这个命名空间里。

  2. 在某节点停掉 kubelet,观察 Node 变为 NotReady 的时间,以及 Pod 多久后被驱逐重建(提示:node-monitor-grace-period 与污点驱逐)。

  3. 用 kubectl get --raw /metrics 找出 apiserver 的请求延迟指标,解释 apiserver_request_duration_seconds 的分位数含义。

  4. 画一张从 kubectl apply 到容器启动的时序图,标注 CRI、CNI、CSI 分别在哪个时刻介入。

1.1.12. 小结

  • 容器是内核能力的组合,编排解决的是调度、自愈、发现、存储与发布。

  • OCI 定义镜像与运行时,CRI/CNI/CSI 定义 kubelet 与基础设施插件的契约。

  • 所有组件通过 API Server 协作,etcd 是唯一状态源。

  • Pod 网络有四条路径,排错要逐条验证,不要一上来就怀疑 CNI。

  • 声明式 API + 控制器 = 水平触发的自愈系统;Operator 是把运维知识写成控制器。

1.1.13. 延伸阅读