flyc
La vie est drôle
給前端知其然不知其所以然的
FlyC
Introduction
我們需要在不大翻新商業邏輯的前提下讓原本是 CSR 的 SPA 專案變成 SSR 進而達成 SEO 的提升。 在這個前提下現在我們是透過 cloudfront 將請求傳給 lambda 後判斷使用者是不是機器人, 如果是的話添加一個 header 傳給公司自己的機器去執行 prerender ,最後回傳給使用者。 目前這個架構雖然有 SEO ,但對於要添加客製化 headers 的情況下, 由於 prerender 限制了 cloudfront 的 cache policy 為 legacy cache settings, 所以在 behavior 的部分沒辦法使用目前現行的 cache policy and origin request policy, 再加上如果前端要 support compression 的話如果不透過 cache policy and origin request policy 就要自己訂製 content-encoding, 在這種前提下 Infra 團隊很容易在修改 headers 的時候出錯, 除此之外,由於 prerender 是架設在實體機器上,在 loading 上升時其擴展相當不易, 綜合以上,希望我們前端提供一個 serverless, 在可以有 SSR 的同時,也能把 prerender 移除, 這樣日後 security team 要新增 headers 的時候可以大幅度減少 Infra 出錯的機會, 而 support compression 的部分甚至可以交給 aws 去處理不用自己做, prerender 所造成的 loading 上升和其擴展不易的問題也可以透過 serverless 解決。 但也因為 origin 不是 s3 了,在沒有了 cdn 快取的情況下需要壓力測試, 同時也得注意 lambda 的 code 容量不能太大,所以資源檔要放在原本的 bucket 這樣
來劃個重點吧!
我們需要在不大翻新商業邏輯的前提下讓原本是 CSR 的 SPA 專案變成 SSR 進而達成 SEO 的提升。 在這個前提下現在我們是透過 cloudfront 將請求傳給 lambda 後判斷使用者是不是機器人, 如果是的話添加一個 header 傳給公司自己的機器去執行 prerender ,最後回傳給使用者。 目前這個架構雖然有 SEO ,但對於要添加客製化 headers 的情況下, 由於 prerender 限制了 cloudfront 的 cache policy 為 legacy cache settings, 所以在 behavior 的部分沒辦法使用目前現行的 cache policy and origin request policy, 再加上如果前端要 support compression 的話如果不透過 cache policy and origin request policy 就要自己訂製 content-encoding, 在這種前提下 Infra 團隊很容易在修改 headers 的時候出錯, 除此之外,由於 prerender 是架設在實體機器上,在 loading 上升時其擴展相當不易, 綜合以上,希望我們前端提供一個 serverless, 在可以有 SSR 的同時,也能把 prerender 移除, 這樣日後 security team 要新增 headers 的時候可以大幅度減少 Infra 出錯的機會, 而 support compression 的部分甚至可以交給 aws 去處理不用自己做, prerender 所造成的 loading 上升和其擴展不易的問題也可以透過 serverless 解決。 但也因為 origin 不是 s3 了,在沒有了 cdn 快取的情況下需要壓力測試, 同時也得注意 lambda 的 code 容量不能太大,所以資源檔要放在原本的 bucket 這樣
至少比剛剛好一點了
總而言之言而總之,在實際了解各個名詞解釋之前,
前端聽起來要做的事情是這樣的:
還是很難懂,總之慢慢來吧
在這個有 ChatGPT 的年代我們無所畏懼
大家都看得懂的 TL;DR
AWS
雅馬遜公司提供的網路服務們
Serverless
希望讓開發人員專心在功能、
不用擔心伺服器的「概念」
Lambda
會依照設定好的條件,
做設定好的事情
S3
一個大雲端空間。
Cloudfront
就是 CDN
CDN
全世界都是我的發貨地,
快還要更快。
Amazon Web Services
雅馬遜 網路 服務們
Hi
註冊完之後,秉持著免費仔的精神
第一步我們先創建預算告警,
避免在免費試用階段被收費了還沒注意到
2. 點選側邊欄的 Budget
3. 點選右側的 Create budget
免費仔 4. 選 template 和 zero spend budget
5. 取個名字,寫個描述就可以送出了
如果有希望多個 email 收到信的話可以在 email recipients 填寫
設定好後,如果有超過設定的金額,像是不能當免費仔了
在設定的 email 就會收到信,可以去看看哪裡出了問題這樣
在方前的 budget and cost 頁面下可以看到已被收費的服務,
可以根據這個清單看到哪個服務花了多少錢
❌ 伺服器 沒有
✅ 伺服器 少
Serverless 是一個 概念 ,
代表開發者無需擔心伺服器方面的事情,
不用擔心附載平衡時的擴展,
也不用擔心是不是租太大台空間花太多錢,
可以在幾乎 0 調整的情況下就可以部署自己的程式碼,
讓開發人員可以專心開發功能。
通常 serverless 也包含了「用多少付多少」的概念,
一般來說啟動一台機器,
就算沒有處理任何事情也還是有租金。
而 serverless 的概念是「有處理事情的時候才收錢」,
對於沒有那麼頻繁呼叫的微型服務來說相當划算。
可是,雖然說 serverless 是一個 概念 ,
那 這個網站 又是什麼啊 ?
是一個 開源工具 ,
用於管理開發者寫的 serverless 服務,為了避免混淆,
一般會稱這個開源工具為 Serverless framework
不過,
serverless 聽起來已經很簡單了,為什麼還要這個工具 ?
Hello 👋
雖然如 AWS 或 Azure 等各大網路服務供應商都有提供/實作serverless 的服務,
但如果要把自己的 code 放到這些平台上,依照平台差異,還是要經過一些不同的步驟。
只要設定好各大平台的權限,甚至可以直接管理各大網路平台的 serverless 服務
serverless framework 意在連這些步驟都可以整整齊齊,
可以僅透過一個設定檔就全部搞定。
然後
我們
沒有要用 Serverless framework
。
嚴格來說還是會用到他的 npm packages, 但不會用到介面。
會額外介紹 serverless framework 是因為其名字實在太容易與 serverless 這個概念混淆了,
同時也很容易與 AWS Lambda 這個服務攪和在一起,
不提不行。
那麼,趁著剛介紹完 serverless 和 serverless framework,
就來介紹與之相關的 AWS 服務: Lambda 吧
λ
在這個有 ChatGPT 的年代我們無所畏懼
比起 Server, Lambda 更像是一個 function:
「呼叫,傳參數,做點事情,回結果」
比起「這個 path 被呼叫了,我要回給他一個 view」,
用「收到了 string, 我要整理一下後回給他一個 string」會更好理解
同時,Lambda 也是 AWS 實作了 serverless 概念的一個服務,
所以它有 serverless 用多少付多少的優點,
同時還有 serverless 部署非常簡單的特色
假設我們要做一個「計算機加法功能」的 function 好了:
Hello 👋
1. 透過 主控台 上的搜尋導航到 lambda 頁面, 點選 create function
2. 輸入名字後直接按 Create
3. 回到 Lambda 列表後選擇剛剛創建的 lambda, 寫 code,
寫完後按 Deploy
export const handler = async (params) => {
const { num1, num2 } = params
return num1 + num2
};
程式碼的部分
4. 好了,你成功了 🎉
是的,真的就這麼簡單。
不過寫完了總要測試吧? 介面上剛好有一個測試按鈕...
測試也成功了 🎉🎉🎉
嚴格來說是 code 沒有 error,
至於結果對不對就要自己看了 (1 + 2 = 3 的部分)
我有問題 ✋
如果我的 code 很複雜,總不能都靠這個 ide 寫吧?
只要提供一個有 export 出去叫 handler 的 function 的 index.js
再將 node_modules 和 package.json 等等一同壓縮成 xxx.zip
然後點選 upload from .zip file
就完成了
# bash
mkdir hello-server
cd hello-server
npm init # press enter until ready
npm install express
touch index.js
// index.js
const express = require('express')
const app = express()
app.get('/', (_, res) => res.send('home'))
app.get('*', (_, res) => res.send('Oooops, 404'))
app.listen(1234)
接著是 index.js 的部分
到這個步驟,已經可以透過指令把 server 喚醒了
# bash
node index.js
# visit http://localhost:1234
所以接下來只要壓縮後上傳到 Lambda 就好了對吧!?
等等,好像少了點什麼...
怎麼辦? 手動封裝 express 的 app 實例聽起來很不實際...
這個時候,
我們需要用到 Serverless framework
Hello again 👋
也就是剛剛提到的 npm package, 直接上 code 吧
# bash
npm install serverless-http # 要注意後面有 -http
接著修改 index.js
// index.js
const express = require('express')
const serverless = require('serverless-http') // 引入剛剛安裝的 serverless-http
const app = express()
app.get('/', (_, res) => res.send('home'))
app.get('*', (_, res) => res.send('Oooops, 404'))
// app.listen(1234) // 將 app.listen 的部分移除
// 透過把 app 傳入 serverless 創建 handler function
const handler = serverless(app)
module.exports = { handler } // 將 handler function export 出去
就這樣,完成了
把打包的部分也處理一下吧
# bash
zip -r my-server-package.zip \
index.js \
node_modules \
package.json \
package-lock.json
可以注意到除了 index.js 以外,連同 node_modules 和 package.json 、 package-lock.json 都要一起壓進去
node_modules 還好說,
package 相關也要一起的原因是如同在本地執行一般,lambda 在執行時也會做像是根據 package.json 裡的 type 是不是 module 之類的部分做不同的處理等等,
因此也需要將他們打包進去
那麼,上傳吧?
然後直接測試一下
嗯,完美 🎉 🎉 🎉
成功了是成功了,不過這樣還是很沒有一個 server 的感覺
我想要可以 訪問 我寫的路由,
我想要看到像是在本地執行 node index.js 時會有的樣子,
我想要在瀏覽器上看到結果
為了達成這個結果,
基於 lambda 只是一個 function 的原則,
我們現在要做的事情是
在 某個東西 收到請求的時候,去執行我們寫好的 function,
然後把 function 的執行結果回傳給 某個東西 去渲染畫面
那個某個東西 就是 AWS API Gateway
Hello 👋
基於這次沒有要詳細介紹 AWS Api Gateway,
這邊就直接介紹該如何和 Lambda 串起來的部分
1. 造訪 Api_Gateway 的頁面,點選 CreateApi
2. API TYPE 選 HTTP API
3. 在 integrations 的部分點 Add integration,
然後在下拉選單選 Lambda 後,
在下面跳出來的 lambda function 區塊選擇方才創建的 lambda
可以從最後面的 function name 做判斷
4. 輸入名字後選 Next, 接著在 Resource path 填上 $default,
填寫正確的話會看到左邊的 Method 變成 disabled
5. 下一頁的 Configure stages 維持 $default, 直接按下一步
6. 最後出來的樣子會長這樣
7. 創建完成後,點選左邊的 api 名稱,
再點選 default endpoint
就可以看到我們剛剛設定的路由已經展示在瀏覽器上囉 🎉
// index.js
const express = require('express')
const serverless = require('serverless-http') // 引入剛剛安裝的 serverless-http
const app = express()
app.get('/', (_, res) => res.send('home'))
app.get('*', (_, res) => res.send('Oooops, 404'))
// app.listen(1234) // 將 app.listen 的部分移除
// 透過把 app 傳入 serverless 創建 handler function
const handler = serverless(app)
module.exports = { handler } // 將 handler function export 出去
附上方才 index.js 的 code 用來對比一下路由結構
順帶一提, 這個時候回到 lambda 頁面的話會看到
上面的 Function overview 多了一個 API Gateway 作為 Trigger
AWS Lambda 基本上就介紹到這邊了,
Lambad 就是個 function, 給他一個 input,他回一個 output
也已經可以讓 Node server 順利地在 lambda 上運行 🎉
至於部署的部分也遵守 serverless 的概念: 只要上傳上去,就好了
Bye
See you
Until next time
Simple Storage Service
在這個有 ChatGPT 的年代我們無所畏懼
大家常常提到的
「就放 S3 啊」
「放在 S3 上就好啦」
「直接從 S3 上拿」
「前端不能直接從 S3 拿嗎?」不能
的這個 S3
對前端來說,就是個 雲端空間
只要記得這點,對前端來說,就很夠了
Hi
在開始介紹怎麼使用前,我們先看看 s3 的 icon
毫無反應,就是個桶子
2. 拉到最下面按 創建, 完成 🎉
點進去創造的 bucket 可以看到基本上就是雲端空間 的操作介面
上傳、創資料夾、刪除、或是整個清空之類的,全都在這裡
S3 的介紹,就這樣囉
蛤
話雖如此
S3 當然並不只是一個雲端空間,他還有像是 GitHubPages 一樣作為靜態網頁的託管空間、或是權限管理的功能,
基於等等在 AWS Cloudfront 的時候就會有實際的場景會用到,
就等到後面再來一起介紹吧
雲 前面
在這個有 ChatGPT 的年代我們無所畏懼
AWS Cloudfront
就是
CDN
Content Delivery Network / Content Distribution Network
至於什麼是 CDN, 就想像成...
以 西遊記 取經來說,基於地理上 的限制,
如果要抄寫位於地球另一端的經文,
勢必,會比抄寫已經存在於身邊的經文慢。
而 CDN 做的事情,
就是把西邊的經文抄寫一份放在花果山
這樣唐三藏就可以不用大老遠跑到西邊去了,
也不用擔心在路上會遭遇什麼不可預期的意外,
畢竟,就在旁邊嘛
超方便的
唐三藏唯一需要擔心的就是經文版本不一的問題。
不過不用擔心, AWS Cloudfront 已經處理好這件事情了。
西天取經CDN 的部分先到這邊,
直接來看看 AWS Cloudfront 要怎麼設定吧
請注意
會在免費方案不小心花到錢的部分就在這裡
由於 cloudfront 的功能比較複雜些,
秉持著知其然不知其所以然的精神
這邊就直接用「幫剛剛的 lambda 服務加 cdn」來做範例
其他細節功能的部分後續在快速帶過吧
Hello
這是一個極其簡易 的概念圖,
但對於了解在創建 cloudfront 的流程會有幫助
請求
Cloudfront
檢查行為(behavior)
源(Origin)
源(Origin)
資源
資源
每一個 Cloudfront 都至少會有一個 源(Origin),
同時,也會有一個 預設的行為 behavior
確保請求進來的時候起碼有東西可以給他
行為(behavior) 用於判斷當前進來的請求要做出什麼對應的行為,
而這個行為會走去一個對應的 源(origin)
源(origin) 可以是一個 s3 的 bucket, 也可以是一個 API Gateway,
還有其他可選項目等等
...我們舉些例子吧
目前花果山只有放一本西經,
還有一本豬八戒的黃色書刊,但豬八戒為了避免被師傅發現,
所以八戒拜託沙悟淨幫他把關:只可以把書給八戒。
而悟空只要遇到他不懂的事情,就會去問沙悟淨。
唐三藏過來說他要抄寫西經的第一頁,悟空去花果山拿第一頁給他;
唐三藏過來說他要抄寫西經的第二頁,悟空去花果山拿第二頁給他;
唐三藏過來說他要八戒的黃色書刊,悟空是和尚不知道那是什麼,
他去問沙悟淨,沙悟淨發現「不是」八戒來拿,回說這裡沒那種東西;
八戒過來說他要他的黃色書刊,悟空是和尚不知道那是什麼,
他去問沙悟淨,沙悟淨發現「是」八戒來拿,把書拿給八戒
花果山-S3 悟空-Cloudfront
西經-bucket(作為源) 沙悟淨-Lambda(作為源)
唐三藏/豬八戒-Client
故事先到這裡,我們還是直接來試試看吧
1. 先到 cloudfront 的主控台,點選 CreateDistribution,
接著在 Origin domain 的部分選擇方才創建的 API Gateway
2. 接著,在 Origin 區塊的 Name 的部分填上 Origin 的名字
注意! 這裡是 Origin 的名字喔
2. 此時的 behavior 是預設的 Default(*) ,所以改不了
稍微往下些可以看到
和 Cache 有關的設定,
方才選 api gateway 的話可以看到有些推薦的選項
這邊直接保持就可以了
3. 往下捲動到 Web Application Firewall(WAF) 的部分
然後
最後,捲動到最下面,直接創建就可以了。
創建後可以看到這個 CDN 正在 部署中(Deploying)
也就是正在散播到全世界的意思
當部署結束後,就可以造訪旁邊寫的 doamin
就可以看到和剛剛 api gateway 一樣的結果了 🎉 🎉
啊這和剛剛的 API Gateway 有差嗎?
還真的有
而且還是可觀測的那種差異
⬆這個是 cdn
這個是 api gateway ➡
CDN 的會有快取機制,
不論快取命中與否,
在瀏覽器的 Response Header 裡
都會有 X-Cache 這個標頭。
而在 api gateway 的則不會有
到這個步驟,
由於我們只創立了最基本的預設行為 default behavior
所以悟空只會什麼都不懂地去問沙悟淨
而我們的沙悟淨現在來者不拒,都會回傳東西回去。
但如果我們什麼東西都交給沙悟淨處理的話...
Lambda 會累死
所以得要教悟空更多行為(behavior),
如果可以的話悟空自己去拿就可以了。
這就是區分 靜態資源 與 動態資源 的部分
我們已經達成了把 Lambda 綁上 CDN 的階段性目標,
cloudfront 的介紹其實可以先就此打住了。
在各種名詞解釋完後,
對於一開始的需求目標其實已經變得相當明確。
剩下的部分,就在最後的「串在一起」階段完成吧
byebye
總而言之言而總之,先放點東西在 S3 上吧!
# bash
mkdir s3-files
cd s3-files
echo 'const a = 123' > main.js
echo 'body { color: red; }' > style.css
連同資料夾一起整個丟上去吧
緊接著,因為現在 cloudfront 的預設行為已經是 lambda,
我們接著要來處理 判斷靜態資源 這塊。
進到方才創建的 cloudfront 後,先建立一個 origin
在 Origin domain 的地方選擇剛剛上傳檔案的 S3,
取個好辨認的名字後直接創建
進到方才創建的 cloudfront 後,我們來創一個 behavior
在 path pattern 的部分寫上 /s3-files/*
也就是希望符合這個 pattern 的這些 behavior,
都導向設定在 Origin and origin groups 裡剛才創建的 s3 origin
填寫完這兩格
先直接保存吧
這樣就完成了嗎?
不,還差一點
一樣等 cloudfront deployed 完成後去造訪 domain,
除了原本的路由正常以外,在後面加上 /s3-files/main.js
看能不能拿到資料
可惜,很明顯的,權限出了問題
不過透過刻意讓 path 沒有符合 pattern,
可以藉此確認的確是 s3 那邊的 origin 設定還沒完成
簡單說明,就是 cloudfront 沒有權限可以去 S3 取資料。
雖然在 origin 的清單上可以有 list 的權限,
但並沒有 read 的權限。
所以需要去 S3 那邊單獨開權限給指定的 cloudfront
在去 s3 之前,先把 cloudfront 的 ARN 複製起來吧
接著到我們的 s3 bucket, 進到 permission
往下拉到 Bucket policy
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "<貼上這個 bucket 的 ARN, 在上面>/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "<貼上剛剛複製的 cloudfront ARN>"
}
}
}]
}
進到編輯後,先別管內容,
將上面這段 code 貼進去,
然後把該貼上的 ARN 都貼一下吧
注意, Resource 後面有 /*
保存過後,現在 s3 已經創了一個權限,
所以現在要回到 cloudfront 去設定方才的 origin,
去使用這個權限。
在那之前
我們得透過設定一個 OAC (Origin Access Control) 來達成這件事。
具體順序是:
添加 s3 policy -> 創建 oac -> 由 origin 選擇該 oac -> 成功 🎉
其中添加 s3 policy 和 創建 oac 的部分可以分別處理,
只要在最後階段都有完成即可
在 cloudfront 頁面左側欄選擇 Security -> Origin access
創一個新的 oac, 填上 Name 後,
在 Origin type 的地方選 S3
接著,到 cloudfront 進入詳情頁面後,選擇 Origin,
然後編輯先前選了 S3 的那個 Origin,
將他的 Origin access 調整成 Origin access control settings
並在下面選擇方才創建的 oac, 按保存
然後
🎉 恭喜你成功了 🎉
所有的靜態資源都有回來 🎉 原來的 path 也沒問題
然後當然,靜態資源也有被 cdn 緩存起來
總算,真的是總算。
經過重重的名詞解釋和介紹,
我們總算完成了:
將 server 部署在 lambda
將靜態資源放在 s3 並有 cloudfront 做 cdn
並且可以透過瀏覽器訪問
這 3 項光描述起來就頭昏的過程,好耶 🎉 🎉 🎉
剩下的
就是農 code 了
該怎麼讓 NodeServer 可以 SSR
哪些檔案該放 s3 哪些該放 lambda
放在 s3 的檔案資料夾要怎麼設計
cloudfront 對此又要添加哪些 behavior
等等等等等
但起碼
整體流程已經沒問題了 🎉
好像還有?
真的還有啊 ?
是的。
根據 Request Header 的 Accept-Encoding 給 Cloudfront 通知,
透過 Response Header 的 Content-Encoding 回使用的壓縮方式
設定的位置是在 Cloudfront > Behavior > Cache key and origin request
如果選擇了推薦選項的話預設是開啟的,
或是可以手動新增一個 cache policy,
裡面可以選擇要不要開啟。
Q: 我想要清掉 cdn cache 的話該怎麼做?
在 Cloudfront -> Invalidations -> Create invalidation
裡面可以輸入想要清掉 cache 的 path, 或是使用 /* 全清也可以
Q: 權限管理?
AWS 有一個叫 IAM 的服務, 裡面可以設定顆粒度非常小的權限控制。
小至使用者只可以看某單一 bucket 或只能修改某單一 lambda 的這種程度
可以透過創建擁有不同權限的金鑰來管理每個使用者的權限。
Q: 有 CLI(Command Line Interface) 嗎?
一定是有的。
可以參考 AWS CLI ,文件相當詳細
Q: 有 SDK(Software Development Kit) 嗎?
感謝 🎉
參考資料
https://aws.plainenglish.io/deploying-a-node-express-api-on-aws-lambda-c9730a17f932
https://blog.awsfundamentals.com/lambda-limitations
https://blog.awsfundamentals.com/lambda-limitations
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html
https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html
https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/nodejs-handler.html
https://gist.github.com/RakaDoank/05da35887051a8f999aabc23d30194e3
https://repost.aws/knowledge-center/cloudfront-access-to-amazon-s3
https://repost.aws/knowledge-center/cloudfront-access-to-amazon-s3
https://stackoverflow.com/questions/31329958/how-to-pass-a-querystring-or-route-parameter-to-aws-lambda-from-amazon-api-gatew
https://stackoverflow.com/questions/62557216/aws-api-gateway-lambda-favicon-issue~~
https://www.serverless.com/blog/serverless-express-rest-api
https://www.serverless.com/framework/docs/tutorial
By flyc