【AI图像生成网站Golang】部署图像生成服务(阿里云ACK+GPU实例)
文章目录
- 一、模型上传
- 二、容器构建与上传
- 相关代码
- 服务端:
- 后端
- Dockerfile:
- 三、集群搭建
- 1. ACK创建集群
- 四、运行容器
- 整体步骤
- 具体步骤
- 部署服务pod
- 五、测试
一、模型上传
项目使用的模型文件有30多个G,直接创建容器会在创建过程中占用内存过大,以至于磁盘崩溃,实时下载需要额外给集群中的服务配置连接外网的通道,所以选择将容器挂载到集群中,然后映射到容器中调用。
官方提供的上传方式有四种——ossutil、OSS SDK、OSS API 以及直接拖拽上传。我选择将文件夹直接在网页上上传,然后使用ossutil上传超过5GB的文件。
上传文件的命令行如下:
ossutil cp D:/localpath/example.iso[本地文件] oss://examplebucket/desfolder/[目标文件夹]
Bucket里可以不创建文件所在的文件夹目录,上传时平台会自动创建相关目录存放文件。
二、容器构建与上传
服务使用grpc与后端通信,监听端口为50051。
目录结构如下:
由于后端代码是用go写的,所以通过ai.proto文件生成了ai.pb.go 与 ai_grpc.pb.go用来与后端通信。
相关代码
服务端:
# server.py
import grpc
from concurrent import futures
import ai_pb2, ai_pb2_grpcimport requests, base64, torch, asyncio
from diffusers import StableDiffusionInstructPix2PixPipeline, EulerAncestralDiscreteScheduler
from PIL import Image, ImageOps
from io import BytesIOimport 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 inferenceimg = 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()
后端
package apiimport ("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()), // 不用 TLSgrpc.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:
# syntax=docker/dockerfile:1FROM 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 .
在本地创建镜像后,用以下命令在本地测试运行容器:
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
访问
本地部署运行测试后,推送到云端
# 先打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
整体步骤
$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
环境变量
$env:KUBECONFIG = "ack-kubeconfig.yaml"
创建命名空间
kubectl apply -f namespace.yaml
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:name: ai-service
创建并部署ACR镜像拉取密码
创建密码
kubectl -n ai-service create secret docker-registry regcred --<你的网址> --docker-username=<你的账号> --docker-password=<你的密码> --dry-run=client -o yaml > secrets.yaml
可在以下页面设置密码
# secrets.yaml
apiVersion: v1
data:.dockerconfigjson: *******
kind: Secret
metadata:creationTimestamp: nullname: regcrednamespace: ai-service
type: kubernetes.io/dockerconfigjson
部署密码
kubectl apply -f secrets.yaml
创建oss-pv
# oss-pv.yaml
apiVersion: v1
kind: Secret
metadata:name: oss-secretnamespace: ai-service
stringData:akId: "Id"akSecret: "Secret"
---
apiVersion: v1
kind: PersistentVolume
metadata:name: oss-pvlabels:alicloud-pvname: oss-pv
spec:storageClassName: oss-scncapacity:storage: 40GiaccessModes:- ReadWriteManypersistentVolumeReclaimPolicy: Retaincsi:driver: ossplugin.csi.alibabacloud.comvolumeHandle: oss-pvnodePublishSecretRef:name: oss-secretnamespace: ai-servicevolumeAttributes:bucket: "Bucket name"url: "OSS endpoint"path: "子目录"otherOpts: "-o max_stat_cache_size=0 -o allow_other"
kubectl create -f oss-pv.yaml
kubectl get pv
查看状态
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
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: oss-pvcnamespace: ai-service
spec:storageClassName: oss-scnaccessModes:- ReadWriteManyresources:requests:storage: 40Giselector:matchLabels:alicloud-pvname: oss-pv
kubectl create -f oss-pvc.yaml
kubectl get pvc -n ai-service
查看状态
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
oss-pvc Bound oss-pv 40Gi RWX oss-scn <unset> 1s
部署服务pod
将pod对应的yaml文件复制到模板,可以选择保存模板,这样再次创建的时候可以直接使用。
点击下方的链接,会自动跳转到容器组子页面。
成功运行
成功运行后,在本地运行下面命令行可以将容器地址映射到本地调用。
kubectl port-forward -n ai-service pod/ai-service 50051:50051
五、测试
成功调用。