从零开始学 Kubernetes

从容器概念到可验证的本地应用部署

Aias00

2026-10-21

写在开始之前

如果你第一次看到 Kubernetes,读作“库伯内特斯”,觉得它像一堵由英文缩写组成的墙,这本书就是为你准备的。Kubernetes 常简称 K8s,因为首字母 K 与末字母 s 之间有八个字母。它管理运行在一组机器上的容器,让应用按照你声明的要求运行。

本书目标是让你理解核心概念,在自己的电脑上部署一个网页,能找到常见故障的原因,并知道下一步该学什么。完成本书不等于能够独立维护生产集群;生产运维还涉及网络、备份、升级、审计和团队流程。

全书以 Kubernetes 官方文档为主要依据,并用自己的教学语言重新组织。末尾提供可点击的来源与延伸阅读。网络资料持续变化,安装时应以对应工具的官方页面为准。书中的 API 使用稳定版本,不把某个“最新版本号”写死。

怎样阅读

第一次学习请按顺序阅读第 1 至 9 章。每读完一章,先用自己的话回答章末问题,再执行实验。第 10 至 14 章用于补全能力。附录提供练习答案、命令速查和两周安排。

建议准备一台可以安装软件、连接互联网的电脑,至少约 4 GB 可供实验使用的内存和几 GB 空闲磁盘;实际需求取决于操作系统、容器运行时和虚拟化方式,8 GB 及以上整机内存更从容。镜像下载可能很慢,先区分网络问题与 Kubernetes 配置问题。

本书默认选择 Docker + kind + kubectl:Docker 提供容器运行环境,kind 在容器里创建练习集群,kubectl 向集群发命令。macOS、Windows 和 Linux 都能使用这条路线。命令示例以 Bash、zsh 或 WSL 的 shell 为准;Windows 原生 PowerShell 在多行输入和引号上存在差异,初学者可在 WSL 中统一操作。

发布前的自动化实验在新的 GitHub Actions Linux runner 中运行真实 kind 集群,固定 kind v0.33.0、Kubernetes v1.37.0 的官方 node 镜像摘要和同版本 kubectl。它验证网页服务、探针故障、回滚、存储与调度边界;实际证据和未测试项见公开仓库的交付记录。这个固定组合用于复现本次结果,不代替各平台安装文档,也不意味着所有 Kubernetes 版本行为完全相同。

实验仅针对你创建的本地 k8s-book 集群。执行任何写操作前,确认当前上下文,避免误操作工作环境。若你没有权限安装软件,可以先阅读概念,但动手结果必须在真正的集群中验证。

第 1 章 先弄懂:为什么需要 Kubernetes

从一个网站讲起

假设你写了一个小网站。最初只需在电脑上启动程序,就能从浏览器访问。后来要让别人访问,你把它放到服务器上。服务器重启怎么办?程序退出怎么办?访问量增加后要开三份程序怎么办?更新版本时如何尽量减少中断?这时你开始需要一套管理运行过程的方法。

容器先解决“把程序连同运行环境打包”的问题。镜像是一份可分发的模板,容器是用这份模板启动的进程实例。镜像像菜谱加备好的食材,容器像实际做出来的一份菜。这个比喻只帮助记忆:容器本质上仍然是进程,不是完整虚拟机。

Kubernetes 再解决“很多容器怎么持续运行”的问题。你告诉它希望有三份应用,它观察实际状态,如果只剩两份,就尝试补回一份。这个不断缩小实际状态与期望状态差距的过程,叫作协调。

它能做什么,不能替你做什么

Kubernetes 可以调度应用到节点,维持副本数,提供服务发现,组织滚动更新,并按配置执行健康检查。它不会自动修好程序逻辑错误,也不会自动替你设计数据库备份。配置了三个副本,也不代表应用天然高可用:如果三个副本都依赖同一处故障点,仍可能一起不可用。

如果你只是运行一个个人脚本,直接启动进程或者使用 Docker Compose 可能更合适。学习 Kubernetes 的价值,是理解应用规模扩大后,系统如何声明、调度和恢复。

你需要先掌握的终端知识

终端是用文字控制电脑的入口。cd 切换目录,ls 列出文件,mkdir 建目录。命令执行完没有输出,可能代表成功;遇到错误要读完整信息,尤其是最后几行。

mkdir k8s-practice
cd k8s-practice
pwd

pwd 显示当前目录。后续 YAML 文件都放在这里,避免不知道文件保存到了哪里。代码块内的命令不带提示符 $,可以直接复制。出现 <名称> 这样的占位符时,需要替换为实际值;不要原样执行。

本章自测:镜像与容器有什么区别?Kubernetes 可以保证程序永远没有 bug 吗?如果期望三个副本而实际只有两个,控制器会尝试做什么?

第 2 章 用一张地图认识集群

集群、节点、Pod

集群(Cluster)是一组受 Kubernetes 管理的计算资源。节点(Node)通常是一台虚拟机或物理机,在 kind 中则由容器模拟。节点提供 CPU、内存和网络等资源。

Pod 是 Kubernetes 调度和管理应用的基本单位。最常见的 Pod 只运行一个业务容器;某些紧密协作的容器也可以放在同一个 Pod 内,共享网络和指定的卷。你扩容的是多个 Pod 副本,而不是把同一应用的十个副本塞进一个 Pod。

集群
├── 控制平面:接收请求,记录状态,做出调度与协调决策
└── 工作节点
    ├── Pod A → 网页容器
    └── Pod B → 网页容器

