使用阿里雲函數服務部署 nestjs

對於一個現有的 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 的函數服務,所以,先要配置阿里雲的訪問權限。

Config 命令 - Serverless Devs

第一步 創建賬號,配置 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 用於配置運行環境

function字段 - Serverless Devs

方式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"]

第二步,推送鏡像到阿里雲

容器鏡像服務

第三步,在函數服務部署中,使用鏡像進行部署

完工!

https://www.cnblogs.com/jasongrass/p/17615357.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章