【AI图像生成网站&Golang】部署图像生成服务(阿里云ACK+GPU实例)

文章目录

一、模型上传

项目使用的模型文件有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

具体步骤

  1. 若要使用本地命令行控制云端,需要配置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

五、测试

成功调用。

相关推荐
geneculture33 分钟前
社会应用融智学的人力资源模式:潜能开发评估;认知基建资产
人工智能·课程设计·融智学的重要应用·三级潜能开发系统·人力资源升维·认知基建·认知银行
呆呆的小草2 小时前
Cesium距离测量、角度测量、面积测量
开发语言·前端·javascript
uyeonashi2 小时前
【QT系统相关】QT文件
开发语言·c++·qt·学习
仙人掌_lz3 小时前
Qwen-3 微调实战:用 Python 和 Unsloth 打造专属 AI 模型
人工智能·python·ai·lora·llm·微调·qwen3
weixin_985432113 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
冬天vs不冷3 小时前
Java分层开发必知:PO、BO、DTO、VO、POJO概念详解
java·开发语言
sunny-ll3 小时前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
猎人everest3 小时前
快速搭建运行Django第一个应用—投票
后端·python·django
猎人everest3 小时前
Django的HelloWorld程序
开发语言·python·django
嵌入式@秋刀鱼4 小时前
《第四章-筋骨淬炼》 C++修炼生涯笔记(基础篇)数组与函数
开发语言·数据结构·c++·笔记·算法·链表·visual studio code