图表示逻辑职责。本书的单节点 kind 集群会在同一个节点中承担控制平面和工作负载,适合练习;生产常会把这些职责分开,并配置冗余。

控制平面各组件在忙什么

kube-apiserver 是 API 入口。kubectl 提交资源描述,首先与它交互。etcd 保存集群的关键状态;它并不是供你随意存放业务数据的数据库。kube-scheduler 为还没有绑定节点的 Pod 选择合适节点。控制器持续观察状态,例如副本数不足时触发补建。

每个节点上的 kubelet 负责让分配给这个节点的 Pod 按要求运行,并上报状态。容器运行时负责实际拉取镜像、启动容器。网络组件让 Pod 互通;Service 转发常由 kube-proxy 或其他替代实现提供。

不要把 Docker 和 Kubernetes 的关系理解成“集群必须装 Docker”。现代 Kubernetes 通过 CRI 接口使用容器运行时,例如 containerd。本书用 Docker 是为了运行 kind 的节点容器。

声明式管理

命令式表达是“现在帮我启动三个实例”。声明式表达是“这个应用的副本数应该是三个”。后者允许系统持续纠偏。

YAML 是描述期望状态的常见文本格式,kubectl 是提交与查询这些描述的工具。它们不是 Kubernetes 本身。你可以把 YAML 理解为订单,API 是接单窗口,控制器是不断检查订单是否落实的工作人员。

本章自测:kubectl 在你的电脑上还是集群里运行?谁负责选择节点?Pod 被删除后,为什么有时会自动出现一个新的?

第 3 章 搭建你的练习环境

安装顺序与平台选择

首先安装并启动 Docker。macOS 与 Windows 常使用 Docker Desktop;Windows 需按其官方要求启用 WSL 2 等组件。Linux 可以安装 Docker Engine。Docker Desktop 的许可与平台要求请自行查看其官方说明。之后从官方安装页安装 kubectl 和 kind。

在 macOS 中,已经安装 Homebrew 的读者可以执行以下命令安装后两个工具;Docker 需先按官网完成安装并启动。

brew install kubectl kind

其他平台请使用上述官方页面中与你的操作系统、CPU 架构匹配的步骤。不要混用 Intel 与 ARM 下载包。请尽量让 kubectl 与集群版本一致;官方支持的 kubectl 偏差范围通常是相对 kube-apiserver 前后一个次版本,具体限制见文末版本偏差政策。

安装完成后检查:

docker version
kubectl version --client
kind version

docker version 应同时显示客户端和服务端信息。若只有客户端信息并提示连接不到 daemon,先启动 Docker,或检查 Linux 上的服务与用户权限。不要通过对所有用户开放 Docker socket 来绕过权限问题。

创建本地集群

kind create cluster --name k8s-book --wait 120s
kubectl config current-context
kubectl --context kind-k8s-book get nodes

创建完成后的上下文名应为 kind-k8s-book,节点最终应显示 Ready。首次创建要下载节点镜像,需要等待。若超过 120 秒,先看错误,不要连续创建多个同名集群。可以用 kind get clusters 查看已存在的集群,再运行 kind export logs --name k8s-book 导出日志辅助排查。

上下文(context)是 kubectl 使用的集群、用户及默认命名空间组合。它相当于你当前操作的目的地。之后每次练习前检查:

kubectl config use-context kind-k8s-book
kubectl config current-context

创建独立命名空间

命名空间(Namespace)是同一集群里对资源进行逻辑分组的方式。先创建本书专用区域:

kubectl create namespace k8s-book
kubectl get namespaces

若提示 AlreadyExists,说明已经创建,不必重复。全书在应用命令中显式写 -n k8s-book,帮助你看清资源所在的位置。命名空间本身不能代替完整的安全隔离机制。

完成标志:你能列出一个 Ready 节点,并能在命名空间列表中看到 k8s-book。如果这一步尚未完成,先解决环境问题再继续,后续所有实验都依赖它。

第 4 章 读懂第一份 YAML

四个核心字段

Kubernetes 对象常见的四个部分如下:

apiVersion: v1
kind: Pod
metadata:
  name: hello-pod
spec:
  containers:
    - name: web
      image: nginx:1.28
      ports:
        - containerPort: 80

apiVersion 表示资源所属 API 版本,不是你电脑上的 Kubernetes 版本。kind 表示资源类型。metadata 放名字、标签等标识信息。spec 描述你期望它怎样运行。查询时看到的 status 是系统报告的实际状态,通常不应复制进你维护的清单。

YAML 用缩进表示层级,使用空格,不用 Tab。同一层级保持对齐。containers 前的短横线表示列表,意味着一个 Pod 可以描述多个容器。书中使用的 nginx:1.28 固定到次版本系列,方便复现;标签可能继续指向该系列的新补丁。严格复现应固定镜像摘要,并在更新时验证安全性。

创建、观察与删除

把上述内容保存为 pod.yaml。文件必须是纯文本,扩展名是 .yaml,避免编辑器悄悄保存成 .yaml.txt。

kubectl apply -n k8s-book -f pod.yaml
kubectl get pods -n k8s-book
kubectl describe pod hello-pod -n k8s-book
kubectl logs hello-pod -n k8s-book

apply 创建或更新资源;get 看概况;describe 看细节和事件;logs 看容器输出。首次拉取镜像期间,Pod 可能显示 ContainerCreating,随后进入 Running。

想看网页,可以运行:

kubectl port-forward -n k8s-book pod/hello-pod 8080:80

保持这个终端开启,用浏览器访问 http://localhost:8080,应看到 NGINX 欢迎页。8080:80 表示本机 8080 端口转发到 Pod 的 80 端口。按 Ctrl+C 停止转发,Pod 本身仍在运行。

