在 WSL2 中搭建 Minikube:从安装到常见故障排查
为什么要在 WSL2 中搭建 Minikube
我搭建这套环境的目的并不是为了在本地长期运行一个“生产级 Kubernetes 集群”,而是为了给 Kubernetes 学习、排障和面试准备提供一个可以反复折腾的实验环境。
相比只阅读文档,本地集群最大的价值是能够主动制造问题。例如:
- 修改 Pod 的资源限制,观察
Pending、OOMKilled 等状态; - 配置错误的镜像,观察
ImagePullBackOff; - 修改 readiness / liveness probe,观察 Pod 生命周期变化;
- 创建不同类型的 Service,理解 Kubernetes 网络访问路径;
- 修改 Deployment 副本数,观察调度和滚动更新;
- 查看 Events、Logs 和资源状态,练习排障流程;
- 安装 Prometheus、Grafana 等组件,熟悉 Kubernetes 中的可观测性体系。
我的主要开发环境本身就在 WSL2 中,因此把 Kubernetes 实验环境也放在 WSL2 里有几个明显优势:
- 可以直接使用 Linux 命令行和工具链;
- 不需要额外准备虚拟机;
- 可以直接使用现有的 Docker / container runtime;
- 实验环境可以随时删除和重建;
- 与日常使用的 Git、Shell、VS Code 等工具能够自然结合。
对于我的使用场景来说,“方便破坏、方便重建”比“尽量模拟生产环境”更加重要。
Minikube、kind 和 Docker Desktop Kubernetes 怎么选
在本地学习 Kubernetes 时,常见选择包括 Minikube、kind 和 Docker Desktop Kubernetes,但三者的侧重点不同。
Minikube
Minikube 面向本地学习和开发,支持 Docker、KVM、Hyper-V 等 driver,并提供方便实验的命令:
1 | minikube dashboard |
它操作直观,适合主动制造故障并观察 Kubernetes 的行为,因此我选择 Minikube 作为主要实验环境。
kind
kind 即 Kubernetes IN Docker,使用 Docker container 模拟 Kubernetes node:
1 | kind create cluster |
集群创建和销毁较快,也适合在 CI 中临时运行 integration test:
1 | CI |
如果目标是 Controller / Operator 开发、CI 集成测试或快速创建多个测试集群,kind 往往更合适;而我的重点是学习 Kubernetes 运维、排障和资源行为。
Docker Desktop Kubernetes
Docker Desktop 可以直接启用 Kubernetes,配置最少:
1 | Docker Desktop |
但我希望环境运行在 WSL2 的 Linux 工具链中,并且需要频繁执行:
1 | 创建集群 |
Minikube 对这种实验式工作流更灵活。
最终选择
因此我目前的选择是:
| 工具 | 更适合的场景 |
|---|---|
| Minikube | Kubernetes 学习、实验、排障 |
| kind | CI、Operator 开发、快速测试集群 |
| Docker Desktop Kubernetes | 希望最少配置快速获得一个本地 Kubernetes |
我的目标是建立一个可以反复破坏和恢复的 Kubernetes 实验场,因此本文后续使用 Minikube + WSL2。
我的环境
这次排障使用的实际环境如下:
1 | Windows 10 + WSL2 |
安装前准备
安装 Docker Engine
Docker Engine 从 Docker 官方 apt 仓库安装,并直接运行在 WSL 中。安装后的第一次验证失败了:
1 | sudo docker run hello-world |
关键错误是:
1 | dial tcp ...:443: i/o timeout |
Shell 已经配置代理,并不代表 Docker daemon 会自动继承它。docker pull 的网络请求由 daemon 发起,因此需要给 Docker 的 systemd 服务单独配置代理:
1 | HTTP_PROXY=http://127.0.0.1:8118 |
重新加载 systemd 配置并重启 Docker 后,再次运行 docker run hello-world 成功。这说明 WSL 内的 Docker daemon 已经可以通过 Privoxy 访问 Docker Hub。
随后把当前用户加入 docker 用户组,使后续 Docker 和 Minikube 操作不再依赖 sudo。
安装 kubectl
安装后的客户端版本为:
1 | kubectl client: v1.34.11 |
本机 kubectl 为 v1.34.11,而 Minikube 集群为 Kubernetes v1.37.0,已超出 kubectl 官方支持的 ±1 个 minor 版本范围。因此本文后续统一使用 minikube kubectl -- ...,避免客户端版本偏差干扰实验:
1 | minikube kubectl -- <kubectl arguments> |
安装 Minikube
安装后的版本为:
1 | minikube version: v1.39.0 |
Docker 已经能够运行普通容器,因此选择 Docker driver 启动 Minikube。这里需要先区分两个概念:driver 决定 Minikube 节点运行在哪里,而 Kubernetes container runtime 决定 Pod 镜像由谁拉取和运行。二者不一定相同。
第一次启动 Minikube 集群
第一次使用 Docker driver 启动时,Minikube 为节点选择了 containerd 2.3.4 作为 Kubernetes container runtime:
1 | Minikube driver: docker |
启动过程中首先出现 NO_PROXY 未包含 Minikube IP 192.168.49.2 的警告。代理不应处理集群内部流量,因此将以下地址加入 NO_PROXY:
1 | localhost,127.0.0.1,::1,192.168.49.0/24,10.96.0.0/12 |
其中 192.168.49.0/24 覆盖 Minikube 节点网络,10.96.0.0/12 覆盖集群 Service 网络。这样访问节点、API Server 和 Service 时不会绕到外部代理。
Minikube 随后仍然警告无法访问 registry.k8s.io。集群表面上完成了启动,但节点一直处于 NotReady。
从 Pod 状态定位镜像拉取失败
先查看系统 Pod,而不是只看 minikube start 的最后一行:
1 | minikube kubectl -- get pods -A |
关键状态为:
1 | kindnet ImagePullBackOff |
coredns 和 storage-provisioner 的 Pending 是结果,不一定是根因。继续使用 kubectl describe pod 查看 Events,发现最早、最具体的错误来自 kindnet:
1 | docker.io/kindest/kindnetd:v20260820-69b56db7 |
因此排障方向从“为什么节点 NotReady”收敛为“谁在拉取 kindnet 镜像,以及它为什么没有走代理”。
关键诊断:Docker driver 不等于 container runtime
宿主 WSL 中的 Docker 已经可以拉取镜像,但这不能证明 Minikube 节点内的 containerd 也有代理。检查 containerd 服务环境:
1 | minikube ssh -- 'sudo systemctl show containerd --property=Environment' |
输出为:
1 | Environment= |
kubelet 的环境同样为空。这证明 Docker daemon 的 systemd 代理只解决了宿主 Docker 的网络请求,并没有传递给 Minikube 节点内部的 containerd。
直接沿着 Kubernetes 实际使用的 CRI 拉取路径复现问题:
1 | minikube ssh -- \ |
结果仍然是超时。至此可以确认:失败点不在 kubectl、API Server 或宿主 Docker,而在没有代理环境的 containerd 拉取路径。
验证 Minikube 节点到代理的网络路径
Minikube 节点有自己的网络命名空间。节点内的 127.0.0.1 指向节点自身,不是 WSL 宿主机,因此不能把宿主代理地址原样写成 http://127.0.0.1:8118。
先让 Privoxy 监听 0.0.0.0:8118,使它能够接受来自 Minikube 节点的连接。Minikube 提供的宿主桥接名称在节点内解析为:
1 | host.minikube.internal -> 192.168.49.1 |
验证 TCP 端口:
1 | minikube ssh -- 'nc -vz host.minikube.internal 8118' |
连接成功后,再分别从 WSL 和 Minikube 节点通过 Privoxy 请求 Registry v2 接口:
1 | https://registry.k8s.io/v2/ -> HTTP 401 |
这里的 401 Unauthorized 是预期结果。Docker Registry v2 会先返回认证挑战;能够收到 HTTP 401,说明 DNS、TCP、代理转发和 TLS 握手都已经成功。它不是网络不通的证据。
最终修复:让实际拉取 Pod 镜像的 runtime 使用代理
这并不意味着 containerd 不适合使用代理;继续为 containerd 正确配置代理同样是有效方案。这里选择 Docker runtime,主要是为了复用已经验证成功的 Docker daemon 代理路径,减少学习环境中的额外变量。因此删除失败集群,并通过 --docker-env 把代理交给该 runtime。
1 | minikube delete |
这次配置解决问题的关键不是简单地“换成 Docker”,而是让真正执行 Pod 镜像拉取的 runtime 获得可达的代理地址。
验证最终集群
查看节点:
1 | minikube kubectl -- get nodes -o wide |
关键结果:
1 | STATUS Ready |
查看 Minikube 组件状态:
1 | minikube status |
1 | host: Running |
再次检查 kube-system,以下 Pod 均为 Running:
1 | coredns |
部署一个最小测试应用
创建第一个 Deployment:
1 | minikube kubectl -- create deployment web --image=nginx:alpine |
依次查看 Deployment、ReplicaSet 和 Pod,可以观察控制器如何把声明的副本状态逐层转换为实际运行的容器:
1 | minikube kubectl -- get deployment web |
然后用 NodePort Service 暴露应用:
1 | minikube kubectl -- expose deployment web --type=NodePort --port=80 |
Service 通过标签选择 Pod;现代 Kubernetes 主要使用 EndpointSlice 表示 Service 对应的后端 Pod 端点,同时保留 get endpoints 便于对照。获取本地访问地址:
1 | minikube service web --url |
最终通过以下地址成功访问 Nginx 页面:
1 | http://127.0.0.1:43449 |
在 WSL2 + Docker driver 环境下,minikube service web --url 可能返回 127.0.0.1:<随机端口> 形式的本地访问地址。这里的 43449 并不是 Service 本身的 NodePort,而是 Minikube 为当前环境建立的本地访问入口。
这次验证对应的对象和请求路径是:
1 | Deployment |
Deployment 管理期望副本,ReplicaSet 维持 Pod 数量,Pod 承载 Nginx 容器;Service 提供稳定入口,EndpointSlice 记录后端 Pod 端点,最后 HTTP 请求到达 Nginx。
常用排障路径
这次问题适合按以下顺序排查:
1 | minikube status |
顺序背后的思路是:先确认集群和节点状态,再从异常 Pod 的 Events 找到具体镜像,最后进入节点,沿实际 container runtime 的拉取路径复现网络错误。
Lessons learned
- Docker driver 不等于 Kubernetes container runtime。 本次失败环境使用 Docker driver,但 Pod 镜像实际由节点内的 containerd 拉取。
- Shell 代理、Docker daemon 代理和 container runtime 代理互相独立。 一个组件能联网,不能证明另一个组件也继承了代理。
- Minikube 节点内的
127.0.0.1不是 WSL 宿主 localhost。 它只指向节点自身。 - 访问宿主服务应使用
host.minikube.internal。 本次它解析到192.168.49.1,是节点访问 Privoxy 的正确桥梁。 - 排查
ImagePullBackOff要从 Pod Events 追到真实 runtime。describe pod找到镜像和 Registry,再用crictl pull复现,能避免在错误层面反复修改配置。 - Registry
/v2/返回 HTTP 401 是正常认证挑战。 它证明代理网络和 TLS 路径已经打通,不代表连接失败。 - 注意客户端与集群版本差异。 本次 kubectl 为 v1.34.11,Kubernetes 为 v1.37.0,因此文章使用
minikube kubectl -- ...执行需要版本对齐的命令。
总结
这次故障的表象是节点 NotReady、kindnet ImagePullBackOff,根因却是 Minikube 节点内负责拉取 Pod 镜像的 containerd 没有代理环境。
真正有效的排障不是重复执行 minikube start,而是从 Pod Events 找到失败镜像,确认实际 container runtime,再进入节点复现拉取请求并验证代理路径。最终重建集群,让 Docker runtime 使用 host.minikube.internal:8118,集群恢复 Ready,系统 Pod 和第一个 Nginx 工作负载均正常运行。