v1.0 / 作成日: 2026-06-07 / ALB + ECS Fargate冗長化(プライベートサブネット)+ RDS PostgreSQL Multi-AZ / AWSマネジメントコンソール操作
| 項目 | EC2版 | ECS版(本手順書) |
|---|---|---|
| 実行環境 | EC2インスタンス(OS管理必要) | Fargate(サーバレスコンテナ) |
| アプリ管理 | Nginx + Node.js + pm2 | Dockerコンテナ(Node.js:8080) |
| イメージ管理 | 不要 | ECR(Dockerレジストリ) |
| スケーリング | インスタンス追加 | タスク数変更(数秒〜数分) |
| 接続・デバッグ | Session Manager(EC2接続) | ECS Exec(コンテナ接続) |
| DB初期化 | psqlコマンド手動実行 | アプリ起動時に自動実行 |
| ALBターゲット | インスタンス型 | IP型(Fargate必須) |
| リソース | 名前 | 設定値 |
|---|---|---|
| VPC | handson-vpc | 10.0.0.0/16 |
| パブリックサブネット(ALB-1a) | handson-subnet-public-1a | 10.0.1.0/24 / ap-northeast-1a |
| パブリックサブネット(ALB-1c) | handson-subnet-public-1c | 10.0.2.0/24 / ap-northeast-1c |
| プライベートサブネット(ECS-1a) | handson-subnet-private-ecs-1a | 10.0.3.0/24 / ap-northeast-1a |
| プライベートサブネット(ECS-1c) | handson-subnet-private-ecs-1c | 10.0.4.0/24 / ap-northeast-1c |
| プライベートサブネット(DB-1a) | handson-subnet-private-db-1a | 10.0.5.0/24 / ap-northeast-1a |
| プライベートサブネット(DB-1c) | handson-subnet-private-db-1c | 10.0.6.0/24 / ap-northeast-1c |
| Internet Gateway | handson-igw | VPCにアタッチ |
| NAT Gateway | handson-natgw | public-1a に配置 / ECSタスクのECR取得・CloudWatch用 |
| ECR リポジトリ | handson-ecr | プライベートリポジトリ |
| ECS クラスター | handson-ecs-cluster | Fargate(サーバレス) |
| ECS タスク定義 | handson-task-def | Fargate / CPU:256 / Mem:512MB |
| ECS サービス | handson-ecs-service | タスク数: 2 / プライベートサブネット配置 |
| ALB | handson-alb | インターネット向け / HTTP:80 |
| ターゲットグループ | handson-tg-ecs | IP型 / HTTP:8080 / ECSタスク2つ |
| RDS | handson-rds | PostgreSQL 16 / db.t3.micro / Multi-AZ |
| セキュリティグループ | sg-alb / sg-ecs / sg-rds | 3つ |
| リソース | 概算料金 | 備考 |
|---|---|---|
| ECS Fargate タスク × 2(0.25vCPU / 0.5GB) | 約$0.02/時間 | タスク停止で課金なし |
| ALB × 1台 | 約$0.028/時間 | ハンズオン後すぐ削除 |
| RDS PostgreSQL db.t3.micro Multi-AZ | 約$0.052/時間 | 必ず削除すること |
| NAT Gateway × 1台 | 約$0.062/時間 | ECSのECR取得・CloudWatch送信に必要 |
| ECR(ストレージ) | 約$0.10/GB/月 | 数十MBのため無視できる |
| 項目 | 内容 |
|---|---|
| AWSアカウント | AdministratorAccess権限を持つIAMユーザー |
| リージョン | 東京(ap-northeast-1)― コンソール右上で必ず確認 |
| ブラウザ | Chrome / Edge / Firefox(最新版) |
| 追加ツール | 不要(Dockerイメージ作成はAWS CloudShellで実施) |
コンソール右上の アカウント名 / メールアドレス をクリック → 「アカウント ID」(12桁の数字)をメモしてください。
123456789012ECSがECRからイメージを取得し、CloudWatch Logsにログを送信するために必要なロールを作成します。
| 項目 | 値 |
|---|---|
| エンティティタイプ | AWSのサービス |
| ユースケース | Elastic Container Service(「ECS」で検索) |
| ユースケース(詳細) | Elastic Container Service Task |
検索欄に AmazonECSTaskExecutionRolePolicy と入力 → チェックを入れる
| 項目 | 値 |
|---|---|
| ロール名 | handson-ecs-task-execution-role |
| 項目 | 値 |
|---|---|
| 作成するリソース | VPCのみ |
| 名前タグ | handson-vpc |
| IPv4 CIDR | 10.0.0.0/16 |
| テナンシー | デフォルト |
handson-vpc を選択 → アクション → 「VPCの設定を編集」
| 項目 | 値 |
|---|---|
| DNS ホスト名を有効化 | チェックを入れる(有効化) |
| サブネット名 | AZ | CIDR | 用途 |
|---|---|---|---|
| handson-subnet-public-1a | ap-northeast-1a | 10.0.1.0/24 | ALB-1a |
| handson-subnet-public-1c | ap-northeast-1c | 10.0.2.0/24 | ALB-1c |
| handson-subnet-private-ecs-1a | ap-northeast-1a | 10.0.3.0/24 | ECS-1a |
| handson-subnet-private-ecs-1c | ap-northeast-1c | 10.0.4.0/24 | ECS-1c |
| handson-subnet-private-db-1a | ap-northeast-1a | 10.0.5.0/24 | DB-1a |
| handson-subnet-private-db-1c | ap-northeast-1c | 10.0.6.0/24 | DB-1c |
「新しいサブネットを追加」ボタンで6つをまとめて作成し、サブネットを作成
handson-subnet-public-1a を選択 → アクション → 「サブネットの設定を編集」
| 項目 | 値 |
|---|---|
| パブリック IPv4 アドレスの自動割り当てを有効化 | チェックを入れる |
handson-subnet-public-1c についても同様に設定します。
| 項目 | 値 |
|---|---|
| 名前タグ | handson-igw |
作成後、画面上部のバナーの「VPCにアタッチ」をクリック(または アクション → 「VPCにアタッチ」)
| 項目 | 値 |
|---|---|
| 使用可能な VPC | handson-vpc(プルダウンから選択) |
状態が 「Attached」 になっていることを確認します。
| 項目 | 値 |
|---|---|
| 名前 | handson-natgw |
| サブネット | handson-subnet-public-1a(パブリックサブネットに配置) |
| 接続タイプ | パブリック |
| Elastic IP 割り当て ID | Elastic IP を割り当て をクリックして新規EIPを取得 |
| 項目 | 値 |
|---|---|
| 名前 | handson-rt-public |
| VPC | handson-vpc |
handson-rt-public を選択 → 「ルート」タブ → ルートを編集 → ルートを追加
| 送信先 | ターゲット |
|---|---|
| 0.0.0.0/0 | Internet Gateway → handson-igw |
「サブネットの関連付け」タブ → サブネットの関連付けを編集
以下 2つにチェック → 関連付けを保存
handson-subnet-public-1ahandson-subnet-public-1c新しいルートテーブルを作成します:
| 項目 | 値 |
|---|---|
| 名前 | handson-rt-private-ecs |
| VPC | handson-vpc |
handson-rt-private-ecs を選択 → 「ルート」タブ → ルートを編集 → ルートを追加
| 送信先 | ターゲット |
|---|---|
| 0.0.0.0/0 | NAT Gateway → handson-natgw |
「サブネットの関連付け」タブ → サブネットの関連付けを編集
以下 2つにチェック → 関連付けを保存
handson-subnet-private-ecs-1ahandson-subnet-private-ecs-1c| 項目 | 値 |
|---|---|
| セキュリティグループ名 | sg-alb |
| 説明 | ALB security group |
| VPC | handson-vpc |
インバウンドルール:
| タイプ | プロトコル | ポート | ソース | 説明 |
|---|---|---|---|---|
| HTTP | TCP | 80 | 0.0.0.0/0 | インターネットからのHTTP |
| HTTPS | TCP | 443 | 0.0.0.0/0 | インターネットからのHTTPS |
アウトバウンドルール:デフォルト(すべてのトラフィックを許可)のまま
セキュリティグループを作成| 項目 | 値 |
|---|---|
| セキュリティグループ名 | sg-ecs |
| 説明 | ECS Fargate task security group |
| VPC | handson-vpc |
インバウンドルール:
| タイプ | プロトコル | ポート | ソース | 説明 |
|---|---|---|---|---|
| カスタムTCP | TCP | 8080 | sg-alb のSG ID | ALBからのみ許可(Node.jsポート) |
| 項目 | 値 |
|---|---|
| セキュリティグループ名 | sg-rds |
| 説明 | RDS PostgreSQL security group |
| VPC | handson-vpc |
インバウンドルール:
| タイプ | プロトコル | ポート | ソース | 説明 |
|---|---|---|---|---|
| PostgreSQL | TCP | 5432 | sg-ecs のSG ID | ECSタスクからのみ許可 |
DockerイメージをAWS上に保存するためのプライベートリポジトリを作成します。
| 項目 | 値 |
|---|---|
| 可視性設定 | プライベート |
| リポジトリ名 | handson-ecr |
| タグのイミュータビリティ | 無効(デフォルト) |
| 暗号化設定 | AES-256(デフォルト) |
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/handson-ecrAWSコンソール上のCloudShellを使って、Node.jsアプリのDockerイメージをビルドしECRにプッシュします。
mkdir ~/handson-app && cd ~/handson-app
package.json の作成:
cat > package.json <<'EOF'
{
"name": "handson-app",
"version": "1.0.0",
"main": "app.js",
"dependencies": {
"express": "^4.18.0",
"pg": "^8.11.0"
}
}
EOF
cat > app.js <<'EOF'
const express = require('express');
const { Pool } = require('pg');
const os = require('os');
const app = express();
const pool = new Pool({
host: process.env.DB_HOST,
port: 5432,
user: process.env.DB_USER || 'admin',
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || 'handson_db',
ssl: { rejectUnauthorized: false },
max: 10
});
async function initDb() {
await pool.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(200) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
const { rowCount } = await pool.query('SELECT 1 FROM users LIMIT 1');
if (rowCount === 0) {
await pool.query(`
INSERT INTO users (name, email) VALUES
('田中 太郎', 'taro@example.com'),
('鈴木 花子', 'hanako@example.com'),
('佐藤 一郎', 'ichiro@example.com')
`);
console.log('Sample data inserted');
}
console.log('DB initialized');
}
initDb().catch(err => console.error('DB init error:', err.message));
app.get('/health', (req, res) => {
res.json({ status: 'ok', container: os.hostname() });
});
app.get('/', (req, res) => {
res.json({
message: 'ECS Fargate app is running!',
container: os.hostname(),
tier: 'ecs-fargate'
});
});
app.get('/users', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM users ORDER BY id');
res.json({ count: result.rowCount, users: result.rows, container: os.hostname() });
} catch (err) {
res.status(500).json({ error: err.message, container: os.hostname() });
}
});
app.listen(8080, () => {
console.log('App listening on port 8080');
});
EOF
process.env.DB_HOST など)でDB接続情報を受け取ります。ハードコードせずに、ECSタスク定義で後から設定します。また、テーブル作成・サンプルデータ投入をアプリ起動時に自動実行します(initDb())。cat > Dockerfile <<'EOF' FROM node:20-alpine WORKDIR /app COPY package.json . RUN npm install --production COPY app.js . EXPOSE 8080 CMD ["node", "app.js"] EOF
【AWSアカウントID】 を手順2でメモした12桁のIDに置き換えて実行します:
aws ecr get-login-password --region ap-northeast-1 | \ docker login --username AWS --password-stdin \ 【AWSアカウントID】.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded と表示されれば成功です。
【AWSアカウントID】 を置き換えて実行します:
# イメージビルド docker build -t handson-app . # ECR用にタグ付け docker tag handson-app:latest \ 【AWSアカウントID】.dkr.ecr.ap-northeast-1.amazonaws.com/handson-ecr:latest # ECRにプッシュ docker push \ 【AWSアカウントID】.dkr.ecr.ap-northeast-1.amazonaws.com/handson-ecr:latest
latest: digest: sha256:... と表示されれば成功です。
handson-ecr リポジトリ → 「イメージ」タブに latest タグのイメージが表示されることを確認します。RDS Multi-AZ配置に必要な、2AZにまたがるサブネットグループを作成します。
| 項目 | 値 |
|---|---|
| 名前 | handson-db-subnet-group |
| 説明 | DB subnet group for Multi-AZ RDS |
| VPC | handson-vpc |
| アベイラビリティーゾーン | ap-northeast-1a と ap-northeast-1c の両方にチェック |
| サブネット | handson-subnet-private-db-1a と handson-subnet-private-db-1c を選択 |
| 項目 | 値 |
|---|---|
| データベース作成方法 | 標準作成 |
| エンジンタイプ | PostgreSQL |
| エンジンバージョン | PostgreSQL 16.x(リストの最新版) |
| 項目 | 値 |
|---|---|
| テンプレート | 開発/テスト |
| デプロイオプション | マルチ AZ DB インスタンス |
| 項目 | 値 |
|---|---|
| DB インスタンス識別子 | handson-rds |
| マスターユーザー名 | admin |
| 認証情報の管理 | セルフマネージド |
| マスターパスワード | AdminPass123! |
| パスワードの確認 | AdminPass123! |
| 項目 | 値 |
|---|---|
| DB インスタンスクラス | バースト可能クラス(t クラスを含む) → db.t3.micro |
| 項目 | 値 |
|---|---|
| ストレージタイプ | 汎用 SSD (gp2) |
| 割り当てられたストレージ | 20 GiB |
| ストレージの自動スケーリング | 無効(チェックを外す) |
| 項目 | 値 |
|---|---|
| コンピューティングリソース | EC2 コンピューティングリソースに接続しない |
| VPC | handson-vpc |
| DB サブネットグループ | handson-db-subnet-group |
| パブリックアクセス | なし |
| VPC セキュリティグループ | 既存の選択 → sg-rds(デフォルトのSGは削除) |
| アベイラビリティーゾーン | ap-northeast-1a(プライマリ) |
「追加設定」セクションをクリックして展開します。
| 項目 | 値 |
|---|---|
| 最初のデータベース名 | handson_db 空のままにするとデータベースが作成されません。必ず入力してください。 |
| 自動バックアップを有効化 | チェックを外す |
| 削除保護の有効化 | チェックを外す |
handson-rds.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com(ポート: 5432)ECSタスク(コンテナ)を実行するクラスターを作成します。Fargateを使用するためEC2インスタンスの管理は不要です。
| 項目 | 値 |
|---|---|
| クラスター名 | handson-ecs-cluster |
| インフラストラクチャ | AWS Fargate(サーバーレス)にチェックが入っていることを確認 「Amazon EC2インスタンス」はチェック不要 |
コンテナの実行設定(使用するイメージ・CPU/メモリ・ポート・環境変数など)を定義します。
| 項目 | 値 |
|---|---|
| タスク定義ファミリー | handson-task-def |
| 起動タイプ | AWS Fargate |
| オペレーティングシステム/アーキテクチャ | Linux/X86_64 |
| タスクサイズ - CPU | .25 vCPU |
| タスクサイズ - メモリ | 0.5 GB |
| タスクロール | なし(アプリからAWSサービスを直接呼ばないため) |
| タスク実行ロール | handson-ecs-task-execution-role |
「コンテナ - 1」セクションを入力します:
| 項目 | 値 |
|---|---|
| 名前 | handson-app |
| イメージ URI | 【AWSアカウントID】.dkr.ecr.ap-northeast-1.amazonaws.com/handson-ecr:latest 手順6でメモしたECRリポジトリのURIに「:latest」を付けたもの |
| コンテナポート | 8080 |
| プロトコル | TCP |
| ポート名 | handson-app-8080(任意) |
「環境変数」セクションで「環境変数を追加」をクリックし、以下を入力します:
| キー | 値タイプ | 値 |
|---|---|---|
DB_HOST | 値 | 【RDSエンドポイント】 手順9でメモしたRDSエンドポイントを入力 |
DB_USER | 値 | admin |
DB_PASSWORD | 値 | AdminPass123! |
DB_NAME | 値 | handson_db |
「ログ収集の設定」セクション:
| 項目 | 値 |
|---|---|
| ログドライバー | awslogs(デフォルトのまま) |
| awslogs-group | /ecs/handson-task-def(自動入力) |
| awslogs-region | ap-northeast-1 |
| awslogs-stream-prefix | ecs |
/ecs/handson-task-def| 項目 | 値 |
|---|---|
| ターゲットタイプ | IP アドレス(Fargate必須) |
| ターゲットグループ名 | handson-tg-ecs |
| プロトコル | HTTP |
| ポート | 8080(Node.jsが8080でリッスン) |
| VPC | handson-vpc |
| プロトコルバージョン | HTTP1 |
ヘルスチェック設定:
| 項目 | 値 |
|---|---|
| ヘルスチェックプロトコル | HTTP |
| ヘルスチェックパス | /health |
| 正常のしきい値 | 2 |
| 非正常のしきい値 | 3 |
| 間隔 | 30 秒 |
| 項目 | 値 |
|---|---|
| ロードバランサー名 | handson-alb |
| スキーム | インターネット向け |
| IP アドレスタイプ | IPv4 |
| 項目 | 値 |
|---|---|
| VPC | handson-vpc |
| マッピング(AZ 1) | ap-northeast-1a → handson-subnet-public-1a |
| マッピング(AZ 2) | ap-northeast-1c → handson-subnet-public-1c |
| 項目 | 値 |
|---|---|
| セキュリティグループ | sg-alb(デフォルトのSGは削除して sg-alb のみ選択) |
| 項目 | 値 |
|---|---|
| プロトコル | HTTP |
| ポート | 80 |
| デフォルトアクション | handson-tg-ecs に転送 |
handson-alb-xxxxxxxxx.ap-northeast-1.elb.amazonaws.comECSサービスは「タスクを常に指定数実行し続け、ALBと連携する」仕組みです。タスクが停止すると自動的に新しいタスクを起動します。
| 項目 | 値 |
|---|---|
| コンピューティングオプション | 起動タイプ |
| 起動タイプ | FARGATE |
| プラットフォームバージョン | LATEST |
| 項目 | 値 |
|---|---|
| アプリケーションタイプ | サービス |
| ファミリー | handson-task-def |
| リビジョン | 最新(LATEST) |
| サービス名 | handson-ecs-service |
| 必要なタスク | 2(2AZに1つずつ配置) |
| 項目 | 値 |
|---|---|
| VPC | handson-vpc |
| サブネット | handson-subnet-private-ecs-1a と handson-subnet-private-ecs-1c を選択 |
| セキュリティグループ | 既存のセキュリティグループを使用 → sg-ecs |
| パブリック IP | オフ(無効) プライベートサブネットのため、パブリックIPは不要 |
| 項目 | 値 |
|---|---|
| ロードバランサーのタイプ | Application Load Balancer |
| ロードバランサー | 既存のロードバランサーを使用 → handson-alb |
| コンテナ | handson-app 8080:8080 |
| リスナー | 既存のリスナーを使用 → 80:HTTP |
| ターゲットグループ | 既存のターゲットグループを使用 → handson-tg-ecs |
2つのタスクのステータスが 「RUNNING」 になっていることを確認します。
2つのFargateタスクのIPアドレスが 「healthy」 になっていることを確認します。
手順13でメモした ALBのDNS名をブラウザのアドレスバーに入力してアクセスします:
| URL | 期待するレスポンス |
|---|---|
http://【ALBのDNS名】/ | {"message":"ECS Fargate app is running!","container":"..."} |
http://【ALBのDNS名】/health | {"status":"ok","container":"..."} |
http://【ALBのDNS名】/users | RDSのusersテーブルデータ(3件) |
ブラウザで /health や /users に複数回アクセスすると、レスポンスの "container" フィールド(コンテナのホスト名)が2つの異なる値で切り替わることを確認します。
"server" フィールドはEC2のホスト名でしたが、ECS版の "container" フィールドはFargateコンテナのID(タスクARNの一部)になります。ECSコンソールでタスクを1つ「停止」すると、ECSサービスが自動的に新しいタスクを起動します。
タスクが停止されても:
Node.jsアプリのログ(DB initialized、App listening on port 8080 など)が確認できます。
| 項目 | 値 |
|---|---|
| 必要なタスク数 | 0(タスクを全停止) |
| デプロイの強制 | チェックを入れる |
全タスクが停止したことを確認後 → アクション → 「サービスを削除」
「最終スナップショットを作成しますか?」→ いいえ を選択
確認テキスト(delete me)を入力して 削除
確認テキスト(delete)を入力して削除します(イメージも同時に削除されます)。
削除には数分かかります。ステータスが「削除済み」になるまで待ちます。
デタッチ完了後 → 再度 アクション → 「インターネットゲートウェイを削除」
sg-rds → sg-ecs → sg-alb の順に削除します(依存関係があるため逆順)。
サブネット 6つ → ルートテーブル(handson-rt-private-ecs → handson-rt-public)→ VPC(handson-vpc)の順に削除します。