- 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.x4-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/terraformmkdir -p aurora-no-human-login/lambda/migrationmkdir -p aurora-no-human-login/lambda/layercd aurora-no-human-logingit init4-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, memberofFROM pg_userJOIN pg_auth_members ON pg_user.usesysid = pg_auth_members.memberJOIN pg_roles ON pg_auth_members.roleid = pg_roles.oidWHERE pg_roles.rolname = 'rds_iam';期待される出力:
usename | usesuper | memberof-------------------+----------+--------- iam_terraform_user | false| {rds_iam} iam_lambda_user | false| {rds_iam}(2 rows)-- psql を終了\qPhase 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 aclWHERE 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 applyterraform 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.appTerraformはリソース間の依存関係(
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.binaryterraform 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.tfgit commit -m "feat: add app_analytics role for reporting"git push origin feat/add-app-schema-v2# → GitHub PR作成 → レビュー → マージ → terraform applyGitHub 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/layerzip -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 boto3import psycopg2import osimport logginglogger = 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 conndef 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-migrationzip -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_rolesWHERE rolname LIKE '%app%' OR rolname LIKE '%iam%'ORDER BY rolname;-- 権限確認SELECT grantee, table_schema, table_name, privilege_typeFROM information_schema.role_table_grantsWHERE 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 startedINFO Already applied: set()INFO Pending migrations: 5INFO Applied migration: v001_create_users_tableINFO Applied migration: v002_create_orders_tableINFO Applied migration: v003_add_index_users_emailINFO Applied migration: v004_add_index_orders_user_idINFO Applied migration: v005_add_index_orders_created_atDB でのテーブル確認:
-- マイグレーション適用履歴SELECT version, applied_at, applied_byFROM schema_migrationsORDER BY applied_at;-- テーブル一覧SELECT tablename, tableownerFROM pg_tablesWHERE schemaname = 'app'ORDER BY tablename;-- インデックス一覧SELECT indexname, tablename, indexdefFROM pg_indexesWHERE 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-accessaurora-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マイグレーション環境を構築できます。
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 terraformcd terraform10-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.hcl10-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)
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 + CREATEresource "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: スキーマ USAGEresource "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.endpointDB_USER = "iam_lambda_user"DB_NAME = var.db_nameAWS_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.migrationaws_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 PostgreSQLAWSコンソール(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 = <<-EOFaws 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 ↓ PushCodePipeline ↓ Source StageCodeBuild(Flyway Docker イメージ) ├─ Secrets Manager から認証情報取得 └─ Aurora PostgreSQL(VPC内) ↓ V001, V002, ... を順番に適用buildspec.yml の例:
version: 0.2phases: 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 Migrationon: push: branches: [main] paths:- 'migrations/**'permissions: id-token: write contents: readjobs: 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.sqlIAMロール信頼ポリシー(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 を入力してください。
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入門ハンズオン
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
- Use IAM authentication to connect with pgAdmin to Amazon Aurora PostgreSQL
- Integrating GitHub Actions OIDC with AWS
- Amazon Aurora PostgreSQL — Express Configuration でIAM認証のデフォルト有効化(2025年発表)