摘要:最近嘗試將應用的頁面錯誤報警功能通過來實現。可以根據實際情況修改函數服務名和函數名。實現報警功能的主要邏輯,就寫在里面。阿里云的函數計算可以使用阿里云日志服務來存儲日志。
最近嘗試將應用的頁面 JS 錯誤報警功能通過 Serverless 來實現。本文主要介紹一下具體實現過程,以及遇到的一些問題。
報警功能的需求也很簡單,就是定時(如每隔 1 分鐘)去讀取 ARMS 的錯誤日志,如果有錯誤日志,則通過釘釘消息發送錯誤詳情進行報警。
在這之前,我通過定時任務實現了該功能。從成本上來說,這種方案就需要多帶帶申請一臺服務器資源;而且定時任務只在對應的時間才執行,這件意味著,服務器有很長的時間都是空閑的,這就造成了資源的浪費。而使用 Serverless,就不需要再申請服務器,函數只需要在需要的時候執行,這就大大節省了成本。
總的來說,我覺得函數計算的優勢就是:
對于開發者,只需要關系業務邏輯的實現,不需要關心代碼所運行的環境、硬件資源、以及運維
節省成本
通過 Serverless 實現前端日志報警,依賴的云服務是阿里云函數計算,依賴的其他工具還有:
函數計算的命令行工具 fun,用于本地調試、部署函數
函數計算的可交互式工具 fcli,用于本地測試
阿里云 JS SDK aliyun-sdk-js,用于讀取 SLS 日志,ARMS 的日志是存儲在 SLS 中的
編程語言使用 Node.js
安裝和配置 fun初次使用需要先安裝 fun
$ npm install @alicloud/fun -g
安裝完成之后,需要通過 fun config 配置一下賬號信息 Aliyun Account ID Aliyun Access Key ID Aliyun Secret Access Key 以及默認的地域。地域這里有個需要注意的是,如果需要使用 SLS 記錄函數日志,則需要 SLS 和函數服務在同一個地域。這里稍后會詳細介紹。
$ fun config ? Aliyun Account ID ****** ? Aliyun Access Key ID ****** ? Aliyun Secret Access Key ****** ? Default region name cn-shanghai ? The timeout in seconds for each SDK client invoking 60 ? The maximum number of retries for each SDK client 6
Aliyun Account ID Aliyun Access Key ID Aliyun Secret Access Key 可以在阿里云的控制臺中查找和設置。
Aliyun Account ID
![Aliyun Account ID]
Aliyun Access Key ID Aliyun Secret Access Key
函數初始化先通過 fun 創建一個 Node.js 的 demo,之后可以在這個 demo 的基礎上進行開發。
$ fun init -n alarm helloworld-nodejs8 Start rendering template... + /Users/jh/inbox/func/code/alarm + /Users/jh/inbox/func/code/alarm/index.js + /Users/jh/inbox/func/code/alarm/template.yml finish rendering template.
執行成功后,分別創建了兩個文件 index.js 和 template.yml。
其中 template.yml 是函數的規范文檔,在里面定義了函數需要的資源、觸發函數的事件等等。
template.yml接下來簡單看看生成的默認的 template.yml 配置文件。
ROSTemplateFormatVersion: "2015-09-01" Transform: "Aliyun::Serverless-2018-04-03" Resources: alarm: Type: "Aliyun::Serverless::Service" Properties: Description: "helloworld" alarm: Type: "Aliyun::Serverless::Function" Properties: Handler: index.handler Runtime: nodejs8 CodeUri: "index.js"
首先定義了規范文檔的版本 ROSTemplateFormatVersion 和 Transform,這兩個都不用修改。
Resources 里面定義了一個名為 alarm 的函數服務(Type: Aliyun::Serverless::Service 表示該屬性為函數服務),并且該服務里面定義了名為 alarm 的函數(Type: "Aliyun::Serverless::Function"表示該屬性為函數)。
函數服務里面可以包含多個函數,就相當于是一個函數組。后面我們會提到的函數日志,是配置到函數服務上的。函數服務里面的所有函數,都用同一個日志。
可以根據實際情況修改函數服務名和函數名。下面就將函數服務名稱改為 yunzhi,函數名依舊保留為 alarm。
ROSTemplateFormatVersion: "2015-09-01" Transform: "Aliyun::Serverless-2018-04-03" Resources: yunzhi: # 函數服務的名稱 Type: "Aliyun::Serverless::Service" # 表示 yunzhi 是一個函數服務 Properties: Description: "helloworld" # 函數服務的描述 alarm: # 函數的名稱 Type: "Aliyun::Serverless::Function" # 表示 alarm 是一個函數 Properties: Handler: index.handler # 函數的調用入口 Runtime: nodejs8 # 函數的運行環境 CodeUri: "index.js" # 代碼的目錄
alarm 函數里面的 Properties 定義了函數的調用入口、運行環境等,如上面的注釋所示。
關于 template.yml 的配置詳見 Serverless Application Model。
index.jsindex.js 文件就是函數的調用入口了。index.handler 就表示,函數的調用的是 index.[extension] 文件中的 handler 函數。
module.exports.handler = function(event, context, callback) { console.log("hello world"); callback(null, "hello world"); };
初始化之后的代碼就上面這幾行,很簡單。主要是理解上面的幾個參數。
event 調用函數時傳入的參數
context 函數運行時的一些信息
callback 函數執行之后的回調
必須要要調用 callback 函數,才會被認為函數執行結束。如果沒有調用,則函數會一直運行到超時
callback 調用之后,函數就結束了
callback 的第一個參數是 error 對象,這和 JS 回調編程的思想一致
關于 event 和 context,詳見 Nodejs 函數入口。
實現報警功能的主要邏輯,就寫在 index.js 里面。具體的實現,就不細說,下面用偽代碼來描述:
alarm/alarm.js
// alarm/alarm.js // 實現報警功能 module.exports = function() { return new Promise((resolve, reject) => { // 查詢 SLS 日志 // - 如果沒有錯誤日志,則 resolve // - 如果有錯誤日志,則發送釘釘消息 // - 如果釘釘消息發送失敗,則 reject // - 如果釘釘消息發送成功,則 resolve resolve(); }) }
alarm/index.js
// alarm/index.js // 調用報警函數 const alarm = require("./alarm"); module.exports.handler = function(event, context, callback) { alarm() .then(() => { callback(null, "success"); }) .catch(error => { callback(error); }) };CodeUri
如果函數里面引入了自定義的其他模塊,比如在 index.js 里面引入了 alarm.js const alarm = require("./alarm");,則需要修改默認的 codeUri 為當前代碼目錄 ./。否則默認的 codeUri 只定義了 index.js,部署的時候只會部署 index.js。
ROSTemplateFormatVersion: "2015-09-01" Transform: "Aliyun::Serverless-2018-04-03" Resources: yunzhi: # 函數服務的名稱 Type: "Aliyun::Serverless::Service" # 表示 yunzhi 是一個函數服務 Properties: Description: "helloworld" # 函數服務的描述 alarm: # 函數的名稱 Type: "Aliyun::Serverless::Function" # 表示 alarm 是一個函數 Properties: Handler: index.handler # 函數的調用入口 Runtime: nodejs8 # 函數的運行環境 CodeUri: "./" # 代碼的目錄
如果沒有修改 CodeUri,則會有類似下面的報錯
$ fun local invoke alarm FC Invoke End RequestId: 16e3099e-6a40-43cb-99a0-f0c75f3422c6 { "errorMessage": "Cannot find module "./alarm"", "errorType": "Error", "stackTrace": [ "Error: Cannot find module "./alarm"", "at Module._resolveFilename (module.js:536:15)", "at Module._load (module.js:466:25)", "at Module.require (module.js:579:17)", "at require (internal/module.js:11:18)", "at (/code/index.js:9:15)", "at Module._compile (module.js:635:30)", "at Module._extensions..js (module.js:646:10)", "at Module.load (module.js:554:32)", "at tryModuleLoad (module.js:497:12)", "at Module._load (module.js:489:3)" ] }
fun local invoke alarm 是本地調試的命令,接下來會講到。
本地調試在開發過程中,肯定需要本地調試。fun 提供了 fun local 支持本地調試。
fun local 的命令格式為 fun local invoke [options] <[service/]function>,其中 options 和 service 都可以忽略。比如調試上面的報警功能的命令就是 fun local invoke alarm。
需要注意的是,本地調試需要先安裝 docker。
$ brew cask install docker
安裝成功后啟動 docker。
如果 docker 沒有啟動,運行 fun local 可能會有如下報錯
$ fun local invoke alarm Reading event data from stdin, which can be ended with Enter then Ctrl+D (you can also pass it from file with -e) connect ENOENT /var/run/docker.sock
正常的輸出如下
$ fun local invoke alarm Reading event data from stdin, which can be ended with Enter then Ctrl+D (you can also pass it from file with -e) skip pulling image aliyunfc/runtime-nodejs8:1.5.0... FC Invoke Start RequestId: 9360768c-5c52-4bf5-978b-774edfce9e40 load code for handler:index.handler FC Invoke End RequestId: 9360768c-5c52-4bf5-978b-774edfce9e40 success RequestId: 9360768c-5c52-4bf5-978b-774edfce9e40 Billed Duration: 79 ms Memory Size: 1998 MB Max Memory Used: 54 MB
第一次調試的話,會安裝 runtime 的鏡像,可能需要點時間。默認的 Docker 鏡像下載會很慢,可以使用國內的加速站點加速下載。
出現 Reading event data from stdin, which can be ended with Enter then Ctrl+D 的提示時,如果不需要輸入,可以按 ctrl+D 跳過。
函數部署開發完成之后,就需要將函數部署到阿里云的函數計算上面了。部署可以通過 fun deploy 命令。
前面已經在安裝 fun 之后,通過 fun config 命令配置了阿里云的賬號和地域信息,fun deploy 會將函數自動部署到對應的賬號和地域下。
在 template.yml 中,也配置了函數的服務名和函數名。如果在函數計算中沒有對應的服務或函數,fun deploy 會自動創建;如果已經存在,則會更新。
$ fun deploy using region: cn-shanghai using accountId: ***********4698 using accessKeyId: ***********UfpF using timeout: 60 Waiting for service yunzhi to be deployed... Waiting for function alarm to be deployed... Waiting for packaging function alarm code... package function alarm code done function alarm deploy success service yunzhi deploy success
部署成功之后,就可以在函數計算的控制臺中看到對應的函數服務和函數了。目前還沒有配置觸發器,可以手動在控制臺中點擊“執行”按鈕來執行函數。
觸發器對于應用到生產環境的函數,肯定不會像上面一樣手動去執行它,而是通過配置觸發器去執行。觸發器就相當于是一個特定的事件,當函數計算接收到該事件的時候,就去調用對應的函數。
阿里云的函數計算支持 HTTP 觸發器(接收到 HTTP 請求之后調用函數)、定時觸發器(定時調用函數)、OSS 觸發器等等。詳見 觸發器列表。
對于報警功能,需要用到的是定時觸發器,因為需要間隔一定的時間就調用函數。
觸發器是配置到函數中的,可以通過函數的 Event 屬性去配置
ROSTemplateFormatVersion: "2015-09-01" Transform: "Aliyun::Serverless-2018-04-03" Resources: yunzhi: Type: "Aliyun::Serverless::Service" Properties: Description: "helloworld" alarm: Type: "Aliyun::Serverless::Function" Properties: Handler: index.handler Runtime: nodejs8 CodeUri: "./" Events: # 配置 alarm 函數的觸發器 TimeTrigger: # 觸發器的名稱 Type: Timer # 表示該觸發器是定時觸發器 Properties: CronExpression: "0 0/1 * * * *" # 每 1 分鐘執行一次 Enable: true # 是否啟用該定時觸發器
上面的配置,就為 alarm 配置了一個名為 TimeTrigger 的定時觸發器,觸發器每隔 1 分鐘執行一次,也就是每隔 1 分鐘調用一次函數。
配置完成之后,再執行 fun deploy 就可以發布函數及觸發器到函數計算上。
這里需要注意的是,阿里云函數計算服務目前支持的觸發器,最小的間隔時間為 1 分鐘。如果小于 1 分鐘,則無法設置成功。定時觸發器的詳細介紹可參考文檔 定時觸發函數。
函數日志對于 serverless 應用,雖然不用關心運維了,其實我們也并不知道我們的函數運行在哪臺服務器上。這個時候,函數的日志就尤為重要了。沒有日志,我們很難知道程序運行狀態,遇到問題更是無從下手。
所以接下來需要對函數配置日志。阿里云的函數計算可以使用阿里云日志服務 SLS來存儲日志。如果要存儲日志,則需要先開通 日志服務。
不存在日志庫如果是第一次使用日志服務,則肯定不存在日志庫。可以在 template.yml 像定義函數服務一樣,通過 Resource 來定義日志資源。
前面也提到,函數日志是配置到對應的服務上的,具體配置也很簡單,就是通過函數服務的 LogConfig 屬性來配置。
完整的 template.yml 如下
ROSTemplateFormatVersion: "2015-09-01" Transform: "Aliyun::Serverless-2018-04-03" Resources: log-yunzhi: # 日志項目名稱為 log-yunzhi Type: "Aliyun::Serverless::Log" # 表示該資源是阿里云的日志服務 Properties: Description: "yunzhi function service log project" log-yunzhi-store: # 日志的 logstore Type: "Aliyun::Serverless::Log::Logstore" Properties: TTL: 10 ShardCount: 1 log-another-logstore: # 日志的另一個 logstore Type: "Aliyun::Serverless::Log::Logstore" Properties: TTL: 10 ShardCount: 1 yunzhi: Type: "Aliyun::Serverless::Service" Properties: Description: "helloworld" LogConfig: # 配置函數的日志 Project: "log-yunzhi" # 存儲函數日志 SLS 項目: log-yunzhi Logstore: "log-yunzhi-store" # 存儲函數日志的 SLS logstore: log-yunzhi-store alarm: Type: "Aliyun::Serverless::Function" Properties: Handler: index.handler Runtime: nodejs8 CodeUri: "./" Events: TimeTrigger: Type: Timer Properties: CronExpression: "0 0/1 * * * *" Enable: true
在上面的配置中,就定義了名為 log-yunzhi 的日志項目(Project),并且在該 Project 中創建了兩個日志倉庫(LogStore):log-yunzhi-store 和 log-yunzhi-store。一個 Project 可以包含多個 LogStore。
注意:日志項目的名稱必須全局唯一。 即配置中,og-yunzhi 這個項目名稱是全局唯一的。
執行 fun deploy 之后,就會自動在函數服務對應的地域創建日志 Project 及日志 logstore,同時也會自動為 logstore 加上全文索引,然后自動為函數服務配置日志倉庫。
之后函數的運行日志都會存儲在對應的 logstore 里。
$ fun deploy using region: cn-shanghai using accountId: ***********4698 using accessKeyId: ***********UfpF using timeout: 60 Waiting for log service project log-yunzhi to be deployed... Waiting for log service logstore log-yunzhi-store to be deployed... retry 1 times Waiting for log service logstore log-yunzhi-store default index to be deployed... log service logstore log-yunzhi-store default index deploy success log serivce logstore log-yunzhi-store deploy success Waiting for log service logstore log-another-logstore to be deployed... Waiting for log service logstore log-another-logstore default index to be deployed... log service logstore log-another-logstore default index deploy success log serivce logstore log-another-logstore deploy success log serivce project log-yunzhi deploy success Waiting for service yunzhi to be deployed... Waiting for function alarm to be deployed... Waiting for packaging function alarm code... package function alarm code done Waiting for Timer trigger TimeTrigger to be deployed... function TimeTrigger deploy success function alarm deploy success service yunzhi deploy success
如果日志庫已經存在,且定義了日志資源,則 fun deploy 會按照 template.yml 中的配置更新日志庫。
存在日志庫如果日志庫已經存在,即已經在日志服務中創建了日志項目 Project 和日志庫 Logstore ,就可以直接為函數服務添加 LogConfig,不用再定義日志資源。
注意,日志庫需要和函數服務在同一個地域 Region。否則不能部署成功。
下面是一個配置函數日志到已經存在的 Project 和 Logstore 中的例子。
ROSTemplateFormatVersion: "2015-09-01" Transform: "Aliyun::Serverless-2018-04-03" Resources: yunzhi: Type: "Aliyun::Serverless::Service" Properties: Description: "helloworld" LogConfig: # 配置函數的日志 Project: "log-yunzhi-exist" # 存儲函數日志到已經存在的 Project: log-yunzhi-exist Logstore: "logstore-exist" # 存儲函數日志到已經存在的 logstore: logstore-exist alarm: Type: "Aliyun::Serverless::Function" Properties: Handler: index.handler Runtime: nodejs8 CodeUri: "./" Events: TimeTrigger: Type: Timer Properties: CronExpression: "0 0/1 * * * *" Enable: true
如果日志庫和函數服務不在同一個地域,函數服務就會找不到日志庫,fun deploy 也會報錯。如下所示,yunzhi-log-qingdao 是我創建的一個青島地域的日志 Project。
$ fun deploy using region: cn-shanghai using accountId: ***********4698 using accessKeyId: ***********UfpF using timeout: 60 Waiting for service yunzhi to be deployed... retry 1 times retry 2 times retry 3 times retry 4 times retry 5 times retry 6 times retry 7 times PUT /services/yunzhi failed with 400. requestid: 6af2afb8-cbd9-0d3e-bf16-fe623834b4ee, message: project "yunzhi-log-qingdao" does not exist.其他問題 子賬號 AccessDenied
如果是使用 RAM 子賬號來開發、部署函數計算,則 fun 工具的配置中 Aliyun Access Key ID Aliyun Secret Access Key
是對應子賬戶的信息,但 Aliyun Account ID 還是主賬號的信息。RAM 子賬號有一個 UID,這個不是 Account ID。
如果 Aliyun Account ID 寫錯了,則使用 fun 或 fcli 的時候,可能會遇到下面的錯誤
Error: { "HttpStatus": 403, "RequestId": "b8eaff86-e0c1-c7aa-a9e8-2e7893acd545", "ErrorCode": "AccessDenied", "ErrorMessage": "The service or function doesn"t belong to you." }代碼版本的管理
在實現報警功能的過程中,我依舊使用了 GitLab 來存儲代碼。每次開發完成之后,將代碼 push 到 GitLab,然后再將代碼部署到函數計算上。不過這兩個過程是獨立的,還是不那么方便。
環境問題一般我們開發的時候,需要日常、預發、線上多個環境部署、測試。阿里云函數計算是一個云產品,沒有環境的區分。但對于報警整個功能,我也沒有去區分環境,只是本地開發的時候,將報警消息發到一個測試的釘釘群,所以也沒有特別去關注。
經濟成本使用函數計算的經濟成本,相比于購買云服務器部署應用,成本低了非常多。
本文中涉及到函數計算和日志服務兩個云產品,都有一定的免費額度。其中函數計算每月前 100 萬次函數調用免費,日志服務每月也有 500M 的免費存儲空間和讀寫流量。所以只用來測試或者實現一些調用量很小的功能,基本是免費的。
函數計算計費方式
日志服務產品定價
總結配置了函數的日志之后,將函數部署到函數計算上,就算是正式發布上線了。
現在回過頭來看,整個流程還算比較簡單。但從零開始一步一步到部署上線的過程,還是遇到了很多問題。比如文中的許多注意事項,都是在不斷嘗試中得出的結論。
最近 serverless 這個話題也很火熱,也期待這個技術即將帶來的變革。
more https://github.com/nodejh/nodejh.github.io/issues
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102957.html
摘要:本文以開發一個類似語音口令紅包小程序為例,向您講解如何使用阿里云函數計算快速構建微信小程序的服務端。 前言 這篇文章適合所有的想微信小程序開發新手、老鳥以及想準備學習開發微信小程序的程序猿。本文以開發一個類似語音口令紅包小程序為例,向您講解如何使用阿里云函數計算快速構建微信小程序的服務端。通過本文,您將會了解以下內容: demo概覽 傳統服務器架構 VS Serverless架構 S...
摘要:本文以開發一個類似語音口令紅包小程序為例,向您講解如何使用阿里云函數計算快速構建微信小程序的服務端。 前言 這篇文章適合所有的想微信小程序開發新手、老鳥以及想準備學習開發微信小程序的程序猿。本文以開發一個類似語音口令紅包小程序為例,向您講解如何使用阿里云函數計算快速構建微信小程序的服務端。通過本文,您將會了解以下內容: demo概覽 傳統服務器架構 VS Serverless架構 S...
摘要:摘要阿里云函數計算是一個事件驅動的全托管計算服務。微信小程序是一種不需要下載安裝即可使用的應用,它可以在微信內被便捷地獲取和傳播。本文以開發一個類似語音口令紅包小程序為例,向您講解如何使用阿里云函數計算快速構建微信小程序的服務端。 摘要: 阿里云函數計算是一個事件驅動的全托管計算服務。通過函數計算,您無需管理服務器等基礎設施,只需編寫代碼并上傳。微信小程序是一種不需要下載安裝即可使用的...
摘要:摘要阿里云函數計算是一個事件驅動的全托管計算服務。微信小程序是一種不需要下載安裝即可使用的應用,它可以在微信內被便捷地獲取和傳播。本文以開發一個類似語音口令紅包小程序為例,向您講解如何使用阿里云函數計算快速構建微信小程序的服務端。 摘要: 阿里云函數計算是一個事件驅動的全托管計算服務。通過函數計算,您無需管理服務器等基礎設施,只需編寫代碼并上傳。微信小程序是一種不需要下載安裝即可使用的...
摘要:無服務器計算是目前最被看好的云端計算執行模型。阿里云函數計算是事件驅動的全托管計算服務。通過函數計算,您無需管理服務器等基礎設施,只需編寫代碼并上傳。Serverless Computing(無服務器計算)是目前最被看好的云端計算執行模型。其最大的好處是提供分布式彈性可伸縮的計算執行環境,僅為實際使用資源付費,并且將應用維護者從常規的運維事務中解放出來,更利于專注到具體的業務上。 在主流的應...
閱讀 3061·2021-11-23 09:51
閱讀 1040·2021-09-02 15:21
閱讀 3005·2019-08-30 13:56
閱讀 1828·2019-08-29 14:12
閱讀 707·2019-08-29 13:53
閱讀 1664·2019-08-29 11:32
閱讀 1325·2019-08-29 11:25
閱讀 1492·2019-08-28 17:51