概要
AWS CDKを利用してECS+FargateでLaravel11の初期画面を表示するまでのテンプレートを作成しました。
何番煎じか分かりませんが、そのものズバリな記事はなかったので残しておこうと思います。
最終的なコードはリポジトリにあります。
前提
- AWS CLIのインストール(インストールガイド)
- AWS CDKのインストール(インストールガイド)
- AWS CLIで
aws configure
を実行し、アカウントに接続できていること -
cdk bootstrap
がアカウントに対して実行済みであること。
利用言語
- Typescript
解説
※コンテナの中身の解説はしません。Dockerfileはリポジトリをご参照ください。
設定
環境ごとで設定を変えたいし、リポジトリには上げたくない情報もあるので設定ファイルを参照する形にしています。
読み込む設定ファイルをcdk deploy
時に自動的に切り替えるようにしたかったですが、今回は直で読み込みに行ってます。
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の定義と、ビルド&デプロイ設定を行います。
// 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はビルド時に親ディレクトリを参照できません。参照する場合、実行ディレクトリはルートにし、ファイルを個別で指定する必要があります。
↓こういうケース
COPY ./app/ /var/www/app
VPCとクラスターの定義
ECSにおけるクラスターはサービスとタスクを実行する基盤です。
クラスター内には複数のタスクが配置され、それぞれが独自のコンテナを実行します。
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コンテナに依存しているため、依存関係も定義します。
/**
* タスクとコンテナのポートとログを定義
*/
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の通信設定はそれに則る必要があります。
# 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をコンテナで運用したい方の参考になれば幸いです。