在 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 里有几个明显优势:

  1. 可以直接使用 Linux 命令行和工具链;
  2. 不需要额外准备虚拟机;
  3. 可以直接使用现有的 Docker / container runtime;
  4. 实验环境可以随时删除和重建;
  5. 与日常使用的 Git、Shell、VS Code 等工具能够自然结合。

对于我的使用场景来说,“方便破坏、方便重建”比“尽量模拟生产环境”更加重要。


Minikube、kind 和 Docker Desktop Kubernetes 怎么选

在本地学习 Kubernetes 时,常见选择包括 Minikube、kind 和 Docker Desktop Kubernetes,但三者的侧重点不同。

Minikube

Minikube 面向本地学习和开发,支持 Docker、KVM、Hyper-V 等 driver,并提供方便实验的命令:

1
2
3
minikube dashboard
minikube addons list
minikube service <service-name>

它操作直观,适合主动制造故障并观察 Kubernetes 的行为,因此我选择 Minikube 作为主要实验环境。


kind

kind 即 Kubernetes IN Docker,使用 Docker container 模拟 Kubernetes node:

1
2
kind create cluster
kind delete cluster

集群创建和销毁较快,也适合在 CI 中临时运行 integration test:

1
2
3
4
5
6
7
8
9
CI

创建 kind cluster

部署待测试组件

运行 integration test

删除 cluster

如果目标是 Controller / Operator 开发、CI 集成测试或快速创建多个测试集群,kind 往往更合适;而我的重点是学习 Kubernetes 运维、排障和资源行为。


Docker Desktop Kubernetes

Docker Desktop 可以直接启用 Kubernetes,配置最少:

1
2
3
4
5
Docker Desktop

Enable Kubernetes

kubectl get nodes

但我希望环境运行在 WSL2 的 Linux 工具链中,并且需要频繁执行:

1
2
3
4
5
创建集群
→ 修改配置
→ 制造故障
→ 删除集群
→ 重新创建

Minikube 对这种实验式工作流更灵活。


最终选择

因此我目前的选择是:

工具 更适合的场景
Minikube Kubernetes 学习、实验、排障
kind CI、Operator 开发、快速测试集群
Docker Desktop Kubernetes 希望最少配置快速获得一个本地 Kubernetes

我的目标是建立一个可以反复破坏和恢复的 Kubernetes 实验场,因此本文后续使用 Minikube + WSL2

我的环境

这次排障使用的实际环境如下:

1
2
3
4
5
6
7
8
9
10
11
Windows 10 + WSL2
Linux DESKTOP-P5CGV6I 6.18.33.2-microsoft-standard-WSL2
Ubuntu 26.04.1 LTS (Resolute)
CPU: 36 cores
Memory: 31 GiB
Swap: 8 GiB
Docker Engine: 29.8.0(安装在 WSL 内)
kubectl client: v1.34.11
Minikube: v1.39.0
Kubernetes: v1.37.0
HTTP proxy: http://127.0.0.1:8118(Privoxy)

安装前准备

安装 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
2
3
HTTP_PROXY=http://127.0.0.1:8118
HTTPS_PROXY=http://127.0.0.1:8118
NO_PROXY=localhost,127.0.0.1,::1

重新加载 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
2
Minikube driver: docker
Kubernetes container runtime: containerd 2.3.4

启动过程中首先出现 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
2
3
kindnet                ImagePullBackOff
coredns Pending
storage-provisioner Pending

corednsstorage-provisionerPending 是结果,不一定是根因。继续使用 kubectl describe pod 查看 Events,发现最早、最具体的错误来自 kindnet:

1
2
docker.io/kindest/kindnetd:v20260820-69b56db7
direct TCP timeout to registry-1.docker.io

因此排障方向从“为什么节点 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
2
minikube ssh -- \
'sudo crictl pull docker.io/kindest/kindnetd:v20260820-69b56db7'

