コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

10分で作る! PHP x Lambda 環境 by AWS CDK

こちらの記事は「コドモン Advent Calendar 2023」の 22日目の記事です🎅

qiita.com

こんにちは、プロダクト開発部の西銘です!

最近、業務でPHPをLambdaで動かす実装をしたのですが、環境構築がちょっと面倒だったので、
AWS CDK (以下、CDK) を使って簡単に始める方法をご紹介しようと思います!

題して、 10分で作る! PHP x Lambda 環境 by AWS CDK の始まり〜

はじめに

前提
  • 使う技術
    • AWS CDK ( 2.x )
    • Docker ( 24.x )
    • TypeScript ( 5.x )
    • Node.js( 20.x )
    • PHP ( 8.2.x )
    • bref ( 2.1.x )
  • 事前知識
    • Docker操作
      • ローカル環境でDocker CLI を叩けるようにしておいてください
    • AWS認証情報
      • ~/.aws/credentials に必要な認証情報が入ってる前提で進めます
なぜ PHP x Lambda ?

弊社ではバックエンドの7割くらいはPHPで書かれています。
そのため、ビジネスロジックを別言語に分散させずに再利用してLambdaを構築したい
という開発要件を満たすために、業務でも採用されました。

なぜ AWS CDK ?

CDKは他のIaCツールと比較して以下のメリットがあります。

  • プログラミング言語でAWSリソースを構築できる
    • AWSリソースの構築に不慣れな方でも始めやすい
    • ( この記事ではTypeScriptを採用しています )
  • IDEのコード補完が豊富
    • SDKの型補完や内部の実装追跡などが容易に出来るため、IDE上で実装が完結しやすい
  • 必要なIAM権限を最小限で自動作成する
    • AWSリソースになれない人が最初に感じる壁であるIAM権限をほぼ意識しないで構築できる

これらのメリットが私が関わった業務でも他の開発者にも受け入れられ、
実際に私以外の開発者もAWSリソース構築に関わることが容易にできたため、
この記事でも採用しました。
( もっと詳しくCDKについて知りたい!という方はぜひ 公式ガイド をご覧ください )

PHP x Lambda 環境の構築手順

1. 作業環境準備

まず最初に、CDKコマンドを使うための作業環境をDockerで用意しましょう。

$ mkdir ~/10PunPhpLambda
$ touch ~/10PunPhpLambda/docker-compose.yml
$ touch ~/10PunPhpLambda/Dockerfile

作成したディレクトリに作業環境を作ったら移動して中身を確かめておきましょう。
※この後の作業では基本的に ~/10PunPhpLambda にいる前提でコマンドを叩きます

$ cd ~/10PunPhpLambda/
$ ls -1
docker-compose.yml
Dockerfile

次に、中身を空で作成した docker-compose.ymlDockerfile に下記を入力してください。

docker-compose.yml

version: '3'

services:
  local-cdk:
    build: ./
    tty: true
    volumes:
      - ./app:/usr/src/app
      - ~/.aws/:/root/.aws/
  local-composer:
    image: composer
    command: composer require bref/bref
    volumes:
      - ./app/src:/app
  local-bref:
    image: bref/php-82-fpm-dev
    volumes:
      - ./app/src:/var/task
    command: ["vendor/bin/bref-local", "index.php", "$PARAM"]
    environment:
        HANDLER: index.php

Dockerfile

FROM node:20

WORKDIR /usr/src/app

RUN npm -g i typescript aws-cdk

Dockerfile は node 環境に cdk をインストールしただけのシンプルな構成です。
docker-compose.yml は作成したcdk, composer, bref のコンテナ環境とローカル環境をVolumeで紐付けてます。
これで作業環境は準備完了です!

2. CDKプロジェクト構築

続いて、CDKプロジェクトを構築していきましょう。
まずは作成したDockerを立ち上げましょう。

$ docker compose up local-cdk -d

次に、立ち上げたDockerコンテナの中に入ります。

$ docker compose exec local-cdk bash

