从零构建 Serverless 图片处理 API:FunctionGraph + OBS + API Gateway 全栈实践
【摘要】 传统图片处理服务需要运维服务器、配置负载均衡、担心流量突增。本文手把手教你用华为云 FunctionGraph(无服务器函数计算)+ OBS(对象存储)+ API Gateway,从零搭建一个按调用量计费、自动弹性伸缩的图片处理 API,成本降低 90%。
## 一、为什么选择 Serverless?
先算一笔账:
| 方案 | 月固定成本 | 弹性能力 | 运维成本 |
|------|-----------|---------|---------|
| 自建 ECS 集群 | ¥2000+ | 手动扩容 | 需专人运维 |
| 容器化部署 | ¥1000+ | 需配置 HPA | 需 K8s 团队 |
| **FunctionGraph** | **¥0(按调用计费)** | **毫秒级自动伸缩** | **零运维** |
华为云 FunctionGraph 的核心优势:
- **按调用计费**:函数不运行就不花钱,冷启动后按 GB·s 计费
- **自动弹性**:流量从 1 QPS 到 10000 QPS,无需任何配置
- **与华为云生态深度集成**:OBS 触发、APIG 网关、SMN 消息通知一键打通
---
## 二、架构总览
用户上传 → API Gateway → FunctionGraph → OBS 存储
↓
返回处理结果
↓
OBS 触发缩略图函数 → 生成缩略图 → 存回 OBS
**涉及服务**:
| 服务 | 作用 |
|------|------|
| OBS | 存储原图和缩略图 |
| FunctionGraph | 运行图片处理逻辑 |
| API Gateway | 对外暴露 HTTP 接口 |
| IAM | 权限管理 |
---
## 三、实战:5 步搭建图片处理 API
### 第 1 步:创建 OBS 存储桶
登录华为云控制台,进入 **对象存储服务 OBS**:
1. 点击「创建桶」
2. 桶名称:`image-processor-xxxx`(全局唯一)
3. 区域:选择离你用户最近的区域(如 `华北-北京四`)
4. 存储类型:标准存储
5. 桶策略:**私有**(通过 API Gateway 临时授权访问)
创建后,在桶内创建两个文件夹:
- `original/` — 存放原图
- `thumbnail/` — 存放缩略图
### 第 2 步:编写图片处理函数
进入 **函数工作流 FunctionGraph**,点击「创建函数」:
**函数配置**:
| 配置项 | 值 |
|--------|-----|
| 函数名称 | `image-resize-handler` |
| 运行时 | Python 3.9 |
| 函数类型 | 事件函数 |
| 内存 | 256 MB |
| 超时 | 30 秒 |
**完整代码**(直接复制):
```python
# -*- coding: utf-8 -*-
"""
华为云 FunctionGraph 图片处理函数
依赖:Pillow(华为云内置),无需额外安装
"""
import json
import io
import os
import logging
from PIL import Image
# 华为云 OBS SDK(FunctionGraph 内置)
from huaweicloudsdkcore.auth.credentials import BasicCredentials
from huaweicloudsdkcore.exceptions import exceptions
from huaweicloudsdkcore.http.http_config import HttpConfig
from huaweicloudsdkobs.v2.region.obs_region import ObsRegion
from huaweicloudsdkobs.v2 import *
# 日志配置
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 环境变量(函数创建时配置)
BUCKET_NAME = os.environ.get("BUCKET_NAME", "image-processor-xxxx")
THUMBNAIL_WIDTH = int(os.environ.get("THUMBNAIL_WIDTH", "300"))
THUMBNAIL_HEIGHT = int(os.environ.get("THUMBNAIL_HEIGHT", "300"))
def get_obs_client():
"""创建 OBS 客户端(函数冷启动时复用)"""
# FunctionGraph 运行时自动注入环境变量
# 实际生产中建议使用 CSMS 管理 AK/SK
ak = os.environ.get("HUAWEI_AK")
sk = os.environ.get("HUAWEI_SK")
region = os.environ.get("HUAWEI_REGION", "cn-north-4")
credentials = BasicCredentials(ak, sk)
http_config = HttpConfig.get_default_config()
region_info = ObsRegion(region)
return ObsClient.new_builder() \
.with_http_config(http_config) \
.with_credentials(credentials) \
.with_region(region_info) \
.build()
# 全局复用 OBS 客户端(减少冷启动延迟)
_obs_client = None
def resize_image(image_data, max_width, max_height):
"""按比例缩放图片"""
img = Image.open(io.BytesIO(image_data))
original_format = img.format
# 计算缩放比例
width, height = img.size
ratio = min(max_width / width, max_height / height)
new_size = (int(width * ratio), int(height * ratio))
# 执行缩放
resized = img.resize(new_size, Image.Resampling.LANCZOS)
# 输出为 JPEG
output = io.BytesIO()
resized.save(output, format="JPEG", quality=85)
return output.getvalue(), original_format
def handler(event, context):
"""函数入口"""
global _obs_client
if _obs_client is None:
_obs_client = get_obs_client()
logger.info("收到事件: %s", json.dumps(event))
try:
# 解析 API Gateway 传入的事件
body = json.loads(event.get("body", "{}"))
image_key = body.get("image_key") # OBS 中的对象路径
if not image_key:
return {
"statusCode": 400,
"body": json.dumps({"error": "缺少 image_key 参数"}),
"headers": {"Content-Type": "application/json"}
}
# 从 OBS 下载原图
download_request = DownloadFileRequest(BUCKET_NAME, f"original/{image_key}")
response = _obs_client.download_file(download_request)
if response.status_code != 200:
return {
"statusCode": 404,
"body": json.dumps({"error": "图片不存在"}),
"headers": {"Content-Type": "application/json"}
}
image_data = response.body.read()
# 执行缩放
thumbnail_data, _ = resize_image(image_data, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
# 上传缩略图到 OBS
thumbnail_key = f"thumbnail/{image_key.replace('.jpg', '_thumb.jpg').replace('.png', '_thumb.jpg')}"
upload_request = UploadFileRequest(
BUCKET_NAME,
thumbnail_key,
content=io.BytesIO(thumbnail_data)
)
upload_response = _obs_client.upload_file(upload_request)
logger.info("缩略图已生成: %s", thumbnail_key)
# 生成临时访问 URL(有效期 1 小时)
signed_url = _obs_client.create_signed_url(
BUCKET_NAME,
thumbnail_key,
expires=3600
)
return {
"statusCode": 200,
"body": json.dumps({
"message": "缩略图生成成功",
"thumbnail_url": signed_url.signed_url,
"thumbnail_key": thumbnail_key
}, ensure_ascii=False),
"headers": {"Content-Type": "application/json"}
}
except Exception as e:
logger.error("处理失败: %s", str(e))
return {
"statusCode": 500,
"body": json.dumps({"error": str(e)}),
"headers": {"Content-Type": "application/json"}
}
环境变量配置(在函数「配置」→「环境变量」中添加):
HUAWEI_AK=你的AK
HUAWEI_SK=你的SK
BUCKET_NAME=image-processor-xxxx
HUAWEI_REGION=cn-north-4
THUMBNAIL_WIDTH=300
THUMBNAIL_HEIGHT=300
⚠️ 安全提示:生产环境建议将 AK/SK 存储在 云数据库服务(CSMS) 中,通过函数运行时动态获取,不要硬编码。
第 3 步:配置 API Gateway 触发器
回到函数详情页,点击「触发器」→「创建触发器」:
| 配置项 | 值 |
|---|---|
| 触发器类型 | API 网关触发器 |
| 协议 | HTTPS |
| 方法 | POST |
| 路径 | /resize |
| 发布环境 | RELEASE |
创建后,你会获得一个 API 调用地址,格式如:
xxx
第 4 步:测试 API
# 替换为你的 API 地址
API_URL="";
# 调用测试
curl -X POST "$API_URL" \
-H "Content-Type: application/json" \
-d '{"image_key": "photo.jpg"}'
成功响应:
{
"message": "缩略图生成成功",
"thumbnail_url": "";,
"thumbnail_key": "thumbnail/photo_thumb.jpg"
}
第 5 步:配置 OBS 事件触发(可选)
如果你想实现 上传即自动处理:
- 进入 OBS 桶详情页 → 「事件通知」→ 「创建事件通知」
- 事件类型:
PostObject(上传对象) - 前缀过滤:
original/ - 触发目标:选择刚才创建的函数
image-resize-handler
这样,任何文件上传到 original/ 目录,函数会自动执行,缩略图秒级生成。
四、成本分析
以每月处理 10 万张图片 为例:
| 项目 | 单价 | 月用量 | 费用 |
|---|---|---|---|
| FunctionGraph 调用 | ¥0.2/百万次 | 10 万次 | ¥0.02 |
| FunctionGraph 资源 | ¥0.0000167/GB·s | 约 500 GB·s | ¥0.008 |
| OBS 存储(100GB) | ¥0.33/GB/月 | 100 GB | ¥33 |
| OBS 请求 | ¥0.01/万次 | 20 万次 | ¥0.2 |
| API Gateway | ¥0.08/万次 | 10 万次 | ¥0.8 |
| 合计 | ¥34.03/月 |
对比同等能力的 ECS 方案(¥200+/月),成本降低 83%。
五、进阶优化建议
1. 使用 CodeArts 实现 CI/CD
将函数代码托管到 CodeArts Repo,配置流水线自动部署:
# .codearts-pipelines/pipeline.yaml
stages:
- name: 单元测试
steps:
- script: pytest tests/
- name: 部署到 FunctionGraph
steps:
- script: |
hcloud fgs update-function-code \
--function-name image-resize-handler \
--zip-file ./dist/function.zip
2. 接入 APM 监控
在函数中集成 应用性能管理 APM:
from huaweicloudsdkapm.v1 import APMClient
# 监控函数执行时间、错误率、调用链
3. 使用 GaussDB 记录处理日志
将每次图片处理记录存入 GaussDB(for MySQL),方便后续分析:
CREATE TABLE image_processing_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
image_key VARCHAR(255),
thumbnail_key VARCHAR(255),
process_time_ms INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
六、总结
| 维度 | 传统方案 | Serverless 方案 |
|---|---|---|
| 部署时间 | 1-2 天 | 1 小时 |
| 运维成本 | 专人运维 | 零运维 |
| 弹性能力 | 手动扩容 | 自动弹性 |
| 月成本 | ¥2000+ | ¥34 |
| 可用性 | 依赖人工 | 99.95% SLA |
华为云 FunctionGraph + OBS + API Gateway 的组合,让开发者从基础设施中彻底解放出来,真正做到 "写代码,不运维"。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)