PHPアプリケーションを想定したECS FargateのEFSマウントやRDS接続を検証してみた

PHPアプリケーションを想定したECS FargateのEFSマウントやRDS接続を検証してみた

ECS on FargateでEFSをマウントし、Secrets Managerを使用してRDS接続を検証してみました。実際にコンテナに接続して、複数タスク間でのファイル共有やDB操作が正常に動作することを確認しています。
2026.08.21

このブログを見つけていただきありがとうございます!
コンサルティング部のみゃんです。

皆さんは耳で聞いた情報でも、実際に自分の目で見るまでは「本当?」と疑いたくなること、ありませんか?
私は自分で試してみるまで、たとえ公式ドキュメントに書いてあることでも100%信用できません。

ちょうど業務でAmazon ECS on AWS Fargate(以下、ECS on Fargate)を構築する機会がありました。
ECS on Fargateにおいて、EFSのマウントやAWS Secrets Manager(以下、Secrets Manager)からDBシークレットを取得して、RDS(MySQL)へ接続するなど、机上では簡単にできそうなことでも実際に作って試してみないと分かりません。
というわけで今回は、ECS on FargateをPHPアプリケーション用に構築した際に、インフラ観点でいくつかの動作検証を実際に試してみることにしました。

最初に断っておきますが、今回はあくまで私の個人的な好奇心を満たすために一部本番環境では許されない設計(特に権限周りやセキュリティ)になっています。
絶対に真似しないでください!!!

まずは結論から

忙しい人のために、まずは結論をまとめます。
詳細は、後述の「やってみた」章をご覧ください!

今回主に試したのは以下の2点です。
①ECS on FargateにEFSをマウントして、ファイルの読み書きをさせる
②ECS on FargateからRDS(MySQL)へ接続して、SQLを実行する

①CloudFormationとDockerfileの設定で、手をあまり動かすことなく、EFSのマウントができました!
実際にコンテナに接続して、EFSをマウントしたディレクトリを複数タスク間で共通で読み取り、書き込みが行えていることを確認しています。

②も同様にIaCで定義しておけば、簡単にSecrets Manager経由でDBの接続情報をECS on Fargateに取得させて、環境変数として持っておき、PHPアプリケーションへ変数を渡して、DB接続することを確認できました。
また、実際にコンテナへ接続して、DBへの書き込みや読み取りが行えることを確認しています。

やってみた

前提

  • CloudFormationで構築
  • 今回は構築部分はカットし、検証部分にフォーカスしてお届け
  • アプリケーション : PHP
  • Webサーバー : nginx
  • コンテナOS : Alpine Linux

今回の検証を行った構成概要図はこちらです。
2026-08-20_CFn検証用_簡易構成図.png

PHPアプリケーションのファイルはClaude Codeに生成させました。簡易的なPHPファイルを実行して、EFSとRDSへの接続性を検証できる構成になっています。
Dockerビルドでコンテナの中に以下のテストファイルを仕込んでおき、コンテナへCloudShellから接続して、テストファイルを実行し、EFS・RDSへの接続性を検証しました。

実際に使用したテスト用PHPファイル「verify-infra.php」

<?php

declare(strict_types=1);

/**
 * 必須環境変数を取得する。
 */
function requiredEnv(string $name): string
{
    $value = getenv($name);

    if ($value === false || trim($value) === '') {
        throw new RuntimeException(
            sprintf(
                'Required environment variable is missing: %s',
                $name,
            ),
        );
    }

    return $value;
}

/**
 * Boolean形式の環境変数を取得する。
 */
function booleanEnv(string $name, bool $default): bool
{
    $value = getenv($name);

    if ($value === false || trim($value) === '') {
        return $default;
    }

    $parsed = filter_var(
        $value,
        FILTER_VALIDATE_BOOLEAN,
        FILTER_NULL_ON_FAILURE,
    );

    if ($parsed === null) {
        throw new RuntimeException(
            sprintf(
                'Environment variable %s must be a boolean value: %s',
                $name,
                $value,
            ),
        );
    }

    return $parsed;
}

/**
 * EFSの読み書きを確認する。
 */
