在 AWS Lambda 中使用 SQLite 資料庫

程式技術
在 AWS Lambda 中使用 SQLite 資料庫

前陣子讀到一篇很有意思的文章。文章大意是說,你完全可以考慮在個人網站上使用 SQLite 當做資料庫。其實 SQLite 官方網站有提到,對於中小型網站來說,以 SQLite 當做資料庫絕對是綽綽有餘的。這讓我不禁開始思考,我那個使用 Laravel 開發的部落格網站是不是也可以使用 SQLite 當做資料庫 (換換病發作)。

The 100K hits/day figure is a conservative estimate, not a hard upper bound. SQLite has been demonstrated to work with 10 times that amount of traffic.

每天 10 萬次請求的流量是保守估計,並不是一個硬上限。SQLite 已經被證明可以處理每天 100 萬次請求的流量,是保守估計的 10 倍。

既然官網都這麼說了,我決定試試看將自己的部落格網站資料庫改為使用 SQLite。

什麼是 SQLite?

SQLite 是一個輕量級的資料庫引擎,最有趣的特點之一就是它不需要像 MySQL 或是 PostgreSQL 那樣去啟動一個伺服器來處理對資料庫的查詢。

不需要啟動伺服器?那麼 SQLite 要怎麼處理查詢?又會把資料儲存在哪裡呢?

這就要說到 SQLite 另外一個有趣的特點。它會將所有的資料,包含資料表欄位等資訊,都儲存在單一個檔案裡面。沒錯,就是只有一個檔案。SQLite 實際上是一個由 C 語言開發的函式庫,只要程式語言本身有包含該函式庫,就能直接透過該函式庫對 SQLite 的檔案執行常見的資料庫查詢操作,而且也支援資料庫的 ACID、Isolation Level 與 Lock 等功能。

SQLite 跟 PHP 其實交往很久了

剛剛提到我的部落格網站是使用 Laravel 開發的,那麼 Laravel 支援 SQLite 嗎?

答案絕對是肯定的,SQLite 其實是一個非常流行的函式庫,很多熱門語言例如 Python、Java、Ruby、C# … 等,都內建支援 SQLite。

SQLite 是世界上最多人使用的資料庫。因為體積小與資源占用少的關係,SQLite 常常被使用在各種設備中,例如手機、電視、遊戲機、相機、手錶、汽車 … 等,可以說是無處不在。

而 PHP 在 5 之後就開始支援 SQLite,所以 Laravel 當然也支援了很久,在最新版本的 Laravel 中,預設使用的資料庫引擎就是 SQLite。在預設的 .env.example 檔案中,可以看到資料庫連線預設使用 sqlite

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

根據這個設定,當你執行 Migration 時,Laravel 會在 database/ 資料夾底下生成一個 database.sqlite 檔案,並在裡面建立所有的資料表。之後就能夠透過 Laravel 的 Eloquent ORM 對資料進行操作,使用上與 MySQL 或是 PostgreSQL 幾乎沒有差別。

看得出來在 Laravel 中將資料庫改為使用 SQLite 並不是什麼新鮮事。但是有一個問題,因為我的部落格網站是部署在 AWS Lambda 上面的…

So… You Know… Lambda 本身不提供永久性的儲存空間

那麼我的 SQLite 檔案應該放在哪裡?怎麼做才能在 Lambda 上面使用 SQLite 呢?接下來就來簡單分享一下我的做法吧。

在 AWS Lambda 中使用 SQLite

因為我的部落格網站是部署在 AWS Lambda 上面,考量到 Lambda 本身不具備永久儲存的功能,若要使用 SQLite 資料庫,就必須進行額外的配置。有一個解決方法是將 EFS (Elastic File System) 掛載至 Lambda 執行環境,並將 SQLite 檔案放置於 EFS 上。透過這種方式,就可以讓 Lambda 擁有一個可永久儲存檔案的空間。

