3. 容器化与编排:Docker基础、Kubernetes集群搭建、GPU Operator部署、节点池管理
好,咱们进入第三章。这一章可以说是整个AIGC平台的「地基工程」。你想想看,一个企业级的AIGC平台,背后跑着几十上百个模型服务,有推理的、有训练的、有数据预处理的。如果没有容器化和编排,那管理起来简直就是噩梦。
我个人习惯把这一章分成四个部分来讲:Docker基础、Kubernetes集群搭建、GPU Operator部署、节点池管理。咱们一个一个来。
3.1 Docker基础:从镜像到容器
Docker这东西,说白了就是把你的应用连同它的运行环境一起打包。我刚开始接触的时候也觉得不就是个轻量级虚拟机吗?后来才发现,这玩意儿的设计哲学比虚拟机高明多了。
咱们先看一个最基础的Dockerfile,用来打包一个Python推理服务:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "serve.py"]
嗯,这里要注意几个点。第一,python:3.9-slim 比 python:3.9 小了将近一半。我在项目中遇到过,有人直接用完整版镜像,结果一个镜像1.2GB,部署的时候传输慢得要命。第二,把 requirements.txt 单独复制出来安装依赖,这是利用了Docker的层缓存机制。你改代码的时候,依赖没变,就不用重新安装。
构建和运行命令也很简单:
docker build -t my-inference-service:v1 .
docker run -d -p 8080:8080 --gpus all my-inference-service:v1
这里 --gpus all 是让容器能访问GPU。不过说实话,单机Docker跑GPU服务只是小打小闹,真正生产环境还是要上Kubernetes。
RUN apt-get update && apt-get install -y --no-install-recommends,这样能避免安装不必要的包,镜像体积能再小20%左右。
3.2 Kubernetes集群搭建:从零到生产
Kubernetes,简称K8s。这名字怎么来的?K和s之间有8个字母,所以叫K8s。我第一次听到这个解释的时候觉得挺有意思的。
搭建K8s集群,我推荐用kubeadm。为什么?因为它是最官方、最灵活的方式。虽然有些工具能一键部署,但出了问题你都不知道怎么排查。
咱们先看环境准备:
# 所有节点执行
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 设置sysctl参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
然后安装容器运行时,我选containerd:
# 安装containerd
sudo apt-get update
sudo apt-get install -y containerd
# 配置containerd使用systemd cgroup驱动
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
接下来安装kubeadm、kubelet、kubectl:
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
初始化控制平面节点:
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
# 配置kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
然后安装网络插件,我选Flannel:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
工作节点加入集群:
sudo kubeadm join <控制平面IP>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
3.3 GPU Operator部署:让K8s认识GPU
K8s默认是不认识GPU的。你得告诉它,这个节点上有GPU,而且要让容器能用到。NVIDIA GPU Operator就是干这个的。
部署之前,先确保节点上有NVIDIA驱动:
nvidia-smi
# 确认驱动版本,建议535以上
然后安装NVIDIA Container Toolkit:
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart containerd
接下来用Helm安装GPU Operator:
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install gpu-operator nvidia/gpu-operator \
--namespace gpu-operator \
--create-namespace \
--set toolkit.enabled=true
验证部署:
kubectl get pods -n gpu-operator
# 应该看到所有Pod都是Running状态
kubectl describe node <node-name> | grep nvidia.com/gpu
# 应该能看到节点上的GPU数量
部署完成后,你就可以在Pod里请求GPU了:
apiVersion: v1
kind: Pod
metadata:
name: gpu-test
spec:
containers:
- name: cuda-container
image: nvidia/cuda:12.2.0-base-ubuntu22.04
command: ["nvidia-smi"]
resources:
limits:
nvidia.com/gpu: 1
3.4 节点池管理:弹性与成本的艺术
节点池,说白了就是把一组节点放在一起管理。为什么要这么做?因为不同的工作负载对资源的需求不一样。
比如,训练任务需要A100这种大显存GPU,推理任务用T4就够了,数据预处理可能只需要CPU。你把这些任务混在一起跑,资源利用率会很低。
我建议这样划分节点池:
| 节点池名称 | 实例类型 | GPU型号 | 用途 |
|---|---|---|---|
| gpu-a100-pool | GPU计算型 | NVIDIA A100 | 大模型训练 |
| gpu-t4-pool | GPU通用型 | NVIDIA T4 | 模型推理 |
| cpu-pool | 通用型 | 无 | 数据预处理、API网关 |
| spot-pool | 竞价实例 | NVIDIA A10 | 批量推理、实验 |
在K8s里,节点池通常通过节点标签和污点来实现。比如给A100节点打上标签:
kubectl label node <node-name> gpu-type=a100
kubectl taint node <node-name> gpu-type=a100:NoSchedule
然后在Pod里通过节点选择器和容忍度来控制调度:
apiVersion: v1
kind: Pod
metadata:
name: training-job
spec:
nodeSelector:
gpu-type: a100
tolerations:
- key: "gpu-type"
operator: "Equal"
value: "a100"
effect: "NoSchedule"
containers:
- name: training
image: my-training-image:v1
resources:
limits:
nvidia.com/gpu: 4
如果是云环境,还可以结合Cluster Autoscaler实现自动扩缩容。当Pod因为资源不足而Pending时,自动创建新节点;当节点利用率低时,自动回收节点。
嗯,这一章的内容就到这里。容器化和编排这块,说白了就是让资源管理变得自动化、标准化。你想想看,没有Docker的时候,部署一个服务要手动装依赖、配环境,出了错都不知道是代码问题还是环境问题。有了K8s之后,这些问题都变成了声明式配置,出了问题直接看YAML文件就行。
下一章咱们聊聊数据管道和模型存储,那是AIGC平台的「血液系统」。