function verifyEfs(): void
{
    $mountPath = getenv('EFS_MOUNT_PATH') ?: '/mnt/efs';
    $mountPath = rtrim($mountPath, '/');

    if ($mountPath === '') {
        throw new RuntimeException(
            'EFS_MOUNT_PATH must not be empty',
        );
    }

    if (!is_dir($mountPath)) {
        throw new RuntimeException(
            sprintf(
                'EFS mount path does not exist: %s',
                $mountPath,
            ),
        );
    }

    if (!is_readable($mountPath)) {
        throw new RuntimeException(
            sprintf(
                'EFS mount path is not readable: %s',
                $mountPath,
            ),
        );
    }

    if (!is_writable($mountPath)) {
        throw new RuntimeException(
            sprintf(
                'EFS mount path is not writable: %s',
                $mountPath,
            ),
        );
    }

    $verificationFile = $mountPath
        . '/ECS on Fargate-fargate-efs-verification.log';

    $marker = bin2hex(random_bytes(16));

    $record = sprintf(
        "%s\thost=%s\tmarker=%s\n",
        gmdate(DATE_ATOM),
        gethostname() ?: 'unknown',
        $marker,
    );

    $handle = fopen($verificationFile, 'c+b');

    if ($handle === false) {
        throw new RuntimeException(
            sprintf(
                'Failed to open EFS verification file: %s',
                $verificationFile,
            ),
        );
    }

    $locked = false;

    try {
        $locked = flock($handle, LOCK_EX);

        if (!$locked) {
            throw new RuntimeException(
                sprintf(
                    'Failed to lock EFS verification file: %s',
                    $verificationFile,
                ),
            );
        }

        if (fseek($handle, 0, SEEK_END) !== 0) {
            throw new RuntimeException(
                sprintf(
                    'Failed to seek EFS verification file: %s',
                    $verificationFile,
                ),
            );
        }

        $writtenBytes = fwrite($handle, $record);

        if (
            $writtenBytes === false
            || $writtenBytes !== strlen($record)
        ) {
            throw new RuntimeException(
                sprintf(
                    'Failed to write EFS verification file: %s',
                    $verificationFile,
                ),
            );
        }

        if (!fflush($handle)) {
            throw new RuntimeException(
                sprintf(
                    'Failed to flush EFS verification file: %s',
                    $verificationFile,
                ),
            );
        }

        if (fseek($handle, 0, SEEK_SET) !== 0) {
            throw new RuntimeException(
                sprintf(
                    'Failed to rewind EFS verification file: %s',
                    $verificationFile,
                ),
            );
        }

        $contents = stream_get_contents($handle);

        if (
            $contents === false
            || !str_contains($contents, $marker)
        ) {
            throw new RuntimeException(
                sprintf(
                    'Failed to read the written marker from EFS: %s',
                    $verificationFile,
                ),
            );
        }

        $recordCount = count(
            array_filter(
                explode("\n", trim($contents)),
                static fn (string $line): bool => $line !== '',
            ),
        );
    } finally {
        if ($locked) {
            flock($handle, LOCK_UN);
        }

        fclose($handle);
    }

    printf(
        "[OK] EFS read/write succeeded: path=%s records=%d marker=%s\n",
        $verificationFile,
        $recordCount,
        $marker,
    );
}

/**
 * RDS MySQL / Aurora MySQLへのTLS接続を確認する。
 */
