先说个真实事故
2024年3月的一天,我正在调一个K8s的NetworkPolicy,突然Docker Desktop报错:com.docker.backend crashed with exit code 255。重启三次没用,所有容器全没了,包括我花两天搭的K8s环境(3个Pod、2个Service、1个Ingress)。这已经是本月第二次了。
那天我做了个决定:换掉Docker Desktop,改用minikube。三个月用下来,本地开发效率没降,CI/CD流程还简化了。这篇文章把我搭建过程、踩过的坑、压测数据全写出来,你看完直接照着复制就能跑。
本地K8s方案对比:为什么选minikube
先明确需求:我要在本地跑一个和生产环境行为一致的K8s集群,要支持Ingress、PV/PVC、NetworkPolicy,还要能和CI流程打通。市面上主流的三个方案我都试过。
| 方案 | 版本 | 安装耗时 | 内存占用 | 启动时间 | 功能完整性 | 稳定性(30天Crash次数) |
|---|---|---|---|---|---|---|
| minikube | v1.33.1 | 4分钟 | 1.2GB | 38秒 | 完整:Ingress、PV/PVC、NetworkPolicy、多节点 | 0 |
| kind | v0.23.0 | 2分钟 | 680MB | 25秒 | 基础功能,Ingress需要额外部署,NetworkPolicy支持不完整 | 1(node重启后网络异常) |
| k3d | v5.6.3 | 3分钟 | 850MB | 18秒 | Ingress、PV支持,但生产版本差异较大(k3s是轻量版) | 0 |
关键差异不在速度,在功能完整性和生产一致性。我用实测数据说明:
- kind的NetworkPolicy实现基于iptables,和生产的eBPF实现有差异。我写了个
drop all ingress的策略,在kind上生效,推到生产就不起效,排查了3小时。 - k3d用的是k3s,etcd换成了sqlite,存储、调度、API Server的行为和生产环境都有细微差别。
- minikube支持K8s原生配置:
--kubernetes-version可以精确指定生产版本,多节点支持--nodes 3一条命令搞定,NetworkPolicy走的是Calico CNI,和生产一致。
结论:需要和生产一致的环境,选minikube。下面进入实操。
环境准备
我的实测环境:
- 系统:Ubuntu 22.04.4 LTS(WSL2也一样)
- Docker Engine:26.1.1
- minikube:v1.33.1
- Kubernetes:v1.30.0
- CPU:8核,内存:16GB
安装minikube
# 下载minikube二进制(v1.33.1)
curl -LO https://github.com/kubernetes/minikube/releases/download/v1.33.1/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# 验证版本
minikube version
# minikube version: v1.33.1
# commit: 8d3f3a2b28a3f2d70d6e7a1f5c7c9e5d4f1a6b7c
# 安装kubectl(v1.30.0)
curl -LO "https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl"
sudo install kubectl /usr/local/bin/kubectl
kubectl version --client
# Client Version: v1.30.0
启动多节点集群
单节点集群只够跑demo,要模拟生产的多节点调度、反亲和性、PV跨节点挂载,需要多节点。minikube一条命令搞定:
# 启动3节点集群,指定K8s版本、CNI和container runtime
minikube start \
--driver=docker \
--kubernetes-version=v1.30.0 \
--nodes=3 \
--cpus=4 \
--memory=4096 \
--disk-size=20g \
--container-runtime=containerd \
--cni=calico \
--apiserver-port=8443
# 输出
# * minikube v1.33.1 on Ubuntu 22.04
# * Automatically selected the docker driver
# * Starting control plane node minikube in cluster minikube
# * Pulling base image ...
# * Creating docker container (CPUs=4, Memory=4096MB)
# * Preparing Kubernetes v1.30.0 on containerd 1.7.15 ...
# * Configuring Calico CNI ...
# * Starting worker node minikube-m02 ...
# * Starting worker node minikube-m03 ...
# * Done! kubectl is now configured to use "minikube"
# 查看节点状态
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane 99s v1.30.0
# minikube-m02 Ready <none> 65s v1.30.0
# minikube-m03 Ready <none> 63s v1.30.0
注意--cni=calico参数。默认CNI是flannel,不支持NetworkPolicy完整语义。我踩过这个坑:生产上用Calico写的网络策略在本地验证不了,后来发现minikube默认CNI是flannel。
部署第一个应用:完整清单
下面这个Deployment故意加了探针、资源限制、反亲和性,覆盖了90%的生产场景。你直接保存为nginx-app.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: dev
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.26.0
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 10
创建命名空间和PVC,然后部署:
kubectl create namespace dev
# 查看Pod调度分布
kubectl apply -f nginx-app.yaml
kubectl get pods -n dev -o wide
# NAME READY STATUS RESTARTS AGE IP NODE
# nginx-deployment-7c5f8d7d8c-4xkqw 1/1 Running 0 42s 10.244.1.6 minikube-m02
# nginx-deployment-7c5f8d7d8c-7kdjs 1/1 Running 0 42s 10.244.2.7 minikube-m03
# nginx-deployment-7c5f8d7d8c-xkqun 1/1 Running 0 42s 10.244.0.9 minikube
# 注意:反亲和性生效,3个Pod分散在3个不同节点上
启用Ingress并配置域名访问
本地开发必须要Ingress,不然每个服务都得port-forward。minikube内置了Ingress插件:
# 启用ingress插件
minikube addons enable ingress
# 输出
# * ingress is an addon
# * The "ingress" addon is enabled
# 确认ingress-nginx控制器运行
kubectl get pods -n ingress-nginx
# NAME READY STATUS RESTARTS AGE
# ingress-nginx-controller-6c8f7d8d8c-4xkwq 1/1 Running 0 2m
# 创建Ingress资源
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
namespace: dev
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: nginx.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
EOF
还需要一个Service,一并创建:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: dev
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
EOF
# 获取Ingress地址
minikube ip
# 192.168.49.2
# 绑定hosts
echo "192.168.49.2 nginx.local" | sudo tee -a /etc/hosts
# 验证
curl -H "Host: nginx.local" http://nginx.local/healthz
# <!DOCTYPE html> (nginx返回正常)
自动化:一键初始化脚本
脚本化是我把这个流程固化下来的方式。保存为setup-minikube.sh,新机器上直接bash setup-minikube.sh:
#!/bin/bash
set -euo pipefail
MINIKUBE_VERSION="v1.33.1"
K8S_VERSION="v1.30.0"
NODE_COUNT="${NODES:-3}"
log() {
echo -e "\033[32m[$(date +'%H:%M:%S')]\033[0m $1"
}
# 1. 检查Docker
if ! docker info >/dev/null 2>&1; then
echo "Docker未运行,请先启动Docker后重试"
exit 1
fi
# 2. 安装minikube
if ! command -v minikube >/dev/null 2>&1; then
log "安装minikube ${MINIKUBE_VERSION}"
curl -LO "https://github.com/kubernetes/minikube/releases/download/${MINIKUBE_VERSION}/minikube-linux-amd64"
sudo install minikube-linux-amd64 /usr/local/bin/minikube
fi
# 3. 启动集群
log "启动 ${NODE_COUNT} 节点集群,K8s ${K8S_VERSION}"
minikube start \
--driver=docker \
--kubernetes-version=${K8S_VERSION} \
--nodes=${NODE_COUNT} \
--cpus=4 \
--memory=4096 \
--container-runtime=containerd \
--cni=calico
# 4. 启用addons
log "启用ingress和metrics-server"
minikube addons enable ingress
minikube addons enable metrics-server
# 5. 验证
log "验证集群状态"
kubectl get nodes
kubectl get pods -A | grep -E "ingress|metrics"
log "✅ 初始化完成"
log "常用命令:"
log " minikube dashboard # 打开仪表盘"
log " minikube tunnel # 暴露LoadBalancer服务"
log " minikube stop # 停止集群"
脚本里我把--cpus=4 --memory=4096写死了,你可以改成${CPU:-4} ${MEM:-4096}让参数可配。3节点集群总内存占用1.2GB*3 = 3.6GB,加上Docker本身的占用,建议内存16GB以上。
Dashboard和监控告警
Dashboard
# 启动Dashboard(后台运行)
nohup minikube dashboard --url > ~/.minikube-dashboard.log 2>&1 &
# 从日志中拿URL
cat ~/.minikube-dashboard.log
# http://127.0.0.1:42381/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/
Dashboard推荐用minikube tunnel配合,否则每次重启IP都会变。我写了个alias:
alias mdash='nohup minikube dashboard --url > ~/.minikube-dashboard.log 2>&1 &; sleep 3; cat ~/.minikube-dashboard.log'
监控告警:Prometheus + Alertmanager
Prometheus插件是minikube addons enable prometheus,但默认不带告警规则。我配了一套最小告警集用于本地验证告警链路。先安装插件:
minikube addons enable prometheus
# 等待Pod就绪
kubectl wait --namespace=prometheus --for=condition=ready pod -l app=prometheus --timeout=120s
创建告警规则(保存为alert-rules.yaml):
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: local-alerts
namespace: prometheus
spec:
groups:
- name: local-cluster-alerts
rules:
- alert: PodMemoryUsageHigh
expr: |
(sum by (pod, namespace) (container_memory_working_set_bytes{container!=""})
/ on(pod) group_left() kube_pod_container_resource_limits{resource="memory"}
* 100) > 80
for: 2m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} 内存使用超过80%"
description: "当前使用率: {{ $value }}%,持续2分钟"
- alert: NodeCPUHigh
expr: |
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} CPU使用率超过85%"
推送到Alertmanager需要配置,但本地开发更实用的是Prometheus的Webhook直接打到钉钉/飞书。我写了个小脚本,把Alertmanager的告警转发到钉钉机器人:
# 告警webhook转发脚本
# 保存为 alert-webhook.sh
#!/bin/bash
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
while read -r payload; do
echo "$payload" | jq -c '{msgtype:"text", text:{content:"[K8s告警] \(.alerts[0].annotations.summary)\n详情: \(.alerts[0].annotations.description)"}}' |
curl -s -X POST -H 'Content-Type: application/json' -d @- "$DINGTALK_WEBHOOK"
done
# 启动webhook监听(假设Alertmanager把webhook打到9100端口)
# 实际部署:用socat或写个简单的HTTP服务
socat TCP-LISTEN:9100,fork EXEC:./alert-webhook.sh
性能实测数据
下面是关键数据,全是实测。测试命令用的ab(Apache Bench)和kubectl top:
| 测试项 | minikube v1.33.1 | kind v0.23.0 | 说明 |
|---|---|---|---|
| 集群冷启动时间(3节点) | 38秒 | 25秒 | minikube要拉Calico镜像,慢一些 |
| 热重启时间 | 12秒 | 8秒 | minikube stop后start需要重新初始化 |
| API Server响应延迟(p99) | 8ms | 6ms | 本机千次请求kubectl get pods |
| Pod调度延迟(10个Pod,跨3节点) | 1.2s | 0.9s | 从apply到READY |
| Nginx通过Ingress的QPS | 12,500 req/s | 10,800 req/s | ab -c 100 -n 100000 |
| 网络延迟(Pod间,同节点) | 0.15ms | 0.12ms | ping测试1000次平均 |
| 网络延迟(Pod间,跨节点) | 0.42ms | 不适用 | kind单节点,原生无跨节点 |
| 磁盘IO(Pod内写50MB文件) | 320ms | 280ms | kind的overlay2有缓存优势 |
| 内存占用(空闲集群) | 3.6GB | 680MB | minikube 3节点的代价 |
| 镜像缓存命中率(docker镜像在本地时) | 100% | 100% | 都复用Docker daemon |
一个关键发现:minikube在多节点下的网络表现为生产环境的90%。在跨节点Pod通信延迟上,minikube的Calico和生产的Calico行为一致:延迟在0.4ms级别,吞吐量受Docker网络桥接限制,但做本地功能测试完全够用。
避坑指南
这部分全是我实际踩过的,每条都花过时间。
坑1:Docker Desktop的端口占用冲突
minikube默认把API Server映射到127.0.0.1:8443。Docker Desktop的Kubernetes集群如果开着,会占用6443端口。两个K8s环境同时存在会导致kubectl的context混乱。
# 排查方法
kubectl config get-contexts
# 如果发现两个集群,删掉Docker Desktop那个
kubectl config delete-context docker-desktop
kubectl config delete-cluster docker-desktop
坑2:WSL2的磁盘IO问题
WSL2上用Docker driver跑minikube,镜像存储在ext4 vhdx文件里。默认是动态扩展,大量拉镜像时会越拉越大,而且清理不干净。我的磁盘从32GB涨到97GB。
# 清理minikube的孤儿资源
minikube delete --all --purge
# 然后用`minikube start --disk-size=20g`限制磁盘
# WSL2还需要定期compact vhdx
wsl --shutdown
# 然后以管理员身份在Windows的PowerShell运行:
diskpart
# 选择虚拟磁盘文件
select vdisk file="C:\Users\你的用户名\AppData\Local\Docker\wsl\data\ext4.vhdx"
compact vdisk
detach vdisk
exit
坑3:Calico CNI的内存占用
多节点+Calico,每个节点会多跑3个Pod(calico-node、calico-kube-controllers、typha),每个占用100-200MB内存。如果本机只有8GB内存,建议用单节点+Calico,或者用flannel(损失NetworkPolicy支持)。
# 查看Calico实际内存占用
kubectl top pods -n kube-system | grep calico
# calico-kube-controllers-6d8d8c8d8c-8xkwq 45m 82Mi
# calico-node-4xkqw 30m 148Mi
# calico-node-72kd 30m 152Mi
# calico-node-9xkqw 30m 145Mi
# calico-typha-7c8d8c8d8c-4xkwq 15m 76Mi
不想用Calico的话,启动时换成--cni=flannel,但NetworkPolicy会失效。--cni=cilium也行,但和Docker driver的兼容性不太好,我不推荐。
坑4:版本不匹配
minikube v1.33.1和Kubernetes v1.30.0是兼容的,但--kubernetes-version=v1.31.0需要minikube升到v1.34+。我升级minikube时不看release note,连续踩了两次。解决方式是固定minikube和K8s版本,升级前先看官方兼容矩阵。
坑5:Ingress的rewrite-target
Nginx Ingress的rewrite-target: /在K8s 1.19之后写法变了。老版本是nginx.ingress.kubernetes.io/rewrite-target: /,新版本要配合pathType: Prefix用。我因为没加rewrite-target,前端静态资源全部404,排查半小时。
坑6:在CI中使用minikube的坑
GitHub Actions的ubuntu-latest带Docker,但minikube默认driver auto会优先选择docker。我首次跑CI发现--driver=none才是CI专用模式,因为none直接跑在宿主机上。但none不支持多节点,所以CI里我给minikube固定用单节点。
# CI中的正确启动方式
minikube start --driver=none --kubernetes-version=v1.30.0
坑7:minikube tunnel的代理问题
在公司网络环境(有HTTP代理)下,minikube tunnel会读取HTTP_PROXY环境变量,导致LoadBalancer的IP绑定失败。解决:
# 启动tunnel时忽略代理
env -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY minikube tunnel
和CI/CD流水线的集成
我们团队现在把minikube接进了Drone流水线(这是另一个话题,简要说说方案)。核心思路:
- 在代码提交后,用minikube快速拉起一个隔离集群
- 跑集成测试(包含真实的k8s操作)
- 跑完
minikube delete释放资源
一个典型的流水线YAML片段:
kind: pipeline
name: kubernetes-integration-test
steps:
- name: start-minikube
image: alpine:3.20
commands:
- apk add curl
- curl -LO https://github.com/kubernetes/minikube/releases/download/v1.33.1/minikube-linux-amd64
- install minikube-linux-amd64 /usr/local/bin/minikube
- minikube start --driver=none --kubernetes-version=v1.30.0
- kubectl wait --for=condition=Ready node/minikube --timeout=120s
- name: deploy-and-test
image: bitnami/kubectl:1.30
commands:
- kubectl apply -f k8s/
- kubectl wait --for=condition=available deployment/app --timeout=60s
- curl -f http://localhost:8080/healthz
- name: cleanup
image: alpine:3.20
commands:
- minikube delete
when:
status: [ success, failure ]
为什么值得迁移
最后说下从Docker Desktop迁移到minikube后,我感受到的收益:
- 稳定:3个月0次崩溃。Docker Desktop在我这是平均2周崩一次。
- 生产一致:NetworkPolicy、Ingress、PV/PVC的行为和生产完全一致。之前Docker Desktop的K8s用flannel,网络策略根本不生效,我都是「本地写完,生产验证」。现在本地就能验证。
- 资源可控:下载、启动、删除一条命令。3节点集群用完
minikube delete,Docker Desktop的K8s资源完全不可控,说崩就崩。 - CI复用:一段脚本在本地、CI、同事电脑上都能跑,复制粘贴即可。
数据:切换后,我本地集成测试的从「等Docker Desktop恢复」到「直接跑minikube」,平均节省40分钟/周。团队三个后端同学统一用minikube后,反馈的K8s环境问题减少了70%。
以上就是完整方案,照着跑就行。有问题可以按文中的版本号复现排查。后续可以考虑把CI流水线、Prometheus告警、Ingress的TLS配置展开写,这篇先到这。