<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docker on Refrain</title><link>https://refrain-blog.pages.dev/tags/docker/</link><description>Recent content in Docker on Refrain</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 10 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://refrain-blog.pages.dev/tags/docker/index.xml" rel="self" type="application/rss+xml"/><item><title>GitHub Actions 部署 FastAPI 到阿里云函数计算：代码适配与完整发布流程</title><link>https://refrain-blog.pages.dev/posts/github-actions-fastapi-aliyun-function-compute/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0800</pubDate><guid>https://refrain-blog.pages.dev/posts/github-actions-fastapi-aliyun-function-compute/</guid><description>&lt;p&gt;写好的 FastAPI 项目，为什么放进函数计算以后不能直接运行？通常要先检查选了哪一种运行时，以及云端怎样启动这个程序。&lt;/p&gt;
&lt;p&gt;本文记录一条完整的发布路线：&lt;strong&gt;在 GitHub 保存代码，由 GitHub Actions 构建 Docker 镜像，推送到阿里云容器镜像服务，再用 Serverless Devs 更新函数计算。&lt;/strong&gt; 以后改动代码并推送到 &lt;code&gt;main&lt;/code&gt;，同一条流程就会再次执行。&lt;/p&gt;
&lt;p&gt;文中配置按 &lt;strong&gt;2026 年 10 月 10 日&lt;/strong&gt;查阅的官方资料整理，使用 FC 3.0 和自定义容器运行时。它是一份可复用的参考配置；本文整理时未在阿里云账号里执行这套新流程。&lt;/p&gt;
&lt;h2 id="一先弄明白fastapi-要不要改成函数"&gt;一、先弄明白：FastAPI 要不要改成“函数”？&lt;/h2&gt;
&lt;p&gt;FastAPI 平时的工作方式，是由 Uvicorn 启动一个 HTTP 服务，再根据请求路径调用路由函数。函数计算提供几种不同的启动方式：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方式&lt;/th&gt;
&lt;th&gt;云端接收程序的方式&lt;/th&gt;
&lt;th&gt;FastAPI 需要做什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内置 Python 事件运行时&lt;/td&gt;
&lt;td&gt;加载指定的处理函数，例如 &lt;code&gt;main.handler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;遵循 &lt;code&gt;handler(event, context)&lt;/code&gt; 等运行时契约，或使用经过核实的 ASGI 适配器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自定义运行时&lt;/td&gt;
&lt;td&gt;上传代码和依赖，由启动命令运行 HTTP 服务&lt;/td&gt;
&lt;td&gt;保留普通 FastAPI 路由，配置启动命令、依赖和监听端口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自定义容器&lt;/td&gt;
&lt;td&gt;拉取镜像，按容器启动命令运行 HTTP 服务&lt;/td&gt;
&lt;td&gt;把 Python、依赖和应用放进镜像，配置镜像地址和监听端口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此，&lt;code&gt;app = FastAPI()&lt;/code&gt; 和 &lt;code&gt;@app.get(&amp;quot;/tasks&amp;quot;)&lt;/code&gt; 可以继续使用。本文选择自定义容器，让 Uvicorn 提供 HTTP 服务；&lt;code&gt;main:app&lt;/code&gt; 这种 ASGI 导入路径，则不能直接当成内置 Python 事件运行时的 Handler。&lt;a href="https://www.alibabacloud.com/help/en/functioncompute/principles-1"&gt;自定义运行时机制&lt;/a&gt;、&lt;a href="https://help.aliyun.com/zh/functioncompute/event-handlers-1-1"&gt;Python 事件处理函数&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Web 服务模式要特别注意三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;监听地址使用 &lt;code&gt;0.0.0.0&lt;/code&gt;，使平台能够连接应用。&lt;/li&gt;
&lt;li&gt;应用实际监听的端口，与函数配置的端口一致。&lt;/li&gt;
&lt;li&gt;启动命令能够找到应用模块及其依赖，并在平台要求的时间内启动完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本文统一使用 &lt;code&gt;9000&lt;/code&gt;。如果改成 &lt;code&gt;8000&lt;/code&gt;，Uvicorn 和函数配置也要一起修改。&lt;/p&gt;
&lt;h2 id="二整个发布过程各部分负责什么"&gt;二、整个发布过程，各部分负责什么？&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;修改代码并推送到 GitHub 的 main
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GitHub Actions 取出代码、导出依赖
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Docker 构建 linux/amd64 镜像
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;推送到阿里云 ACR 镜像仓库
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Serverless Devs 根据 s.yaml 创建或更新函数
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;函数计算拉取新镜像、启动 Uvicorn
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;通过 HTTP 触发器访问 /health 等应用路由
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;部件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app/main.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实际的 FastAPI 接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;、&lt;code&gt;uv.lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记录并锁定 Python 依赖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;定义镜像里的运行环境和启动命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACR&lt;/td&gt;
&lt;td&gt;保存构建好的镜像，供函数计算拉取&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;描述函数名称、地域、资源、镜像、端口和触发器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;自动串起构建、推送镜像和更新函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM 身份和角色&lt;/td&gt;
&lt;td&gt;分别授权部署、推送镜像、拉取镜像等操作&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;GitHub 仓库里有代码，并不会自动让函数计算更新。需要工作流实际执行构建、镜像推送和部署命令，才能形成这条自动发布链路。&lt;/p&gt;
&lt;h2 id="三第一次部署前准备阿里云资源"&gt;三、第一次部署前，准备阿里云资源&lt;/h2&gt;
&lt;h3 id="1-选定地域与镜像仓库"&gt;1. 选定地域与镜像仓库&lt;/h3&gt;
&lt;p&gt;先选一个函数计算和 ACR 都可用的地域，例如 &lt;code&gt;cn-hongkong&lt;/code&gt;。在该地域准备私有镜像仓库，示例命名如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命名空间：&lt;code&gt;fc-demo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;仓库名称：&lt;code&gt;fastapi-api&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;函数名称：&lt;code&gt;fastapi-github-demo&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从 ACR 控制台复制实际的镜像仓库地址，以及 Docker 登录所需的用户名和密码。地址通常由“登录域名 / 命名空间 / 仓库名”组成；不要仅凭地域猜出登录域名。&lt;/p&gt;
&lt;p&gt;本文的主配置适用于&lt;strong&gt;已有 ACR 个人版仓库&lt;/strong&gt;的情况。私人镜像使用与函数相同账号、相同地域的 ACR；构建目标使用 &lt;code&gt;linux/amd64&lt;/code&gt;。新账号能否开通个人版，需要以控制台当前可用情况为准；使用企业版时，还要填写下文说明的实例 ID。ACR 经济版目前不在 FC 自定义容器支持范围内。&lt;a href="https://help.aliyun.com/zh/functioncompute/create-a-custom-container-function-in-a-container-runtime"&gt;自定义容器要求与仓库准备&lt;/a&gt;、&lt;a href="https://help.aliyun.com/zh/functioncompute/custom-container/"&gt;镜像运行环境要求&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;第一次试用时，选一个新的函数名。部署工具会创建不存在的函数；名称已经存在时，会更新对应函数的配置。&lt;/p&gt;
&lt;h3 id="2-配置函数使用的-ram-角色"&gt;2. 配置函数使用的 RAM 角色&lt;/h3&gt;
&lt;p&gt;函数计算需要获得拉取私有镜像的权限。可以在 RAM 控制台创建以函数计算为可信云服务的角色，再为它授权读取 ACR 镜像。&lt;/p&gt;
&lt;p&gt;官方给出的镜像拉取权限示例包括 &lt;code&gt;AliyunContainerRegistryReadOnlyAccess&lt;/code&gt;。如果账号有更细的权限管理要求，可以按目标仓库缩小授权范围。保存该角色的 ARN，形如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;acs:ram::&amp;lt;你的账号ID&amp;gt;:role/&amp;lt;函数角色名称&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;稍后将它填入 &lt;code&gt;FC_FUNCTION_ROLE_ARN&lt;/code&gt;，最终成为 &lt;code&gt;s.yaml&lt;/code&gt; 的 &lt;code&gt;role&lt;/code&gt; 字段。&lt;a href="https://www.alibabacloud.com/help/en/fc/support/what-permissions-must-i-grant-to-function-compute-service-role-if-alibaba-cloud-public-container-images-are-not-used"&gt;函数角色的镜像读取权限&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="3-准备-github-部署身份"&gt;3. 准备 GitHub 部署身份&lt;/h3&gt;
&lt;p&gt;为发布流程准备专用 RAM 身份和 AccessKey，按官方权限示例授权目标函数的创建、更新、读取及 HTTP 触发器管理，并允许它把前面准备的角色交给函数计算，即对目标角色授予 &lt;code&gt;ram:PassRole&lt;/code&gt;。CLI 执行中需要的额外读取操作，也应按实际资源补齐。&lt;a href="https://help.aliyun.com/zh/functioncompute/policies-and-sample-policies"&gt;函数计算权限示例&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这里有两组不同的凭据：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;凭据&lt;/th&gt;
&lt;th&gt;谁使用&lt;/th&gt;
&lt;th&gt;做什么&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;阿里云 AccessKey ID / Secret&lt;/td&gt;
&lt;td&gt;GitHub 中的 Serverless Devs&lt;/td&gt;
&lt;td&gt;调用函数计算 API，更新函数及触发器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACR 登录用户名 / 密码&lt;/td&gt;
&lt;td&gt;GitHub 中的 Docker 登录步骤&lt;/td&gt;
&lt;td&gt;推送镜像到指定仓库&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;云端函数拉取镜像时使用前面配置的 &lt;strong&gt;RAM 角色&lt;/strong&gt;。把 ACR 密码设置到 GitHub，并不等于函数计算也获得了读取镜像的权限。&lt;/p&gt;
&lt;h2 id="四准备应用文件"&gt;四、准备应用文件&lt;/h2&gt;
&lt;p&gt;一个最小项目可以这样组织：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fastapi-demo/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── app/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ ├── __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └── main.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── pyproject.toml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── uv.lock
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── .dockerignore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── s.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── .github/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── workflows/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── deploy-fc.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="1-普通的-fastapi-应用"&gt;1. 普通的 FastAPI 应用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;app/__init__.py&lt;/code&gt; 可以留空。&lt;code&gt;app/main.py&lt;/code&gt; 放一个首页和一个用于检查应用是否正常响应的路由：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; fastapi &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; FastAPI
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; FastAPI(title&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;&amp;#34;FastAPI on Function Compute&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@app.get(&lt;span style="color:#b8bb26"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;async&lt;/span&gt; &lt;span style="color:#fe8019"&gt;def&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;home&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; {&lt;span style="color:#b8bb26"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;FastAPI is running&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@app.get(&lt;span style="color:#b8bb26"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;async&lt;/span&gt; &lt;span style="color:#fe8019"&gt;def&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;health&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; {&lt;span style="color:#b8bb26"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;: &lt;span style="color:#fe8019"&gt;True&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;原有的 Pydantic 模型、路由拆分和 &lt;code&gt;include_router&lt;/code&gt; 可以继续保留。后面的启动命令导入 &lt;code&gt;app.main:app&lt;/code&gt;，意思是找到 &lt;code&gt;app/main.py&lt;/code&gt; 里的 &lt;code&gt;app&lt;/code&gt; 对象。&lt;/p&gt;
&lt;h3 id="2-锁定依赖"&gt;2. 锁定依赖&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 示例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[project]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;name = &lt;span style="color:#b8bb26"&gt;&amp;#34;fastapi-fc-demo&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version = &lt;span style="color:#b8bb26"&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;requires-python = &lt;span style="color:#b8bb26"&gt;&amp;#34;&amp;gt;=3.12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies = [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;fastapi&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;uvicorn&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一次准备项目时，安装好 uv 后，在项目根目录执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv lock
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;把生成的 &lt;code&gt;uv.lock&lt;/code&gt; 与 &lt;code&gt;pyproject.toml&lt;/code&gt; 一起提交。CI 用锁文件导出 &lt;code&gt;requirements.txt&lt;/code&gt;，Docker 再用它安装依赖：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv &lt;span style="color:#fabd2f"&gt;export&lt;/span&gt; --locked --no-dev --no-hashes --format requirements.txt --output-file requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--locked&lt;/code&gt; 会检查依赖描述与锁文件是否一致。修改依赖后，应先更新并提交锁文件；部署时临时重新解析依赖，可能让相同代码的安装结果变化。&lt;a href="https://docs.astral.sh/uv/concepts/projects/export/"&gt;uv 导出依赖说明&lt;/a&gt;、&lt;a href="https://docs.astral.sh/uv/reference/cli/"&gt;uv 参数参考&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="3-dockerfile把启动方式写清楚"&gt;3. Dockerfile：把启动方式写清楚&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;FROM&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;python:3.12-slim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;/code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;ENV&lt;/span&gt; PYTHONUNBUFFERED&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;ENV&lt;/span&gt; PYTHONDONTWRITEBYTECODE&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;COPY&lt;/span&gt; requirements.txt ./
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;RUN&lt;/span&gt; python -m pip install --no-cache-dir -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;COPY&lt;/span&gt; app ./app
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;9000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;CMD&lt;/span&gt; [&lt;span style="color:#b8bb26"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;-m&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;uvicorn&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;app.main:app&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;--host&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;--port&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;9000&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;--timeout-keep-alive&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;86400&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里通过 &lt;code&gt;python -m uvicorn&lt;/code&gt; 启动服务，应用导入路径是 &lt;code&gt;app.main:app&lt;/code&gt;。有额外的模板、静态文件或其他代码目录时，应相应增加 &lt;code&gt;COPY&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;EXPOSE 9000&lt;/code&gt; 描述容器使用的端口。真正的监听端口仍由 Uvicorn 的 &lt;code&gt;--port 9000&lt;/code&gt; 决定，函数计算也需要配置相同的值。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--timeout-keep-alive 86400&lt;/code&gt; 用于保持平台与应用之间的连接，按阿里云对 Web Server 连接超时的建议设置。它与函数一次请求的 &lt;code&gt;timeout: 60&lt;/code&gt; 是两个不同的参数。&lt;a href="https://help.aliyun.com/en/functioncompute/process-exited-unexpectedly-before-completing-request-message"&gt;Uvicorn 连接超时排错&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="4-排除本地配置和临时文件"&gt;4. 排除本地配置和临时文件&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.dockerignore&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.git
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.github
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.venv
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.env
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.env.*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;__pycache__
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;*.pyc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.DS_Store
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.venv/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.env
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.env.*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.s/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;__pycache__/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;*.pyc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.DS_Store
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;requirements.txt&lt;/code&gt; 在构建前生成，所以可以不提交；&lt;code&gt;uv.lock&lt;/code&gt; 要提交。示例只把 &lt;code&gt;app&lt;/code&gt; 复制进镜像，同时用 &lt;code&gt;.dockerignore&lt;/code&gt; 排除本地环境和配置。&lt;/p&gt;
&lt;h2 id="五写-fc-30-的-syaml"&gt;五、写 FC 3.0 的 s.yaml&lt;/h2&gt;
&lt;p&gt;在项目根目录创建以下配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;edition&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;3.0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: fastapi-fc3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;access&lt;/span&gt;: fc-ci
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;resources&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;api&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;component&lt;/span&gt;: fc3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;props&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;region&lt;/span&gt;: ${env(&amp;#39;FC_REGION&amp;#39;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;functionName&lt;/span&gt;: ${env(&amp;#39;FC_FUNCTION_NAME&amp;#39;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;runtime&lt;/span&gt;: custom-container
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;cpu&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;0.5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;memorySize&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;diskSize&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;512&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;timeout&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;instanceConcurrency&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;role&lt;/span&gt;: ${env(&amp;#39;FC_FUNCTION_ROLE_ARN&amp;#39;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;customContainerConfig&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;image&lt;/span&gt;: ${env(&amp;#39;FC_IMAGE_URI&amp;#39;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;port&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;9000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;triggers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;triggerName&lt;/span&gt;: http
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;triggerType&lt;/span&gt;: http
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;qualifier&lt;/span&gt;: LATEST
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;triggerConfig&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;authType&lt;/span&gt;: function
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;disableURLInternet&lt;/span&gt;: &lt;span style="color:#fe8019"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;methods&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - GET
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - POST
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - PUT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - PATCH
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - DELETE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - OPTIONS
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;${env('FC_REGION')}&lt;/code&gt; 表示部署工具从执行环境中读取变量。它不会自动读取 GitHub 设置，后面的工作流要明确把 GitHub Variables 映射成环境变量。&lt;a href="https://docs.serverless-devs.com/user-guide/spec/"&gt;Serverless Devs 变量语法&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;几个容易混淆的字段：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;access: fc-ci&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serverless Devs 的凭据别名，须与 CI 注册的名称一致&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;runtime: custom-container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;通过镜像运行应用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customContainerConfig.image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;这次构建推送的完整镜像地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customContainerConfig.port&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;平台连接 Uvicorn 的端口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;role&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;函数计算使用的 RAM 角色 ARN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;authType: function&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 请求需要阿里云签名认证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qualifier: LATEST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;触发器指向函数当前的 LATEST 配置&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CPU、内存、磁盘、超时和并发值是教学起点，可以按实际应用调整。FC 3.0 自定义容器使用嵌套的 &lt;code&gt;port&lt;/code&gt; 字段；不要把旧版的 &lt;code&gt;caPort&lt;/code&gt; 混进这份配置。&lt;a href="https://docs.serverless-devs.com/user-guide/aliyun/fc3/spec/"&gt;FC3 YAML 规范&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="企业版-acr-的补充"&gt;企业版 ACR 的补充&lt;/h3&gt;
&lt;p&gt;使用企业版时，将镜像地址换成实际企业版仓库地址，再在容器配置中补充实例 ID：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;customContainerConfig&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;image&lt;/span&gt;: ${env(&amp;#39;FC_IMAGE_URI&amp;#39;)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;port&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;9000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;acrInstanceId&lt;/span&gt;: ${env(&amp;#39;ACR_INSTANCE_ID&amp;#39;)}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时在 GitHub Variables 中增加 &lt;code&gt;ACR_INSTANCE_ID&lt;/code&gt;，并在工作流的 &lt;code&gt;env&lt;/code&gt; 中增加 &lt;code&gt;ACR_INSTANCE_ID: ${{ vars.ACR_INSTANCE_ID }}&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;FC3 API 定义了这个字段。当前 fc3 部署源码会把它传给 SDK，但公开 YAML 表格未完整列出它；实际使用时，应核对安装的组件版本以及 ACR 网络、权限配置。&lt;a href="https://help.aliyun.com/en/functioncompute/api-fc-2023-03-30-struct-customcontainerconfig"&gt;FC3 API 镜像配置&lt;/a&gt;、&lt;a href="https://github.com/devsapp/fc3/blob/master/src/subCommands/deploy/impl/function.ts"&gt;当前部署实现&lt;/a&gt;、&lt;a href="https://github.com/aliyun/alibabacloud-typescript-sdk/blob/master/fc-20230330/src/models/CustomContainerConfig.ts"&gt;SDK 容器模型&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="六在-github-保存配置与凭据"&gt;六、在 GitHub 保存配置与凭据&lt;/h2&gt;
&lt;p&gt;进入目标仓库的 &lt;strong&gt;Settings → Secrets and variables → Actions&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 &lt;strong&gt;Variables&lt;/strong&gt; 中创建这些普通配置：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;示例或填写方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FC_REGION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cn-hongkong&lt;/code&gt;，与 ACR 地域一致&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FC_FUNCTION_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fastapi-github-demo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FC_FUNCTION_ROLE_ARN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从 RAM 控制台复制函数角色 ARN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACR_REGISTRY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ACR 登录域名，不含 &lt;code&gt;https://&lt;/code&gt; 和仓库路径&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACR_NAMESPACE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fc-demo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACR_REPOSITORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fastapi-api&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在 &lt;strong&gt;Secrets&lt;/strong&gt; 中创建：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ALIYUN_ACCESS_KEY_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;专用 RAM 部署身份的 AccessKey ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ALIYUN_ACCESS_KEY_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对应的 AccessKey Secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACR_USERNAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ACR 控制台提供的 Docker 登录用户名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ACR_PASSWORD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ACR 登录密码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这些值由工作流读取，不需要写进源码。GitHub 自带的 &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; 用于 GitHub 权限，不能替代阿里云的凭据。缺少的 Secret 会被表达式读成空字符串，所以工作流应先检查配置是否齐全。&lt;a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets"&gt;GitHub Actions Secrets&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也可以在已登录 GitHub CLI 的项目目录里执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gh secret &lt;span style="color:#fabd2f"&gt;set&lt;/span&gt; ALIYUN_ACCESS_KEY_ID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gh secret &lt;span style="color:#fabd2f"&gt;set&lt;/span&gt; ALIYUN_ACCESS_KEY_SECRET
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gh secret &lt;span style="color:#fabd2f"&gt;set&lt;/span&gt; ACR_USERNAME
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gh secret &lt;span style="color:#fabd2f"&gt;set&lt;/span&gt; ACR_PASSWORD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;按交互提示输入对应值。普通 Variables 可通过同一设置页面填写。&lt;/p&gt;
&lt;h2 id="七添加完整的-github-actions-工作流"&gt;七、添加完整的 GitHub Actions 工作流&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;.github/workflows/deploy-fc.yml&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Deploy FastAPI to Function Compute
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;branches&lt;/span&gt;: [main]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;workflow_dispatch&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;permissions&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;contents&lt;/span&gt;: read
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;concurrency&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;group&lt;/span&gt;: fc-production
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;cancel-in-progress&lt;/span&gt;: &lt;span style="color:#fe8019"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;deploy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;runs-on&lt;/span&gt;: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;FC_REGION&lt;/span&gt;: ${{ vars.FC_REGION }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;FC_FUNCTION_NAME&lt;/span&gt;: ${{ vars.FC_FUNCTION_NAME }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;FC_FUNCTION_ROLE_ARN&lt;/span&gt;: ${{ vars.FC_FUNCTION_ROLE_ARN }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ACR_REGISTRY&lt;/span&gt;: ${{ vars.ACR_REGISTRY }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ACR_NAMESPACE&lt;/span&gt;: ${{ vars.ACR_NAMESPACE }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ACR_REPOSITORY&lt;/span&gt;: ${{ vars.ACR_REPOSITORY }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Checkout
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;uses&lt;/span&gt;: actions/checkout@v7.0.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Set up Node.js for Serverless Devs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;uses&lt;/span&gt;: actions/setup-node@v7.1.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;node-version&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;22&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Set up uv and Python
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;uses&lt;/span&gt;: astral-sh/setup-uv@v10.3.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;python-version&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;3.12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;enable-cache&lt;/span&gt;: &lt;span style="color:#fe8019"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Check required configuration
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;shell&lt;/span&gt;: bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ALIYUN_ACCESS_KEY_ID&lt;/span&gt;: ${{ secrets.ALIYUN_ACCESS_KEY_ID }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ALIYUN_ACCESS_KEY_SECRET&lt;/span&gt;: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ACR_USERNAME&lt;/span&gt;: ${{ secrets.ACR_USERNAME }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ACR_PASSWORD&lt;/span&gt;: ${{ secrets.ACR_PASSWORD }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;run&lt;/span&gt;: |&lt;span style="color:#b8bb26"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; set -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; required=(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; FC_REGION FC_FUNCTION_NAME FC_FUNCTION_ROLE_ARN
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; ACR_REGISTRY ACR_NAMESPACE ACR_REPOSITORY
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; ALIYUN_ACCESS_KEY_ID ALIYUN_ACCESS_KEY_SECRET
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; ACR_USERNAME ACR_PASSWORD
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; for name in &amp;#34;${required[@]}&amp;#34;; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; if [[ -z &amp;#34;${!name}&amp;#34; ]]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; echo &amp;#34;::error::Missing configuration: $name&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; exit 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Export locked dependencies
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;run&lt;/span&gt;: |&lt;span style="color:#b8bb26"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; uv export --locked --no-dev --no-hashes \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; --format requirements.txt \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; --output-file requirements.txt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Select image tag
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;shell&lt;/span&gt;: bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;run&lt;/span&gt;: |&lt;span style="color:#b8bb26"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; image=&amp;#34;$ACR_REGISTRY/$ACR_NAMESPACE/$ACR_REPOSITORY:$GITHUB_SHA&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; echo &amp;#34;FC_IMAGE_URI=$image&amp;#34; &amp;gt;&amp;gt; &amp;#34;$GITHUB_ENV&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Set up Docker Buildx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;uses&lt;/span&gt;: docker/setup-buildx-action@v4.4.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Log in to ACR
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;uses&lt;/span&gt;: docker/login-action@v4.6.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;registry&lt;/span&gt;: ${{ env.ACR_REGISTRY }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;username&lt;/span&gt;: ${{ secrets.ACR_USERNAME }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;password&lt;/span&gt;: ${{ secrets.ACR_PASSWORD }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Build and push image
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;uses&lt;/span&gt;: docker/build-push-action@v7.4.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;context&lt;/span&gt;: .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;file&lt;/span&gt;: ./Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;platforms&lt;/span&gt;: linux/amd64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;push&lt;/span&gt;: &lt;span style="color:#fe8019"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;tags&lt;/span&gt;: ${{ env.FC_IMAGE_URI }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Install Serverless Devs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;run&lt;/span&gt;: npm install --global @serverless-devs/s@3.1.10
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#fb4934"&gt;name&lt;/span&gt;: Deploy function
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ALIYUN_ACCESS_KEY_ID&lt;/span&gt;: ${{ secrets.ALIYUN_ACCESS_KEY_ID }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;ALIYUN_ACCESS_KEY_SECRET&lt;/span&gt;: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;run&lt;/span&gt;: |&lt;span style="color:#b8bb26"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; s config add \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; --AccessKeyID &amp;#34;$ALIYUN_ACCESS_KEY_ID&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; --AccessKeySecret &amp;#34;$ALIYUN_ACCESS_KEY_SECRET&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; -a fc-ci -f
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26"&gt; s deploy --skip-push --assume-yes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;示例使用整理时已核对的 Action 版本和 Serverless Devs CLI 版本。以后更新工具时，应先阅读相应项目的发布说明；CLI 版本与它下载使用的 fc3 组件版本也要分开记录。&lt;a href="https://docs.github.com/en/actions/tutorials/publish-packages/publish-docker-images?learn=continuous_deployment"&gt;GitHub Docker 发布教程&lt;/a&gt;、&lt;a href="https://docs.docker.com/build/ci/github-actions/"&gt;Docker GitHub Actions&lt;/a&gt;、&lt;a href="https://docs.serverless-devs.com/user-guide/install/"&gt;Serverless Devs 安装&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这份工作流有几个细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; 允许在 Actions 页面手动发布一次；文件需要先进入默认分支。&lt;a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow"&gt;工作流触发说明&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;context: .&lt;/code&gt; 让 Docker 构建读取上一步导出的 &lt;code&gt;requirements.txt&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;镜像使用完整提交 SHA 作为标签，方便判断哪一次代码对应哪一个镜像。保留旧标签，后续才能回退。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;linux/amd64&lt;/code&gt; 明确目标架构；本地电脑是 Apple Silicon，也不能直接把 ARM 镜像当作本例的部署产物。&lt;/li&gt;
&lt;li&gt;Docker 已经推送镜像，&lt;code&gt;s deploy --skip-push&lt;/code&gt; 让 fc3 使用现成镜像更新函数。&lt;/li&gt;
&lt;li&gt;自动确认参数使用 &lt;code&gt;--assume-yes&lt;/code&gt; 或 &lt;code&gt;-y&lt;/code&gt;。当前 fc3 参数解析没有把 &lt;code&gt;--yes&lt;/code&gt; 当作同一个选项。&lt;a href="https://docs.serverless-devs.com/user-guide/aliyun/fc3/deploy/"&gt;FC3 部署参数&lt;/a&gt;、&lt;a href="https://github.com/devsapp/fc3/blob/master/src/subCommands/deploy/index.ts"&gt;官方参数解析&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="八第一次发布与之后的更新"&gt;八、第一次发布与之后的更新&lt;/h2&gt;
&lt;p&gt;将前面的文件提交到自己的仓库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git add app pyproject.toml uv.lock Dockerfile .dockerignore .gitignore s.yaml .github/workflows/deploy-fc.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git commit -m &lt;span style="color:#b8bb26"&gt;&amp;#34;Add FastAPI deployment to Function Compute&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git push origin main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后打开 GitHub 的 &lt;strong&gt;Actions&lt;/strong&gt; 页面，进入本次运行，依次查看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置检查是否通过。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uv export&lt;/code&gt; 是否成功。&lt;/li&gt;
&lt;li&gt;Docker 镜像是否构建并推送成功。&lt;/li&gt;
&lt;li&gt;Serverless Devs 是否成功创建或更新目标函数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ACR 中应出现以此次提交 SHA 命名的镜像标签，函数配置中的镜像地址也应指向它。登录阿里云函数计算控制台，在所选地域找到函数，打开 HTTP 触发器，复制它的实际访问 URL。&lt;/p&gt;
&lt;p&gt;之后修改应用，执行提交和推送即可再次发布。若更新依赖，应先运行 &lt;code&gt;uv lock&lt;/code&gt; 并提交新的锁文件。不要只在云端控制台手动修改启动参数，却让仓库里的配置一直保持旧值；下次部署可能再次覆盖这些设置。&lt;/p&gt;
&lt;h2 id="九部署成功以后怎样确认接口真的能用"&gt;九、部署成功以后，怎样确认接口真的能用？&lt;/h2&gt;
&lt;p&gt;工作流的部署步骤成功，说明发布命令完成了。接下来还要检查应用是否能启动，以及目标路由是否返回正确结果。&lt;/p&gt;
&lt;h3 id="1-理解签名认证"&gt;1. 理解签名认证&lt;/h3&gt;
&lt;p&gt;本文使用 &lt;code&gt;authType: function&lt;/code&gt;。触发器有互联网访问地址，但调用者仍需进行签名认证；直接在浏览器打开，或者执行没有签名的 &lt;code&gt;curl&lt;/code&gt;，可能返回认证错误。&lt;/p&gt;
&lt;p&gt;如果只是公开的学习 Demo，可以明确把 &lt;code&gt;authType&lt;/code&gt; 改为 &lt;code&gt;anonymous&lt;/code&gt; 后部署，再用浏览器和 &lt;code&gt;curl&lt;/code&gt; 检查。此时允许的方法和应用路由会对访问者开放，新增、修改、删除接口也包含在内。公网触发器鉴权和应用自己的用户登录，需要分别设计。&lt;a href="https://www.alibabacloud.com/help/en/functioncompute/configure-an-http-trigger-for-a-function-and-invoke-the-function-by-using-http-requests"&gt;HTTP 触发器配置&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="2-使用签名请求检查-health"&gt;2. 使用签名请求检查 /health&lt;/h3&gt;
&lt;p&gt;保存下面的脚本为 &lt;code&gt;check_health.py&lt;/code&gt;。它按官方签名接口构造一个没有请求体、没有查询参数的 &lt;code&gt;GET /health&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;import&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; datetime &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; datetime, timezone
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; urllib.parse &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; urlsplit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;import&lt;/span&gt; requests
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; Tea.request &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; TeaRequest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; alibabacloud_openapi_util.client &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; Client &lt;span style="color:#fe8019"&gt;as&lt;/span&gt; Signing
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;endpoint &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; os&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;environ[&lt;span style="color:#b8bb26"&gt;&amp;#34;FC_HTTP_URL&amp;#34;&lt;/span&gt;]&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;rstrip(&lt;span style="color:#b8bb26"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;) &lt;span style="color:#fe8019"&gt;+&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;headers &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;x-acs-date&amp;#34;&lt;/span&gt;: datetime&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;now(timezone&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;utc)&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;strftime(&lt;span style="color:#b8bb26"&gt;&amp;#34;%Y-%m-&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;%d&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;T%H:%M:%SZ&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;if&lt;/span&gt; token &lt;span style="color:#fe8019"&gt;:=&lt;/span&gt; os&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;getenv(&lt;span style="color:#b8bb26"&gt;&amp;#34;ALIBABA_CLOUD_SECURITY_TOKEN&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; headers[&lt;span style="color:#b8bb26"&gt;&amp;#34;x-acs-security-token&amp;#34;&lt;/span&gt;] &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;request &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; TeaRequest()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;request&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;method &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;request&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;pathname &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; urlsplit(endpoint)&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;path
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;request&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;query &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;request&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;headers &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; headers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;headers[&lt;span style="color:#b8bb26"&gt;&amp;#34;authorization&amp;#34;&lt;/span&gt;] &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; Signing&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;get_authorization(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;ACS3-HMAC-SHA256&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; os&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;environ[&lt;span style="color:#b8bb26"&gt;&amp;#34;ALIBABA_CLOUD_ACCESS_KEY_ID&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; os&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;environ[&lt;span style="color:#b8bb26"&gt;&amp;#34;ALIBABA_CLOUD_ACCESS_KEY_SECRET&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;response &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; requests&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;get(endpoint, headers&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;headers, timeout&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fabd2f"&gt;print&lt;/span&gt;(response&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;status_code, response&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;text)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;response&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;raise_for_status()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在自己的终端环境中设置以下值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FC_HTTP_URL&lt;/code&gt;：从触发器复制的基础 URL，例如 &lt;code&gt;https://&amp;lt;实际主机&amp;gt;.cn-hongkong.fcapp.run&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALIBABA_CLOUD_ACCESS_KEY_ID&lt;/code&gt;、&lt;code&gt;ALIBABA_CLOUD_ACCESS_KEY_SECRET&lt;/code&gt;：有权限调用该函数的身份凭据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALIBABA_CLOUD_SECURITY_TOKEN&lt;/code&gt;：仅在使用 STS 临时凭据时一并设置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本地检查变量沿用官方 SDK 命名，和 GitHub Secrets 的 &lt;code&gt;ALIYUN_...&lt;/code&gt; 名称不同，要填写正确。随后运行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uv run --with alibabacloud-openapi-util --with requests python check_health.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最小应用的预期响应是 HTTP &lt;code&gt;200&lt;/code&gt; 和 &lt;code&gt;{&amp;quot;ok&amp;quot;:true}&lt;/code&gt;。签名脚本不要打印凭据或完整请求头；改变方法、路径、查询参数或请求体时，也要同步修改签名输入。&lt;a href="https://help.aliyun.com/zh/functioncompute/configure-signature-authentication-for-http-triggers"&gt;HTTP 触发器签名认证教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;另外，&lt;code&gt;s invoke&lt;/code&gt; 与请求 &lt;code&gt;GET /health&lt;/code&gt; 并不等价。Web 服务模式下，InvokeFunction 调用会映射为 &lt;code&gt;POST /invoke&lt;/code&gt;；应用没有该路由时可能返回 404，不能据此断定所有 HTTP 接口都不可用。&lt;a href="https://help.aliyun.com/zh/functioncompute/web-functions"&gt;Web 函数调用映射&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="十以前的配置为什么容易让人记混"&gt;十、以前的配置为什么容易让人记混？&lt;/h2&gt;
&lt;p&gt;早期练习中用过下面这些 FC 2.0 写法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;旧练习的字段&lt;/th&gt;
&lt;th&gt;本文 FC 3.0 的写法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;edition: 1.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;edition: 3.0.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;services&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resources&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;component: fc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;component: fc3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;service.name&lt;/code&gt; 与 &lt;code&gt;function.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;本例使用 &lt;code&gt;functionName&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;function.caPort: 9000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;customContainerConfig.port: 9000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;triggers[].name/type/config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;triggerName/triggerType/triggerConfig&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;旧练习是 FastAPI 任务 CRUD 示例，包含 Dockerfile 和 &lt;code&gt;s.yaml&lt;/code&gt;。它的部署前钩子会导出 &lt;code&gt;requirements.txt&lt;/code&gt;，但&lt;strong&gt;没有 GitHub Actions 工作流，也没有明确的 Docker 构建、推送步骤&lt;/strong&gt;。因此，单凭那份配置不能复原一条已经存在的 GitHub 自动发布链路；本文把这些缺少的步骤补齐。&lt;/p&gt;
&lt;p&gt;还有一个值得记住的区别：&lt;code&gt;s.yaml&lt;/code&gt; 写了镜像地址，只是告诉函数计算要使用哪个镜像。镜像必须先构建并出现在仓库中，本例用 Docker Actions 完成这件事。&lt;/p&gt;
&lt;p&gt;Serverless Devs 提供 FC2 到 FC3 的配置转换命令，转换后仍要检查运行时、触发器和代码语义是否匹配。&lt;a href="https://docs.serverless-devs.com/user-guide/aliyun/fc3/s2tos3/"&gt;FC2 配置转换说明&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="十一如果不用-docker代码包怎样适配"&gt;十一、如果不用 Docker，代码包怎样适配？&lt;/h2&gt;
&lt;p&gt;也可以选自定义运行时，上传代码目录和依赖。下面用 &lt;code&gt;custom.debian12&lt;/code&gt; 展示启动配置的结构，替换前面示例的 &lt;code&gt;runtime&lt;/code&gt; 和容器块：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;runtime&lt;/span&gt;: custom.debian12
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;code&lt;/span&gt;: ./code
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;environmentVariables&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;PYTHONPATH&lt;/span&gt;: /code/python
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;customRuntimeConfig&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;command&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /usr/bin/python3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;args&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - -m
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - uvicorn
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - app.main:app
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --host
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#d3869b"&gt;0.0.0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --port
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#b8bb26"&gt;&amp;#34;9000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - --timeout-keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#b8bb26"&gt;&amp;#34;86400&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;port&lt;/span&gt;: &lt;span style="color:#d3869b"&gt;9000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在这份例子里，上传目录包含 &lt;code&gt;code/app/&lt;/code&gt; 与 &lt;code&gt;code/python/&lt;/code&gt;：前者放应用，后者放安装好的第三方依赖。目录名 &lt;code&gt;python&lt;/code&gt; 是这里的安排，&lt;code&gt;PYTHONPATH&lt;/code&gt; 要与实际布局一致。&lt;/p&gt;
&lt;p&gt;依赖要匹配云端 Linux、CPU 架构及 Python 版本；尤其包含二进制扩展的包，不能直接拿 macOS 的虚拟环境上传。Debian 12 的可用地域和预装 Python 版本也要按控制台及当前文档确认；其自带 Python 版本不能直接等同于前面容器里的 Python 3.12。&lt;a href="https://www.alibabacloud.com/help/en/functioncompute/custom-runtime/"&gt;自定义运行时支持情况&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;选择这条路线时，GitHub 工作流需要改成准备 Linux 代码包及依赖，再执行部署。本节只说明代码包的运行契约，前面完整的构建和推送工作流配套的是容器路线。&lt;/p&gt;
&lt;h2 id="十二常见问题怎么定位"&gt;十二、常见问题怎么定位？&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;现象&lt;/th&gt;
&lt;th&gt;优先检查&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;构建时找不到 &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;导出步骤有没有执行，Docker 是否使用 &lt;code&gt;context: .&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv export --locked&lt;/code&gt; 报错&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 改动后，有没有更新并提交 &lt;code&gt;uv.lock&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker 登录或推送失败&lt;/td&gt;
&lt;td&gt;登录域名、用户名、密码、仓库路径及推送权限&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;函数拉取镜像失败&lt;/td&gt;
&lt;td&gt;镜像标签是否存在、账号地域是否一致、函数角色的读取权限&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;企业版镜像拉取失败&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acrInstanceId&lt;/code&gt;、实例网络及 RAM 配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出现 &lt;code&gt;exec format error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否构建了 &lt;code&gt;linux/amd64&lt;/code&gt; 镜像&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务连接失败或启动超时&lt;/td&gt;
&lt;td&gt;导入路径、依赖、&lt;code&gt;0.0.0.0&lt;/code&gt;、监听端口、容器启动日志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 认证错误&lt;/td&gt;
&lt;td&gt;触发器是否要求签名，调用身份权限、签名方法与时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接口返回 404&lt;/td&gt;
&lt;td&gt;请求方法和路径是否匹配，是否误用 &lt;code&gt;s invoke&lt;/code&gt; 检查普通路由&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改依赖后仍像旧版本&lt;/td&gt;
&lt;td&gt;函数镜像地址是否指向此次提交 SHA，部署步骤是否真的成功&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据一会有、一会没有&lt;/td&gt;
&lt;td&gt;是否把数据保存在进程内存，是否发生实例切换或回收&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自定义域名 HTTPS 失败&lt;/td&gt;
&lt;td&gt;DNS、FC 域名绑定、路由及该域名的证书&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;观察日志时，应分别看 GitHub 构建日志、部署 API 返回以及云端应用启动日志。只有镜像构建成功，还不能说明 Uvicorn 启动成功；认证失败，也需要先判断请求是否到达应用。&lt;/p&gt;
&lt;h3 id="内存列表为什么不能当数据库"&gt;内存列表为什么不能当数据库？&lt;/h3&gt;
&lt;p&gt;任务练习常用 &lt;code&gt;db = []&lt;/code&gt; 保存数据。这个列表只属于某一个 Python 进程：实例回收后会丢失，不同实例也不会共享它。&lt;code&gt;instanceConcurrency: 1&lt;/code&gt; 只限制单实例并发，并不能把所有流量固定到一个永久进程。&lt;/p&gt;
&lt;p&gt;需要保存业务数据时，应接数据库或其他持久化服务；容器内临时文件也不能默认当成永久存储。&lt;a href="https://help.aliyun.com/en/functioncompute/overview-of-instance-isolation"&gt;实例生命周期与隔离&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="自定义域名还要配置什么"&gt;自定义域名还要配置什么？&lt;/h3&gt;
&lt;p&gt;例如想用 &lt;code&gt;api.example.com&lt;/code&gt;，需要完成三个部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 DNS 中添加指向函数计算要求目标的记录。&lt;/li&gt;
&lt;li&gt;在 FC 中绑定该域名，并配置到目标函数的路由。&lt;/li&gt;
&lt;li&gt;配置覆盖该域名的 HTTPS 证书与协议。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;只添加 CNAME 并不能代替函数计算里的域名和证书配置。首次验证可先使用触发器原始地址，应用确认正常后再绑定域名。&lt;a href="https://docs.serverless-devs.com/user-guide/aliyun/fc3-domain/spec/"&gt;函数计算自定义域名配置&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="十三发布出问题时怎样回退"&gt;十三、发布出问题时怎样回退？&lt;/h2&gt;
&lt;p&gt;使用提交 SHA 作为镜像标签，可以找到上一个已经验证过的镜像。在完成本地 Serverless Devs 凭据配置、并设置与 GitHub 相同的地域、函数名和角色变量后：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fabd2f"&gt;export&lt;/span&gt; FC_IMAGE_URI&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;&amp;#34;&amp;lt;实际ACR登录域名&amp;gt;/&amp;lt;命名空间&amp;gt;/&amp;lt;仓库&amp;gt;:&amp;lt;上一个正常提交SHA&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s deploy --skip-push --assume-yes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本地使用前，可执行 &lt;code&gt;s config add&lt;/code&gt; 按交互提示配置阿里云凭据，别名填 &lt;code&gt;fc-ci&lt;/code&gt;，与 &lt;code&gt;s.yaml&lt;/code&gt; 一致。&lt;a href="https://docs.serverless-devs.com/user-guide/builtin/config/"&gt;Serverless Devs 凭据配置&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这会让 LATEST 配置重新使用旧镜像；它不会回退数据库变更，也不构成基于发布版本和别名的完整流量切换方案。还要把 GitHub 中有问题的代码或配置修正，再走正常发布流程，否则下一次推送可能重新部署有问题的版本。&lt;/p&gt;
&lt;h2 id="十四保留文档以后清理项目时记住什么"&gt;十四、保留文档以后，清理项目时记住什么？&lt;/h2&gt;
&lt;p&gt;以后再做同类项目，可以从本文的目录结构、Dockerfile、&lt;code&gt;s.yaml&lt;/code&gt; 和工作流开始，重新填写自己的地域、仓库、角色及凭据。&lt;/p&gt;
&lt;p&gt;删除 GitHub 源码仓库，是源码管理操作。已经部署的函数、HTTP 触发器、ACR 镜像和其他云资源有各自的生命周期，仍需在阿里云单独管理。需要停止服务时，要另行检查并处理云端资源。&lt;/p&gt;
&lt;p&gt;这条流程最值得记住的是：&lt;strong&gt;选择 Web 服务运行方式，让应用按指定地址和端口启动；先把镜像构建并推送成功，再让函数计算使用它；最后通过真实 HTTP 路由确认应用响应。&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>