( 以降、コンテナ内作業は # 始まりとします )

コンテナ環境に入りましたら、下記コマンドを実行して早速CDKプロジェクトを構築しましょう

# cdk init --language=typescript

実行が完了すると、下記のような構成になっていると思います。

10PunPhpLambda/app
|-- README.md
|-- bin
|   `-- app.ts
|-- cdk.json
|-- jest.config.js
|-- lib
|   `-- app-stack.ts
|-- package-lock.json
|-- package.json
|-- test
|   `-- app.test.ts
`-- tsconfig.json

こちらの構成について少し補足すると、注目していただきたいのは bin/app.tslib/app-stack.ts です。

  • lib/app-stack.ts
    • CDK で構築するAWSリソースが定義されるファイルです
    • ここで記述したコードが、実際にAWSリソースとして作られます
  • bin/app.ts
    • CDK のエントリポイントのファイルです
    • lib/app-stack.ts で定義されたリソースを呼び出す箇所です

lib/app-stack.ts を修正してデプロイコマンドを叩くことで実際にAWS環境にリソースが作られます
後ほどPHPコードを書いたら実際に修正しますが、一旦置いておきます。

下記コマンドでコンテナの外に出て次の手順に進みましょう。

# exit;
3. PHPのサンプルコード準備

では、CDKプロジェクトができたので、PHPのサンプルコードを作りましょう。
appディレクトリの下にsrc ディレクトリを作り、さらにその配下に index.php を追加します。

$ mkdir app/src
$ touch app/src/index.php

中身が空の index.php に下記を入力しましょう。

index.php

<?php
require __DIR__ . '/vendor/autoload.php';

return function ($event, $context) {
    print_r($event);
    print_r($context);
    return 'Hello ' . ($event['name'] ?? 'world');
};

index.php を追加したら、LambdaでPHPを動かすためのライブラリである bref を準備します。
下記コマンドを実行して composer で追加しましょう。

$ docker compose up local-composer -d

bref 追加後は src ディレクトリ配下は下記のようになっていればOKです。

$ ls -1 app/src 
composer.json
composer.lock
index.php
vendor

ここまで書いたらPHPのサンプルコードは完成です!

4. ローカル実行検証

ではサンプルコードができたので、早速ローカル実行してみましょう。
ローカル実行は下記コマンドを実行するだけです。

$ docker compose up local-bref

実行すると下記結果が表示されます。

WARN[0000] The "PARAM" variable is not set. Defaulting to a blank string. 
[+] Running 19/1
 ✔ local-bref 18 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿]      0B/0B      Pulled                                   123.2s 
[+] Running 1/1
 ✔ Container 10PunPhpLambda-local-bref-1  Created                                                               0.3s 
Attaching to 10PunPhpLambda-local-bref-1
10PunPhpLambda-local-bref-1  | START
10PunPhpLambda-local-bref-1  | Bref\Context\Context Object
10PunPhpLambda-local-bref-1  | (
10PunPhpLambda-local-bref-1  |     [awsRequestId:Bref\Context\Context:private] => fake-aws-request-id
10PunPhpLambda-local-bref-1  |     [deadlineMs:Bref\Context\Context:private] => 1701736394000
10PunPhpLambda-local-bref-1  |     [invokedFunctionArn:Bref\Context\Context:private] => fake-invoked-function-arn
10PunPhpLambda-local-bref-1  |     [traceId:Bref\Context\Context:private] => fake-trace-id
10PunPhpLambda-local-bref-1  | )
10PunPhpLambda-local-bref-1  | END Duration: 4 ms Max Memory Used: 1 MB
10PunPhpLambda-local-bref-1  | 
10PunPhpLambda-local-bref-1  | "Hello world"
10PunPhpLambda-local-bref-1 exited with code 0

“Hello world” の文字が見えたら成功です!
せっかくですし、Lambdaに引数を渡して実行する方法も見てみましょう。
下記コマンドを実行してください。

PARAM='{"name":"local test"}' docker compose up local-bref

上記は環境変数経由で $event 変数に入るJSONパラメータを指定して実行したコマンドです。
実行すると下記結果が表示されます。

