terraformで構成管理しつつaws lambdaをデプロイする

DMMデータインフラ部に所属している[yuua](https://qiita.com/yuua0216)です
terraformで作成しlambdaをデプロイする際にterraformにlambda関数を含めるとchangeがでたり、扱いづらく
悩むときがあるのでその際の手順的な備忘録です

今回のlambdaはlambda containerではありません

## 初回の手順

1.terraformで基本的なlambdaの事前の情報を作成する(配置先など)
2.lambda関数をzip化しs3バケットに配置する

## 継続的なデプロイの手順

基本的にgithub actionsなどのCI/CDを使います

1.github actionsでlambda function / lambda layerをバケットに配置
2.github actionsでupdate functionの実行

## 下準備

lambdaを配置するようのs3 keyなどをterraformで作成します

```s3.tf
// bucket作成
resource "aws_s3_bucket" "lambda_bucket" {
  bucket = "lambda-bucket"
}

// lambda関数配置するようのkeyを作成
resource "aws_s3_bucket_object" "lambda_function" {
  key    = "functions/nodejs/"
  bucket = aws_s3_bucket.lambda_bucket.id
}

// layerを使う場合はlayer用も
resource "aws_s3_bucket_object" "lambda_layer" {
  key    = "layers/nodejs/hoge/"
  bucket = aws_s3_bucket.lambda_bucket.id
}
```

これで一旦 `terraform apply` を実施する

## lambda functionを配置

default のnode.js lambda

```aiu.js
exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};
```

上記を `zip` 化し先ほど作成したs3のkeyに配置します。
配置自体はcliでもconsoleでもciでも配置できれば何でも良いです

## lambdaのリソース定義

関数をs3に配置したらterraformでlambda関数を定義します

```lambda.tf
data "aws_iam_policy_document" "role" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      identifiers = ["lambda.amazonaws.com"]
      type        = "Service"
    }
  }
}

resource "aws_iam_role" "role" {
  name               = "${var.function_name}-lambda"
  assume_role_policy = data.aws_iam_policy_document.role.json
}

// logginなど必要であれば、適宜roleにattachなどしてください

// 関数定義
resource "aws_lambda_function" "function" {
  function_name                  = var.function_name
  handler                        = var.handler
  role                           = aws_iam_role.role.arn
  runtime                        = var.runtime
  s3_bucket                      = bucketを指定
  s3_key                         = 配置したkeyを指定
  layers                         = [layerを使うのであれば指定]
}

```

`terraform apply`を実施する

上記でlambda関数の作成自体は完了です
publishなどのoptionは適宜

## 継続的なデプロイをするために

基本的に一度作ったら終わりということはあまりないと思うので継続的なdeployをするために定義を作成します

ここではgithub actionsを定義して、Pull Requestから各branchにPR close/mergeされたタイミングでdeployできるようにします
外部からdeployする際は `iam` で 必要な権限を付与したuserを作成し、そのユーザのsecretを使います。

ざっと必要になりそうな権限

```
s3:PutObject
s3:GetObject
s3:DeleteObject
s3:ListBucket
lambda:GetFunction
lambda:UpdateFunctionCode
lambda:UpdateFunctionConfiguration
// layerがいるなら
lambda:GetLayerVersion
lambda:PublishLayerVersion
lambda:ListLayerVersions
lambda:AddLayerVersionPermission
```
 
## github actions定義

### lambda function

下記はセルフホストランナーでの定義ですが通常通りgihtub hosting/セルフホスティングどちらでも問題ないです

```function.deploy.yml
name: deploy-function

on:
  pull_request:
    branches:
      - ブランチ名
    types: [closed]

jobs:
  update-functions:
    name: lambda deploy
    runs-on: [self-hosted, linux]
    if: github.event.pull_request.merged == true // mergeされたら処理継続
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: code build
        uses: actions/setup-node@v2
        with:
          node-version: "14"
          cache: "npm"
      - run: npm install // 必要であれば
      - run: npx webpack // buildなどあれば

      // aws credentialを設定
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: リージョン

      // zipファイルを作成
      - name: compression
        working-directory: ./dist
        run: zip function.zip main.js

      - name: lambda update function codes
        working-directory: ./dist
        run: |
          aws lambda update-function-code --function-name function名 --zip-file fileb://function.zip
      // 必要であればpublish
```

コードをzip化し `aws cli` の `update-function-code`を実行しています
これでPR Close/merge時にdeployが実行されます

### lambda layer

layerはnodeの場合 `nodejs/node_modules` をzip化する必要があります
ここではlambda functionとは別にlayer用のディレクトリを定義しそこにpackage.jsonをおいています 

```layer.deploy.yml
name: lambda-layer-deploy

on:
  pull_request:
    branches:
      - develop
    types: [closed]
    paths:
      - "layerのpath/**"

jobs:
  update-functions:
    name: lambda layer deploy
    runs-on: [self-hosted, linux]
    if: github.event.pull_request.merged == true
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: code build
        uses: actions/setup-node@v2
        with:
          node-version: "14"
          cache: "npm"

      - name: lambda layer
        working-directory: ./layer
        run: |
          mkdir nodejs
          cp package*.json nodejs/
          npm install --prefix ./nodejs --production
          zip -r package.zip nodejs/node_modules

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: リージョン

      - name: lambda layer deploy
        working-directory: ./layer
        run: aws lambda publish-layer-version --layer-name layer名 --zip-file fileb://package.zip --compatible-runtimes nodejs14.x

      // 現在のlatestを取得
      - name: lambda layer latest version
        id: step1
        run: |
          layer=$(aws lambda list-layer-versions --layer-name layer名 --query 'LayerVersions[0].LayerVersionArn')
          echo "::set-output name=layer_ver::$layer"

      - name: lambda update configure
        run: |
          aws lambda update-function-configuration --function-name function名 --layers ${{ steps.step1.outputs.layer_ver }} //複数layerがある場合は ${{ hogehoge }} ${{ foobar }} このような形で続けてかけます 
```

# まとめ
terraformでlambdaのリソースを作成しつつ継続的なdeployはCI/CDで行う環境を備忘録的な形でまとめました。
IaCとlambda関数のcodeが分離され、意識することもすくないのでlambda containerを使わない場合は
今のところ一番使いやすいかなと思っています。

データインフラ部では、AWSでのビッグデータ基盤関連の運用を行っており、
様々リソースを活用したプロダクト開発を行っています。
中途採用などもおこなっておりますので、興味のある方は、一度弊社HPなどから
カジュアル面談など、是非ご応募ください。