無伺服器架構允許開發者將基礎設施管理交由雲端供應商,從而專注於應用程式開發。本文以 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 函式:MoviesLoaderMoviesParserMoviesLoader 使用 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_moviesaws_api_gateway_resource 資源,它代表了 API 中的 /movies 路徑。其中,rest_api_idparent_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_arninvoke_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"
    }
}

內容解密:

  1. stage('Build'):在這個階段中,使用zip命令將index.pymovies.json檔案壓縮成一個名為${commitId}.zip的檔案。這裡的${commitId}是根據目前Git commit ID生成的唯一識別碼,用於命名佈署套件。
  2. stage('Push'):將生成的佈署套件上傳到指定的S3儲存桶中,路徑為s3://${bucket}/${functionName}/
  3. 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"
    }
}

內容解密:

  1. stage('Build'):使用Docker構建映像並執行容器,然後從容器中複製編譯好的二進位制檔案main,最後將其壓縮成${commitID()}.zip
  2. stage('Push'):將佈署套件上傳到S3儲存桶。
  3. finally:刪除本地的佈署套件。

多倉函式庫與單一倉函式庫策略

在管理多個Lambda函式時,可以採用多倉函式庫或單一倉函式庫的策略。多倉函式庫策略是將每個Lambda函式放在獨立的Git儲存函式庫中,而單一倉函式庫策略則是將多個相關的Lambda函式放在同一個倉函式庫中。

多倉函式庫策略

每個Lambda函式都有自己的GitHub倉函式庫和Jenkinsfile,用於定義其CI/CD流程。這種方式適合於功能獨立、開發團隊不同的Lambda函式。

單一倉函式庫策略

將多個相關的Lambda函式放在同一個GitHub倉函式庫中,例如movies-store倉函式庫。這種方式便於分享程式碼和管理相關功能。