containerPort: 80 是端口声明,不会自动把网页暴露到你的电脑或公网;真正访问还需要端口转发或后面的 Service 等机制。

kubectl delete pod hello-pod -n k8s-book
kubectl get pods -n k8s-book

这个手工创建的独立 Pod 删除后不会自动回来,因为没有 Deployment 等上层控制器替它维持副本。这就是下一章的切入点。

本章自测:metadata.name 与容器 name 是同一个概念吗?为什么声明 containerPort 后还需要转发?

第 5 章 用 Deployment 管理应用

从“启动一次”变成“持续保持”

Deployment 管理无状态应用的副本和更新。它通过 ReplicaSet 维持 Pod 数量。一般情况下,你维护 Deployment 清单,不直接编辑它生成的 Pod,也不手工修改它管理的 ReplicaSet。

把以下内容保存为 deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-web
  template:
    metadata:
      labels:
        app: hello-web
    spec:
      containers:
        - name: web
          image: nginx:1.28
          ports:
            - containerPort: 80

template 是 Pod 模板。selector.matchLabels 告诉 Deployment 哪些 Pod 属于自己,必须与模板的标签匹配。标签(Label)是键值标记,如 app: hello-web;名字标识单个对象,标签则帮助筛选一组对象。Deployment 的选择器创建后通常不能直接修改,修改时要规划重建。

kubectl apply -n k8s-book -f deployment.yaml
kubectl rollout status deployment/hello-web -n k8s-book --timeout=120s
kubectl get deployments,replicasets,pods -n k8s-book

Deployment 中 READY 最终应为 2/2。Pod 名称带自动生成的后缀,这是正常现象。

亲手观察自愈

kubectl get pods -n k8s-book -l app=hello-web

从输出复制一个 Pod 名,将下面的占位符替换后执行:

kubectl delete pod <实际的Pod名称> -n k8s-book
kubectl get pods -n k8s-book -w

你会看到一个新名字的 Pod 被创建。-w 持续观察,Ctrl+C 结束。这不是旧 Pod 起死回生,而是控制器创建了替代品。Pod 的 IP 和名字都可能变化,因此应用之间不应该依赖某一个 Pod 的地址。

扩容和声明文件的一致性

kubectl scale deployment hello-web -n k8s-book --replicas=3
kubectl get pods -n k8s-book

这是一次命令式修改。如果 deployment.yaml 里仍是 replicas: 2,下次 apply 可能把它改回两个。长期管理要把你真正希望的副本数写回清单。练习完成后,将副本数恢复为两个,并重新应用文件。

本章自测:删除 Deployment 管理的一个 Pod 后发生什么?为什么它的替代品通常不沿用旧名字?

第 6 章 Service:给会变化的 Pod 一个稳定入口

名字稳定,后端可以变化

Deployment 创建的 Pod 会更换,而调用者希望访问一个固定名字。Service 提供一个稳定的服务抽象,通过标签选择对应的 Pod,并维护转发所需的后端信息。现代集群使用 EndpointSlice 记录后端地址。

把以下内容保存为 service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: hello-web
spec:
  selector:
    app: hello-web
  ports:
    - name: http
      port: 80
      targetPort: 80
  type: ClusterIP

port 是 Service 的入口端口,targetPort 是后端 Pod 的目标端口,两者可以不同。选择器必须对应 Pod 的标签。Service 和 Deployment 可以同名,因为它们是不同类型的对象。

kubectl apply -n k8s-book -f service.yaml
kubectl get services -n k8s-book
kubectl get endpointslices -n k8s-book -l kubernetes.io/service-name=hello-web
kubectl port-forward -n k8s-book service/hello-web 8080:80

再次访问 http://localhost:8080。这里的端口转发会选择一个后端 Pod,适合开发调试;它并不等价于完整测试 Service 负载均衡路径。后端 Pod 被替换时,可能需要重新运行转发命令。

从集群内部验证 DNS 和访问

在另一个终端运行以下一次性检查。它会创建一个临时客户端 Pod,执行请求后删除。

kubectl run netcheck -n k8s-book --rm -i --restart=Never \
  --image=busybox:1.37 -- wget -qO- http://hello-web

应该输出 NGINX 页面的 HTML。如果本次运行异常中断,临时 Pod 可能残留,可用 kubectl delete pod netcheck -n k8s-book 清理后重试。BusyBox 是用于此练习的小型工具镜像,不属于应用本身。

状态显示就绪与一次真实请求成功是两项证据。刚滚动更新时,服务后端与节点转发规则可能还在传播;可在有限时间内重复只读 HTTP 检查,确认页面实际恢复。持续失败就继续排查后端、DNS 与网络,不应只看 Ready 就宣布整条路径成功。

同一命名空间里的 Pod 通常可用 hello-web 访问该 Service。跨命名空间可用 hello-web.k8s-book。完整名称通常为 hello-web.k8s-book.svc.cluster.local,其中集群域名可由管理员配置,并非永远是 cluster.local。你的电脑一般不能直接解析这种集群内部名称。

常见访问方式

方式 主要用途 本地练习的注意点
ClusterIP 集群内访问 本书默认使用
NodePort 通过节点端口访问 kind 节点在容器里,还涉及宿主机映射
LoadBalancer 由外部负载均衡实现提供入口 裸机或 kind 通常需要额外实现,可能一直 Pending
Ingress 按 HTTP 域名、路径路由 必须安装相应控制器
port-forward 本地调试 依赖持续运行的 kubectl 会话