[+] Running 1/1
 ✔ Container 10PunPhpLambda-local-bref-1  Recreated                                                             0.4s 
Attaching to 10PunPhpLambda-local-bref-1
10PunPhpLambda-local-bref-1  | START
10PunPhpLambda-local-bref-1  | Array
10PunPhpLambda-local-bref-1  | (
10PunPhpLambda-local-bref-1  |     [name] => local test
10PunPhpLambda-local-bref-1  | )
10PunPhpLambda-local-bref-1  | Bref\Context\Context Object
10PunPhpLambda-local-bref-1  | (
10PunPhpLambda-local-bref-1  |     [awsRequestId:Bref\Context\Context:private] => fake-aws-request-id
10PunPhpLambda-local-bref-1  |     [deadlineMs:Bref\Context\Context:private] => 1701736719000
10PunPhpLambda-local-bref-1  |     [invokedFunctionArn:Bref\Context\Context:private] => fake-invoked-function-arn
10PunPhpLambda-local-bref-1  |     [traceId:Bref\Context\Context:private] => fake-trace-id
10PunPhpLambda-local-bref-1  | )
10PunPhpLambda-local-bref-1  | END Duration: 4 ms Max Memory Used: 1 MB
10PunPhpLambda-local-bref-1  | 
10PunPhpLambda-local-bref-1  | "Hello local test"

print_r で標準出力した内容がログとして出ていることと、
最後に return した文字列が、 "Hello " と引数で渡した name の値が結合した文字列が表示されていれば成功です。

5. AWS環境デプロイ準備

ローカル実行ができたところで、実際にAWS環境にデプロイする準備をしましょう。

Lambdaをデプロイするためにまず app/lib/app-stack.ts のコードを下記のように書き換えます。

app/lib/app-stack.ts

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class AppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * NOTE:
     *   brefRuntimeLayerArnは、下記サイトを参考に対象のレイヤー(php82の最新)を指定する
     *   https://runtimes.bref.sh/?region=ap-northeast-1
     **/
    const brefRuntimeLayerArn = "arn:aws:lambda:ap-northeast-1:534081306603:layer:php-82:52"
    const brefLayer = cdk.aws_lambda.LayerVersion.fromLayerVersionArn(this, "php-82", brefRuntimeLayerArn)

    new cdk.aws_lambda.Function(this, "10PunPhpLambda", {
      runtime: cdk.aws_lambda.Runtime.PROVIDED_AL2,
      handler: "index.php",
      code: cdk.aws_lambda.Code.fromAsset("./src"),
      memorySize: 512,
      layers: [brefLayer],
    });
  }
}

書いてる内容としては、下記部分でLambdaでPHPを動かすために必要なLambdaレイヤーの設定をしています。

const brefRuntimeLayerArn = "arn:aws:lambda:ap-northeast-1:534081306603:layer:php-82:52"
const brefLayer = cdk.aws_lambda.LayerVersion.fromLayerVersionArn(this, "php-82", brefRuntimeLayerArn)

brefRuntimeLayerArn の値は bref が公式で配布しているARNパスなので、デプロイしたいPHPバージョンに合わせて変えてください。
( 私のAWSアカウントIDが漏洩しているわけではないのでご安心ください )

定義したLambdaレイヤーを設定したLambdaリソースの定義を下記で実装しています。

new cdk.aws_lambda.Function(this, "10PunPhpLambda", {
  runtime: cdk.aws_lambda.Runtime.PROVIDED_AL2,
  handler: "index.php",
  code: cdk.aws_lambda.Code.fromAsset("./src"),
  memorySize: 512,
  layers: [brefLayer],
});

その他の詳細を説明していると10分すぎてしまうので割愛しますが、
気になる方はぜひCDK の公式ドキュメントを読んでみてください!

CDKのコードの準備ができたらデプロイの準備です。
再びコンテナの中に入りましょう。

$ docker compose exec local-cdk bash

