镜像只是静态交付物,Pod 才是 Kubernetes 调度和运行容器的最小单位。这一章会把 taskboard:1.0.0 送进 kind 节点,创建一个裸 Pod,再沿着状态、事件、日志、进程身份和 HTTP 响应逐层确认它是否真的可用。最后会删除这个裸 Pod,为下一章交给 Deployment 管理做好准备。
进入本章时,welearn-course 节点 Ready,Docker 侧已有经过独立容器验收的 taskboard:1.0.0,但节点内的 containerd 还不一定拥有这个镜像,Kubernetes API 中也没有 taskboard Namespace。完成后,Namespace 和 k8s 清单会保留,镜像已进入 kind 节点;用于观察的裸 Pod 会被明确删除,因此业务副本数回到零。
本章的重点不是“终于把状态变成 Running”。我们要学会沿着 API 接受、调度、镜像、容器、日志、身份和 HTTP 响应逐层取证。只有知道每一层证明什么,下一章看到控制器自愈时,才能分清是 Pod 自己复活,还是上层对象创建了替代实例。
容器是运行时创建的进程隔离单元,Pod 是 Kubernetes API 中的调度与生命周期单元。Scheduler 选择节点时选择的是整个 Pod;kubelet创建和停止时也围绕 Pod 规格工作。一个 Pod 可以包含一个或多个容器,这些容器共享 Pod 的网络身份,也可以共享声明的卷,并被共同调度到同一节点。
TaskBoard Pod 目前只放一个 api 容器。多容器 Pod 适合真正必须共享生命周期和网络的紧密协作者,例如给主程序提供代理或辅助处理的 sidecar。不要为了“把相关服务放一起”就把 API 和 Redis 塞进同一个 Pod:二者扩缩容节奏不同,Redis 还需要独立稳定存储,拆成不同工作负载更容易管理。
Pod 有自己的 UID、IP、节点绑定和生命周期。容器崩溃时,kubelet可以按重启策略在同一 Pod 内重启容器;节点丢失或上层控制器创建替代 Pod 时,新对象会获得新的 UID 和通常不同的 IP。稳定服务不能把某个 Pod 当作永久服务器。
这也是 Kubernetes 的核心转变:我们不维护“这台名为 taskboard-pod 的机器”,而是声明“需要符合某个模板的工作负载实例”。本章使用固定名称的裸 Pod,是为了把生命周期看清;真实长期服务将交给 Deployment,并通过 Service 获得稳定访问入口。
直接创建 Pod 可以完整观察 API 校验、调度、镜像解析、容器启动和状态上报,却没有上层控制器维持副本。删除它,相当于从 API 中删除这份期望状态,集群不会猜测你还想要一个替代者。
因此裸 Pod 常用于短暂诊断或学习,不是 TaskBoard 的最终部署方式。课程有意在本章末尾删除它,让下一章的 Deployment 从清楚的零副本状态开始。
Namespace 给一组对象划定名称与管理边界。TaskBoard 后续的 Deployment、Service 和 Redis 都会进入 taskboard Namespace,避免和集群系统组件混在一起。
同一种 namespaced 资源在同一 Namespace 内不能重名,但不同 Namespace 可以各有一个同名 Pod。命令若漏写 --namespace,kubectl 通常使用 context 的默认 Namespace,容易出现“明明创建了却查不到”的错觉。课程在清单写 metadata.namespace,在查询命令也显式写 Namespace,让目标边界可见。
Namespace 还支持按边界应用配额、默认资源规则、RBAC 与 Pod Security 标签,不过创建 Namespace 本身不会自动完成这些安全配置。本章先建立对象归属,后面再逐步增加策略。
创建目录是本章第一个变更。在它之前,命令会恢复 COURSE_ROOT、课程 PATH 和独立 KUBECONFIG,精确校验 .course-owned,再读取 current context。只有 context 逐字等于 kind-welearn-course 才继续;标记或 context 不匹配会执行 exit 1,所以同一命令块中的 mkdir 不会发生,也不会把后续 apply 发给其他集群。
守卫通过后,mkdir -p 只在课程树中创建 k8s,不改变 app;cd 把后续清单路径统一为 k8s/...;pwd 在写清单前再次核对范围。后面统一从这里用 app/ 和 k8s/ 相对路径,即使切换章节也不会混淆源码与清单位置。预期先看到 context 校验通过,再看到以 welearn-kubernetes-course/taskboard 结尾的路径。
COURSE_ROOT="$HOME/welearn-kubernetes-course"
if ! { [ -d "$COURSE_ROOT" ] && \
printf 'welearn-kubernetes-course\n' | cmp -s - "$COURSE_ROOT/.course-owned"; }; then
echo '停止:课程目录标记校验失败,未执行任何变更' >&2
exit 1
fi
export PATH="$COURSE_ROOT/bin:$PATH"
export KUBECONFIG="$COURSE_ROOT/welearn-course.kubeconfig"
if ACTUAL_CONTEXT="$(kubectl config current-context 2>&1)"; then
:
else
CONTEXT_RC=$?
printf '停止:无法读取 current context,rc=%s\n%s\n' \
"$CONTEXT_RC" "$ACTUAL_CONTEXT" >&2
exit 1
fi
if [ "$ACTUAL_CONTEXT" != 'kind-welearn-course' ]; then
echo "停止:当前 context 是 ${ACTUAL_CONTEXT:-<空>},期望 kind-welearn-course" >&2
exit 1
fi
echo 'context=kind-welearn-course,目标校验通过'
mkdir -p "$COURSE_ROOT/taskboard/k8s"
cd "$COURSE_ROOT/taskboard"
pwdcontext=kind-welearn-course,目标校验通过
<项目工作目录>/welearn-kubernetes-course/taskboard路径前缀是动态值,结尾一致即可。
这个结果只证明文件工作目录正确,与 kubectl 当前 Namespace 无关。读取 context 的命令若遇到 kubeconfig 文件损坏、权限不足或其他错误,会保留原始错误并非零停止,不会把故障吞成“空 context”。若结尾是 taskboard/app,需要先 cd ..;若路径完全不同,回到 $HOME/welearn-kubernetes-course/taskboard 再继续。把清单放在 k8s 目录是项目约定,不会让 Kubernetes 自动发现它们,只有显式 kubectl apply 才会写入 API。
请新建 k8s/namespace.yaml。保存文件本身不会创建 Namespace。apiVersion: v1 表示使用核心 API 组稳定版本,kind: Namespace 决定校验结构,metadata.name 是集群范围内的唯一名称。Namespace 本身不是 namespaced 资源,所以清单没有 metadata.namespace。标签 app.kubernetes.io/part-of: taskboard 是可查询的项目归属元数据,不会改变网络流量。
apiVersion: v1
kind: Namespace
metadata:
name: taskboard
labels:
app.kubernetes.io/part-of: taskboardkubectl apply 采用声明式写入:kubectl读取文件,识别对象键(类型、名称和 Namespace),向 API Server 发出创建或补丁请求。API Server 校验后把对象写入集群状态。对象不存在时创建,已存在时把受管理字段调整到清单声明的状态。此时执行,是因为后续 Pod 清单显式引用 taskboard;若 Namespace 不存在,Pod 会被 API 拒绝。命令会写入一个 Namespace,不会创建 Pod。预期 Namespace 被创建。
kubectl apply --filename k8s/namespace.yamlnamespace/taskboard created若重复执行,输出会变成 namespace/taskboard unchanged,这正是声明式操作可以安全重放的表现。
created 证明 API 已保存 Namespace;它不说明任何应用已运行。unchanged 说明现有对象与本次 apply 管理的字段没有差异,configured 则表示发生更新。如果出现 AlreadyExists,通常是用了 create 而非 apply;如果提示连接错误,先检查 KUBECONFIG 和 context;如果 YAML 字段不合法,API 会在创建前拒绝。下一条诊断命令是 kubectl get namespace taskboard --show-labels。
上一章构建的镜像存在于 Docker 的镜像存储中,kind 节点内的 containerd 看不到它。kind load docker-image 会把指定镜像导入集群节点,因此 Pod 可以在不推送远程仓库的情况下使用它。
正常的跨机器交付流程会把镜像推送到仓库,节点按镜像引用拉取。课程只有一个 kind 节点,不需要建立仓库凭据和推送流程,于是用 kind 的导入能力缩短路径。这个方法只适用于 kind 工作流;生产节点不能依靠开发者终端逐台导入镜像。
导入时要区分两个同名引用:Docker 侧的 taskboard:1.0.0 是源,节点 containerd 的镜像存储是目标。kind 会把镜像内容复制到所选集群节点,镜像不会因此变成公网可用,也不会修改 pod.yaml。
下面把固定版本镜像加载到 welearn-course。kind load docker-image 指定源镜像,--name 防止在有多个 kind 集群时导入错误目标。命令会读取 Docker 镜像并写入 welearn-course-control-plane 的 containerd;不会创建 Kubernetes API 对象。此时执行,是因为镜像已在第三章验收,节点也已 Ready。预期输出指出镜像正在进入控制平面节点;镜像 ID 由实际内容产生。
kind load docker-image taskboard:1.0.0 --name welearn-courseImage: "taskboard:1.0.0" with ID "sha256:6b0269772bb82d567d73c41ee795e4f5a9940314cee2e22db15b144590cde2e3" not yet present on node "welearn-course-control-plane", loading...如果输出说镜像已经存在,也代表节点可以使用它。清单稍后设置 imagePullPolicy: IfNotPresent,让 kubelet 优先使用这个已导入的固定版本。
示例中的完整 SHA256 是一次实际构建得到的内容 ID;源码、基础镜像或构建元数据变化都可能使它不同。稳定关系是 taskboard:1.0.0 已存在于 welearn-course-control-plane。若提示 Docker 中找不到镜像,先运行 docker image inspect taskboard:1.0.0,回到第三章构建;若找不到集群,用 kind get clusters 与 kubectl config current-context 检查名称。
IfNotPresent 并不验证节点现有同名标签是否与 Docker 侧最新构建相同。开发时若反复覆盖同一标签,可能运行旧内容。标签本身可以被重新指向其他镜像,并非技术上不可变;课程约定不覆盖 1.0.0,后续更新另用 2.0.0。只有内容摘要按镜像内容寻址,生产交付应优先记录并固定摘要。
接下来把“怎样运行 TaskBoard”写成 Pod 对象。保存文件只会改变项目目录;真正提交要等到 kubectl apply。请新建 k8s/pod.yaml,保存下面的完整清单。
apiVersion: v1
kind: Pod
metadata:
name: taskboard-pod
namespace: taskboard
labels:
app: taskboard
spec:
restartPolicy: Always
containers:
- name: api
image: taskboard:1.0.0
imagePullPolicy: IfNotPresent
ports:
-
这份 YAML 可以按四层来读:
apiVersion 与 kind 告诉 API Server 按核心 API 的 Pod 结构校验数据。metadata 给对象名称、Namespace 和标签。spec.containers 声明容器镜像、端口和运行配置。restartPolicy: Always 告诉当前节点上的 kubelet:只要这个 Pod 仍存在,容器退出后就尝试在同一 Pod 内重启。fieldRef 使用 Downward API,把 Pod 自己的名称与 Namespace 注入进程;镜像因此不必预先知道将来叫什么。metadata.name: taskboard-pod 是 API 对象名,containers[].name: api 是 Pod 内的容器名。二者不同:kubectl logs 用 Pod 名定位对象,再用 --container api 选择容器。多容器 Pod 中,容器名必须在 Pod 内唯一。
image: taskboard:1.0.0 告诉 kubelet需要哪份交付物,IfNotPresent 允许使用刚导入节点的镜像。ports[].containerPort: 8080 记录应用监听约定,方便工具和后续 Service 引用;它既不修改 Python 监听端口,也不创建宿主端口映射。
restartPolicy: Always 管理的是一个现存 Pod 里的容器。容器进程崩溃时,kubelet可以重新创建容器,Pod 的名称和 UID 不变,RESTARTS 增加;删除整个裸 Pod 后,对象已经不存在,restartPolicy 没有作用。要在 Pod 被删除或节点失效后得到新 Pod,需要 Deployment 等控制器维持更高一层的副本期望。
env[].value 直接注入欢迎语。valueFrom.fieldRef 属于 Downward API:kubelet在创建容器配置时,从当前 Pod 对象读取 metadata.name 和 metadata.namespace,形成环境变量。这样同一镜像复制成多个 Pod 后,每个实例都能报告自己的身份。
清单暂时没有资源 requests/limits、探针和 Pod 级 securityContext。这不是生产模板,而是控制变量:先证明最小 Pod 的镜像、身份和网络链路,再在后续章节逐项增加约束。镜像内部仍默认非 root,运行身份会通过 exec id 验证。
containerPort: 8080 只是描述容器监听端口,不会自动让终端访问到 Pod。稍后仍需用 kubectl port-forward 建立临时通道。
提交清单前,预期 Namespace 已存在、镜像已加载。下面创建 Pod;API Server 接受清单并不等于应用已经就绪,因此下一步还要等待条件。
kubectl apply --filename 会读取一个对象并请求 API Server。请求成功后,API 中出现期望 Pod,Scheduler、kubelet和容器运行时才开始异步工作。命令会创建业务对象和随后产生的容器进程;它不会创建 Service,也不会让回环地址直接访问 Pod。此时执行,是因为 Namespace 与节点镜像两个前置状态已经有证据。