创建 Ingress YAML 不会自动获得可工作的入口。Ingress API 仍可使用,但其功能已冻结;学习新入口能力时可以进一步了解 Gateway API,同样需要相应控制器与配置。本书不要求把应用开放到公网。

本章自测:Service 通过什么找到 Pod?Service 没有后端时,应首先检查什么?

第 7 章 ConfigMap 与 Secret:把配置移出镜像

为什么配置不应全部写进镜像

同一份程序在开发和生产可能使用不同的欢迎语、日志级别或数据库地址。如果每改一行配置就重新打包镜像,维护会变得笨重。ConfigMap 存放非敏感配置,Secret 用来承载需要谨慎处理的数据,例如令牌或密码。

本章把网页内容放进 ConfigMap。保存为 configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-page
data:
  index.html: |
    <!doctype html>
    <html lang="zh-CN">
      <meta charset="utf-8">
      <title>我的 Kubernetes 小站</title>
      <h1>你好,Kubernetes!</h1>
      <p>这是我通过 ConfigMap 提供的第一个网页。</p>
    </html>

竖线 | 表示保留换行的多行文本。先创建 ConfigMap,下一章的完整 Deployment 会把它挂载到 NGINX 默认网页目录。

kubectl apply -n k8s-book -f configmap.yaml
kubectl get configmap hello-page -n k8s-book

两种常见注入方式

配置可以作为环境变量,也可以以卷挂载成文件。环境变量在容器启动时读取,修改 ConfigMap 后,已有容器的环境变量不会自动刷新,通常需要重新创建 Pod。普通 ConfigMap 卷的文件更新通常会经过一定延迟传播,应用是否重新读取还取决于自身行为;使用 subPath 挂载时,不会收到这类自动更新。

本书整目录挂载网页,不使用 subPath。初学阶段,为了明确结果,修改 ConfigMap 后可以执行滚动重启,让所有副本重新读取配置。

Secret 的边界

kubectl create secret generic demo-secret -n k8s-book \
  --from-literal=DEMO_TOKEN=only-for-practice

这里刻意使用无价值的练习字符串。真实机密不要直接留在命令历史、聊天记录或公开 Git 仓库中。Secret 的 data 常以 Base64 表示,Base64 是编码,不是加密。是否启用存储加密、谁有权限读取 Secret,都需要单独配置。

环境变量引用片段如下,阅读理解即可,不必加入本书网页项目:

env:
  - name: DEMO_TOKEN
    valueFrom:
      secretKeyRef:
        name: demo-secret
        key: DEMO_TOKEN

这段片段应放在某个容器定义下。它不是可以独立 apply 的完整资源。实验后可删除示例 Secret:

kubectl delete secret demo-secret -n k8s-book

本章自测:把密码做 Base64 后放进公开仓库安全吗?修改 ConfigMap 后,已有容器的环境变量会立即改变吗?

第 8 章 健康检查与资源:让系统知道应用的状态

Running 不等于能服务

容器进程存在,只能说明它在运行;它可能还在加载数据,也可能已经卡死。Kubernetes 提供三种探针。

readinessProbe(就绪探针)回答“现在能接流量吗”。失败时,Pod 会变为未就绪,通常不再作为 Service 的就绪后端;它本身不会因此被重启。

livenessProbe(存活探针)回答“是否需要重启这个容器”。连续失败达到阈值后,kubelet 会按重启策略处理。错误地把外部数据库偶发故障作为存活失败原因,可能造成大量无效重启。

startupProbe(启动探针)给慢启动应用留时间。配置了启动探针后,在它成功之前,就绪和存活探针不会开始工作。简单 NGINX 静态网页启动很快,本书完整示例只使用就绪和存活探针。

若两种探针都检查同一个路径,删除页面可能同时触发两种失败,不能据此推断“就绪失败导致重启”。分开验证时,应让就绪路径失败、存活路径仍成功,观察 Service 就绪后端移除而 restartCount 不变;再单独破坏存活路径,检查容器重启与恢复。随书自动化实验用另建的故障 Pod 完成这项隔离检查,主线网页清单仍保持易读。

requests 与 limits

requests 是调度时用于计算资源需求的重要依据。limits 约束运行时资源使用。CPU 的 100m 是 0.1 核,内存 64Mi 是 64 × 1024 × 1024 字节。

CPU 超出限制通常会被限流;内存达到限制后,进程可能被内核终止,出现 OOMKilled。节点内存压力也可能导致 Pod 被驱逐。资源不足时应结合事件和实际负载分析,不能见到重启就无限增大上限。

把前面知识合成完整应用

将之前的 deployment.yaml 替换为下面内容。这是完整文件,不是片段。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-web
spec:
  replicas: 2
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: hello-web
  template:
    metadata:
      labels:
        app: hello-web
    spec:
      containers:
        - name: web
          image: nginx:1.28
          ports:
            - name: http
              containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 2
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
          volumeMounts:
            - name: page
              mountPath: /usr/share/nginx/html
              readOnly: true
      volumes:
        - name: page
          configMap:
            name: hello-page

这个清单挂载整个网页目录,所以该目录中原镜像自带的文件会被挂载内容遮住。readOnly 表示容器不能通过这个挂载点改写配置。普通 NGINX 镜像的权限模型适合本地教学,不要据此假定已满足生产环境的非 root 与安全加固要求。

kubectl apply -n k8s-book -f configmap.yaml
kubectl apply -n k8s-book -f deployment.yaml
kubectl apply -n k8s-book -f service.yaml
kubectl rollout status deployment/hello-web -n k8s-book --timeout=120s
kubectl get pods -n k8s-book