EFS 是 AWS 提供的一種基於 NFS (Network File System) 的雲端儲存服務,具備空間彈性和可加密等優點,並且可以同時掛載在多個目標上。所以如果需要給多個目標共享檔案或者是需要很大的儲存空間時,就可以考慮使用 EFS。

在 2017 年,EFS 宣布支援 NFSv4 的 Lock Upgrading 與 Downgrading 等功能,所以 SQLite 可以在 EFS 上面正常使用。

想要掛載 EFS,就必須要把 Lambda 放在 VPC 底下,並且幫 EFS 建立 Mount Target 與 Access Point。簡單的架構圖如下。

2024_12_25_11_44_04_a0c1ca88d657.png

Mount Target 可以在 VPC 中提供一個 NFSv4 終端節點的 IP 位址,作為 Lambda 掛載 EFS 的連接點。而 Access Point 則是作為 EFS 的存取入口,可以讓應用程式透過預先設定好的 UID 與 GID 來存取 EFS 上的檔案。

這邊補充一下,將 Lambda 放在 VPC 底下有許多缺點。除了 Lambda 訪問公網需要透過 NAT 轉發之外,因為 VPC 底下的 IP 有限,所以 Lambda 的水平擴展能力也會受限。但是如果想掛載 EFS,就一定要將 Lambda 放在 VPC 底下。

透過 Terraform 建立 EFS

掛載 EFS 需要的 VPC 我使用現有的,這樣我只需要新增 EFS 與修改現有 Lambda 的設定即可。首先在 Terraform 中使用 Data Source 取得 VPC 與 Private Subnet 的詳細資料,方便待會給其他資源使用。

data "aws_vpc" "main" {
  id = "現有的 VPC ID"
}

data "aws_subnet" "private" {
  id = "VPC 底下私有子網域的 ID"
}

接下來建立 EFS、Mount Target、Access Point 與其他相關資源。Mount Target 我會放在 Private Subnet 中。