function verifyRds(): void
{
    $driver = getenv('DB_CONNECTION') ?: 'mysql';

    if ($driver !== 'mysql') {
        throw new RuntimeException(
            sprintf(
                'This verification script supports mysql only: %s',
                $driver,
            ),
        );
    }

    if (!extension_loaded('pdo_mysql')) {
        throw new RuntimeException(
            'The pdo_mysql PHP extension is not loaded',
        );
    }

    $host = requiredEnv('DB_HOST');
    $port = getenv('DB_PORT') ?: '3306';
    $database = requiredEnv('DB_DATABASE');
    $username = requiredEnv('DB_USERNAME');
    $password = requiredEnv('DB_PASSWORD');

    if (!ctype_digit($port)) {
        throw new RuntimeException(
            sprintf(
                'DB_PORT must be numeric: %s',
                $port,
            ),
        );
    }

    $dsn = sprintf(
        'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
        $host,
        $port,
        $database,
    );

    $options = [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_TIMEOUT => 5,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ];

    $sslCa = requiredEnv('MYSQL_ATTR_SSL_CA');

    if (!is_file($sslCa) || !is_readable($sslCa)) {
        throw new RuntimeException(
            sprintf(
                'RDS CA bundle is not readable: %s',
                $sslCa,
            ),
        );
    }

    $options[PDO::MYSQL_ATTR_SSL_CA] = $sslCa;

    $verifyServerCertificate = booleanEnv(
        'DB_SSL_VERIFY_SERVER_CERT',
        true,
    );

    if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) {
        $option = constant(
            'PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT',
        );

        $options[$option] = $verifyServerCertificate;
    } elseif ($verifyServerCertificate) {
        throw new RuntimeException(
            'PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT is not available',
        );
    }

    $pdo = new PDO(
        $dsn,
        $username,
        $password,
        $options,
    );

    $connectionResult = $pdo
        ->query(
            <<<'SQL'
SELECT
    1 AS connection_test,
    DATABASE() AS database_name,
    CURRENT_USER() AS current_user
SQL,
        )
        ->fetch();

    if ((int) ($connectionResult['connection_test'] ?? 0) !== 1) {
        throw new RuntimeException(
            'Unexpected RDS SELECT 1 result',
        );
    }

    $sslStatus = $pdo
        ->query("SHOW SESSION STATUS LIKE 'Ssl_cipher'")
        ->fetch();

    $sslCipher = '';

    if (is_array($sslStatus)) {
        $sslCipher = (string) (
            $sslStatus['Value']
            ?? $sslStatus['VALUE']
            ?? ''
        );
    }

    if ($sslCipher === '') {
        throw new RuntimeException(
            'RDS connection succeeded, but TLS is not enabled',
        );
    }

    printf(
        "[OK] RDS connection succeeded: host=%s database=%s user=%s server=%s tlsCipher=%s\n",
        $host,
        (string) ($connectionResult['database_name'] ?? ''),
        (string) ($connectionResult['current_user'] ?? ''),
        (string) $pdo->getAttribute(PDO::ATTR_SERVER_VERSION),
        $sslCipher,
    );
}

try {
    verifyEfs();
    verifyRds();

    fwrite(
        STDOUT,
        "[OK] All infrastructure checks succeeded.\n",
    );

    exit(0);
} catch (Throwable $exception) {
    fwrite(
        STDERR,
        sprintf(
            "[NG] Infrastructure verification failed: %s: %s\n",
            $exception::class,
            $exception->getMessage(),
        ),
    );

    exit(1);
}

使用したDockerfile

ARG PHP_IMAGE=php:8.5-fpm-alpine
ARG NGINX_IMAGE=nginx:alpine

## PHP-FPM

FROM ${PHP_IMAGE} AS php-fpm

RUN set -eux; \
    apk add --no-cache \
        ca-certificates \
        curl \
        su-exec; \
    \

    docker-php-ext-install -j"$(nproc)" pdo_mysql; \
    \
    mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"; \
    \
    # RDS TLS接続検証用のAWS公式CA証明書を取得する。
    curl \
        --fail \
        --silent \
        --show-error \
        --location \
        --retry 5 \
        --retry-delay 2 \
        --proto '=https' \
        --tlsv1.2 \
        --output /etc/ssl/certs/rds-global-bundle.pem \
        https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem; \
    \
    test -s /etc/ssl/certs/rds-global-bundle.pem; \
    \
    # 必要なPHP拡張をビルド時に確認する。
    php -r ' \
        $requiredExtensions = ["PDO", "pdo_mysql", "Zend OPcache"]; \
        foreach ($requiredExtensions as $extension) { \
            if (!extension_loaded($extension)) { \
                fwrite(STDERR, "Missing PHP extension: {$extension}\n"); \
                exit(1); \
            } \
        } \
    '

COPY docker/php/zz-app.ini \
    "$PHP_INI_DIR/conf.d/zz-app.ini"

COPY docker/php/zz-fpm.conf \
    /usr/local/etc/php-fpm.d/zz-fpm.conf

WORKDIR /var/www/html

COPY docker/public /var/www/html/public

COPY --chmod=0644 docker/php/verify-infra.php \
    /usr/local/libexec/verify-infra.php

COPY --chmod=0755 docker/php/verify-infra \
    /usr/local/bin/verify-infra

RUN set -eux; \
    \
    # ECS on Fargateタスク定義のEfsMountContainerPathと一致させる。
    # ECS on Fargate起動時、このディレクトリへEFSがマウントされる。
    # この処理はマウントポイントを事前作成するためのものです。EFSマウント後の権限は、EFSアクセスポイントに設定したPOSIXユーザーおよびルートディレクトリの所有者・パーミッションに依存します。
    mkdir -p /mnt/efs; \
    chown www-data:www-data /mnt/efs; \
    chmod 0750 /mnt/efs; \
    \
    # PHPアプリケーション自体はroot所有・読み取り専用にする。
    chown -R root:root /var/www/html; \
    find /var/www/html -type d -exec chmod 0755 {} +; \
    find /var/www/html -type f -exec chmod 0644 {} +; \
    \
    # PHP構文チェック。
    php -l /var/www/html/public/index.php; \
    php -l /usr/local/libexec/verify-infra.php