下記コマンドを実行してデプロイコマンドを叩ける準備をしましょう。
( このコマンド実行は、1つのCDKプロジェクトで一度実行したらその後は実行不要です )

# cdk bootstrap

Unable to resolve AWS account to use. It must be either configured when you define your CDK Stack, or through the environment
root@************:/usr/src/app# cdk bootstrap --profile codmon-sandbox
 ⏳  Bootstrapping environment aws://************/ap-northeast-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.

 ✨ hotswap deployment skipped - no changes were detected (use --force to override)

 ✅  Environment aws://************/ap-northeast-1 bootstrapped (no changes).

実行が完了すると上記のような内容が出力されます。エラー等出てなければ成功です。
※cdkコマンド実行の際に、認証にProfileの指定が必要な場合は --profile {profile名} オプションをつけてください

6. AWS環境デプロイ実行

準備ができたら下記コマンドを実行していざAWS環境にデプロイです!

# cdk deploy

※こちらも認証にProfileの指定が必要な場合は --profile {profile名} オプションをつけてください

たくさんの標準出力が流れるのを見守り、途中で「 Do you wish to deploy these changes (y/n)? 」と聞かれるので「y」を入力してEnter押してください。
下記のような文字が見えたら完了です!

AppStack: creating CloudFormation changeset...

 ✅  AppStack

✨  Deployment time: 47.21s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:************:stack/AppStack/************-****-****-****-********************

✨  Total time: 58.67s

デプロイが完了したので、AWSマネジメントコンソール画面を開き、Lambdaの関数一覧を見ると、、、

デデーーン!!

PHPLambda画像1

あっという間にPHP x Lambda 環境の完成です!

少し補足すると、CDKで作成したリソースは基本的に自動で命名されます。
無理やり指定した名前をつけることもできますが、基本的には自動命名を使うのがベストプラクティスです。
( ちょっとわかりづらいですよねー、、 )

ついでに画面の赤線部分を選択していくとAWSマネジメントコンソール画面上からテスト実行が出来るのでやってみましょう。

PHPLambda画像2

デデーーン!!

PHPLambda画像3

AWS環境上でも実行可能なことを確認できました!
ここまで出来た方は大成功ですね!

以上、「PHP x Lambda 環境の構築」でした。

PHP x Lambda 環境お掃除

1. CDKでリソースを削除

PHP x Lambda 構築を一通り楽しんだら最後にリソースを削除していきましょう。
まず始めに、下記コマンドを実行してください。

# cdk destroy

※こちらも認証にProfileの指定が必要な場合は --profile {profile名} オプションをつけてください。

このコマンド実行の際に「 Are you sure you want to delete: AppStack (y/n)? 」と聞かれるので、「y」を入力してEnterしてください。
こちらが完了し、下記が出力されるとCDKで作成したリソースはほぼすべて削除されました。

Are you sure you want to delete: AppStack (y/n)? y
AppStack: destroying... [1/1]

 ✅  AppStack: destroyed
2. CDKで削除できないリソースを削除

実は cdk destroy コマンドを実行しても消せないリソースがあるので、そちらも消していきましょう。

AWSマネジメントコンソール画面を開き、CloudWatchの画面を開いてください。
サイドバーでロググループを選択すると、先ほど作成・実行したLambdaのログが残っています。
こちらは手動で削除してあげてください。

これで後片付けは完了です。
もし、今回の作業のためだけにIAMユーザー・アクセスキーを作った方がいたらこちらも削除しておくと安全です。

おわりに

いかがでしたしょうか?
この記事は、私と同じようにPHP x Lambda 環境をサクッと試したいだけなのに色々インストールするの面倒だなぁと思ってる方や、
普段AWSやIaCに触る機会が少ない方、Lambdaを触ってみたいけどやったことない方のためになればと思い、書きました。
この記事を読んで「Lambdaを活用した実装してやるぞ!」「CDKでIaC勉強していくぞ!」といったモチベーションの糧になれば幸いです。

最後まで読んでいただきありがとうございました!
明日は弊社SREチーム期待の星、三口さんの記事です。お楽しみに!