AWS Lambda関数をCodeDeployでデプロイする

はじめに

AWS Lambda関数をCodeDeployでデプロイすることができたので、ここにまとめておきます。

AWSのドキュメントが以下にあるのですが、試してみたところ動かなかったのでゼロから実装してみました。

docs.aws.amazon.com

言語は慣れていてサクッと動かせるRubyにしてみました。

関連ファイルの作成

まずは関連ファイルを作成します。SAMのテンプレートファイルを作成します。Hookを使わず単純なものにしました。

AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A sample SAM template for deploying Lambda functions.

Resources:
  # Details about the mainFunction Lambda function
  mainFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handler.handler
      Runtime: ruby3.2
      AutoPublishAlias: live
      DeploymentPreference:
        Type: Linear10PercentEvery1Minute  # 1分間に10%ずつ更新

アプリはhandler.rbに以下のように実装します。とりあえずの適当に作った関数です。

# frozen_string_literal: true

require "json"
require "date"

# event には {"command": コマンド名, "option": オプション値}という
# Hashで出力される
def handler(event:, context:)
  status_code = 200
  case event["command"]
  when "date"
    result = { date: JSON.generate(Date.today) }
  when "time"
    result = { time: JSON.generate(Time.now) }
  else
    status_code = 400
    result = { "error" => "Must specify 'date' or 'time'" }
  end

  {
    statusCode: status_code,
    body: result,
    message: "message v1"
  }
end

デプロイ

必要なファイルが作成できたらsam packagesam deployを使ってデプロイします。これだけでCodeDeployでAWS Lambda関数がデプロイされます。DeploymentPreferenceLinear10PercentEvery1Minuteと指定したので、1分ごとに更新されることが確認できます。

AWS Lambda関数をaws lambda invokeで実行してみます。

awscli.amazonaws.com

--function-nameに指定するARN文字列にAlias(ここではlive)を付与して実行すると、タイミングによってはExecutedVersionの値が変わることが確認できます。

Hookを実装する

Hookを実装します。こんな感じで実装します。まずはbefore_allow_traffic.rbです。

# frozen_string_literal: true

require 'aws-sdk-codedeploy'
require 'logger'

class BeforeAllowTraffic
  @logger = Logger.new(STDOUT)
  @codedeploy_client = Aws::CodeDeploy::Client.new

  def self.notify_execution_status(event:, status:)
    deployment_id = event['DeploymentId']
    execution_id = event['LifecycleEventHookExecutionId']

    @codedeploy_client.put_lifecycle_event_hook_execution_status(
      {
        deployment_id: deployment_id,
        lifecycle_event_hook_execution_id: execution_id,
        status: status
      })
  end

  def self.handler(event: , context:)
    @logger.info(event)
    status = "Succeeded"

    begin
      notify_response = notify_execution_status(event: event, status: status)
    rescue => e
      @logger.fatal(e)
      status = "Failed"
      notify_response = notify_execution_status(event: event, status: status)
    ensure
      @logger.info("notify_response=[#{notify_response}]")
    end
  end
end

次にafter_allow_traffic.rbです。

# frozen_string_literal: true

require 'aws-sdk-codedeploy'
require 'logger'

class AfterAllowTraffic
  @logger = Logger.new(STDOUT)
  @codedeploy_client = Aws::CodeDeploy::Client.new

  def self.notify_execution_status(event:, status:)
    deployment_id = event['DeploymentId']
    execution_id = event['LifecycleEventHookExecutionId']

    @codedeploy_client.put_lifecycle_event_hook_execution_status(
      {
        deployment_id: deployment_id,
        lifecycle_event_hook_execution_id: execution_id,
        status: status
      })
  end

  def self.handler(event: , context:)
    @logger.info(event)
    status = "Succeeded"

    begin
      notify_response = notify_execution_status(event: event, status: status)
    rescue => e
      @logger.fatal(e)
      status = "Failed"
      notify_response = notify_execution_status(event: event, status: status)
    ensure
      @logger.info(notify_response)
    end
  end
end

CodeDeployのput_lifecycle_event_hook_execution_statusに必要な引数の値はhandlerのeventにHashとして入っているのでそれを読み取ります。

SAMのtemplate.ymlも更新します。

AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: A sample SAM template for deploying Lambda functions.

Resources:
  # Details about the mainFunction Lambda function
  mainFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handler.handler
      Runtime: ruby3.2
      AutoPublishAlias: live
      DeploymentPreference:
#        Type: AllAtOnce  # 一度に全部更新する
        Type: Linear10PercentEvery1Minute  # 1分間に10%ずつ更新
        Hooks:
          PreTraffic: !Ref BeforeTrafficFunction
          PostTraffic: !Ref AfterTrafficFunction

  BeforeTrafficFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: before_allow_traffic.BeforeAllowTraffic.handler
      Runtime: ruby3.2
      Policies:
        - Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action:
                - codedeploy:PutLifecycleEventHookExecutionStatus
              Resource: "*"
  AfterTrafficFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: after_allow_traffic.AfterAllowTraffic.handler
      Runtime: ruby3.2
      Policies:
        - Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action:
                - codedeploy:PutLifecycleEventHookExecutionStatus
              Resource: "*"

この段階で、sam deployしたときに権限不足でエラーが出ましたのでCodeDeployのIAMロールにAWSLambdaRoleを付与してあげたら動きました。

これで当初の目的は実装することができました。