AWS STUDY NOTE

用 Bref 1.0 讓 Laravel 8 serverless 起來

Bref 1.0 釋出了,來試試看用 Laravel 8 搭配 Bref 1.0 做出一個可以存取 ElastiCache Redis 的 Laravel 網站~

Azole (小賴)

--

去年分享過「Deploy Serverless Laravel by Bref」,當時是用 Laravel 5.8 的版本。最近 Bref 釋出了 1.0,Laravel 版號也飛速地跳到了 8.16,用新版來試試看怎麼做。

因為之前已經介紹過了,所以此次不會有太多的介紹,有興趣的朋友可以參考這篇文章 Deploy Serverless Laravel by Bref,但實際操作上,建議以本篇為主。

簡單的說,Bref 就是一個可以讓你把 PHP 包成可以在 AWS Lambda 上執行的工具,如他官網所示,它提供了你需要的東西,讓你可以輕易地以 serverless 的方式部署與執行 PHP 程式。

Everything you need to easily deploy and run serverless PHP applications.

準備動作

本文會用到的 AWS 服務有 Lambda、API Gateway、ElastiCache、CloudWatch、CloudFormation,不過除了 ElastiCache 外,其他 AWS 服務都不需要我們自己建立,Bref 本身是搭配 serverless 這個工具進行 Lambda 的部署,serverless 會把需要用的服務都建立好,我們只需要關心 Bref 怎麼使用即可。想要用 CDK 來部署的也可以參考「Deploy Laravel to Lambda with Bref by AWS CDK」,不過這也是去年的舊文,不確定現在差異多少就是了。

  • AWS 帳號: 既然是要用 AWS 服務,當然就會需要有一個 AWS 帳號,沒有的朋友可以先註冊一下,需要一張信用卡跟可以接收簡訊的電話。
  • 安裝 serverless: serverless 是用 nodejs 開發的,所以安裝前也需要先裝 nodejs 跟 npm,裝好之後執行以下安裝指令。(我的環境是 node 12.19 及 npm 6.14.8。)
$ npm install -g serverless# 安裝過後可以先幫 serverless 設定一下 AWS 的 credentials,記得把 key 跟 secret 換成自己的,如果已經有用 aws configure 設定過,這一步可以跳過
$ serverless config credentials --provider aws \
--key YOUR_AWS_KEY \
--secret YOUR_AWS_SECRET
  • Laravel 8: 這需要 PHP 7.4,當然也需要先裝好 composer,裝好 PHP 7.4 與 composer 後執行以下指令建立 Laravel 專案。(PHP 7.4.11, composer 1.10.15,目前建立的 Laravel 版本是 8.16.1。)
$ composer create-project --prefer-dist laravel/laravel laravel8-bref
  • redis: Bref 為了做輕巧一點的 runtime 出來,所以移除了一些不常用的 PHP extension,例如 GD, Imagick, Redis, MongoDB 等。如果你的 PHP 專案需要用到這幾個 extension,就需要自己把它們加回來。本文中,我會透過讓 Laravel 專案去讀寫 Redis 來試驗看看怎麼引用額外的 PHP Redis extension。因此我會做一個很簡單的 Todo 功能,並且把資料存在 Redis 中,所以我會先在本機啟動 redis server 作為測試,要直接安裝或是用 docker 都可以,沒有想要測試的可以跳過。

安裝與設定 Bref

讓我們在剛剛建立出來的 Laravel 專案中安裝 Bref:

$ composer require bref/bref

註:我目前用的還是 composer 還是 1.0,我有找了一個環境:PHP 7.4.12 + composer 2.0.4,安裝 Bref 1.0 也是沒有問題的,而且 composer 2 真的快好多啊~

現在 Bref 有提供了命令,讓你可以在本地做測試,例如我們可以透過這個命令對專案進行初始化:

$ vendor/bin/bref init

這個指令執行時,會出現選項:

Web application 就是用來給網站用的,像我們現在要建置 Laravel,所以就是選 Web application,他底層會用 PHP-FPM。另外一個 Event-driven function 是可以讓我們直接處理 Lambda events。

