對於一個現有的 nestjs 項目,如何在阿里雲上進行函數部署。
本文是一個一路踩坑的記錄,其中遇到的問題,沒有完全解決,直接繞過了。最終使用自定義鏡像的方式部署完成。
Serverless Devs
按照官方推薦,使用 Serverless Devs,
具體而言,先全局安裝 npm install @serverless-devs/s -g
,然後在項目中添加 s.yaml
配置文件。
配置文件詳細說明:
Yaml規範 - Serverless Devs
fc/docs/zh/yaml at main · devsapp/fc
但是這個是完整版本的配置,初次使用完全是懵的,所以,有個偷懶的辦法是,使用現成的組件,生成一個 yaml 文件,然後複製粘貼過來。
Serverless Regsitry - Serverless package management platform
找一個測試目錄,運行 s init start-nest -d start-nest
,就能得到一個新的 nestjs 模板。
edition: 1.0.0
name: web-framework-app
# access 是當前應用所需要的密鑰信息配置:
# 密鑰配置可以參考:https://www.serverless-devs.com/serverless-devs/command/config
# 密鑰使用順序可以參考:https://www.serverless-devs.com/serverless-devs/tool#密鑰使用順序與規範
access: "xxxxxx"
vars: # 全局變量
region: "cn-hangzhou"
functionName: "start-nest"
service:
name: "web-framework-1jl5"
description: 'Serverless Devs Web Framework Service'
services:
framework: # 業務名稱/模塊名稱
# 如果只想針對 framework 下面的業務進行相關操作,可以在命令行中加上 framework,例如:
# 只對framework進行構建:s framework build
# 如果不帶有 framework ,而是直接執行 s build,工具則會對當前Yaml下,所有和 framework 平級的業務模塊(如有其他平級的模塊,例如下面註釋的next-function),按照一定順序進行 build 操作
component: fc # 組件名稱,Serverless Devs 工具本身類似於一種遊戲機,不具備具體的業務能力,組件類似於遊戲卡,用戶通過向遊戲機中插入不同的遊戲卡實現不同的功能,即通過使用不同的組件實現不同的具體業務能力
actions: # 自定義執行邏輯,關於actions 的使用,可以參考:https://www.serverless-devs.com/serverless-devs/yaml#行爲描述
pre-deploy: # 在deploy之前運行
- run: npm install --registry=https://registry.npmmirror.com # 要執行的系統命令,類似於一種鉤子的形式
path: . # 執行系統命令/鉤子的路徑
- run: npm run build
path: .
props: # 組件的屬性值
region: ${vars.region} # 關於變量的使用方法,可以參考:https://www.serverless-devs.com/serverless-devs/yaml#變量賦值
service: ${vars.service}
function:
name: ${vars.functionName}
description: 'Serverless Devs Web Framework Function'
codeUri: .
runtime: custom
memorySize: 512
timeout: 6
caPort: 9000
customRuntimeConfig:
command:
- ./bootstrap
triggers:
- name: http-trigger
type: http
config:
authType: anonymous
methods:
- GET
- POST
customDomains:
- domainName: auto
protocol: HTTP
routeConfigs:
- path: /*
有如下關鍵信息需要修改
賬號相關
最終的目標,是通過 s deploy
這個命令,就可以一鍵部署到 aliyun 的函數服務,所以,先要配置阿里雲的訪問權限。
第一步 創建賬號,配置 AccessKey SecretKey
在 RAM 訪問控制 中創建賬號,但是,這個賬號創建之後,並沒有部署函數服務的權限
創建賬號之後,使用 s config add
命令,在本地添加 AccessKey SecretKey,這裏會讓你配置一個別名,s.yaml
中會用到,如這裏的別名爲 ali-fc-account
第二步 給賬號權限
在權限策略中,創建一個自定義的權限策略,選上 FC(函數計算)相關的權限,
回到用戶界面,爲創建的用戶添加權限,選擇剛剛創建的權限策略。
或者,也可以不自定義策略,參考 AliyunFCServerlessDevsRole
的權限,給賬號授予相關的權限。
授權之後,使用 s deploy
推送部署到阿里雲,纔不會報錯。
未授權時的錯誤提示
POST /services failed with 403. requestid: 1-64d1fbb6-398045ba8576a96c74a485c9, message: the caller is not authorized to perform 'fc:CreateService' on resource 'acs:fc:cn-hangzhou:1111:services/serverless-devs-check' with condition '[fc:EnableServiceInternetAccess=unknown, fc:EnableServiceSLSLogging=unknown]'.
第三步 修改 s.yaml 中的配置
其實就是 access: "ali-fc-account"
這一行,其中 ali-fc-account 爲使用 s config add
時,配置的別名
s.yaml 中的路徑配置
使用 s init start-nest -d start-nest
生成的項目模板中,將源碼放在了 code 子目錄中,不是很喜歡,所以我將其移動到了根目錄,那這裏的幾個路徑,也需要跟着修改。但你如果不在意,建議別改,後面有坑。
其它文件
除了在項目中添加 s.yaml
文件,還需要添加 .fcignore
文件,在部署打包時,忽略不需要的文件。一般配置如下
./test
./src
./.s
運行環境
在 s.yaml
中,runtime 用於配置運行環境
方式1
runtime: custom
customRuntimeConfig:
command:
- ./bootstrap
這裏的 custom,在阿里雲上,是 debian 環境,帶了 nodejs
在使用 custom 這個配置時,需要帶上 customRuntimeConfig 的配置。這裏的配置就是說,使用 bootstrap
文件來啓動程序,所以,此時你的項目根目錄中,還需要有一個 bootstrap 文件(沒有後綴名)
bootstrap 的具體內容是:
#!/usr/bin/env bash
node ./dist/main.js
如果一切順利的話,此時運行 s deploy
,就可以將服務部署到阿里雲函數服務上了。
遇到的問題
1 疑似 nodejs 版本問題
不知道上面的 custom 運行環境中,node 的版本具體是多少,但我這裏運行起來,會有如下錯誤。
Function instance exited unexpectedly(code 1, message:operation not permitted) with start command './bootstrap '.\nLogs:/code/node_modules/@nestjs/common/decorators/http/route-params.decorator.js:53\r\n if (options?.passthrough) {\r\n ^\r\n\r\nSyntaxError: Unexpected token .\r\n at Module._compile (internal/modules/cjs/loader.js:723:23)\r\n at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)\r\n at Module.load (internal/modules/cjs/loader.js:653:32)\r\n at tryModuleLoad (internal/modules/cjs/loader.js:593:12)\r\n at Function.Module._load (internal/modules/cjs/loader.js:585:3)\r\n at Module.require (internal/modules/cjs/loader.js:692:17)\r\n at require (internal/modules/cjs/helpers.js:25:18)\r\n at Object.<anonymous> (/code/node_modules/@nestjs/common/decorators/http/index.js:5:22)\r\n at Module._compile (internal/modules/cjs/loader.js:778:30)\r\n at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)"
chatgpt 是這麼說的
這個錯誤 Function instance exited unexpectedly (code 1, message: operation not permitted) 表示函數實例異常退出,返回了一個退出碼爲 1 的錯誤,並且錯誤消息顯示操作不允許。
根據錯誤日誌中的內容,看起來是在加載 @nestjs/common 模塊時發生了語法錯誤 SyntaxError: Unexpected token .。這可能是由於你的 Node.js 版本不支持可選鏈操作符 ?. 導致的。
可選鏈操作符是在 ECMAScript 2020 中引入的新特性,它用於簡化訪問可能爲 null 或 undefined 的屬性或方法。如果你的 Node.js 版本較舊,不支持可選鏈操作符,那麼就會出現語法錯誤。
要解決這個問題,你可以嘗試以下方法:
更新 Node.js 版本:確保你的 Node.js 版本符合支持可選鏈操作符的要求。建議使用較新的 LTS(長期支持)版本,以確保你可以使用最新的語言特性。
修改代碼:如果你無法更新 Node.js 版本,你可以修改代碼,將可選鏈操作符 ?. 替換爲傳統的屬性訪問方式,以避免語法錯誤。
例如,將 options?.passthrough 替換爲 options && options.passthrough。
檢查依賴項:如果你的代碼依賴於其他模塊或庫,確保這些依賴項與你使用的 Node.js 版本兼容,並且能夠正確加載和執行。
請注意,具體的解決方法可能因你的代碼和環境而異。如果問題仍然存在,請提供更多詳細信息,以便我可以更好地幫助你解決問題。
如果真是版本問題,還不好處理了。
2 修改 runtime 爲 nodejs16 之後的問題
儘管文檔中說只支持到 nodejs14,但好像設置成 16 也可以,不過,會有其它的問題。
函數計算運行時的支持情況和終止策略_函數計算-阿里雲幫助中心
function:
codeUri: ./
runtime: nodejs16
handler: main.fcHandler
此時,需要配置 handler,文檔見 什麼是函數Node.js運行時的請求處理程序_函數計算-阿里雲幫助中心
於是在 nestjs 項目的 main.ts 中,添加了如下代碼
export async function fcHandler() {
await bootstrap();
}
然後得到了一個報錯,說 main.js 沒有找到,因爲去 code 目錄下找了,但是我的 codeUri
已經配置成根目錄了啊。感覺這裏有只 BUG。我不想把代碼放到 code 子目錄中驗證了,考慮其它方案。
{
"errorMessage": "Module '/code/main.js' is missing.",
"errorType": "FunctionUnhandledError: ImportModuleError",
"stackTrace": [
"ImportModuleError: Module '/code/main.js' is missing."
]
}
這裏的問題意思是,函數服務沒有在 code 目錄下,找到 main.js,其實 code 目錄就是函數服務的根目錄,所以,這裏的 codeUri
應該修改成 ./dist
,不過,修改之後還是有其它問題,nestjs 應該不太能夠是有 handler 這種喚起方式。
handler 只適合非常簡單的 web 服務。
最終方案
折騰了一圈之後,放棄使用 serverless dev 進行部署了。直接使用 docker 鏡像進行部署。當然,serverless dev 也支持配置使用鏡像部署,當已經不想再折騰這個配置了。還不如手動操作。
第一步,在本地構建鏡像
dockerfile 參考
# 一個本地的鏡像,在 debian 基礎上,安裝了 node18 和 npm
FROM debian-nodejs18:v2
WORKDIR /app
COPY package*.json ./
COPY tsconfig*.json ./
COPY nest-cli.json ./
COPY src .
RUN npm i -g @nestjs/cli
RUN npm ci --production
# RUN npm run build # 在容器中運行會出錯,無法解析 @,安裝了全部 npm 依賴(包括開發依賴)也無效,擱置,直接複製 dist 目錄
COPY dist ./dist
EXPOSE 9000
CMD ["node", "./dist/main.js"]
第二步,推送鏡像到阿里雲
第三步,在函數服務部署中,使用鏡像進行部署
完工!