- 1 Aurora PostgreSQL — 人間がログインしないDB管理 完全ガイド
- 1.1 1. なぜ「人間がログインしないDB管理」なのか
- 1.2 2. DB管理手法 徹底比較
- 1.3 3. アーキテクチャ概要
- 1.4 4. 前提条件と環境準備
- 1.5 5. Phase 1: VPC + Aurora PostgreSQL + RDS Proxy 構築(コンソール)
- 1.6 6. Phase 2: IAM 認証設定と DB ユーザー作成
- 1.7 7. Phase 3: Terraform postgresql provider でスキーマ管理
- 1.7.1 7-1. cyrilgdn/postgresql provider の設定
- 1.7.2 7-2. postgresql_database — データベース作成
- 1.7.3 7-3. postgresql_role — ロール/ユーザー作成
- 1.7.4 7-4. postgresql_schema — スキーマ作成
- 1.7.5 7-5. postgresql_grant — 権限付与(GRANT)
- 1.7.6 7-6. postgresql_default_privileges — デフォルト権限
- 1.7.7 7-7. postgresql_extension — 拡張機能
- 1.7.8 7-8. terraform apply でスキーマ管理
- 1.7.9 7-9. スキーマ変更の差分管理
- 1.8 8. Phase 4: Lambda + psycopg2 で DDL 実行
- 1.9 9. Phase 5: 動作確認とトラブルシューティング
- 1.10 Section 10: Phase 6 — Terraform IaC 全リソース
- 1.10.1 10-1. ディレクトリ構成
- 1.10.2 10-2. providers.tf — Provider設定
- 1.10.3 10-3. variables.tf — 変数定義
- 1.10.4 10-4. vpc.tf — ネットワーク基盤
- 1.10.5 10-5. aurora.tf — Aurora PostgreSQL Serverless v2
- 1.10.6 10-6. rds_proxy.tf — RDS Proxy(IAM認証 REQUIRED)
- 1.10.7 10-7. iam.tf — IAMロール / ポリシー
- 1.10.8 10-8. postgresql.tf — スキーマ / ロール / 権限管理(cyrilgdn/postgresql)
- 1.10.9 10-9. lambda.tf — マイグレーション Lambda
- 1.10.10 10-10. outputs.tf — 出力値
- 1.10.11 10-11. デプロイ手順
- 1.11 Section 11: 他の方法(概要解説)
- 1.12 Section 12: クリーンアップ
- 1.13 Section 13: まとめ
- 1.14 Section 14: 参考リンク
Aurora PostgreSQL — 人間がログインしないDB管理 完全ガイド
- Aurora PostgreSQL — 人間がログインしないDB管理 完全ガイド(この記事)
本記事は単独の完全ガイドです。Terraform + IAM認証 + RDS Proxy を組み合わせた「パスワードなし・ログインなし」のDB管理を一から解説します。
公開日: 2026-04-14
難易度: 中級〜上級
所要時間: 約120分
タグ: AWS, Aurora, PostgreSQL, Terraform, IAM認証, RDS Proxy, Lambda, IaC
- 従来のDB管理の問題点と「人間がログインしない」アーキテクチャのメリット
- Aurora PostgreSQLへの8つのアクセス手法を徹底比較
- Terraform
cyrilgdn/postgresqlproviderでスキーマ・ロール・権限をIaC管理 - IAM認証(パスワードレス)でAurora PostgreSQLに接続する仕組み
- RDS Proxyでコネクションプーリングとフェイルオーバーを透過的に処理
- Lambda + psycopg2 でDDLを自動実行するサーバーレスパターン
1. なぜ「人間がログインしないDB管理」なのか
1-1. 従来のDB管理が抱える問題
多くの現場では、Aurora PostgreSQLを含むRDBMSの管理を「人間が直接ログインして操作する」方法で行っています。しかしこのアプローチには、セキュリティ・運用・コンプライアンスの観点から深刻な問題があります。
問題1: パスワードの共有と管理コスト
従来の構成では、DBのマスターパスワードやアプリ用パスワードをチームで共有することが一般的です。
| 問題 | 具体的な影響 |
|---|---|
| パスワード共有 | 誰がいつログインしたか追跡不能。退職者の認証情報が残留 |
| ローテーション困難 | パスワード変更時に全アプリの設定変更が必要。ダウンタイムリスク |
| シークレット管理 | .envファイルやSlackでのパスワード共有。情報漏洩リスク |
| 監査証跡の欠如 | 「誰が何をしたか」のログが取れない。コンプライアンス違反 |
問題2: 踏み台サーバーによる属人的な運用
多くの現場では「踏み台EC2 → SSH → psql」というオペレーションフローが定着しています。
[開発者PC] → SSH → [踏み台EC2] → psql → [Aurora PostgreSQL]
この構成の問題点:
- 属人化: 踏み台サーバーのIPアドレス、SSHキー、接続手順がドキュメントに散在
- セキュリティリスク: 踏み台サーバー自体が攻撃対象になる。SSHキーの漏洩リスク
- 変更追跡不可能: psqlで実行したSQLはCloudTrailに残らない
- スケール困難: 開発者が増えるたびにSSHキー発行・管理が必要
問題3: 手動DDLによる一貫性の欠如
「スキーマを変更したいとき」に開発者がpsqlで直接DDLを実行するケースは珍しくありません。
-- 開発者Aがログインして手動実行(ステージング環境に反映済み)
ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP;
-- 本番環境への展開を忘れていた...
この問題は:
– 本番/ステージングの乖離: 誰かが手動で変更を加えると環境間で差分が生まれる
– レビュープロセスの欠如: コードと異なり、DBへの手動変更はPull Requestでのレビューがない
– ロールバックが困難: 誤ったDDLをロールバックするには再度手動で修正が必要
1-2. 理想の形: Infrastructure as Code + IAM認証
本記事が目指すアーキテクチャは 「人間がAurora PostgreSQLに直接ログインしない」 構成です。
理念
❌ 従来: 人間 → psql → Aurora(パスワード認証・手動操作)
✅ 理想: コード → Terraform/Lambda → Aurora(IAM認証・自動実行)
具体的な解決策
| 課題 | 解決策 |
|---|---|
| パスワード共有 | IAM認証でパスワードレス接続。パスワード自体を持たない |
| スキーマ手動変更 | Terraform postgresql providerでDDLをIaC化。Git管理・PR必須 |
| 踏み台サーバー | RDS Proxy + VPC Lambdaで接続。SSHキー不要 |
| 監査証跡の欠如 | CloudTrail + CloudWatch Logsで全接続・操作を記録 |
| 環境間の乖離 | Terraformのterraform planで差分を可視化。applyで一致させる |
1-3. 本記事のゴール
本記事では以下を実現します:
- Aurora PostgreSQL Serverless v2 をVPC内に構築
- RDS Proxy をフロントに配置してコネクション管理
- IAM認証 を有効化し、パスワードなしで接続
- Terraform
cyrilgdn/postgresqlprovider でスキーマ・ロール・権限を宣言的管理 - Lambda + psycopg2 でDDLを自動実行するサーバーレスパターンを実装
- 全リソースを Terraform IaC で管理し、
git pushだけで環境を再現可能に
「人間がDB管理をするのではなく、コードがDB管理をする。人間はコードをレビューする。」
すべての変更はGitHubのPull Requestを通じ、Terraformが自動適用します。
2. DB管理手法 徹底比較
Aurora PostgreSQLへのアクセス・管理方法は多岐にわたります。本セクションでは8つの代表的な手法を整理し、本記事でのハンズオン対象選定理由を説明します。
2-1. 8つの方法の概要
A. Terraform postgresql provider(cyrilgdn/postgresql)
Terraform HashiCorp Registryで公開されている cyrilgdn/postgresql プロバイダー(v1.26.0、2025年9月)を使い、Aurora PostgreSQLのスキーマ・ロール・権限をTerraformリソースとして宣言的に管理します。
- 特徴: 19種類のリソース(
postgresql_database/postgresql_schema/postgresql_role/postgresql_grant等) - IAM認証:
scheme=awspostgres+aws_rds_iam_auth=trueでパスワードレス接続対応(v1.25.0〜) - ユースケース: 初期スキーマ構築・ロール管理・権限管理をIaCで完結させたい場合
B. Flyway(CodeBuild / Fargate)
Java製のDBマイグレーションツール「Flyway」をAWSサービス上で実行するパターン。AWS公式ブログでもCodePipeline + CodeBuildの構成が紹介されています。
- 特徴: SQLスクリプトをバージョン番号管理(V1.0__init.sql, V2.0__add_column.sql)
- IAM認証: 直接サポートなし(Secrets Manager経由のパスワード認証が一般的)
- ユースケース: 継続的なスキーママイグレーションをCI/CDパイプラインに組み込みたい場合
C. Lambda + psycopg2
Python製のPostgreSQL接続ライブラリ psycopg2 をLambda LayerとしてパッケージングしたLambda関数からAuroraにアクセスするパターン。
- 特徴:
boto3.client('rds').generate_db_auth_token()でIAM認証トークンを生成し接続 - ユースケース: 軽量DDL実行・スキーマ初期化・EventBridge Schedulerでの定期実行
D. RDS Data API
AuroraにHTTPS経由でSQLを実行できるAPI。VPCへの接続不要でVPC外からも利用可能。
- 特徴: DDL対応、トランザクション対応、IAM認証(
rds-data:ExecuteStatement権限) - 制約: ENUM型非対応、結果サイズ上限1MB、T系インスタンス非対応
- ユースケース: VPC外からの軽量DB操作・開発環境でのデバッグ
E. IAM データベース認証
Aurora PostgreSQLクラスターレベルでIAM認証を有効化し、トークンベースのパスワードレス接続を実現する基盤機能。他の方法(A/C/F)と組み合わせて使用します。
- 仕組み: IAMポリシーで
rds-db:connect権限を付与 → 15分間有効のトークンを生成 → パスワード代わりに使用 - RDS Proxy連携: アプリ→RDS Proxy間はIAM認証、RDS Proxy→Aurora間はSecrets Manager
F. SSM Session Manager + ECS Run Task
SSM Session ManagerのPort Forwarding機能でEC2経由のポートフォワードを行い、ローカルPCからpsqlやpgAdminで接続するパターン、またはECS Fargateでpsqlコンテナを一時実行するパターン。
- SSM:
aws ssm start-session --document-name AWS-StartPortForwardingSessionToRemoteHost→localhost:15432で接続 - ユースケース: 開発者のad-hocなDB参照(踏み台EC2サーバー不要)
G. DMS / SCT(Database Migration Service)
異種DB間のスキーマ変換・データ移行ツール。同一エンジン(PostgreSQL → Aurora PostgreSQL)のスキーマ管理には不適切で、既存DBからの初期移行時のみ利用します。本記事の対象外です。
H. その他のパターン
- GitHub Actions OIDC: OIDC → IAMロール →
generate_db_auth_token→ Aurora接続。CI/CDパイプラインからパスワードなしでDB操作 - Secrets Manager 自動ローテーション: ローテーションLambdaをカスタマイズしてスキーマ変更も実行(非推奨)
- AWSコンソール Query Editor: Data APIを使ったGUI操作。開発・デバッグ用途
2-2. 比較表
| 方法 | ユースケース | VPC内接続 | IAM認証 | Terraform統合 | スキーマ変更 | 難易度 | 推奨度 |
|---|---|---|---|---|---|---|---|
| A. TF postgresql provider | 初期構築・IaC管理 | 要(直接接続) | ✅ awspostgres | ✅ 19リソース | ✅ DDL管理 | ★★★☆☆ | ★★★★★ |
| B. Flyway (CodeBuild/Fargate) | バージョン管理マイグレーション | 要 | ❌(Secrets Manager) | △ null_resource | ✅ SQLスクリプト | ★★★★☆ | ★★★★☆ |
| C. Lambda + psycopg2 | 軽量DDL・スキーマ初期化 | 要(VPC Lambda) | ✅ generate_db_auth_token | △ aws_lambda | ✅ 任意SQL | ★★☆☆☆ | ★★★★☆ |
| D. RDS Data API | VPC外からの軽量操作 | 不要(HTTPS) | ✅ IAM | △ null_resource | ✅ DDL可 | ★☆☆☆☆ | ★★★☆☆ |
| E. IAM DB Auth | パスワードレス基盤 | — | ✅ 基盤機能 | ✅ RDS設定 | — | ★★☆☆☆ | ★★★★★ |
| F. SSM / ECS Run Task | 開発者ad-hoc操作 | EC2経由 | ✅ SSM | ❌ | — | ★★☆☆☆ | ★★★☆☆ |
| G. ECS Run Task + psql | 長時間マイグレーション | 要 | ✅ | △ | ✅ | ★★★☆☆ | ★★★☆☆ |
| H. GitHub Actions OIDC | CI/CDパイプライン | OIDC経由 | ✅ | — | ✅ | ★★★☆☆ | ★★★☆☆ |
2-3. 本記事でのハンズオン対象
本記事では以下を選定しました。
メインハンズオン: A + E + RDS Proxy(Terraform + IAM認証 + RDS Proxy)
[Terraform実行環境]
↓(IAM認証 / awspostgres scheme)
[RDS Proxy] → [Aurora PostgreSQL Serverless v2]
↑
[Lambda / アプリ](IAM認証 / generate_db_auth_token)
選定理由:
– IaCに最も忠実: スキーマ・ロール・権限がすべてTerraform stateで管理され、terraform planで差分を可視化できる
– パスワードレス: IAM認証によりパスワード自体が存在しない。ローテーション不要
– 「人間がログインしない」コンセプトに最合致: コードのみがDBを操作し、全変更がGit管理される
– RDS Proxy: コネクションプーリングによるスケーラビリティ、フェイルオーバーの高速化
サブハンズオン: C(Lambda + psycopg2 + IAM認証)
Terraform providerでは管理できないインデックス作成やデータシードのユースケースをLambdaで補完します。Phase 4〜5で実装します。
解説のみ(ハンズオン手順なし):
- D(RDS Data API): VPC不要で最も手軽だが制約が多い。概要とQuery Editor紹介のみ
- B(Flyway on CodeBuild): Java依存・Enterprise版が必要なケースあり。アーキテクチャ概要のみ
- F(SSM Port Forwarding): 開発者のデバッグ用。コマンド例のみ
- H(GitHub Actions OIDC): CI/CD応用パターンとして紹介
3. アーキテクチャ概要
3-1. 全体構成
本記事で構築するシステムの全体像を示します。

ネットワーク構成
システム全体はVPC内のプライベートサブネットに配置します。インターネットからAurora PostgreSQLへの直接アクセスは行いません。
ap-northeast-1(東京リージョン)
└── VPC (10.0.0.0/16)
├── パブリックサブネット (10.0.1.0/24, 10.0.2.0/24)
│└── NAT Gateway(Lambda → VPCエンドポイント用)
└── プライベートサブネット (10.0.11.0/24, 10.0.12.0/24)
├── Aurora PostgreSQL Serverless v2(マルチAZ)
├── RDS Proxy
└── Lambda関数(VPC Lambda)
コンポーネント一覧
| コンポーネント | サービス | 役割 |
|---|---|---|
| データベース | Aurora PostgreSQL Serverless v2 | メインDB(0.5〜128 ACU自動スケール) |
| コネクション管理 | RDS Proxy | コネクションプーリング・IAM認証フロント |
| スキーマ管理 | Terraform postgresql provider | DDL(スキーマ/ロール/権限)のIaC化 |
| DDL実行 | Lambda + psycopg2 | インデックス作成等のサーバーレス実行 |
| 認証 | IAM Database Authentication | パスワードレス接続の基盤 |
| シークレット管理 | Secrets Manager | RDS Proxy→Aurora間のマスターパスワード格納 |
| VPC接続 | VPCエンドポイント | STS/Secrets Manager/RDSへのプライベート接続 |
3-2. IAM認証フロー
IAM認証は「パスワードの代わりに15分間有効なトークンを使う」仕組みです。

