在 Serverless 架構下,Lambda 函式的自動化佈署至關重要。本文詳細說明如何利用 Jenkins Pipeline 建立 CI/CD 流程,實作 Lambda 函式的自動化建置、測試、佈署及版本管理。流程涵蓋程式碼簽出、建置 Docker 映像、封裝 Node.js 相依套件、上傳至 S3 儲存桶、更新 Lambda 函式程式碼,並利用版本控制和別名機制實作多環境佈署策略。同時整合 API Gateway 與 Lambda 函式,並使用 Terraform 進行基礎設施佈署,最後加入電子郵件通知功能,提升佈署流程的可靠性和可控性。

使用 Jenkins 實作 Lambda 函式的 CI/CD 流程

在開發 Serverless 架構的應用程式時,如何有效地管理和佈署 Lambda 函式是一個重要的課題。本文將介紹如何使用 Jenkins 實作 Lambda 函式的持續整合和持續佈署(CI/CD)流程。

建立 Jenkinsfile

首先,我們需要在專案中建立一個 Jenkinsfile,用於定義 CI/CD 流程。以下是一個範例 Jenkinsfile:

def imageName = 'mlabouardy/movies-store'
node('workers'){
    try {
        stage('Checkout'){
            checkout scm
            notifySlack('STARTED')
        }
        def imageTest= docker.build("${imageName}-test", "-f Dockerfile.test .")
        stage('Tests'){
            parallel(
                'Quality Tests': {
                    sh "docker run --rm ${imageName}-test npm run lint"
                },
                'Unit Tests': {
                    sh "docker run --rm ${imageName}-test npm run test"
                },
                'Coverage Reports': {
                    sh "docker run --rm -v $PWD/coverage:/app/coverage ${imageName}-test npm run coverage"
                    publishHTML (target: [
                        allowMissing: false,
                        alwaysLinkToLastBuild: false,
                        keepAll: true,
                        reportDir: "$PWD/coverage",
                        reportFiles: "index.html",
                        reportName: "Coverage Report"
                    ])
                }
            )
        }
        // ...

內容解密:

此 Jenkinsfile 定義了一個 CI/CD 流程,包括簽出程式碼、執行測試、產生測試報告等階段。其中,stage('Checkout') 階段負責簽出程式碼,stage('Tests') 階段負責執行測試,包括程式碼品品檢查、單元測試和測試報告生成。

建立 Node.js 型 Lambda 函式的佈署套件

接下來,我們需要建立一個 Node.js 型 Lambda 函式的佈署套件。以下是一個範例:

stage('Build'){
    sh """
        docker build -t ${imageName} .
        containerName=\$(docker run -d ${imageName})
        docker cp \$containerName:/app/node_modules node_modules
        docker rm -f \$containerName
        zip -r ${commitID()}.zip node_modules src
    """
}

內容解密:

此階段負責建立 Node.js 型 Lambda 函式的佈署套件。首先,使用 Docker 建置映像檔,然後從容器中複製 node_modules 資料夾,最後將 node_modulessrc 資料夾壓縮成一個 zip 檔案。

將佈署套件上傳到 S3

接下來,我們需要將佈署套件上傳到 S3。以下是一個範例:

stage('Push'){
    def fileName = commitID()
    def parallelStagesMap = functions.collectEntries {
        ["${it}" : {
            stage("Lambda: ${it}") {
                sh "aws s3 cp ${fileName}.zip s3://${bucket}/${it}/"
            }
        }]
    }
    parallel parallelStagesMap
}

內容解密:

此階段負責將佈署套件上傳到 S3。首先,定義了一個 parallelStagesMap,用於平行上傳佈署套件到不同的 S3 資料夾。然後,使用 parallel 指令平行執行上傳任務。

更新 Lambda 函式程式碼

最後,我們需要更新 Lambda 函式程式碼。以下是一個範例:

stage('Deploy'){
    sh "aws lambda update-function-code --function-name ${functionName} --s3-bucket ${bucket} --s3-key ${functionName}/${commitID()}.zip --region ${region}"
}

內容解密:

此階段負責更新 Lambda 函式程式碼。使用 AWS CLI 的 update-function-code 指令更新 Lambda 函式程式碼。

自動化佈署與管理 AWS Lambda 函式的多環境策略

在開發無伺服器應用程式時,管理多個環境對於測試新變更而不影響生產環境至關重要。本章節將探討如何利用 Jenkins 實作 AWS Lambda 函式的持續整合和持續佈署(CI/CD),並探討如何維護多個 Lambda 環境。

使用 Jenkins 自動佈署 Lambda 函式

Jenkins 可以與 AWS Lambda 無縫整合,以自動化佈署流程。首先,我們需要在 Jenkinsfile 中定義佈署階段,利用 AWS CLI 更新 Lambda 函式程式碼。

更新 Lambda 函式程式碼

stage('Deploy'){
    functions.each { function ->
        sh "aws lambda update-function-code --function-name ${function} --s3-bucket ${bucket} --s3-key ${function}/${commitID()}.zip --region ${region}"
    }
}

詳細解說:

  1. 更新函式程式碼:使用 aws lambda update-function-code 命令更新指定的 Lambda 函式。
  2. S3 儲存桶:從指定的 S3 儲存桶和鍵讀取程式碼包。
  3. 版本控制:利用 Git commit ID 作為版本標識,確保每次佈署都有唯一的版本。

載入資料到 DynamoDB 表

在自動化佈署市場應用之前,需要將一些資料載入到 DynamoDB 表中。可以透過觸發 MoviesLoader 函式來實作這一點。

呼叫 MoviesLoader 函式

aws lambda invoke --function-name MoviesLoader --payload '{}' response.json

詳細解說:

  1. 呼叫函式:使用 aws lambda invoke 命令呼叫指定的 Lambda 函式。
  2. 處理結果:將函式的輸出儲存到 response.json 檔案中。
  3. IAM 許可權:確保執行 AWS CLI 的 IAM 使用者具有 AWSLambda_FullAccess 策略。

將靜態網站佈署到 S3

市場應用是一個使用 Angular 框架編寫的單頁應用(SPA)。我們可以利用 S3 的網站託管功能來佈署靜態內容。

Jenkinsfile 中的佈署階段

stage('Push'){
    sh "aws s3 cp --recursive dist/ s3://${bucket}/"
}

詳細解說:

  1. 複製檔案:使用 aws s3 cp 命令遞迴地將本地檔案複製到 S3 儲存桶。
  2. 網站託管:確保 S3 儲存桶已啟用網站託管功能。

維護多個 Lambda 環境

AWS Lambda 允許發布版本,以代表函式程式碼和組態在某一時間點的狀態。預設情況下,每個 Lambda 函式都有 $LATEST 版本,指向最新的變更。

發布新版本

stage('Deploy'){
    sh "aws lambda update-function-code --function-name ${functionName} --s3-bucket ${bucket} --s3-key ${functionName}/${commitID()}.zip --region ${region}"
    sh "aws lambda publish-version --function-name ${functionName} --description ${commitID()} --region ${region}"
}

詳細解說:

  1. 更新函式程式碼:首先更新 Lambda 函式的程式碼。
  2. 發布新版本:使用 aws lambda publish-version 命令發布新版本,並使用 Git commit ID 作為版本描述。

無伺服器函式的多環境管理

在現代化的軟體開發流程中,使用Lambda-based serverless functions已成為主流趨勢。為了有效管理不同環境下的Lambda函式,開發團隊需要建立一套完善的CI/CD流程,以確保程式碼的版本控制、發布和佈署能夠順暢進行。

Lambda函式版本控制與發布

當開發人員將程式碼推播到Git儲存函式庫後,Jenkins會自動觸發CI/CD流程。在佈署階段,Jenkins會執行預先定義的命令,將Lambda函式的程式碼更新並發布新的版本。圖12.24展示了佈署階段的執行日誌。

值得注意的是,Lambda函式的版本是不可變的,一旦建立就無法更新其程式碼或設定。因此,每次發布新版本時,都會建立一個新的版本。

使用Lambda別名管理多環境

為了方便管理不同環境下的Lambda函式,可以使用Lambda別名。別名是一個指向特定版本的指標,可以用來將函式從一個環境提升到另一個環境,例如從測試環境提升到生產環境。與版本不同,別名是可變的。

建立Lambda別名

可以使用AWS CLI命令建立Lambda別名:

aws lambda create-alias --function-name MoviesStoreViewFavorites --name sandbox --version 1

建立後,新的別名會出現在Lambda函式的Aliases列表中。

在Jenkinsfile中更新Lambda別名

為了自動化更新Lambda別名,可以在Jenkinsfile中新增相關程式碼。在佈署階段,Jenkins會根據目前的Git分支,更新對應的Lambda別名,使其指向最新發布的版本。

sh "aws lambda update-function-code --function-name ${it} --s3-bucket ${bucket} --s3-key ${it}/${fileName}.zip --region ${region}"
def version = sh(
    script: "aws lambda publish-version --function-name ${it} --description ${fileName} --region ${region} | jq -r '.Version'",
    returnStdout: true
).trim()
if (env.BRANCH_NAME in ['master','preprod','develop']){
    sh "aws lambda update-alias --function-name ${it} --name ${environments[env.BRANCH_NAME]} --function-version ${version} --region ${region}"
}

內容解密:

  1. 更新Lambda函式程式碼:使用aws lambda update-function-code命令更新Lambda函式的程式碼。
  2. 發布新版本:使用aws lambda publish-version命令發布新版本,並使用jq命令解析輸出的JSON結果,取得新版本的版本號。
  3. 更新Lambda別名:根據目前的Git分支,更新對應的Lambda別名,使其指向最新發布的版本。

組態API Gateway使用Lambda別名

為了讓API Gateway能夠使用Lambda別名,需要在API Gateway的組態中,將Lambda函式的版本替換為stage variable。

設定Stage Variable

  1. 在API Gateway控制檯中,導航到Movies API,點選GET方法。
  2. 更新目標Lambda函式,使用stage variable ${stageVariables.environment}
  3. 儲存組態後,API Gateway會提示授予呼叫Lambda函式別名的許可權。

使用Terraform佈署API

可以使用Terraform程式碼佈署API,並設定stage variable。

resource "aws_api_gateway_deployment" "sandbox" {
  depends_on = [
    module.GetMovies,
    module.GetOneMovie,
    module.GetFavorites,
    module.PostFavorites
  ]
  variables = {
    "environment" = "sandbox"
  }
  rest_api_id = aws_api_gateway_rest_api.api.id
  stage_name  = "sandbox"
}

圖表翻譯:

此圖示展示了API Gateway佈署的流程,包括建立新的佈署階段、設定stage variable等步驟。

佈署到不同環境

建立不同的佈署階段(sandbox、staging、production),並使用對應的Lambda別名。可以透過建立pull request,將程式碼從develop分支合併到preprod分支,觸發Jenkins自動構建和佈署。

最終,使用者可以透過不同的API URL存取不同環境下的Lambda函式,例如:https://id.execute-api.region.amazonaws.com/sandbox/movies

佈署多環境的Lambda無伺服器函式

為了在多個環境中佈署市場,我們將根據目前的分支名稱注入環境名稱。請參考以下程式碼。

stage('Build'){
    sh """
        docker build -t ${imageName} \
        --build-arg ENVIRONMENT=${environments[env.BRANCH_NAME]} .
        containerName=\$(docker run -d ${imageName})
        docker cp \$containerName:/app/dist dist
        docker rm -f \$containerName
    """
}

內容解密:

這段程式碼定義了一個名為Build的階段,主要功能是建立Docker映像並將靜態檔案複製到本地目錄。首先,它使用docker build命令建立映像,並傳入ENVIRONMENT建構引數,該引數的值根據目前分支名稱從environments對映中取得。接著,它執行一個容器,將容器內的/app/dist目錄複製到本地的dist目錄,最後刪除該容器。

更新S3上傳指令

接下來,我們更新aws s3 cp指令,將靜態檔案推播到S3儲存桶中以環境名稱命名的資料夾下。

if (env.BRANCH_NAME in ['master','preprod','develop']){
    stage('Push'){
        sh "aws s3 cp --recursive dist/ s3://${bucket}/${environments[env.BRANCH_NAME]}/"
    }
}

內容解密:

這段程式碼檢查目前分支是否為masterpreproddevelop。如果是,則執行一個名為Push的階段,將本地dist目錄下的檔案遞迴上傳到S3儲存桶中對應環境名稱的資料夾內。

維護多個Lambda環境

將變更推播到功能分支,並發起一個合併請求以合併到develop分支。當合併發生時,將執行新的Pipeline。

佈署Lambda函式到生產環境

要將Lambda函式佈署到生產環境,需要將preprod分支合併到master分支。

if(env.BRANCH_NAME == 'master'){
    timeout(time: 2, unit: "HOURS") {
        input message: "Deploy to production?", ok: "Yes"
    }
    sh "aws lambda update-alias --function-name ${it} \
    --name ${environments[env.BRANCH_NAME]} \
    --function-version ${version} \
    --region ${region}"
}

內容解密:

這段程式碼檢查目前分支是否為master。如果是,則在佈署到生產環境之前彈出一個輸入對話方塊,要求確認是否佈署。如果確認,則更新Lambda函式的別名指向新佈署的版本。

設定Jenkins的電子郵件通知

要在Jenkins中設定電子郵件通知,需要安裝Email Extension外掛程式。進入「管理Jenkins」>「設定系統」,向下捲動到「Extended E-mail Notification」部分,輸入SMTP伺服器的相關資訊。

def sendEmail(String buildStatus){
    buildStatus = buildStatus ?: 'SUCCESSFUL'
    emailext body: "More info at: ${env.BUILD_URL}",
        subject: "Name: '${env.JOB_NAME}' Status: ${buildStatus}",
        to: '$DEFAULT_RECIPIENTS'
}

內容解密:

這段程式碼定義了一個名為sendEmail的函式,用於傳送電子郵件通知。函式根據構建狀態傳送自訂的電子郵件,包含構建任務的名稱、狀態和詳細資訊連結。

在Jenkinsfile中呼叫sendEmail函式

node('workers'){
    try {
        stage('Checkout'){...}
        stage('Tests'){...}
        stage('Build'){...}
        stage('Push'){...}
        stage('Deploy'){...}
    } catch(e){
        currentBuild.result = 'FAILED'
        throw e
    } finally {
        notifySlack(currentBuild.result)
        if (env.BRANCH_NAME == 'master'){
            sendEmail(currentBuild.result)
        }
    }
}

內容解密:

這段程式碼在Jenkinsfile的finally區塊中呼叫了sendEmail函式,以便在Pipeline完成後傳送電子郵件通知。只有當目前分支為master時,才會傳送電子郵件,以避免向開發人員傳送不必要的通知。