这一章会搭好后续项目共用的隔离练习集群。你会安装并验证三样工具:Docker 负责提供容器能力,kind 负责把 Kubernetes 节点运行在容器里,kubectl 负责向 Kubernetes API 发出命令。完成后,名为 welearn-course 的集群会继续保留,后面的 TaskBoard 部署都在这里进行。
进入本章时,没有课程目录,也没有课程专用的 Kubernetes context。你的默认 kubeconfig 中可能已有其他 context,本章既不依赖也不修改它们。上一章只是建立了概念模型,还没有对课程 API 发出请求。本章要解决的不是“随便找一个集群”,而是建立一套边界清楚、版本固定、能在结课时整体移除的练习基础设施。完成后,Docker daemon 正常工作,kubectl v1.36.1 与 kind v0.32.0 位于课程专用目录,独立 kubeconfig 只指向 welearn-course,一个 Kubernetes v1.36.1 节点达到 Ready。
从这一章开始,课程命令统一使用 Bash。macOS 和 Linux 直接使用系统终端;Windows 先安装 WSL 2 发行版,开启 Docker Desktop 的 WSL 集成,然后在 WSL 终端中执行本章的 Linux 命令和后续所有章节。这样项目路径、续行符和脚本语法全程一致。
kind 创建的“节点”本身是容器,但节点里的 Kubernetes 仍然使用 containerd 运行 Pod。Docker 提供外层容器能力,containerd 负责集群内的容器生命周期,两者处在不同层次。
同一套 Kubernetes API 可以由托管云集群、虚拟机集群或裸机集群提供。学习对象和控制器时,我们希望环境有四个特点:创建速度快、版本可固定、资源边界清楚、最后能完整删除。kind 正好把 Kubernetes 节点封装成 Docker 容器,节点镜像里已经准备好 kubelet、containerd 和控制平面组件。
这不等于“在容器里模拟一个假的 Kubernetes”。API Server、Scheduler、Controller Manager、kubelet 和 containerd 仍然是真实组件,Pod 与 Deployment 也按正常 API 工作。不同之处在基础设施层:生产节点通常是云主机、虚拟机或物理机,kind 节点是 Docker 容器;课程使用单节点,生产常使用跨故障域的多个节点。
三个工具的调用方向可以先记成:
你输入 kubectl 命令
│ 读取 kubeconfig,发送 HTTPS API 请求
▼
kind 创建的 Kubernetes API Server
│ 保存对象并驱动控制器、Scheduler、kubelet
▼
Docker 承载 kind 节点容器,节点内 containerd 承载 Podkubectl 是客户端,不是集群;kind 是集群生命周期工具,不是日常管理应用的 API;Docker 是 kind 的容器提供者,不负责解释 Deployment。先分清这三个边界,后面出现连接失败时才知道应检查命令行、kubeconfig、API Server,还是 Docker daemon。