kubectl apply --filename k8s/pod.yamlpod/taskboard-pod createdcreated 只证明 API 接受并持久化对象。返回时 Pod 可能仍是 Pending,不能把它当作应用启动完成。如果提示 Namespace NotFound,回到 namespace apply;若出现镜像字段校验错误,修复 YAML;若提示 Forbidden,查看错误中的 RBAC 或准入原因。对象已创建但后续不 Ready 时,下一条命令应是 kubectl describe pod taskboard-pod -n taskboard,而不是反复 apply。
Pod 创建后会依次经历调度、创建容器和启动进程。没有自定义就绪探针时,容器进入运行状态后 kubelet 会把它标为 Ready。kubectl wait 把“等到 Ready=True”变成一个明确条件,而不是凭固定睡眠时间猜测。
Pod 状态不是单一开关。Scheduler 写入节点绑定后 PodScheduled=True;kubelet准备 sandbox 与网络后出现 PodReadyToStartContainers=True;初始化完成后 Initialized=True;容器就绪后 ContainersReady=True,最终聚合为 Ready=True。本章没有 readinessProbe,所以容器运行即被视为就绪;第七章会让就绪条件真正请求 /readyz。
下面最多等待 90 秒。--for=condition=Ready 观察 Pod status.conditions,--namespace 锁定目标边界,--timeout 防止终端无限等待。它只读取和等待状态,不会替你修复对象。预期输出 condition met,否则应该直接查看事件而不是继续访问应用。
kubectl wait \
--for=condition=Ready \
pod/taskboard-pod \
--namespace taskboard \
--timeout=90spod/taskboard-pod condition met这行证明 API 中该 Pod 的 Ready 条件已变为 True。它没有证明 HTTP 内容正确,也没有证明未来 Service 选择器正确,所以还要继续取证。若超时,先看 kubectl get pod -n taskboard 的 STATUS:Pending 常查调度事件,ErrImagePull/ImagePullBackOff 查镜像名与节点存储,CrashLoopBackOff 查当前和上一次容器日志。
现在查看调度节点与 Pod IP。--output wide 在默认列之外显示 IP 和 Node,读取的是刚才同一 Pod 的 API 状态。此时查询,是为了把 Ready 条件与调度落点、网络身份联系起来。命令不进入容器。预期 READY 为 1/1、STATUS 为 Running。
kubectl get pod taskboard-pod --namespace taskboard --output wideNAME READY STATUS RESTARTS AGE IP NODE
taskboard-pod 1/1 Running 0 16s 10.244.0.5 welearn-course-control-plane这里的 AGE 与 Pod IP 10.244.0.5 都是本次调度产生的动态值,重新创建后可能变化。名称、1/1 Running 和目标节点才是当前要核对的稳定关系。
1/1 表示一个已就绪容器除以一个声明容器;它不是副本数。Running 表示 Pod 阶段,RESTARTS 0 表示当前容器尚未被 kubelet重启,NODE 证明 Scheduler 绑定到唯一 kind 节点。IP 属于 Pod 网络,不是稳定服务地址。
若 READY 为 0/1 但 STATUS 是 Running,说明进程存在却没有就绪;本章通常是启动瞬间的短暂状态,后续有探针时则要检查 /readyz。下一条通用诊断命令是 kubectl get pod taskboard-pod -n taskboard -o yaml,它能看到完整 conditions 和容器状态,但不应通过手改 status 解决问题。
kubectl get 适合快速看表格,kubectl describe 则把对象规格、当前状态和事件时间线放在一起。排查 Pending、ImagePullBackOff 或反复重启时,Events 往往能直接说明调度、拉取或启动发生了什么。
describe 是 kubectl 组织出的可读视图,不是 API 中单独保存的“诊断报告”。它读取 Pod,并查询相关事件,把标签、节点、容器配置、Conditions 和 Events 拼在一起。事件有保留期限和聚合行为,不能当永久审计日志;排查当前启动问题时却非常有效。
下面描述 Pod。命令只读取 taskboard Namespace 中固定名称对象,不改变运行状态。此时执行,是为了解释 wait 为何成功,而不是只相信一行 condition met。预期容器状态为 Running、Ready 为 True,事件按顺序包含调度、容器创建和启动。
kubectl describe pod taskboard-pod --namespace taskboardName: taskboard-pod
Namespace: taskboard
Node: welearn-course-control-plane/172.19.0.2
Labels: app=taskboard
Status: Running
IP: 10.244.0.5
Containers:
api:
Image: taskboard:1.0.0
Port: 8080/TCP
State: Running
Ready: True
Restart Count: 0
Environment:
WELCOME_MESSAGE: 你好,这是第一个 Pod
POD_NAME: taskboard-pod (v1:metadata.name)
POD_NAMESPACE: taskboard (v1:metadata.namespace)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Events:
Type Reason From Message
Normal Scheduled default-scheduler Successfully assigned taskboard/taskboard-pod to welearn-course-control-plane
Normal Pulled kubelet Container image "taskboard:1.0.0" already present on machine
Normal Created kubelet Created container: api
Normal Started kubelet Started container api节点地址、Pod IP、事件时间和底层容器 ID 都是动态值。这里最有价值的证据链是 Scheduled → Pulled → Created → Started,以及所有 Ready 条件为 True。
逐段对照会得到完整因果链:Node 表示调度绑定已经写入;Image 是期望引用;Pulled ... already present 证明 kubelet使用了节点中已导入镜像;Created 和 Started 证明运行时成功创建进程;Environment 证明 Downward API 解析了字段;Conditions 全部 True 解释了 wait 成功。

