2
1

AWS CDKを利用してECS+FargateでLaravel環境構築

Posted at

概要

AWS CDKを利用してECS+FargateでLaravel11の初期画面を表示するまでのテンプレートを作成しました。
何番煎じか分かりませんが、そのものズバリな記事はなかったので残しておこうと思います。
最終的なコードはリポジトリにあります。

前提

  • AWS CLIのインストール(インストールガイド)
  • AWS CDKのインストール(インストールガイド)
  • AWS CLIでaws configureを実行し、アカウントに接続できていること
  • cdk bootstrapがアカウントに対して実行済みであること。

利用言語

  • Typescript

解説

※コンテナの中身の解説はしません。Dockerfileはリポジトリをご参照ください。

設定

環境ごとで設定を変えたいし、リポジトリには上げたくない情報もあるので設定ファイルを参照する形にしています。
読み込む設定ファイルをcdk deploy時に自動的に切り替えるようにしたかったですが、今回は直で読み込みに行ってます。

stack/config/example.ts
import { RemovalPolicy } from "aws-cdk-lib"

export const config = {
  account: "xxxxxxxxxxxxxx",
  region: "ap-northeast-1",
  // スタック名
  stackName: "xxxxxx-stack",
  resourceName: "xxxxxxxxxxxxx",
  ecrRepository: {
    // お試しなのでスタック削除時はまとめて削除、本番では非推奨
    removalPolicy: RemovalPolicy.DESTROY,
    autoDeleteImage: true,
  },
  logGroup: {
    // お試しなのでスタック削除時はまとめて削除、本番では非推奨
    removalPolicy: RemovalPolicy.DESTROY,
  }
}

ECRの定義

LaravelとNginxのコンテナイメージを保存するECRの定義と、ビルド&デプロイ設定を行います。

stack/ecs_stack.ts
    // Nginx用リポジトリ
    const ecrRepositoryNginx = new Ecr.Repository(this, "EcrRepoNginx", {
      repositoryName: `${resourceName}-ecr-repo-nginx`,
      removalPolicy: config.ecrRepository.removalPolicy,
      emptyOnDelete: config.ecrRepository.autoDeleteImage,
    })

    const dockerImageAssetNginx = new DockerImageAsset(this, "DockerImageAssetNginx", {
      // Dockerfileは親ディレクトリ参照できないのでdirecotryとfileを別で定義
      directory: '.',
      file: "docker/nginx/Dockerfile",
      platform: Platform.LINUX_AMD64,
    })

    new EcrDeploy.ECRDeployment(this, "DeployDockerImageNginx", {
      src: new EcrDeploy.DockerImageName(dockerImageAssetNginx.imageUri),
      dest: new EcrDeploy.DockerImageName(
        `${accountId}.dkr.ecr.${region}.amazonaws.com/${ecrRepositoryNginx.repositoryName}:latest`
      )
    })

    //PHPのリポジトリは名称以外同様のため省略

注意点

Dockerfileはビルド時に親ディレクトリを参照できません。参照する場合、実行ディレクトリはルートにし、ファイルを個別で指定する必要があります。

↓こういうケース

./docker/php/Dockerfile
COPY ./app/ /var/www/app

VPCとクラスターの定義

ECSにおけるクラスターはサービスとタスクを実行する基盤です。
クラスター内には複数のタスクが配置され、それぞれが独自のコンテナを実行します。

stack/ecs_stack.ts
    const vpc = new Ec2.Vpc(this, "Vpc", {
      vpcName: `${resourceName}-vpc`,
      maxAzs: 2,
      ipAddresses: Ec2.IpAddresses.cidr("10.0.0.0/20"),
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: `${resourceName}-public`,
          subnetType: Ec2.SubnetType.PUBLIC,
        },
      ],
    })

    const cluster = new Ecs.Cluster(this, "EcsCluster", {
      clusterName: `${resourceName}-cluster`,
      vpc: vpc,
    })

タスク定義

