Terraform で RDS 構築して接続確認するまで
※2023/7/9更新 サンプル用にコードを簡素化しました。
※2023/12/30更新 Amazon RDS と AWS Secrets Manager の統合に対応しました。
この記事ではAmazon RDSをTerraformで構築し、EC2のセッションマネージャーを介してRDSに接続する方法について書いていこうと思います。
TerraformのバージョンとOSは以下の通りです。
% terraform -v
Terraform v1.5.5
on darwin_amd64
急いでる人向け
下記URLにコードがありますので、READMEの通りにすればできると思います。
suganuma3510/terraform-sample
コード
RDSはMySQLを使用します。
また今回はTerrafromのmoduleを使用して実装していきますが、ここでは細かいmoduleの使い方や各リソースの説明は省きます。
ディレクトリ構成
実装後のディレクトリ構成は以下の通りです。
実際に手を動かしながら実装する方は、以下のフォルダ構成を参考にファイルを作成すると詰まらず進めると思います。
各moduleフォルダにそれぞれmain.tf
、outputs.tf
、variables.tf
を作成して、リソースを定義していきます。
.
├── README.md
├── main.tf
├── module
│ ├── ec2
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── network
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ └── rds
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── terraform.tfvars
Network
AWSのネットワークに関するリソースを定義します。
Terraformコード
#--------------------------------------------------------------
# VPC
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
resource "aws_vpc" "default" {
cidr_block = var.vpc_cidr
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.name}-vpc"
}
}
#--------------------------------------------------------------
# Public subnet
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "public" {
for_each = var.pub_cidrs
vpc_id = aws_vpc.default.id
cidr_block = each.value
availability_zone = "${var.region}${each.key}"
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-${each.key}"
}
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.default.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.default.id
}
tags = {
Name = "${var.name}-public-rtb"
}
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.public.id
}
#--------------------------------------------------------------
# Private subnet
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "private" {
for_each = var.pri_cidrs
vpc_id = aws_vpc.default.id
cidr_block = each.value
availability_zone = "${var.region}${each.key}"
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-private-${each.key}"
}
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table
resource "aws_route_table" "private" {
vpc_id = aws_vpc.default.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.default.id
}
tags = {
Name = "${var.name}-private-rtb"
}
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association
resource "aws_route_table_association" "private" {
for_each = aws_subnet.private
subnet_id = each.value.id
route_table_id = aws_route_table.private.id
}
#--------------------------------------------------------------
# Internet Gateway
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway
resource "aws_internet_gateway" "default" {
vpc_id = aws_vpc.default.id
tags = {
Name = "${var.name}-igw"
}
}
#--------------------------------------------------------------
# NAT
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip
resource "aws_eip" "nat" {
domain = "vpc"
depends_on = [aws_internet_gateway.default]
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway
resource "aws_nat_gateway" "default" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public["a"].id
depends_on = [aws_internet_gateway.default]
}
output "vpc_id" {
value = aws_vpc.default.id
}
output "vpc_cidr" {
value = aws_vpc.default.cidr_block
}
output "pub_subnet_ids" {
value = [for value in aws_subnet.public : value.id]
}
output "pri_subnet_ids" {
value = [for value in aws_subnet.private : value.id]
}
output "pub_subnet_cidr_blocks" {
value = [for value in aws_subnet.public : value.cidr_block]
}
output "pri_subnet_cidr_blocks" {
value = [for value in aws_subnet.private : value.cidr_block]
}
variable "name" {}
variable "region" {}
variable "vpc_cidr" {}
variable "pub_cidrs" {
type = map(string)
}
variable "pri_cidrs" {
type = map(string)
}
EC2
EC2のリソースを定義します。
RDSには直接接続することができないので、セッションマネージャーを介しEC2を踏み台にする形でRDSに接続します。
Terraformコード
#--------------------------------------------------------------
# EC2
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami
data "aws_ami" "amazon2_amd64" {
most_recent = true
filter {
name = "name"
values = ["amzn2-ami-kernel*"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
owners = ["amazon"]
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance
resource "aws_instance" "ec2" {
ami = data.aws_ami.amazon2_amd64.id
instance_type = "t2.micro"
subnet_id = var.subnet_ids[0]
associate_public_ip_address = "false"
vpc_security_group_ids = [aws_security_group.ec2.id]
iam_instance_profile = aws_iam_instance_profile.systems-manager.name
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y mysql
EOF
}
#--------------------------------------------------------------
# Security group
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
resource "aws_security_group" "ec2" {
name = "${var.app_name}-ec2-sg"
description = "EC2 service security group for ${var.app_name}"
vpc_id = var.vpc_id
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
resource "aws_security_group_rule" "ec2_egress_all" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.ec2.id
}
#--------------------------------------------------------------
# IAM Role
#--------------------------------------------------------------
data "aws_iam_policy_document" "ec2" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
data "aws_iam_policy" "systems-manager" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role" "ec2" {
name = "${var.app_name}-ec2-role"
assume_role_policy = data.aws_iam_policy_document.ec2.json
}
resource "aws_iam_role_policy_attachment" "ec2" {
role = aws_iam_role.ec2.name
policy_arn = data.aws_iam_policy.systems-manager.arn
}
resource "aws_iam_instance_profile" "systems-manager" {
name = "${var.app_name}-ec2-instance-profile"
role = aws_iam_role.ec2.name
}
output "ec2_security_group_id" {
value = aws_security_group.ec2.id
}
variable "app_name" {}
variable "vpc_id" {}
variable "subnet_ids" {}
RDS
RDSのリソースを定義していきます。
今回使用するデータベースはMySQLです。
DBのパスワードはmanage_master_user_password
を指定して、Secrets Managerへ自動作成します。
こうすることでデプロイ後、terraform.tfstate
にパスワードが残らないため安全です。
Terraformコード
#--------------------------------------------------------------
# RDS
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance
resource "aws_db_instance" "rds" {
allocated_storage = 10
storage_type = "gp2"
engine = var.engine
engine_version = var.engine_version
instance_class = var.db_instance
identifier = var.db_name
username = var.db_username
manage_master_user_password = true
skip_final_snapshot = true
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.rds.name
}
#--------------------------------------------------------------
# Subnet group
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group
resource "aws_db_subnet_group" "rds" {
name = var.app_name
description = "rds subnet group for ${var.db_name}"
subnet_ids = var.subnet_ids
}
#--------------------------------------------------------------
# Security group
#--------------------------------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
resource "aws_security_group" "rds" {
name = "${var.app_name}-rds-sg"
description = "RDS service security group for ${var.app_name}"
vpc_id = var.vpc_id
tags = {
Name = "${var.app_name}-rds-sg"
}
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
resource "aws_security_group_rule" "rds_ingress_mysql" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
cidr_blocks = var.subnet_cidr_blocks
security_group_id = aws_security_group.rds.id
}
resource "aws_security_group_rule" "rds_ingress_app" {
count = length(var.source_security_group_ids)
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1"
source_security_group_id = element(var.source_security_group_ids, count.index)
security_group_id = aws_security_group.rds.id
}
resource "aws_security_group_rule" "rds_egress_all" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.rds.id
}
output "db_instance_id" {
value = aws_db_instance.rds.id
}
output "db_address" {
value = aws_db_instance.rds.address
}
output "db_security_group_id" {
value = aws_security_group.rds.id
}
variable "app_name" {}
variable "db_name" {}
variable "db_username" {}
variable "vpc_id" {}
variable "subnet_ids" {}
variable "subnet_cidr_blocks" {
type = list(string)
}
variable "source_security_group_ids" {
type = list(string)
}
variable "engine" {
default = "mysql"
}
variable "engine_version" {
default = "8.0.33"
}
variable "db_instance" {
default = "db.t2.micro"
}
main
次にそれぞれ定義したモジュールを呼び出すためのルートモジュールを定義します。
ここではmain.tf
に全てまとめて呼び出しています。
Terraformコード
variable "region" {}
variable "name" {}
variable "vpc_cidr" {}
variable "public_subnet_cidrs" { type = map(string) }
variable "private_subnet_cidrs" { type = map(string) }
variable "db_name" {}
variable "db_username" {}
terraform {
required_version = "=v1.5.5"
}
provider "aws" {
region = var.region
}
module "network" {
source = "./module/network"
name = var.name
region = var.region
vpc_cidr = var.vpc_cidr
pub_cidrs = var.public_subnet_cidrs
pri_cidrs = var.private_subnet_cidrs
}
module "jump-ec2" {
source = "./module/ec2"
app_name = var.name
vpc_id = module.network.vpc_id
subnet_ids = module.network.pri_subnet_ids
}
module "rds" {
source = "./module/rds"
app_name = var.name
db_name = var.db_name
db_username = var.db_username
vpc_id = module.network.vpc_id
subnet_ids = module.network.pri_subnet_ids
subnet_cidr_blocks = module.network.pri_subnet_cidr_blocks
source_security_group_ids = [module.jump-ec2.ec2_security_group_id]
}
tfvar
変数を定義するファイルを作成します。
ここはお好きなように変えていただいて構いません。
Terraformコード
#--------------------------------------------------------------
# General
#--------------------------------------------------------------
name = "rds-sample"
region = "ap-northeast-1"
#--------------------------------------------------------------
# Network
#--------------------------------------------------------------
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = {
"a" = "10.0.0.0/24",
"c" = "10.0.1.0/24"
}
private_subnet_cidrs = {
"a" = "10.0.2.0/24",
"c" = "10.0.3.0/24"
}
#--------------------------------------------------------------
# RDS
#--------------------------------------------------------------
db_name = "testdb"
db_username = "admin"
構築
手順は以下の通りです。
-
terraform init
して初期化 -
terraform validate
してエラーチェック -
terraform plan
して構成チェック -
terraform apply
してリソース作成
terraform init
して初期化
1. すでにinitしていた方も実行する必要があります。
moduleを使用している場合はterraform init
やterraform get
を実行することでルートモジュールに記載されているモジュールをダウンロードおよび更新することができます。
$ terraform init
Initializing modules...
- ec2 in module/ec2
- network in module/network
- rds in module/rds
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Finding latest version of hashicorp/tls...
- Installing hashicorp/aws v3.66.0...
- Installed hashicorp/aws v3.66.0 (signed by HashiCorp)
- Installing hashicorp/tls v3.1.0...
- Installed hashicorp/tls v3.1.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
terraform validate
してエラーチェック
2. $ terraform validate
Success! The configuration is valid.
terraform plan
して構成チェック
3. $ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.ec2.aws_eip.ec2-eip will be created
+ resource "aws_eip" "ec2-eip" {
+ allocation_id = (known after apply)
+ association_id = (known after apply)
+ carrier_ip = (known after apply)
+ customer_owned_ip = (known after apply)
+ domain = (known after apply)
~~~~~~~~~~~~~~~長いので割愛~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ self = false
+ source_security_group_id = (known after apply)
+ to_port = 3306
+ type = "ingress"
}
Plan: 26 to add, 0 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
terraform apply
してリソース作成
4. Plan: 26 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
module.ec2.tls_private_key._: Creating...
module.network.aws_vpc.vpc: Creating...
~~~~~~~~~~~~~~~長いので割愛~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
module.rds.aws_db_instance.db: Still creating... [3m20s elapsed]
module.rds.aws_db_instance.db: Still creating... [3m30s elapsed]
module.rds.aws_db_instance.db: Creation complete after 3m37s [id=testdb]
Apply complete! Resources: 26 added, 0 changed, 0 destroyed.
RDS接続
手順は以下の通りです。
- AWS Secrets ManagerでDBのパスワードを取得(後述のDBログイン時に使用します)
- セッションマネージャーでEC2にログイン
-
mysql -u admin -p -h [RDSのエンドポイント]
を実行後、DBパスワードを入力しMySQLにログイン
1. AWS Secrets ManagerでDBのパスワードを取得
マネジメントコンソールでAWS Secrets Manager > シークレット > rdsXXXXX(自動で作成されたシークレットの名称)
の順でアクセスします。
シークレットの値を取得するを押下すると作成されたパスワードが表示されます。(後述のDBログイン時に使用します)
2. セッションマネージャーでEC2にログイン
AWSのマネジメントコンソールでEC2に接続します。
作成したEC2インスタンスにチェックを入れ、接続ボタンを押下します。
次にセッションマネージャーから接続の順で押下すると、コンソール画面に遷移します。
mysql -u admin -p -h [RDSのエンドポイント]
を実行後、パスワードを入力しMySQLにログイン
3. エンドポイントはマネジメントコンソールの以下の場所で確認することができます。
RDS > データベース > [作成したDB名]
そして、コンソール画面でmysql -u admin -p -h [RDSのエンドポイント]
を実行します。
パスワードを聞かれますが、先ほどSecrets Managerから控えたパスワードを入力します。
これでRDSに接続することができると思います。
念のため、マネジメントコンソールでRDSのモニタリングを確認してみると、DB接続(カウント)が0から1に変わっていることが分かると思います。
最後に
terraform destroy
を実行しリソースを削除します。
以上になります。見ていただきありがとうございました。
参考
- Amazon RDS(マネージドリレーショナルデータベース)| AWS
- Amazon RDSってなに? – Amazon Web Service(AWS)導入開発支援
- aws_db_instance | Resources | hashicorp/aws | Terraform Registry
- 【RDS構築】terraform AWS環境構築 第5回 - たけログ
- Terraform Aurora MySQL 編 | 30歳未経験からのITエンジニア
- AWSのEC2とRDSをTerraformで構築する Terraform3分クッキング - Qiita
- Terraformを使用してEC2とRDSで基本的なVPCをセットアップする方法-DEVコミュニティ
- 完全初心者向けTerraform入門(AWS)
- 【初心者向け】RDS for MySQLを構築しEC2からアクセスしてみる | DevelopersIO
- TerraformでAuroraを作成する際にセキュアにパスワードを設定したい
- amazon web services - Terraform RDS database credentials - Stack Overflow
Discussion