# 環境固有値および認証情報はECS on Fargateタスク定義から注入する。

ENV APP_ENV=verification \
    APP_DEBUG=false \
    DB_CONNECTION=mysql \
    DB_PORT=3306 \
    MYSQL_ATTR_SSL_CA=/etc/ssl/certs/rds-global-bundle.pem \
    DB_SSL_VERIFY_SERVER_CERT=true \
    EFS_MOUNT_PATH=/mnt/efs

EXPOSE 9000

CMD ["php-fpm", "-F"]

## Nginx

FROM ${NGINX_IMAGE} AS nginx

# ECS on Fargateタスク定義のコンテナHealthCheckでcurlを使用する。
RUN apk add --no-cache curl

COPY docker/nginx/default.conf.template \
    /etc/nginx/templates/default.conf.template

COPY docker/public /var/www/html/public

ENV PHP_FPM_HOST=127.0.0.1 \
    PHP_FPM_PORT=9000 \
    NGINX_ENVSUBST_FILTER="^(PHP_FPM_HOST|PHP_FPM_PORT)$"

EXPOSE 80

今回ご紹介するのは、ECS on Fargateの疎通確認項目を網羅するものではなく、個人的に気になった箇所をピックアップして実施しています。
実際にテスト項目を検討する際は、ユースケースやインフラ・アプリの構成に合わせて、何をテストするべきかを十分にご検討ください。

今回検証する項目は以下の通りです。

  • ECS on FargateにEFSをマウントして、ファイル書き込み、読み取りができるか
  • ECS on FargateからSecrets Manager経由でDB認証情報を取得して、RDS(MySQL)へ接続し、SQLを実行できるか

検証1: ECS on Fargate FargateからEFSをマウントして、ファイル書き込み、読み取りする

まずは、EFSのマウント部分を簡単に説明します。

手動でのマウント手順は以下のブログがわかりやすいのでこちらをご覧ください。

https://dev.classmethod.jp/articles/202310-ecs-efs-01/

前述のDockerfile内でマウント用のディレクトリを作成しています。

抜粋

    mkdir -p /mnt/efs; \
    chown www-data:www-data /mnt/efs; \
    chmod 0750 /mnt/efs; \
    \

CloudFormation側ではECS on Fargateのタスク定義とコンテナ設定において、EFSのマウント設定を記述しています。
EFSへの接続情報等はCloudFormationのパラメータ化してあります。
例えば、EFSのAccessPointのArnやID、FileSystemのIDといった情報が設定には必要です。
パラメータのイメージ
※EFSのARNはIAMロールの権限を絞るために使用しているので、マウント設定に使用しているわけではありません。

CloudFormationのECS on Fargateのテンプレートの該当箇所を抜粋しました。

〜略〜
  TaskDefinition:
    Type: AWS::ECS on Fargate::TaskDefinition
    Properties:
〜略〜
      Volumes:
        - Name: efs-data
          EFSVolumeConfiguration:
            FilesystemId: !Ref EfsFileSystemId
            RootDirectory: "/"
            TransitEncryption: ENABLED
            AuthorizationConfig:
              AccessPointId: !Ref EfsAccessPointId
              IAM: ENABLED
      ContainerDefinitions:
        - Name: php-fpm
          Image: !Ref PhpContainerImage
          Essential: true
          PortMappings:
            - ContainerPort: !Ref PhpFpmPort
              Protocol: tcp
          Environment:
   〜略〜
            - Name: EFS_MOUNT_PATH
              Value: !Ref EfsMountContainerPath
          Secrets:
            - Name: APP_ENV
              ValueFrom: !Sub "${AppConfigSecretArn}:app_environment::"
            - Name: LOG_LEVEL
              ValueFrom: !Sub "${AppConfigSecretArn}:log_level::"
            - Name: API_TIMEOUT_SEC
              ValueFrom: !Sub "${AppConfigSecretArn}:api_timeout_sec::"
            - Name: DB_CONNECTION
              ValueFrom: !Sub "${AppConfigSecretArn}:db_engine::"
            - Name: DB_HOST
              ValueFrom: !Sub "${AppConfigSecretArn}:db_endpoint::"
            - Name: DB_PORT
              ValueFrom: !Sub "${AppConfigSecretArn}:db_port::"
            - Name: DB_DATABASE
              ValueFrom: !Sub "${AppConfigSecretArn}:db_name::"
            - Name: DB_USERNAME
              ValueFrom: !Sub "${DbSecretArn}:username::"
            - Name: DB_PASSWORD
              ValueFrom: !Sub "${DbSecretArn}:password::"
          MountPoints:
            - SourceVolume: efs-data
              ContainerPath: !Ref EfsMountContainerPath
              ReadOnly: false