在選擇了之後,Bref 會幫我們建立兩個檔案:index.phpserverless.yml。index.php 就是這個 Lambda 函式的入口,但是我們是 Laravel 專案,顯然這個不適用於我們,之後再來看怎麼改。

serverless.yml 則是 serverless 這個工具要用到的設定檔:

可以看到這裡用到的 layers 是 php-74-fpm,如果在 bref init 時選的是 Event-driven function,那這裡就會是 php-74 。關於 Lambda Layer,可以參考 AWS 官方文件「AWS Lambda Layers」,簡單的說,就是可以把共用的部份抽出來包成 layer,讓 Lambda function 來引用,一來是可以重用、二來是可以減少 Lambda Function 的大小,小一點到時候部署也比較快。

Bref 的重點就在於它幫我們做好了各種給 PHP 用的 runtime,並且包成了 Layer 讓我們可以輕易地使用。Lambda 原生是不支援 PHP 的,「AWS Lambda Custom Runtime for PHP: A Practical Example」這篇文章有教我們怎麼自己在 Lambda 上做出 PHP 的執行環境,但老實說,我沒有成功地做出可以跑 Laravel 的環境…

如果你不想透過 serverless 這個工具,想自己直接在 Lambda 上嘗試,那可以在 runtimes 這一頁選擇自己適用的 layer 去引用,注意有 region 上的區別,選擇你要測試/部署的區域。

測試部署

到目前為止,我們先來試一次部署看看:

$ serverless delpoy
serverless deploy

從這過程中可以看到,serverless 這工具底層其實也還是 CloudFormation,所以在部署過程中,進入 AWS CloudFormation 的控制台,可以看到有一個 CloudFormation stack 正在建立與更新中。

CloudFormation

現在讓我們打開部署完成後提供的那個 endpoints: https://yb0fmoqf2h.execute-api.ap-northeast-1.amazonaws.com/

這是 Bref 透過 serverless 這個工具幫我們建立了一個 API Gateway 的入口,在 AWS API Gateway Console 一樣可以看到被建立出來的這個入口。不過,這顯然不是我們的 Laravel 專案,沒錯,這是剛剛 bref init 時建立的那個 index.php,接下來就來研究看看怎麼改成 Laravel 專案的頁面。

Laravel 專案的設定

在開始進行 Laravel 的調整之前,如果有跟著做上一步的人,可以先用 serverless remove 來把剛剛部署的服務給移除掉,因為等一下會建立一個內容不太一樣的部署配置,我的經驗是,它會在 AWS 上重新建一個 CloudFormation stack,如果不熟悉怎麼自己手動去移除的人,可能會有點麻煩,所以建議再往下進行之前,先用現在這個 serverless.yml 去移除剛剛建立的服務。

Bref 有提供了怎麼設定 Laravel 專案的文件「Serverless Laravel applications」,現在讓我們按照文件來做做看。根據文件,Laravel 專案需要 Laravel-Bref 這個 package:

$ composer require bref/laravel-bridge

然而我在 mac 上安裝的時候,遇到了問題:

Allowed memory size of 1610612736 bytes exhausted (tried to allocate 4096 bytes) in phar:…

只好用 memory_limit=-1 再去試一次,不過我在另外找的 PHP 7.4.12 + composer 2.0.4 的環境中安裝 laravel-bridge 是沒有遇到任何問題的。如果有遇到一樣問題的方法,也可以這樣試試看,暫時先解開 memory_limit 的封印:

php -d memory_limit=-1 /usr/local/bin/composer require bref/bref bref/laravel-bridge

安裝成功後,就可以利用這個 package 來產生 Laravel 適用的 serverless.yml:

php artisan vendor:publish --tag=serverless-config# 如果專案中已經有了 serverless.yml 這個檔案,要先刪除才能成功,因為這行指令不會覆寫已有的檔案。重新建立後,例如 region 這類個人的設定,也記得要再調整一下。

我比較了一下用這個 package 產生的 serverlesss.yml 跟之前的,發現其差異在於:

  • web function 的 handler 由 index.php 改為 public/index.php
  • 為 artisan 多做了一個 Lambda function
  • exclude 的檔案夾多了一些 Laravel 專屬但不需要部署的設定

