Amazon Linux2023 + Aurora Serverlessv2 のLaravel環境をCDKで構築する


## はじめに

CDK の勉強がてらに、Amazon Linux2023 + Aurora Serverlessv2 の Laravel 環境を構築してみました。
本記事は、備忘録用のメモになります。

### 前提条件

- AWS CLI がインストールされていること
- CDK がインストールされているかつ、`BootStrap`スタックが作成されていること。

### 動作環境

- macOS Ventura 13.4
- CDK: 2.134.0 (build 265d769)
- AWS CLI: aws-cli/2.15.2 Python/3.11.6 Darwin/23.4.0 exe/x86_64 prompt/off

## CDK のプロジェクトの作成

1. プロジェクト用ディレクトリを作成する

   ```terminal
   mkdir cdk-laravel
   cd cdk-laravel
   ```

2. CDK init を実行する。

   ```terminal
   cdk init app --language typescript
   ```

これで CDK の構築準備が整いました。

## ネットワーク関連の構築

VPC やサブネットなどネットワーク関連のリソースを作成していきます。

```diff_typescript:cdk-laravel/lib/cdk-laravel-stack.ts
+ import { aws_ec2 as ec2 } from "aws-cdk-lib";

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

+     const vpc = new ec2.Vpc(this, "vpc", {
+       maxAzs: 2,
+       natGateways: 0,
+       subnetConfiguration: [
+         {
+           cidrMask: 24,
+           name: "public",
+           subnetType: ec2.SubnetType.PUBLIC,
+         },
+         {
+           cidrMask: 24,
+           name: "private",
+           subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
+         },
+       ],
+     });
```

NAT ゲートウェイを作成すると料金がかかるので、`natGateways: 0`で作成されないようにします。

## EC2 Instance Connect Endpoint の構築

### リソースの作成

EC2 Instance Connect Endpoint を使用することで、簡単に EC2 に接続することができるようになりますので、こちらを作成します。
なお、EC2 Instance Connect Endpoint は料金はかかりませんので、ご安心ください。

※ファイル内の全てのコードを記載すると記述が長くなりますので、追記した箇所のみ記載させていただきます

```diff_typescript:cdk-laravel/lib/cdk-laravel-stack.ts
    const eicSg = new ec2.SecurityGroup(this, "eic_sg", {
      vpc,
      allowAllOutbound: false,
      securityGroupName: "eic_sg",
    });
    const eic = new CfnInstanceConnectEndpoint(this, "eic", {
      subnetId: vpc.isolatedSubnets[0].subnetId,
      securityGroupIds: [eicSg.securityGroupId],
    });
```

- EC2 Instance Connect Endpoint 用のセキュリティグループ
  - 全てのアウトバウンドを不許可
- EC2 Instance Connect Endpoint
  - プライベートサブネットに配置
  - `eic_sg`のセキュリティグループ割り当て

なお、EC2 Instance Connect Endpoint は VPC 内に 1 つしか作成できません。
プライベートサブネットに構築した EC2 への接続のことを考えて、プライベートサブネットの方に作成しておきます。

## EC2 の構築

### リソースの作成

次に EC2 関連のリソースを作成していきます。

```diff_typescript:cdk-laravel/lib/cdk-laravel-stack.ts
import { aws_ec2 as ec2, aws_iam as iam, CfnOutput } from "aws-cdk-lib";

  ~省略~

    const ec2Role = new iam.Role(this, "ec2_role", {
      assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          "AmazonSSMManagedInstanceCore"
        ),
      ],
    });
    const ec2Sg = new ec2.SecurityGroup(this, "ec2_sg", {
      vpc,
      allowAllOutbound: true,
      securityGroupName: "ec2_sg",
    });
    eicSg.connections.allowTo(ec2Sg, ec2.Port.tcp(22));
    ec2Sg.connections.allowFrom(eicSg, ec2.Port.tcp(22));
    ec2Sg.connections.allowFromAnyIpv4(ec2.Port.tcp(80));

    const ec2Instance = new ec2.Instance(this, "ec2_instance", {
      vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
      role: ec2Role,
      securityGroup: ec2Sg,
      instanceName: "ec2_instance",
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T3,
        ec2.InstanceSize.MICRO
      ),
      machineImage: new ec2.AmazonLinuxImage({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
      }),
      associatePublicIpAddress: true,
    });

    new CfnOutput(this, "ec2_instance_ip", {
      value: ec2Instance.instancePublicIp,
    });
    new CfnOutput(this, "ec2 EIC connection command", {
      value: `aws ec2-instance-connect ssh --instance-id ${ec2Instance.instanceId} --connection-type eice`,
    });
```