保持 port-forward 运行,刷新浏览器。如果转发因 Pod 替换退出,重新执行第 6 章的转发命令。你应该看到中文欢迎页。

本章自测:就绪失败是否必然重启容器?100m 是一百核吗?为什么 ConfigMap 缺失可能导致 Pod 一直无法启动?

第 9 章 升级、回滚与配置更新

先区分三种改变

修改 Deployment 的 Pod 模板,例如镜像或探针,会触发新的滚动发布。只修改副本数是扩缩容,不会生成同样意义上的新模板版本。只修改外部 ConfigMap 内容,也不会自动产生 Deployment 的新模板修订。

本章不依赖另一个真实镜像版本,而是故意填错镜像标签,观察一次失败更新,然后恢复。务必先确认当前两个 Pod 均就绪。

kubectl rollout history deployment/hello-web -n k8s-book
kubectl set image deployment/hello-web -n k8s-book \
  web=nginx:this-tag-does-not-exist-k8s-book
kubectl rollout status deployment/hello-web -n k8s-book --timeout=45s
kubectl get pods -n k8s-book

预期更新超时,新 Pod 可能显示 ErrImagePull 或 ImagePullBackOff。因为设置了 maxUnavailable: 0 与 maxSurge: 1,且旧副本仍健康、集群资源足够,旧版本应继续服务。这是演示条件下的行为,不是对所有故障零中断的保证。

恢复到上一版

kubectl rollout undo deployment/hello-web -n k8s-book
kubectl rollout status deployment/hello-web -n k8s-book --timeout=120s
kubectl get pods -n k8s-book

undo 恢复之前的 Pod 模板。它不会撤销数据库迁移,也不会把 ConfigMap、Secret 或 PVC 一起恢复。因此“能够回滚镜像”不能等同于“整个系统能够回到过去”。长期管理应让 Git 中的声明文件也与目标版本一致。

修改网页

将 configmap.yaml 中的欢迎语改成“我完成了第一次配置更新”,然后:

kubectl apply -n k8s-book -f configmap.yaml
kubectl rollout restart deployment/hello-web -n k8s-book
kubectl rollout status deployment/hello-web -n k8s-book --timeout=120s

重新启动端口转发并刷新浏览器。rollout restart 通过修改 Pod 模板注解触发滚动重建。此处用它来得到清晰、可预期的练习结果;大型系统通常会使用配置版本化、校验和注解或应用热加载机制。

本章自测:为什么更新 ConfigMap 不等于产生新镜像版本?Deployment 回滚能恢复昨天的数据库数据吗?

第 10 章 存储:为什么文件会消失

三种生命周期

容器可写层里的文件依附于该容器实例。容器重建时,不应指望这些修改保留。emptyDir 卷跟随 Pod 生命周期,在同一 Pod 内的容器重启后可继续使用,但 Pod 删除后数据通常就消失。持久卷则用来把数据生命周期与单个 Pod 分离。

PersistentVolume(PV)代表集群中的一份存储资源。PersistentVolumeClaim(PVC)代表应用申请存储的请求。StorageClass 描述动态供应存储的类别和参数。你可以把它们理解为仓库空间、租用申请和租赁规格。

PVC 不是备份。存储所在磁盘、节点或整个本地集群被删除时,数据仍可能丢失。尤其在 kind 中,即使 PVC 叫“持久”,删除节点容器也可能让底层数据一并消失。

一个独立的存储实验

先检查是否有默认 StorageClass:

kubectl get storageclass

如果没有标为默认的 StorageClass,下面省略 storageClassName 的申请可能一直 Pending。此时先了解所用环境的存储供应方式,不要为了让状态变绿随便填写一个名字。kind 常提供本地存储供应,但应以实际输出为准。

保存为 storage.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: notes
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: notes-writer
spec:
  containers:
    - name: tools
      image: busybox:1.37
      command: ["sh", "-c", "sleep 86400"]
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: notes

一个文件中可以用 --- 分隔多个对象。ReadWriteOnce 通常表示卷可被单个节点以读写方式挂载,不代表永远只允许一个 Pod 访问,具体还取决于存储实现。

kubectl apply -n k8s-book -f storage.yaml
kubectl wait -n k8s-book --for=condition=Ready pod/notes-writer --timeout=120s
kubectl exec -n k8s-book notes-writer -- sh -c 'echo hello-storage > /data/note.txt'
kubectl exec -n k8s-book notes-writer -- cat /data/note.txt
kubectl delete pod notes-writer -n k8s-book
kubectl apply -n k8s-book -f storage.yaml
kubectl wait -n k8s-book --for=condition=Ready pod/notes-writer --timeout=120s
kubectl exec -n k8s-book notes-writer -- cat /data/note.txt

最后应仍能读到 hello-storage,因为你删除的是 Pod,PVC 还在。本轮 CI 用相同 PVC UID、不同 Pod UID 和实际读回文件验证这一点;没有删除 kind 节点或恢复整个集群,因此不能据此推断节点丢失后的数据恢复能力。动态供应使用 WaitForFirstConsumer 时,PVC 在使用它的 Pod 创建前保持 Pending 也可能正常。

若没有按预期启动,查看 kubectl describe pvc notes -n k8s-book 与 Pod 事件。清理时 kubectl delete -n k8s-book -f storage.yaml 会同时删除 Pod 和 PVC;回收策略为 Delete 时,底层卷也可能被删除,请先确认不需要数据。

数据库应该怎样学