再去 brefphp/laravel-bridge 的 repo 看一下,發現它就是做了一個 BrefServiceProvider,這個 provider 把我們必須要手動做的一些設定給做掉了,包括因為在 Lambda 環境下,只有 /tmp 是可寫的,其他都是唯獨的,因此像是 view.compiled ,原本是在 storage/framework/views ,現在要改到 /tmp/storage/framework/views 去,sessionDriver 如果是設定成 file,他會改成用 cookie, cache.stores.file.path 原本是 storage/framework/cache/data ,也改到 /tmp/storage/framework/cache 了。

至於 Log,原本 Laravel 預設是 stack,其實也是寫在本地,但我們現在是在 Lambda 上,所以他會改成用 stderr ,這樣到時候 Log 就會寫到 AWS 的 CloudWatch 去。如果專案中有需要處理任何像這樣的「狀態」,例如檔案上傳、圖片上傳等,千萬不要存在本地,Lambda 是有叫用的時候,AWS 幫你啟動、部署,啟動後過一陣子(很短的一陣子)沒有被叫用,就會被回收,如果我們把使用者上傳的資料存在本地,那被回收後當然就會不見了,因此像是 Log、上傳的資料等,一定要往外放,Log 就放到 CloudWatch 去就好,上傳的資料可以放到 S3 去,session 則可以用 redis 或 DB 存起來。關於這部分,我在 2020 年的 Laravel Conf Taiwan 有分享了一場「我們與 Serverless 的距離」,有興趣的了解的朋友可以去聽聽看,分享中討論了怎麼把一個原本是單機運行的 Laravel 專案,逐步地改成可以擴展、可以 serverless。

有了 laravel-bridge 後,一切事情真的變得很簡單,安裝 package、建立 Laravel 適用的 serverless.yml、檢查是否有需要調整的地方,然後就可以 serverless deploy 啦,幾乎一鍵部署等級。部署完成後,一樣在瀏覽器打開部署結果中提供的 endpoints,就可以成功地看到我們的 Laravel 專案首頁了:

Laravel 8 on AWS Lambda

引用額外的 PHP Extension

Bref 1.0 的釋出文件上有提到,為了讓 runtime 更瘦,所以移除了一些不常用的 PHP extensions,想要用這些 extensions 的可以參照 brefphp/extra-php-extensions 這個 github repo,以下我們也會以 Redis 來做個測試。

我這邊做的試驗是讓我的 Laravel 專案可以存取 Redis,其步驟如下:

  1. 在 Laravel 專案中增加 Todo 管理功能,我是從 Vue 的範例中複製下來改的,原本範例中是存在 localstorage 中,稍微改寫一下,用 axios 從後端讀取 Todo 列表,並且在更改後回存。當然,Laravel 專案這邊也需要提供兩個 API 好用來讀、寫資料,因為這不是本文的重點,就不多贅述了。
  2. 在專案中安裝 extra-php-extensions 這個 package:
$ composer require bref/extra-php-extensions

3. 在 serverless.yml 中加入以下設定:

除了要引用 PHP redis extension layer 外,為了要讓 Lambda 可以連到 ElastiCache,必須要把這個 Lambda function 設置在 VPC 內,因此還多了 VPC 區段的設定。

(完整版的 serverless.yml 放在文末。)

...
plugins:
- ./vendor/bref/bref
# for redis extension
- ./vendor/bref/extra-php-extensions

...
functions:
web:
handler: public/index.php
description: ''
timeout: 28
layers:
- ${bref:layer.php-74-fpm}
# for redis extension
- ${bref:extra.redis-php-74}
events:
- httpApi: '*'
vpc: # for connect to ElastiCache
securityGroupIds:
- YOUR_SECURITY_GROUP_ID
subnetIds:
- YOUR_SUBNET_ID

4. 引用 PHP redis extensions

在 Laravel 專案中建立檔案夾 php/conf.d ,並且在裡面建立一個名為 redis.ini 檔案:

