Laravel 9 を ECS on Fargateで運用にするにあたり、Tipsまとめ

# はじめに
下記の続きになります。タイトル通り、Fargateを運用にするにあたり、Tipsまとめます。

https://qiita.com/holdout0521/items/241df305a90af3ccf973

# 事前構築
下記の記事で、fargateを構築済み

https://qiita.com/holdout0521/items/241df305a90af3ccf973

ソースに関して、mainブランチは、この記事の対応前のソースになります。
この記事の対応後のソースは、feature/tipsブランチにあります。

https://github.com/yuji-hirai/laravel-ecs

# この記事のtips一覧
- envファイルをs3に保管
- Basic認証とヘルスチェックを通す
- Laravel.logをCloudWatchLogsに出力
- EFSを利用
- ElastiCache Redis Cluster を利用
- AWS Graviton2を利用し、コスト削減
- `npm run prod`コマンドでコンパイルして圧縮処理
- 読み取り専用コンテナ化



# envファイルをs3に保管
envファイルは、`API_KEY`などがあるため、GitHubに載せたくないです。
envファイルをS3に保管し、Fargateをデプロイのタイミングで、S3からenvファイルを取得する方法を説明します。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/taskdef-envfiles.html

## S3バケットを作成
下記の設定で、S3バケットを作成します。
バケット名は、`laravel-environment-variable`にしました。
![スクリーンショット 2022-04-03 11.04.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b68847b5-0706-791d-aa78-b8d90cd0794b.png)

作成後、envファイルをアップロードします。
注意点として、ファイルの拡張子は「.env」にする必要があります。
envファイルの名前は、`laravel.env`という名前にします。

ファイルの内容について、`# で始まる行はコメントとして扱われ、無視されます。`

```laravel.env
# ここの文言は無視されます
ENV=production
API_KEY=xxxxxxxxxx
# ここの文言は無視されます
```

`laravel.env`ファイルの`Amazon リソースネーム (ARN)`をコピーしておきます。

![スクリーンショット 2022-04-03 11.09.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/c6e6b327-c28e-6d82-3ed7-ff2eb4dc57c3.png)

## IAMポリシーを作成
fargateがs3のenvファイルを取得できるように権限を付与します。

```get-S3-env
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::laravel-environment-variable/*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:GetBucketLocation",
            "Resource": "arn:aws:s3:::laravel-environment-variable"
        }
    ]
}
```
このポリシーをタスクロールではなく**タスク実行ロール**にアタッチします。
タスク定義作成時にデフォルトで、 `ecsTaskExecutionRole`が作成されていると思います。
![スクリーンショット 2022-04-03 11.22.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a23b24cd-6bd4-c39e-48de-20b62276d1ed.png)

`ecsTaskExecutionRole`に`get-S3-env`ポリシーをアタッチしましょう。
![スクリーンショット 2022-04-03 11.20.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4e674610-afca-1e2e-5b5d-b6473a310cb0.png)

## タスク定義の設定
あとは、タスク定義のappコンテナ内の`環境ファイル`に先程コピーしたS3のARNを貼り付けると完了です。
環境変数内に記載する必要はなくなりました。
![スクリーンショット 2022-04-03 11.23.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/e68a39a8-30b2-e7d7-7dfb-23141f433c25.png)

タスク定義更新後、サービス内で、新しいタスク定義を選択し、強制デプロイするだけです。

## エラー[ResourceInitializationError]

https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-unable-to-pull-secrets/

envファイルのダウンロードに失敗したエラーが出た場合、セキュリティーグループを確認してください。
- elbのsgは、httpで全開放（0.0.0.0/0）
- fargateのsgは、elbからのhttpで許可
- ACLとセキュリティグループが、サブネットからのポート 443 へのアウトバウンドアクセスを許可
```
ResourceInitializationError: failed to download env files: file download command: non empty error stream: service call has been retried 5 time(s): RequestCanceled: request context canceled caused by: context deadline exceeded
```
```
ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 3 time(s): RequestError: send request failed caused by: Post https://api.ecr....
```