事件中的 Pulled 只是 Kubernetes 使用的 Reason 名称,即使镜像已在节点也可能显示这一类事件。不要因为字面“Pulled”就认为一定访问了远程仓库。若是 FailedScheduling,看 Message 的资源或约束原因;若是 ErrImagePull,检查镜像引用和策略;若 Started 后反复 BackOff,读取 kubectl logs ... --previous 获取上一个容器实例日志。
Kubernetes 状态只说明容器层面发生了什么,应用日志则回答进程是否按预期启动。TaskBoard 第一行日志会记录端口与构建版本。
下面读取 api 容器日志。Pod 名确定对象,--container api 明确容器,--namespace 防止查错边界。kubectl通过 API Server 请求 kubelet提供容器 stdout/stderr;命令只读日志,不打开 shell、不修改文件。此时执行,是为了从 Kubernetes 对象层进入应用进程层。预期看到 server_started 和版本 1.0.0。
kubectl logs taskboard-pod --container api --namespace taskboard{"event": "server_started", "port": 8080, "version": "1.0.0"}此时还没有 HTTP 请求日志,因为我们尚未访问 Pod。
这行证明 Python 进程成功执行入口、读取 APP_VERSION=1.0.0 并绑定 8080。它没有证明端口从终端可达,下一节的 port-forward 与 curl 会补上网络和业务证据。如果日志为空,可能是应用尚未输出;若容器正在崩溃,用 --previous 读取上一实例;若提示容器名不存在,回到 describe 核对 containers[].name。
kubectl exec 适合执行短小、只读的诊断命令。它不是修改容器的配置手段,因为 Pod 重建后临时改动会消失。这里用它验证镜像中的用户约束是否延续到了 Pod。
下面执行 id。-- 分隔 kubectl 参数与容器内命令,Pod 和容器参数精确指定目标。API Server 与 kubelet建立 exec 流,在现有容器命名空间中启动一个短进程;它会暂时增加一个进程,但不修改镜像或 Pod spec。此时执行,是为了验证实际运行身份,而不只依赖 Docker 镜像元数据。预期 UID 与 GID 都是 10001,而不是 root 的 0。
kubectl exec taskboard-pod \
--container api \
--namespace taskboard \
-- iduid=10001(taskboard) gid=10001(taskboard) groups=10001(taskboard)这个结果与上一章的镜像检查互相印证:安全身份来自镜像默认值,并被 Kubernetes 原样执行。
数字 10001 是真正权限身份,名称 taskboard 来自镜像用户数据库。如果显示 root,说明运行配置覆盖了镜像 USER 或使用了错误镜像;下一条诊断命令是 kubectl get pod taskboard-pod -n taskboard -o jsonpath='{.spec.securityContext}{"\n"}{.spec.containers[0].securityContext}{"\n"}'。课程尚未配置 Pod securityContext,所以预期为空,身份应来自镜像。
不要用 kubectl exec 进入容器后安装包、修改源文件或手工启动第二个服务来“修复”部署。这样的改动不在 spec 中,Pod 删除后就消失,也无法审计。正确修复路径是修改源码或清单,构建新镜像,再让控制器创建新实例。
Pod IP 属于集群网络,且生命周期变化后地址会重分配。开发和诊断时,可以用 kubectl port-forward 把终端的 127.0.0.1:18081 临时转发到 Pod 的 8080 端口。显式设置 --address 127.0.0.1 可以把监听范围限制在回环地址;这条命令会持续占用当前终端。
port-forward 是 kubectl 建立的临时诊断通道,不是 Service、Ingress 或生产流量入口。它依赖当前 kubectl 进程、kubeconfig 和目标 Pod;终端退出、网络中断或 Pod 被删除,通道就消失。它的好处是还没有 Service 时也能验证单个 Pod,并确保请求命中指定实例。
在第一个终端启动转发。pod/taskboard-pod 指定资源,--address 127.0.0.1 限制监听范围,18081:8080 左边是终端侧端口、右边是 Pod 容器端口,Namespace 锁定目标。命令会占用 18081 并保持前台运行,不修改 Pod spec。此时执行,是因为 Pod Ready、日志和身份已验证,接下来只补网络与业务证据。预期看到 IPv4 回环地址的监听信息,并让命令保持运行。
kubectl port-forward \
pod/taskboard-pod \
--address 127.0.0.1 \
18081:8080 \
--namespace taskboardForwarding from 127.0.0.1:18081 -> 8080这行证明 kubectl 已在回环地址监听,但直到有请求到来才真正验证目标流。若报 address already in use,先查明 18081 的占用者或选择课程未占用的端口并同步修改 curl;若提示 Pod not found,核对 Namespace;若 Pod 不再 Running,回到 get/describe,而不是反复启动转发。
保持第一个终端不动,在第二个终端请求 /api/info。curl 使用 --fail-with-body --show-error --silent:隐藏进度条,但连接错误仍可见,HTTP 4xx/5xx 会返回非零,不会被当作成功结果。字节经 kubectl 通道进入 Pod 8080,再由 TaskBoard 返回 JSON。请求只读取接口信息,不连接 Redis。此时预期 Downward API 已把 pod 和 namespace 从 unknown 变为真实对象身份,欢迎语也来自 Pod 清单。
curl --fail-with-body --show-error --silent \
http://127.0.0.1:18081/api/info{"service":"taskboard","version":"1.0.0","message":"你好,这是第一个 Pod","pod":"taskboard-pod","namespace":"taskboard","redisConfigured":false}这条响应把清单、Downward API、容器端口和应用行为连成了一条可验证链路。回到第一个终端按 Ctrl+C 停止转发,18081 端口随即释放。
逐项看:version=1.0.0 对应镜像,message 对应 Pod env,pod 和 namespace 对应 fieldRef,redisConfigured=false 对应尚未注入的存储密码。JSON 字段顺序可能变化,判断应基于键值。此时再读 kubectl logs,还应新增一条 GET 200 访问日志,把外部响应与容器记录对上。
如果返回 pod=unknown,说明环境变量没有按 Downward API 注入,核对清单和实际 Pod spec;如果连接关闭,观察第一个终端的 port-forward 错误;如果 HTTP 500 或 Python traceback,回到容器日志。停止转发只删除临时通道,不影响 Pod。
裸 Pod 没有上层控制器。删除后,Kubernetes 会照做,但不会自动创建替代者。这正是实际应用通常交给 Deployment 管理的原因。
删除不是“杀掉一个随机进程”,而是从 API 中移除这个 Pod 对象的期望状态。API Server 为对象设置删除时间,kubelet按 Pod 的终止宽限期先发送 TERM、等待,再在超时后强制终止仍未退出的容器并清理网络。这个平台流程给应用留出了退出窗口,但 TaskBoard 没有实现信号处理和请求排空,因此不能据此声称业务已经优雅下线。--wait=true 只让 kubectl等待对象删除完成,不验证应用是否处理完在途请求。命令会销毁本章业务容器及 Pod 临时文件,但不会删除节点镜像、Namespace 或清单。
kubectl delete pod taskboard-pod --namespace taskboard --wait=truepod "taskboard-pod" deleted这行证明 API 中目标 Pod 的删除流程已经完成。finalizer 是对象 metadata 中的待办标记:负责清理外部资源的控制器完成工作并移除标记前,API Server 会让对象保持正在删除状态。若终止卡住,先用 kubectl get pod ... -o yaml 查看 deletionTimestamp 和 metadata.finalizers,不要立即使用强制删除;强制从 API 移除对象可能让节点侧进程清理滞后。本章 Pod 没有自定义 finalizer,正常情况下应在终止宽限期流程后消失,但这仍不等同于应用完成请求排空。
再查询同名对象。这个只读请求是在删除后立即核对 API,不是期待成功列表;预期收到 NotFound。若有 Deployment 管理相同模板,通常会出现一个不同名称的新 Pod,而不是恢复同一 UID。本章还没有控制器,所以同名对象不存在且列表中没有替代者。
kubectl get pod taskboard-pod --namespace taskboardError from server (NotFound): pods "taskboard-pod" not foundNamespace、taskboard:1.0.0 镜像和 welearn-course 集群继续保留。下一章会用 Deployment 创建受控制的副本,并再次删除其中一个 Pod 观察自愈。
NotFound 在这里是预期证据,不是失败:API 中没有这个名称,也没有控制器重新创建它。注意它只查询固定名称;若要确认 Namespace 中没有其他 TaskBoard Pod,可追加读取 kubectl get pods -n taskboard -l app=taskboard。下一章会看到删除受 Deployment 管理的 Pod 后,列表很快出现新名称和新 UID。
本章结束时,资源状态应当是:
taskboard Namespace 存在,并带有项目归属标签。k8s/namespace.yaml 与 k8s/pod.yaml 保存在课程项目中,后者是教学记录,不代表对象仍存在。taskboard:1.0.0 已导入 welearn-course-control-plane 的 containerd 镜像存储。taskboard-pod 已删除,port-forward 已用 Ctrl+C 停止,Namespace 中暂时没有 API 工作负载。进入 Deployment 章节前,先恢复课程 PATH 与 KUBECONFIG,确认 context 仍为 kind-welearn-course。然后检查 Namespace 存在,而裸 Pod 查询返回 NotFound。这三个条件分别证明连接目标正确、项目边界保留、旧工作负载已经清空。
最重要的概念核对是:Pod 是 Kubernetes 的调度和生命周期单位,Ready 是条件而非业务验收,裸 Pod 删除后不会自愈。下一章的 Deployment 不会改变这三个事实,它只会在 Pod 之上增加一份持续存在的副本期望。