其內容如下:

;php/conf.d/redis.ini
; Loading the Redis extension.
extension=/opt/bref-extra/redis.so

5. 建立 ElastiCache Redis 並且修改 .env

從 AWS Console 建立一個 ElastiCache Redis,並且把 .env 中的 REDIS_HOST 修改為 AWS ElastiCache Redis 的 Endpoint:

# 修改 REDIS_HOST 為 ElastiCache 的 endppoint
REDIS_HOST=bref.xxxx.0001.apne1.cache.amazonaws.com

6. (option) 既然已經設置了 redis,所以我後來也把 .env 中的 SESSION_DRIVER 也改成用 redis。

以上動作都完成後,再重新部署一次: serverless deploy ,完成後打開網址,就成功地看到了 Todo 畫面了,而且因為有存在 ElastiCache 中,所以每次重新開啟或是換瀏覽器,資料都會在。

到 Lambda 的控制台去確認一下,果然可以看到我們的 Lambda function 引用了兩個 Layer:

Lambda Layers

看起來很順利,不過有個小問題是通常我們不止需要 PHP redis 這一個 extension,而 Lambda 有一個最多只能引用 5 個 layers 的限制,如果我們需要的 extension 超過這個限制的話,就會需要我們自己建立自己的 Lambda layer 了。

移除部署的服務

以上實驗完成後,記得移除所建置出來的服務,用 serverless deploy 部署的,可以用 serverless remove 來移除。如果有建立 ElastiCache redis 來玩的話,也記得要移掉,不然月底會收到帳單的。

結語

Bref 1.0 相較於去年 Deploy Serverless Laravel by Bref 這篇的紀錄來說,概念上沒有太大的差異,但用法上有不小的變動,整體來說是朝向了讓我們更簡便、更容易使用的方向走去,例如多了一個 laravel-bridge,不需要再自行對 Laravel 專案做太多的調整,而且也處理了本地開發的問題,在去年紀錄的時候,一旦將專案改成 Bref 可用後,本地就無法開發了。此外,也多提供了一個 vendor/bin/bref 的命令,來幫我們建立 serverless.yml,不用再自己建立了。此外,Bref 1.0 也把 runtime 瘦身了,雖然額外的 extension 需要自己引用,但這個瘦身是可以改善 Lambda function cold start 情況的。

說到 cold start,這應該是很想用 Lambda、最後卻沒用的人最主要的原因之一吧,尤其當這個 Lambda function 需要被放在 VPC 中的時候,第一次開啟的延遲感會更明顯。雖然說 AWS 在去年(2019)的時候有宣布大幅地改進了在 VPC 中的 Lambda function 的效能( Announcing improved VPC networking for AWS Lambda functions),但我自己之前的試驗,總還是會有有感的慢。不過 Bref 不知道怎麼做到的,一直以來我對他的速度都還算滿意,雖然還是感受到 cold start,但我覺得可以了,當然這還是依賴於專案本身的使用場景,效能沒有最好的答案,只有滿不滿足的問題,有效能疑慮的,不防自己多做一些測試。

第一次啟動:

Region: Tokyo

第一次啟動後,一小段時間內重新開啟頁面:

Region: Tokyo

備註:降低 Cold Start 的一個鄉野傳說作法就是定期去打自己的 lamba 程式,研究了一下,serverless 這個工具有提供,Bref 的文件上也有提到,有興趣的可以參考這裡

備註:如果對 Lambda cold start 有疑慮,或是其他原因無法使用 Lambda,但又想要用 serverless 的方式來建置專案,不防考慮 AWS ECS with Fargate,這也很好用喔。

Next

不知道大家會不會好奇,在這樣的環境下要怎麼執行 artisan 與排程工作?有興趣的可以參考「Laravel 8 + Bref 1.0 執行 artisan 與排程工作」,這篇文章紀錄了我參考文件試著執行 artisan 指令與排程工作的過程。

參考資料

serverless.yml

--

--

Azole (小賴)

As a passionate software engineer and dedicated technical instructor, I have a particular fondness for container technologies and AWS.