# Basic認証とヘルスチェックを通す
開発中や管理画面は、Basic認証を設定することがあると思います。
Basic認証の設定と、ELBからのヘルスチェックを200で返す方法について説明します。
## Basic認証の設定
`/docker/nginx/default.conf`内を修正します。
```diff_conf:/docker/nginx/default.conf
server {
  listen 80;
  root /var/www/html/public;
  error_log /var/log/nginx/error.log;
  index index.php;

+  if ($http_user_agent = "ELB-HealthChecker/*") {
+    set $auth off;
+  }
+  location /test/ {
+    try_files $uri $uri/ /index.php?$query_string;
+    satisfy any;
+    allow   all;
+  }

  location = /favicon.ico { access_log off; log_not_found off; }
  location = /robots.txt  { access_log off; log_not_found off; }

  location / {
    try_files $uri $uri/ /index.php?$query_string;
+    auth_basic $auth;
+    auth_basic_user_file /etc/nginx/.htpasswd;
  }
  error_page 404 /index.php;

  location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    include fastcgi_params;
  }

  location ~ /\.(?!well-known).* {
    deny all;
  }
}
```

ヘルスチェックは、Basic認証をパスします。
```diff_conf:/docker/nginx/default.conf
+  if ($http_user_agent = "ELB-HealthChecker/*") {
+    set $auth off;
+  }
```

パス`/test/`の場合、Basic認証をパスします。
```diff_conf:/docker/nginx/default.conf
+  location /test/ {
+    try_files $uri $uri/ /index.php?$query_string;
+    satisfy any;
+    allow   all;
+  }
```

上記以外の場合、Basic認証をありにします。
`/etc/nginx/.htpasswd`ファイルは、Basic認証で必要なユーザー名とパスワードを記載されたものになります。後ほど説明します。
```diff_conf:/docker/nginx/default.conf
  location / {
    try_files $uri $uri/ /index.php?$query_string;
+    auth_basic $auth;
+    auth_basic_user_file /etc/nginx/.htpasswd;
  }
```
## htpasswdの設定
Basic認証で必要なユーザーとパスワードを作成していきます。
`./docker/nginx/`配下にhtpasswdディレクトリを作成し、.htpasswdを加えます。
```terminal
.
├── docker
│   ├── nginx
│   │   ├── Dockerfile
│   │   ├── default.conf
│   │   ├── nginx.conf
│   │   └── htpasswd
│   │        └── .htpasswd
│   └── php
│       ├── Dockerfile
│       └── php.ini
└── src
```

```diff:./docker/nginx/Dockerfile
FROM nginx:1.20-alpine
COPY ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
+ COPY ./docker/nginx/htpasswd /etc/nginx
COPY ./src /var/www/html
ENV TZ Asia/Tokyo
EXPOSE 80
```


