無伺服器架構允許開發者將基礎設施管理交由雲端供應商,從而專注於應用程式開發。本文以 AWS Lambda 為例,講解如何佈署無伺服器應用程式。首先,我們會使用 Terraform 建立 Lambda 函式、API Gateway 和 S3 等資源,並設定它們之間的互動。接著,我們會使用 Jenkins 建立 CI/CD 管道,自動化建置、測試和佈署流程。文章將分別以 Python 和 Go 語言的 Lambda 函式為例,詳細說明如何建立佈署套件,並比較多倉函式庫和單一倉函式庫策略的適用場景。最後,我們會討論如何使用 API Gateway 暴露 Lambda 函式,以及如何使用 S3 託管靜態網站資源。
Lambda 基礎的無伺服器函式佈署
在前面的章節中,我們學習瞭如何為在 Docker Swarm 和 Kubernetes 中執行的容器化應用程式撰寫 CI/CD 管道。在本章中,我們將學習如何佈署以不同架構編寫的相同應用程式。無伺服器(Serverless)是目前發展最快的架構運動,它允許開發人員透過將基礎設施管理的全部責任委託給雲端供應商,從而更快地開發可擴充套件的應用程式。然而,無伺服器架構也帶來了幾個關鍵挑戰,其中之一就是 CI/CD。
佈署根據 Lambda 的應用程式
有多個無伺服器供應商可供選擇,但為了簡化,我們將使用 AWS——具體來說,是 AWS Lambda(https://aws.amazon.com/lambda/),它是目前無伺服器領域中最知名和最成熟的解決方案。AWS Lambda 在 AWS re:Invent 2014 期間推出,是第一個無伺服器計算的實作。使用者可以將程式碼上傳到 Lambda,Lambda 然後代表使用者執行操作和擴充套件活動。
該服務遵循事件驅動架構,這意味著佈署在 Lambda 中的程式碼可以回應諸如來自 Amazon API Gateway(https://aws.amazon.com/api-gateway/)等服務的 HTTP 請求等事件而被觸發。
在進一步瞭解如何為無伺服器應用程式建立 CI/CD 管道之前,我們將檢視相應的架構。圖 12.1 顯示了無伺服器服務(如 Amazon API Gateway、Amazon DynamoDB、Amazon S3 和 AWS Lambda)如何融入應用程式架構中。
圖 12.1 根據無伺服器架構的 Watchlist 應用程式。每個 Lambda 函式負責一個單一的 API 端點。這些端點透過 API Gateway 管理,並由託管在 S3 儲存桶上的 Marketplace 服務使用。
圖表翻譯: 圖 12.1 展示了一個根據無伺服器架構的 Watchlist 應用程式。該架構中,Amazon API Gateway、AWS Lambda、Amazon DynamoDB 和 Amazon S3 等無伺服器服務協同工作,以實作應用程式的功能。每個 Lambda 函式對應一個特定的 API 端點,這些端點由 API Gateway 管理,並被託管在 S3 上的 Marketplace 服務呼叫。
AWS Lambda 增強了微服務開發。也就是說,每個端點都會觸發一個不同的 Lambda 函式。這些函式彼此獨立,可以用不同的語言編寫。因此,這導致了在函式級別進行擴充套件、更容易的單元測試和鬆散耦合。所有來自客戶端的請求首先透過 API Gateway,然後將傳入的請求路由到正確的 Lambda 函式。這些函式是無狀態的,因此 DynamoDB 就派上用場,用於管理跨 Lambda 函式的資料永續性。Amazon S3 儲存桶用於提供市場靜態 Web 應用程式。最後,Amazon CloudFront 分發(可選)用於從全球邊緣快取位置交付靜態資產,如 CSS 或 JavaScript 檔案。
要佈署 Lambda 函式,我們需要建立一個 AWS Lambda 資源和一個 IAM 執行角色,該角色具有 Lambda 函式在執行時可以存取的 AWS 資源列表。例如,Lambda 函式 MoviesStoreListMovies 對 DynamoDB 表執行掃描操作以取得電影列表。因此,Lambda 執行角色應授予對 DynamoDB 表的存取許可權。
為了避免程式碼重複並為建立 Lambda 函式提供輕量級抽象,我們將使用 Terraform 模組。模組是多個一起使用的資源的容器。
使用 Terraform 模組建立 Lambda 函式
module "MoviesLoader" {
source = "./modules/function"
name = "MoviesLoader"
handler = "index.handler"
runtime = "python3.7"
environment = {
SQS_URL = aws_sqs_queue.queue.id
}
}
module "MoviesParser" {
source = "./modules/function"
name = "MoviesParser"
handler = "main"
runtime = "go1.x"
environment = {
TABLE_NAME = aws_dynamodb_table.movies.id
}
}
#### 內容解密:
上述 Terraform 程式碼定義了兩個 Lambda 函式:MoviesLoader 和 MoviesParser。MoviesLoader 使用 Python 3.7 環境,而 MoviesParser 使用 Go 環境。每個模組區塊參照了一個自定義的 Terraform 模組,該模組位於 ./modules/function 目錄下,並覆寫了預設變數,如 Lambda 的執行環境和環境變數。這種方法使得建立和管理多個 Lambda 函式變得更加簡單和一致。
使用 Amazon API Gateway 佈署 RESTful API
我們將使用 Amazon API Gateway 定義 HTTP 端點,以在接收到 HTTP/HTTPS 請求時觸發 Lambda 函式。下面是 apigateway.tf 檔案中的 Terraform 程式碼,用於公開 /movies 資源上的 GET 方法。
resource "aws_api_gateway_resource" "path_movies" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "movies"
}
#### 內容解密:
這段程式碼定義了一個名為 path_movies 的 aws_api_gateway_resource 資源,它代表了 API 中的 /movies 路徑。其中,rest_api_id 和 parent_id 分別指定了該資源所屬的 REST API 和其父資源。這是建立 API Gateway 端點的第一步,後續還需要定義方法和整合,以觸發相應的 Lambda 函式。
使用 Terraform 佈署 Lambda 基礎的無伺服器應用程式
設定 API Gateway 與 S3
為了佈署一個根據 Lambda 的無伺服器應用程式,我們需要設定 API Gateway 和 S3。首先,我們使用 Terraform 來建立必要的資源。
API Gateway 設定
module "GetMovies" {
source = "./modules/method"
api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.path_movies.id
method = "GET"
lambda_arn = module.MoviesStoreListMovies.arn
invoke_arn = module.MoviesStoreListMovies.invoke_arn
api_execution_arn = aws_api_gateway_rest_api.api.execution_arn
}
內容解密:
此段 Terraform 程式碼定義了一個名為 GetMovies 的模組,用於建立一個 API Gateway 的方法。該方法與 Lambda 函式 MoviesStoreListMovies 相關聯,當接收到 GET 請求時,會觸發該 Lambda 函式。主要引數包括:
api_id:API Gateway 的 ID。resource_id:API Gateway 資源的 ID。method:HTTP 方法,此處為 GET。lambda_arn和invoke_arn:Lambda 函式的 ARN 和呼叫 ARN。
S3 設定
resource "aws_s3_bucket" "marketplace" {
bucket = "marketplace.${var.domain_name}"
acl = "public-read"
website {
index_document = "index.html"
error_document = "index.html"
}
}
內容解密:
此段程式碼建立了一個 S3 儲存桶,用於託管靜態網站內容。主要組態包括:
bucket:儲存桶名稱,包含變數domain_name。acl:存取控制列表,設為public-read以允許公開讀取。website區塊:定義了網站的首頁和錯誤頁面,均為index.html。
建立 CI/CD 管道
為了自動化佈署 Lambda 函式,我們需要建立一個 CI/CD 管道。這裡使用 Jenkins 來實作。
Jenkinsfile 設定
def imageName = 'mlabouardy/movies-loader'
node('workers') {
try {
stage('Checkout') {
checkout scm
notifySlack('STARTED')
}
stage('Unit Tests') {
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside {
sh "python test_index.py"
}
}
} catch (e) {
currentBuild.result = 'FAILED'
throw e
} finally {
notifySlack(currentBuild.result)
}
}
內容解密:
此 Jenkinsfile 定義了一個管道,包含兩個階段:Checkout 和 Unit Tests。主要步驟包括:
checkout scm:從 Git 倉函式庫簽出程式碼。- 使用 Docker 建置測試映像並執行單元測試。
- 當發生錯誤時,將建置結果設為 FAILED,並傳送 Slack 通知。
建立佈署套件的流程與實作
在現代化的軟體開發流程中,自動化佈署是至關重要的一環。對於根據AWS Lambda的無伺服器函式,建立佈署套件是佈署流程中的關鍵步驟。本文將探討如何為Lambda函式建立佈署套件,並介紹相關的CI/CD流程。
佈署套件的建立
佈署套件是一個ZIP檔案,包含了Lambda函式的程式碼及其所需的依賴項。建立佈署套件的流程可以透過Jenkins等CI/CD工具自動化完成。
Python Lambda函式範例
以下是一個使用Jenkinsfile定義的CI/CD流程,用於建立Python Lambda函式的佈署套件:
def functionName = 'MoviesLoader'
def imageName = 'mlabouardy/movies-loader'
def bucket = 'deployment-packages-watchlist'
def region = 'AWS REGION'
node('workers') {
try {
stage('Checkout') {
// 省略 checkout scm 指令
}
stage('Unit Tests') {
// 省略單元測試指令
}
stage('Build') {
sh "zip -r ${commitId}.zip index.py movies.json"
}
stage('Push') {
sh "aws s3 cp ${commitId}.zip s3://${bucket}/${functionName}/"
}
} catch (e) {
currentBuild.result = 'FAILED'
throw e
} finally {
notifySlack(currentBuild.result)
sh "rm -rf ${commitId}.zip"
}
}
內容解密:
stage('Build'):在這個階段中,使用zip命令將index.py和movies.json檔案壓縮成一個名為${commitId}.zip的檔案。這裡的${commitId}是根據目前Git commit ID生成的唯一識別碼,用於命名佈署套件。stage('Push'):將生成的佈署套件上傳到指定的S3儲存桶中,路徑為s3://${bucket}/${functionName}/。finally:無論構建流程成功或失敗,都會刪除本地的佈署套件,以節省硬碟空間。
Go語言Lambda函式範例
對於使用Go語言編寫的Lambda函式,需要使用Docker的多階段構建功能來編譯二進位制檔案,然後建立佈署套件。
def functionName = 'MoviesParser'
def imageName = 'mlabouardy/movies-parser'
def region = 'eu-west-3'
node('workers') {
try {
stage('Checkout') {
// 省略 checkout scm 指令
}
stage('Pre-integration Tests') {
// 省略預先整合測試指令
}
stage('Build') {
sh """
docker build -t ${imageName} .
docker run --rm ${imageName}
docker cp ${imageName}:/go/src/github.com/mlabouardy/movies-parser/main main
zip -r ${commitID()}.zip main
"""
}
stage('Push') {
sh "aws s3 cp ${commitID()}.zip s3://${bucket}/${functionName}/"
}
} catch (e) {
currentBuild.result = 'FAILED'
throw e
} finally {
notifySlack(currentBuild.result)
sh "rm ${commitID()}.zip"
}
}
內容解密:
stage('Build'):使用Docker構建映像並執行容器,然後從容器中複製編譯好的二進位制檔案main,最後將其壓縮成${commitID()}.zip。stage('Push'):將佈署套件上傳到S3儲存桶。finally:刪除本地的佈署套件。
多倉函式庫與單一倉函式庫策略
在管理多個Lambda函式時,可以採用多倉函式庫或單一倉函式庫的策略。多倉函式庫策略是將每個Lambda函式放在獨立的Git儲存函式庫中,而單一倉函式庫策略則是將多個相關的Lambda函式放在同一個倉函式庫中。
多倉函式庫策略
每個Lambda函式都有自己的GitHub倉函式庫和Jenkinsfile,用於定義其CI/CD流程。這種方式適合於功能獨立、開發團隊不同的Lambda函式。
單一倉函式庫策略
將多個相關的Lambda函式放在同一個GitHub倉函式庫中,例如movies-store倉函式庫。這種方式便於分享程式碼和管理相關功能。