AI Infra 学习路线 · 阶段一 · 模块三(上半部分)
目标:理解声明式 → 搭本地集群 → 掌握 Pod/Deployment/自愈/扩缩容
环境:Windows + WSL2 (Ubuntu 24.04) + Docker Desktop + minikube v1.38.1 + NVIDIA RTX 5060 Ti (8G)
下半部分(GPU 接入 + PyTorch 训练 = 阶段一毕业项目)见后续笔记
0. 核心心智模型:声明式 (最重要)
- 命令式 (Docker 时代):你下一条条指令------"跑这个容器""停那个""删掉它"。机器只管执行,出状况(容器崩了)它不管。
- 声明式 (Kubernetes) :你描述「想要的最终状态」------"我要 3 个这样的容器一直运行着"。K8s 自己负责让现实变成那样,并持续维持:容器挂了自动拉起,机器宕了把任务挪走。
类比:命令式 = 手动开关每盏灯;声明式 = 设定"这房间要一直保持明亮",系统自己换坏掉的灯泡。
一句话:你描述想要什么,K8s 负责实现并持续维持。后面每个对象都是在帮你"描述想要什么"。
为什么 AI Infra 必须用它:成百上千个容器(训练任务、推理服务)跑在几十上百台机器上,手动 docker run 不可能。你只需声明"这个推理服务要 10 个副本、每个 1 张 GPU",K8s 负责调度到哪台机器、哪张卡、挂了怎么重启。它是「集群的操作系统」。
1. 核心概念
| 对象 | 作用 |
|---|---|
| Pod | 最小调度单位。装一个(或几个紧密关联的)容器。K8s 管 Pod 而非直接管容器。可粗略理解为"K8s 世界里的一个容器"。 |
| Deployment | 管理一组相同的 Pod。声明"我要 N 个副本",它负责维持:挂一个补一个,升级时滚动替换。 |
| Service | 给一组 Pod 提供稳定访问入口。Pod 会重建、IP 会变,Service 给它们固定的名字和地址。 |
| Namespace | 把资源分组隔离,像文件夹。不同项目/团队放不同 namespace。 |
| kubectl | 和 K8s 对话的命令行工具。所有操作通过它:get/apply/describe/logs/exec。 |
集群组件(K8s 的"大脑",排查问题会反复见到):
- apiserver:所有操作的唯一入口,kubectl 命令都发给它。
- kubelet:节点上负责真正把容器跑起来的代理。
- control-plane:控制平面,负责调度决策。真实集群里和工作节点分开;minikube 单节点身兼两职。
2. 安装 minikube + kubectl
bash
# kubectl (阿里云源,把版本号写进去)
curl -LO https://kubernetes.oss-cn-hangzhou.aliyuncs.com/kubernetes-release/release/v1.32.0/bin/linux/amd64/kubectl
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client
# minikube (阿里云源,带明确版本号,不要用 latest 路径)
curl -LO https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.38.1/minikube-linux-amd64
# 下完先验证文件!!!
ls -lh minikube-linux-amd64 # 应是 ~100MB,不是几百字节
file minikube-linux-amd64 # 应是 ELF 64-bit executable,不是 XML/HTML
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube version
好习惯 :下载任何二进制后,先 ls -lh 看大小、file 看类型,确认无误再装。
3. 启动集群(Docker 驱动)
bash
# 选了 Docker 驱动:最省心,复用现有 Docker Desktop,GPU 也走得通
# 确保 Docker Desktop 正在运行(minikube 用 Docker 驱动靠它)
minikube start --driver=docker --base-image="gcr.io/k8s-minikube/kicbase:v0.0.50"
成功标志:Done! kubectl is now configured to use "minikube" cluster
验证集群:
bash
kubectl get nodes # 节点 minikube,STATUS=Ready
kubectl cluster-info # control plane 信息
minikube status # host/kubelet/apiserver 都 Running
4. 第一个 Pod(声明式实操)
first-pod.yaml(YAML 对缩进敏感:用空格,每级 2 个,不要 Tab):
yaml
apiVersion: v1
kind: Pod
metadata:
name: my-first-pod
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent # 本地有就用本地,不强制远程拉
ports:
- containerPort: 80
bash
kubectl apply -f first-pod.yaml # 提交声明
kubectl get pods # 看状态
kubectl get pods -w # 实时盯状态变化,Ctrl+C 退出
kubectl describe pod my-first-pod # 详情,重点看底部 Events(排查第一去处)
kubectl logs my-first-pod # 容器日志
kubectl exec -it my-first-pod -- bash # 钻进容器(类比 docker exec)
关键实验:裸 Pod 删了不会自愈
bash
kubectl delete pod my-first-pod
kubectl get pods # No resources found ------ 没人管它,删了就没了
裸 Pod(直接 kind: Pod)没有管理者。要自愈,需要更高层对象 = Deployment。
5. Deployment(自愈 + 扩缩容)
first-deployment.yaml:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3 # 关键:声明要 3 个副本一直在
selector:
matchLabels:
app: nginx # 管理哪些 Pod(靠标签匹配)
template: # 每个 Pod 的模板
metadata:
labels:
app: nginx # 给 Pod 打标签(和上面 selector 对应)
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
bash
kubectl apply -f first-deployment.yaml
kubectl get pods # 3 个 Pod: nginx-deployment-xxxx-yyyy
kubectl get deployment # READY 3/3
kubectl get pods -o wide # 含每个 Pod 的 IP、所在节点
关键实验:Deployment 管的 Pod 删了会自愈
bash
kubectl delete pod <某个pod名>
kubectl get pods # 总数仍是 3!被删的消失,立刻冒出一个 AGE=几秒 的新 Pod 补位
对比裸 Pod:同样删 Pod,裸 Pod 直接没了,Deployment 立刻补回。差别 = 有没有管理者持续维持声明的状态。
声明式扩缩容(改声明,不用删了重建):
bash
kubectl scale deployment nginx-deployment --replicas=5 # 声明要 5 个 → 立刻变 5
kubectl scale deployment nginx-deployment --replicas=2 # 声明要 2 个 → 多的被回收
清理:
bash
kubectl delete deployment nginx-deployment # 删 deployment,它管的 pod 一起清理
6. 踩坑记录(国内环境 minikube 必遇)
坑 1:阿里云 latest 路径下到错误页
curl .../minikube/releases/latest/... 只下到 412 字节,file 显示是 XML。
- 原因:latest 路径在阿里云源不可靠,返回了错误提示页。
- 解决:用带明确版本号的路径
.../releases/v1.38.1/...。下完务必ls -lh+file验证。
坑 2:minikube start 拉 kicbase 极慢 (40 B/s)
日志:cannot pull kicbase image from any docker registry, and is trying to download ... from github release page via HTTP,速度 40 B/s。
- 原因:kicbase(1.28G 基础镜像)从镜像仓库拉失败,回退到 GitHub HTTP 下载,GitHub 在国内极慢。
- 解决:换网 + 下面的手动拉镜像办法。
坑 3:kicbase 镜像 sha256 指纹对不上 (not found)
日志:failed to resolve reference ... @sha256:eb4fec...: not found。
-
原因:minikube 1.38.1 写死要找指纹 eb4fec...,但阿里云那份 kicbase 实际指纹是 39500e5a...,按指纹精确匹配失败。
-
解决(手动拉 + 绕过指纹):
bashminikube delete docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.50 # 按标签拉,不带@sha256 docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.50 gcr.io/k8s-minikube/kicbase:v0.0.50 minikube start --driver=docker --base-image="gcr.io/k8s-minikube/kicbase:v0.0.50" # 用本地镜像,跳过指纹
坑 4:Pod 卡在 ImagePullBackOff / ErrImagePull
kubectl describe pod 的 Events:Failed to pull image "nginx:latest" ... registry-1.docker.io ... EOF。
-
原因:minikube 集群是独立环境,有自己的镜像存储,不走宿主机 Docker 配的加速源,默认直连 Docker Hub 拉失败。
-
关键概念:
ImagePullBackOff= 拉不到镜像后 K8s 退避等待(越等越久再试);ErrImagePull= 拉取失败。这是 K8s 最高频错误之一。 -
解决(本地拉 + load 进集群):
bashdocker pull nginx:latest # 用本地 Docker(已配加速源)拉 minikube image load nginx:latest # 把镜像塞进 minikube 集群(桥梁!) minikube image ls | grep nginx # 确认集群里有了 # YAML 里加 imagePullPolicy: IfNotPresent,告诉 K8s 用本地的别去远程拉 -
备选:用阿里云的 Docker Hub 镜像副本,路径形如
registry.cn-hangzhou.aliyuncs.com/library/nginx:1.21(library 命名空间 = Docker Hub 官方镜像)。
7. 关键认知沉淀
- 命令式 vs 声明式是 K8s 的世界观,比记任何 yaml 字段都重要。
- minikube 集群 ≠ 宿主机 Docker :两个独立的镜像环境,
minikube image load是桥梁。以后自己构建的训练/推理镜像也要这样塞进集群。 - 自愈的本质:你声明期望状态,K8s 持续让现实 = 声明。扩缩容、崩溃重启、故障转移都是这一个原理的放大。
- 排 Pod 问题第一步 :
kubectl describe pod <name>看 Events。
已掌握能力清单
- 理解命令式 vs 声明式
- 安装 kubectl + minikube,国内环境绕过 kicbase 下载/指纹坑
- 启动并验证本地集群
- 写 Pod YAML,apply/get/describe/logs/exec
- 理解并复现 ImagePullBackOff,用 image load + imagePullPolicy 解决
- 写 Deployment YAML,理解 replicas/selector/template/labels
- 亲手验证自愈(删 Pod 自动补回)
- 声明式扩缩容 kubectl scale