`.htpasswd`には、Basic認証時に必要なユーザー名とパスワードを下記のように記載します。
```.htpasswd
ユーザー名:ハッシュ化されたパスワード
```
[こちらのサイトでは、](https://htaccess.cman.jp/explain/basic.html)Basic認証用の`.htpasswd`ファイルを自動生成できます

![スクリーンショット 2022-04-03 12.20.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/d886fe42-c993-5f5d-953d-8c595420ffdd.png)
ユーザー名を`test`、パスワードを`pass`としました。
```./docker/nginx/htpasswd/.htpasswd
test:$apr1$YEgvP5H/$YSKmv5c5Xp6mSCgQGPfoN.
```
あとは、ビルドし、ecrにpushし、タスクを強制デプロイします。詳細は、[前回の記事を参考にしてください](https://qiita.com/holdout0521/items/241df305a90af3ccf973#ecr%E3%82%92%E4%BD%9C%E6%88%90)


```terminal
laravel-ecs $ docker build -t nginx:prod -f ./docker/nginx/Dockerfile .

$ docker tag nginx:prod [アカウントエイリアス12桁].dkr.ecr.ap-northeast-1.amazonaws.com/nginx:prod

$ docker push [アカウントエイリアス12桁].dkr.ecr.ap-northeast-1.amazonaws.com/nginx:prod
```

`http://xxxx.com`にアクセスすると、Basic認証が表示されました。ここに、testとpassを入力すると、laravelのページが表示されます。

また、ヘルスチェックも成功しているようですね。
![スクリーンショット 2022-04-03 12.30.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/99cc87a8-ec9c-2e73-3ef8-8285b148b66c.png)



![スクリーンショット 2022-04-03 12.31.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/cec6b15c-a5fc-7a73-8548-38d77864e334.png)


### .gitignore追加
htpasswdディレクトリ内に.gitignoreを追加し、.htpasswdがgithubに上がらないようにしましょう。
```.docker/nginx/htpasswd/.gitignore 
.htpasswd
```

## Login機能がある場合、ELBのヘルスチェックパスとrouteの修正
ELBのヘルスチェックパスは、`/index.php`となっております。
login機能がある場合、`/index.php`にヘルスチェックをしても強制的に`/login`にリダイレクトされてしまい、ヘルスチェックが失敗します。

その場合、`src/route/web.php`ファイルを修正し、`/healthcheck`にアクセスしたら'health ok'を返す(表示する)設定をすることで、ヘルスを成功させることができます。

```diff_php:.src/route/web.php
<?php

use Illuminate\Support\Facades\Route;

+ Route::get('/healthcheck', function()
+ {
+     return 'health ok';
+ });
```

また、ロードバランサーのfargateのターゲットグループのヘルスチェックパスを`/index.php`から`/healthcheck`修正します。

再度、ビルドし、再デプロイしましょう。
`http://xxxx.com/healthcheck`にアクセスすると`health ok`と返ってきます。
![スクリーンショット 2022-04-03 13.15.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/5e196720-7d69-2989-2017-6806b07e52f6.png)

# Laravel.logをCloudWatchLogsに出力
fargateのログは、CloudwatchLogsに出力する設定をタスク定義でできますが、Laravelの場合、laravel.logはデフォルトでは、CloudwatchLogs出力しないため、設定する必要があります。

`src/config/logging.php`ファイルを一度確認しましょう。
```src/config/logging.php
<?php 
一部省略

'channels' => [
  'stack' => [
    'driver' => 'stack',
    'channels' => ['single'],
    'ignore_exceptions' => false,
  ],

  'single' => [
    'driver' => 'single',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
  ],

  'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 14,
  ],

  'slack' => [
    'driver' => 'slack',
    'url' => env('LOG_SLACK_WEBHOOK_URL'),
    'username' => 'Laravel Log',
    'emoji' => ':boom:',
    'level' => env('LOG_LEVEL', 'critical'),
  ],

  'papertrail' => [
    'driver' => 'monolog',
    'level' => env('LOG_LEVEL', 'debug'),
    'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
    'handler_with' => [
      'host' => env('PAPERTRAIL_URL'),
      'port' => env('PAPERTRAIL_PORT'),
      'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
    ],
  ],

  'stderr' => [
    'driver' => 'monolog',
    'level' => env('LOG_LEVEL', 'debug'),
    'handler' => StreamHandler::class,
    'formatter' => env('LOG_STDERR_FORMATTER'),
    'with' => [
      'stream' => 'php://stderr',
    ],
  ],

  'syslog' => [
    'driver' => 'syslog',
    'level' => env('LOG_LEVEL', 'debug'),
  ],

  'errorlog' => [
    'driver' => 'errorlog',
    'level' => env('LOG_LEVEL', 'debug'),
  ],

  'null' => [
    'driver' => 'monolog',
    'handler' => NullHandler::class,
  ],

  'emergency' => [
    'path' => storage_path('logs/laravel.log'),
  ],
],
```
`'default' => env('LOG_CHANNEL', 'stack'),`とあるように、デフォルトでは、`LOG_CHANNEL`が設定されていない場合、`stack`となります。
`stack`は`single`チャンネルを使用されるようですね。
下記を見ると分かりますが、出力ログ先は、`logs/laravel.log`のようです。
```php
  'single' => [
    'driver' => 'single',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
  ],
```
Laravelがエラーログを CloudWatchLogs 出力する設定は、`stderr`になります。
そのため、envファイルに`LOG_CHANNEL=stderr`の一行を加えるだけで、LaravelのログがCloudWatchLogsに出力できます。

```diff:laravel.env
+ LOG_CHANNEL=stderr
```

envファイルは、s3にあるため、envファイル修正しアップロードしましょう。

## awslogs-datetime-formatオプション追加
また、タスク定義のappコンテナのログ設定で以下の一行を加えます。

`awslogs-datetime-format：%Y-%m-%d %H:%M:%S"`

このオプションは、指定した形式の日時がログのある行に含まれていれば，続く行も同じ日時とみなされます。
この一行を加えない場合、Stacktrace のようにログレコードが複数行にわたる際、ログレコードが細分化されてしまい、可読性が下がります。
![スクリーンショット 2022-04-03 14.06.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b819c544-470d-4ef9-27b2-d53bb2c281a7.png)


これで、デプロイすると、laravelのログがCloudwatchlogsに出力されるようになりました。

ちなみに、laravelにエラーログが出力された場合、slackに通知させる方法はこちらになります。

https://qiita.com/holdout0521/items/e566faa62b02226f6749#slack%E3%81%AEwebhookurl%E3%82%92%E4%BD%9C%E6%88%90

# EFSを利用
Fargateでは、EFSをマウントすることができます。

以下の記事がわかりやすかったです。

ポイントは、EFSのマウントターゲットに設定するセキュリティグループは、`fargateのsgに対して、port2049を許可`する点くらいです。
fargate側のsgは、特に変更することはないですね。



https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-fargate-mount-efs-containers-tasks/

ちなみに、マウントポイントは、fargate内のディレクトリに存在しない場合、作成してくれます。
例えば、マウントポイントが`/var/www/html/efs`の場合、efsディレクトリがないのであれば、作成してくれます。

ちなみに、以下の記事で、EFSに直接ファイルが転送できます。

https://blog.denet.co.jp/ecs-fargate-efs/

## タスクが起動できない場合
デプロイ後、以下のエラーログを出た場合、セキュリティーグループが適正でない可能性があります。
先程も説明しましたが、EFSのマウントターゲットに設定するセキュリティグループは、`fargateのsgに対して、port2049を許可`が必要です。
```
ResourceInitializationError:
failed to invoke EFS utils commands to set up EFS volumes: stderr: 
Failed to resolve "fs-0eaebf306ce71301a.efs.ap-northeast-1.amazonaws.com" 
- check that your file system ID is correct. See https://docs.aws.amazon.com/conso...
```

# ElastiCache Redis クラスター使用
タスクが複数になった場合、sessionデータをRedisに保存しないと、ログイン機能などが使用できません。

そのため、クラスターモード有効のElastiCache Redis を作成し、cacheとsessionをRedisに保存するようにします。

作成方法は、省略します。
![スクリーンショット 2022-10-21 18.16.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4cb7c1ff-c239-8152-58ef-3d473a478db4.png)

![スクリーンショット 2022-10-21 18.13.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3a6783ab-b1b0-2cd6-1912-3ea550390b0a.png)

## .envファイル修正
以下の項目を修正しました。
```diff_conf:.env
CACHE_DRIVER=redis
SESSION_DRIVER=redis
REDIS_HOST=test.wwww.clustercfg.apne1.cache.amazonaws.com
REDIS_PASSWORD=null
REDIS_PORT=6379
```
`REDIS_HOST`は、クラスター詳細の`設定エンドポイント`になります。末尾のポートは不要です。

## php.iniファイル修正
以下の項目を修正しました。
```php.ini
[redis]
extension = redis.so
session.save_handler = rediscluster
session.save_path = "seed[]=".${REDIS_HOST}.":6379&failover=distribute&persistent=1"
```
`${REDIS_HOST}`は、envの環境変数を参照しています。

下記を参考にしました。

https://github.com/phpredis/phpredis/blob/develop/cluster.markdown

## config/database.phpファイルを修正
以下の項目を修正しました。
```php:src/config/database.php
    'redis' => [
        'client' => env('REDIS_CLIENT', 'phpredis'),

        'options' => [
            'cluster' => env('REDIS_CLUSTER', 'redis'),
        ],

        'clusters' => [

            'default' => [
                [
                  'host' => env('REDIS_HOST', 'localhost'),
                  'password' => env('REDIS_PASSWORD', null),
                  'port' => env('REDIS_PORT', 6379),
                  'database' => 0,
                ],
            ],

            'cache' => [
                [
                  'host' => env('REDIS_HOST', '127.0.0.1'),
                  'password' => env('REDIS_PASSWORD', null),
                  'port' => env('REDIS_PORT', '6379'),
                  'database' => env('REDIS_CACHE_DB', '1'),
                ]
            ],
        ],
    ],
```
これでcacheとsessionをRedisに保存できます。
下記を参考にしました。

https://readouble.com/laravel/9.x/ja/redis.html

## Dockerfileを修正
以下の項目を修正しました。

```dockerfile:docker/php/Dockerfile
RUN pecl install redis \
         docker-php-ext-enable redis
```
これでビルドすると、cacheとsessionをredisに保存することができます。


# AWS Graviton2を利用し、コスト削減
AWS Fargate の Graviton2 が使用できます。
従来のよりも 20% 使用料が安く、最大 40% 高性能なリソースを利用できるようです。

Docker ImageでARM64を探し、Dockerfileに記載し、ビルドします。
タスク定義でARM64を設定し、デプロイする、といった流れになります。

https://dev.classmethod.jp/articles/spring-boot-application-on-fargate-graviton2/

https://www.m3tech.blog/entry/2021/12/13/110318

## Docker ImageでARM64を探す
`php:8.1-fpm-bullseye`で検索し、`linux/arm64`を探します。
`linux/arm64/v8`があったため、選択し`sha256:1a.......`をコピーし、Dockerfileにペーストします。
![スクリーンショット 2022-04-03 16.58.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/729238aa-a0e4-8e0d-bd23-61eff13e16f5.png)

https://hub.docker.com/layers/php/library/php/8.1-fpm-bullseye/images/sha256-8372fbae7646dfde35413e409deef8740afc6a74b12492dbe18ac7e58f7e71d2?context=explore

```diff:./docker/php/Dockerfile
+ FROM php:8.1-fpm-bullseye@sha256:804ea577022c459dbbc69ac42e0adf405a24c12d032a045cb558fe98ccea03de
- FROM php:8.1-fpm-bullseye
```

nginxも同様に、`nginx:1.20-alpine`で検索し、`linux/arm64/v8`があったため、`sha256:88.......`をコピーし、Dockerfileにペーストします。
![スクリーンショット 2022-04-03 17.01.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/f34c1cf2-2c8d-6cca-dca3-1c6bdd693f99.png)

https://hub.docker.com/layers/nginx/library/nginx/1.20/images/sha256-88d982bc06d4c192f6eb3a96f78d551b79a18840910907323fd719237a21b15b?context=explore

```diff:./docker/nginx/Dockerfile
+ FROM nginx:1.20-alpine@sha256:88d982bc06d4c192f6eb3a96f78d551b79a18840910907323fd719237a21b15b
- FROM nginx:1.20-alpine
```
## タスク定義で設定
タスク定義のボリュームの`JSONによる設定`で、以下を加えることで、ARM64を指定したデプロイができます。
```diff_json:json設定
    "runtimePlatform": {
-        "cpuArchitecture": null,
+        "cpuArchitecture": "ARM64",
        "operatingSystemFamily": "LINUX"
    },
```
![スクリーンショット 2022-04-03 17.06.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b80809e3-2a76-b2f9-a30c-30abadd03ee5.png)

デプロイすると、Graviton2に対応した構築になり、コストが20%削減されます。

### ARM64は、ビルド時間が長い場合があります。
私の場合、phpのビルド時間が長かったです。
特に、phpのDockerfile内の`RUN docker-php-ext-install intl　pdo_mysql bcmath zip`が長かったです。

| イメージ | ベースのイメージ | ビルド時間 |
|:-:|:-:|:-:|
| php:8.1-fpm-bullseye  |  x86/amd64 | 2分  |
| php:8.1-fpm-bullseye  | arm64  | 20分  |
| nginx:1.20-alpine  |  x86/amd64 | 1.5秒  |
| nginx:1.20-alpine  | arm64  | 4.0秒  |


下記の記事でも同様に、ビルド時間が長いことが確認されており、結果として`x86/amd64`を利用することにしました。

https://blog.shin1x1.com/entry/docker-multi-arch-build-on-github-actions

https://poyo.hatenablog.jp/entry/2021/09/25/225329

# `npm run prod`コマンドでコンパイルして圧縮処理
srcディレクトリ配下に`package.json`,`package-lock.json`,`webpack.mix.js`等を設置します。
Dockerfileに`npm`をインストールし、`npm install`と`npm run prod`を追加すると、CSSファイルとJavaScriptファイルを簡単にコンパイルして圧縮できます。

```diff_docker:docker/php/Dockerfile
FROM php:8.1-fpm-bullseye

# 省略

RUN apt-get update \
  && apt-get -y install --no-install-recommends \
    locales \
+    npm \
    git \
# 省略

RUN composer install -q -n --no-ansi --no-dev --no-scripts --no-progress --prefer-dist \
  && chmod -R 777 storage bootstrap/cache \
  && php artisan optimize:clear \
  && php artisan optimize \
  && php artisan cache:clear \
  && php artisan config:clear \
  && php artisan route:clear \
  && php artisan view:clear \
+  && npm install \
+  && npm run prod
```

# コンテナを使うにあたり便利ツール
Laravelに限らず、コンテナを使うにあたり便利ツールを紹介します。
## hadolintを利用してベストプラクティスに基づいたDockerfileを作成する
以下の記事を参考に、`hadolint`を terminal もしくは VSCode にインストールしましょう。


https://dev.classmethod.jp/articles/hadolint-docker/

## Dockleを利用してコンテナのセキュリティ診断する

Dockleは、コンテナイメージから危険な項目をチェックするとともに、ベストプラクティスに沿ったコンテナイメージが作れているか確認できます。
下記の記事を参考にインストールしましょう。

https://qiita.com/tomoyamachi/items/bb6ac5788bb734c91282

## ECRの脆弱性スキャン機能を利用してコンテナのセキュリティ診断する

ECRにpushすると、自動で脆弱性スキャンをする機能がECRにあります。
下記通りに設定するとよいです。

https://dev.classmethod.jp/articles/ecr-repository-scan/

# 読み取り専用コンテナ化

記事を作成しましたので、こちらを参考にしてください。

https://qiita.com/hirai-11/items/cf3bc72b9fac055a412f

# 参考

https://github.com/shonansurvivors/laravel-fargate-app

https://blog.e2info.co.jp/2016/07/03/aws%E3%81%A7nginx%E3%82%92%E4%BD%BF%E3%81%86/