文章目录
一、模型上传
项目使用的模型文件有30多个G,直接创建容器会在创建过程中占用内存过大,以至于磁盘崩溃,实时下载需要额外给集群中的服务配置连接外网的通道,所以选择将容器挂载到集群中,然后映射到容器中调用。
官方提供的上传方式有四种------ossutil、OSS SDK、OSS API 以及直接拖拽上传。我选择将文件夹直接在网页上上传,然后使用ossutil上传超过5GB的文件。
上传文件的命令行如下:
bash
ossutil cp D:/localpath/example.iso[本地文件] oss://examplebucket/desfolder/[目标文件夹]
Bucket里可以不创建文件所在的文件夹目录,上传时平台会自动创建相关目录存放文件。
二、容器构建与上传
服务使用grpc与后端通信,监听端口为50051。
目录结构如下:
由于后端代码是用go写的,所以通过ai.proto文件生成了ai.pb.go 与 ai_grpc.pb.go用来与后端通信。
相关代码
服务端:
python
# server.py
import grpc
from concurrent import futures
import ai_pb2, ai_pb2_grpc
import requests, base64, torch, asyncio
from diffusers import StableDiffusionInstructPix2PixPipeline, EulerAncestralDiscreteScheduler
from PIL import Image, ImageOps
from io import BytesIO
import os
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["TRANSFORMERS_OFFLINE"] = "1"
# --- model init (same as before) ---
model_id = "./instruct-pix2pix"
# model_id = "timbrooks/instruct-pix2pix"
pipe = StableDiffusionInstructPix2PixPipeline.from_pretrained(
model_id, torch_dtype=torch.float16, safety_checker=None,
local_files_only=True, # ← 强制本地加载
)
pipe.to("cuda")
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
num_inference_steps = 5
def download_and_prep(url: str) -> Image.Image:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
img = Image.open(BytesIO(resp.content))
img = ImageOps.exif_transpose(img).convert("RGB")
return img.resize((512,512), Image.Resampling.LANCZOS)
def progress_callback(step: int, timestep: int, latents):
# step 是当前步数(0 ~ num_inference_steps-1)
print(f"[Inference] Step {step+1}/{num_inference_steps} (timestep={timestep})")
class AiService(ai_pb2_grpc.AiServiceServicer):
def Generate(self, request, context):
print(f"[Generate] 收到请求 prompt={request.prompt!r}, url={request.url}")
try:
# synchronous single-image inference
img = download_and_prep(request.url)
print("[Generate] 下载并预处理完毕,开始推理")
out = pipe(
prompt=request.prompt,
image=[img],
num_inference_steps=num_inference_steps,
image_guidance_scale=1,
callback=progress_callback,
callback_steps=1,
)
print("[Generate] 推理完成,准备返回")
buf = BytesIO()
out.images[0].save(buf, format="PNG")
return ai_pb2.GenerateReply(
processed_image=buf.getvalue(),
error=""
)
except Exception as e:
print(f"[Generate] 捕获到异常: {e}")
return ai_pb2.GenerateReply(
processed_image=b"",
error=str(e)
)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
ai_pb2_grpc.add_AiServiceServicer_to_server(AiService(), server)
server.add_insecure_port('[::]:50051')
print("Starting gRPC AI server on port 50051 ...")
server.start()
server.wait_for_termination()
if __name__ == "__main__":
serve()
后端
go
package api
import (
"backend/ai_service"
"context"
"encoding/base64"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"time"
)
// InstructPix2PixGRPC 通过 gRPC 调用后端生成图像
func InstructPix2PixGRPC(ctx context.Context, prompt, url string) (string, error) {
// 1. 建立 gRPC 连接
conn, err := grpc.DialContext(ctx, "localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()), // 不用 TLS
grpc.WithBlock(), // 等待连接成功
)
if err != nil {
return "", fmt.Errorf("连接 gRPC 服务失败: %w", err)
} else {
fmt.Println("连接 gRPC 服务成功")
}
defer conn.Close()
// 2. 创建客户端
client := ai_service.NewAiServiceClient(conn)
// 3. 设置超时时间
ctx, cancel := context.WithTimeout(ctx, 1000*time.Second)
defer cancel()
// 4. 发起请求
req := &ai_service.GenerateRequest{
Url: url,
Prompt: prompt,
}
resp, err := client.Generate(ctx, req)
if err != nil {
return "", fmt.Errorf("gRPC 调用失败: %w", err)
}
if resp.Error != "" {
return "", fmt.Errorf("服务端返回错误: %s", resp.Error)
}
fmt.Printf("服务端返回: %s", resp)
// 5. 将二进制图像数据 base64 编码
encoded := base64.StdEncoding.EncodeToString(resp.ProcessedImage)
fmt.Printf("编码: %s", encoded)
return encoded, nil
}
Dockerfile:
bash
# syntax=docker/dockerfile:1
FROM docker.io/python:3.9-slim
# 1. 系统依赖(如果需要编译 Pillow 等)
RUN apt-get update && apt-get install -y --no-install-recommends build-essential libjpeg-dev && rm -rf /var/lib/apt/lists/*
# 2. 创建工作目录
WORKDIR /app
# 3. 复制 Python 依赖列表并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 安装 GPU 版 PyTorch (cu121)
RUN pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 5. 复制服务代码
COPY api_offline.py ai_pb2.py ai_pb2_grpc.py ./
# 6. 环境变量:关闭联网拉取
ENV HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1
# 如果你指定过 revision,还可以把它写到环境里
# 7. 暴露 gRPC 端口
EXPOSE 50051
# 8. 默认启动命令
CMD ["python", "api_offline.py"]
使用命令docker build -t my-instruct-pix2pix-offline .
在本地创建镜像后,用以下命令在本地测试运行容器:
bash
docker run --gpus all -it -d --name ai-offline -v F:/ai_service/instruct-pix2pix:/app/instruct-pix2pix:ro -p 50051:50051 docker-name
参数说明:
--gpus all
:模将宿主机所有 GPU 分配给容器,用于加速深度学习推理。-it
:连接标准输入和伪终端,支持交互式操作-d
:以后台模式(守护进程)运行容器-v F:/ai_service/instruct-pix2pix:/app/instruct-pix2pix:ro
:- 将本地目录
F:\ai_service\instruct-pix2pix
挂载到容器/app/instruct-pix2pix
:ro
表示只读,防止容器修改宿主机文件
- 将本地目录
-p 50051:50051
:映射容器的 50051 端口到宿主机同端口,外部可通过localhost:50051
访问
本地部署运行测试后,推送到云端
bash
# 先打tag
docker tag docker-name:latest crpi-xxxxxxx.cn-xxxxx.personal.cr.aliyuncs.com/ai-web-rpc/docker-name:latest
# 推送
docker push crpi-xxxxxxx.cn-xxxxx.personal.cr.aliyuncs.com/ai-web-rpc/docker-name:latest
三、集群搭建
接下来使用ACK服务搭建k8s集群。
1. ACK创建集群


网络插件选Terway,Flannel容易有版本不适配的问题。

点下一步,开始创建节点池。
GPU实例选择: i
型适用于模型推理,v
型适用于模型训练,ENI 数量与可创建 pod 数挂钩,这里ENI数量为4时,可创建pod数为33

其他选项默认,推理模型节点设置为1
即可

剩下的默认就行,自己测试的话这里可以选基础版

检测全部通过就可以创建集群了,注意余额要大于100,不然会报错,学生每年会有300块的优惠券,记得领

创建成功
等待节点池和节点就绪之后检查节点中pod运行情况会发现

解决方法在【k8s】阿里云ACK服务中GPU实例部署问题,暂时没发现别的解决方法,有小伙伴有更好的办法的话可以在评论区分享。
四、运行容器
参考了阿里云的文档:使用阿里云容器ACK通过云存储网关(CSG)挂载OSS
整体步骤
bash
$env:KUBECONFIG = "ack-kubeconfig.yaml"
kubectl apply -f namespace.yaml
kubectl apply -f secrets.yaml
kubectl create -f oss-pv.yaml
kubectl get pv
kubectl create -f oss-pvc.yaml
kubectl get pvc -n ai-service
具体步骤
- 若要使用本地命令行控制云端,需要配置
KUBECONFIG
环境变量
bash
$env:KUBECONFIG = "ack-kubeconfig.yaml"

创建命名空间
bash
kubectl apply -f namespace.yaml
yaml
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ai-service
创建并部署ACR镜像拉取密码
创建密码
bash
kubectl -n ai-service create secret docker-registry regcred --<你的网址> --docker-username=<你的账号> --docker-password=<你的密码> --dry-run=client -o yaml > secrets.yaml
可在以下页面设置密码
yaml
# secrets.yaml
apiVersion: v1
data:
.dockerconfigjson: *******
kind: Secret
metadata:
creationTimestamp: null
name: regcred
namespace: ai-service
type: kubernetes.io/dockerconfigjson
部署密码
bash
kubectl apply -f secrets.yaml
创建oss-pv
yaml
# oss-pv.yaml
apiVersion: v1
kind: Secret
metadata:
name: oss-secret
namespace: ai-service
stringData:
akId: "Id"
akSecret: "Secret"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: oss-pv
labels:
alicloud-pvname: oss-pv
spec:
storageClassName: oss-scn
capacity:
storage: 40Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: ossplugin.csi.alibabacloud.com
volumeHandle: oss-pv
nodePublishSecretRef:
name: oss-secret
namespace: ai-service
volumeAttributes:
bucket: "Bucket name"
url: "OSS endpoint"
path: "子目录"
otherOpts: "-o max_stat_cache_size=0 -o allow_other"
bash
kubectl create -f oss-pv.yaml
kubectl get pv
查看状态
bash
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
oss-pv 40Gi RWX Retain Bound ai-service/oss-pvc oss-scn <unset> 2m49s

部署oss-pvc
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: oss-pvc
namespace: ai-service
spec:
storageClassName: oss-scn
accessModes:
- ReadWriteMany
resources:
requests:
storage: 40Gi
selector:
matchLabels:
alicloud-pvname: oss-pv
bash
kubectl create -f oss-pvc.yaml
kubectl get pvc -n ai-service
查看状态
bash
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
oss-pvc Bound oss-pv 40Gi RWX oss-scn <unset> 1s
部署服务pod

将pod对应的yaml文件复制到模板,可以选择保存模板,这样再次创建的时候可以直接使用。
点击下方的链接,会自动跳转到容器组子页面。
成功运行
成功运行后,在本地运行下面命令行可以将容器地址映射到本地调用。
bash
kubectl port-forward -n ai-service pod/ai-service 50051:50051
五、测试
成功调用。