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-slimpython: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。

小技巧: 我个人习惯在Dockerfile里加上 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>
注意: 我曾经遇到过一个问题,工作节点加入后状态一直是NotReady。排查了半天,发现是节点时间不同步。所以建议所有节点先配置好NTP时间同步。

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
关键点: GPU Operator会自动管理节点上的GPU驱动、容器运行时和监控组件。你不需要在每个节点上手动安装驱动,Operator会帮你搞定。但前提是节点上得有NVIDIA显卡硬件。

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平台的「血液系统」。