3. 第一个Serverless函数

Serverless 函数的第一课,不是背概念,而是亲手把一个函数从本地代码推到云端,再让它被真实请求触发。本节以最经典的 Hello World 为例,走完“编写—构建—推送—部署—调用—观察—更新—删除”的完整链路,并借此理解 OCI Functions 中函数的生命周期。OCI Functions 的底层运行时与开源项目 Fn Project 同源,因此本地用 Fn 验证、云端用 OCI Functions 部署,是学习成本最低的路径。

一、先明确对象:函数、应用与镜像

在 OCI Functions 中,最小的部署单元不是单个函数,而是应用(Application)。一个应用是一组函数的逻辑容器,它绑定一个子网(用于函数出站访问)和一组配置。函数本身则是一个符合 Fn 规范的容器镜像,运行时由平台按需拉起。理解这层关系,才能明白为什么部署命令里既有应用名,又有函数名。

Hello World 函数的核心逻辑非常简单:读取输入,返回一段 JSON。以 Python 为例,函数入口约定为 handler(ctx, data),其中 ctx 是调用上下文,data 是请求体。下面是一个可直接使用的示例:

import io
import json

def handler(ctx, data: io.BytesIO = None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name", name)
    except Exception:
        pass
    return json.dumps({"message": f"Hello, {name}!"})

这段代码体现了 Serverless 函数的一个关键特征:无状态。函数不保存任何跨请求的数据,每次调用都是独立的一次执行。如果需要共享配置,应通过应用配置或环境变量注入,而不是写在函数内部。

二、本地用 Fn Project 验证

在推送到云端之前,建议先用 Fn Project 在本地跑通。Fn 提供了 fn init、fn deploy、fn invoke 等命令,与 OCI Functions 的 CLI 操作高度一致。初始化一个 Python 函数:

fn init --runtime python hello-fn
cd hello-fn
fn deploy --app hello-app --local
fn invoke hello-app hello-fn

如果本地 Fn 服务已启动,fn invoke 会直接返回 {"message": "Hello, World!"}。这一步的价值在于:它把“函数能否被正确加载、入口是否匹配、依赖是否完整”这些问题在本地暴露出来,避免把错误带到云端。带参数调用可以这样验证:

echo -n '{"name":"OCI"}' | fn invoke hello-app hello-fn

预期输出为 {"message": "Hello, OCI!"}。如果返回的是错误堆栈,通常说明入口函数名、运行时版本或依赖声明有问题,应先修复再继续。

三、部署到 OCI Functions

云端部署需要先准备好 OCI 的认证上下文。使用 OCI CLI 时,函数相关命令位于 oci fn 下。典型流程是:先创建应用,再创建函数,最后调用。创建应用时需要指定子网 OCID 和显示名:

oci fn application create \
  --compartment-id <compartment_ocid> \
  --display-name hello-app \
  --subnet-ids '["<subnet_ocid>"]'

应用创建后,用 Fn CLI 配合 OCI 上下文部署函数。OCI Functions 支持通过 fn deploy 指定 --app 和上下文,将本地构建的镜像推送到 OCI 容器镜像仓库(OCIR),再注册为函数。部署完成后,可以用 oci fn function list 查看函数状态,确认其处于 ACTIVE。

部署前请确认三件事:一是 OCIR 中已创建对应仓库;二是本地 Docker 可正常构建镜像;三是 OCI CLI 的配置文件中已设置正确的 region 和认证方式。这三项任一缺失,部署都会在推送或注册阶段失败。

四、调用与观察

调用函数有两种常用方式:CLI 调用和 HTTP 触发。CLI 调用适合快速验证:

oci fn function invoke \
  --function-id <function_ocid> \
  --body '{"name":"Serverless"}' \
  --file -

返回结果会直接输出到终端。若要通过 HTTP 调用,需要为函数创建 API 网关或使用函数自身的调用端点,并携带认证信息。无论哪种方式,观察重点都包括:返回内容是否正确、响应时间是否在预期范围、日志中是否出现异常。OCI Functions 的日志会进入 OCI Logging,可在控制台按函数名和时间范围检索。

五、理解函数生命周期

函数从创建到删除,会经历若干状态。下图展示了 OCI Functions 中一个函数的典型生命周期,以及每个阶段对应的关键操作和观察点。

OCI Functions 函数生命周期 编写 handler + 依赖 构建 Docker 镜像 推送 OCIR 仓库 部署 注册为函数 调用 CLI / HTTP 更新 新镜像版本 删除 释放资源 代码变更 重新部署 关键状态与观察点 创建后:函数状态为 ACTIVE,表示已就绪可调用 调用时:平台按需拉起容器,冷启动与热启动耗时不同 更新时:新镜像替换旧版本,函数 ID 不变 删除时:函数移除,关联日志保留策略需单独确认

从图中可以看到,函数生命周期并不是一条单向直线。更新操作会回到部署阶段,用新镜像替换旧版本,而函数 ID 保持不变。这意味着调用方不需要修改端点,就能获得新逻辑。删除则是终点,函数被移除后,相关调用会失败,因此删除前应确认没有其他系统依赖该函数。

六、几个容易踩的坑

第一,冷启动。函数在长时间无调用后,容器会被回收,下一次调用需要重新拉起,耗时可能从几十毫秒上升到数秒。Hello World 本身很轻,冷启动影响不明显,但生产函数应关注这一点。第二,超时设置。函数默认有执行超时,若逻辑耗时超过阈值会被强制终止,返回超时错误。第三,日志排查。调用失败时,先看函数日志中的异常堆栈,再检查应用配置和子网连通性。第四,镜像标签。更新函数时如果沿用同一个镜像标签,可能因为缓存导致旧版本仍被使用,建议每次更新使用新标签。

删除函数或应用前,请确认没有 API 网关、事件规则或其他服务正在引用它。一旦删除,调用方会立即收到错误,且函数配置无法自动恢复。

七、小结

Hello World 虽然简单,但它完整覆盖了 Serverless 函数的核心操作:编写入口、构建镜像、推送仓库、部署注册、调用验证、更新替换和删除释放。走通这一遍之后,再去看更复杂的函数,无非是在入口逻辑、依赖、配置和触发方式上做扩展。理解生命周期中每个状态的含义,才能在函数不按预期工作时,快速判断问题出在构建、部署还是调用阶段。