Terraform Aurora MySQL 編

  • Terraform
  • RDS
  • MySQL
  • 今回は RDS を構築してみたいと思います。

    これまでに、「VPC編」、「terraform.tfvars編」、「EC2編」とやってきましたので、だいたいコツが掴めてきました。

    今回は、AuroraMySQL(ver 2.08.1) のクラスター構成(インスタンス2台)でやってみたいと思います。

    ※EC2を作成するところまでは「EC2編」のものを継承して行います。

    Resource

    まずは、Terraform の AWS provider ページから、RDS に必要な resource の仕様について目を通します。

    DBクラスター作成するために「DBサブネット」「セキュリティグループ」「クラスターパラメータグループ」「DBパラメータグループ」「オプショングループ」を用意します。

    全体のファイル構成は以下の通りです。

    terraform-aws-test03 |-- .terraform |-- ec2.tf |-- key_pair.tf |-- provider.tf |-- rds.tf |-- security_group.tf |-- subnet.tf |-- terraform.tfstate.backup |-- terraform.tfvars |-- variables.tf |-- version.tf |-- vpc.tf

    サービス毎にファイルを分けても大丈夫なのでしょうか?と私は疑問に思っていました。

    先にネットワークコンポーネントから作成していかないと、EC2 や RDS の構築ができないはずです。

    ですが、Terraform のすごいところは、この resource の依存関係を自動的に解決して処理をしてくれることです。

    世の中で注目されるのも頷けますね。

    DBサブネット作成 count / index / length

    DBサブネットを作成するためには、最低でも2つ以上のサブネットが必要です。

    そこで、Terraform の cidrsubnet 関数と、count / index / length を使って動的にサブネットを作成します。

    構成としては、EC2用のサブネット "subnet_ec2" 1つとDBサブネット用の "subnet_rds" 3つを作成します。

    まずは、結論から書きますとこのようになります。

    subnet.tf
    # Subnet resource "aws_subnet" "subnet_ec2" { count = 1 vpc_id = aws_vpc.vpc.id cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 12, count.index) map_public_ip_on_launch = true # Required to set Public IP when creating EC2 tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # cidrsubnet Function resource "aws_subnet" "subnet_rds" { count = 3 vpc_id = aws_vpc.vpc.id cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 12, count.index + length(aws_subnet.subnet_ec2)) map_public_ip_on_launch = false availability_zone = var.aws_rds_az[count.index] tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # DB Subnet resource "aws_db_subnet_group" "db_subnet" { name = var.rds_db_subnet_group subnet_ids = [ aws_subnet.subnet_rds.0.id, aws_subnet.subnet_rds.1.id, aws_subnet.subnet_rds.2.id ] tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # Route Table resource "aws_route_table" "route_table" { vpc_id = aws_vpc.vpc.id tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # Route resource "aws_route" "route_ec2" { destination_cidr_block = "0.0.0.0/0" route_table_id = aws_route_table.route_table.id gateway_id = aws_internet_gateway.igw.id } # Route Table Asoociation resource "aws_route_table_association" "route_table_association" { subnet_id = aws_subnet.subnet_ec2.0.id route_table_id = aws_route_table.route_table.id }

    cidrsubnet 関数と、count / index / length

    赤くしたところが cidrsubnet 関数と、count / index / length を使用している部分です。

    によって、count を使い sabunet を自動的に複数作成することができます。

    今回の例では、aws_vpc.vpc.cidr_block = "10.100.0.0/16" としていました。上記の例では、以下のように解釈されます。

    # もともとはこうでした。 cidrsubnet(aws_vpc.vpc.cidr_block, 12, count.index + length(aws_subnet.subnet_ec2)) # aws_vpc.vpc.cidr_block = "10.100.0.0/16" です。 cidrsubnet("10.100.0.0/16", 12, count.index + length(aws_subnet.subnet_ec2)) # 1つ目の処理では、 count.index = "1" です。 cidrsubnet("10.100.0.0/16", 12, 1 + length(aws_subnet.subnet_ec2)) # length は要素の個数を表すので、今回は1個です。 cidrsubnet("10.100.0.0/16", 12, 1 + 1) # 1 + 1 = 2 です。 cidrsubnet("10.100.0.0/16", 12, 2) # cidrsubnet 関数を参考に計算します。 # 12 という数字は以下を表現しています。 "10.100.0.0/28" "10.100.0.16/28" "10.100.0.32/28" "10.100.0.48/28" ... # 2 という数字はその中の2番目を表現します。つまり、 "10.100.0.16/28" # 2つ目の処理では、count.index = "2" ですので、 "10.100.0.32/28" # 3つ目の処理では、count.index = "3" ですので、 "10.100.0.48/28"

    count で作成した Subnet の参照方法

    count を使って作成した Subnet は、そのまま aws_subnet.subnet_rds.id とやろうとしても Error となります。

    aws_subnet.subnet_rds は内部的には配列になっています。そのため、参照するためには index を指定する必要があります。

    それが、DB Subnet を作成する部分に表現されています。

    index はゼロから始まるので以下のような指定の仕方になります。

    subnet_ids = [ aws_subnet.subnet_rds.0.id, aws_subnet.subnet_rds.1.id, aws_subnet.subnet_rds.2.id ]

    セキュリティグループ

    セキュリティグループはこんな感じです。

    security_group.tf
    # Security Group resource "aws_security_group" "public_security_group" { name = var.aws_public_sg description = "Twemily EC2 Public Security Group" vpc_id = aws_vpc.vpc.id ingress { description = "SSH" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = [var.my_home_ip, var.my_office_ip] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } resource "aws_security_group" "private_security_group" { name = var.aws_private_sg description = "Twemily RDS Private Security Group" vpc_id = aws_vpc.vpc.id ingress { description = "MySQL" from_port = 3306 to_port = 3306 protocol = "tcp" security_groups = [ aws_security_group.public_security_group.id ] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } }

    オプション・パラメータグループ

    クラスターパラメータグループは、後で使いやすいように文字コードを "utf8mb4" に揃え、collation を "utf8mb4_bin" に揃えます。

    TimeZone もデフォルトは UTC ですので、東京にしています。

    DBパラメータグループとオプショングループの設定は特に変更せず、とりあえず作っただけになっています。

    rds.tf
    # aws_rds_cluster_parameter_group resource "aws_rds_cluster_parameter_group" "rds_cluster_parameter_group" { name = var.rds_cluster_parameter_name family = var.rds_cluster_parameter_family tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } parameter { name = "character_set_client" value = var.rds_cluster_parameter_character apply_method = "immediate" } parameter { name = "character_set_connection" value = var.rds_cluster_parameter_character apply_method = "immediate" } parameter { name = "character_set_database" value = var.rds_cluster_parameter_character apply_method = "immediate" } parameter { name = "character_set_filesystem" value = var.rds_cluster_parameter_character apply_method = "immediate" } parameter { name = "character_set_results" value = var.rds_cluster_parameter_character apply_method = "immediate" } parameter { name = "character_set_server" value = var.rds_cluster_parameter_character apply_method = "immediate" } parameter { name = "collation_connection" value = var.rds_cluster_parameter_collation apply_method = "immediate" } parameter { name = "collation_server" value = var.rds_cluster_parameter_collation apply_method = "immediate" } parameter { name = "time_zone" value = var.rds_cluster_parameter_timezone apply_method = "immediate" } } # aws_db_parameter_group resource "aws_db_parameter_group" "db_parameter_group" { name = var.rds_db_parameter_name family = var.rds_db_parameter_family tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # aws_db_option_group resource "aws_db_option_group" "db_option_group" { name = var.rds_db_option_name engine_name = var.rds_db_option_engine_name major_engine_version = var.rds_db_option_major_engine tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } }

    以上のところまで書いたところで、Terraform apply を実行しておきました。

    AuroraMySQL の構築

    さて、いよいよ本丸です。

    インスタンスは3台構成としましょう。先ほどの count を使って作ってみます。

    rds.tf
    # aws_rds_cluster resource "aws_rds_cluster" "rds_cluster" { cluster_identifier = var.rds_cluster_identifier engine = var.rds_cluster_engine engine_version = var.rds_cluster_engine_version copy_tags_to_snapshot = true skip_final_snapshot = true monitoring_interval = 60 database_name = var.rds_cluster_database_name db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.rds_cluster_parameter_group.name db_subnet_group_name = aws_db_subnet_group.db_subnet.name master_username = var.rds_db_master_user master_password = var.rds_db_master_user_pass port = 3306 vpc_security_group_ids = [ aws_security_group.private_security_group.id ] tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # aws_rds_cluster_instance resource "aws_rds_cluster_instance" "rds_cluster_instance" { count = var.rds_instance_count # count = 3 identifier = "${var.rds_instance_identifier}-${count.index}" cluster_identifier = aws_rds_cluster.rds_cluster.cluster_identifier instance_class = var.rds_instance_class monitoring_interval = 60 db_subnet_group_name = aws_db_subnet_group.db_subnet.name db_parameter_group_name = aws_db_parameter_group.db_parameter_group.name copy_tags_to_snapshot = true performance_insights_enabled = false tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } }

    Terraform apply からの Error !

    さて、緊張の Terraform コマンド実行です。

    $ Terraform plan Plan: 4 to add, 0 to change, 0 to destroy. $ Terraform apply Error: error creating RDS DB Instance: InvalidParameterCombination: Cannot find version 5.7.mysql_aurora.2.08.0 for aurora status code: 400, request id: f3db0ccb-81b9-4e8e-9eaf-84f49e8a6ed6

    ふむふむ「Cannot find version 5.7.mysql_aurora.2.08.0 for aurora」バージョンが存在しないだと。。。

    もしかして、クラスターからエンジン種別が継承できていないのか?と思い、インスタンス側にも "engine" "engine_version" を指定して再実行してみました。

    rds.tf
    # aws_rds_cluster_instance resource "aws_rds_cluster_instance" "rds_cluster_instance" { count = var.rds_instance_count identifier = "${var.rds_instance_identifier}-${count.index}" cluster_identifier = aws_rds_cluster.rds_cluster.cluster_identifier instance_class = var.rds_instance_class engine = var.rds_cluster_engine # 追記 engine_version = var.rds_cluster_engine_version # 追記 monitoring_interval = 60 db_subnet_group_name = aws_db_subnet_group.db_subnet.name db_parameter_group_name = aws_db_parameter_group.db_parameter_group.name copy_tags_to_snapshot = true performance_insights_enabled = false tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } }

    さあ、これでどうでしょうか。

    $ Terraform plan Plan: 3 to add, 0 to change, 0 to destroy. $ Terraform apply ... aws_rds_cluster_instance.rds_cluster_instance[2]: Creating... aws_rds_cluster_instance.rds_cluster_instance[0]: Creating... aws_rds_cluster_instance.rds_cluster_instance[1]: Creating... ... Error: error creating RDS DB Instance: InvalidParameterCombination: A MonitoringRoleARN value is required if you specify a MonitoringInterval value other than 0. status code: 400, request id: 8a57597d-40ee-40a7-9a5f-868d8afc34a1

    またまた、Error が出てしまいました。

    "monitoring_interval" は IAM Role がないと指定できないようですね。"monitoring_interval = 60" は削除します。

    ただ、DBインスタンスは作成されてしまっているようです。

    terraform-rds-01

    ハマった!

    先の画面からインスタンスが『作成中』から全く進みません。取り消すことも出来ません。

    そのため、一旦全て削除(terraform destroy)しました。

    "monitoring_interval = 60" を削除した状態で再度実行してみます。

    rds.tf
    # aws_rds_cluster resource "aws_rds_cluster" "rds_cluster" { cluster_identifier = var.rds_cluster_identifier engine = var.rds_cluster_engine engine_version = var.rds_cluster_engine_version copy_tags_to_snapshot = true skip_final_snapshot = true database_name = var.rds_cluster_database_name db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.rds_cluster_parameter_group.name db_subnet_group_name = aws_db_subnet_group.db_subnet.name master_username = var.rds_db_master_user master_password = var.rds_db_master_user_pass port = 3306 vpc_security_group_ids = [ aws_security_group.private_security_group.id ] tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } } # aws_rds_cluster_instance resource "aws_rds_cluster_instance" "rds_cluster_instance" { count = var.rds_instance_count identifier = "${var.rds_instance_identifier}-${count.index}" cluster_identifier = aws_rds_cluster.rds_cluster.cluster_identifier instance_class = var.rds_instance_class engine = var.rds_cluster_engine engine_version = var.rds_cluster_engine_version db_subnet_group_name = aws_db_subnet_group.db_subnet.name db_parameter_group_name = aws_db_parameter_group.db_parameter_group.name copy_tags_to_snapshot = true performance_insights_enabled = false tags = { Service = var.aws_tags_service Owner = var.aws_tags_owner } }

    では、いきます。

    $ Terraform plan Plan: 21 to add, 0 to change, 0 to destroy. $ Terraform apply Apply complete! Resources: 21 added, 0 changed, 0 destroyed.

    できました!!

    ここまで長かったですね。

    接続確認

    では、EC2 に接続したのち、mysql コマンドで接続を試みてみましょう。

    terraform-rds-02

    無事に接続することが出来ました。また、TimeZone も東京時間になっていることを確認しました。

    次回は S3 に terraform.tfstate を配置したいと思います。Terraform の output 機能も使ってみたいと思っています。

  • Terraform
  • RDS
  • MySQL