タスク定義を行います。
ECSにおけるタスクとは、コンテナ群の定義です。個人的にはdocker-composeの役割と考えるとしっくり来ます。
今回は、コンテナのポートとログ設定を行っています。

また、NginxコンテナはPHPコンテナに依存しているため、依存関係も定義します。

stack/ecs_stack.ts
    /**
     * タスクとコンテナのポートとログを定義
     */
    const taskDefinition = new Ecs.FargateTaskDefinition(this, "TaskDefinition", {
      family: `${resourceName}-taskdef`
    })

    const logGroup = new Logs.LogGroup(this, "LogGroup", {
      logGroupName: `/aws/ecs/${resourceName}`,
      removalPolicy: config.logGroup.removalPolicy,
    })

    const nginxContainer = taskDefinition.addContainer('nginx', {
      image: Ecs.ContainerImage.fromEcrRepository(ecrRepositoryNginx, "latest"),
      portMappings: [
        {
          hostPort: 80,
          containerPort: 80,
        }
      ],
      logging: new Ecs.AwsLogDriver({
        streamPrefix: `nginx`,
        logGroup: logGroup,
      }),
    })

    const appContainer = taskDefinition.addContainer('app', {
      image: Ecs.ContainerImage.fromEcrRepository(ecrRepositoryPhp, "latest"),
      portMappings: [
        {
          hostPort: 9000,
          containerPort: 9000,
        }
      ],
      logging: new Ecs.AwsLogDriver({
        streamPrefix: `app`,
        logGroup: logGroup,
      }),
    })

    // コンテナの依存関係を定義
    nginxContainer.addContainerDependencies({
      container: appContainer,
      condition: Ecs.ContainerDependencyCondition.START
    })

注意点

ネットワークはデフォルトのawsvpcを利用しています。
この場合、コンテナ間はlocalhostで通信を行うため、Nginxの通信設定はそれに則る必要があります。

./docker/nginx/default.conf
    # ECSのネットワーキングがawsvpcの場合、localhostで接続できる
    fastcgi_pass localhost:9000;

私は当初コンテナ名で記載しており、通信が通らず苦悩しました。。。😭

ecs-patternsを利用してALBとFargateの定義

ecs-patternsはECSの利用パターンに沿ったテンプレートを用意してくれているライブラリです。
今回はApplicationLoadBalancedFargateServiceを利用します(公式Doc)

    const service = new EcsPatterns.ApplicationLoadBalancedFargateService(
      this,
      "FargateService",
      {
        loadBalancerName: `${resourceName}-lb`,
        publicLoadBalancer: true,
        cluster: cluster,
        serviceName: `${resourceName}-service`,
        cpu: 256,
        desiredCount: 2,
        memoryLimitMiB: 512,
        assignPublicIp: true,
        taskSubnets: { subnetType: Ec2.SubnetType.PUBLIC },
        targetProtocol: ApplicationProtocol.HTTP,
        taskDefinition: taskDefinition,
      }
    )

注意点

特に定義していない場合、LBのターゲットポートが最初にタスク定義したコンテナのポートになります。
今回の場合、PHPのコンテナを最初に定義すると9000番にアクセスしてしまいます。
当然、ヘルスチェックも失敗するので、タスクが停止します。
LBがどのポートに繋げようとしているかはチェックしておきましょう。

デプロイ

cdk deployを実行すればスタックを作成することができます。
複数プロファイルがある場合は、事前にexport AWS_PROFILE=xxxxxxxでプロファイルを指定してください。

デプロイに成功すると下記の出力がされるので、DNS名にブラウザでアクセスしてLaravelの初期画面が表示されたら完了です。

 ✅ xxxxxxxx-stack

✨  Deployment time: 00.00s

Outputs:
xxxxxxxx-stack.FargateServiceLoadBalancerDNSxxxxxxx = {DNS名}
xxxxxxxx-stack.FargateServiceServiceURLxxxxx = {ECSサービスURL}

まとめ

普段EC2インスタンスでの構築をしており、ECSに慣れていなかったため良い勉強になりました。
Laravelをコンテナで運用したい方の参考になれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1