课程会主动删除 Pod、制造错误镜像、安装 Metrics Server,并最终删除整套资源。复用重要集群会把学习操作与其他工作负载混在一起,也很难保证版本和默认插件一致。单独的集群与 kubeconfig 把影响范围限制在课程资源名下。
这种隔离仍有边界。kind 会使用 Docker 的 CPU、内存、磁盘和网络,下载节点镜像也会占用存储;它并不是一个与操作系统毫无关系的远程世界。因此我们固定资源名,不执行全局 docker system prune,清理时只删除本课程创建的集群、镜像和目录。
如果 Docker 已经安装,可以阅读自己平台的步骤并直接进入“检查下载工具”。如果还没有安装,请只执行与自己平台对应的一条路径。macOS 和 Windows 使用 Docker Desktop;Ubuntu、Debian 可以安装 Docker Engine。其他 Linux 发行版的软件源、服务管理方式和包名可能不同,应从 Docker Engine 官方安装页 选择对应发行版,不能照抄 Debian 系命令。
先点击苹果菜单的“关于本机”查看芯片。显示 Apple M 系列时选择 Apple silicon 安装包;显示 Intel 时选择 Intel 安装包。也可以在终端执行 uname -m:arm64 对应 Apple silicon,x86_64 对应 Intel。无法识别的结果不要猜安装包。
从 Docker Desktop for Mac 官方安装页 下载对应 DMG,打开后把 Docker 拖入 Applications。第一次启动 Docker.app 时,系统会要求确认许可条款和所需权限;选择推荐设置,等待菜单栏中的 Docker 状态显示引擎已经运行。课程不需要在 Docker Desktop 中额外启用它自带的 Kubernetes,因为 kind 会创建独立集群。
安装包架构不匹配时,应用通常无法启动或会借助额外转译层运行。遇到这种情况应回到芯片信息重新下载,而不是继续安装 kind。
Windows 路径有两层:Windows 运行 Docker Desktop,Ubuntu WSL 发行版提供课程使用的 Bash 终端。先确认 BIOS/UEFI 中的虚拟化能力已经开启,再以管理员身份打开 PowerShell。下面两条命令会安装或更新 WSL,并安装 Ubuntu 发行版;它们会修改 Windows 功能,执行后按系统提示重启。
wsl --install -d Ubuntu
wsl --update重启后第一次打开 Ubuntu,按提示创建 Linux 用户名和密码。回到 Windows,按 Docker Desktop for Windows 官方安装页 安装 Docker Desktop;安装选项使用 WSL 2 后端。首次启动完成后,进入 Settings → Resources → WSL Integration,开启默认 WSL 发行版集成,并显式开启刚安装的 Ubuntu,然后选择 Apply & restart。
再次打开 Ubuntu 终端,执行 wsl.exe -l -v 时目标发行版的 VERSION 应为 2。后续的 Linux 命令都在这个 Ubuntu 终端执行。不要同时在该发行版里再安装一套 Docker Engine;Docker Desktop 已通过 WSL Integration 提供客户端和 daemon 连接,重复安装会造成 socket 和 context 混淆。
下面路径适用于使用 systemd 的 Ubuntu 或 Debian。命令会在一个子 shell 中读取 /etc/os-release,只接受 ubuntu 或 debian;然后安装 HTTPS 证书、curl、摘要与文件比较工具,导入 Docker 官方签名密钥,写入与发行版代号和 CPU 架构匹配的软件源,最后安装 Engine、CLI、containerd、Buildx 和 Compose 插件。子 shell 只隔离 ID、VERSION_CODENAME 等临时发行版变量;通过 sudo 完成的软件安装、软件源写入和 systemd 服务变更仍然作用于系统。它会修改系统软件源并安装系统服务,应在专门用于课程的系统中执行。
如果系统已经安装 docker.io、第三方 containerd 或由组织管理的 Docker 包,先阅读 Docker 官方页面的“卸载冲突包”部分并确认影响,不要直接删除正在承载其他工作负载的软件。
(
. /etc/os-release
case "$ID" in
ubuntu|debian)
DOCKER_DISTRO="$ID"
;;
*)
echo "停止:此安装块只支持 Ubuntu 或 Debian,当前 ID=$ID" >&2
exit 1
;;
esac
sudo apt-get update
sudo apt-get install -y ca-certificates curl coreutils diffutils
systemctl enable --now 同时让服务立即启动并在以后启动系统时自动运行。usermod -aG 把当前用户追加到 docker 组,但当前登录会话不会自动获得新组;退出这个登录会话并重新登录后再继续。Docker 官方文档特别提醒,docker 组可以通过 daemon 获得接近 root 的系统权限,所以只应加入可信用户。
重新登录后,用下面两条只读命令确认无需 sudo 即可访问 daemon。docker version 同时读取客户端与服务端版本,docker info 再让 daemon 返回存储驱动;它们不会拉取镜像、创建容器或增加需要结课清理的资源。后面的 kind 创建会真正验证拉取和运行容器的能力,因此这里无需额外运行一次性示例镜像。
docker version --format 'client={{.Client.Version}} server={{.Server.Version}}'
docker info --format 'storageDriver={{.Driver}}'成功输出类似:
client=28.5.1 server=28.5.1
storageDriver=overlayfs版本号与存储驱动会因系统而不同;稳定条件是两条命令都以零状态结束,且客户端、服务端字段均非空。若这里仍显示 socket 权限不足,用 id 检查输出是否已有 docker 组,并确认确实完成了重新登录。不要通过长期使用 sudo docker ... 绕过权限,否则后面的课程目录和 Docker 配置可能混入 root 所有权。
后面会下载 kubectl 和 kind,再用官方 SHA256 文件校验。macOS 需要 curl、shasum 与 cmp;Linux 和 Windows WSL 需要 curl、sha256sum 与 cmp。下面只读取命令位置,不下载文件。case 会拒绝课程没有覆盖的系统,任一工具缺失都会立即停止。
case "$(uname -s)" in
Darwin)
REQUIRED_TOOLS="curl shasum cmp"
;;
Linux)
REQUIRED_TOOLS="curl sha256sum cmp"
;;
*)
echo "停止:课程下载命令只覆盖 macOS 与 Linux/WSL" >&2
exit 1
;;
esac
for tool in $REQUIRED_TOOLS; do
if ! command -v "$tool"
预期结果是:
下载与 SHA256 校验工具已就绪macOS 正常系统会自带这些工具;若缺失,应先通过系统更新或管理员提供的受信软件恢复,不要从未知站点下载同名程序。Ubuntu/Debian 上一段已安装 curl、coreutils 和 diffutils;sha256sum 来自 coreutils,cmp 来自 diffutils。
Docker 命令实际包含客户端与服务端两部分。终端中的 docker 客户端解析参数,再通过 socket 请求长期运行的 Docker daemon;构建镜像、创建容器和管理网络的是 daemon。只安装客户端,docker --version 也可能成功,但无法运行容器。
因此这里不使用只打印客户端版本的 docker --version,而是让客户端请求服务端信息。--format 只保留两端版本,避免系统详情干扰判断。命令只读取状态,不创建镜像、容器或文件。预期结果是 client 与 server 都有值;具体版本会随安装时间变化。
docker version --format 'client={{.Client.Version}} server={{.Server.Version}}'client=28.5.1 server=28.5.1你的版本号可以不同。这个结果证明两件事:终端找到了客户端程序,客户端也成功连接 daemon。它还没有证明磁盘空间足够或能下载节点镜像,这些问题会在 kind 创建阶段暴露。
若只有客户端信息或出现 Cannot connect to the Docker daemon,先启动 Docker Desktop 或 Linux 的 Docker 服务,再继续。Linux 若出现 socket 权限错误,应按 Docker 官方安装后的权限配置处理;不要为了绕过权限长期对课程命令使用 sudo,否则课程目录和 Docker 配置可能混入 root 所有权。
下一条诊断命令是 docker info。连接正常时它会显示 Server 区块、存储驱动与资源信息;仍然连接失败时,问题位于 Docker 服务层,尚未进入 Kubernetes。
第三章不会把构建缓存写进默认共享 builder,而会使用 docker-container driver 建立课程专属 BuildKit 容器和状态卷,再用 --load 只导出最终 TaskBoard 镜像。因此仅有 docker build 命令还不够,Buildx 插件必须同时支持 create --driver/--driver-opt 和 build --builder/--load。
下面先读取 Buildx 版本,再检查帮助文本中课程实际依赖的参数。版本字符串用于诊断,不把补丁号写成唯一门槛;本课程验证时为 v0.29.1-desktop.1,但兼容的新版本只要具备这些功能也可以继续。三个命令都只读取客户端能力,不创建 builder、容器、卷或缓存。
if BUILDX_VERSION="$(docker buildx version 2>&1)"; then
printf '%s\n' "$BUILDX_VERSION"
else
BUILDX_RC=$?
printf '停止:Docker Buildx 不可用,rc=%s\n%s\n' \
"$BUILDX_RC" "$BUILDX_VERSION" >&2
exit 1
fi
if BUILDX_CREATE_HELP="$(docker buildx create --help 2>&1)"
github.com/docker/buildx v0.29.1-desktop.1 <提交摘要>
buildxFeatures=docker-container,driver-opt,builder,load版本和提交摘要会变化,第二行才是课程依赖的功能断言。Docker Desktop 若缺少或无法加载 Buildx,先更新到受支持版本并重新启动;Ubuntu/Debian 若提示 buildx is not a docker command,确认前面安装的 docker-buildx-plugin 来自 Docker 官方仓库。不要改用默认 builder 绕过检查,否则结课时无法精确归属和回收构建缓存。
kubectl 是 Kubernetes 的命令行客户端。课程固定使用 v1.36.1,这样命令行为和示例输出有共同基线。课程下载的工具统一放进 welearn-kubernetes-course/bin,只修改当前终端的 PATH,不写入系统目录;结课时删除课程目录即可一起移除。下面选择与你的系统对应的一组命令即可。
kubectl 本身不保存集群状态。它读取 kubeconfig 中的 API 地址、证书和 context,把子命令转换成 HTTP 请求,再格式化响应。客户端版本会影响可用参数和默认行为,所以课程把它和服务端固定在同一个小版本。生产场景需要遵守 Kubernetes 官方的 版本偏差策略,不能因为某次旧客户端“看起来能用”就忽略兼容边界。
安装二进制时还有一个供应链问题:下载完成不代表内容正确。官方同时发布 SHA256 摘要,校验命令会计算二进制实际摘要并与官方值比较。只有出现 OK 才给文件执行权限。摘要能发现下载损坏或内容不匹配,但不能替代操作系统权限、来源域名和发布流程的完整安全控制。
课程结尾会按这个边界整体删除文件,所以绝不能把既有项目混进来。预检必须同时处理首次开始和重新进入课程:路径不存在时创建并写入归属标记;路径存在且标记内容逐字节匹配时安全恢复;路径存在但标记缺失、类型不对或内容不同,立即拒绝。
命令先把 COURSE_ROOT 设为用户目录下的固定路径。cmp -s 把期望的单行标记与文件逐字节比较,连多余空行也会判为不匹配。首次执行会创建 bin 和 .course-owned;恢复时只补齐 bin,不会覆盖其他课程文件。这个标记不是 Kubernetes 功能,它是后续每个写操作和结课清理都会复查的安全护栏。
课程稍后会临时覆盖 KUBECONFIG,所以这里还要在第一次覆盖之前保存它原先的 shell 状态。.kubeconfig-env-before-course 的前两行记录变量是 set 还是 unset 以及原值的字节数,后面直接保存 set 状态下的完整值。值可以包含空格、冒号甚至换行,不能用 KEY=value 再按行解析。账本必须是当前用户拥有的普通文件、权限精确为 0600,恢复课程时只校验既有字节结构,绝不根据当前变量重新生成或覆盖;原值也不会打印到终端。
COURSE_ROOT="$HOME/welearn-kubernetes-course"
COURSE_MARKER='welearn-kubernetes-course'
KUBECONFIG_ENV_MARKER="$COURSE_ROOT/.kubeconfig-env-before-course"
COURSE_ROOT_CREATED='no'
read_file_mode_and_uid() {
RFM_FILE="$1"
if RFM_MODE="$(stat -f '%Lp' "$RFM_FILE" 2>/dev/null)" && \
RFM_UID="$(stat
课程目录已安全创建
kubeconfigPreviousState=unset(原值未显示)首次执行会看到“安全创建”,以后重新进入会看到“安全恢复”。第二行只公开原状态;如果变量原先已设置,会显示 set,但不会泄露证书路径或其他完整值。只有目录和环境账本都通过才继续。如果命令拒绝既有路径,先用 ls -la "$COURSE_ROOT" 检查其内容和来源;不要为了继续课程盲目删除或伪造标记。
成功输出证明路径带有精确课程标记,后面可以在这个边界内新增二进制、kubeconfig、源码与 YAML。若 mkdir 报 Permission denied,检查 $HOME 是否可写。后续命令会再次执行同样的精确标记守卫,即使读者跳过本节,也不会把文件写入一个碰巧同名的目录。
下载地址与校验方式来自 kubectl macOS 官方安装文档。命令会根据处理器架构选择 Apple Silicon 或 Intel 文件,先验证官方 SHA256,再让当前终端从课程专用目录查找程序。预期校验通过,kubectl 为 v1.36.1。
这组命令会先精确复查课程标记,再把课程 bin 放在当前 PATH 最前面。case 只接受 arm64 和 x86_64,无法识别的架构立即停止。两个 curl 都使用 --fail --show-error --location:HTTP 错误返回非零、错误信息可见、官方重定向可跟随。只有二进制与摘要都下载成功且校验返回成功,才执行 chmod 和版本查询;失败分支会删除可能不完整的文件。
COURSE_ROOT="$HOME/welearn-kubernetes-course"
COURSE_BIN="$COURSE_ROOT/bin"
if ! { [ -d "$COURSE_ROOT" ] && \
printf 'welearn-kubernetes-course\n' | cmp -s - "$COURSE_ROOT/.course-owned"; }; then
echo '停止:课程目录标记校验失败' >&2
exit 1
fi
mkdir -p "$COURSE_BIN"
/Users/you/welearn-kubernetes-course/bin/kubectl: OK
Client Version: v1.36.1
Kustomize Version: v5.8.1校验输出中的用户目录是动态路径。最后两行证明客户端版本符合课程基线,此时还没有连接集群,因此只检查客户端。
如果摘要校验显示 FAILED,失败分支会删除二进制与摘要,不会执行 chmod 或 kubectl。确认 URL、网络代理和系统架构后重新下载。若校验成功但提示 Permission denied,先用 ls -l "$COURSE_BIN/kubectl" 检查执行位;若终端调用了其他版本,用 command -v kubectl 确认 PATH 的实际解析结果。
下载地址与校验方式来自 kubectl Linux 官方安装文档。命令用 case 把 x86_64/amd64 映射到 amd64,把 aarch64/arm64 映射到 arm64;其他架构直接拒绝。预期 SHA256 校验通过,当前终端能调用固定版本。
Linux 的步骤与 macOS 相同,区别是下载平台名以及使用 sha256sum 校验。WSL 中必须下载 Linux 二进制,因为执行命令的是 Linux 用户空间,不是 Windows PowerShell。x86_64 映射为 amd64,aarch64 映射为 arm64;其他架构不应被这段二选一逻辑默认为 amd64,需要回到官方安装页确认支持情况。
COURSE_ROOT="$HOME/welearn-kubernetes-course"
COURSE_BIN="$COURSE_ROOT/bin"
if ! { [ -d "$COURSE_ROOT" ] && \
printf 'welearn-kubernetes-course\n' | cmp -s - "$COURSE_ROOT/.course-owned"; }; then
echo '停止:课程目录标记校验失败' >&2
exit 1
fi
mkdir -p "$COURSE_BIN"
/home/you/welearn-kubernetes-course/bin/kubectl: OK
Client Version: v1.36.1
Kustomize Version: v5.8.1校验输出中的用户目录是动态路径。Windows 学员会在 WSL 中看到 /home/<用户名> 形式的路径,这是预期结果。
输出中的 Client Version 来自刚下载的程序,Kustomize Version 是 kubectl 内置的清单定制能力版本。这里没有 Server Version 很正常,因为我们显式用了 --client,也尚未创建集群。若命令意外访问了其他服务端,先运行 command -v kubectl 检查是否调用了课程目录之外的程序。
kind 的全名是 Kubernetes IN Docker。课程固定 v0.32.0,安装链接来自 kind Quick Start 和 v0.32.0 发布页。仍然只需选择自己的平台。
kind 有两个主要输入:kind 程序决定怎样创建和删除集群,kindest/node 镜像提供某个 Kubernetes 版本的节点文件系统和组件。升级 kind 程序不等于自动升级已有集群,换一个节点镜像也不等于修改课程目录中的 kind 二进制。后面我们会分别固定这两个版本。

上图用多节点形式展示通用角色关系。为了让课程资源更轻,后面创建的是单节点 kind 集群:同一个 welearn-course-control-plane 节点同时运行控制平面组件和 TaskBoard 工作负载。
下面会根据芯片架构把对应文件和官方 .sha256sum 下载到课程专用目录。预期校验为 OK,随后 kind 报告 v0.32.0。
命令先检查精确课程标记,再用 case 决定 Apple silicon 或 Intel 资源;未知架构停止。kind 的官方 checksum 文件记录原始资源名,而课程把二进制保存为 kind,所以校验前只取官方摘要,再与课程路径组合。下载或校验任一步失败,都会删除不完整文件,chmod 和 kind version 不会执行。此步骤不会创建节点容器或修改 kubeconfig。
COURSE_ROOT="$HOME/welearn-kubernetes-course"
COURSE_BIN="$COURSE_ROOT/bin"
if ! { [ -d "$COURSE_ROOT" ] && \
printf 'welearn-kubernetes-course\n' | cmp -s - "$COURSE_ROOT/.course-owned"; }; then
echo '停止:课程目录标记校验失败' >&2
exit 1
fi
mkdir -p "$COURSE_BIN"
/Users/you/welearn-kubernetes-course/bin/kind: OK
kind v0.32.0 go1.26.3 darwin/arm64校验行中的用户目录是动态路径。处理器与 Go 的补丁版本可能不同,关键证据是摘要校验为 OK 且 kind 为 v0.32.0。
darwin/arm64 表示编译目标,会随系统变化;go1.26.3 表示构建 kind 时使用的 Go 工具链,也不是集群版本。真正需要稳定匹配的是开头的 kind v0.32.0。若 shell 仍提示找不到 kind,使用 command -v kind 和 echo "$PATH" 检查当前终端是否包含课程 bin。
下面会根据 x86_64/amd64 或 aarch64/arm64 选择文件,其他架构立即拒绝。预期结果是 SHA256 校验通过,kind 报告 v0.32.0。
与 kubectl 一样,WSL 应使用 Linux 版本。命令会写入 $COURSE_BIN/kind 和官方摘要文件;此时 Docker 中还没有 kind 节点或网络。只有摘要成功后才增加执行权限,chmod +x 不会授予程序 root 权限。
COURSE_ROOT="$HOME/welearn-kubernetes-course"
COURSE_BIN="$COURSE_ROOT/bin"
if ! { [ -d "$COURSE_ROOT" ] && \
printf 'welearn-kubernetes-course\n' | cmp -s - "$COURSE_ROOT/.course-owned"; }; then
echo '停止:课程目录标记校验失败' >&2
exit 1
fi
mkdir -p "$COURSE_BIN"
/home/you/welearn-kubernetes-course/bin/kind: OK
kind v0.32.0 go1.26.3 linux/amd64处理器与 Go 的补丁版本可能不同,kind 版本应为 v0.32.0。Windows WSL 同样显示 linux/amd64 或 linux/arm64,因为命令运行在 WSL Linux 发行版中。
如果看到 Exec format error,最常见原因是下载架构或平台不匹配,下一步应运行 uname -s 与 uname -m 对照下载 URL。若版本正确但后面创建集群时提示无法连接 Docker,kind 程序本身没有坏,应回到本章第一节检查 daemon。
kubeconfig 保存集群地址、证书和当前上下文。单独设置 KUBECONFIG 可以避免把课程集群写入你已有的默认配置。后续每次打开新终端,都要重新设置课程 PATH 与 KUBECONFIG;两者都位于同一个可整体删除的目录。
一个 kubeconfig 通常包含三类记录:clusters 保存 API Server 地址和集群 CA,users 保存客户端身份材料,contexts 把某个 cluster、user 和可选的默认 Namespace 组合起来。current-context 指出 kubectl 默认使用哪一组组合。context 本身不是集群,它只是客户端连接选择。
若不设置 KUBECONFIG,kubectl 默认读取 $HOME/.kube/config。kind 也可能把新 context 合并进去。课程显式指向单独文件,能防止误切到已有 context,也让最终删除连接凭据时不需要编辑共享配置。
下面恢复当前终端的课程工具路径,并让 kubectl 只读写课程自己的 kubeconfig。预期同时看到专用 bin 和文件名 welearn-course.kubeconfig。
命令会设置两个环境变量并切换到课程根目录。PATH 影响程序查找,KUBECONFIG 影响集群配置文件位置;cd 只是把后续相对路径限制在课程目录。此时 kubeconfig 文件可能还不存在,下一节的 kind 会创建它。basename 只用于打印文件名,不改写文件。
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"
cd "$COURSE_ROOT
bin=/Users/you/welearn-kubernetes-course/bin
kubeconfig=welearn-course.kubeconfigbin 前面的用户目录是动态路径。Windows WSL 中也使用这组 Bash 命令。
输出证明变量指向了预定边界,但还不能证明文件内容有效。现在若执行 kubectl get nodes,可能得到配置缺失或连接错误,这是正常的前置状态。创建集群后,我们会用 kubectl version 与 kubectl config current-context 的事实验证连接。
新开终端后,shell 不会自动继承上一终端临时设置的 PATH 和 KUBECONFIG。后续若 kubectl 突然连到错误集群,先重新设置这两个变量,再运行 kubectl config current-context,不要在未确认 context 时执行删除命令。
kind 的节点镜像同时固定标签与摘要。标签 v1.36.1 表示 Kubernetes 版本;摘要则锁定镜像的确切内容,避免同名标签发生变化。创建过程中,kind 会启动一个控制平面节点并把连接信息写入刚才指定的 kubeconfig。
镜像标签便于人阅读,摘要是内容寻址标识。kindest/node:v1.36.1@sha256:... 同时写出两者:读者能看出版本,Docker 又会验证下载内容与摘要一致。若只写可变标签,远端标签重新指向其他内容后,同一条安装命令可能得到不同节点;固定摘要可以让课程基线可复现。
单节点配置会把控制平面组件、etcd、网络插件以及后续 TaskBoard Pod 都放到 welearn-course-control-plane。这能完整观察对象流程,但不测试节点故障转移。我们没有额外映射宿主端口,因为应用访问会通过 kubectl port-forward 完成。
资源名称相同只说明字符串相同,不说明是谁创建、谁有权删除。Docker 中可能已经有固定 kind 节点镜像,多个 kind 集群也共用名为 kind 的通用网络;另一个项目还可能先创建了 welearn-course 集群。若课程看到同名资源就直接认领,结课清理可能删除别人的镜像、网络或集群。
因此,第一次创建集群前要记录两个基线:.kind-node-image-preexisting 精确保存固定节点镜像在课程开始前是否存在,.kind-network-preexisting 精确保存通用 kind 网络是否存在。两个文件的内容只能是 yes 或 no,并以单个换行结束;后续恢复会逐字节验证并保留第一次记录,不会因为课程运行后资源已经出现而把 no 改成 yes。
集群不能只用名称证明归属,因为容器删掉后重建仍可能沿用 welearn-course-control-plane,但它已经是另一个对象。课程为此使用两份四行账本。.kind-create-attempt 在执行创建命令之前写入,最初的 node_id=pending 表示“创建已经获得授权,但尚未观察到可归属的容器”;创建命令返回后,只要能够同时验证容器名称、kind 集群标签和完整 64 位容器 ID,就把这个 ID 写回尝试账本。.kind-cluster-owned 只在创建成功且上述三个事实都成立后写入同一份四行记录,并在它落盘后移除尝试账本。
四行内容依次固定集群名、节点名、完整容器 ID 与标签:
cluster=welearn-course
node=welearn-course-control-plane
node_id=<64 位小写十六进制容器 ID>
label=io.x-k8s.kind.cluster=welearn-course恢复时,名称、标签、kind 列表与账本 ID 必须同时一致。只有尝试账本中的完整 ID 才能授权删除一次失败创建留下的容器;pending 遇到任何同名或同标签资源都会停止,因为此时没有足够证据证明它来自这次尝试。这样宁可让读者检查一次中断现场,也不会把未知资源认作课程资源。
“课程使用了这个资源”和“课程创建并拥有这个资源”是两件事。preexisting 标记保护课程开始前已存在的共享资源,owned 标记只在创建成功后授予后续精确清理权限。

确认 Docker 正在运行,且当前终端中的 KUBECONFIG 已设置。下面的命令先检查同名集群和归属标记,再建立或读取基线,最后只在集群尚不存在时执行创建。预期首次运行时 welearn-course-control-plane 创建成功,并在 120 秒内达到可用状态。
record_preexisting 只在基线文件不存在时调用 docker image inspect 或 docker network inspect;文件已经存在时,它要求内容精确为 yes\n 或 no\n。检查失败也不能一概解释为“不存在”:只有 Docker 明确返回 No such image 或 network kind not found 才记录 no,连接 daemon 失败、权限不足与其他错误都会原样输出并停止。否则一次临时故障就可能伪造错误基线,让结课清理误删原有资源。
接着命令交叉读取三个事实源:kind get clusters、按 kind 标签筛选的完整容器 ID、按节点名称筛选的完整容器 ID。名称筛选可能匹配子串,所以还必须 docker inspect 并逐字节核对精确名称 /welearn-course-control-plane、标签值 welearn-course 与 .Id。三个事实源一致,才允许复用、恢复或创建。
创建会产生真实资源:Docker 下载或复用节点镜像,kind 创建节点容器和通用网络,在节点中生成证书并启动控制平面,安装默认 CNI 和 StorageClass,最后把连接信息写入 $KUBECONFIG。--name 决定集群及节点名称,--image 固定 Kubernetes 节点内容,--kubeconfig 限定凭据文件,--wait 120s 要求控制平面在超时前达到 Ready。正式 owned 账本写在整个创建命令成功并完成容器事实校验之后;失败时最多留下绑定到精确 ID 的 attempt 账本,不会凭名称获得所有权。
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=
nodeImagePreexisting=no
kindNetworkPreexisting=no
Creating cluster "welearn-course" ...
✓ Ensuring node image (kindest/node:v1.36.1) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Waiting ≤ 2m0s for control-plane = Ready ⏳
• Ready after 16s 💚
Set kubectl context to "kind-welearn-course"
clusterNodeId=<64 位小写十六进制容器 ID>
课程集群归属已记录示例中的两个 no 来自这次课程执行前的真实基线;如果你的 Docker 原本已有相同节点镜像或通用 kind 网络,相应值会是 yes,这不是错误。Ready after 16s 是一次实际执行结果,耗时会随设备而变化。clusterNodeId 每次创建都会不同,关键是它必须为 64 位小写十六进制,并与 Docker 当前节点的完整 ID 及 owned 账本逐字节一致。
安全恢复已有课程集群时,不会重复出现创建过程,而会得到 课程集群与归属台账一致,安全复用。失败创建留下精确 ID 时,下一次执行只会删除那个 ID 对应且名称、标签仍匹配的孤立容器,或把已经出现在 kind 列表中的同一节点提升为正式归属。若 pending 与资源同时出现,或任一事实不一致,命令都会停止。不要手工补写账本;应先确认中断现场和资源来源。
各行对应一段创建流程:Preparing nodes 创建节点容器,Writing configuration 生成 Kubernetes 配置,Installing CNI 建立 Pod 网络,Installing StorageClass 提供后面 PVC 会用到的默认存储类,Waiting 才是在验证 API 与节点条件。emoji、耗时和缓存行为会变化,不应逐字符比较。
若在拉取镜像时失败,先运行 docker pull 对同一摘要检查网络和磁盘;若卡在控制平面 Ready,运行 kind export logs --name welearn-course "$COURSE_ROOT/kind-logs" 导出诊断资料;若提示同名集群已存在,不要直接覆盖,先用 kind get clusters 判断它是否是本课程此前创建的资源。课程只在结课步骤统一删除它。
kubectl version 会分别询问终端中的客户端和 API Server。两端都是 v1.36.1,说明工具与集群版本符合本课程基线;Kustomize 版本由 kubectl 自带。
这个命令读取当前 kubectl 二进制版本,再根据 KUBECONFIG 的 current context 请求 API Server 的版本端点。它不创建或修改集群对象。预期状态是客户端、服务端都能返回版本,而不是连接被拒绝。
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=
Client Version: v1.36.1
Kustomize Version: v5.8.1
Server Version: v1.36.1这也证明独立 kubeconfig 中的地址和证书可以正常访问 API Server。
三行分别来自不同位置:Client Version 来自课程二进制,Server Version 来自正在连接的 API,Kustomize Version 来自 kubectl 内置库。若只有客户端而服务端连接拒绝,先运行 kubectl config view --minify 检查当前 context 与 server 地址;若证书错误,确认 $KUBECONFIG 没有指向旧文件;若 Server Version 不是 v1.36.1,很可能连到了别的集群。
为了把“很可能”变成证据,可以继续运行:
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=
预期输出是:
kind-welearn-course这个 context 名来自 kind 的命名规则。它证明当前选择正确,但真正执行删除或变更前,仍建议同时核对 kubectl cluster-info 返回的 API 地址。
节点的 Ready 条件表示 kubelet 已向控制平面报告自己可以接收工作负载。先查看节点表,预期只有一个控制平面节点,状态为 Ready,版本为 v1.36.1。
节点对象不是 Docker 容器列表的简单别名。kubelet向 API 注册 Node,并持续上报容量、可分配资源、系统信息和 Conditions。Scheduler 主要依据 Node 对象及 Pod 约束做放置决策。Ready=True 表示 kubelet健康且节点没有被已知条件阻止接收普通工作负载;它不保证节点上每个系统 Pod 都已 Ready,所以后面还要单独检查 kube-system。
下面的查询只读取 Node 列表。STATUS、ROLES、AGE 和 VERSION 是 kubectl 从对象字段整理出的列。我们此时关注对象数量、Ready 与版本,不把不断增长的 AGE 当成固定输出。
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=
NAME STATUS ROLES AGE VERSION
welearn-course-control-plane Ready control-plane 1m v1.36.1AGE 是动态时长,会随执行时间增长。
若 STATUS 是 NotReady,下一条诊断命令是 kubectl describe node welearn-course-control-plane,重点看 Conditions 与 Events。NetworkUnavailable=True 往往指向 CNI;磁盘、内存或 PID 压力会显示对应 Pressure 条件。taint 是写在节点上的“排斥标记”,Pod 只有声明匹配的 toleration 才能忽略相应排斥效果。不要因为节点名称中有 control-plane 就误判它不能运行课程 Pod;kind 的这个单节点没有额外用于阻止课程工作负载的 taint。
Docker 创建了节点容器,但 kubelet 通过 CRI 使用节点内的 containerd。下面直接读取节点状态中的操作系统与运行时字段,预期节点镜像基于 Debian 13,运行时为 containerd://2.3.1。
--output jsonpath=... 不会请求另一套数据,它只从同一个 Node 对象的 .status.nodeInfo 精确取字段。这样比在宽表中猜测运行时更可靠。查询只读 API,不进入节点容器。
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=
os=Debian GNU/Linux 13 (trixie) runtime=containerd://2.3.1这条证据区分了“Docker 提供 kind 节点容器”和“containerd 运行 Kubernetes Pod”两件事。
osImage 和 containerRuntimeVersion 来自固定节点镜像,因此课程实际结果是 Debian 13 与 containerd 2.3.1。其他节点镜像版本可能不同;若运行时字段为空或节点尚未 Ready,应先回到 Node Conditions,而不是安装另一个 Docker。后续 Pod 事件中的“image already present on machine”指的正是这个节点运行时的镜像存储。
Pod 的 STATUS=Running 只说明它已被调度且至少有容器正在运行,不代表应用已经能接收流量。READY 列和 Ready 条件才表达容器是否通过就绪检查。与其反复目测列表,更可靠的做法是让 kubectl 等待条件成立。
kube-system Namespace 里运行着集群基础组件。静态 Pod 形式的 API Server、etcd、Scheduler 和 Controller Manager由控制平面节点 kubelet管理;CoreDNS 提供集群 DNS;kindnet 配置 Pod 网络;kube-proxy 参与 Service 转发。Node Ready 之后,CoreDNS 仍可能短暂处于 0/1,所以应用部署前需要第二道门槛。
下面等待 kube-system 中当前所有 Pod 的 Ready=True。预期在 120 秒内完成;若超时,应先查看未就绪 Pod 的事件与日志,而不是继续部署应用。
--all 选择这个 Namespace 当前所有 Pod,--for=condition=Ready 等待每个对象的 Ready 条件,--timeout=120s 给出明确失败边界。命令不会修复 Pod,只是在 API 上观察条件;超时也是有价值的结果,因为它告诉我们集群基础还未满足课程前置条件。
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=
pod/coredns-xxxxxxxxxx-xxxxx condition met
pod/coredns-xxxxxxxxxx-yyyyy condition met
pod/etcd-welearn-course-control-plane condition met
pod/kindnet-xxxxx condition met
pod/kube-apiserver-welearn-course-control-plane condition met
pod/kube-controller-manager-welearn-course-control-plane condition met
pod/kube-proxy-xxxxx condition met
pod/kube-scheduler-welearn-course-control-plane condition met由哈希生成的 Pod 名称后缀会变化,xxxxx 只表示这部分是动态值。最后再查看表格,预期每行 READY 的分子和分母相等,且状态为 Running。
每一行 condition met 对应一个实际 Pod。CoreDNS 两个副本都 Ready,说明 DNS 服务具备预期副本;控制平面静态 Pod Ready,说明对应进程的就绪检查通过。若 wait 返回 no matching resources found,说明查询时该 Namespace 没有 Pod,通常表示集群创建不完整;若只某个 Pod 超时,用 kubectl get pods -n kube-system 先定位名称,再执行 kubectl describe pod <名称> -n kube-system 和 kubectl logs <名称> -n kube-system。
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=
NAME READY STATUS RESTARTS AGE
coredns-xxxxxxxxxx-xxxxx 1/1 Running 0 1m
coredns-xxxxxxxxxx-yyyyy 1/1 Running 0 1m
etcd-welearn-course-control-plane 1/1 Running 0 1m
kindnet-xxxxx 1/1 Running 0 1m
kube-apiserver-welearn-course-control-plane 1/1 Running 0 1m
kube-controller-manager-welearn-course-control-plane 1/1 Running 0 1m
kube-proxy-xxxxx 1/1 Running 0 1m
kube-scheduler-welearn-course-control-plane 1/1 Running 0 1m名称后缀与 AGE 都是动态值。这里不删除集群,因为下一章构建的 TaskBoard 镜像还要加载进它。
这张表是对 wait 的可读复核:每个 READY 都是 1/1,状态为 Running,重启次数为 0。将来看到重启次数非零,不一定代表当前故障,但要结合 AGE、Last State 和日志判断是否存在反复崩溃。下一条通用诊断命令是 kubectl get events -n kube-system --sort-by=.lastTimestamp,它能按时间整理调度、拉取和启动事件。
现在可以列出本章真正留下的内容:
$HOME/welearn-kubernetes-course/.course-owned 标识课程目录归属。.kubeconfig-env-before-course 以权限 0600 保存进入课程前 KUBECONFIG 的 set/unset 状态、字节数和 set 时的完整原值;课程中不会覆盖或打印它。.kind-node-image-preexisting 与 .kind-network-preexisting 分别以 yes/no 保存创建前基线;它们不会在安全恢复时重算。.kind-cluster-owned 以四行账本绑定集群名、节点名、kind 标签和完整 Docker 容器 ID;正常成功后 .kind-create-attempt 已移除,失败时它可能保留 pending 或精确残留 ID。bin/kubectl 与 bin/kind 是固定版本客户端工具。welearn-course.kubeconfig 保存课程集群连接信息,current context 为 kind-welearn-course。welearn-course-control-plane 节点容器,并存在 kind 为它创建的网络与节点镜像。kube-system Pod 均已 Ready。源码、TaskBoard 镜像和业务 Namespace 还不存在。下一章只会在课程目录中创建应用文件,并使用 Docker 构建 taskboard:1.0.0;集群保持运行,但直到第四章才接收第一个业务对象。
新开终端时先重新执行:
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=
预期 context 是 kind-welearn-course,节点是 welearn-course-control-plane 且状态 Ready。这组命令只恢复当前 shell 变量并读取状态,不创建重复集群。若任何一项不符,先修复连接,不要继续运行后续章节的 apply 或 delete。