見出し画像

俺たち流! Lambda 関数開発:Codespaces × Terraform × GitHub Actions の活用術


今回のテーマ:Lambda 関数


こんにちは、TOPPANデジタルで AWS Top Engineers を目指している奈良です。今回のテーマは 「Lambda 関数」です。AWS サービスの中でも頻繁に利用される Lambda 関数は、アプリケーション開発だけでなく、自動化や監視など多用途に活用できる非常に優秀なサービスですね。

課題:汎用性が高いゆえの悩み


しかし、Lambda 関数はその汎用性の高さから、開発・管理・デプロイの方法が多岐にわたります。様々な案件や記事、公式ドキュメントを少し調べただけでも、多くの選択肢が見つかります。そこで、私たちが実案件を通して試行錯誤した3つのパターンからから、開発・管理・デプロイに特に効果的だったパターンをご紹介したいと思います!

検証:(俺たち流)開発手法


私たちは「通常のアプリケーションと同様の方法」で開発を行うことにいたしました。具体的な方法に入る前に、このアプローチの背景について説明します。Lambda は AWS サービスでありながら、アプリケーションとしても機能します。そのため、これらを無理に一つにまとめるのではなく、AWS サービスとアプリケーションを分離して管理する方法を選びました。

  • アプリケーション部分は通常の開発・管理

  • AWS サービスは Terraform で構築・管理・デプロイ

処理の流れイメージ

それでは具体的な方法の解説をしていきたいと思います。まずは、全体の流れですが、以下の3つのステップです。流れ自体には特別なステップや複雑なロジックなどは、含めていません。

  • Lambda 関数のソースコードを pullrequest

  • pullrequest が branch に merge されると Githubactions が起動

  • Terraform にて AWS へ apply

構成イメージ
上記の処理の流れを実現するために、さらに詳細にディレクトリ構成などをご紹介します。まず、Lambda 関数をデプロイする Terraform と Lambda 関数のソースコードを同一リポジトリで管理しています。
以下に、ディレクトリ構成、Lambda 関数の設定とポリシー、GitHub Actions の yml ファイル構成をご紹介します。doc、src、test コードについては、皆さまのご想像通りかと思いますので割愛いたします。
ポイントは、各 Lambda 関数ごとに doc、src、test、terraform のコードを1つのセットにしています。

ディレクトリ構成

app
├ .github              # GitHub Actions用のディレクトリ
├ template             # Lambda関数ディレクトリ内に配置する設定ファイルのテンプレート
│ └ terraform
├ terraform            # Terraformのコード
└ lambda_func_A        # Lambda関数のディレクトリ(Lambda関数名と同一のディレクトリ名)
   ├ doc               # Lambda関数のREADMEを格納
   ├ src               # Lambda関数にアップロードするコードを格納
   ├ test              # テストコードを格納
   └ terraform         # Terraform用設定ファイルを格納(環境ごとに準備)
      ├ dev.lambda.config.json    # Lambda関数の設定ファイル
      ├ dev.policy_statement.json # Lambda関数で使用するIAMロールのポリシー記載
      └ requirements.txt          # Lambda関数にレイヤーを設定
├ lambda_func_B
├ lambda_func_C
… (以下略)

lambda.config.json.tmpl ( Lambda 関数の設定を記述するファイル)

{
  "source_dir": "../../../../{edit here}/src/",
  "function_name": "ai-proof-dev-textapi-force-shutdownv2",
  "handler": "lambda_function.lambda_handler",
  "runtime": "python3.10",
  "environment": [
    {"name": "ENV_VAL1", "value": "value1"},
    {"name": "ENV_VAL2", "value": "value2"}
  ],
  "ephemeral_storage_size": 512,
  "role_name": "${var.role_name}",
  "file_system_arn": "",
  "local_mount_path": "",
  "subnet_ids": [],
  "security_group_ids": [],
  "timeout": 20,
  "memory_size": 128,
  "policy_statement_path": "../../../../{edit here}/terraform/policy_statement.json",
  "managed_policy_arns": [
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess",
    "arn:aws:iam::aws:policy/CloudWatchFullAccessV2"
  ]
}

policy_statement.json.tmpl ( Lambda 関数で使用するIAMロールのポリシー記載)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "arn:aws:logs:ap-northeast-1:${account_id}:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "${cloudwatch_logs_arn}:*"
      ]
    }
  ]
}

GitHub Actions の yml ファイル(※SECRET は GitHub の SECRET に値をセットし、デプロイ時に取得しています)

name: Develop Deploy

on:
  push:
    branches:
      - develop

env:
  TF_VERSION: 1.6.2
  AWS_ACCESS_KEY_ID: ${{ secrets.DEV_ACCESS_KEY }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_SECRET_ACCESS_KEY }}
  DEV_TFSTATE_BACKEND: ${{ secrets.DEV_TFSTATE_BACKEND }}
  DEV_TFSTATE_KEY: ${{ secrets.DEV_TFSTATE_KEY }}
  DEV_ROLE_ARN: ${{ secrets.DEV_ROLE_ARN }}
  AWS_REGION: ${{ secrets.DEFAULT_REGION }}
  TFSTATE_ENCRYPT: ${{ secrets.TFSTATE_ENCRYPT }}
  TF_VAR_role_arn: ${{ secrets.DEV_TERRAFORM_ARN }}
jobs:
  terraform_apply:
    name: Terraform apply
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read
      pull-requests: write

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.DEV_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}
      
      - name: Check Branch
        run: |
          if ["${{ github.event_name }}" == "push"] && [ "${{ github.ref }}" != "refs/heads/develop" ]; then
            return
          fi

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: ${{ env.TF_VERSION }}
      
      - name: Execute create paths file
        working-directory: terraform/src/environment/dev
        run: |
          chmod 755 ./create_paths_file.sh
          ./create_paths_file.sh
          ls -l
      
      - name: Execute create layer file
        working-directory: terraform/src/environment/dev
        run: |
          chmod 755 ./create_layer_file.sh
          ./create_layer_file.sh

      - name: Create backend config
        working-directory: terraform/src/environment/dev
        run: envsubst < dev.tfbackend.tmpl > dev.tfbackend

      - name: init
        working-directory: terraform/src/environment/dev
        run: |
          ls -l
          cat ./dev.tfbackend
          terraform init -backend-config=dev.tfbackend

      - name: apply
        working-directory: terraform/src/environment/dev
        run: terraform apply -auto-approve

所感:実際に導入してみた効果


Lambda 関数の自由度と柔軟性を活かして、以下のような課題を解決し、効果的な開発・管理・デプロイのパターンを確立できました。
・チームメンバーが使い慣れた環境(VSCode や Codespaces、GitHub)での開発・コード管理が可能
・ AWS へデプロイする前にユニットテストが可能
・AWS コンソール上での開発時に発生しがちなセキュリティ事故や他リソースへの影響を防ぎ、コストを抑えることができる

〆:さいごに


最後までお読みいただき、ありがとうございました。今回ご紹介した方法は一例に過ぎません。他にも、AWS SAM を利用した方法などが存在します。今後も試行錯誤を重ね、より良い方法を皆さまにお届けできるよう努めます。
それでは、次回の記事もお楽しみに!

参考


本記事の内容を以下のLT会でも紹介し登壇資料もアップしております。なお、登壇時間の都合上、LT会ではご紹介できなかったコードも記載しております!