结果仍然是超时。至此可以确认:失败点不在 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
2
3
4
5
6
7
8
minikube delete

minikube start \
--driver=docker \
--container-runtime=docker \
--docker-env HTTP_PROXY=http://host.minikube.internal:8118 \
--docker-env HTTPS_PROXY=http://host.minikube.internal:8118 \
--docker-env NO_PROXY=localhost,127.0.0.1,::1,192.168.49.0/24,10.96.0.0/12

这次配置解决问题的关键不是简单地“换成 Docker”,而是让真正执行 Pod 镜像拉取的 runtime 获得可达的代理地址。

验证最终集群

查看节点:

1
minikube kubectl -- get nodes -o wide

关键结果:

1
2
3
4
STATUS              Ready
Kubernetes v1.37.0
INTERNAL-IP 192.168.49.2
CONTAINER-RUNTIME docker://29.7.2

查看 Minikube 组件状态:

1
minikube status
1
2
3
4
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

再次检查 kube-system,以下 Pod 均为 Running

1
2
3
4
5
6
7
coredns
etcd
kube-apiserver
kube-controller-manager
kube-proxy
kube-scheduler
storage-provisioner

部署一个最小测试应用

创建第一个 Deployment:

1
minikube kubectl -- create deployment web --image=nginx:alpine

依次查看 Deployment、ReplicaSet 和 Pod,可以观察控制器如何把声明的副本状态逐层转换为实际运行的容器:

1
2
3
minikube kubectl -- get deployment web
minikube kubectl -- get replicaset
minikube kubectl -- get pod

然后用 NodePort Service 暴露应用:

1
2
3
4
minikube kubectl -- expose deployment web --type=NodePort --port=80
minikube kubectl -- get service web
minikube kubectl -- get endpoints web
minikube kubectl -- get endpointslice

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
2
3
4
5
6
Deployment
-> ReplicaSet
-> Pod
-> Service
-> EndpointSlice
-> HTTP request

Deployment 管理期望副本,ReplicaSet 维持 Pod 数量,Pod 承载 Nginx 容器;Service 提供稳定入口,EndpointSlice 记录后端 Pod 端点,最后 HTTP 请求到达 Nginx。

常用排障路径

这次问题适合按以下顺序排查:

1
2
3
4
5
6
7
minikube status
minikube kubectl -- get nodes -o wide
minikube kubectl -- get pods -A
minikube kubectl -- describe pod <pod-name> -n <namespace>
minikube ssh -- 'sudo systemctl show containerd --property=Environment'
minikube ssh -- 'sudo crictl pull <image>'
minikube ssh -- 'nc -vz host.minikube.internal 8118'

顺序背后的思路是:先确认集群和节点状态,再从异常 Pod 的 Events 找到具体镜像,最后进入节点,沿实际 container runtime 的拉取路径复现网络错误。

Lessons learned

  1. Docker driver 不等于 Kubernetes container runtime。 本次失败环境使用 Docker driver,但 Pod 镜像实际由节点内的 containerd 拉取。
  2. Shell 代理、Docker daemon 代理和 container runtime 代理互相独立。 一个组件能联网,不能证明另一个组件也继承了代理。
  3. Minikube 节点内的 127.0.0.1 不是 WSL 宿主 localhost。 它只指向节点自身。
  4. 访问宿主服务应使用 host.minikube.internal 本次它解析到 192.168.49.1,是节点访问 Privoxy 的正确桥梁。
  5. 排查 ImagePullBackOff 要从 Pod Events 追到真实 runtime。 describe pod 找到镜像和 Registry,再用 crictl pull 复现,能避免在错误层面反复修改配置。
  6. Registry /v2/ 返回 HTTP 401 是正常认证挑战。 它证明代理网络和 TLS 路径已经打通,不代表连接失败。
  7. 注意客户端与集群版本差异。 本次 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 工作负载均正常运行。