フローの詳細
Terraform postgresql provider(スキーマ管理時):
1. Terraform実行環境(IAMロール: terraform_pg_role)
2. generate_db_auth_token(scheme=awspostgres が自動処理)
3. RDS Proxy エンドポイントに接続(TLS必須)
4. RDS Proxy → Aurora PostgreSQL(Secrets Manager認証)
5. DDLリソースを作成/更新/削除
Lambda + psycopg2(DDL実行時):
import boto3, psycopg2
# IAM認証トークン生成(15分間有効)
client = boto3.client('rds', region_name='ap-northeast-1')
token = client.generate_db_auth_token(
DBHostname=rds_proxy_endpoint,
Port=5432,
DBUsername='iam_user',
Region='ap-northeast-1'
)
# パスワード代わりにトークンを使用
conn = psycopg2.connect(
host=rds_proxy_endpoint,
port=5432,
user='iam_user',
password=token,
dbname='mydb',
sslmode='require'
)
RDS Proxy の役割:
| 接続区間 | 認証方式 | 補足 |
|---|---|---|
| アプリ/Lambda → RDS Proxy | IAM認証(トークン) | TLS必須 |
| RDS Proxy → Aurora | Secrets Manager(パスワード) | 自動ローテーション対応 |
RDS Proxyを経由することで、アプリ側はIAM認証のみを意識すればよく、Aurora側のパスワード管理はRDS Proxyが吸収します。
3-3. マイグレーションフロー
スキーマ変更の流れを示します。