StatefulSet 为有状态工作负载提供稳定身份、顺序和卷关联,但不会自动替你完成数据库复制、故障恢复和备份。新手先把网站应用作为无状态服务,把数据库可靠性作为单独专题学习。

本章自测:容器重启和 Pod 删除对 emptyDir 的影响相同吗?PVC 为什么不能代替备份?

第 11 章 排错:用证据缩小问题范围

一条通用路径

遇到问题,先确认目标,再看状态,再看事件,最后看应用输出。不要在不了解原因时连续删除资源;删除可能让关键现场消失。

kubectl config current-context
kubectl get pods -n k8s-book -o wide
kubectl get deployment,service -n k8s-book
kubectl get events -n k8s-book --sort-by=.metadata.creationTimestamp

接着选择具体 Pod,替换名称后检查:

kubectl describe pod <实际的Pod名称> -n k8s-book
kubectl logs <实际的Pod名称> -n k8s-book --tail=100
kubectl logs <实际的Pod名称> -n k8s-book --previous

--previous 查看同一 Pod 内上一次退出的容器实例日志,并非任意历史 Pod 的日志;如果没有上一次实例,报错是正常的。多容器 Pod 应增加 -c 容器名。应用没有把信息写到标准输出时,logs 不一定有你需要的内容。

常见状态与第一步

现象 常见方向 优先证据
Pending 资源不足、调度约束、PVC 未绑定 describe 中的调度事件
ContainerCreating 拉镜像、挂载、网络初始化 Pod 事件
ImagePullBackOff 镜像名错误、权限、网络 拉取镜像错误原文
CrashLoopBackOff 启动后反复退出 当前与 previous 日志、退出码
Running 但未就绪 探针失败或依赖未准备好 探针事件、应用日志
OOMKilled 容器内存不足等 容器终止状态、内存限制
Service 访问失败 选择器、端口、后端、DNS、网络策略 EndpointSlice 与实际 Pod 标签

这些字符串并非都属于 Pod 的正式 phase 字段。kubectl get pods 的 STATUS 栏综合展示多种信息;例如 CrashLoopBackOff 表示容器处于重启退避状态,而不是一种独立的 Pod phase。

一次可恢复的网络故障实验

保留正常 service.yaml,只在实时对象中把选择器临时改错:

kubectl patch service hello-web -n k8s-book \
  --type=merge -p '{"spec":{"selector":{"app":"wrong-app"}}}'
kubectl get endpointslices -n k8s-book -l kubernetes.io/service-name=hello-web

此时后端会消失或没有可用地址,集群内部对该服务的请求会失败。空后端可能表现为没有可用切片、endpoints: [] 或 endpoints: null;判断时应检查可用地址与实际请求,不能假设 API 永远返回非空数组。不要用已经连上的旧端口转发来判断,因为它可能仍指向之前选中的 Pod。

观察后恢复:

kubectl apply -n k8s-book -f service.yaml
kubectl get pods -n k8s-book --show-labels
kubectl get endpointslices -n k8s-book -l kubernetes.io/service-name=hello-web

这次实验让你把“服务不通”具体化为“Service 选择器没有选中正确的 Pod”。恢复后,重新运行第 6 章的临时客户端验证访问。

看懂四种常见报错

connection refused 可能表示目标端口没有进程监听,也可能出现在错误的 API 地址上,要先看它连接的是哪里。NotFound 先查名字、类型和命名空间。Forbidden 表示请求已被识别但权限不足,不应通过借用管理员凭据来草率绕过。no matches for kind 可能表示 API 版本或类型错误,也可能表示所需自定义资源尚未安装。

本章自测:镜像拉取失败时,先改探针有用吗?为什么排查 Service 要同时看选择器与 Pod 标签?

第 12 章 安全、监控与自动扩容的第一课

最小权限

RBAC 通过 Role、ClusterRole 和绑定对象管理访问权限。Role 通常描述命名空间范围的权限,RoleBinding 把权限授予用户、组或 ServiceAccount。ClusterRole 可以描述集群范围权限,也可通过 RoleBinding 在一个命名空间内授予适用的权限。

ServiceAccount 是工作负载向 Kubernetes API 表明身份的一种机制,不是普通网页登录账号。不要为了方便给每个应用绑定 cluster-admin。不需要访问 API 的应用,可以考虑在 Pod 中设置 automountServiceAccountToken: false。

网络策略(NetworkPolicy)用于表达允许哪些流量,但只有网络插件支持并执行时才有效。创建了策略对象,并不自动证明隔离已生效;要实际测试允许与拒绝两类流量。命名空间也不等于网络防火墙。

镜像与运行权限

生产实践通常包括可信镜像来源、漏洞扫描、固定摘要、以非 root 运行、减少 Linux capabilities、限制权限提升及只读根文件系统。不能把这些字段不加测试地塞入任意镜像:有些镜像需要写临时目录或使用特定用户,应选兼容镜像并明确配置。

本书使用常见 NGINX 镜像突出概念,未完成生产安全基线。把学习项目投入真实业务前,应先调整镜像与运行权限,再进行验证。

指标、日志与事件各回答什么

指标描述随时间变化的数值,如 CPU、内存、请求量。日志记录应用输出的细节。事件描述 Kubernetes 观察到的调度、拉取、启动等动作,通常不是长期存储的审计档案。

kubectl top pods -n k8s-book

这个命令依赖 Metrics API,常由 Metrics Server 提供。kind 不一定预装,所以报 Metrics API not available 不等于应用故障。本书不要求为了完成基础实验安装监控栈。生产中通常还需要长期指标存储、日志收集、告警和追踪。

HPA 为什么不能凭空扩容

