この記事について
| 記事 | タイトル | 状態 |
|---|---|---|
| 第0回 | 全体ガイド | ✅ 完了 |
| 第1回 | 構成図編 | ✅ 完了 |
| 第2回 | VPC & Subnet | ✅ 完了 |
| 第3回 | NAT Gateway & Route Table | ✅ 完了 |
| 第4回 | Security Group | ✅ 完了 |
| 第5回 | ECR & Docker Image | ✅ 完了 |
| 第6回 | Public ALB | ✅ 完了 |
| 第7回 | ECS Front & IAM | ✅ 完了 |
| 第8回 | ECS API & Internal ALB | ✅ 完了 |
| 第9回 | RDS MySQL | ✅ 完了 |
| 第10回 | Secrets Manager & 完成 | 📍今回 |
進捗: 100% (11/11記事) | Phase: 5-2 | 🎉マイルストーン5達成: 全システム完全動作!
📁 完全なコードはGitHubで公開:GitHub: fargate-iac-02
1. はじめに
この記事は、Phase 5の後半として、Secrets ManagerでDBパスワードを管理し、全システムを完成させます。
📝 この記事の構成:
- Terraformコードの全文を掲載し、1ファイルずつ詳しく解説します
- 各コードブロックには初心者向けのコメントを充実させています
- 完全なコードはGitHubでも公開しています(記事末尾のリンク参照)
1-1. 前回までの振り返り
Phase 5-1(第9回)で作成したもの:
- ✅ RDS MySQL(Multi-AZ)
- ✅ DB Subnet Group
- ✅ 初期テーブルとデータ
今回(第10回: Phase 5-2)で作成するもの:
- Secrets Manager(DBパスワード)
- Secret Version
- Phase 4の環境変数更新(Secrets Manager参照)
- IAM Policy更新(Secrets Manager読み取り権限)
1-2. この記事で学べること
- Secrets Managerの基本と役割
- 環境変数でのSecrets参照方法
- IAM Policyの更新
- 全システムの統合
1-3. 対象読者
- Phase 5-1(第9回)を完了した方
- RDS MySQLを作成済みの方
1-4. 想定環境
- Terraform: v1.9.x
- Phase 1-5-1: 実行済み
2. Phase 5-2で作成するもの
2-1. リソース一覧
| リソース | 数量 | 用途 | 日額費用(24h) | 月額費用 |
|---|---|---|---|---|
| Secrets Manager | 1 | DB接続情報保存 | $0.013 | 約$0.40 |
| Secret Version | 1 | 実際の値保存 | 無料 | 無料 |
合計: 2リソース
コスト: 約$0.013/日(約$0.40/月)
💰 Secrets Managerコスト詳細:
- シークレット保存: $0.40/月 ÷ 30日 = $0.013/日
- API呼び出し: $0.05/10,000回(従量課金)
Phase 5完成時点の累計コスト:
- Phase 1-4: 約$5.74/日
- Phase 5: 約$1.18/日
- 合計: 約$6.92/日(約$208/月)
2-2. 構成図での位置づけ
API (ECS Fargate)
↓ IAM Task Role
Secrets Manager 👈 今回作成
├─ DB_PASSWORD
├─ DB_USERNAME
└─ DB_SERVERNAME
↓ 環境変数として注入
API Container
↓ MySQL接続
RDS MySQL
3. Secrets Managerの役割
3-1. Secrets Managerとは
AWS Secrets Managerは、機密情報を安全に保存・管理するサービスです。
特徴:
- パスワードを暗号化して保存
- 自動ローテーション機能
- IAMで細かくアクセス制御
- 監査ログ(CloudTrail)
料理で例えると:
- 環境変数にパスワード = レシピにパスワードを書く(誰でも見える)
- Secrets Manager = 金庫にパスワードを保管(許可された人だけ見える)
3-2. なぜSecrets Managerが必要?
環境変数に直接書く問題点:
❌ 悪い例: Task Definitionに直接書く
environment = [
{
name = "DB_PASSWORD"
value = "MySecurePass123!" # 👈 平文で保存される
}
]
問題点:
- terraform.tfstateに平文で保存される
- AWSコンソールで誰でも見える
- ログに出力される可能性
Secrets Managerを使う:
✅ 良い例: Secrets Managerから取得
secrets = [
{
name = "DB_PASSWORD"
valueFrom = "arn:aws:secretsmanager:...:secret:db-password" # 👈 参照のみ
}
]
メリット:
- パスワードは暗号化して保存
- IAMで読み取り制限
- ローテーション可能
3-3. Secrets vs Environment
ECS Task Definitionでの設定方法:
| 方式 | 用途 | セキュリティ |
|---|---|---|
| environment | 一般的な設定値 | 低(平文) |
| secrets | 機密情報 | 高(暗号化) |
今回の使い分け:
- environment: なし
- secrets: DB_PASSWORD、DB_USERNAME、DB_SERVERNAME
4. ディレクトリ構成
4-1. Phase 5の完全なファイル構成
terraform/phase5-rds/
├── main.tf ← 第9回で作成済み
├── variables.tf ← 第9回で作成済み
├── terraform.tfvars ← 第9回で作成済み
├── data.tf ← 第9回で作成済み
├── rds.tf ← 第9回で作成済み
├── secrets.tf 👉 今回追加
└── outputs.tf ← 更新
4-2. 追加ファイルの詳細
| ファイル名 | 役割 | 行数 |
|---|---|---|
| secrets.tf | Secrets Manager、Secret Version | 40 |
5. 追加ファイルの作成
5-1. ディレクトリ移動
Phase 5のディレクトリに移動します:
cd terraform/phase5-rds
5-2. 追加ファイルの作成
Secrets Manager用のファイルを作成します:
touch secrets.tf
作成されたファイル:
-
secrets.tf- Secrets Manager、Secret Version
確認:
ls -la
# main.tf, variables.tf, terraform.tfvars, data.tf, rds.tf, outputs.tf (第9回)
# secrets.tf (今回追加)
6. ファイル別コード解説
それでは、追加したファイルのコードを解説していきます。
6-1. secrets.tf - Secrets Manager
ファイルの役割:
- Secrets Managerを作成
- DB接続情報を保存
コード:
# ファイルパス: terraform/phase5-rds/secrets.tf
# ==========================================
# Secrets Manager Secret
# ==========================================
resource "aws_secretsmanager_secret" "db" {
name = "${var.project_name}-db-credentials"
description = "Database credentials for ${var.project_name}"
tags = {
Name = "${var.project_name}-db-credentials"
}
}
# ==========================================
# Secret Version
# ==========================================
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
DB_SERVERNAME = aws_db_instance.main.address
DB_USERNAME = var.db_username
DB_PASSWORD = var.db_password
})
}
ポイント:
- secret_string: JSON形式で複数の値を保存
- DB_SERVERNAME: RDSのエンドポイント(自動取得)
- DB_USERNAME, DB_PASSWORD: variables.tfから取得
保存されるJSON:
{
"DB_SERVERNAME": "myproject-mysql.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com",
"DB_USERNAME": "admin",
"DB_PASSWORD": "MySecurePass123!"
}
6-2. outputs.tf - 出力の更新
既存のoutputs.tfに、Secrets Manager ARNを追加:
# ファイルパス: terraform/phase5-rds/outputs.tf
# ... 既存のRDS出力 ...
output "secrets_manager_arn" {
description = "Secrets Manager ARN"
value = aws_secretsmanager_secret.db.arn
}
7. Phase 4の更新(ECS API)
7-1. Phase 4のiam.tfを更新
Task RoleにSecrets Manager読み取り権限を追加:
# ファイルパス: terraform/phase4-ecs-api/iam.tf
# ... 既存のTask Role定義 ...
# カスタムポリシー(Secrets Manager読み取り権限を追加)
resource "aws_iam_role_policy" "ecs_task" {
name = "${var.project_name}-ecs-task-policy"
role = aws_iam_role.ecs_task.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = [
data.terraform_remote_state.rds.outputs.secrets_manager_arn
]
}
]
})
}
ポイント:
- GetSecretValue: Secretの値を取得
- Resource: Phase 5で作成したSecrets Manager ARN
7-2. Phase 4のdata.tfを更新
Phase 5の参照を追加:
# ファイルパス: terraform/phase4-ecs-api/data.tf
# ... 既存のdata, locals ...
# Phase 5の出力を参照(追加)
data "terraform_remote_state" "rds" {
backend = "local"
config = {
path = "../phase5-rds/terraform.tfstate"
}
}
# Phase 5から取得する値をローカル変数化(追加)
locals {
secrets_manager_arn = data.terraform_remote_state.rds.outputs.secrets_manager_arn
}
7-3. Phase 4のecs_task_def.tfを更新
ファイルパス: terraform/phase4-ecs-api/ecs_task_def.tf
コード骨格:
🔄 変更点: environment → secrets
| 変更前 | 変更後 |
|---|---|
environment ブロック |
secrets ブロック |
value = "直接値" |
valueFrom = "${SECRET_ARN}:KEY::" |
secrets ブロックの設定:
| name | valueFrom | 説明 |
|---|---|---|
DB_SERVERNAME |
${local.secrets_manager_arn}:DB_SERVERNAME:: |
RDSエンドポイント |
DB_USERNAME |
${local.secrets_manager_arn}:DB_USERNAME:: |
DBユーザー名 |
DB_PASSWORD |
${local.secrets_manager_arn}:DB_PASSWORD:: |
DBパスワード |
valueFrom の形式:
arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXX:secret:myproject-db-credentials-xxxxx:DB_PASSWORD::
^^^^^^^^^^^^
JSONのキー
簡易コード(変更部分のみ):
# terraform/phase4-ecs-api/ecs_task_def.tf
container_definitions = jsonencode([
{
name = "api"
image = "${local.ecr_api_url}:latest"
essential = true
# 🔄 変更前: environment(平文)
# environment = [
# { name = "DB_USERNAME", value = "admin" }
# ]
# 🔄 変更後: secrets(Secrets Manager参照)
secrets = [
{
name = "DB_SERVERNAME"
valueFrom = "${local.secrets_manager_arn}:DB_SERVERNAME::"
},
{
name = "DB_USERNAME"
valueFrom = "${local.secrets_manager_arn}:DB_USERNAME::"
},
{
name = "DB_PASSWORD"
valueFrom = "${local.secrets_manager_arn}:DB_PASSWORD::"
}
]
portMappings = [{ containerPort = 8080, protocol = "tcp" }]
# ... logConfiguration, healthCheck 省略
}
])
フルコード:
# terraform/phase4-ecs-api/ecs_task_def.tf
resource "aws_ecs_task_definition" "api" {
family = "${var.project_name}-api"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256" # 0.25 vCPU
memory = "512" # 0.5 GB
execution_role_arn = local.ecs_task_execution_role_arn
task_role_arn = local.ecs_task_role_arn
container_definitions = jsonencode([
{
name = "api"
image = "${local.ecr_api_url}:latest"
essential = true
# 🔄 secrets: Secrets Managerから環境変数を取得
secrets = [
{
name = "DB_SERVERNAME"
valueFrom = "${local.secrets_manager_arn}:DB_SERVERNAME::"
},
{
name = "DB_USERNAME"
valueFrom = "${local.secrets_manager_arn}:DB_USERNAME::"
},
{
name = "DB_PASSWORD"
valueFrom = "${local.secrets_manager_arn}:DB_PASSWORD::"
}
]
portMappings = [
{
containerPort = 8080
protocol = "tcp"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.api.name
"awslogs-region" = var.region
"awslogs-stream-prefix" = "ecs"
}
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8080/ || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}
])
tags = {
Name = "${var.project_name}-api-task"
}
}
ポイント解説:
| 項目 | 値 | 説明 |
|---|---|---|
family |
${var.project_name}-api |
タスク定義のファミリー名 |
network_mode |
awsvpc |
Fargate必須。ENI割り当て |
cpu / memory
|
256 / 512
|
0.25 vCPU / 0.5 GB |
execution_role_arn |
Phase 3から参照 | ECR pull、CloudWatch Logs書き込み用 |
task_role_arn |
Phase 3から参照 | Secrets Manager読み取り用 |
secrets |
3項目 |
environment の代わり。Secrets Managerから取得 |
valueFrom |
ARN:KEY:: |
末尾の :: は必須(バージョン省略を意味) |
containerPort |
8080 |
APIがLISTENするポート |
healthCheck |
curl で /
|
コンテナの死活監視 |
📁 完全なコードは GitHub: fargate-iac-02 を参照
8. 実行手順
8-1. Phase 5にSecrets Managerを追加
cd terraform/phase5-rds
# secrets.tfを作成
# outputs.tfを更新
terraform apply:
terraform apply
実行結果:
Plan: 2 to add, 0 to change, 0 to destroy.
aws_secretsmanager_secret.db: Creating...
aws_secretsmanager_secret.db: Creation complete
aws_secretsmanager_secret_version.db: Creating...
aws_secretsmanager_secret_version.db: Creation complete
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
secrets_manager_arn = "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXX:secret:myproject-db-credentials-xxxxx"
8-2. Phase 4を更新
cd ../phase4-ecs-api
# iam.tfを更新(Secrets Manager読み取り権限)
# data.tfを更新(Phase 5参照)
# ecs_task_def.tfを更新(secrets設定)
terraform plan:
terraform plan
実行結果:
Plan: 1 to add, 1 to change, 1 to destroy.
# aws_iam_role_policy.ecs_task will be updated in-place
# aws_ecs_task_definition.api must be replaced
# aws_ecs_service.api will be updated in-place
terraform apply:
terraform apply
確認メッセージ:
Enter a value: yes
実行結果:
aws_iam_role_policy.ecs_task: Modifying...
aws_iam_role_policy.ecs_task: Modifications complete
aws_ecs_task_definition.api: Creating...
aws_ecs_task_definition.api: Creation complete
aws_ecs_service.api: Modifying...
aws_ecs_service.api: Still modifying... [1m0s elapsed]
aws_ecs_service.api: Still modifying... [2m0s elapsed]
aws_ecs_service.api: Modifications complete after 2m30s
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
所要時間: 約3-4分(ECS Serviceの更新)
9. 動作確認
9-1. AWSコンソールで確認
Secrets Manager:
- AWSコンソール → Secrets Manager → Secrets
- 「myproject-db-credentials」が作成されていることを確認
- 「Retrieve secret value」をクリック
- JSON形式でDB接続情報が保存されていることを確認
ECS Task Definition:
- ECS → Task Definitions → myproject-api
- 最新のRevisionをクリック
- Container definitions → api → Environment
- Secrets セクションに3つの設定があることを確認
ECS Service:
- ECS → Clusters → myproject-cluster → Services
- api-service → Tasks タブ
- 新しいTaskがRunning状態
9-2. ブラウザでテスト
Public ALBのDNS名でアクセス:
cd ../phase3-ecs-front
terraform output public_alb_dns
ブラウザでアクセス:
http://myproject-public-alb-xxx.ap-northeast-1.elb.amazonaws.com/
テスト1: API Test
「API Test」ボタンをクリック:
結果:
API Response: API接続テストが成功しました
✅ 成功!
テスト2: Database Test 🎉
「Database Test」ボタンをクリック:
結果:
DB Response: データベース接続テストが成功しました(test_tableの件数:4)
✅ 成功!
10. トラブルシューティング
10-1. エラー1: Database Testが失敗する
現象:
- 「Database Test」ボタンをクリックするとエラー
確認ポイント:
確認1: Secrets Managerから値が取得できているか
CloudWatch Logsでエラー確認:
- CloudWatch → Log groups → /ecs/myproject/api
- 最新のLog streamを開く
- エラーメッセージを確認
よくあるエラー:
Error: dial tcp: lookup xxx.rds.amazonaws.com: no such host
原因: DB_SERVERNAMEが間違っている
解決策:
cd ../../terraform/phase5-rds
terraform output db_endpoint_address
# 正しいエンドポイントを確認
確認2: IAM Roleの権限
Task RoleにSecrets Manager読み取り権限があるか:
cd ../phase4-ecs-api
terraform state show aws_iam_role_policy.ecs_task
# secretsmanager:GetSecretValue があることを確認
確認3: RDSに初期データが投入されているか
ECS Task経由でRDSに接続:
# 第9回の「7-2. 接続テスト」を参照
# test_tableにデータがあるか確認
SELECT COUNT(*) FROM test_table;
期待される結果:
+----------+
| COUNT(*) |
+----------+
| 4 |
+----------+
10-2. エラー2: Secrets Managerの値が更新されない
現象:
- Secrets Managerの値を変更したが、ECS Taskに反映されない
原因:
- ECS Taskは起動時にSecretsを取得するため、再起動が必要
解決策:
# ECS Serviceを強制更新
aws ecs update-service \
--cluster myproject-cluster \
--service myproject-api-service \
--force-new-deployment \
--region ap-northeast-1
11. 全システムのまとめ
11-1. 作成したリソース(全Phase)
| Phase | リソース数 | 主要リソース | 日額費用(24h) | 月額費用 |
|---|---|---|---|---|
| Phase 1 | 16 | VPC、NAT Gateway × 2 | $3.00 | $90 |
| Phase 2 | 7 | Security Group × 5、ECR × 2 | 無料 | 無料 |
| Phase 3 | 11 | Public ALB、ECS Front × 2 | $0.77 | $23 |
| Phase 4 | 8 | Internal ALB、ECS API × 2 | $1.37 | $43 |
| Phase 5 | 8 | RDS Multi-AZ、Secrets Manager | $1.18 | $35 |
| 合計 | 50 | - | 約$6.32/日 | 約$191/月 |
11-2. コスト詳細(日額・月額)
| カテゴリ | リソース | 日額費用(24h) | 月額費用 | 割合 |
|---|---|---|---|---|
| ネットワーク | NAT Gateway × 2 | $3.00 | $90 | 42% |
| 負荷分散 | ALB × 2 | $1.34 | $40 | 19% |
| コンピューティング | ECS Fargate(4 Task) | $1.36 | $41 | 19% |
| データベース | RDS MySQL Multi-AZ | $1.17 | $35 | 16% |
| その他 | Logs、Secrets等 | $0.20 | $6 | 3% |
| 合計 | - | 約$7.07/日 | 約$212/月 | 100% |
💰 日額コスト内訳(詳細):
| リソース | 時間単価 | 計算式 | 日額 |
|---|---|---|---|
| NAT Gateway × 2 | $0.062/h × 2 | $0.124 × 24h = $2.976 | $3.00 |
| Public ALB | $0.028/h | $0.028 × 24h = $0.672 | $0.67 |
| Internal ALB | $0.028/h | $0.028 × 24h = $0.672 | $0.67 |
| ECS Front × 2 | $0.01420/h × 2 | $0.02840 × 24h = $0.6816 | $0.68 |
| ECS API × 2 | $0.01420/h × 2 | $0.02840 × 24h = $0.6816 | $0.68 |
| RDS Multi-AZ | $0.048/h | $0.048 × 24h = $1.152 | $1.17 |
| Secrets Manager | - | $0.40/月 ÷ 30 = $0.0133 | $0.013 |
| CloudWatch Logs | - | 約$0.05/日 | $0.05 |
| 合計 | - | - | $6.95/日 |
月額換算: $6.95 × 30日 = $208.50/月
11-3. システム構成図(完成版)
インターネット
↓ HTTPS:443 / HTTP:80
Public ALB (internet-facing, Multi-AZ)
↓ HTTP:80
Frontend × 2 (ECS Fargate, Private Subnet)
├─ Nginx
└─ CloudWatch Logs
↓ HTTP:80
Internal ALB (internal, Multi-AZ)
↓ HTTP:8080
API × 2 (ECS Fargate, Private Subnet)
├─ Go Application
├─ CloudWatch Logs
└─ IAM Task Role
↓ Secrets Manager読み取り
Secrets Manager
├─ DB_SERVERNAME
├─ DB_USERNAME
└─ DB_PASSWORD
↓ MySQL:3306
RDS MySQL (Multi-AZ)
├─ Primary DB (AZ-1a)
└─ Standby DB (AZ-1c)
第1回記事から再掲:
11-4. 達成したマイルストーン
🎯 マイルストーン1: Phase 1完了
✅ ネットワーク基盤(VPC、Subnet、NAT Gateway)
🎯 マイルストーン2: Phase 2完了
✅ セキュリティ基盤(Security Group × 5、ECR × 2)
🎯 マイルストーン3: Phase 3完了
✅ Frontend動作(ブラウザで画面表示成功!)
🎯 マイルストーン4: Phase 4完了
✅ API動作(「API Test」ボタン成功!)
🎯 マイルストーン5: Phase 5完了
✅ 全システム完成(「Database Test」ボタン成功!)
12. リソースのクリーンアップ
12-1. 全リソースの削除
逆順で削除:
# Phase 5
cd terraform/phase5-rds
terraform destroy
# Phase 4
cd ../phase4-ecs-api
terraform destroy
# Phase 3
cd ../phase3-ecs-front
terraform destroy
# Phase 2
cd ../phase2-security
terraform destroy
# Phase 1
cd ../phase1-network
terraform destroy
⚠️ 注意:
- 削除保護が有効なリソースは先に無効化
- RDSの最終スナップショットを確認
- 重要データは必ずバックアップ
12-2. ECRイメージの削除
手動削除が必要:
# Frontend
aws ecr batch-delete-image \
--repository-name myproject-front \
--image-ids imageTag=latest \
--region ap-northeast-1
# API
aws ecr batch-delete-image \
--repository-name myproject-api \
--image-ids imageTag=latest \
--region ap-northeast-1
(シリーズ完結)