# 設定要給 Mount Target 使用的 Security Group
# 只允許 VPC 內的 IP 進行訪問
resource "aws_security_group" "allow_internal_access_to_efs" {
  name   = "Allow internal access to EFS"
  vpc_id = data.aws_vpc.main.id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [data.aws_vpc.main.cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# 建立 EFS 檔案系統
resource "aws_efs_file_system" "for_lambda" {
  performance_mode = "generalPurpose"
  encrypted        = true

  tags = {
    Name = "efs_for_lambda"
  }
}

# 設定 EFS 備份策略
resource "aws_efs_backup_policy" "policy" {
  file_system_id = aws_efs_file_system.for_lambda.id

  backup_policy {
    status = "ENABLED"
  }
}

# 在 Private Subnet 底下建立 EFS Mount Target
# 並套用剛剛建立的 Security Group
resource "aws_efs_mount_target" "for_lambda" {
  file_system_id  = aws_efs_file_system.for_lambda.id
  subnet_id       = data.aws_subnet.private.id
  security_groups = [aws_security_group.allow_internal_access_to_efs.id]
}

# 建立 EFS Access Point
resource "aws_efs_access_point" "for_lambda" {
  file_system_id = aws_efs_file_system.for_lambda.id

  # 設定 EFS Access Point 在 EFS 中的掛載路徑
  # 如果 Lambda 嘗試透過該存取點存取 EFS,則會在 EFS 中建立一個 /lambda 的資料夾
  root_directory {
    path = "/lambda"

    creation_info {
      owner_gid   = 1000
      owner_uid   = 1000
      permissions = "755"
    }
  }

  # 設定 EFS Access Point 的 POSIX 使用者 UID 與 GID
  # Lambda 在存取 EFS 上的檔案時會以這個使用者身份進行存取
  posix_user {
    gid = 1000
    uid = 1000
  }
}

EFS 建立好之後,就可以將 Lambda 放入 VPC 並掛載 EFS。

# 用來部署 Laravel 的 Lambda Function
resource "aws_lambda_function" "web" {
  # ...

  # 將 Lambda 放入 VPC 底下
  vpc_config {
    subnet_ids         = var.subnet_ids
    security_group_ids = var.security_group_ids
  }

  # 透過 Access Point 在 Lambda 上掛載 EFS
  file_system_config {
    arn              = var.access_point_arn
    # 路徑一定要是 /mnt 開頭
    local_mount_path = "/mnt/efs"
  }
}

使用 EC2 將 SQLite 檔案放入 EFS

此時 EFS 中還是空空如也的狀態,我們需要把 SQLite 檔案上傳到 EFS 中。要上傳檔案到 EFS,最簡單也最直接的方式,就是開一台 EC2 去掛載 EFS,並透過 SFTP 的方式將檔案上傳到 EFS 中。

首先在同一個 VPC 底下開好一台可以使用 SSH 遠端連線的 EC2。這裡 EC2 的 OS 我使用 Amazon Linux。連線進去之後,我們需要先下載 AWS 官方的 EFS 工具。

sudo yum install -y amazon-efs-utils

然後在跟目錄底下新建一個 /efs 資料夾,做為 EFS 的掛載點。

sudo mkdir /efs

使用 EFS 工具將 EFS 掛載到剛剛建立的 /efs 資料夾。

EC2 在 AWS 上不需要設定任何 IAM 權限就能掛載 EFS。

sudo mount -t efs "<FILE-SYSTEM-ID>" /efs

# 進入 EFS 看看吧
cd /efs

這裡有幾點需要特別說明。

  • 如果 Lambda 還沒有對 EFS 進行存取的話,那麼 EFS 底下就不會有 /lambda 這個資料夾。你可以先訪問一次部落格網站,藉由觸發 Lambda 的執行來自動生成這個資料夾,或是自己手動建立資料夾也可以。
  • Access Point 是透過 UID 與 GID 來確認是否擁有對檔案操作的權限。剛剛之所以在 Access Point 將 UID 與 GID 均設定為 1000,是因為 Amazon Linux 系統預設用戶 ec2-user 的 UID 與 GID 也是 1000。所以用戶 ec2-user 上傳的檔案,其權限會跟 Lambda 使用的權限吻合,讓 Lambda 擁有對檔案操作的權限。

透過 SFTP 指令將 SQLite 檔案上傳到 EC2 中。

sftp> put database.sqlite

將 SQLite 檔案移動到 EFS 底下,讓 Lambda 可以對其進行存取。

mkdir /efs/lambda/db

mv database.sqlite /efs/lambda/db/

接下來只要在 Lambda 的環境變數中設定資料庫連線資訊就完成了 。

DB_CONNECTION=sqlite
DB_DATABASE=/mnt/efs/db/database.sqlite

此時訪問部落格網站看看有沒有問題,如果沒有問題,就代表成功的讓 Lambda 上的 Laravel 使用 SQLite 當做資料庫了。

如果想要定期備份資料庫避免意外,可以開啟 EFS 的備份功能。EFS 會使用 AWS Backup 服務來進行備份。

需要注意的是,從備份點還原 EFS 的時候,Backup 不會覆蓋掉 EFS 上原本的檔案,而是會在 EFS 中新開一個資料夾存放備份點的檔案。

Lambda 使用 EFS 的成本

EFS 的計費方式相當彈性,僅針對實際使用的儲存空間和 I/O 的使用量進行計費,而 Access Point 本身是不需要任何費用的。因此,對於檔案較小、存取流量也小的個人網站,EFS 的費用會非常低廉,甚至可以控制在每個月一美元以下。所以 …

那個 RDS 我們不要了。(這句話是開玩笑的)

參考資料

sharkHead
written by
sharkHead

持續努力中的後端打工仔,在下班後喜歡研究各種不同的技術。稍微擅長 PHP,並偶爾涉獵前端開發。個性就像動態語言般隨興,但渴望做事能像囉嗦的靜態語言那樣嚴謹。

0 則留言
新增留言
編輯留言