Terraformによるスキーマ変更フロー
① 開発者がpostgresql.tf を編集
② git push → Pull Request作成
③ CIで terraform plan を実行(差分レビュー)
④ レビュー承認 → main ブランチにマージ
⑤ CD(GitHub Actions等)で terraform apply 自動実行
⑥ postgresql provider が RDS Proxy 経由でDDLを実行
⑦ terraform state に変更後の状態を記録
Lambdaによるマイグレーションフロー(インデックス・データシード等)
① EventBridge Scheduler(毎日実行)またはStep Functions
② Lambda関数(Python 3.12 + psycopg2)を起動
③ Lambda が RDS Proxy 経由でIAM認証接続
④ migration_versionテーブルで未適用マイグレーションを確認
⑤ 未適用のDDL/DMLを冪等に実行(IF NOT EXISTS等)
⑥ CloudWatch Logsに実行結果を記録
3-4. 各コンポーネントの役割詳細
VPC
プライベートサブネット内にAurora・RDS Proxy・Lambdaを配置し、インターネットからの直接アクセスを遮断します。セキュリティグループでポート5432の通信を制御します。
セキュリティグループ:
- aurora_sg: ポート5432 ← rds_proxy_sg, lambda_sg のみ許可
- rds_proxy_sg: ポート5432 ← lambda_sg, terraform_sg のみ許可
- lambda_sg: アウトバウンドのみ(STS, Secrets Manager, RDS Data API)
Aurora PostgreSQL Serverless v2
最小0.5 ACU(Aurora Capacity Unit)から最大128 ACUまで自動スケールします。IAM認証有効化により、マスターパスワードを知っている人間でも、IAM権限なしでは接続できなくなります。
resource "aws_rds_cluster" "aurora_pg" {
engine = "aurora-postgresql"
engine_mode= "provisioned"
engine_version= "16.4"
iam_database_authentication_enabled = true # IAM認証を有効化
serverlessv2_scaling_configuration {
min_capacity = 0.5
max_capacity = 4.0
}
}
RDS Proxy
Aurora PostgreSQLのフロントに配置するフルマネージドのコネクションプロキシです。
- コネクションプーリング: Lambda関数が並列実行されても、Aurora側のコネクション数を抑制
- フェイルオーバー高速化: Aurora障害時のフェイルオーバーを66%高速化
- IAM認証必須(REQUIRED): IAM認証なしの接続を完全拒否
Terraform postgresql provider
cyrilgdn/postgresql プロバイダーのv1.26.0(2025年9月リリース)を使用します。
terraform {
required_providers {
postgresql = {
source = "cyrilgdn/postgresql"
version = "~> 1.26"
}
}
}
provider "postgresql" {
scheme = "awspostgres" # IAM認証用スキーム
host= aws_db_proxy.main.endpoint
port= 5432
database = "postgres"
username = "terraform_user"
aws_rds_iam_auth= true # IAMトークン自動生成
aws_rds_iam_region = "ap-northeast-1"
superuser = false
}
Lambda(psycopg2)
Python 3.12ランタイムのLambda関数に psycopg2 をLayerとして追加します。VPC内に配置し、RDS Proxy経由でIAM認証接続します。
3-5. コスト概算
本記事の構成を1日稼働した場合の概算コスト(東京リージョン、2026年4月時点):
| リソース | 単価 | 1日の概算コスト |
|---|---|---|
| Aurora PostgreSQL Serverless v2(最小0.5 ACU) | $0.12/ACU時間 | $1.44/日 |
| RDS Proxy | $0.015/VCPUh(Auroraの0.5 vCPU相当) | $0.18/日 |
| NAT Gateway(データ転送なし) | $0.062/時間 | $1.49/日 |
| Lambda(月10万回実行以内) | 無料枠内 | $0 |
| Secrets Manager(1シークレット) | $0.40/月 | $0.01/日 |
| 合計 | 約$3.12/日(約470円) |
Aurora Serverless v2 は「最小ACU = 0.5」でも、クラスターが起動している間は課金が発生します。ハンズオン完了後は必ず
terraform destroy でリソースを削除してください。特に RDS Proxy は Aurora クラスターが停止していても課金されます。4. 前提条件と環境準備
4-1. 必要な環境
- AWSアカウント: Aurora, RDS Proxy, Lambda, IAM, VPC, Secrets Manager の操作権限が必要
- Terraform: バージョン
1.5以上(terraform versionで確認) - AWS CLI v2:
aws --versionでaws-cli/2.x.xであることを確認 - AWS認証情報:
aws configure済み、またはIAMロールが設定済み - Docker(任意): psycopg2 Layerをローカルビルドする場合に必要
- 対象リージョン:
ap-northeast-1(東京)を前提に手順を記載
ツールのバージョン確認
# Terraform バージョン確認(1.5以上が必要)
terraform version
# Terraform v1.9.x
# AWS CLI v2 確認
aws --version
# aws-cli/2.x.x Python/3.x.x ...
# Python(Lambda開発用)
python3 --version
# Python 3.12.x
# Docker(任意: psycopg2 Layer ビルド用)
docker --version
# Docker version 27.x.x
4-2. IAM権限の確認
ハンズオンを実行するIAMユーザー/ロールには以下の権限が必要です。
{
"Effect": "Allow",
"Action": [
"rds:*",
"rds-db:connect",
"secretsmanager:*",
"iam:*",
"lambda:*",
"ec2:*",
"logs:*",
"sts:GetCallerIdentity"
],
"Resource": "*"
}
上記はハンズオン用の広めの権限です。本番環境では各リソースに応じた最小権限を設計してください。Terraformが必要とする権限の詳細は
iam.tf の作成時に説明します。4-3. 作業ディレクトリ構成
本記事のTerraformコードは以下の構成で管理します。
aurora-no-human-login/
├── terraform/
│├── providers.tf # AWSプロバイダー + PostgreSQLプロバイダー
│├── variables.tf # 変数定義
│├── outputs.tf# 出力値
│├── vpc.tf # VPC/サブネット/NAT Gateway/セキュリティグループ
│├── aurora.tf # Aurora PostgreSQL Serverless v2 + Secrets Manager
│├── rds_proxy.tf # RDS Proxy + IAM認証設定
│├── iam.tf # IAMロール/ポリシー(Terraform用・Lambda用)
│├── postgresql.tf# スキーマ/ロール/権限(cyrilgdn/postgresql)
│├── lambda.tf # マイグレーションLambda
│└── terraform.tfvars# 変数値(gitignore推奨)
├── lambda/
│├── migration/
││├── handler.py # Lambda関数コード
││└── requirements.txt # psycopg2-binary
│└── layer/
│ └── build.sh # psycopg2 Layer ビルドスクリプト
└── README.md
作業ディレクトリを作成します:
mkdir -p aurora-no-human-login/terraform
mkdir -p aurora-no-human-login/lambda/migration
mkdir -p aurora-no-human-login/lambda/layer
cd aurora-no-human-login
git init
4-4. cyrilgdn/postgresql provider について
本記事のメインハンズオンで使用する cyrilgdn/postgresql プロバイダーの概要を事前に把握しておきましょう。
# providers.tf(最小構成)
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
postgresql = {
source = "cyrilgdn/postgresql"
version = "~> 1.26"
}
}
}
主要リソース(本記事で使用するもの)
| リソース名 | 用途 |
|---|---|
postgresql_database | データベース作成 |
postgresql_schema | スキーマ作成(名前空間分離) |
postgresql_role | ロール/ユーザー作成 |
postgresql_grant | 権限付与(GRANT) |
postgresql_default_privileges | デフォルト権限設定 |
postgresql_extension | 拡張機能追加(pgcrypto, uuid-ossp等) |
IAM認証での接続設定(scheme=awspostgres)
cyrilgdn/postgresql v1.25.0 以降、scheme=awspostgres を指定することでIAM認証トークンを自動生成して接続できます。パスワードを一切コードに記述する必要がありません。
provider "postgresql" {
scheme = "awspostgres" # ← GoCloud経由のIAM認証
host= "<RDS Proxyエンドポイント>"
port= 5432
database = "postgres"
username = "terraform_user"
aws_rds_iam_auth= true
aws_rds_iam_region = "ap-northeast-1"
# password は不要!IAMトークンが自動使用される
superuser = false
sslmode= "require"
}
4-5. コスト警告
- Aurora Serverless v2: 最小 0.5 ACU × $0.12/時間 = 約$1.44/日。停止しても削除しない限り課金継続
- RDS Proxy: Aurora クラスターの vCPU 数 × $0.015/時間。Aurora を停止していても課金
- NAT Gateway: 稼働時間 × $0.062/時間 = 約$1.49/日(データ転送除く)
- Secrets Manager: $0.40/シークレット/月(30日間の無料トライアルあり)
ハンズオン完了後は必ず terraform destroy を実行してすべてのリソースを削除してください。
5. Phase 1: VPC + Aurora PostgreSQL + RDS Proxy 構築(コンソール)
Aurora PostgreSQL クラスターを「人間がログインしないDB管理」に適した構成で構築します。このフェーズでは AWSコンソールを使い、VPC/ネットワーク基盤から Aurora クラスター・Secrets Manager・RDS Proxy までをセットアップします。
Phase 1(Section 5)はコンソールで手動構築します。Phase 3 以降(Section 7)で同じリソースを Terraform コードに落とし込みます。まずコンソールで全体像を把握し、その後 IaC へ移行する流れです。
Phase 1-1: VPC・ネットワーク基盤の作成
Aurora を VPC のプライベートサブネットに配置し、外部から直接アクセスできない構成を作ります。
1. VPC の作成
- AWSコンソール → VPC → 左メニュー「Your VPCs」→ 「Create VPC」をクリック
- 以下の設定を入力します:
| 項目 | 設定値 |
|---|---|
| VPC only を選択 | (「VPC and more」ではなく単体作成) |
| Name tag | aurora-db-vpc |
| IPv4 CIDR block | 10.0.0.0/16 |
| IPv6 CIDR block | No IPv6 CIDR block |
| Tenancy | Default |
- 「Create VPC」をクリック
2. サブネットの作成
VPC ダッシュボード → 「Subnets」→ 「Create subnet」
Public Subnet A(NAT Gateway 配置用)
| 項目 | 設定値 |
|---|---|
| VPC ID | aurora-db-vpc(上で作成した VPC) |
| Subnet name | aurora-public-1a |
| Availability Zone | ap-northeast-1a |
| IPv4 subnet CIDR block | 10.0.0.0/24 |
「Add new subnet」をクリックして続けて追加:
Public Subnet C
| 項目 | 設定値 |
|---|---|
| Subnet name | aurora-public-1c |
| Availability Zone | ap-northeast-1c |
| IPv4 subnet CIDR block | 10.0.64.0/24 |
「Add new subnet」→ Private Subnet A:
| 項目 | 設定値 |
|---|---|
| Subnet name | aurora-private-1a |
| Availability Zone | ap-northeast-1a |
| IPv4 subnet CIDR block | 10.0.128.0/24 |
「Add new subnet」→ Private Subnet C:
| 項目 | 設定値 |
|---|---|
| Subnet name | aurora-private-1c |
| Availability Zone | ap-northeast-1c |
| IPv4 subnet CIDR block | 10.0.192.0/24 |
4 つのサブネットをまとめて作成します。「Create subnet」をクリック。
3. Internet Gateway の作成とアタッチ
- VPC ダッシュボード → 「Internet gateways」→ 「Create internet gateway」
- Name tag:
aurora-igw→ 「Create internet gateway」 - 作成後に「Attach to a VPC」が表示されます →
aurora-db-vpcを選択して「Attach internet gateway」
4. NAT Gateway の作成
Public Subnet からプライベートサブネット内の Lambda がアウトバウンド通信できるように NAT Gateway を配置します。
- VPC ダッシュボード → 「NAT gateways」→ 「Create NAT gateway」
- 設定:
| 項目 | 設定値 |
|---|---|
| Name | aurora-nat-1a |
| Subnet | aurora-public-1a (Publicサブネットに配置) |
| Connectivity type | Public |
| Elastic IP allocation ID | 「Allocate Elastic IP」をクリックして新規割り当て |
- 「Create NAT gateway」をクリック
5. ルートテーブルの設定
パブリック用ルートテーブル(IGW 経由)
- VPC ダッシュボード → 「Route tables」
aurora-db-vpcに紐づくデフォルトルートテーブルを選択(Name が空のもの)- 「Actions」→「Edit routes」→「Add route」
- Destination:
0.0.0.0/0 - Target:
aurora-igw(先ほど作成した Internet Gateway) - 「Save changes」
- 「Subnet associations」タブ → 「Edit subnet associations」
aurora-public-1a、aurora-public-1cを選択 → 「Save associations」
プライベート用ルートテーブル(NAT 経由)
- 「Create route table」→ Name:
aurora-private-rt、VPC:aurora-db-vpc - 「Create route table」→ 「Edit routes」→ 「Add route」
- Destination:
0.0.0.0/0 - Target:
aurora-nat-1a(先ほど作成した NAT Gateway) - 「Save changes」
- 「Subnet associations」→
aurora-private-1a、aurora-private-1cを追加 → 「Save associations」
Phase 1-2: セキュリティグループの作成
Aurora・RDS Proxy・Lambda の間の通信を最小権限で制御する 3 つのセキュリティグループを作ります。
VPC ダッシュボード → 「Security groups」→ 「Create security group」
セキュリティグループ 1: aurora-sg(Aurora PostgreSQL 用)
| 項目 | 設定値 |
|---|---|
| Security group name | aurora-sg |
| Description | Aurora PostgreSQL security group |
| VPC | aurora-db-vpc |
Inbound rules(後で追加): RDS Proxy SG 作成後に設定
Outbound rules(デフォルト): すべて許可
「Create security group」
セキュリティグループ 2: rds-proxy-sg(RDS Proxy 用)
| 項目 | 設定値 |
|---|---|
| Security group name | rds-proxy-sg |
| Description | RDS Proxy security group |
| VPC | aurora-db-vpc |
Inbound rules(後で追加): Lambda SG 作成後に設定
Outbound rules:
| Type | Protocol | Port | Destination |
|---|---|---|---|
| Custom TCP | TCP | 5432 | aurora-sg のセキュリティグループ ID |
「Create security group」
セキュリティグループ 3: lambda-sg(Lambda 用)
| 項目 | 設定値 |
|---|---|
| Security group name | lambda-sg |
| Description | Lambda migration function security group |
| VPC | aurora-db-vpc |
Inbound rules: なし(Lambda は受信しない)
Outbound rules:
| Type | Protocol | Port | Destination |
|---|---|---|---|
| Custom TCP | TCP | 5432 | rds-proxy-sg のセキュリティグループ ID |
| HTTPS | TCP | 443 | 0.0.0.0/0(STS/Secrets Manager へのアクセス) |
「Create security group」
セキュリティグループのルールを更新
aurora-sg の Inbound rules を追加:
aurora-sgを選択 → 「Edit inbound rules」→ 「Add rule」
| Type | Protocol | Port | Source |
|---|---|---|---|
| Custom TCP | TCP | 5432 | rds-proxy-sg のセキュリティグループ ID |
| Custom TCP | TCP | 5432 | lambda-sg のセキュリティグループ ID |
- 「Save rules」
rds-proxy-sg の Inbound rules を追加:
rds-proxy-sgを選択 → 「Edit inbound rules」→「Add rule」
| Type | Protocol | Port | Source |
|---|---|---|---|
| Custom TCP | TCP | 5432 | lambda-sg のセキュリティグループ ID |
- 「Save rules」
Phase 1-3: Aurora PostgreSQL Serverless v2 クラスターの作成
- AWSコンソール → RDS → 「Create database」
- 設定を順番に入力します:
① Standard create を選択
② Engine options
| 項目 | 設定値 |
|---|---|
| Engine type | Aurora (PostgreSQL Compatible) |
| Engine version | Aurora PostgreSQL 16.x(最新の 16 系を選択) |
③ Templates
| 項目 | 設定値 |
|---|---|
| Template | Production |
④ Settings
| 項目 | 設定値 |
|---|---|
| DB cluster identifier | aurora-no-human-login-cluster |
| Master username | postgres |
| Credentials management | Self managed |
| Master password | 任意の強いパスワード(後で Secrets Manager に登録する) |
⑤ Cluster storage configuration
| 項目 | 設定値 |
|---|---|
| Storage type | Aurora Standard |
⑥ Instance configuration
| 項目 | 設定値 |
|---|---|
| DB instance class | Serverless v2 |
| Minimum capacity | 0.5 ACU |
| Maximum capacity | 16 ACU |
⑦ Availability & durability
| 項目 | 設定値 |
|---|---|
| Multi-AZ deployment | Don’t create an Aurora Replica(ハンズオン用途のため) |
⑧ Connectivity
| 項目 | 設定値 |
|---|---|
| Virtual private cloud (VPC) | aurora-db-vpc |
| DB subnet group | 「Create new DB subnet group」を選択 |
| Public access | No |
| VPC security group | Choose existing → aurora-sg を選択(default は削除) |
| Availability Zone | ap-northeast-1a |
⑨ Database authentication
| 項目 | 設定値 |
|---|---|
| Database authentication | Password and IAM database authentication ← 必須 |
⑩ Monitoring
| 項目 | 設定値 |
|---|---|
| Enable Enhanced monitoring | チェックを外す(ハンズオン用途では不要) |
⑪ Additional configuration
| 項目 | 設定値 |
|---|---|
| Initial database name | appdb |
| DB cluster parameter group | default.aurora-postgresql16 |
| Backup retention period | 1 day(ハンズオン用途) |
| Enable deletion protection | チェックを外す(後の削除を容易にするため) |
- 「Create database」をクリック
Aurora クラスターの作成には約 5〜10 分かかります。Status が「Available」になるまで次のステップに進みます。
Phase 1-4: Secrets Manager シークレットの作成
RDS Proxy がマスターユーザーの認証情報を安全に管理するため、Secrets Manager にシークレットを登録します。
- AWSコンソール → Secrets Manager → 「Store a new secret」
- 設定:
① Secret type
| 項目 | 設定値 |
|---|---|
| Secret type | Credentials for Amazon RDS database |
| Username | postgres |
| Password | Aurora 作成時に設定したマスターパスワード |
| Database | 上で作成した aurora-no-human-login-cluster を選択 |
「Next」
② Configure secret
| 項目 | 設定値 |
|---|---|
| Secret name | aurora-no-human-login/master |
| Description | Master credentials for Aurora aurora-no-human-login-cluster |
「Next」
③ Configure rotation(ローテーション設定)
| 項目 | 設定値 |
|---|---|
| Automatic rotation | Enable automatic rotation |
| Rotation schedule | 30 days |
| Rotation function | Create a new Lambda function |
| Lambda function name | SecretsManagerAuroraPostgreSQLRotation |
「Next」→「Store」をクリック
Phase 1-5: RDS Proxy の作成
RDS Proxy を介することで、コネクションプーリングと IAM 認証の一元管理が実現します。
- AWSコンソール → RDS → 左メニュー「Proxies」→ 「Create proxy」
- 設定:
① Proxy configuration
| 項目 | 設定値 |
|---|---|
| Proxy identifier | aurora-no-human-login-proxy |
| Engine family | PostgreSQL |
| Require Transport Layer Security | チェックを入れる (TLS 必須) |
| Idle client connection timeout | 1800 seconds(30 分) |
② Target group configuration
| 項目 | 設定値 |
|---|---|
| Database | aurora-no-human-login-cluster |
| Connection pool maximum connections | 100(ハンズオン用途) |
③ Connectivity
| 項目 | 設定値 |
|---|---|
| Secrets Manager secret | aurora-no-human-login/master |
| IAM role | Create a new IAM role → 名前: aurora-rds-proxy-role |
| IAM authentication | Required ← 重要 |
| Subnets | aurora-private-1a、aurora-private-1c を選択 |
| Security groups | rds-proxy-sg |
④ Additional configuration
| 項目 | 設定値 |
|---|---|
| Enable enhanced logging | チェックを入れる(トラブルシューティング用) |
- 「Create proxy」をクリック
RDS Proxy の作成には約 5〜10 分かかります。Status が「Available」になったら Phase 2 に進みます。
aurora-no-human-login-proxy.proxy-xxxx.ap-northeast-1.rds.amazonaws.com)を後の手順で使用します。メモしておきましょう。6. Phase 2: IAM 認証設定と DB ユーザー作成
Phase 1 で構築した Aurora クラスターに IAM 認証用の DB ユーザーを作成し、パスワードレス接続の基盤を完成させます。
Phase 2-1: VPC 内からの初回接続
Aurora はプライベートサブネットに配置しているため、VPC 内のリソースからしかアクセスできません。Cloud9(または EC2)を使ってプライベートサブネット内から接続します。
Cloud9 環境の準備
- AWSコンソール → Cloud9 → 「Create environment」
- 設定:
| 項目 | 設定値 |
|---|---|
| Name | aurora-admin-env |
| Environment type | New EC2 instance |
| Instance type | t3.small |
| Platform | Amazon Linux 2023 |
| Network settings | aurora-db-vpc / aurora-public-1a |
- 「Create」→ Cloud9 IDE が起動するまで待つ(約 2 分)
aws ssm start-session \
--target i-xxxxxxxxxxxxxxxxx \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["<aurora-writer-endpoint>"],"portNumber":["5432"],"localPortNumber":["15432"]}'
その後 localhost:15432 で psql 接続できます。
psql のインストールと Aurora への接続
Cloud9 ターミナルで以下を実行します:
# PostgreSQL クライアントのインストール
sudo dnf install -y postgresql15
# Aurora Writer エンドポイントへの接続
# (RDS コンソール → Databases → aurora-no-human-login-cluster → Connectivity & security タブで確認)
AURORA_ENDPOINT="aurora-no-human-login-cluster.cluster-xxxx.ap-northeast-1.rds.amazonaws.com"
psql -h $AURORA_ENDPOINT -U postgres -d appdb
パスワードを求められたら、Phase 1-3 で設定したマスターパスワードを入力します。
接続確認:
SELECT version();
-- PostgreSQL 16.x (Aurora PostgreSQL ...) が表示される
Phase 2-2: IAM 認証用 DB ユーザーの作成
PostgreSQL の rds_iam ロールを付与したユーザーだけが IAM トークンでログインできます。
-- Terraform 実行環境用 IAM ユーザーの作成
CREATE USER iam_terraform_user;
GRANT rds_iam TO iam_terraform_user;
-- Lambda 関数用 IAM ユーザーの作成
CREATE USER iam_lambda_user;
GRANT rds_iam TO iam_lambda_user;
-- 確認: rds_iam ロールが付与されているか確認
SELECT usename, usesuper, memberof
FROM pg_user
JOIN pg_auth_members ON pg_user.usesysid = pg_auth_members.member
JOIN pg_roles ON pg_auth_members.roleid = pg_roles.oid
WHERE pg_roles.rolname = 'rds_iam';
期待される出力:
usename | usesuper | memberof
-------------------+----------+---------
iam_terraform_user | false| {rds_iam}
iam_lambda_user | false| {rds_iam}
(2 rows)
-- psql を終了
\q
Phase 2-3: IAM ポリシーの作成
rds-db:connect アクションを IAM ポリシーで許可します。
- AWSコンソール → IAM → 「Policies」→ 「Create policy」
- 「JSON」タブを選択して以下を入力:
まず Aurora クラスターのリソース ID を確認します:
- AWSコンソール → RDS →
aurora-no-human-login-clusterを選択 - 「Configuration」タブ → 「Resource ID」の値をコピー(例:
cluster-XXXXXXXXXXXXXXXXX)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": [
"arn:aws:rds-db:ap-northeast-1:{ACCOUNT_ID}:dbuser:{CLUSTER_RESOURCE_ID}/iam_terraform_user",
"arn:aws:rds-db:ap-northeast-1:{ACCOUNT_ID}:dbuser:{CLUSTER_RESOURCE_ID}/iam_lambda_user"
]
}
]
}
{ACCOUNT_ID} は自身の AWS アカウント ID(12桁)、{CLUSTER_RESOURCE_ID} は上で確認したリソース ID に置き換えます。
- 「Next」→ Policy name:
aurora-iam-db-access→ 「Create policy」
dbuser 部分は RDS Proxy のリソース ID ではなく Aurora クラスターのリソース ID を使います。RDS Proxy は IAM トークンを検証した後、内部的に Secrets Manager の認証情報で Aurora に接続します。Phase 2-4: IAM ロールの作成
Terraform 実行環境と Lambda 関数それぞれに IAM ロールを作成し、aurora-iam-db-access ポリシーをアタッチします。
aurora-terraform-role(Terraform 実行環境用)
- IAM → 「Roles」→ 「Create role」
- Trusted entity type: AWS service → EC2
- Add permissions:
aurora-iam-db-accessを検索してアタッチ - Role name:
aurora-terraform-role - 「Create role」
aurora-lambda-role(Lambda 実行用)
- IAM → 「Roles」→ 「Create role」
- Trusted entity type: AWS service → Lambda
- Add permissions:
aurora-iam-db-accessAWSLambdaVPCAccessExecutionRole(VPC Lambda 実行に必要)- Role name:
aurora-lambda-role - 「Create role」
Phase 2-5: IAM トークンの生成テスト
RDS Proxy エンドポイントに対してトークンを生成できるか確認します。Cloud9 ターミナルで実行します。
# RDS Proxy エンドポイントを変数に設定
PROXY_ENDPOINT="aurora-no-human-login-proxy.proxy-xxxx.ap-northeast-1.rds.amazonaws.com"
# IAM 認証トークンの生成
TOKEN=$(aws rds generate-db-auth-token \
--hostname $PROXY_ENDPOINT \
--port 5432 \
--username iam_terraform_user \
--region ap-northeast-1)
# トークンの先頭 80 文字を確認
echo "Token (first 80 chars): ${TOKEN:0:80}..."
期待される出力例:
Token (first 80 chars): aurora-no-human-login-proxy.proxy-xxxx.ap-northeast-1.rds.amazonaws.com:5432/...
トークンは hostname:port/?Action=connect&DBUser=...&X-Amz-Credential=... 形式の署名付き URL です。
rds:connect 権限がない可能性があります。Cloud9 コンソール → 環境設定 → IAM ロールを確認するか、Cloud9 の AWS Managed Temporary Credentials を使用してください。Phase 2-6: パスワードレス接続テスト
生成したトークンを使って RDS Proxy 経由で Aurora に接続します。
# SSL 証明書のダウンロード(ap-northeast-1)
wget https://truststore.pki.rds.amazonaws.com/ap-northeast-1/ap-northeast-1-bundle.pem
# IAM トークン(再生成)
PROXY_ENDPOINT="aurora-no-human-login-proxy.proxy-xxxx.ap-northeast-1.rds.amazonaws.com"
TOKEN=$(aws rds generate-db-auth-token \
--hostname $PROXY_ENDPOINT \
--port 5432 \
--username iam_terraform_user \
--region ap-northeast-1)
# パスワードレス接続
PGPASSWORD=$TOKEN psql \
"host=$PROXY_ENDPOINT \
port=5432 \
user=iam_terraform_user \
dbname=appdb \
sslmode=require \
sslrootcert=ap-northeast-1-bundle.pem"
接続に成功したら以下が表示されます:
psql (15.x, server 16.x)
SSL connection (protocol: TLSv1.3, cipher: ..., bits: 256, compression: off)
Type "help" for help.
appdb=>
-- 現在のユーザーとバージョンを確認
SELECT current_user, version();
期待される出力:
current_user | version
--------------------+---------
iam_terraform_user | PostgreSQL 16.x ...
(1 row)
Phase 2-7: RDS Proxy 経由の接続確認と設定まとめ
ここまでの設定を整理します。
設定済みリソース一覧
| リソース | 名前/値 | 備考 |
|---|---|---|
| VPC | aurora-db-vpc | CIDR: 10.0.0.0/16 |
| Public Subnet 1a | aurora-public-1a | 10.0.0.0/24 |
| Public Subnet 1c | aurora-public-1c | 10.0.64.0/24 |
| Private Subnet 1a | aurora-private-1a | 10.0.128.0/24 |
| Private Subnet 1c | aurora-private-1c | 10.0.192.0/24 |
| Aurora Cluster | aurora-no-human-login-cluster | PostgreSQL 16, Serverless v2 |
| RDS Proxy | aurora-no-human-login-proxy | IAM: Required |
| Secrets Manager | aurora-no-human-login/master | 自動ローテーション |
| IAM Policy | aurora-iam-db-access | rds-db:connect |
| IAM Role (TF) | aurora-terraform-role | EC2 用 |
| IAM Role (Lambda) | aurora-lambda-role | Lambda 用 |
| DB User | iam_terraform_user | rds_iam ロール付き |
| DB User | iam_lambda_user | rds_iam ロール付き |
接続フローのまとめ
Client (Lambda/Terraform)
↓ generate_db_auth_token (AWS STS)
↓ 15分有効なIAMトークン取得
↓ SSL接続 (user=iam_*_user, password=token)
RDS Proxy
↓ IAMトークンを検証
↓ Secrets Managerからmaster認証情報取得
↓ 接続プール管理
Aurora PostgreSQL
トラブルシューティング: Phase 1-2 よくあるエラー
エラー: PAM authentication failed for user "iam_terraform_user"
原因: RDS Proxy が IAM 認証モードなのに、psql コマンドに SSL 設定が不足している。
対処: sslmode=require を必ず指定する。
# NG: SSL なし
psql "host=$PROXY_ENDPOINT user=iam_terraform_user dbname=appdb"
# OK: SSL あり
PGPASSWORD=$TOKEN psql "host=$PROXY_ENDPOINT user=iam_terraform_user dbname=appdb sslmode=require"
エラー: FATAL: IAM authentication is not allowed for user "iam_terraform_user"
原因: Aurora クラスターの IAM DB Authentication が無効。
対処: RDS コンソール → クラスター → 「Modify」→ 「Database authentication」→「Password and IAM database authentication」に変更して Apply。
エラー: connection timeout または could not connect to server
原因: セキュリティグループのルールが誤っている、またはサブネットの設定ミス。
対処:
1. lambda-sg の Outbound: rds-proxy-sg へのポート 5432 が許可されているか確認
2. rds-proxy-sg の Inbound: lambda-sg からのポート 5432 が許可されているか確認
3. RDS Proxy が aurora-private-1a と aurora-private-1c の両サブネットにデプロイされているか確認
エラー: The provided token has expired
原因: IAM トークンの有効期限(15 分)が切れた。
対処: トークンを再生成してから接続する。アプリケーションコードでは、接続前に毎回 generate_db_auth_token を呼び出すのがベストプラクティスです。
# 毎回トークンを再生成(スクリプト化するとよい)
TOKEN=$(aws rds generate-db-auth-token \
--hostname $PROXY_ENDPOINT \
--port 5432 \
--username iam_terraform_user \
--region ap-northeast-1)
7. Phase 3: Terraform postgresql provider でスキーマ管理
このPhaseでは、cyrilgdn/postgresql Terraform providerを使い、AuroraのDB・スキーマ・ロール・権限・拡張機能をコードで宣言的に管理します。これにより、terraform plan でスキーマ変更差分が可視化され、Pull Requestレビューと組み合わせたガバナンスが実現します。
| ステップ | 内容 |
|---|---|
| 7-1 | cyrilgdn/postgresql provider の設定 |
| 7-2 | postgresql_database — データベース作成 |
| 7-3 | postgresql_role — ロール/ユーザー作成 |
| 7-4 | postgresql_schema — スキーマ作成 |
| 7-5 | postgresql_grant — 権限付与(GRANT) |
| 7-6 | postgresql_default_privileges — デフォルト権限 |
| 7-7 | postgresql_extension — 拡張機能 |
| 7-8 | terraform apply でスキーマ管理 |
| 7-9 | スキーマ変更の差分管理 |
7-1. cyrilgdn/postgresql provider の設定
cyrilgdn/postgresql は、PostgreSQL リソース(DB・スキーマ・ロール・権限)を Terraform リソースとして管理するサードパーティproviderです。AWS IAM認証に対応しており、scheme = "awspostgres" と aws_rds_iam_auth = true を指定することでパスワードなしのIAM認証が自動的に使用されます。
providers.tf に追加
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
postgresql = {
source = "cyrilgdn/postgresql"
version = "~> 1.26.0"
}
}
}
provider "postgresql" {
scheme = "awspostgres"
host= aws_db_proxy.main.endpoint
port= 5432
database = "postgres"
username = "iam_terraform_user"
superuser = false
aws_rds_iam_auth= true
aws_rds_iam_region = "ap-northeast-1"
# IAMロールARN(Terraform実行環境のロール)
# aws_rds_iam_provider_role_arn = var.terraform_role_arn # assume role使用時
}
scheme = “awspostgres” と aws_rds_iam_auth = true の組み合わせが重要です。
このprovider設定でパスワードなしのIAM認証トークンが自動的に生成・使用されます。
username には Section 6 で作成した iam_terraform_user を指定してください。前提: Terraform実行環境のIAMロールに必要な権限
Terraform CLIを実行するIAMロール(または実行ユーザー)には、以下のポリシーが必要です。
# iam.tf (抜粋)
resource "aws_iam_policy" "terraform_pg_connect" {
name = "terraform-pg-connect"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect= "Allow"
Action= "rds-db:connect"
Resource = "arn:aws:rds-db:ap-northeast-1:${data.aws_caller_identity.current.account_id}:dbuser:${aws_db_proxy.main.id}/iam_terraform_user"
}]
})
}
cyrilgdn/postgresql provider は
terraform apply 時に Aurora(またはRDS Proxy)へ直接TCP接続します。CI/CD環境(CodeBuild、GitHub Actions self-hosted runner等)では、必ずVPC内(プライベートサブネット)から実行してください。ローカル開発環境の場合は SSM Session Manager Port Forwarding でVPNトンネルを確立してから実行します。
7-2. postgresql_database — データベース作成
postgresql_database リソースでアプリケーション用のデータベースを作成します。
postgresql.tf
resource "postgresql_database" "app_db" {
name = "app_database"
owner = postgresql_role.app_owner.name
encoding = "UTF8"
lc_collate = "C"
lc_ctype = "C"
connection_limit = -1
allow_connections = true
}
| パラメータ | 値 | 説明 |
|---|---|---|
name | app_database | 作成するDB名 |
owner | app_owner_role | DBオーナーロール(後述の 7-3 で定義) |
encoding | UTF8 | 文字コード |
lc_collate | C | ソート順(Cは最速) |
lc_ctype | C | 文字分類(Cは最速) |
connection_limit | -1 | 無制限(-1) |
lc_collate = "C" はロケール依存のソートを行わないため、パフォーマンスが最速です。日本語テキストのソートが必要な場合のみ
"ja_JP.UTF-8" を検討してください。ただしAurora PostgreSQLではサポートするロケールに制限がある点に注意してください。7-3. postgresql_role — ロール/ユーザー作成
PostgreSQLのロール設計は「ロール(権限の束)」と「ログインユーザー(IAMユーザー)」を分離するのがベストプラクティスです。アプリケーションコードから直接ロールにログインするのではなく、IAMユーザー(例: iam_lambda_user)に対してロールをGRANTして使います。
# アプリ書き込みロール
resource "postgresql_role" "app_writer" {
name = "app_writer_role"
login= false # ロールとして使用(直接ログイン不可)
connection_limit = -1
}
# アプリ読み取り専用ロール
resource "postgresql_role" "app_reader" {
name = "app_reader_role"
login= false
connection_limit = -1
}
# アプリオーナーロール(DB所有者・スキーマ作成権限)
resource "postgresql_role" "app_owner" {
name = "app_owner_role"
login= false
createdb= true
connection_limit = -1
}
login = false のロールは直接ログインできません。
Lambda用IAMユーザー(
iam_lambda_user)やTerraform用IAMユーザー(iam_terraform_user)に対して GRANT ROLE で付与して使います。これにより「IAMユーザー単位の認証」と「PostgreSQL権限の管理」を分離できます。IAMユーザーへのロール付与
iam_lambda_user に app_writer_role を付与する例です。postgresql_grant の object_type = "role" を使います。
# iam_lambda_user に app_writer_role を付与
resource "postgresql_grant" "lambda_writer_role" {
database = postgresql_database.app_db.name
role = "iam_lambda_user"
object_type = "role"
privileges = []
# NOTE: object_type = "role" の場合、privileges は使用されない
# GRANT ROLE は postgresql_grant ではなく
# postgresql_grant_role リソースを使うことを推奨
}
# 推奨: postgresql_grant_role を使う場合
# resource "postgresql_grant_role" "lambda_writer" {
#role = "iam_lambda_user"
#grant_role = postgresql_role.app_writer.name
# }
iam_lambda_user、iam_terraform_user は Section 6 の手順(CREATE USER ... WITH LOGIN + GRANT rds_iam)で事前に作成済みである必要があります。cyrilgdn/postgresql provider はこれらのIAMユーザーをTerraform管理外のリソースとして参照します。
7-4. postgresql_schema — スキーマ作成
postgresql_schema リソースでアプリケーション用のスキーマを作成し、ロールごとのUSAGE権限をインラインで設定します。
resource "postgresql_schema" "app" {
name = "app"
database = postgresql_database.app_db.name
owner = postgresql_role.app_owner.name
policy {
usage = true
role = postgresql_role.app_writer.name
}
policy {
usage = true
role = postgresql_role.app_reader.name
}
}
| パラメータ | 説明 |
|---|---|
name | スキーマ名(app) |
database | 対象DB(app_database) |
owner | スキーマオーナー(app_owner_role) |
policy.usage | USAGEを付与するか |
policy.role | 権限付与対象のロール |
PostgreSQL 15以降、
public スキーマへのデフォルト権限は廃止されました。アプリケーション用に専用スキーマ(この例では
app)を作成し、明示的に権限管理することを強く推奨します。search_path を app に設定することで、app. プレフィックスなしでテーブルにアクセスできます。7-5. postgresql_grant — 権限付与(GRANT)
postgresql_grant リソースでスキーマ・テーブルへの権限を付与します。object_type ごとに別リソースを定義する必要があります。
# app_writer ロールにスキーマ使用権限
resource "postgresql_grant" "writer_schema" {
database = postgresql_database.app_db.name
role = postgresql_role.app_writer.name
schema= postgresql_schema.app.name
object_type = "schema"
privileges = ["USAGE", "CREATE"]
}
# app_reader ロールにスキーマ使用権限
resource "postgresql_grant" "reader_schema" {
database = postgresql_database.app_db.name
role = postgresql_role.app_reader.name
schema= postgresql_schema.app.name
object_type = "schema"
privileges = ["USAGE"]
}
# app_writer ロールにテーブル権限(読み書き)
resource "postgresql_grant" "writer_table" {
database = postgresql_database.app_db.name
role = postgresql_role.app_writer.name
schema= postgresql_schema.app.name
object_type = "table"
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
# app_reader ロールにテーブル参照権限
resource "postgresql_grant" "reader_table" {
database = postgresql_database.app_db.name
role = postgresql_role.app_reader.name
schema= postgresql_schema.app.name
object_type = "table"
privileges = ["SELECT"]
}
object_type ごとの権限マッピング
| object_type | 利用可能な privileges | 用途 |
|---|---|---|
schema | USAGE, CREATE | スキーマへのアクセス許可 |
table | SELECT, INSERT, UPDATE, DELETE, TRUNCATE | テーブル操作 |
sequence | USAGE, SELECT, UPDATE | シーケンス操作 |
function | EXECUTE | 関数実行 |
database | CONNECT, TEMPORARY, CREATE | DB接続 |
privileges を変更すると、Terraformは既存の GRANT を REVOKE してから再 GRANT します(destroy + create)。本番環境でテーブル権限を変更する場合は、
terraform plan で差分を事前確認し、アプリケーションへの影響を考慮してから terraform apply を実行してください。7-6. postgresql_default_privileges — デフォルト権限
postgresql_grant はすでに存在するテーブルへの権限付与です。後から作成されるテーブルにも自動的に権限を付与するには postgresql_default_privileges を使います。
# app_owner が作成する新規テーブルに自動で app_writer の権限を付与
resource "postgresql_default_privileges" "writer_tables" {
database = postgresql_database.app_db.name
schema= postgresql_schema.app.name
owner = postgresql_role.app_owner.name
object_type = "table"
role = postgresql_role.app_writer.name
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
# app_owner が作成する新規テーブルに自動で app_reader の権限を付与
resource "postgresql_default_privileges" "reader_tables" {
database = postgresql_database.app_db.name
schema= postgresql_schema.app.name
owner = postgresql_role.app_owner.name
object_type = "table"
role = postgresql_role.app_reader.name
privileges = ["SELECT"]
}
# シーケンスへのデフォルト権限(SERIAL/BIGSERIAL型使用時)
resource "postgresql_default_privileges" "writer_sequences" {
database = postgresql_database.app_db.name
schema= postgresql_schema.app.name
owner = postgresql_role.app_owner.name
object_type = "sequence"
role = postgresql_role.app_writer.name
privileges = ["USAGE", "SELECT", "UPDATE"]
}
postgresql_default_privileges を設定しないと、後から作成したテーブルには権限が自動付与されません。
Lambdaマイグレーション(Section 8)や
null_resource + local-exec で新規テーブルを作成するたびに、手動で GRANT を実行する必要が生じます。必ずデフォルト権限を設定してください。SQLでの確認方法
terraform apply 後、以下のSQLでデフォルト権限が正しく設定されているか確認できます。
-- デフォルト権限の確認
SELECT
pg_get_userbyid(d.defaclrole) AS "owner",
pg_get_userbyid(acl.grantee) AS "grantee",
acl.privilege_type,
d.defaclobjtype AS "object_type"
FROM
pg_default_acl d,
aclexplode(d.defaclacl) AS acl
WHERE
d.defaclnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'app');
7-7. postgresql_extension — 拡張機能
Aurora PostgreSQLで利用可能な拡張機能をTerraformで管理します。
# UUID生成(uuid_generate_v4() 関数)
resource "postgresql_extension" "uuid_ossp" {
name = "uuid-ossp"
database = postgresql_database.app_db.name
schema= "public"
}
# 暗号化(crypt()、pgp_sym_encrypt() 等)
resource "postgresql_extension" "pgcrypto" {
name = "pgcrypto"
database = postgresql_database.app_db.name
schema= "public"
}
Aurora PostgreSQL でよく使われる拡張機能一覧
| 拡張機能名 | 用途 | Terraform resource |
|---|---|---|
uuid-ossp | UUID生成(uuid_generate_v4()) | postgresql_extension.uuid_ossp |
pgcrypto | 暗号化・ハッシュ | postgresql_extension.pgcrypto |
pg_stat_statements | クエリ統計(パフォーマンス分析) | postgresql_extension.pg_stat_statements |
pg_trgm | 部分一致検索(LIKE最適化) | postgresql_extension.pg_trgm |
postgis | 地理情報(GIS) | postgresql_extension.postgis |
vector | pgvector(AI埋め込みベクトル) | postgresql_extension.vector |
すべての拡張機能がAurora PostgreSQLで利用できるわけではありません。
サポート一覧は AWS公式ドキュメント を参照してください。
pg_stat_statements は Aurora のパラメータグループで shared_preload_libraries に追加が必要です。7-8. terraform apply でスキーマ管理
provider設定とリソース定義が完了したら、terraform init → terraform plan → terraform apply の順で実行します。
初回実行手順
cd terraform/
# プロバイダーをダウンロード(cyrilgdn/postgresql を含む)
terraform init
# 差分確認(何が作成・変更・削除されるか確認)
terraform plan
# 適用
terraform apply
terraform plan の出力例
Terraform will perform the following actions:
# postgresql_database.app_db will be created
+ resource "postgresql_database" "app_db" {
+ name = "app_database"
+ owner= "app_owner_role"
+ encoding= "UTF8"
+ lc_collate = "C"
+ lc_ctype= "C"
+ connection_limit = -1
}
# postgresql_schema.app will be created
+ resource "postgresql_schema" "app" {
+ name = "app"
+ database = "app_database"
+ owner = "app_owner_role"
+ policy {
+ role = "app_writer_role"
+ usage = true
}
+ policy {
+ role = "app_reader_role"
+ usage = true
}
}
# postgresql_role.app_writer will be created
+ resource "postgresql_role" "app_writer" {
+ name = "app_writer_role"
+ login= false
+ connection_limit = -1
}
# postgresql_grant.writer_table will be created
+ resource "postgresql_grant" "writer_table" {
+ database = "app_database"
+ role = "app_writer_role"
+ schema= "app"
+ object_type = "table"
+ privileges = ["DELETE", "INSERT", "SELECT", "UPDATE"]
}
# postgresql_extension.uuid_ossp will be created
+ resource "postgresql_extension" "uuid_ossp" {
+ name = "uuid-ossp"
+ database = "app_database"
+ schema= "public"
}
Plan: 12 to add, 0 to change, 0 to destroy.
実行ログの確認
# apply後、tfstateで管理対象リソースを確認
terraform state list | grep postgresql
# 出力例:
# postgresql_database.app_db
# postgresql_default_privileges.reader_tables
# postgresql_default_privileges.writer_sequences
# postgresql_default_privileges.writer_tables
# postgresql_extension.pgcrypto
# postgresql_extension.uuid_ossp
# postgresql_grant.reader_schema
# postgresql_grant.reader_table
# postgresql_grant.writer_schema
# postgresql_grant.writer_table
# postgresql_role.app_owner
# postgresql_role.app_reader
# postgresql_role.app_writer
# postgresql_schema.app
Terraformはリソース間の依存関係(
depends_on や参照)を自動解析し、正しい順序で作成します。たとえば
postgresql_schema.app は postgresql_role.app_owner を参照しているため、ロールが先に作成されてからスキーマが作成されます。明示的な depends_on の記述は原則不要です。7-9. スキーマ変更の差分管理
Terraform postgresql providerはスキーマ(Schema)・ロール・権限・拡張機能の宣言的管理に対応していますが、テーブルレベルのDDL(ALTER TABLE等)には対応していません。テーブル変更は Section 8 のLambdaマイグレーション、またはnull_resource + local-exec で実施します。
対応パターン: テーブルの管理方法
| 操作 | 手段 | Terraform対応 |
|---|---|---|
| スキーマ作成・変更 | postgresql_schema | ✅ 対応 |
| ロール作成・変更 | postgresql_role | ✅ 対応 |
| 権限付与・変更 | postgresql_grant | ✅ 対応(変更はdestroy+create) |
| 拡張機能管理 | postgresql_extension | ✅ 対応 |
| テーブル作成 | postgresql_function + null_resource | ⚠️ 間接対応 |
| カラム追加 | Lambda(Section 8)または null_resource | ❌ 直接不可 |
| インデックス追加 | Lambda(Section 8)または null_resource | ❌ 直接不可 |
null_resource + local-exec を使ったカラム追加の例
Terraformからpsqlコマンドを実行してALTER TABLEを行う代替パターンです。IAM認証トークンを動的に生成して使用します。
# 一例: usersテーブルに email_verified カラムを追加
resource "null_resource" "add_email_verified_column" {
# トリガー: この値が変わるたびに再実行される
triggers = {
migration_version = "v1_add_email_verified"
}
provisioner "local-exec" {
command = <<-EOF
# IAM認証トークンを生成(有効期間15分)
TOKEN=$(aws rds generate-db-auth-token \
--hostname ${aws_db_proxy.main.endpoint} \
--port 5432 \
--region ap-northeast-1 \
--username iam_terraform_user)
# psql で ALTER TABLE 実行
PGPASSWORD="$TOKEN" psql \
"host=${aws_db_proxy.main.endpoint} \
port=5432 \
dbname=app_database \
user=iam_terraform_user \
sslmode=require" \
-c "ALTER TABLE app.users ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE;"
EOF
}
}
Terraform postgresql provider はスキーマ・ロール・権限・拡張機能の管理に特化しており、テーブルレベルのDDL(
CREATE TABLE・ALTER TABLE・インデックス等)には対応していません。カラム変更は Lambdaマイグレーション(Section 8) または
null_resource + local-exec + psql コマンドで実施します。大規模なスキーマ変更管理が必要な場合は Flyway on CodeBuild(Section 11-2)の導入も検討してください。terraform plan による差分可視化
スキーマ変更をGitHubでレビューするワークフロー例です。
# 1. ブランチを切る
git checkout -b feat/add-app-schema-v2
# 2. postgresql.tf を変更(例: 新しいロールを追加)
# resource "postgresql_role" "app_analytics" { ... }
# 3. terraform plan で差分確認
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary | jq '.resource_changes[] | {address, action: .change.actions}'
# 出力例:
# {
#"address": "postgresql_role.app_analytics",
#"action": ["create"]
# }
# 4. Pull Requestを作成(terraform plan の出力をPRコメントに貼付)
git add postgresql.tf
git commit -m "feat: add app_analytics role for reporting"
git push origin feat/add-app-schema-v2
# → GitHub PR作成 → レビュー → マージ → terraform apply
GitHub Actions の
hashicorp/setup-terraform アクションと github-script を組み合わせることで、PR作成時に terraform plan の出力を自動コメントできます。これにより、レビュアーはコード差分とインフラ差分を同時に確認でき、「意図しない削除」や「権限の変更」を事前に発見できます。CI/CDとの統合はSection 11-4(GitHub Actions OIDC連携)で詳しく解説します。
Section 7 まとめ
| リソース | 役割 |
|---|---|
postgresql_database | アプリケーション用DB作成 |
postgresql_role | ロール定義(writer/reader/owner) |
postgresql_schema | スキーマ作成 + USAGE権限 |
postgresql_grant | 既存オブジェクトへの権限付与 |
postgresql_default_privileges | 新規作成オブジェクトへの自動権限付与 |
postgresql_extension | 拡張機能の有効化 |
null_resource + local-exec | Terraform非対応DDL(ALTER TABLE等)の代替手段 |
次のSection 8では、Lambda + psycopg2 を使ったDDL実行(CREATE TABLE・CREATE INDEX等)と冪等マイグレーションの実装を解説します。
8. Phase 4: Lambda + psycopg2 で DDL 実行
Phase 3 では Terraform postgresql provider を使い、スキーマ・ロール・権限を IaC で管理しました。しかし Terraform provider が対応していない操作(ALTER TABLE、CREATE INDEX、データ投入、条件付き DDL)は別の手段が必要です。Phase 4 では Lambda 関数 + psycopg2 を使い、IAM 認証経由でこれらの DDL を実行します。
8-1. Lambda + psycopg2 パターンの概要
Terraform postgresql provider では以下の操作を直接管理できません:
| 操作 | 理由 |
|---|---|
ALTER TABLE(カラム追加・削除・型変更) | Terraform state との競合が発生しやすい |
CREATE INDEX | provider がインデックス管理リソースを未提供 |
INSERT / UPDATE などのデータ投入 | DDL ではなく DML のため対象外 |
IF NOT EXISTS チェック後の条件付き DDL | Terraform のべき等性モデルと相性が悪い |
これらを Lambda 関数から psycopg2 経由で実行することで、Terraform では管理しにくい操作を補完します。
Terraform postgresql provider → 宣言的なスキーマ・ロール・権限管理(CREATE DATABASE / SCHEMA / ROLE / GRANT)
Lambda + psycopg2 → 動的な DDL 実行(ALTER TABLE、インデックス作成、データ投入)
この 2 つを組み合わせることで、IaC による一元管理と柔軟な運用変更の両立が可能です。
8-2. psycopg2 Lambda Layer の作成
psycopg2 は C 拡張ライブラリを含むため、Lambda 実行環境(Amazon Linux 2023)に合わせた専用バイナリが必要です。ローカル環境で pip install psycopg2-binary しても Lambda では動作しません。Docker を使ってビルドします。
手順:
① Docker でバイナリをビルド
# 作業ディレクトリ作成
mkdir -p lambda-migration/layer/python
# Lambda 実行環境(Amazon Linux 2023)と同じコンテナでビルド
docker run --rm \
-v $(pwd)/lambda-migration/layer:/output \
public.ecr.aws/lambda/python:3.12 \
pip install psycopg2-binary -t /output/python/
# ビルド結果を確認
ls lambda-migration/layer/python/
② ZIP ファイル作成
cd lambda-migration/layer
zip -r psycopg2-layer.zip python/
cd ../..
# ファイルサイズ確認(5〜10 MB 程度)
ls -lh lambda-migration/layer/psycopg2-layer.zip
③ Lambda Layer として登録
aws lambda publish-layer-version \
--layer-name psycopg2-aurora \
--zip-file fileb://lambda-migration/layer/psycopg2-layer.zip \
--compatible-runtimes python3.12 \
--region ap-northeast-1
出力例:
{
"LayerVersionArn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:psycopg2-aurora:1",
"LayerArn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:psycopg2-aurora",
"Version": 1
}
LayerVersionArn の値を控えておきます。後続の Lambda 関数作成で使用します。
psycopg2(C ライブラリ依存)と psycopg2-binary(バイナリ同梱)の違いは、ビルド時の依存関係にあります。Lambda 環境では libpq などの C ライブラリが利用できないため、psycopg2-binary を使用します。また、Docker ビルドは Lambda の実行環境(Amazon Linux 2023)と同じ OS でビルドするため、バイナリ互換性が保証されます。ローカル Mac/Windows 環境での pip install は OS が異なるためネイティブバイナリが動作しません。8-3. Lambda 関数コードの作成
Lambda 関数は以下の役割を担います:
- IAM 認証トークンを生成(毎回新規生成、15 分間有効)
- RDS Proxy 経由で Aurora PostgreSQL に接続
- 未適用のマイグレーションを検出して実行
- べき等性を確保(
schema_migrationsテーブルで管理)
関数コード:
ローカルに lambda-migration/lambda_function.py を作成します。
import boto3
import psycopg2
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def get_connection():
"""IAM 認証トークンで Aurora に接続(RDS Proxy 経由)"""
rds_client = boto3.client('rds', region_name=os.environ['AWS_REGION'])
# IAM 認証トークンを生成(毎接続前に新規生成することが重要)
token = rds_client.generate_db_auth_token(
DBHostname=os.environ['RDS_PROXY_HOST'],
Port=5432,
DBUsername=os.environ['DB_USER'],
Region=os.environ['AWS_REGION']
)
conn = psycopg2.connect(
host=os.environ['RDS_PROXY_HOST'],
port=5432,
user=os.environ['DB_USER'],
password=token,
database=os.environ['DB_NAME'],
sslmode='require' # IAM 認証では TLS 必須
)
return conn
def lambda_handler(event, context):
"""べき等なマイグレーション実行ハンドラー"""
logger.info("Migration Lambda started")
conn = get_connection()
try:
with conn.cursor() as cur:
# バージョン管理テーブルの作成(べき等: 既存なら何もしない)
cur.execute("""
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMPTZ DEFAULT NOW(),
applied_by VARCHAR(255) DEFAULT current_user
)
""")
conn.commit()
# 未適用マイグレーションの取得と実行
migrations = get_pending_migrations(cur)
logger.info(f"Pending migrations: {len(migrations)}")
for migration in migrations:
apply_migration(cur, migration)
logger.info(f"Applied migration: {migration['version']}")
conn.commit() # マイグレーションごとにコミット
return {
'statusCode': 200,
'body': f'Applied {len(migrations)} migrations successfully'
}
except Exception as e:
conn.rollback()
logger.error(f"Migration failed: {e}")
raise
finally:
conn.close()
def get_pending_migrations(cur):
"""適用済みマイグレーション一覧を取得し、未適用分を返す"""
cur.execute("SELECT version FROM schema_migrations")
applied = {row[0] for row in cur.fetchall()}
logger.info(f"Already applied: {applied}")
all_migrations = [
{
'version': 'v001_create_users_table',
'sql': """
CREATE TABLE IF NOT EXISTS app.users (
idUUIDPRIMARY KEY DEFAULT gen_random_uuid(),
emailVARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)
"""
},
{
'version': 'v002_create_orders_table',
'sql': """
CREATE TABLE IF NOT EXISTS app.orders (
idUUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES app.users(id) ON DELETE CASCADE,
amount DECIMAL(10, 2) NOT NULL CHECK (amount >= 0),
status VARCHAR(50) DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW()
)
"""
},
{
'version': 'v003_add_index_users_email',
'sql': "CREATE INDEX IF NOT EXISTS idx_users_email ON app.users(email)"
},
{
'version': 'v004_add_index_orders_user_id',
'sql': "CREATE INDEX IF NOT EXISTS idx_orders_user_id ON app.orders(user_id)"
},
{
'version': 'v005_add_index_orders_created_at',
'sql': "CREATE INDEX IF NOT EXISTS idx_orders_created_at ON app.orders(created_at DESC)"
},
]
return [m for m in all_migrations if m['version'] not in applied]
def apply_migration(cur, migration):
"""マイグレーションを実行し、バージョンテーブルに記録"""
cur.execute(migration['sql'])
cur.execute(
"""
INSERT INTO schema_migrations (version)
VALUES (%s)
ON CONFLICT (version) DO NOTHING
""",
(migration['version'],)
)
schema_migrations テーブルで適用済みバージョンを管理することで、同じ Lambda を何度実行しても安全です。CREATE TABLE IF NOT EXISTS と CREATE INDEX IF NOT EXISTS も DDL レベルでのべき等性を保証します。マイグレーションごとにコミットすることで、途中失敗時のリカバリーも容易になります。8-4. Lambda 関数のデプロイ(コンソール)
① Lambda 関数の ZIP 作成
cd lambda-migration
zip -j migration-function.zip lambda_function.py
② Lambda コンソールで関数を作成
- AWS マネジメントコンソールで Lambda を開く
- 「関数の作成」をクリック
- 「一から作成」を選択し、以下を入力:
- 関数名:
aurora-migration - ランタイム:
Python 3.12 - アーキテクチャ:
x86_64 - 「デフォルトの実行ロールの変更」を展開:
- 「既存のロールを使用する」を選択
- 既存のロール:
aurora-lambda-role(Phase 2 で作成) - 「関数の作成」をクリック
③ コードのアップロード
- 関数の「コード」タブを開く
- 「アップロード元」→「.zip ファイル」を選択
migration-function.zipをアップロード- 「保存」をクリック
④ VPC 設定
- 「設定」タブ→「VPC」を選択
- 「編集」をクリック
- 以下を設定:
- VPC:
aurora-db-vpc(Phase 1 で作成) - サブネット: プライベートサブネット 2 つ(
ap-northeast-1a、ap-northeast-1c) - セキュリティグループ:
lambda-sg - 「保存」をクリック
⑤ 一般設定(タイムアウト変更)
- 「設定」タブ→「一般設定」→「編集」
- タイムアウト:
5分0秒(デフォルト 3 秒では不足) - メモリ:
256MB - 「保存」をクリック
⑥ 環境変数の設定
- 「設定」タブ→「環境変数」→「編集」
- 「環境変数を追加」で以下を設定:
| キー | 値 |
|---|---|
RDS_PROXY_HOST | {RDS Proxy エンドポイント}(Phase 1 で記録) |
DB_USER | iam_lambda_user |
DB_NAME | app_database |
- 「保存」をクリック
AWS_REGION は Lambda 実行環境が自動で設定する予約済み環境変数です。手動で追加しようとするとエラーになります。コード内では os.environ['AWS_REGION'] でそのまま参照できます。⑦ Layer の追加
- 「コード」タブの最下部にある「レイヤー」セクションを開く
- 「レイヤーの追加」をクリック
- 「カスタムレイヤー」を選択
- カスタムレイヤー:
psycopg2-aurora - バージョン:
1(8-2 で登録したもの) - 「追加」をクリック
8-5. EventBridge Scheduler での定期実行(任意)
マイグレーション Lambda を定期的に自動実行する場合は、EventBridge Scheduler を使います。べき等性が確保されているため、複数回実行しても問題ありません。
Terraform コード(lambda.tf に追記):
# EventBridge Scheduler 用 IAM ロール
resource "aws_iam_role" "scheduler" {
name = "aurora-migration-scheduler-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "scheduler.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "scheduler_invoke" {
name = "invoke-migration-lambda"
role = aws_iam_role.scheduler.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect= "Allow"
Action= "lambda:InvokeFunction"
Resource = aws_lambda_function.migration.arn
}]
})
}
# EventBridge Scheduler(毎日 AM 3:00 に実行)
resource "aws_scheduler_schedule" "migration" {
name = "aurora-migration-schedule"
group_name = "default"
flexible_time_window {
mode = "OFF"
}
# 毎日 JST 3:00(UTC 18:00)
schedule_expression = "cron(0 18 * * ? *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn= aws_lambda_function.migration.arn
role_arn = aws_iam_role.scheduler.arn
}
}
マイグレーション Lambda はべき等性が確保されているため、複数回実行しても安全です。
schema_migrations テーブルで適用済みバージョンをチェックするため、すでに適用済みの場合は 0 件の変更で正常終了します。定期実行を設定しておくと、新しいマイグレーションを追加した際に自動で適用されます。9. Phase 5: 動作確認とトラブルシューティング
Lambda 関数のデプロイが完了したら、動作確認を行います。Terraform apply の確認、Lambda のテスト実行、CloudWatch Logs の確認の順に進めます。
9-1. Terraform apply の動作確認
Phase 3 で作成した Terraform コード(postgresql provider)の適用結果を確認します。
確認コマンド:
# 全リソースの状態確認
terraform state list
# 出力値の確認
terraform output
出力例:
aurora_cluster_endpoint = "aurora-cluster.cluster-xxxx.ap-northeast-1.rds.amazonaws.com"
rds_proxy_endpoint= "aurora-proxy.proxy-xxxx.ap-northeast-1.rds.amazonaws.com"
DB 内の状態確認(CloudShell または SSM Port Forwarding 経由):
-- 作成されたデータベース一覧
SELECT datname FROM pg_database WHERE datname = 'app_database';
-- スキーマ一覧
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app';
-- ロール一覧(IAM 認証ユーザーを含む)
SELECT rolname, rolcanlogin FROM pg_roles
WHERE rolname LIKE '%app%' OR rolname LIKE '%iam%'
ORDER BY rolname;
-- 権限確認
SELECT grantee, table_schema, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee IN ('app_readonly', 'app_readwrite', 'iam_lambda_user')
ORDER BY grantee, table_schema, table_name;
期待する出力:
datname
-----------
app_database
(1 row)
schema_name
-------------
app
(1 row)
rolname | rolcanlogin
----------------------+-------------
app_readonly| t
app_readwrite | t
iam_lambda_user| t
iam_terraform_user| t
(4 rows)
9-2. Lambda 関数のテスト実行
Lambda コンソールからテスト:
- Lambda コンソールで
aurora-migration関数を開く - 「テスト」タブを選択
- 「テストイベントを作成」:
- イベント名:
migration-test - テンプレート:
hello-world - イベント JSON:
{}のまま - 「保存」→「テスト」をクリック
初回実行の期待結果:
{
"statusCode": 200,
"body": "Applied 5 migrations successfully"
}
2 回目以降(べき等性確認):
{
"statusCode": 200,
"body": "Applied 0 migrations successfully"
}
2 回目以降は全マイグレーションが適用済みとして検出され、0 件の変更で正常終了します。
9-3. CloudWatch Logs での確認
ログの確認手順:
- Lambda コンソール → 「モニタリング」タブ
- 「CloudWatch のログを表示」をクリック
- 最新のログストリームを開く
期待するログ出力(初回):
INFO Migration Lambda started
INFO Already applied: set()
INFO Pending migrations: 5
INFO Applied migration: v001_create_users_table
INFO Applied migration: v002_create_orders_table
INFO Applied migration: v003_add_index_users_email
INFO Applied migration: v004_add_index_orders_user_id
INFO Applied migration: v005_add_index_orders_created_at
DB でのテーブル確認:
-- マイグレーション適用履歴
SELECT version, applied_at, applied_by
FROM schema_migrations
ORDER BY applied_at;
-- テーブル一覧
SELECT tablename, tableowner
FROM pg_tables
WHERE schemaname = 'app'
ORDER BY tablename;
-- インデックス一覧
SELECT indexname, tablename, indexdef
FROM pg_indexes
WHERE schemaname = 'app'
ORDER BY tablename, indexname;
期待する出力:
version| applied_at | applied_by
------------------------------------+-------------------------------+--------------
v001_create_users_table| 2026-04-14 09:00:01.123+00 | iam_lambda_user
v002_create_orders_table | 2026-04-14 09:00:01.456+00 | iam_lambda_user
v003_add_index_users_email| 2026-04-14 09:00:01.789+00 | iam_lambda_user
v004_add_index_orders_user_id| 2026-04-14 09:00:02.012+00 | iam_lambda_user
v005_add_index_orders_created_at| 2026-04-14 09:00:02.234+00 | iam_lambda_user
(5 rows)
tablename | tableowner
-----------+--------------
orders | iam_lambda_user
users | iam_lambda_user
(2 rows)
9-4. よくあるエラーと対処法
エラー 1: IAM 認証トークン期限切れ
FATAL: PAM authentication failed for user "iam_lambda_user"
原因: IAM 認証トークンの有効期限(15 分)が切れている。Lambda 関数内でトークンをキャッシュしている場合に発生します。
対処法:
Lambda 関数のグローバルスコープ(ハンドラー外)でトークンを生成すると、同一コンテナの再利用時に期限切れトークンが使われます。
get_connection() 関数内で毎回 generate_db_auth_token() を呼び出す設計にしてください。コネクションプールは使わず、ハンドラー実行ごとに接続・切断する設計が安全です。# NG: グローバルスコープでトークン生成(期限切れになる)
token = rds_client.generate_db_auth_token(...) # Lambda コンテナ再利用時に15分で無効化
# OK: ハンドラー内または接続関数内で毎回生成
def get_connection():
token = rds_client.generate_db_auth_token(...) # 毎回新規生成
return psycopg2.connect(password=token, ...)
エラー 2: セキュリティグループの設定ミス
[Errno 110] Connection timed out
原因: Lambda のセキュリティグループ(lambda-sg)から RDS Proxy のセキュリティグループ(rds-proxy-sg)へのポート 5432 通信が許可されていない。
対処法:
# lambda-sg の ID を確認
aws ec2 describe-security-groups \
--filters Name=group-name,Values=lambda-sg \
--query 'SecurityGroups[0].GroupId' \
--output text
# rds-proxy-sg の ID を確認
aws ec2 describe-security-groups \
--filters Name=group-name,Values=rds-proxy-sg \
--query 'SecurityGroups[0].GroupId' \
--output text
# rds-proxy-sg に inbound ルールを追加(lambda-sg からのポート 5432)
aws ec2 authorize-security-group-ingress \
--group-id <rds-proxy-sg-id> \
--protocol tcp \
--port 5432 \
--source-group <lambda-sg-id>
RDS Proxy は Aurora クラスターのセキュリティグループとは別のセキュリティグループを持ちます。Lambda → RDS Proxy → Aurora の通信経路で、それぞれのセキュリティグループにインバウンドルールが必要です:
rds-proxy-sg:
lambda-sg からのポート 5432 を許可aurora-sg:
rds-proxy-sg からのポート 5432 を許可エラー 3: RDS Proxy の IAM 認証拒否
SSL connection required for IAM authentication
原因: psycopg2.connect() に sslmode='require' が設定されていない。IAM 認証では TLS が必須です。
対処法: 接続コードに sslmode='require' を追加します。
conn = psycopg2.connect(
host=os.environ['RDS_PROXY_HOST'],
port=5432,
user=os.environ['DB_USER'],
password=token,
database=os.environ['DB_NAME'],
sslmode='require' # 必須: IAM 認証には TLS が必要
)
エラー 4: Terraform postgresql provider 接続エラー
Error: Error connecting to PostgreSQL server
on postgresql.tf line 1, in provider "postgresql":
Configuration for provider "postgresql" is invalid
原因: Terraform 実行環境の IAM ロールに rds-db:connect 権限がない。
対処法:
# Terraform 実行ロールのポリシーを確認
aws iam list-attached-role-policies --role-name aurora-terraform-role
# 権限がない場合はポリシーをアタッチ
aws iam attach-role-policy \
--role-name aurora-terraform-role \
--policy-arn arn:aws:iam::{ACCOUNT_ID}:policy/aurora-iam-db-access
aurora-iam-db-access ポリシーの内容(Phase 2 で作成):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:ap-northeast-1:{ACCOUNT_ID}:dbuser:*/*"
}]
}
エラー 5: VPC Lambda タイムアウト(STS API 疎通不可)
Task timed out after 300.00 seconds
ログには以下のようなメッセージ:
botocore.exceptions.EndpointConnectionError: Could not connect to the endpoint URL: "https://sts.ap-northeast-1.amazonaws.com/"
原因: VPC 内の Lambda が STS API(IAM 認証トークン生成に必要)に接続できない。NAT Gateway もなく、VPC エンドポイントも未設定の場合に発生します。
対処法: STS 用の VPC エンドポイントを作成します。
# VPC ID を取得
VPC_ID=$(aws ec2 describe-vpcs \
--filters Name=tag:Name,Values=aurora-db-vpc \
--query 'Vpcs[0].VpcId' \
--output text)
# プライベートサブネット ID を取得
SUBNET_IDS=$(aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$VPC_ID Name=tag:Type,Values=private \
--query 'Subnets[*].SubnetId' \
--output text | tr '\t' ',')
# STS VPC エンドポイント作成
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.ap-northeast-1.sts \
--vpc-endpoint-type Interface \
--subnet-ids $SUBNET_IDS \
--security-group-ids <lambda-sg-id>
NAT Gateway を使わない場合、以下の VPC エンドポイントが必要です:
com.amazonaws.ap-northeast-1.sts — IAM 認証トークン生成com.amazonaws.ap-northeast-1.secretsmanager — Secrets Manager 参照(必要な場合)com.amazonaws.ap-northeast-1.logs — CloudWatch Logs 書き込みcom.amazonaws.ap-northeast-1.lambda — Lambda 関数の呼び出し(別 Lambda を呼ぶ場合)9-5. CloudWatch Metrics でのモニタリング
Lambda + Aurora の動作状態を確認するために、以下のメトリクスを監視します。
Lambda メトリクス(CloudWatch → Lambda → aurora-migration):
| メトリクス | 確認内容 | 正常値の目安 |
|---|---|---|
Duration | 実行時間 | 初回: 5〜15 秒、2 回目以降: 1〜3 秒 |
Errors | エラー数 | 0 |
Throttles | スロットリング数 | 0 |
ConcurrentExecutions | 同時実行数 | 1(マイグレーションは直列実行推奨) |
RDS Proxy メトリクス(CloudWatch → RDS → aurora-proxy):
| メトリクス | 確認内容 | 正常値の目安 |
|---|---|---|
DatabaseConnections | DB 接続数 | Lambda 実行中: 1、アイドル: 0 |
ClientConnections | クライアント接続数 | Lambda 実行中: 1 |
QueryCount | クエリ実行数 | マイグレーション数 + α |
FailedConnections | 失敗接続数 | 0 |
Aurora メトリクス(CloudWatch → RDS → aurora-cluster):
| メトリクス | 確認内容 | 正常値の目安 |
|---|---|---|
ServerlessDatabaseCapacity | ACU(処理能力単位) | 0.5〜2 ACU(アイドル〜軽負荷) |
DatabaseConnections | DB 接続数 | 通常 1〜3(RDS Proxy 経由) |
CPUUtilization | CPU 使用率 | 通常 10% 以下 |
本番環境では以下のアラームを設定することを推奨します:
Lambda Errors > 0 → SNS 通知(マイグレーション失敗の即時検知)
RDS Proxy FailedConnections > 0 → SNS 通知(IAM 認証失敗の検知)
Aurora CPUUtilization > 80% → SNS 通知(リソース不足の検知)
9-6. 動作確認完了の確認チェックリスト
Phase 4〜5 の動作確認が完了したら、以下の項目をすべて確認してください。
Terraform postgresql provider(Phase 3):
- [ ]
terraform state listで postgresql_* リソースが表示される - [ ]
app_databaseデータベースが作成されている - [ ]
appスキーマが作成されている - [ ]
app_readonly、app_readwrite、iam_lambda_user、iam_terraform_userロールが存在する - [ ]
GRANT権限が正しく設定されている(information_schema.role_table_grantsで確認)
Lambda + psycopg2(Phase 4〜5):
- [ ] Lambda 関数
aurora-migrationがデプロイされている - [ ] VPC 設定(プライベートサブネット +
lambda-sg)が正しい - [ ] 環境変数(
RDS_PROXY_HOST、DB_USER、DB_NAME)が設定されている - [ ] Layer(
psycopg2-aurora:1)が追加されている - [ ] テスト実行で
Applied 5 migrations successfullyが返る - [ ] 2 回目のテスト実行で
Applied 0 migrations successfullyが返る(べき等性) - [ ] CloudWatch Logs にマイグレーション実行ログが出力されている
- [ ]
schema_migrationsテーブルに 5 件のレコードが存在する - [ ]
app.users、app.ordersテーブルが作成されている - [ ] インデックスが 3 件作成されている(
pg_indexesで確認)
Section 10: Phase 6 — Terraform IaC 全リソース
Section 5〜9のコンソール・手動ハンズオンで構築した全リソースを、Terraform で再現可能な IaC として整理します。terraform apply 一発で Aurora PostgreSQL Serverless v2 + RDS Proxy + IAM認証 + Lambdaマイグレーション環境を構築できます。
Terraform 1.5以上・AWS CLI v2設定済み・必要なIAM権限(VPC/RDS/Lambda/IAM/SecretsManager の作成・管理権限)が必要です。コンソールハンズオン(Section 5〜9)を先に読んでおくと各リソースの役割を理解しやすくなります。
10-1. ディレクトリ構成
プロジェクトディレクトリ terraform/ を作成し、以下の構成にします。
terraform/
├── providers.tf # Providerバージョン固定 + PostgreSQL Provider設定
├── variables.tf # 変数定義
├── vpc.tf # ネットワーク基盤(VPC/サブネット/NAT/SG/VPCエンドポイント)
├── aurora.tf # Aurora PostgreSQL Serverless v2 + Secrets Manager
├── rds_proxy.tf # RDS Proxy(IAM認証 REQUIRED)
├── iam.tf # IAMロール/ポリシー(RDS Proxy用・Lambda用・Terraform PG用)
├── postgresql.tf# cyrilgdn/postgresql provider(DB/スキーマ/ロール/権限)
├── lambda.tf # マイグレーションLambda + psycopg2 Layer + EventBridge Scheduler
└── outputs.tf# 出力値
mkdir terraform
cd terraform
10-2. providers.tf — Provider設定
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
postgresql = {
source = "cyrilgdn/postgresql"
version = "~> 1.26.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# PostgreSQL Providerは RDS Proxy エンドポイント経由で IAM認証接続
# Aurora クラスターが起動済み・RDS Proxyが作成済みの状態で初めて有効になる
provider "postgresql" {
scheme = "awspostgres"
host= aws_db_proxy.main.endpoint
port= 5432
database = "postgres"
username = "iam_terraform_user"
superuser = false
aws_rds_iam_auth= true
aws_rds_iam_region = var.aws_region
}
providers.tf の provider "postgresql" ブロックは、Aurora クラスターおよび RDS Proxy の作成後でないと接続できません。初回デプロイは -target オプションで段階的に適用してください(10-9 デプロイ手順を参照)。
10-3. variables.tf — 変数定義
variable "aws_region" {
description = "AWSリージョン"
default = "ap-northeast-1"
}
variable "project_name" {
description = "プロジェクト名(リソース名プレフィックス)"
default = "aurora-no-human-login"
}
variable "vpc_cidr" {
description = "VPC CIDR ブロック"
default = "10.0.0.0/16"
}
variable "db_name" {
description = "Aurora クラスターのデータベース名"
default = "app_database"
}
variable "aurora_master_username" {
description = "Aurora マスターユーザー名"
default = "postgres"
sensitive= true
}
variable "aurora_master_password" {
description = "Aurora マスターパスワード(terraform.tfvars で管理)"
sensitive= true
}
aurora_master_password は terraform.tfvars に記載し、.gitignore に追加してください。本番環境では AWS Secrets Manager または HashiCorp Vault 経由での管理を推奨します。
terraform.tfvars(.gitignore に追加すること):
aurora_master_password = "YourSecurePassword123!"
.gitignore:
*.tfvars
*.tfstate
*.tfstate.backup
.terraform/
.terraform.lock.hcl
10-4. vpc.tf — ネットワーク基盤
VPC・サブネット(パブリック×2 / プライベート×2)・NAT Gateway・セキュリティグループ・VPCエンドポイントを定義します。
# ─────────────────────────────────────────
# データソース: AZリスト
# ─────────────────────────────────────────
data "aws_availability_zones" "available" {
state = "available"
}
# ─────────────────────────────────────────
# VPC
# ─────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support= true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-vpc"
Project = var.project_name
}
}
# ─────────────────────────────────────────
# サブネット(パブリック × 2)
# ─────────────────────────────────────────
resource "aws_subnet" "public_a" {
vpc_id= aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-a"
Project = var.project_name
}
}
resource "aws_subnet" "public_c" {
vpc_id= aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-c"
Project = var.project_name
}
}
# ─────────────────────────────────────────
# サブネット(プライベート × 2)
# ─────────────────────────────────────────
resource "aws_subnet" "private_a" {
vpc_id= aws_vpc.main.id
cidr_block = "10.0.10.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "${var.project_name}-private-a"
Project = var.project_name
}
}
resource "aws_subnet" "private_c" {
vpc_id= aws_vpc.main.id
cidr_block = "10.0.11.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
tags = {
Name = "${var.project_name}-private-c"
Project = var.project_name
}
}
# ─────────────────────────────────────────
# Internet Gateway
# ─────────────────────────────────────────
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
Project = var.project_name
}
}
# ─────────────────────────────────────────
# NAT Gateway(パブリックサブネット a に配置)
# ─────────────────────────────────────────
resource "aws_eip" "nat" {
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip"
Project = var.project_name
}
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_a.id
tags = {
Name = "${var.project_name}-nat"
Project = var.project_name
}
depends_on = [aws_internet_gateway.main]
}
# ─────────────────────────────────────────
# ルートテーブル(パブリック)
# ─────────────────────────────────────────
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-rt-public"
Project = var.project_name
}
}
resource "aws_route_table_association" "public_a" {
subnet_id= aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_c" {
subnet_id= aws_subnet.public_c.id
route_table_id = aws_route_table.public.id
}
# ─────────────────────────────────────────
# ルートテーブル(プライベート)
# ─────────────────────────────────────────
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "${var.project_name}-rt-private"
Project = var.project_name
}
}
resource "aws_route_table_association" "private_a" {
subnet_id= aws_subnet.private_a.id
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "private_c" {
subnet_id= aws_subnet.private_c.id
route_table_id = aws_route_table.private.id
}
# ─────────────────────────────────────────
# セキュリティグループ: Aurora
# ─────────────────────────────────────────
resource "aws_security_group" "aurora" {
name = "${var.project_name}-aurora-sg"
description = "Aurora PostgreSQL SG — RDS Proxy and Lambda only"
vpc_id= aws_vpc.main.id
tags = {
Name = "${var.project_name}-aurora-sg"
Project = var.project_name
}
}
resource "aws_security_group_rule" "aurora_ingress_proxy" {
type= "ingress"
from_port = 5432
to_port= 5432
protocol = "tcp"
security_group_id = aws_security_group.aurora.id
source_security_group_id = aws_security_group.rds_proxy.id
description = "Allow from RDS Proxy"
}
resource "aws_security_group_rule" "aurora_ingress_lambda" {
type= "ingress"
from_port = 5432
to_port= 5432
protocol = "tcp"
security_group_id = aws_security_group.aurora.id
source_security_group_id = aws_security_group.lambda.id
description = "Allow from Lambda migration"
}
resource "aws_security_group_rule" "aurora_egress" {
type = "egress"
from_port= 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.aurora.id
}
# ─────────────────────────────────────────
# セキュリティグループ: RDS Proxy
# ─────────────────────────────────────────
resource "aws_security_group" "rds_proxy" {
name = "${var.project_name}-rds-proxy-sg"
description = "RDS Proxy SG — Lambda and Terraform PG provider"
vpc_id= aws_vpc.main.id
tags = {
Name = "${var.project_name}-rds-proxy-sg"
Project = var.project_name
}
}
resource "aws_security_group_rule" "rds_proxy_ingress_lambda" {
type= "ingress"
from_port = 5432
to_port= 5432
protocol = "tcp"
security_group_id = aws_security_group.rds_proxy.id
source_security_group_id = aws_security_group.lambda.id
description = "Allow from Lambda"
}
resource "aws_security_group_rule" "rds_proxy_egress" {
type = "egress"
from_port= 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.rds_proxy.id
}
# ─────────────────────────────────────────
# セキュリティグループ: Lambda
# ─────────────────────────────────────────
resource "aws_security_group" "lambda" {
name = "${var.project_name}-lambda-sg"
description = "Lambda migration function SG"
vpc_id= aws_vpc.main.id
tags = {
Name = "${var.project_name}-lambda-sg"
Project = var.project_name
}
}
resource "aws_security_group_rule" "lambda_egress" {
type = "egress"
from_port= 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.lambda.id
}
# ─────────────────────────────────────────
# VPCエンドポイント: STS(IAM認証トークン生成)
# ─────────────────────────────────────────
resource "aws_vpc_endpoint" "sts" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.sts"
vpc_endpoint_type= "Interface"
subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_c.id]
security_group_ids = [aws_security_group.lambda.id]
private_dns_enabled = true
tags = {
Name = "${var.project_name}-sts-endpoint"
Project = var.project_name
}
}
# ─────────────────────────────────────────
# VPCエンドポイント: Secrets Manager
# ─────────────────────────────────────────
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.secretsmanager"
vpc_endpoint_type= "Interface"
subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_c.id]
security_group_ids = [aws_security_group.lambda.id]
private_dns_enabled = true
tags = {
Name = "${var.project_name}-sm-endpoint"
Project = var.project_name
}
}
10-5. aurora.tf — Aurora PostgreSQL Serverless v2
# ─────────────────────────────────────────
# DB サブネットグループ
# ─────────────────────────────────────────
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-subnet-group"
subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_c.id]
tags = {
Name = "${var.project_name}-subnet-group"
Project = var.project_name
}
}
# ─────────────────────────────────────────
# Aurora クラスター(Serverless v2)
# ─────────────────────────────────────────
resource "aws_rds_cluster" "main" {
cluster_identifier= "${var.project_name}-cluster"
engine= "aurora-postgresql"
engine_version = "16.4"
engine_mode = "provisioned"
database_name = var.db_name
master_username= var.aurora_master_username
master_password= var.aurora_master_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.aurora.id]
skip_final_snapshot = true
deletion_protection = false
storage_encrypted = true
# IAM データベース認証を有効化(パスワードレス接続の基盤)
iam_database_authentication_enabled = true
serverlessv2_scaling_configuration {
min_capacity = 0.5
max_capacity = 16
}
tags = {
Project = var.project_name
}
}
# ─────────────────────────────────────────
# Aurora クラスターインスタンス(Writer)
# ─────────────────────────────────────────
resource "aws_rds_cluster_instance" "writer" {
cluster_identifier = aws_rds_cluster.main.id
instance_class = "db.serverless"
engine = aws_rds_cluster.main.engine
engine_version = aws_rds_cluster.main.engine_version
identifier= "${var.project_name}-writer"
tags = {
Project = var.project_name
}
}
# ─────────────────────────────────────────
# Secrets Manager(マスター認証情報)
# RDS Proxy が参照するシークレット
# ─────────────────────────────────────────
resource "aws_secretsmanager_secret" "aurora_master" {
name = "${var.project_name}/master"
recovery_window_in_days = 0 # ハンズオン用: 即時削除を許可
tags = {
Project = var.project_name
}
}
resource "aws_secretsmanager_secret_version" "aurora_master" {
secret_id = aws_secretsmanager_secret.aurora_master.id
secret_string = jsonencode({
host = aws_rds_cluster.main.endpoint
port = 5432
username = var.aurora_master_username
password = var.aurora_master_password
dbname= var.db_name
})
}
10-6. rds_proxy.tf — RDS Proxy(IAM認証 REQUIRED)
# ─────────────────────────────────────────
# RDS Proxy
# ─────────────────────────────────────────
resource "aws_db_proxy" "main" {
name = "${var.project_name}-proxy"
debug_logging = false
engine_family = "POSTGRESQL"
idle_client_timeout = 1800
require_tls= true
role_arn= aws_iam_role.rds_proxy.arn
vpc_security_group_ids = [aws_security_group.rds_proxy.id]
vpc_subnet_ids= [aws_subnet.private_a.id, aws_subnet.private_c.id]
auth {
auth_scheme = "SECRETS"
description = "Aurora master credentials via Secrets Manager"
iam_auth = "REQUIRED" # IAM認証を強制(パスワード直接接続を禁止)
secret_arn = aws_secretsmanager_secret.aurora_master.arn
}
tags = {
Project = var.project_name
}
depends_on = [aws_rds_cluster_instance.writer]
}
# ─────────────────────────────────────────
# RDS Proxy デフォルトターゲットグループ
# ─────────────────────────────────────────
resource "aws_db_proxy_default_target_group" "main" {
db_proxy_name = aws_db_proxy.main.name
connection_pool_config {
connection_borrow_timeout = 120
max_connections_percent= 100
max_idle_connections_percent = 50
}
}
# ─────────────────────────────────────────
# RDS Proxy ターゲット(Aurora クラスター)
# ─────────────────────────────────────────
resource "aws_db_proxy_target" "main" {
db_cluster_identifier = aws_rds_cluster.main.id
db_proxy_name= aws_db_proxy.main.name
target_group_name = aws_db_proxy_default_target_group.main.name
}
10-7. iam.tf — IAMロール / ポリシー
data "aws_caller_identity" "current" {}
# ─────────────────────────────────────────
# IAMロール: RDS Proxy 用(Secrets Manager読み取り)
# ─────────────────────────────────────────
resource "aws_iam_role" "rds_proxy" {
name = "${var.project_name}-rds-proxy-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "rds.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
tags = {
Project = var.project_name
}
}
resource "aws_iam_policy" "rds_proxy_secrets" {
name = "${var.project_name}-rds-proxy-secrets-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect= "Allow"
Action= ["secretsmanager:GetSecretValue"]
Resource = aws_secretsmanager_secret.aurora_master.arn
}]
})
}
resource "aws_iam_role_policy_attachment" "rds_proxy_secrets" {
role = aws_iam_role.rds_proxy.name
policy_arn = aws_iam_policy.rds_proxy_secrets.arn
}
# ─────────────────────────────────────────
# IAMロール: Lambda マイグレーション用
# ─────────────────────────────────────────
resource "aws_iam_role" "lambda_migration" {
name = "${var.project_name}-lambda-migration-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
tags = {
Project = var.project_name
}
}
# VPC Lambda 基本実行権限
resource "aws_iam_role_policy_attachment" "lambda_vpc_execution" {
role = aws_iam_role.lambda_migration.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}
# Aurora IAM認証: rds-db:connect 権限
resource "aws_iam_policy" "aurora_iam_connect_lambda" {
name = "${var.project_name}-lambda-aurora-connect-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["rds-db:connect"]
Resource = [
"arn:aws:rds-db:${var.aws_region}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster.main.cluster_resource_id}/iam_lambda_user"
]
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_aurora_connect" {
role = aws_iam_role.lambda_migration.name
policy_arn = aws_iam_policy.aurora_iam_connect_lambda.arn
}
# ─────────────────────────────────────────
# IAMロール: Terraform postgresql provider 用
# terraform apply 実行者のロールに rds-db:connect を付与
# ─────────────────────────────────────────
resource "aws_iam_policy" "aurora_iam_connect_terraform" {
name = "${var.project_name}-terraform-pg-connect-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["rds-db:connect"]
Resource = [
"arn:aws:rds-db:${var.aws_region}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster.main.cluster_resource_id}/iam_terraform_user"
]
}]
})
}
# terraform apply 実行ユーザー/ロールにアタッチ
# 実際の環境では適切なユーザーまたはロールにアタッチしてください
resource "aws_iam_role_policy_attachment" "terraform_pg_connect" {
role = aws_iam_role.lambda_migration.name # 本番では専用ロールを使用
policy_arn = aws_iam_policy.aurora_iam_connect_terraform.arn
}
10-8. postgresql.tf — スキーマ / ロール / 権限管理(cyrilgdn/postgresql)
このファイルは Aurora クラスターおよび RDS Proxy が稼働済みの状態でのみ適用できます。また、IAM認証用 DB ユーザー iam_terraform_user が事前に作成されている必要があります(Section 6 参照)。
# ─────────────────────────────────────────
# データベース作成
# ─────────────────────────────────────────
resource "postgresql_database" "app_db" {
name = var.db_name
owner = "postgres"
encoding = "UTF8"
lc_collate = "en_US.UTF-8"
lc_ctype = "en_US.UTF-8"
connection_limit = -1
allow_connections = true
}
# ─────────────────────────────────────────
# ロール定義(アプリケーション用)
# ─────────────────────────────────────────
# オーナーロール(DDL実行・テーブル所有)
resource "postgresql_role" "app_owner" {
name = "app_owner"
login= false
inherit = true
connection_limit = -1
}
# ライタロール(INSERT/UPDATE/DELETE)
resource "postgresql_role" "app_writer" {
name = "app_writer"
login= false
inherit = true
connection_limit = -1
}
# リーダーロール(SELECT のみ)
resource "postgresql_role" "app_reader" {
name = "app_reader"
login= false
inherit = true
connection_limit = -1
}
# IAM認証ユーザー(Lambdaアプリ用)— app_writer を継承
resource "postgresql_role" "iam_lambda_user" {
name = "iam_lambda_user"
login= true
inherit = true
connection_limit = 10
roles= [postgresql_role.app_writer.name]
}
# IAM認証ユーザー(読み取り専用アプリ用)— app_reader を継承
resource "postgresql_role" "iam_readonly_user" {
name = "iam_readonly_user"
login= true
inherit = true
connection_limit = 10
roles= [postgresql_role.app_reader.name]
}
# ─────────────────────────────────────────
# スキーマ作成
# ─────────────────────────────────────────
resource "postgresql_schema" "app" {
name = "app"
owner= postgresql_role.app_owner.name
database= postgresql_database.app_db.name
drop_cascade = false
}
# ─────────────────────────────────────────
# 権限付与(GRANT)
# ─────────────────────────────────────────
# app_writer: スキーマ USAGE + CREATE
resource "postgresql_grant" "writer_schema" {
database = postgresql_database.app_db.name
role = postgresql_role.app_writer.name
schema= postgresql_schema.app.name
object_type = "schema"
privileges = ["USAGE", "CREATE"]
}
# app_reader: スキーマ USAGE
resource "postgresql_grant" "reader_schema" {
database = postgresql_database.app_db.name
role = postgresql_role.app_reader.name
schema= postgresql_schema.app.name
object_type = "schema"
privileges = ["USAGE"]
}
# app_writer: 既存テーブルへの DML 権限
resource "postgresql_grant" "writer_tables" {
database = postgresql_database.app_db.name
role = postgresql_role.app_writer.name
schema= postgresql_schema.app.name
object_type = "table"
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
# app_reader: 既存テーブルへの SELECT 権限
resource "postgresql_grant" "reader_tables" {
database = postgresql_database.app_db.name
role = postgresql_role.app_reader.name
schema= postgresql_schema.app.name
object_type = "table"
privileges = ["SELECT"]
}
# app_writer: シーケンスへの権限(SERIAL/BIGSERIAL カラム用)
resource "postgresql_grant" "writer_sequences" {
database = postgresql_database.app_db.name
role = postgresql_role.app_writer.name
schema= postgresql_schema.app.name
object_type = "sequence"
privileges = ["USAGE", "SELECT", "UPDATE"]
}
# ─────────────────────────────────────────
# デフォルト権限(将来作成されるオブジェクトへの自動付与)
# ─────────────────────────────────────────
# app_owner が作成したテーブルを app_writer が自動で DML 可能
resource "postgresql_default_privileges" "writer_tables" {
database = postgresql_database.app_db.name
role = postgresql_role.app_writer.name
owner = postgresql_role.app_owner.name
schema= postgresql_schema.app.name
object_type = "table"
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
# app_owner が作成したテーブルを app_reader が自動で SELECT 可能
resource "postgresql_default_privileges" "reader_tables" {
database = postgresql_database.app_db.name
role = postgresql_role.app_reader.name
owner = postgresql_role.app_owner.name
schema= postgresql_schema.app.name
object_type = "table"
privileges = ["SELECT"]
}
# ─────────────────────────────────────────
# 拡張機能(Extension)
# ─────────────────────────────────────────
resource "postgresql_extension" "uuid_ossp" {
name = "uuid-ossp"
database = postgresql_database.app_db.name
schema= "public"
}
resource "postgresql_extension" "pgcrypto" {
name = "pgcrypto"
database = postgresql_database.app_db.name
schema= "public"
}
10-9. lambda.tf — マイグレーション Lambda
# ─────────────────────────────────────────
# Lambda レイヤー: psycopg2
# ─────────────────────────────────────────
# 事前にビルドしたpsycopg2のzipを lambda_layer/ に配置してください
# ビルド方法: Section 8-2 参照
data "archive_file" "psycopg2_layer" {
type = "zip"
source_dir = "${path.module}/../lambda_layer/psycopg2"
output_path = "${path.module}/.terraform/psycopg2_layer.zip"
}
resource "aws_lambda_layer_version" "psycopg2" {
layer_name = "${var.project_name}-psycopg2"
filename= data.archive_file.psycopg2_layer.output_path
source_code_hash = data.archive_file.psycopg2_layer.output_base64sha256
compatible_runtimes = ["python3.12"]
description= "psycopg2-binary for PostgreSQL connection"
}
# ─────────────────────────────────────────
# Lambda 関数コードのアーカイブ
# ─────────────────────────────────────────
data "archive_file" "lambda" {
type = "zip"
source_dir = "${path.module}/../lambda_src"
output_path = "${path.module}/.terraform/lambda_migration.zip"
}
# ─────────────────────────────────────────
# Lambda 関数: マイグレーション
# ─────────────────────────────────────────
resource "aws_lambda_function" "migration" {
function_name = "${var.project_name}-migration"
role = aws_iam_role.lambda_migration.arn
filename= data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
handler = "migration.lambda_handler"
runtime = "python3.12"
timeout = 300
memory_size= 256
layers = [aws_lambda_layer_version.psycopg2.arn]
vpc_config {
subnet_ids= [aws_subnet.private_a.id, aws_subnet.private_c.id]
security_group_ids = [aws_security_group.lambda.id]
}
environment {
variables = {
RDS_PROXY_HOST = aws_db_proxy.main.endpoint
DB_USER = "iam_lambda_user"
DB_NAME = var.db_name
AWS_REGION_NAME = var.aws_region
}
}
tags = {
Project = var.project_name
}
depends_on = [
aws_iam_role_policy_attachment.lambda_vpc_execution,
aws_iam_role_policy_attachment.lambda_aurora_connect,
]
}
# ─────────────────────────────────────────
# EventBridge Scheduler: 定期マイグレーション
# ─────────────────────────────────────────
resource "aws_iam_role" "scheduler" {
name = "${var.project_name}-scheduler-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "scheduler.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "scheduler_lambda" {
name = "invoke-migration-lambda"
role = aws_iam_role.scheduler.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect= "Allow"
Action= ["lambda:InvokeFunction"]
Resource = aws_lambda_function.migration.arn
}]
})
}
resource "aws_scheduler_schedule" "migration_trigger" {
name = "${var.project_name}-migration-schedule"
schedule_expression = "rate(1 day)"
schedule_expression_timezone = "Asia/Tokyo"
flexible_time_window {
mode = "OFF"
}
target {
arn= aws_lambda_function.migration.arn
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
action = "migrate"
})
}
}
10-10. outputs.tf — 出力値
output "aurora_writer_endpoint" {
description = "Aurora クラスター Writer エンドポイント"
value = aws_rds_cluster.main.endpoint
}
output "aurora_reader_endpoint" {
description = "Aurora クラスター Reader エンドポイント"
value = aws_rds_cluster.main.reader_endpoint
}
output "aurora_cluster_resource_id" {
description = "Aurora クラスターリソースID(IAMポリシーで使用)"
value = aws_rds_cluster.main.cluster_resource_id
}
output "rds_proxy_endpoint" {
description = "RDS Proxy エンドポイント(アプリケーション接続先)"
value = aws_db_proxy.main.endpoint
}
output "migration_lambda_arn" {
description = "マイグレーション Lambda ARN"
value = aws_lambda_function.migration.arn
}
output "migration_lambda_name" {
description = "マイグレーション Lambda 関数名"
value = aws_lambda_function.migration.function_name
}
output "secrets_manager_arn" {
description = "Aurora マスター認証情報 Secrets Manager ARN"
value = aws_secretsmanager_secret.aurora_master.arn
}
10-11. デプロイ手順
Aurora クラスターと cyrilgdn/postgresql provider の依存関係があるため、2段階で適用します。
Phase A: AWS インフラ基盤の構築
cd terraform/
# 初期化(プロバイダーのダウンロード)
terraform init
# プラン確認(postgresql リソースを除外)
terraform plan \
-var="aurora_master_password=YourSecurePassword123!" \
-target=aws_vpc.main \
-target=aws_rds_cluster.main \
-target=aws_rds_cluster_instance.writer \
-target=aws_db_proxy.main \
-target=aws_lambda_function.migration
# 適用(VPC/Aurora/RDS Proxy/Lambda)
terraform apply \
-var="aurora_master_password=YourSecurePassword123!" \
-target=aws_vpc.main \
-target=aws_rds_cluster.main \
-target=aws_rds_cluster_instance.writer \
-target=aws_db_proxy.main \
-target=aws_lambda_function.migration
RDS Proxy の作成には 5〜10分かかります。aws_db_proxy リソースのステータスが available になるまで待ってから Phase B に進んでください。
Phase B: postgresql provider でスキーマ適用
# IAM認証ユーザーを事前に作成(Section 6 の手順)
# その後、postgresql リソースを適用
terraform apply -var="aurora_master_password=YourSecurePassword123!"
全リソース削除
terraform destroy -var="aurora_master_password=YourSecurePassword123!"
Section 11: 他の方法(概要解説)
本記事のメインハンズオンでは Terraform postgresql provider + IAM認証 + RDS Proxy を採用しましたが、実際の運用では要件に応じて複数の方法を組み合わせるケースもあります。以下に代表的な4つの方法を概説します。
11-1. RDS Data API — VPC接続不要のSQL実行
概要: HTTPS経由でSQLを実行できるマネージドAPIです。Lambda関数やCI/CDパイプラインからVPC設定なしでAuroraにアクセスできます。
アーキテクチャ:
クライアント(Lambda / CI/CD / コンソール)
↓ HTTPS + IAM認証(rds-data:ExecuteStatement)
RDS Data API エンドポイント
↓
Aurora PostgreSQL
AWSコンソール(Query Editor)での使用方法:
- RDS コンソール → クラスター選択 → 「クエリエディター」タブ
- データベースに接続(シークレット選択 or パスワード入力)
- SQLを直接入力・実行
AWS CLI での実行例:
aws rds-data execute-statement \
--resource-arn "arn:aws:rds:ap-northeast-1:123456789:cluster:aurora-cluster" \
--secret-arn "arn:aws:secretsmanager:ap-northeast-1:123456789:secret:aurora/master" \
--database "app_database" \
--sql "SELECT table_name FROM information_schema.tables WHERE table_schema = 'app'"
Terraform連携 (null_resource + local-exec):
resource "null_resource" "create_schema_via_data_api" {
triggers = {
schema_version = "v1"
}
provisioner "local-exec" {
command = <<-EOF
aws rds-data execute-statement \
--resource-arn "${aws_rds_cluster.main.arn}" \
--secret-arn "${aws_secretsmanager_secret.aurora_master.arn}" \
--database "${var.db_name}" \
--sql "CREATE SCHEMA IF NOT EXISTS app"
EOF
}
depends_on = [aws_rds_cluster_instance.writer]
}
主な制約:
| 制約 | 内容 |
|---|---|
| ENUM型 | 非対応 |
| 結果セットサイズ | 1MB上限 |
| DDL + DML の順序 | DDL実行が前のDMLをコミット |
| トランザクション | BeginTransaction/CommitTransaction APIで別途管理が必要 |
推奨ユースケース: 開発・デバッグ用のad-hocクエリ、VPC外からの軽量DDL実行
11-2. Flyway on CodeBuild — バージョン管理マイグレーション
概要: Javaベースのマイグレーションツール「Flyway」をAWS CodeBuild上で実行し、SQL スクリプトをバージョン管理します。V001__init.sql, V002__add_index.sql のように連番管理されたスクリプトを順番に実行します。
アーキテクチャ:
GitHub / CodeCommit
↓ Push
CodePipeline
↓ Source Stage
CodeBuild(Flyway Docker イメージ)
├─ Secrets Manager から認証情報取得
└─ Aurora PostgreSQL(VPC内)
↓ V001, V002, ... を順番に適用
buildspec.yml の例:
version: 0.2
phases:
pre_build:
commands:
- DB_HOST=$(aws secretsmanager get-secret-value --secret-id aurora-no-human-login/master --query SecretString --output text | jq -r .host)
- DB_PASS=$(aws secretsmanager get-secret-value --secret-id aurora-no-human-login/master --query SecretString --output text | jq -r .password)
build:
commands:
- |
docker run --rm \
-v $(pwd)/migrations:/flyway/sql \
flyway/flyway:10 \
-url="jdbc:postgresql://${DB_HOST}:5432/app_database" \
-user=postgres \
-password="${DB_PASS}" \
migrate
メリット / デメリット:
| メリット | デメリット | |
|---|---|---|
| バージョン管理 | SQL スクリプトの実行履歴をDB管理 | Java依存 |
| ロールバック | Enterprise版のみ undo migration 対応 | Community版は undo 不可 |
| IAM認証 | 非対応(Secrets Managerのパスワード認証) | パスワードレス化不可 |
| CI/CD統合 | CodePipeline/GitHub Actionsと容易に統合 | Docker環境が必要 |
参考: AWS公式ブログ — Automate database object deployments in Amazon Aurora using AWS CodePipeline
11-3. SSM Session Manager Port Forwarding — 開発者のad-hocアクセス
概要: AWS Systems Manager Session Manager を使って、踏み台サーバー(Bastion)なしに開発者のローカルマシンからAurora PostgreSQLへ接続します。EC2インスタンスをSSMエージェント経由で中継します。
接続コマンド:
# ローカルポート 15432 → Aurora エンドポイント:5432 へ転送
aws ssm start-session \
--target i-xxxxxxxxxxxxxxxxx \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{
"host": ["aurora-no-human-login-cluster.cluster-xxxxxx.ap-northeast-1.rds.amazonaws.com"],
"portNumber": ["5432"],
"localPortNumber": ["15432"]
}'
別ターミナルから接続:
# psql で接続
psql -h localhost -p 15432 -U postgres -d app_database
# pgAdmin や TablePlus の接続先
# Host: localhost
# Port: 15432
# Database: app_database
必要条件:
- EC2インスタンスに SSM エージェントがインストール済み(Amazon Linux 2023はデフォルトでインストール)
- EC2に
AmazonSSMManagedInstanceCoreポリシーをアタッチ - 開発者ローカルに AWS CLI + Session Manager Plugin インストール
# Session Manager Plugin のインストール(macOS)
brew install --cask session-manager-plugin
推奨ユースケース: 開発者のデータ確認・デバッグ・スキーマ変更のテスト。自動化ワークフローには不向き。
11-4. GitHub Actions OIDC連携 — CI/CDパイプラインからのパスワードレス接続
概要: GitHub Actions の OIDC(OpenID Connect)を使ってAWS IAMロールを一時的に引き受け、generate-db-auth-token でAurora IAM認証トークンを生成してDB操作を実行します。シークレットのローテーション不要でセキュリティを維持できます。
GitHub Actions ワークフロー例:
name: DB Migration
on:
push:
branches: [main]
paths:
- 'migrations/**'
permissions:
id-token: write
contents: read
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials(OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-aurora-role
aws-region: ap-northeast-1
- name: Generate RDS Auth Token
id: token
run: |
TOKEN=$(aws rds generate-db-auth-token \
--hostname ${{ vars.RDS_PROXY_ENDPOINT }} \
--port 5432 \
--username iam_lambda_user \
--region ap-northeast-1)
echo "token=${TOKEN}" >> $GITHUB_OUTPUT
- name: Run DB Migration
env:
PGPASSWORD: ${{ steps.token.outputs.token }}
PGSSLMODE: require
run: |
psql \
-h ${{ vars.RDS_PROXY_ENDPOINT }} \
-U iam_lambda_user \
-d app_database \
-f migrations/V$(date +%Y%m%d)__migration.sql
IAMロール信頼ポリシー(GitHub Actions OIDC用):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}]
}
メリット:
– GitHub Secretsにパスワードを保存不要
– IAM認証トークンは15分で自動失効(漏洩リスク低)
– GitHub Actions の OIDC は追加費用なし
Section 12: クリーンアップ
ハンズオン終了後は不要なリソースを削除してください。Aurora Serverless v2 は起動中のインスタンスに対して時間課金が発生します。
12-1. Terraform で構築したリソースの削除
Terraform で構築した場合は terraform destroy で一括削除できます。依存関係は自動解決されます。
削除前の確認(dry-run)
cd terraform/
terraform plan -destroy -var="aurora_master_password=YourSecurePassword123!"
Plan: 0 to add, 0 to change, 38 to destroy.
# aws_db_proxy.main will be destroyed
# aws_lambda_function.migration will be destroyed
# aws_rds_cluster.main will be destroyed
# aws_rds_cluster_instance.writer will be destroyed
# aws_secretsmanager_secret.aurora_master will be destroyed
# ...
削除の実行
terraform destroy -var="aurora_master_password=YourSecurePassword123!"
確認プロンプトで yes を入力してください。
Aurora クラスターの削除には 5〜10分かかります。削除完了前に同名のクラスターを再作成しようとするとエラーになります。terraform destroy 完了を確認してから再構築してください。
12-2. コンソールで構築したリソースの手動削除
コンソールハンズオン(Section 5〜6)で手動作成したリソースは以下の順序で削除します。
削除順序(依存関係を考慮):
| 順序 | リソース | 注意点 |
|---|---|---|
| 1 | EventBridge Scheduler(スケジュール) | Lambda より先に削除 |
| 2 | Lambda 関数(マイグレーション) | — |
| 3 | Lambda レイヤー(psycopg2) | — |
| 4 | RDS Proxy | Aurora より先に削除必須 |
| 5 | Aurora クラスターインスタンス | クラスターより先に削除 |
| 6 | Aurora クラスター | スナップショット不要なら「最終スナップショットをスキップ」 |
| 7 | Secrets Manager シークレット | 即時削除: 「復旧期間なしで削除」を選択 |
| 8 | IAMポリシー / ロール | 順不同 |
| 9 | NAT Gateway | EIP 解放より先に削除 |
| 10 | EIP(Elastic IP) | NAT Gateway削除後に解放 |
| 11 | VPCエンドポイント | — |
| 12 | VPC | サブネット・SGは VPC削除で自動削除 |
12-3. 部分削除(特定リソースのみ)
Terraform での部分削除は -target オプションで対象リソースを指定します。
# RDS Proxy のみ削除(コスト削減 + Aurora は残す場合)
terraform destroy \
-var="aurora_master_password=YourSecurePassword123!" \
-target=aws_db_proxy.main \
-target=aws_db_proxy_default_target_group.main \
-target=aws_db_proxy_target.main
-target での部分削除は Terraform State と実際のインフラに不整合が生じる可能性があります。本番環境での使用は最小限にし、通常のクリーンアップには terraform destroy(全体削除)を使用してください。
Section 13: まとめ
13-1. 本記事で実現したこと
本記事では「人間がログインしないDB管理」をコンセプトに、Aurora PostgreSQL の完全 IaC 化・パスワードレス化を実現しました。
構築したアーキテクチャのポイント:
| 課題 | 解決策 | 実装方法 |
|---|---|---|
| パスワード共有のリスク | IAM データベース認証 | iam_database_authentication_enabled = true + rds_iam ロール |
| 踏み台サーバー不要 | RDS Proxy 経由の接続集約 | aws_db_proxy (IAM_AUTH = REQUIRED) |
| スキーマのドリフト防止 | Terraform で宣言的管理 | cyrilgdn/postgresql provider |
| コネクション枯渇 | RDS Proxy によるプーリング | max_connections_percent / idle 制御 |
| マイグレーションの自動化 | Lambda + EventBridge Scheduler | 冪等DDL + 日次スケジュール実行 |
| 監査証跡 | CloudWatch Logs + IAMロール | Lambda実行ログ + IAMポリシー単位の制御 |
13-2. 手法選定フローチャート
運用シナリオに合わせた手法の選び方:
初期スキーマ構築・ロール管理・権限管理
→ Terraform postgresql provider(IaC で宣言的管理、変更差分が見える)
ALTER TABLE / インデックス追加 / データ投入
→ Lambda + psycopg2 + IAM認証(柔軟なDDL実行、冪等性を保証)
バージョン管理された複雑なマイグレーション
→ Flyway on CodeBuild(SQL スクリプトのバージョン管理、CI/CD統合)
開発者のデバッグ・データ確認(ad-hoc)
→ SSM Session Manager Port Forwarding(踏み台不要、一時的なローカル接続)
CI/CDパイプラインからのDB操作
→ GitHub Actions OIDC + IAM認証(シークレット不要、最小権限の一時認証)
VPC外から素早くSQL確認・開発
→ RDS Data API + Query Editor(VPC設定不要、コンソールから即実行)
13-3. セキュリティのベストプラクティス
本記事を通じて実践したセキュリティ原則:
最小権限の原則:
– Lambda には iam_lambda_user のみの rds-db:connect 権限
– リーダーには app_reader ロール(SELECT のみ)
– ライターには app_writer ロール(DML のみ、DDL不可)
パスワードレス化:
– RDS Proxy の iam_auth = "REQUIRED" でパスワード直接接続を禁止
– IAM認証トークンは15分で自動失効
暗号化:
– storage_encrypted = true(KMSによる保存データの暗号化)
– require_tls = true(転送データの暗号化)
13-4. 応用と次のステップ
マルチアカウント構成:
– Resource Access Manager(RAM)でサブネットを共有
– IAM ロールの cross-account trust で別アカウントの Lambda からアクセス
Blue/Green デプロイメント:
– Aurora Blue/Green Deployments でゼロダウンタイムのメジャーバージョンアップ
– Terraform で aws_rds_cluster_blue_green_update ブロックを使用(AWS Provider 5.x)
Flyway 統合:
– Terraform で基盤を構築し、アプリケーション層のマイグレーションは Flyway に委譲
– null_resource + local-exec で terraform apply 後に自動的に Flyway を実行
関連記事: Terraform基礎 — AWS IaC入門ハンズオン ▶
13-5. シリーズリンク
本記事は「AWS ハンズオン TechBlog」の Aurora PostgreSQL 完全ガイドです。
関連シリーズ:
– AWS Step Functions 入門 — コンソールとTerraformで学ぶハンズオン
– ECS × Step Functions 入門 — CSVバッチをFargateタスクでジョブ化するハンズオン
– Step Functions エラーハンドリング完全ガイド
– Step Functions 入出力データフロー制御完全ガイド
– Step Functions Callbackパターン完全ガイド
– Step Functions Distributed Map完全ガイド
– Terraform基礎 — AWS IaC入門ハンズオン
– IAM Identity Center × SSM Session Manager 本番運用完全ガイド — 本記事の汎用横展開版 (EC2/ECS/RDS/RDP 4接続 + Permission Set 6パターン)
Section 14: 参考リンク
AWS 公式ドキュメント
- Aurora PostgreSQL での IAM データベース認証
- Amazon RDS Proxy ユーザーガイド
- Aurora Serverless v2 スケーリング設定
- RDS Data API の使用
- AWS Secrets Manager でのシークレット管理
- SSM Session Manager ポートフォワーディング
- EventBridge Scheduler — Terraform Lambda スケジュール
Terraform Registry
- cyrilgdn/postgresql Provider ドキュメント
- hashicorp/aws — aws_db_proxy リソース
- hashicorp/aws — aws_rds_cluster リソース
- hashicorp/aws — aws_lambda_function リソース
- hashicorp/aws — aws_scheduler_schedule リソース
AWS 公式ブログ
- Automate database object deployments in Amazon Aurora using AWS CodePipeline
- Automate schema version control and migration with Flyway and AWS Lambda on Amazon Aurora PostgreSQL
- Connecting to your DB cluster using IAM authentication
- Integrating GitHub Actions OIDC with AWS
- Amazon Aurora PostgreSQL — Express Configuration でIAM認証のデフォルト有効化(2025年発表)