- IAM ロール作成
  - セッションマネージャー用に、`AmazonSSMManagedInstanceCore` ポリシーを付与。
- EC2 のセキュリティグループ作成
  - `eic_sg` からの、22 番ポートを解放。
  - 全ての IP からの、80 番ポートを解放
- EIC のセキュリティグループ
  - `ec2_sg` への 22 番ポートを解放。
- EC2
  - AmazonLinux 2023 、t3.micro
  - パブリック IP 設定
  - `ec2_sg`、`ec2_role`設定

CDK では`CfnOutput`を利用することで、CloudFormation の`Outputs`のように値を出力することができます。
本コードでは、EC2 のパブリック IP アドレスと`EC2 Instance Connect Endpoint`接続用のコマンドを出力するようにしています。

### CDK Deploy

では、デプロイしてみましょう。

```terminal
cdk deploy
```

正常にデプロイできた場合、`CfnOutput`で定義した値が出力されているはずです。

```terminal
Outputs:
CdkLaravelStack.ec2EICconnectioncommand = aws ec2-instance-connect ssh --instance-id <instanceId> --connection-type eice
CdkLaravelStack.ec2instanceip = <パブリックIP>
```

上記の`CdkLaravelStack.ec2EICconnectioncommand`に出力されたコマンドをコピーして、EC2 に接続してみましょう。

```terminal
aws ec2-instance-connect ssh --instance-id <instanceId> --connection-type eice
```

以下の画面が出力されれば、接続成功です。

```terminal
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
[ec2-user@ip-10-0-0-95 ~]$
```

## Laravel の構築

### パッケージのインストール

構築に必要なパッケージをインストールしていきます。

```terminal
sudo dnf upgrade --releasever=latest
dnf install -y php httpd mariadb105 php-mysqli
```

PHP と MySQL がインストールされていることを確認します

- PHP のインストール確認

```terminal
$ php -v
PHP 8.2.15 (cli) (built: Jan 16 2024 12:19:32) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.2.15, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.15, Copyright (c), by Zend Technologies
```

- MySQL のインストール確認

```terminal
$ mysql -V
mysql  Ver 15.1 Distrib 10.5.23-MariaDB, for Linux (x86_64) using  EditLine wrapper
```

### Apache のインストール、起動

Apache を起動させ、自動起動を有効にします。

```terminal
sudo systemctl start httpd
sudo systemctl enable httpd
```

ステータスが`Active`になっていたら、起動成功です。

```terminal
$ sudo systemctl status httpd
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/httpd.service.d
             └─php-fpm.conf
     Active: active (running) since Fri 2024-05-03 05:18:53 UTC; 56s ago
```

ここで、EC2 インスタンスに 80 ポートでアクセスできるか確認してみます。

```terminal
http://<EC2インスタンスのIP>:80
```

Apache が起動しているかつ、セキュリティグループの設定に問題がなければ以下の画面が表示されるはずです。
![スクリーンショット 2024-05-03 14.20.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/ee93c8b6-dec6-20de-23f2-44d83451f0e5.png)

### Composer のインストール

Laravel をインストールするために、composer をインストールします。

```terminal
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
```

composer のインストール確認

```terminal
$ composer -V
Composer version 2.7.4 2024-04-22 21:17:03
PHP version 8.2.15 (/usr/bin/php)
Run the "diagnose" command to get more detailed diagnostics output.
```

### Laravel のインストール

まずはパーミッションの変更をします。
Laravel の環境構築が目的のため、権限の問題を取り除くために`777`を設定します。

```terminal
$ cd /var/www
$ sudo chmod 777 /var/www
```

Laravel のプロジェクトを作成します。

```terminal
composer create-project laravel/laravel webapp
```

エラーがなければ問題ないです。

### Apache の設定変更

デフォルトだと `/var/www/html`を表示するようになっているので、Laravel 側を表示するように設定変更する。

```terminal
$ sudo vi /etc/httpd/conf/httpd.conf
```

Laravel のディレクトリに修正する。

```diff_conf:/etc/httpd/conf/httpd.conf
- DocumentRoot "/var/www/html"
+ DocumentRoot "/var/www/webapp/public"

#
# Relax access to content within /var/www.
#
- <Directory "/var/www">
+ <Directory "/var/www/webapp/public">
```

conf を修正したので、Apache をリロードする。

```terminal
$ sudo systemctl reload httpd
```

Apache が Laravel 側のファイルなどを正常に読み込めるよう、権限を修正する。

```terminal
$ sudo chown -R apache:apache /var/www/webapp
```