〜略〜

主なタスク定義のEFSマウント用のパラメータの解説
※パラメータ名から自明なものは割愛します

パラメータ名 説明 今回の設定値
TransitEncryption ECS on FargateとEFS間でのデータ転送中のEFSデータの暗号化の有効化、今回IAM認証を使用してEFSへアクセスさせているので、有効化に設定 ENABLED
RootDirectory ECS on Fargate内にEFSをマウントするときに、ルートディレクトリとしてマウントするEFSファイルシステム内のディレクトリを指定、今回アクセスポイントを使用するため、省略するか/に指定する必要あり(省略すると/に設定される) "/"
AccessPointId 使用するアクセスポイントのID/アクセスポイントを使用する際は、前述のデータ転送の暗号化の有効化とRootDirectoryを省略または/に設定する必要がある XXX
IAM タスク定義で指定したECS on FargateタスクのIAMロールをEFSのマウント時に使用するかを指定/使用するには前述のデータ転送の暗号化を有効にする必要あり ENABLED

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/specify-efs-config.html

それではお待ちかねの検証をやっていきましょう!

CloudShellから以下のコマンドを実行して、構築した検証用ECS on Fargateへ接続します。

aws ECS on Fargate execute-command \
   --cluster <ECS on Fargateクラスター> \
   --task <タスクID> \
   --container <コンテナ> \
   --interactive \
   --command "/bin/sh"

接続できました。
 ECS on Fargate接続完了

まずは仕込んでおいたテストファイルを使って、EFSへの書き込みとRDSへの接続が上手くいっているかを検証します。
(www-dataユーザー権限でPHPスクリプトverify-infra.phpを実行)

su-exec www-data php /usr/local/libexec/verify-infra.php

成功画面

ブログ執筆前に何度か実行していたので、EFSのレコード数は5になっています。
無事EFSへの書き込みとRDSへの接続を検証できたようです。

それでは試しにタスクをもう1つ増やしてみて、マウント先のディレクトリを確認してみます。
※タスク数の増やし方は割愛しますが、ECS on Fargateのサービスの更新から簡単に増やすことができました。

ls /mnt/efs
cat /mnt/efs/<書き込んだファイ>

追加したタスクに接続して、先ほどのテストファイルで作成、書き込みされたファイルを確認しました!
中身を見ると検証時の日時で書き込みされていることも確認できました。
確認結果

まだ物足りないので、直接マウントしたディレクトリにファイルを作成してみます。
作成結果

一度今接続しているコンテナからはexitで抜けて、元々起動していた方に再接続します。

再度ファイルを確認し、無事こちらのコンテナでも見れることを確認しました!
再度確認結果

検証2: ECS on Fargate FargateからSecrets Manager経由でDB認証情報を取得して、RDS(MySQL)へ接続し、SQLを実行する

先ほど検証1で以下をコンテナで実行した際に、PHPでRDSへの接続は検証しました。

su-exec www-data php /usr/local/libexec/verify-infra.php

Secrets Manager側にDBの接続情報を登録しておき、タスク起動時に、タスク実行ロールを使用してSecrets Managerから値を取得し、コンテナの環境変数へ注入します。

Secrets Managerの例
Secrets Managerの例

コンテナの環境変数設定の例(ECS on Fargate>クラスター>タスク>設定)
コンテナの環境変数設定の例

例えば、DBのエンドポイント情報は先ほどのSecrets Managerのシークレットキー「db_endpoint」に登録された値を取ってくる設定になっています。
参照例

先ほどのテストファイル実行結果を見ると、RDSへの接続先ホスト名や、データベース名、ユーザー名等が確認できます。

成功画面

では実際にDBへ読み取り、書き込みできるかを試していきます。
事前に踏み台経由でRDSへはblog_verification_testというテーブルを作成して、1件テストレコードを登録しておきました。

以下でテーブルの全レコードを取得してみます。
query("SELECT * FROM blog_verification_test"); ここがいわゆる実行するSQLにあたる箇所です。

