TerraformでLambdaコンテナイメージを自動構築する

# はじめに
昨年末にGAされたLambdaのコンテナイメージ機能。
プラットフォームとしてのデプロイ方法が増えて、ますますLambdaの使い勝手が良くなってきている。

今回は、通常のZipによるデプロイではなくて、コンテナイメージでのデプロイをTerraformで自動化してみよう。

前提知識としては以下だ。

- TerraformによるLambdaのデプロイ経験がある
- SAMによるLambdaのデプロイ経験がある([参考記事](https://qiita.com/neruneruo/items/1ef52f908e4497103a59))
- Dockerの基本のキは理解している([参考記事](https://qiita.com/neruneruo/items/82c9daf90d5782f150d2))

# ソースコード
今回は動けば何でもよいので、AWSリソースとも接続しない、テキトーにJSONを返すだけのコードにしよう。
クエリからidを受け取り、それに応じた名前を返すような機能(getemployee)とする。

```main.go
package main

import (
	"context"
	"encoding/json"
	"log"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type responseBody struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	lambda.Start(handler)
}

func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	employees := map[string]string{
		"00001": "Taro",
		"00002": "Jiro",
		"00003": "Saburo",
		"00004": "Shiro",
		"00005": "Goro",
	}

	if len(request.QueryStringParameters) == 0 {
		log.Println("QueryStringParameters is not specified.")
		return events.APIGatewayProxyResponse{
			StatusCode:      400,
			IsBase64Encoded: false,
		}, nil
	}

	id, idIsNotNull := request.QueryStringParameters["id"]
	if !idIsNotNull {
		log.Println("[QueryStringParameters]id is not specified")
		return events.APIGatewayProxyResponse{
			StatusCode:      400,
			IsBase64Encoded: false,
		}, nil
	}

	name, nameIsNotNull := employees[id]
	if !nameIsNotNull {
		log.Println("[QueryStringParameters]id is not specified")
		return events.APIGatewayProxyResponse{
			StatusCode:      404,
			IsBase64Encoded: false,
		}, nil
	}

	responseBody := responseBody{
		ID:   id,
		Name: name,
	}
	jsonBytes, _ := json.Marshal(responseBody)

	return events.APIGatewayProxyResponse{
		StatusCode:      200,
		IsBase64Encoded: false,
		Body:            string(jsonBytes),
	}, nil
}
```

# Dockerfile
Dockerfileは、[公式の開発者ガイド](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/go-image.html)に従って作ろう。
中身はほぼそのままである。RIE(後述)の部分だけ少し変更している。

```Dockerfile:Dockerfile
FROM public.ecr.aws/lambda/provided:al2 as build
# install compiler
RUN yum install -y golang
RUN go env -w GOPROXY=direct
# cache dependencies
ADD src/go.mod src/go.sum ./
RUN go mod download
# build
ADD src .
RUN go build -o /main
# copy artifacts to a clean image
FROM public.ecr.aws/lambda/provided:al2
COPY --from=build /main /main
# (Optional) Add Lambda Runtime Interface Emulator and use a script in the ENTRYPOINT for simpler local runs
ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
COPY ./script/entry.sh /
RUN chmod 755 /entry.sh

ENTRYPOINT [ "/entry.sh" ]
CMD [ "/main" ]
```

entry.shは以下のようにする。

```shell
#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
  exec /usr/bin/aws-lambda-rie "$@"
else
  exec "$@"
fi
```

AWS_LAMBDA_RUNTIME_APIが、Lambdaのマネージドサービスから起動された場合は設定されるので、設定されていない場合はローカル起動としてRIE経由での起動とする。

# Terraform
さて、これを以下のように構築する。

## ECR
コンテナイメージでのデプロイなので、まずはECRにPUSHしよう。

```ecr.tf
################################################################################
# ECR                                                                          #
################################################################################
resource "aws_ecr_repository" "getemployee" {
  name = local.ecr_getemployee_repository_name
}

data "aws_ecr_authorization_token" "token" {}

resource "null_resource" "image_push" {
  provisioner "local-exec" {
    command = <<-EOF
      docker build ../ -t ${aws_ecr_repository.getemployee.repository_url}:latest; \
      docker login -u AWS -p ${data.aws_ecr_authorization_token.token.password} ${data.aws_ecr_authorization_token.token.proxy_endpoint}; \
      docker push ${aws_ecr_repository.getemployee.repository_url}:latest
    EOF
  }
}
```

## Lambda
Lambdaは以下のように作る。

```lambda.tf
################################################################################
# Lambda                                                                       #
################################################################################
resource "aws_lambda_function" "getemployee" {
  depends_on = [
    aws_cloudwatch_log_group.lambda_getemployee,
    null_resource.image_push,
  ]

  function_name = local.lambda_getemployee_function_name
  package_type  = "Image"
  image_uri     = "${aws_ecr_repository.getemployee.repository_url}:latest"
  role          = aws_iam_role.lambda_getemployee.arn
  publish       = true

  memory_size = 128
  timeout     = 28

  lifecycle {
    ignore_changes = [
      image_uri, last_modified
    ]
  }
}

################################################################################
# IAM Role for Lambda                                                          #
################################################################################
resource "aws_iam_role" "lambda_getemployee" {
  name               = local.lambda_getemployee_iam_role_name
  assume_role_policy = data.aws_iam_policy_document.lambda_getemployee_assume.json
}

data "aws_iam_policy_document" "lambda_getemployee_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "lambda.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "lambda_getemployee" {
  role       = aws_iam_role.lambda_getemployee.name
  policy_arn = aws_iam_policy.lambda_getemployee_custom.arn
}

resource "aws_iam_policy" "lambda_getemployee_custom" {
  name   = local.lambda_getemployee_iam_policy_name
  policy = data.aws_iam_policy_document.lambda_getemployee_custom.json
}

data "aws_iam_policy_document" "lambda_getemployee_custom" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      "*",
    ]
  }
}

################################################################################
# CloudWatch Logs                                                              #
################################################################################
resource "aws_cloudwatch_log_group" "lambda_getemployee" {
  name              = "/aws/lambda/${local.lambda_getemployee_function_name}"
  retention_in_days = 3
}

################################################################################
# Lambda Alias                                                                 #
################################################################################
resource "aws_lambda_alias" "getemployee_prod" {
  name             = "Prod"
  function_name    = aws_lambda_function.getemployee.arn
  function_version = aws_lambda_function.getemployee.version

  lifecycle {
    ignore_changes = [function_version]
  }
}

################################################################################
# Lambda Permission                                                            #
################################################################################
resource "aws_lambda_permission" "allow_apigateway" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.getemployee.function_name
  principal     = "apigateway.amazonaws.com"
  qualifier     = aws_lambda_alias.getemployee_prod.name
}
```

前述の通り、超簡易な機能でAWSへのリソースアクセスはないので、IAMはCloudWatch Logsへのログ出力ができれば良い。

キモになるのは、Lambdaの以下の部分。

```HCL
  package_type  = "Image"
  image_uri     = "${aws_ecr_repository.getemployee.repository_url}:latest"
```

これで、ECRのコンテナイメージを参照しにいってくれる。
```null_resource.image_push```を待ち合せないと、コンテナイメージの参照エラーになるので、```depends_on```で順序性担保しておこう(今回のイメージのPUSHはけっこう時間かかるので、やっておかないとほぼ確実にエラーになる)。

また、この後CI/CDを回していくと、コンテナイメージのURIやバージョンは変わってしまうので、```ignore_changes```で変わっても無視するようにしておく。

同様に、CI/CDを意識して以下のようにしておく。

- エイリアスに対するデプロイをするために、```aws_lambda_function.getemployee```でpublishを設定してバージョンを払い出し、払い出したバージョンに対して```aws_lambda_alias.getemployee_prod```でエイリアスを割り付ける
- ```aws_lambda_alias.getemployee_prod```のバージョンもこの後のCI/CDで変わってしまうので、```ignore_changes```する
- API Gatewayや別のリソースと紐づける場合は、```aws_lambda_permission```でアクセス権を設定するが、ここでもエイリアスを意識できるようにするためにqualifierに```aws_lambda_alias.getemployee_prod.name```を設定する(今回のサンプルではこの後API Gatewayと接続する)

## API Gateway
API Gatewayは以下のように設定する。
ちょっと前まではAPI Gateway系のリソースをゴリゴリ定義しているが、結局中途半端にYAMLと合わせるとデグレードしまくるので、最近はYAMLに一本化するようにすることが多い。

```apigateway.tf
################################################################################
# API Gateway                                                                  #
################################################################################
resource "aws_api_gateway_rest_api" "go_container_image" {
  name = local.apigateway_name
  body = data.template_file.apigateway_body.rendered
}

data "template_file" "apigateway_body" {
  template = file("./apigateway_body.yaml")

  vars = {
    title                            = local.apigateway_name
    aws_account                      = data.aws_caller_identity.self.account_id
    aws_region_name                  = data.aws_region.current.name
    lambda_getemployee_function_name = aws_lambda_function.getemployee.function_name
  }
}

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.go_container_image.id
  deployment_id = aws_api_gateway_deployment.for_prod.id

  cache_cluster_enabled = false
  xray_tracing_enabled  = false
}

resource "aws_api_gateway_deployment" "for_prod" {
  rest_api_id = aws_api_gateway_rest_api.go_container_image.id

  triggers = {
    redeployment = sha1(jsonencode([
      data.template_file.apigateway_body.rendered,
    ]))
  }

  lifecycle {
    create_before_destroy = true
  }
}
```

```apigateway_body.yaml
swagger: "2.0"
info:
  description: "Lambdaコンテナイメージ用API Gateway"
  title: ${title}
schemes:
- "https"
paths:
  /employee:
    get:
      responses: {}
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: "arn:aws:apigateway:${aws_region_name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${aws_region_name}:${aws_account}:function:${lambda_getemployee_function_name}:Prod/invocations"
        passthroughBehavior: "when_no_match"
        timeoutInMillis: 29000
        type: "aws_proxy"
```

この後記載するCI/CDでは、エイリアスで新旧振り分けをするために、API GatewayからのLambdaのアクセスもエイリアス向けにする。つまり、YAML中のuriは

```YAML
        uri: "arn:aws:apigateway:${aws_region_name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${aws_region_name}:${aws_account}:function:${lambda_getemployee_function_name}:Prod/invocations"
```

としておく。

これで、ひとまずはLambdaコンテナイメージの自動構築までは完成だ。
デプロイして、

```shell
$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/employee?id=00001
```

とアクセスすると

```JSON
{"id":"00001","name":"Taro"}
```

という結果が得られる。

# ローカルテストをしてみよう
さて、せっかくコンテナを使うので、「これまで実際にLambdaに組み込まないと試験しにくかったんだけど……」というつらみがあったのを解消してみよう(別に、SAMとか使えばこれまでもやりようはあったけど)。Dockerfileのところにも記載したRIE(Runtime Interface Emulator)を使う。RIEについては、[公式の開発者ガイド](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-test.html)に詳しく記載されている。要は、HTTPサーバを立ててLambdaを起動してくれるのだ。

```docker build``` したら、以下のようにローカルでコンテナ起動すればよい。

```shell-session
$ docker run -p 9000:8080 [ビルドイメージ名] /main
```

```/main``` はあってもなくても問題ない。Dockerfileで、CMDで指定しているため、なくても勝手に引数は```/main```で動作する。

これで起動したサーバに対して

```shell
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"queryStringParameters":{"id": "00001"}}' | jq -r
```

と投げ込めば良い。

気を付けなければいけないのは、期待するHTTPリクエストがGETだとしても、curlではPOSTにすること。
要は、API GatewayのLambda統合プロキシを疑似っているイメージだ。Bodyについても、[API Gatewayのイベント](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format)を疑似る必要がある。

さて、これでローカルテストもできるし、なんならDocker ComposeでDynamoDB Localとつなぐことも可能だ！CIが捗る！

# というわけでCI/CDもしてみよう
CI/CDは普通にパイプラインを組んであげれば良い。
LambdaのデプロイはSAMでやるのが一般的ではあるが、Terraformとリソース管理が分割されてしまってイケていないので、今回はCodeDeployを使う。

## CodePipeline
CodePipelineはあまり捻らず、標準的に作成する。
必要に応じて承認ステージ等は追加してもらいたい。

なお、CodeDeployのプロバイダでは、インプットとなるアーティファクトは1種類しか設定できない。
今回は、CI/CDの中でLambdaのバージョンを新規に払いして動的にAppSpecを作成するため、ビルド時のアーティファクトをインプットにする。

```codepipeline.tf
################################################################################
# CodePipeline                                                                 #
################################################################################
resource "aws_codepipeline" "lambda_container_image" {
  name     = local.codepipeline_pipeline_name
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    type     = "S3"
    location = aws_s3_bucket.artifact.bucket
  }

  stage {
    name = "Source"

    action {
      run_order        = 1
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["SourceArtifact"]

      configuration = {
        RepositoryName = local.codecommit_repository_name
        BranchName     = "master"
      }
    }
  }

  stage {
    name = "Build"

    action {
      run_order        = 2
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["SourceArtifact"]
      output_artifacts = ["BuildArtifact"]

      configuration = {
        ProjectName = aws_codebuild_project.lambda_container_image.name
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      run_order = 3
      name      = "Deploy"
      category  = "Deploy"
      owner     = "AWS"
      provider  = "CodeDeploy"
      version   = "1"
      input_artifacts = [
        "BuildArtifact",
      ]

      configuration = {
        ApplicationName     = aws_codedeploy_app.lambda_container_image.name
        DeploymentGroupName = aws_codedeploy_deployment_group.lambda_container_image.deployment_group_name
      }
    }
  }
}
```

## CodeBuild
CodeBuildもそれほど難しくはない。ZipのLambdaの場合は```privileged_mode```は不要だが、今回はコンテナを扱うためtrueに設定しておこう。```environment_variable```では、この後出てくるBuildSpecに必要な環境変数を渡しておく。

```codebuild.tf
################################################################################
# CodeBuild                                                                    #
################################################################################
resource "aws_codebuild_project" "lambda_container_image" {
  name         = var.codebuild_project_name
  service_role = aws_iam_role.codebuild.arn

  source {
    type      = "CODEPIPELINE"
    buildspec = "buildspec.yml"
  }

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
    privileged_mode = "true"

    environment_variable {
      name  = "AWS_REGION_NAME"
      value = var.aws_region_name
    }
    environment_variable {
      name  = "ECR_REPOSITORY_NAME"
      value = var.ecr_repository_name
    }
    environment_variable {
      name  = "LAMBDA_FUNCTION_NAME"
      value = var.lambda_function_name
    }
  }

  logs_config {
    cloudwatch_logs {
      group_name = aws_cloudwatch_log_group.codebuild.name
    }
  }

  cache {
    type = "LOCAL"
    modes = [
      "LOCAL_DOCKER_LAYER_CACHE",
    ]
  }
}
```

## BuildSpecとAppSpec
BuildSpecの中で払い出したLambdaのバージョンをAppSpecに設定したいので、AppSpecは以下のように```envsubst```で環境変数を渡せるようにしておく。

```appspec_template.yaml
version: 0.0
Resources:
  - LambdaFunction:
      Type: AWS::Lambda::Function
      Properties:
        Name: "${LAMBDA_FUNCTION_NAME}"
        Alias: "Prod"
        CurrentVersion: "${LAMBDA_CURRENT_VERSION}"
        TargetVersion: "${LAMBDA_TARGET_VERSION}"

```

BuildSpecでは、上述したDockerfileを```docker build -t```して、ECRにPUSHする。
ここは、ECSのコンテナビルドに似ているのでわかりやすい。ECSのビルド同様、GitのCommitハッシュをタグにして冪等性を担保しよう。

```buildspec.yml
version: 0.2
 
phases:
  build:
    commands:
      - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
      - REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com/${ECR_REPOSITORY_NAME}
      - echo Container build started on `date`
      - docker build -t ${REPOSITORY_URI}:latest .
      - echo Container build finished on `date`
  post_build:
    commands:
      - echo ECR login started on `date`
      - $(aws ecr get-login --region ${AWS_REGION_NAME} --no-include-email)
      - echo ECR login finished on `date`

      - IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)
      - docker tag ${REPOSITORY_URI}:latest ${REPOSITORY_URI}:${IMAGE_TAG}
      - echo ECR image push started on `date`
      - docker push ${REPOSITORY_URI}:${IMAGE_TAG}
      - docker push ${REPOSITORY_URI}:latest
      - echo ECR image push finished on `date`

      - echo Lambda publish started on `date`
      - PUBLISH_RESULT=$(aws lambda update-function-code --function-name ${LAMBDA_FUNCTION_NAME} --image-uri ${REPOSITORY_URI}:${IMAGE_TAG} --publish)
      - echo ${PUBLISH_RESULT}
      - echo Lambda finished started on `date`

      - export LAMBDA_CURRENT_VERSION=$(aws lambda get-alias --function-name ${LAMBDA_FUNCTION_NAME} --name Prod | jq -r .FunctionVersion)
      - export LAMBDA_TARGET_VERSION=$(echo ${PUBLISH_RESULT} | jq -r .Version)
      - envsubst < appspec_template.yaml > appspec.yaml
artifacts:
  files:
    - appspec.yaml
  discard-paths: yes
```

上記の通り、```LAMBDA_CURRENT_VERSION```はCLIの```aws lambda get-alias```でエイリアスの最新版を取得する。また、LAMBDA_TARGET_VERSIONは、ビルド後のリソースをpublishした出力から取得する。
このため、CodeBuildのロールには以下の権限を付与しておこう。

- lambda:GetAlias
- lambda:UpdateFunctionCode

これらの変数を```envsubst```で置換して、CodeDeployに渡してあげよう。
ちなみに、appspec.yamlはappspec.ymlにするとCodeDeployがコケるので注意。

## CodeDeploy
CodeDeployは特に難しいことはなく以下のように設定すれば良い。

```codedeploy.tf
################################################################################
# CodeDeploy                                                                   #
################################################################################
resource "aws_codedeploy_app" "lambda_container_image" {
  name             = var.codedeploy_application_name
  compute_platform = "Lambda"
}

resource "aws_codedeploy_deployment_group" "lambda_container_image" {
  deployment_group_name  = var.codedeploy_deployment_group_name
  app_name               = aws_codedeploy_app.lambda_container_image.name
  service_role_arn       = aws_iam_role.codedeploy.arn
  deployment_config_name = "CodeDeployDefault.LambdaCanary10Percent5Minutes"

  deployment_style {
    deployment_type   = "BLUE_GREEN"
    deployment_option = "WITH_TRAFFIC_CONTROL"
  }
}
```

これでパイプラインを走らせると、5分間は10%のトラフィックが新バージョンに流れ、90%のトラフィックが旧バージョンに流れるようになる。
SAMのCI/CDとほぼ同様の動作の完成だ！

# というわけでCI/CDもしてみよう(SAMテンプレート編)
とは言え、これまでLambdaのCI/CDを作ってきた人は、CodePipelineのDeployステージのプロバイダをCloudFormationにして、SAMテンプレートでデプロイするという方法を選択したい人もいるだろう。(SAMテンプレートでコンテナイメージ**ではない**Lambdaをデプロイするサンプルは[以前の記事](https://qiita.com/neruneruo/items/73906cdc3c2aa699644f)参照)

SAMテンプレートは、ちゃんとコンテナイメージのLambdaにも対応している。
参考になるのは[クラスメソッド先生の記事](https://dev.classmethod.jp/articles/deploy-container-image-lambda-function-with-cloudformation/)だが、これはSAMテンプレートではなく、変換なしのCloudFormationテンプレートだ。

[AWS公式のSAMに関するデベロッパーガイド](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-function.html)で以下のように記載されている。

> ImageUri
Lambda 関数のコンテナイメージ用の Amazon Elastic Container Registry (Amazon ECR) リポジトリの URI です。このプロパティは、PackageType プロパティが Image に設定されている場合にのみ適用され、それ以外の場合は無視されます。詳細については、AWS Lambda デベロッパーガイドの「Lambda でのコンテナイメージの使用」を参照してください。
> 
> 注意:PackageType プロパティが Image に設定されている場合は、ImageUri が必要になります。または、AWS SAM テンプレートファイルで必要な Metadata を使用してアプリケーションを構築する必要があります。詳細については、「アプリケーションの構築」を参照してください。
> 
> 必要な Metadata エントリを使用してアプリケーションを構築することは、ImageUri よりも優先されるので、両方を指定すれば ImageUri は無視されます。
> 
> 型: 文字列
> 
> 必須: いいえ
> 
> AWS CloudFormation との互換性: このプロパティは、AWS::Lambda::Function Code データ型の ImageUri プロパティに直接渡されます。

CloudFormationではCodeの配下になるが、SAMの場合はImageプロパティと同列に記載して良いので、

```YAML:template.yml抜粋
      PackageType: Image
      ImageUri: !Ref 'EcrImageUri'
```

と書いておけば良い。

しかし、CI/CDで対応する場合、CloudFormationに対して変更後のイメージURIを動的に渡すことができない。
タグをlatestにしておけば気にする必要はないが、latestを参照してしまうとバージョンを戻してもECR側が戻らないため運用上の問題が発生する。

ここは、SAMテンプレートを

```YAML:template.yml抜粋
      PackageType: Image
      ImageUri: ${EcrImageUri}
```

としておき、CodeBuild側で以下のようにenvsubstしてビルドアーティファクトとしてCloudFormationに継承しよう。

```YAML:buildspec.yml抜粋
phases:
  # (中略)
  post_build:
    commands:
      - export EcrImageUri=[イメージURI]:[タグ]
      - envsubst '${EcrImageUri}' < template.yml > package.yml
  # (中略)
artifacts:
  type: zip
  files:
    - package.yml
```

これで、従来のCI/CDを大きく見直さなくてもコンテナイメージのLambdaをデプロイできるようになった！