再度、EC2 インスタンスにアクセスしてみる。
Laravel の画面が表示されていることを確認する。

![スクリーンショット 2024-05-03 14.50.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317229/5a7495a5-0288-c771-bdaf-54cae264401e.png)

## Aurora ServerlessV2 の構築

現在のままだと、データベースに接続していないので`php artisan migrate`が実行できない。
そのため、`Aurora ServerlessV2`を構築する。

```diff_typescript:cdk-laravel/lib/cdk-laravel-stack.ts
import {
  aws_ec2 as ec2,
  aws_iam as iam,
  CfnOutput,
  aws_rds as rds,
} from "aws-cdk-lib";

  ~省略~

    const auroraSg = new ec2.SecurityGroup(this, "aurora_sg", {
      vpc,
      allowAllOutbound: true,
      securityGroupName: "aurora_sg",
    });
    auroraSg.connections.allowFrom(ec2Sg, ec2.Port.tcp(3306));

    const auroraCluster = new rds.DatabaseCluster(this, "aurora_cluster", {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: rds.AuroraMysqlEngineVersion.VER_3_06_0,
      }),
      vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      writer: rds.ClusterInstance.serverlessV2("writer"),
      serverlessV2MaxCapacity: 1.0,
      serverlessV2MinCapacity: 0.5,
      securityGroups: [auroraSg],
      defaultDatabaseName: "laravel",
    });
    auroraCluster.applyRemovalPolicy(RemovalPolicy.DESTROY);

    new CfnOutput(this, "Aurora Cluster secretmanager", {
      value: `aws secretsmanager get-secret-value --secret-id ${
        auroraCluster.secret!.secretName
      }`,
    });
```

- Aurora 用のセキュリティグループ作成
  - EC2 からの 3306 ポートのみ解放
- Aurora Serverless V2
  - Aurora MySQL の 3.06 バージョン
  - プライベートサブネットに配置
  - `laravel`データベース作成
  - Aurora の容量(ACU)を最大 1、最小 0.5 に設定
  - CDK のリソース削除時に、DB を破棄する

なお、DB への接続情報などは Secret Manager に登録されます。Secret Manager から値を取得するため、`CfnOutput`でシークレット名を取得しています。

### CDK Deploy

デプロイします。

```terminal
cdk deploy
```

正常にデプロイできた場合、次の値が出力されるはずです。

```terminal
CdkLaravelStack.AuroraClustersecretmanager = aws secretsmanager get-secret-value --secret-id <secret-id>
```

この値をもとに、DB の各情報を取得しましょう。

```terminal
aws secretsmanager get-secret-value --secret-id <secret-id>
```

```terminal
{
    "ARN": "...",
    "Name": "...",
    "VersionId": "...",
    "SecretString": "{\"dbClusterIdentifier\":\"...\",\"password\":\"...\",\"dbname\":\"laravel\",\"engine\":\"mysql\",\"port\":3306,\"host\":\"...\",\"username\":\"admin\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "..."
}
```

host、usrename、password の値を控えておきます。

### Aurora への接続

再度、EC2 に接続しましょう。

```terminal
aws ec2-instance-connect ssh --instance-id <instanceId> --connection-type eice
```

先ほど控えた値をもとに Aurora に接続します。

```terminal
mysql -h <host> -P 3306 -u admin -p
```

以下のような表示がでれば、接続成功です。

```terminal
MySQL [(none)]>
```

laravel のテーブル有無を確認します。

```sql
MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.047 sec)
```

ちゃんと作成されていることが確認できました。

### Laravel の DB 接続情報の変更

MySQL から`exit`で抜け、EC2 の画面にもどります。
Laravel では`.env`ファイルに、DB の設定を行うので、こちらのファイルを修正します。

```conf:/var/www/webapp/.env
DB_CONNECTION=mysql
DB_HOST=<host>
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=admin
DB_PASSWORD=<password>
```

設定ファイルを修正したので、`config:cache`コマンドを実行します。

```terminal
sudo php artisan config:cache
```

`php artisan migrate`を実行してみます。

```terminal
$ sudo php artisan migrate

   INFO  Preparing database.

  Creating migration table ........................................................... 437.55ms DONE

   INFO  Running migrations.

  0001_01_01_000000_create_users_table ............................................... 873.25ms DONE
  0001_01_01_000001_create_cache_table ............................................... 238.12ms DONE
  0001_01_01_000002_create_jobs_table ................................................ 477.59ms DONE
```

特にエラーがでていないければ、`migrate`成功です！
これで 、Amazon Linux2023 + Aurora Serverlessv2 の Laravel 環境を CDK で構築することができました。

お疲れ様でした。