HorizontalPodAutoscaler 按指标调整副本数。以 CPU 利用率为目标时,计算通常依赖相应容器的 CPU requests;没有请求值,可能无法正确计算利用率。它还需要可用的指标接口。

HPA 增加的是 Pod,不会自动给集群增加机器。节点没有余量时,新 Pod 仍会 Pending。节点自动扩容通常属于另一套机制。初学阶段先学会手动扩缩容,再学习 HPA 的采样、稳定窗口和行为配置。

本章自测:HPA 与增加节点是一回事吗?创建 NetworkPolicy 就一定会阻断流量吗?

第 13 章 结业项目:交付你的小站

你要完成的成果

不看前面解释,尝试用一个独立命名空间运行两份网页服务,网页内容来自 ConfigMap,经 Service 提供集群内入口,并能通过本机端口转发访问。Deployment 应包含资源请求、限制、就绪与存活探针。你还要证明自己能恢复失败更新、定位一次错误选择器,并清理资源。

随书 examples/project/ 提供完整起点:namespace.yaml、configmap.yaml、deployment.yaml 和 service.yaml。EPUB 内也已列出所有关键清单;单独拿到电子书的读者,可按第 7、8、6 章依次手工创建相应文件。

在书籍项目目录使用配套文件时:

kubectl config use-context kind-k8s-book
kubectl apply -f examples/project/namespace.yaml
kubectl apply -n k8s-book -f examples/project/configmap.yaml
kubectl apply -n k8s-book -f examples/project/deployment.yaml
kubectl apply -n k8s-book -f examples/project/service.yaml
kubectl rollout status deployment/hello-web -n k8s-book --timeout=120s
kubectl port-forward -n k8s-book service/hello-web 8080:80

若你手工建立了工作目录,就把 examples/project/ 路径替换成实际文件位置。网页出现后,保存一次 Pod 列表和浏览器截图作为自己的学习记录。

验收清单

  1. kubectl get deployment hello-web -n k8s-book 显示两个就绪副本。
  2. 浏览器能看到你自己写的中文欢迎语。
  3. 从集群内访问 http://hello-web 能得到相同页面。
  4. 删除一个 Pod 后,控制器会补建新 Pod。
  5. 错误镜像实验失败后,能通过回滚恢复到健康状态。
  6. Service 选择器故意写错时,能从后端和标签找到原因,再恢复。
  7. 能解释为什么这个项目还不是生产部署。

本轮发布已在隔离 kind 集群中实际完成这些验收,并额外检查 HTTP 就绪失败不重启、存活失败后重启、PVC 换 Pod 保留、CPU 请求过大和节点选择器不匹配时的调度失败。自动化入口为 lab/run_kind.py,配套实验包可下载源文件;运行记录见 交付记录。

这些结果来自记录中的 CI 版本与场景,没有在你的电脑上替你执行。没有验证 HPA、NetworkPolicy 执行、多节点容错、节点丢失后的存储恢复或生产安全基线。请继续在自己的隔离环境复现,不把“能部署小站”当作生产验收。

清理,不留下忘记的资源

先停止所有 port-forward 会话,再删除练习命名空间。下面命令会删除该命名空间内的所有对象,包括你创建的 PVC,务必确认里面只有可丢弃的实验数据。

kubectl config current-context
kubectl delete namespace k8s-book

如果整个 kind 集群都不再需要:

kind delete cluster --name k8s-book
kind get clusters

这会删除本地练习集群及其节点容器。不要把它当成“暂停”,数据可能无法找回。只想暂时休息时,可以先不删除集群。

第 14 章 接下来怎样学习

两周学习安排

天数 学习任务 当天产出
第 1 天 阅读第 1、2 章 画出集群、节点、Pod 的关系
第 2 天 第 3 章安装环境 一个 Ready 节点
第 3 天 第 4 章 YAML 与 Pod 能打开欢迎页
第 4 天 第 5 章 Deployment 解释并演示自愈
第 5 天 第 6 章 Service 完成集群内访问
第 6 天 第 7 章配置 写出自己的网页
第 7 天 复习与整理笔记 用自己的话解释五个概念
第 8 天 第 8 章探针与资源 两个健康副本
第 9 天 第 9 章升级回滚 恢复错误镜像更新
第 10 天 第 10 章存储 重建 Pod 后读到原文件
第 11 天 第 11 章排错 一份故障原因记录
第 12 天 第 12 章安全与指标 解释三条生产差距
第 13 天 第 13 章结业项目 独立完成验收
第 14 天 清理和回顾 写出下一阶段计划

如果安装用了一整天,很正常。不要因为没赶上表格而跳过基础。学习速度以你能解释和复现为准。

按需求继续,而不是按名词数量继续

要部署更多应用,学习 Job 与 CronJob、Helm 和 Kustomize。Job 管理以完成为目标的任务,CronJob 按时间计划创建 Job;它们与长期运行的 Deployment 目标不同。Helm 用模板和包组织部署,Kustomize 基于基础清单做组合与覆盖。

要理解线上稳定性,学习 PodDisruptionBudget、亲和性与反亲和性、拓扑分布约束、优雅终止以及节点维护。PodDisruptionBudget 约束某些自愿中断,不会阻止所有故障,也不代替副本与跨节点设计。

要运行真正业务,学习镜像构建与发布流水线、GitOps、入口控制器或 Gateway API、TLS、DNS、监控告警和备份恢复。要准备认证,可以在熟悉基础操作后,再结合官方 CKA 或 CKAD 的能力范围安排练习。