php -r '
$pdo = new PDO(
    sprintf("mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4", getenv("DB_HOST"), getenv("DB_PORT"), getenv("DB_DATABASE")),
    getenv("DB_USERNAME"),
    getenv("DB_PASSWORD"),
);
$stmt = $pdo->query("SELECT * FROM blog_verification_test");
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    print_r($row);
}
'

確認できました!
確認結果

ではテストレコードを5件ほど追加してみましょう。

php -r '
$pdo = new PDO(
    sprintf("mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4", getenv("DB_HOST"), getenv("DB_PORT"), getenv("DB_DATABASE")),
    getenv("DB_USERNAME"),
    getenv("DB_PASSWORD"),
);
$stmt = $pdo->prepare("INSERT INTO blog_verification_test (message) VALUES (?)");
for ($i = 1; $i <= 5; $i++) {
    $stmt->execute([sprintf("test record #%d from app container", $i)]);
}
echo "Inserted 5 rows." . PHP_EOL;
'

5件登録

再度、テーブルの中身を覗いてみます。

ちゃんと5件追加されてますね!
確認結果

つまずいた点

ECSからECRイメージを取得する際はS3への通信も必要

今回の検証構成で、当初は、NAT Gateway経由でAmazon ECRのパブリックサービスエンドポイントにアクセスしていました。

ただふと、本番の業務システムでそのような構成を取るか???と思い直し、あるべき姿で検証するために、ECS on Fargateからのアウトバウンド通信をSGレベルで制限し、ECRへはVPCエンドポイント(インターフェース型/ecr.api ecr.dkr)を使用する構成に変更しました。

変更後、再度ECS on Fargateをデプロイし直したのですが、その際にイメージのプルに失敗して焦りました。。
変更前は正常にイメージ取得して、コンテナが起動していたのになぜ。。。?
再デプロイの手順に間違いがあったようには思えませんでした。

タスクのログを見てみると、以下のような記述がありました。

CannotPullContainerError:
~略~
There is a connection issue between the task and the registry. Check your task network configuration. : failed to copy: httpReadSeeker: failed open: failed to do request:
~略~

ネットワークのエラーなのは一目瞭然ですね。
ここでVPCエンドポイント化やSG周りによる影響を疑いました。

調べてみると、ECRのイメージレイヤーはS3経由(今回の構成では、Gateway型VPCエンドポイント経由)で取得されますが、Gateway EndpointはSGを持たないため、DestinationSecurityGroupIdでは指定できません。代わりにECSタスクのSGでアウトバウンドを制限している場合は、S3のAWS管理プレフィックスリスト宛てにTCP/443を許可する必要があることがわかりました。

インターネット検索ですぐヒットしたので、知らなかった自分が恥ずかしいです。

以下のコマンドで、S3のプレフィックスリストIDを取得できます。
aws ec2 describe-managed-prefix-lists --filters "Name=owner-id,Values=AWS" "Name=prefix-list-name,Values=com.amazonaws.ap-northeast-1.s3" --query 'PrefixLists[0].PrefixListId' --output text --region ap-northeast-1
※リージョンは環境に合わせて変えてください!

ECS on FargateのSGのアウトバウンドルールに追加しました!
SGの追加箇所

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/vpc-endpoints.html#ecr-setting-up-s3-gateway

まとめ

今回は、ECS on Fargateのコンテナに実際に接続し、 EFSやRDSに対する動作検証を行いました。

知識として、「EFSをマウントして〜」や「DBの認証情報はSecrets Managerから取得して〜」といった感じで認識しているものでも、実際にその挙動を目にすると理解度が違うと実感しました!
やって良かったです。

Fargateタスクのローカルストレージは一時的なもので、タスクが終了すると保存データは失われます。一方、EFSはタスクのライフサイクルから独立した永続共有ストレージとして利用できます。
実際に触ってみて、EFSをマウントして共有ストレージとして利用することで、タスクの入れ替え時にもデータが保持されるだけでなく、タスクを増やしたときにも参照できるのが嬉しいポイントだなと改めて思いました。

これからも自分の目で見るまでは信用しないというマインドで積極的にいろいろなことを試してみようと思います!

以上、コンサルティング部のみゃんでした!

参考リンク

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/specify-efs-config.html

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/efs-best-practices.html

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/tutorial-efs-volumes.html

https://dev.classmethod.jp/articles/202310-ecs-efs-01/

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/specifying-sensitive-data-tutorial.html

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/vpc-endpoints.html#ecr-setting-up-s3-gateway

この記事をシェアする

関連記事