いよいよ、第6回で作ったコンテナイメージを AWS の ECS on Fargate(=サーバーを自分で管理せずにコンテナを動かせるサービス)で動かし、ALB(=ロードバランサー。利用者からのアクセスを受けて複数のコンテナに振り分ける装置)経由で HTTPS 公開します。
「コンテナで動かすと聞いたけれど、ECS のタスク定義・サービス・ALB・IAM ロールと部品が多すぎて、どれが何をしているのか分からない…」
結論から言うと、ECS で Laravel の管理画面を公開するときの部品は「イメージ置き場(ECR)」「コンテナの設計図(タスク定義)」「台数を保つ係(サービス)」「入口(ALB)」の4つで、権限は「ECS が使うロール」と「アプリが使うロール」に分けます。この回ではこれらを Terraform で定義し、あわせて ALB の裏で Laravel が HTTPS を正しく認識するための設定(信頼するプロキシ)を入れます。
前回(第8回:RDS for MySQL と秘密情報)では、RDS と秘密情報の置き場所、秘密情報を読むためのタスク実行ロールを作りました。今回はそれらをタスク定義から参照します。
これまでと同じく、執筆環境には AWS アカウントがないため、terraform apply は行っていません。terraform validate と、AWS に接続しない形での plan、ローカルの Docker での動作確認までを行っています。
ECS で管理画面を公開するための部品
今回追加・変更するファイルは次のとおりです。
| ファイル | 内容 |
|---|---|
infra/terraform/ecr.tf | イメージの置き場所(app = php-fpm、web = nginx)と古いイメージの自動削除 |
infra/terraform/logs.tf | コンテナのログの保存先(CloudWatch Logs)と保持期間 |
infra/terraform/ecs_cluster.tf | ECS クラスターと、タスク定義で共通に使う環境変数・秘密情報の参照 |
infra/terraform/ecs_web.tf | web のタスク定義(nginx + php-fpm)とサービス |
infra/terraform/alb.tf | ALB、ターゲットグループ(ヘルスチェック /up)、HTTP→HTTPS リダイレクト、HTTPS リスナー |
infra/terraform/iam.tf | アプリ用のタスクロールを追加 |
infra/terraform/security_groups.tf | ALB に接続できる IP 範囲を変数化 |
config/trustedproxy.php、tests/Feature/TrustedProxyTest.php | ALB を信頼するプロキシとして扱う設定とテスト |
リクエストの流れは次のようになります。
- 利用者のブラウザ →(HTTPS)→ ALB(public サブネット)
- ALB →(HTTP 8080)→ web タスクの nginx(private サブネット)
- nginx →(127.0.0.1:9000)→ 同じタスクの php-fpm(Laravel)
- php-fpm → RDS(3306)、ログは標準出力 → CloudWatch Logs
ECR:イメージの置き場所を作る
infra/terraform/ecr.tf(抜粋)
locals {
ecr_repositories = toset(["app", "web"])
}
resource "aws_ecr_repository" "this" {
for_each = local.ecr_repositories
name = "${local.name_prefix}-${each.key}"
image_tag_mutability = "IMMUTABLE"
# push 時に既知の脆弱性をスキャンする(基本スキャン)
image_scanning_configuration {
scan_on_push = true
}
# (encryption_configuration と tags は省略)
}
# 古いイメージを自動で削除する(保管費用を抑える)。切り戻し用に直近の数世代は残す
resource "aws_ecr_lifecycle_policy" "this" {
for_each = aws_ecr_repository.this
repository = each.value.name
policy = jsonencode({
rules = [
# (rulePriority = 1:タグなしのイメージを1日で削除、は省略)
{
rulePriority = 2
description = "Keep only the latest ${var.ecr_keep_images} images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = var.ecr_keep_images
}
action = { type = "expire" }
},
]
})
}
ポイントは image_tag_mutability = "IMMUTABLE" です。同じタグでイメージを上書きできなくするので、latest のような使い回しのタグではなく、Git のコミット ID などをタグにします。「今どのコードが動いているか」をタグから必ずたどれるようにし、切り戻し(前の版に戻す)のときも迷わないようにするためです。
ライフサイクルポリシーは、イメージを無制限にためないための設定です。ただし、残す世代数は「どこまで前の版に戻せるか」と同じ意味になるので、少なくしすぎないようにします。
📰 出典:Amazon ECR のライフサイクルポリシー(Amazon ECR ユーザーガイド)
タスク定義:nginx と php-fpm を1つのタスクにまとめる
共通の環境変数と秘密情報
web・worker・scheduler・migrate(第10回)の php コンテナには、同じ環境変数と秘密情報を渡します。そこで、ecs_cluster.tf の locals に一度だけ書いて使い回します。
infra/terraform/ecs_cluster.tf(抜粋)
locals {
# php コンテナ(web・worker・scheduler・migrate 共通)に渡す環境変数。秘密ではない値だけ
app_environment = [
{ name = "APP_NAME", value = "Admin Console" },
{ name = "APP_URL", value = "https://${var.app_domain}" },
{ name = "APP_LOCALE", value = "ja" },
{ name = "DB_CONNECTION", value = "mysql" },
{ name = "DB_HOST", value = aws_db_instance.main.address },
{ name = "DB_PORT", value = tostring(aws_db_instance.main.port) },
{ name = "DB_DATABASE", value = aws_db_instance.main.db_name },
{ name = "SESSION_DRIVER", value = "database" },
{ name = "SESSION_SECURE_COOKIE", value = "true" },
{ name = "CACHE_STORE", value = "database" },
{ name = "QUEUE_CONNECTION", value = "database" },
# ALB からの X-Forwarded-* を信用する範囲(config/trustedproxy.php)
{ name = "TRUSTED_PROXIES", value = var.vpc_cidr },
# (APP_FALLBACK_LOCALE は省略)
]
# 秘密情報:値ではなく「どこにあるか(ARN)」だけを書く。取得はタスク実行ロールが行う(第8回)
app_secrets = concat(
[
{ name = "DB_USERNAME", valueFrom = "${aws_db_instance.main.master_user_secret[0].secret_arn}:username::" },
{ name = "DB_PASSWORD", valueFrom = "${aws_db_instance.main.master_user_secret[0].secret_arn}:password::" },
],
[for name, p in aws_ssm_parameter.app_secret : { name = name, valueFrom = p.arn }],
)
app_image = "${aws_ecr_repository.this["app"].repository_url}:${var.image_tag}"
web_image = "${aws_ecr_repository.this["web"].repository_url}:${var.image_tag}"
}
secrets に書くのは値ではなく ARN(=AWS 上の場所を表す識別子)です。RDS が管理するシークレットは username と password を持つ JSON なので、ARN の後ろに :password:: のようにキー名を付けて、その項目だけを環境変数に入れます。ECS がタスクの起動時にタスク実行ロールの権限で値を取り出し、コンテナの環境変数として渡します。
📰 出典:Secrets Manager シークレットを環境変数として Amazon ECS に渡す(Amazon ECS デベロッパーガイド)
第6回の本番イメージは .env を含まず、APP_ENV=production・APP_DEBUG=false・LOG_CHANNEL=stderr だけをイメージの既定値にしています。それ以外の設定は、すべてこのタスク定義から渡すことになります。
web のタスク定義
infra/terraform/ecs_web.tf(タスク定義の抜粋)
resource "aws_ecs_task_definition" "web" {
family = "${local.name_prefix}-web"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = var.web_cpu
memory = var.web_memory
# タスク実行ロール:ECS がイメージ取得・ログ送信・secrets の取得に使う(第8回)
execution_role_arn = aws_iam_role.ecs_task_execution.arn
# タスクロール:アプリ(Laravel)が AWS のサービスを使うときの権限(第10回で SQS を追加)
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "php"
image = local.app_image
essential = true
# CMD の既定(php-fpm)のまま。ENTRYPOINT が起動時に php artisan optimize を実行する(第6回)
environment = local.app_environment
secrets = local.app_secrets
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.ecs["web"].name
awslogs-region = var.region
awslogs-stream-prefix = "php"
}
}
},
{
name = "nginx"
image = local.web_image
essential = true
portMappings = [
{ containerPort = 8080, protocol = "tcp" }
]
# php コンテナが起動してから nginx を起動する
dependsOn = [
{ containerName = "php", condition = "START" }
]
# (logConfiguration は php と同じ。awslogs-stream-prefix = "nginx")
},
])
# (runtime_platform と tags は省略)
}
network_mode = "awsvpc" では、同じタスクのコンテナは localhost を共有します。第6回で nginx の転送先を 127.0.0.1:9000 にし、compose.prod.yaml で network_mode: service:app を使って確認したのは、この動きをローカルで再現するためでした。
CPU とメモリはタスク全体(nginx と php-fpm の合計)の値で、Fargate では決まった組み合わせから選びます。0.5 vCPU(512)なら、メモリは 1GB〜4GB の範囲です。
📰 出典:Fargate の Amazon ECS タスク定義の CPU とメモリ(Amazon ECS デベロッパーガイド)
タスク実行ロールとタスクロールを分ける
task_role_arn に指定したタスクロールは、今回新しく作ったロールです。
infra/terraform/iam.tf(追加部分)
# 第9回の時点では権限を付けない(DB へは SG と DB ユーザーで接続し、IAM 権限は使わない)。
# 第10回で SQS の送受信の権限をここに追加する。
resource "aws_iam_role" "ecs_task" {
name = "${local.name_prefix}-ecs-task"
assume_role_policy = data.aws_iam_policy_document.ecs_tasks_assume.json
}
2つのロールの違いは次のとおりです。
| ロール | 使う主体 | 主な権限 |
|---|---|---|
| タスク実行ロール(第8回) | ECS(タスクを起動する側) | ECR からのイメージ取得、ログ送信、secrets の取得 |
| タスクロール(今回) | コンテナ内のアプリ(Laravel) | アプリが呼ぶ AWS サービス(第10回で SQS) |
分けておくと、アプリに脆弱性があって AWS の認証情報を悪用されたとしても、秘密情報の取得権限までは渡りません。今の時点でタスクロールに権限は何もありません。
📰 出典:Amazon ECS タスクの IAM ロール(Amazon ECS デベロッパーガイド)
サービスと ALB:2つの AZ で動かし、/up で見張る
infra/terraform/ecs_web.tf(サービスの抜粋)
resource "aws_ecs_service" "web" {
name = "web"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.web.arn
desired_count = var.web_desired_count
launch_type = "FARGATE"
# private サブネット(2AZ)に置き、パブリック IP は付けない。外向きは NAT Gateway 経由(第7回)
network_configuration {
subnets = aws_subnet.private[*].id
security_groups = [aws_security_group.web.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.web.arn
container_name = "nginx"
container_port = 8080
}
# 起動直後(php artisan optimize の実行中など)にヘルスチェック失敗で入れ替えられないよう、猶予を置く
health_check_grace_period_seconds = 60
# デプロイ時:新しいタスクが healthy になってから古いタスクを止める(台数が一時的に最大2倍になる)
deployment_minimum_healthy_percent = 100
deployment_maximum_percent = 200
# (depends_on と tags は省略)
}
サービスは「指定した台数(desired_count)のタスクを動かし続ける係」です。タスクが落ちれば新しく起動し直し、ALB のターゲットにも自動で登録します。既定の2台は、2つの AZ(=データセンターのまとまり)に分かれて置かれるので、片方の AZ に障害が起きても画面は使えます。
infra/terraform/alb.tf(ターゲットグループと HTTPS リスナーの抜粋)
resource "aws_lb_target_group" "web" {
name = "${local.name_prefix}-web"
target_type = "ip" # Fargate(awsvpc)はタスクの IP アドレスを登録する
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/up" # Laravel のヘルスチェック用ルート(bootstrap/app.php の health: '/up')
matcher = "200"
interval = 15
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 3
}
# デプロイ時、古いタスクへの処理中リクエストを待つ時間(既定 300 秒は長いので短くする)
deregistration_delay = 30
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" # TLS 1.2 以上
certificate_arn = var.acm_certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
80番(HTTP)のリスナーは、443番(HTTPS)へ 301 リダイレクトするだけにしています。証明書は、ドメインの DNS で検証して ACM(=AWS の無料の証明書発行サービス)に発行したものの ARN を変数で渡します。ドメインの取得や DNS の設定は、会社ごとに管理の仕方が違うため Terraform の外に出しました。
ALB は /up に定期的にアクセスし、200 が返るタスクだけにリクエストを流します。
📰 出典:ターゲットグループのヘルスチェック(Application Load Balancer ユーザーガイド)
接続できる IP を絞る
infra/terraform/security_groups.tf(変更部分)
# 接続を許可する元の IP 範囲は変数で指定する(第9回)。既定はインターネット全体、
# 検証環境や社内専用の管理画面なら、社内・VPN の IP だけに絞る
resource "aws_vpc_security_group_ingress_rule" "alb_https" {
for_each = toset(var.alb_ingress_cidrs)
security_group_id = aws_security_group.alb.id
description = "HTTPS"
cidr_ipv4 = each.value
ip_protocol = "tcp"
from_port = 443
to_port = 443
}
社内向けの管理画面なら、インターネット全体に公開する理由はあまりありません。terraform.tfvars の alb_ingress_cidrs に社内や VPN の IP 範囲を書けば、それ以外からは ALB に届かなくなります。特にこの連載のサンプルは、スターターキットの会員登録画面を残したままです(第3回で触れたとおり)。誰でもアカウントを作れる状態でインターネット全体に公開しないよう、最初は IP で絞っておくことをおすすめします。
ALB の裏で HTTPS を正しく認識させる(TrustProxies)
ALB で HTTPS を終端すると、Laravel から見た通信は「ALB から来た HTTP」です。そのままだと、リダイレクト先やフォームの送信先の URL が http:// で作られ、画面が正しく動きません。
Laravel には、リバースプロキシ(=利用者とアプリの間に立って中継するサーバー。ここでは ALB)が付ける X-Forwarded-Proto などのヘッダーを信用する仕組み(TrustProxies ミドルウェア)が最初から入っています。信用する接続元を設定ファイルで指定しました。
config/trustedproxy.php
<?php
declare(strict_types=1);
/*
| 信頼するリバースプロキシ(ALB)の IP アドレス範囲(第9回)
| (中略)
| ここに書いた範囲からの接続に限り、ALB が付ける X-Forwarded-For / X-Forwarded-Proto などを信用する。
| フレームワークの TrustProxies ミドルウェア(既定で有効)が、bootstrap/app.php で
| trustProxies(at: ...) を指定していないときにこの設定を読む。
|
| 例:TRUSTED_PROXIES=10.0.0.0/16(VPC の範囲。ECS のタスク定義で渡す)
| 未設定(ローカル開発)の場合はどのプロキシも信用しない。
*/
return [
'proxies' => env('TRUSTED_PROXIES'),
];
公式ドキュメントでは bootstrap/app.php で trustProxies(at: '*') のように指定する方法が紹介されています。今回は、設定キャッシュ(php artisan optimize)と環境変数で切り替えやすいように設定ファイルにし、値には VPC の IP 範囲を渡しました。web タスクには ALB からしか届かないようにセキュリティグループで絞っていますが、ヘッダーを信用する範囲も VPC 内に限定しておけば、万一ほかの経路から届いた偽のヘッダーは無視されます。
📰 出典:信頼できるプロキシの設定(Laravel 12.x ドキュメント)
tests/Feature/TrustedProxyTest.php(抜粋)
public function test_requests_from_trusted_proxy_are_treated_as_https(): void
{
config(['trustedproxy.proxies' => '10.0.0.0/16']);
$response = $this->withServerVariables(['REMOTE_ADDR' => '10.0.1.25'])
->withHeaders($this->albHeaders)
->get('/dashboard');
$response->assertRedirect();
$this->assertStringStartsWith('https://', (string) $response->headers->get('Location'));
}
public function test_forwarded_headers_from_untrusted_address_are_ignored(): void
{
config(['trustedproxy.proxies' => '10.0.0.0/16']);
// VPC の外から直接ヘッダーを偽装しても、HTTPS 扱いにはならない
$response = $this->withServerVariables(['REMOTE_ADDR' => '198.51.100.7'])
->withHeaders($this->albHeaders)
->get('/dashboard');
$response->assertRedirect();
$this->assertStringStartsWith('http://', (string) $response->headers->get('Location'));
}
未ログインで /dashboard を開いたときのログイン画面へのリダイレクト先で、HTTPS として扱われているかを確かめています。このほか、設定がない場合は信用しないこと、信用した場合はアクセス元 IP が X-Forwarded-For の値になることもテストしています。あわせて、タスク定義で SESSION_SECURE_COOKIE=true を渡し、セッションの Cookie が HTTPS のときだけ送られるようにしました。
初回のデプロイ手順
ECR が無いとイメージを push できず、イメージが無いとサービスのタスクが起動できません。初回だけは次の順に進めます(コマンドは infra/terraform で実行する想定。<...> は自分の環境の値)。
# 1. ECR だけ先に作る
terraform apply -target='aws_ecr_repository.this'
# 2. イメージを build して push する(app と web は同じタグにする)
aws ecr get-login-password --region ap-northeast-1 \
| docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com
TAG=$(git rev-parse --short HEAD)
docker build --target app -t <app のリポジトリ URL>:$TAG .
docker build --target web -t <web のリポジトリ URL>:$TAG .
docker push <app のリポジトリ URL>:$TAG
docker push <web のリポジトリ URL>:$TAG
# 3. SSM に APP_KEY を登録しておく(第8回)
# 4. terraform.tfvars の image_tag に $TAG を書いて、残りをすべて作る
terraform plan
terraform apply
リポジトリの URL は terraform output ecr_repository_urls で確認できます。最後に、app_domain の DNS レコード(CNAME やエイリアス)を terraform output alb_dns_name の ALB に向ければ、https://<ドメイン>/up にアクセスできるようになります。
ただし、この時点ではまだデータベースにテーブルがありません。/up は DB を使わないので 200 を返しますが、ログイン画面はセッションを DB に保存するため表示できません。テーブルを作るマイグレーションは、次回、専用のタスクで実行します(アプリの起動時に自動で流さない理由も次回説明します)。
動作確認の方法
# Laravel のテスト(ローカル、Docker)
docker compose run --rm --no-deps app php artisan test
# Terraform(infra/terraform で実行)
docker run --rm -v $PWD:/w -w /w hashicorp/terraform:1.11.4 fmt -check -recursive
docker run --rm -v $PWD:/w -w /w hashicorp/terraform:1.11.4 init -backend=false
docker run --rm -v $PWD:/w -w /w hashicorp/terraform:1.11.4 validate
筆者の環境で確認したことは次のとおりです。
php artisan testは 66件すべて成功(今回追加した TrustedProxyTest の4件を含む)terraform fmt -check・validateが成功。AWS に接続しない形でのplanはPlan: 58 to add(第8回の42件に、ECR・ライフサイクルポリシー・ロググループ・クラスター・タスク定義・サービス・タスクロール・ALB・ターゲットグループ・リスナーの16件が追加)- 本番イメージを
compose.prod.yamlで起動し、TRUSTED_PROXIESに Docker のネットワーク範囲を指定したうえで、X-Forwarded-Proto: httpsを付けて/dashboardにアクセスすると、リダイレクト先がhttps://になり、Cookie にsecureが付く。ヘッダーなしではhttp://のまま
AWS 上での確認(ALB の DNS 名への curl -I https://<ドメイン>/up が 200、ECS コンソールでターゲットが healthy、CloudWatch Logs の /ecs/admin-stg/web に nginx のアクセスログが出ること)は、AWS アカウントがないため行っていません。ヘルスチェックの猶予時間(60秒)が十分かどうかも、実際の起動時間を見て調整が必要です。
つまずきやすい点・セキュリティ上の注意
/upは DB の状態を見ない:Laravel の/upはアプリが起動していれば 200 を返し、DB に接続できなくても healthy になります。DB の異常は、第12回のアラームで検知します。- 第7回の構成を apply 済みの場合:ALB のセキュリティグループのルールを
for_eachに変えたため、plan では作り直し(削除と作成)になります。作り直しの間、ALB への通信が一瞬止まる点に注意してください。 - イメージの CPU アーキテクチャ:タスク定義は
X86_64にしています。Apple Silicon の Mac などでビルドしたイメージは ARM64 になり、そのままでは起動できません。第11回で CI からビルドするようにして、ビルド環境をそろえます。 - 会員登録画面が開いている:前述のとおり、IP の制限か、会員登録のルートを外す対応が必要です(第12回で本番前の残作業として整理します)。
- ドメインと証明書:証明書の発行・更新の DNS 検証レコードを誰が管理しているか(社内か開発会社か)を確認しておかないと、更新時に止まることがあります。
発注者向けメモ:月額の土台は「常に動く台数」で決まる
Fargate の料金は、タスクに割り当てた CPU とメモリ × 動いている時間で決まります。つまり、画面にアクセスが無い夜間も、desired_count の台数分の費用が発生します。ALB も時間単位の料金がかかる部品です。
| 決めること | 費用・リスクへの影響 | 決めるための問い |
|---|---|---|
| web の台数(最小2台か1台か) | 2台なら AZ 障害やデプロイ中も止まりにくいが、常に2台分の費用 | 数分止まってよい検証環境か、業務時間中は止められない本番か |
| タスクの CPU・メモリ | 大きいほど費用が増える。小さすぎると遅くなる | 同時に使う人数、重い画面(一覧・帳票)があるか |
| 公開範囲(IP 制限) | 社内・VPN に絞ると攻撃を受ける面が小さくなる | 社外から使う人(在宅・取引先)はいるか |
| ログの保持期間 | 長いほど保管費用が増える | 障害や不正の調査で何日前まで見返したいか |
開発会社には、次のように確認してみてください。
- 「検証環境と本番で、タスクの台数と CPU・メモリはどう違いますか?夜間も同じ台数で動かす必要がありますか?」
- 「管理画面に接続できる IP は制限していますか?社外から使う場合はどうしますか?」
- 「ドメインと SSL 証明書は誰のアカウントで管理し、更新はどう行いますか?」
- 「今動いているのがどのバージョンのコードか、どこで確認できますか?前の版に戻すにはどうしますか?」
まとめと次回予告
第9回では、管理画面をインターネットに公開するための部品を定義しました。
- ECR はタグの上書きを禁止し、ライフサイクルポリシーで古いイメージを整理する
- タスク定義は nginx と php-fpm を1つのタスクにまとめ、秘密情報は ARN で参照して起動時に注入する
- ECS が使うタスク実行ロールと、アプリが使うタスクロールを分ける
- サービスで2つの AZ に2台を保ち、ALB は
/upで見張り、HTTP は HTTPS にリダイレクトする - ALB の裏で HTTPS を正しく扱うため、VPC の範囲だけを信頼するプロキシとして設定する
次回は「キューワーカーとスケジューラを ECS で動かす」です。SQS のキューとデッドレターキュー、worker・scheduler のサービス、そして今回見送ったマイグレーションを実行する専用タスクを作ります。
この連載の記事一覧
この記事は連載「Laravel+Vue.jsで作る管理画面をECSで動かす」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第1回】全体像と開発環境:Laravel 12+Vue 3 スターターキットを Docker で動かす
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第2回】顧客マスタの一覧と登録:FormRequest でバリデーションする
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第3回】ロールと Policy:誰が何をできるかをコードで決める
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第4回】案件管理と一覧画面の作り込み:検索・並べ替え・ページング
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第5回】請求と非同期処理:キューとスケジューラをローカルで動かす
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第6回】本番用コンテナイメージを作る:マルチステージ Dockerfile
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第7回】Terraform の土台とネットワーク:state 管理と VPC
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第8回】RDS for MySQL と秘密情報:パスワードをコードに書かない
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第9回】ECR・ECS on Fargate・ALB:管理画面をインターネットに公開する(この記事)
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第10回】キューワーカーとスケジューラを ECS で動かす
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第11回】GitHub Actions で CI/CD:OIDC でキーを持たずにデプロイ
- 【Laravel+Vue.jsで作る管理画面をECSで動かす 第12回】運用編:ログ・監視・スケーリング・切り戻し










コメント
コメント一覧 (1件)
[…] 前回(第9回:ECR・ECS on Fargate・ALB)では、web サービスと ALB を作りました。今回はその隣に worker・scheduler のサービスと migrate のタスク定義を並べます。 […]