透過AWS EventBridge排程觸發Container類型的Lambda服務

2022/06/22

Scenario

本篇透過Lambda Docker Node.js環境

建立一個micro service

該服務啟動後立即執行js透過Line Notify API發送測試訊息

 

專案架構

  • app(folder)
    • app.js: 核心Node.js程式碼
    • package.json
    • yarn.lock
  • .dockerignore
  • docker-compose.yml
  • Dockerfile

 

Dockerfile設定

這邊Container內使用Node.js環境

可直接參考AWS Lambda  Node.js Image說明頁面的Usge分頁

# env ref: https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/configuration-envvars.html
FROM public.ecr.aws/lambda/nodejs:12

ENV LAMBDA_TASK_ROOT_PATH="${LAMBDA_TASK_ROOT}/"

WORKDIR $LAMBDA_TASK_ROOT_PATH

# 將專案的code複製到lambda task root
COPY ./app ${LAMBDA_TASK_ROOT_PATH}

RUN ls \
  && npm install --global yarn \
  && yarn install

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD ["app.handler"]

 

app/app.js

這邊可以看到透過env環境變數設定Line Notify Token

const axios = require('axios')
const qs = require('qs')
class App {
  constructor() {
    this.token = process.env.LINE_NOTIFY_TOKEN // line notify token透過env帶入
  }
  delay(secondo = 1) {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve()
      }, second*1000)
    })
  }

  async lineNotify(message) {
    const result = await axios({
      url: 'https://notify-api.line.me/api/notify',
      method: 'post',
      headers: {
        Authorization: `Bearer ${this.token}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      data: qs.stringify({
        message,
      }, { arrayFormat: 'indices' }),
    })
  }

  async start() {
    console.warn('start app')
    await this.lineNotify('測試訊息')
    console.warn('finished function')
    return {
      status: 200,
      body: 'success!'
    }
  }
}

exports.handler = async function(event) {
  console.warn('event', event)
  if(event) {
    console.warn('payload', event.payload)
  }
  const app = new App()
  return await app.start()
}

 

測試服務

可參考此篇文件

 

build image

docker build -t lambda-nodejs . --no-cache

 

run service

透過env帶入LINE_NOTIFY_TOKEN

並將container內部的8000 port轉至外部的9000 port

docker run -p 9000:8080 --name lambda-nodejs --env LINE_NOTIFY_TOKEN={YOUR_LINE_NOTIFY_TOKEN} lambda-nodejs

 

測試

curl --request POST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'

 

若測試成功

將會在Line到測試通知

並且可在service log看到接收到payload的資訊

Screenshot_20220623_203702.png

 

透過docker-compose打包、測試

 

docker-compose.yml

透過env_file設定將所有env都放到同一個檔案

以下範例預設env檔案路徑為/default/docker-env/file/path

若有需要覆蓋此設定

可在執行docker-compose時透過設定dockerEnv參數來覆寫

version: "3.8"

services:
  aws-lambda:
    container_name: aws-lambda-nodejs
    build:
      context: .
    env_file:
      - '${dockerEnv-/default/docker-env/file/path}'
    restart: unless-stopped
    tty: true
    ports:
      - "9000:8080"
    working_dir: /var/task
    networks:
      - lambda-test

networks:
  lambda-test:
    driver: bridge

 

docker env file

LINE_NOTIFY_TOKEN=foobar

 

Build、Run

# build
docker-compose build

# build with no cache
docker-compose build --no-cache

# run
docker-compose up

# run with custom docker env file path
dockerEnv=/custom/docker-env-file/path docker-compose

 

將Image push到AWS ECR

參考文件

接著在ECR建立一個新的Registry

 

ECR登入驗證

登入成功後將會看到成功訊息輸出

aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com

 

tag image

docker tag [docker-tag] aws_account_id.dkr.ecr.region.amazonaws.com/[registry-name]:tag

 

push

docker push aws_account_id.dkr.ecr.region.amazonaws.com/[registry-name]:tag

 

設定Lambda、測試功能

 

Lambda設定

建立容器類型的lambda funcction

並設定container image URI

Screenshot_20220623_212736.png

 

設定環境變數

依照本篇需求

設定LINE_NOTIFY_TOKEN環境變數

Screenshot_20220623_213320.png

 

測試lambda函式

測試成功將跟本機測試一樣

會收到Line Notify通知

5.png

 

 

3.png

 

此外可查看執行細節

這邊可以看到response

以及跟本機測試一樣的service log輸出

4.png

 

透過EventBridge觸發Lambda服務

 

在Lambda建立新的觸發條件(Trigger)

排程的Expression可參考AWS文件

可使用Cron(排程)或Rate(頻率)

此範例使用排程

要注意的是Cron設定的時間為UTC

台灣時間為+8

因此UTC時間直接-8計算即可

 

下圖設定為每日台灣時間22點整(14 = 22-8)

Screenshot_20220623_221344.png

 

在Event帶指定的參數

EventBrige可使用JSON常數來傳遞值給Container

Screenshot_20220623_234404.png

 

多樣化的觸發條件

除了此範例的排程觸發

AWS Lambda還提供各種類型的觸發方式

像是S3、SQS、SNS、API Gateway等

可依照需求自行使用各種方式來觸發

Screenshot_20220623_223511.png

 

目的地

AWS Lambda可設定在函式執行完成功/失敗後

進行後續不同的動作(目的地)

像是SQS、SNS、Eventbrige、其他Lambda Function等

Screenshot_20220623_223957.png

 

透過Gitlab CI自動更新Lambda

 

設定IAM User

設定一個可操作AWS ECR、Lambda的IAM User

並產生一組Key

 

設定AWS Credentials

前往Gitlab專案的CI/CD Settings > Variables

加入一個AWS_SHARED_CREDENTIALS_FILE環境變數

將內容設定上述的IAM User Key

[default]
aws_access_key_id = aws_access_key_id
aws_secret_access_key = aws_secret_access_key

 

此變數為AWS CLI的憑證設定環境變數(可參考此文件)

並將此變數類型設定為File Type

Screenshot_20220625_123719.png

 

.gitlab-ci.yml

以此方式設定.gitlab-ci.yml

當push含有"build-image"的git tag即可自動執行build image job

當push含有"deploy-lambda"的git tag即可自動執行Lambda更新Image流程

 

variables:
  ECR_REGISTRY_URI: {AccountId}.dkr.ecr.ap-southeast-1.amazonaws.com/{RegistryName}
  TARGET_IMAGE: $ECR_REGISTRY_URI:latest
  LAMBDA_FUNCTION_NAME: {LambdaFunctionName}

  # AWS CLI需要使用的環境變數
  AWS_DEFAULT_REGION: ap-southeast-1

  # 設定docker in docker service連接的port
  DOCKER_HOST: tcp://docker:2375

image:
  name: amazon/aws-cli
  entrypoint: [""]

build:
  stage: build
  services:
    - docker:dind
  only:
    variables:
      - $CI_COMMIT_MESSAGE =~ /build-image/
      - $CI_COMMIT_TAG =~ /build-image/
  before_script:
    - amazon-linux-extras install docker
    - aws --version
    - docker --version
  script:
    # 取得臨時登入憑證
    - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY_URI
    # build image
    - docker build -t $TARGET_IMAGE .
    # push image to ECR
    - docker push $TARGET_IMAGE
    # 將aws cli找到的untagged image存至env $IMAGES_TO_DELETE
    - IMAGES_TO_DELETE=$(aws ecr list-images --repository-name $ECR_REGISTRY_NAME --filter "tagStatus=UNTAGGED" --query 'imageIds[*]' --output json)
    # 移除舊的所有untagged image
    - aws ecr batch-delete-image --repository-name $ECR_REGISTRY_NAME --image-ids "$IMAGES_TO_DELETE" || true

deploy:
  stage: deploy
  services:
    - docker:dind
  only:
    variables:
      - $CI_COMMIT_MESSAGE =~ /deploy-lambda/
      - $CI_COMMIT_TAG =~ /deploy-lambda/
  script:
    # 更新lambda image
    - aws lambda update-function-code --function-name $LAMBDA_FUNCTION_NAME --image-uri $TARGET_IMAGE

 

實際執行

 

build image

1.png

 

更新Lambda Function Image

2.png