在 AWS 以 Aurora Serverless 搭建無伺服器 Web 應用程式 part4 — 建立及佈署 Serverless Framework 專案
在上一篇中我們將 IAM 設定好,並安裝好 Serverless Framework 及 IAM credential,本文將介紹如何撰寫 Serverless Framework 的核心檔案 serverless.yml
來實現 infrastructure as code,並將專案佈署至 AWS 上。
本文參考:
初始化 Serverless 專案
建立專案
你可以建立一個目錄,底下專門放 serverless 的專案,例如:
$ mkdir serverless-projects
然後切換到該目錄底下:
$ cd serverless-projects
接著下指令來建立新專案,新專案取名為 web-api
:
$ sls create -t aws-nodejs -p web-api
上面這個指令中的 -t aws-nodejs
表示我們要用 “aws-nodejs” 這個 template。而 -p web-api
表示這個專案名稱叫 “web-api”,framework 會自動幫你產生一個 “web-api” 的目錄並做初始化。
其他常用的 templates 可以參考以下網址,常見的語言如 python、ruby、java、go 等等都找得到:
https://serverless.com/framework/docs/providers/aws/cli-reference/create/
跑完後會自動建立一個 web-api
的新目錄,請切換到這個目錄底下:
$ cd web-api
安裝 npm 套件
先初始化 npm:
$ npm init -y
接著安裝 serverless-offline
,這個套件是用來在 local 端跑 web server 以供開發階段的測試:
$ npm i --save-dev serverless-offline
最後,安裝 mysql2
及 sequelize
,用來做資料庫連線以及 ORM:
$ npm i --save mysql2 sequelize
編輯 serverless.yml
專案目錄中的serverless.yml
等於是整個專案的設定檔,也是實現 infrastructure as code 的核心檔案, framework 會依據這個檔案的內容來做對應的處理。它可以設定的項目非常多,有需要時可參考此文件。
請以編輯器開啟 serverless.yml
,一開始會有很多註解,請先把全部內容先清空,然後複製貼上以下的內容:
service: web-apicustom:
defaultStage: local
env: ${file(config/${self:provider.stage}/env.json)}
vpc: ${file(config/${self:provider.stage}/vpc.json)}
serverless-offline:
skipCacheInvalidation: true
stages:
- dev
- prodprovider:
name: aws
runtime: nodejs8.10
stage: ${opt:stage, self:custom.defaultStage}
region: ${self:custom.env.REGION}
timeout: 30
profile: ${opt:profile, self:custom.env.PROFILE} # 新版本已棄用此參數,請改用參數 "iam"
vpc:
securityGroupIds: ${self:custom.vpc.SECURITY_GROUP_IDS}
subnetIds: ${self:custom.vpc.SUBNET_IDS}
environment:
NODE_ENV: ${self:provider.stage}functions:
hello:
handler: handler.hello
events:
- http:
path: /hello
method: get
cors: true
healthCheck:
handler: handler.healthCheck
events:
- http:
path: /
method: get
cors: true
create:
handler: handler.create
events:
- http:
path: notes
method: post
cors: true
getOne:
handler: handler.getOne
events:
- http:
path: notes/{id}
method: get
cors: true
getAll:
handler: handler.getAll
events:
- http:
path: notes
method: get
cors: true
update:
handler: handler.update
events:
- http:
path: notes/{id}
method: put
cors: true
destroy:
handler: handler.destroy
events:
- http:
path: notes/{id}
method: delete
cors: trueplugins:
- serverless-offline
- serverless-stage-manager
如果你覺得一頭霧水,沒關係,我們一部分一部分來簡單說明。
service
service 這部分就是用來宣告專案的名稱,framework 會依照這個名稱當做前綴,加上後面會提到的 stage 名稱當後綴,組合出一個如 web-api-dev
的名字來當做 AWS CloudFormation 的 stack 名稱。
custom
custom 這區段可以拆成兩部分來看,各有各的作用:
(1) 用來自訂義變數:
defaultStage: local
env: ${file(config/${self:provider.stage}/env.json)}
vpc: ${file(config/${self:provider.stage}/vpc.json)}
這幾行是自訂義變數,後面的設定會用到它們。其中 ${file(...)}
表示這個設定要讀取括號內的檔名的內容,這個 file()
函式支援 json 及 yml 格式,還可以直接指定讀取檔案內指定的字段,如 ${file(...):MY_VAR}
,當專案越來越大的時候一定會用得到。詳細說明請參考此文件。
另外,檔名中的變數 ${self:provider.stage}
則會去抓自己這個 yml 檔中 provider.stage
的設定值。
(2) 用來設定 plugins 的參數:
serverless-offline:
skipCacheInvalidation: true
stages:
- dev
- prod
這一段就是為了 serverless-offiline
及 serverless-stage-manager
這些 plugins 而設定的參數,而這些參數如何使用必須去看各 plugins 的文件。
這邊特別提一下,為什麼 stages
裡面不設定 local
這個環境呢?這是為了避免我們以後在用 sls deploy
指令的時候忘了帶 --stage
參數,那麼 stage 就會吃到預設值 “local”,你的 local 環境就跑上雲端了!安裝 serverless-stage-manager
可以用來控制 sls
指令允許的 stages 有哪些,避免打錯字的時候衍生出一些不必要的麻煩。
上述 plugins 的詳細說明請參見:
serverless-offline plugin: https://www.npmjs.com/package/serverless-offline
serverless-stage-manager: https://www.npmjs.com/package/serverless-stage-manager
provider
這區段可以拆解成以下幾段:
(1) 要用什麼樣的環境變數來建立這個專案:
name: aws
runtime: nodejs8.10
stage: ${opt:stage, self:custom.defaultStage}
profile: ${opt:profile, self:custom.env.PROFILE}
region: ${self:custom.env.REGION}
timeout: 30
其中 ${opt:stage, self:custom.defaultStage}
表示:如果 command line 指令中有給 --stage
參數,就用這個參數值,例如指令為 $ sls deploy --stage dev
,那這裡的值就會是 dev
;若 command line 沒有給--stage
參數,就用自己這個 yml 檔案中 custom.defaultStage
的設定值。
profile
表示我們要用哪一個 AWS IAM profile,這裡的語法同上:如果 command line 指令有給 --profile
參數就套用;若無,就會去吃自己這個 yml 檔的 custome.env.PROFILE
這個設定。
請注意,這裡為了方便,直接使用佈署用的 profile,實際上這個 profile 用不到這麼多權限,應該要另外設定才是正解。
2021更新:在較新的版本中已棄用 profile 參數,需改用參數 iam,此參數彈性高,可指定既有的 iam resource,也可直接使用 cloud formation 語法設定權限。
詳見:https://www.serverless.com/framework/docs/providers/aws/guide/iam
其他還有 stackName
、 apiName
等等參數可選用,可參考前面提到的官方文件。
(2) 告訴 framework 我們在 AWS 或其他 provider 那邊的設定值是什麼:
vpc:
securityGroupIds: ${self:custom.vpc.SECURITY_GROUP_IDS}
subnetIds: ${self:custom.vpc.SUBNET_IDS}
vpc
表示 AWS VPC 的網路相關設定是什麼,實際上要填入的就是 Aurora Serverless cluster 裡的設定,這部分的設定會去吃這個檔案的 custom.vpc
這組設定,而 custom.vpc
實際上是去抓 vpc.json
這個檔案,這部分後面會再解釋。
(3) 讓專案下的程式碼可以抓得到專案層級的環境變數:
environment:
NODE_ENV: ${self:provider.stage}
一旦設定了這些變數,之後有 nodejs 裡的程式碼就可以利用 process.env
這個物件來取得環境變數,例如:
process.env.NODE_ENV
這個變數就會回傳 NODE_ENV
也就是 ${self:provider.stage}
這個設定值。
若專案使用的是 python,也可用 os.environ
這個 dictionary 來取得,如:
os.environ['MY_VAR']
更多可供使用的變數請參見:https://serverless.com/framework/docs/providers/aws/guide/variables/
functions
這部分是用來描述 Lambda function 的名稱、觸發方式及指向等 opration 上的細節,例如:
hello:
handler: handler.hello
events:
- http:
path: /hello
method: get
cors: true
在這段宣告中,framework 會自動幫我們建立一個 web-api-dev-hello
的 Lambda function,而這個 function 對應的程式碼是 handler
的內容,也就是handler.hello
這個 method,我們可以在 handler.js
這個檔案中找到這段 Serverless Framework 幫我們自動產生的範例程式。
而 events
用來設定觸發這個 Lambda function 的條件,例如 http、s3、sns、sqs 等等服務都各自有對應的事件可以用來作為觸發條件。
這邊因為我們的應用程式是 Web API,所以事情類型是 http
,對應的服務就是 API Gateway 了。底下的 path
、 method
、 cors
的設定會幫我們自動在 AWS API Gateway 中產生一個 POST /hello
的 endpoint,並開啟 cors 設定。
關於 CORS 的進階設定請參考:
https://serverless.com/blog/cors-api-gateway-survival-guide/
plugins
前面有提到 plugins 的相關設定,而這邊就是告訴 framework 我們要使用哪些 plugins, framework 會自動幫我們將相關設定加進 package.json 中。
編輯設定檔
在上一部分的 serverless.yml
中,我們可以看起 custom
區段引用了兩組設定檔:
${file(config/${self:provider.stage}/env.json)}
${file(config/${self:provider.stage}/vpc.json)}
另外我們還需要一組 db.json
的設定檔讓程式可以連線到資料庫。
因為我們總共會有 3 種環境:local、dev、prod,所以總共會有 9 個檔案。
首先請先建立各環境的目錄:
$ mkdir config
$ mkdir config/local
$ mkdir config/dev
$ mkdir config/prod
以下一一敘述每個檔案的內容。
env.json
新增以下三個檔案:
config/local/env.json
config/dev/env.json
config/prod/env.json
首先是 config/local/env.json
,內容如下:
{
"PROFILE": "",
"REGION": ""
}
因為 local 是本機測試環境,不需要用到 AWS 的設定,所以這邊的設定值留空即可。你可能會問能不能乾脆連 key 都不給,答案是可以,但跑 sls
指令 時會噴 warning,很煩,所以建議不要省略。
再來, config/dev/env.json
及 config/prod/env.json
,內容是一樣的:
{
"PROFILE": "serverless-agent",
"REGION": "YOUR_AWS_REGION"
}
請將 REGION
改成你實際上使用的 AWS region 名稱。
vpc.json
新增以下三個檔案:
config/local/vpc.json
config/dev/vpc.json
config/prod/vpc.json
首先是 config/local/vpc.json
,內容如下:
{
"SECURITY_GROUP_IDS": [],
"SUBNET_IDS": []
}
因為 local 端用不到 AWS 的 VPC,所以這邊都留空陣列即可。
而 config/dev/db.json
及 config/prod/db.json
是我們在 part1 中Aurora Serverless cluster 的設定,如下圖紅框處:
檔案內容大概會長這樣:
{
"SECURITY_GROUP_IDS": [
"sg-xxx"
],
"SUBNET_IDS": [
"subnet-xxx",
"subnet-xxx",
"subnet-xxx"
]
}
把斜體字換成你自己的 cluster 設定值就可以囉。
db.json
新增以下 3 個檔案:
config/local/db.json
config/dev/db.json
config/prod/db.json
檔案內容格式都一樣,但請將各環境的 db 連線參數各自對號入座:
{
"database": "YOUR_DB_NAME",
"username": "YOUR_DB_USER",
"password": "YOUR_DB_PASSWORD",
"host": "YOUR_DB_HOST",
"dialect": mysql
}
config/local/db.json
的內容就是你自己本機測試用的 MySQL 連線設定,如果你還沒有的話就趕快先裝一下吧。
config/dev/db.json
及 config/prod/db.json
就是我們在 part1 中,Aurora Serverless cluster 那邊的設定。host
填入下圖紅框內的 endpoint:
另外幾個 database
、username
、password
則是填入我們在 part2 中設定好的 database 名稱及使用者帳密。
註:關於設定檔目錄的架構,可能會有些不同的實作方式,若覺得這樣設計不符合 nodejs 的 best practice,歡迎提出指教喔。
修改 .gitignore
雖然我們的 project 還沒有做 git 初始化,但 Serverless Framework 很貼心地幫我們生了一個 .gitignore
檔,並且已經將一些基本不需要被追蹤的檔案加進去了。
因為剛剛我們生了一些設定檔,這些檔案中,跟 AWS 相關的設定是不應該要被 git 追蹤到的,請開啟 .gitignore
增加以下內容:
config/dev/*
config/prod/*
至於 config/local/
這個目錄底下的檔案原則上是需要被 git 追蹤的,因為它記載著各個需要被設定的項目,方便讓其他團隊成員知道這個專案需要有哪些設定值才能正常運作。
佈署
其實到這邊,我們就已經可以將現有的資源佈署到 AWS 上了!請下指令:
$ sls deploy --stage dev
結果應該會類似這樣:
可以看到,API Gateway 的 endpoints 及 Lambda functions 都已經幫我們串好了!
接著我們可以來試著送 request 到 /hello
這個 endpoint:
$ curl https://(馬賽克).execute-api.ap-northeast-1.amazonaws.com/dev/hello
應該會回傳類似下面的訊息:
{
"message": "Go Serverless v1.0! Your function executed successfully!",
"input": {
"resource": "/hello",
"path": "/hello",
"httpMethod": "GET",
"headers": {
"Accept": "*/*",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "TW",
(以下略)
表示我們的 REST API 已經可以正常運作囉!但目前只有 /hello
這個 endpoint 是正常的,因為其他 6 個 endpoint 需要存取資料庫,將會在下一篇中實作。
你應該會發現,第一次送的 request 回傳時間會特別久,要等約莫 10 秒,這就是 serverless 界傳說中的 cold start time,也就是當 lambda function 被閒置一段時間後再啟動就會發生的現象。這算是 serverless 天生的缺陷,至於該如何處理,只要 google “cold start time” 就可以找到非常多資訊,這邊就不多討論了。
至此,我們的 serverless project 目錄檔案結構應該會長這樣:
web-api/
├── config
│ ├── dev
│ │ ├── db.json
│ │ ├── env.json
│ │ └── vpc.json
│ ├── local
│ │ ├── db.json
│ │ ├── env.json
│ │ └── vpc.json
│ └── prod
│ ├── db.json
│ ├── env.json
│ └── vpc.json
├── handler.js
├── node_modules
│ └── (略)
├── package-lock.json
├── package.json
└── serverless.yml
小結
本篇完成了 Serverless Framework 專案的初始化及 serverless.yml
的撰寫,並且順利將資源佈署至 AWS ,等於已經將骨架建立起來了,最後我們只要撰寫 application 層的程式碼就可以將 REST API 跑起來囉!
上一篇:在 AWS 以 Aurora Serverless 搭建無伺服器 Web 應用程式 part3 — 設定 IAM Credentials
下一篇:在 AWS 以 Aurora Serverless 搭建無伺服器 Web 應用程式 part5 — 實作 REST API 及測試