最有效的练习方式是每次只改变一个因素,先预测结果,再运行命令,用观察检验预测。能讲清楚失败原因,比背下十条命令更接近掌握。

附录 A 自测参考答案

第 1 章:镜像是可分发的模板,容器是运行实例。Kubernetes 不保证程序没有 bug。控制器会尝试创建替代实例,前提是资源与其他条件允许。

第 2 章:本书的 kubectl 在你的电脑上运行,通过 API 与集群通信。调度器选择节点。由控制器管理的 Pod 被删除后,控制器为维持期望状态创建替代品。

第 4 章:对象名字与容器名字属于不同层级。端口声明不会创建对外网络入口,所以还需要转发或服务暴露机制。

第 5 章:Deployment 会通过 ReplicaSet 补建 Pod。新 Pod 是新对象,通常会有新名字和地址。

第 6 章:Service 根据标签选择器找到后端;先检查选择器、Pod 标签、就绪状态和 EndpointSlice,再检查端口与网络路径。

第 7 章:Base64 可以轻易解码,不能保护公开的密码。通过环境变量注入的值不会因 ConfigMap 更新而自动改变。

第 8 章:就绪失败通常使 Pod 暂停接收 Service 流量,不会单凭该探针触发重启。100m 是 0.1 CPU 核。挂载所引用的 ConfigMap 不存在时,容器所需卷无法准备完成。

第 9 章:ConfigMap 是独立对象,修改它不等于修改镜像或 Deployment 模板。Deployment 的回滚不负责数据库恢复。

第 10 章:emptyDir 可以跨同一 Pod 内容器重启保存,但不能跨 Pod 删除保存。PVC 不提供独立备份副本与恢复流程。

第 11 章:镜像都未拉取成功时,修改探针通常不能解决问题。选择器与标签不匹配,Service 就无法找到正确后端。

第 12 章:HPA 调整 Pod 数量,节点扩容由其他机制负责。NetworkPolicy 是否生效取决于网络实现,还需要实际验证。

附录 B 命令速查与术语

命令速查

# 我在操作哪个集群?
kubectl config current-context
# 节点状态
kubectl get nodes
# 创建或更新声明
kubectl apply -n k8s-book -f deployment.yaml
# 查应用、服务和 Pod
kubectl get deployments,services,pods -n k8s-book
# 查详细状态与事件;替换占位符后执行
kubectl describe pod <Pod名称> -n k8s-book
# 查最近日志
kubectl logs deployment/hello-web -n k8s-book --tail=100
# 观察一次发布是否完成
kubectl rollout status deployment/hello-web -n k8s-book --timeout=120s
# 调试访问
kubectl port-forward -n k8s-book service/hello-web 8080:80

logs deployment/hello-web 通常选择其中一个 Pod,并不自动汇总所有副本。需要按标签汇总时,应查阅 logs 的选择器、并发和容器参数,并留意输出所属副本。

小白术语表

术语 可以先这样理解
API 程序之间约定的交互接口
镜像 Image 应用及运行所需内容的分发模板
容器 Container 从镜像启动的运行实例
Cluster 一组受 Kubernetes 管理的资源
Node 为 Pod 提供运行资源的节点
Pod 一个或多个紧密相关容器的运行单位
Deployment 管理无状态应用副本和更新的控制器资源
ReplicaSet 维持一组 Pod 副本数的资源
Service 为一组后端提供稳定访问抽象
Namespace 资源的逻辑分组范围
Label 便于分组和筛选的键值标签
Selector 按条件选择对象的规则
ConfigMap 非敏感配置对象
Secret 承载需要谨慎处理的数据的对象
PVC 应用的持久存储申请
Ingress HTTP/HTTPS 入口路由规则资源
Controller 持续推动实际状态接近期望状态的程序
Context kubectl 当前使用的集群、身份与命名空间组合

附录 C 资料来源与延伸阅读

本书通过检索并获取以下官方资料组织内容。正文是原创教学编排,并非官方文档的逐页翻译。链接内容可能更新,读者应优先查看与实际集群版本匹配的文档。配套 research/sources.json 记录抓取日期、标题和结果,research/ 保留用于核对的网页快照。

  1. Kubernetes 概述:用途、能力与边界,对应第 1 章。
  2. 集群组件:控制平面、节点组件,对应第 2 章。
  3. Kubernetes 基础教程:部署、查看、服务、扩容、更新的学习路径。
  4. 安装工具:kubectl 等本地工具,对应第 3 章。
  5. kind 快速开始:创建本地集群和运行要求。
  6. Docker 安装入口:容器工具安装。
  7. Deployment:副本、更新与回滚,对应第 5、9 章。
  8. Service:服务类型、端口与选择器,对应第 6 章。
  9. ConfigMap:配置注入与更新限制,对应第 7 章。
  10. Secret:数据表示与安全注意点,对应第 7、12 章。
  11. 存活、就绪与启动探针:探针行为,对应第 8 章。
  12. 容器资源管理:requests、limits 与单位,对应第 8 章。
  13. 持久卷:PV、PVC、回收与访问模式,对应第 10 章。
  14. 调试应用:按现象收集证据,对应第 11 章。
  15. RBAC 良好实践:最小权限,对应第 12 章。
  16. Ingress:控制器前提与 API 状态,对应第 6 章。
  17. 版本偏差政策:kubectl 与控制平面版本关系。

中文官方资料入口:Kubernetes 中文文档。部分中文页面的更新可能滞后于英文页面,涉及版本差异时可对照阅读。遇到陌生术语,先回到术语表,然后只阅读与你当前问题有关的一小段,不必一次读完全部文档。