diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e56eba1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# ディレクトリ名が「.terraform」であるものを除外 +**/.terraform/* +**/.idea/* +# ファイル名が「*.tfstate」にマッチしているものを除外 +*.tfstate +*.tfstate.* +*.tfvars diff --git a/eventbridge-scheduler-stop-rds/env/.terraform.lock.hcl b/eventbridge-scheduler-stop-rds/env/.terraform.lock.hcl new file mode 100644 index 0000000..a2a1621 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/env/.terraform.lock.hcl @@ -0,0 +1,43 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.82.2" + hashes = [ + "h1:ce6Dw2y4PpuqAPtnQ0dO270dRTmwEARqnfffrE1VYJ8=", + "zh:0262fc96012fb7e173e1b7beadd46dfc25b1dc7eaef95b90e936fc454724f1c8", + "zh:397413613d27f4f54d16efcbf4f0a43c059bd8d827fe34287522ae182a992f9b", + "zh:436c0c5d56e1da4f0a4c13129e12a0b519d12ab116aed52029b183f9806866f3", + "zh:4d942d173a2553d8d532a333a0482a090f4e82a2238acf135578f163b6e68470", + "zh:624aebc549bfbce06cc2ecfd8631932eb874ac7c10eb8466ce5b9a2fbdfdc724", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9e632dee2dfdf01b371cca7854b1ec63ceefa75790e619b0642b34d5514c6733", + "zh:a07567acb115b60a3df8f6048d12735b9b3bcf85ec92a62f77852e13d5a3c096", + "zh:ab7002df1a1be6432ac0eb1b9f6f0dd3db90973cd5b1b0b33d2dae54553dfbd7", + "zh:bc1ff65e2016b018b3e84db7249b2cd0433cb5c81dc81f9f6158f2197d6b9fde", + "zh:bcad84b1d767f87af6e1ba3dc97fdb8f2ad5de9224f192f1412b09aba798c0a8", + "zh:cf917dceaa0f9d55d9ff181b5dcc4d1e10af21b6671811b315ae2a6eda866a2a", + "zh:d8e90ecfb3216f3cc13ccde5a16da64307abb6e22453aed2ac3067bbf689313b", + "zh:d9054e0e40705df729682ad34c20db8695d57f182c65963abd151c6aba1ab0d3", + "zh:ecf3a4f3c57eb7e89f71b8559e2a71e4cdf94eea0118ec4f2cb37e4f4d71a069", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:zG9uFP8l9u+yGZZvi5Te7PV62j50azpgwPunq2vTm1E=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", + ] +} diff --git a/eventbridge-scheduler-stop-rds/env/main.tf b/eventbridge-scheduler-stop-rds/env/main.tf new file mode 100644 index 0000000..5f8d9d7 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/env/main.tf @@ -0,0 +1,14 @@ +terraform { + backend "s3" { + bucket = "eventbridge-scheduler-stop-rds-terraform-prod" + key = "terraform.tfstate" + region = "us-east-2" + } +} + +module "prod" { + source = "../src" + db_database = "foobar_database" + db_username = "admin" + db_exec_username = "foobar_exec_username" +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/aws_caller_identity.tf b/eventbridge-scheduler-stop-rds/src/aws_caller_identity.tf new file mode 100644 index 0000000..6a96b4f --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/aws_caller_identity.tf @@ -0,0 +1 @@ +data "aws_caller_identity" "self" {} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/eventbridge-scheduler_aurora_start_stop.tf b/eventbridge-scheduler-stop-rds/src/eventbridge-scheduler_aurora_start_stop.tf new file mode 100644 index 0000000..bbd7728 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/eventbridge-scheduler_aurora_start_stop.tf @@ -0,0 +1,91 @@ +////////////////////////////////////////////////// +// EventBridgeScheduler +// Aurora クラスタを起動 +////////////////////////////////////////////////// +resource "aws_scheduler_schedule" "aurora_start" { + name = "aurora-start" + group_name = "default" + flexible_time_window { + // フレックスタイムウィンドウは使用しない + mode = "OFF" + } + + // 平日 (MON-FRI) の 8:00 に起動 + // cron(分 時 日 月 曜日 年) の順で指定 + // 年の指定は * にすることで毎年有効となる + schedule_expression = "cron(0 8 ? * MON-FRI *)" + schedule_expression_timezone = "Asia/Tokyo" + + target { + arn = "arn:aws:scheduler:::aws-sdk:rds:startDBCluster" + role_arn = aws_iam_role.eventbridge-scheduler-rds-start-stop-role.arn + input = jsonencode({ + DbClusterIdentifier = aws_rds_cluster.default.cluster_identifier + }) + + retry_policy { + maximum_event_age_in_seconds = 600 // リトライする最大のイベントの経過時間:10分 + maximum_retry_attempts = 1 // リトライ最大回数 + } + } +} + +////////////////////////////////////////////////// +// EventBridgeScheduler +// Aurora クラスタを停止 +////////////////////////////////////////////////// +resource "aws_scheduler_schedule" "aurora_stop" { + name = "aurora-stop" + group_name = "default" + flexible_time_window { + // フレックスタイムウィンドウは使用しない + mode = "OFF" + } + + // 平日 (MON-FRI) の 21:00 に停止 + schedule_expression = "cron(0 21 ? * MON-FRI *)" + schedule_expression_timezone = "Asia/Tokyo" + + target { + arn = "arn:aws:scheduler:::aws-sdk:rds:stopDBCluster" + role_arn = aws_iam_role.eventbridge-scheduler-rds-start-stop-role.arn + input = jsonencode({ + DbClusterIdentifier = aws_rds_cluster.default.cluster_identifier + }) + + retry_policy { + maximum_event_age_in_seconds = 600 // リトライする最大のイベントの経過時間:10分 + maximum_retry_attempts = 1 // リトライ最大回数 + } + } +} + +// 動作確認用 +// (定期的: 毎日 20:20 JST) +#resource "aws_scheduler_schedule" "aurora_stop" { +# name = "aurora-stop" +# group_name = "default" +# flexible_time_window { +# mode = "OFF" +# } +# +# // 毎日 (day-of-week は問わない) 20:20 JST +# // cron(Minute Hour Day-of-month Month Day-of-week Year) +# // 日・月は「*」=毎日、曜日は「?」としています。 +# schedule_expression = "cron(50 20 * * ? *)" +# schedule_expression_timezone = "Asia/Tokyo" +# +# target { +# arn = "arn:aws:scheduler:::aws-sdk:rds:stopDBCluster" +# role_arn = aws_iam_role.eventbridge-scheduler-rds-start-stop-role.arn +# input = jsonencode({ +# DbClusterIdentifier = aws_rds_cluster.default.cluster_identifier +# }) +# +# retry_policy { +# maximum_event_age_in_seconds = 600 +# maximum_retry_attempts = 1 +# } +# } +#} + diff --git a/eventbridge-scheduler-stop-rds/src/iam_policy.tf b/eventbridge-scheduler-stop-rds/src/iam_policy.tf new file mode 100644 index 0000000..3cd47b0 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/iam_policy.tf @@ -0,0 +1,21 @@ +// RDSの起動・停止するポリシー +resource "aws_iam_policy" "rds_start_stop_base" { + name = "rds-start-stop-policy" + path = "/service-role/" + description = "start and stop RDS clusters" + policy = data.aws_iam_policy_document.rds_start_stop_base_policy.json +} + +data "aws_iam_policy_document" "rds_start_stop_base_policy" { + statement { + effect = "Allow" + actions = [ + "rds:StartDBCluster", + "rds:StopDBCluster" + ] + // "arn:aws:rds:{リージョン名}:{アカウントID}:cluster:{クラスターID}" + resources = [ + "arn:aws:rds:us-east-2:${data.aws_caller_identity.self.account_id}:cluster:${aws_rds_cluster.default.cluster_identifier}" + ] + } +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/iam_policy_attachment.tf b/eventbridge-scheduler-stop-rds/src/iam_policy_attachment.tf new file mode 100644 index 0000000..172f6f6 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/iam_policy_attachment.tf @@ -0,0 +1,8 @@ +// EventBridge Scheduler +resource "aws_iam_policy_attachment" "rds_start_stop_base" { + name = "rds-start-stop-base-policy-attachment" + policy_arn = aws_iam_policy.rds_start_stop_base.arn + groups = [] + users = [] + roles = [aws_iam_role.eventbridge-scheduler-rds-start-stop-role.name] +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/iam_role.tf b/eventbridge-scheduler-stop-rds/src/iam_role.tf new file mode 100644 index 0000000..1acab62 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/iam_role.tf @@ -0,0 +1,19 @@ +// EventBridge Schedulerにアタッチする +// RDSの起動・停止するIAMロール +resource "aws_iam_role" "eventbridge-scheduler-rds-start-stop-role" { + name = "eventbridge-scheduler-rds-start-stop-role" + path = "/service-role/" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Principal = { + Service = "scheduler.amazonaws.com" + }, + Action = "sts:AssumeRole" + } + ] + }) +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/init.tf b/eventbridge-scheduler-stop-rds/src/init.tf new file mode 100644 index 0000000..0e02c8f --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/init.tf @@ -0,0 +1,17 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.21" + } + } +} + +provider "aws" { + region = "us-east-2" +} + +provider "aws" { + region = "us-east-1" + alias = "use1" +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/rds_cluster.tf b/eventbridge-scheduler-stop-rds/src/rds_cluster.tf new file mode 100644 index 0000000..5c74599 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/rds_cluster.tf @@ -0,0 +1,40 @@ +resource "random_password" "db_initial_password" { + length = 32 + special = true + override_special = "_%?" + keepers = { // 自動生成 + password_version = "1" + } +} + +resource "aws_rds_cluster" "default" { + cluster_identifier = "aurora-cluster" + engine = "aurora-mysql" + engine_version = "8.0.mysql_aurora.3.08.0" + availability_zones = local.aws_az_codes + port = 3306 + database_name = var.db_database + master_username = "admin" + master_password = random_password.db_initial_password.result + allow_major_version_upgrade = false + backup_retention_period = 5 + + preferred_backup_window = "14:00-14:30" + preferred_maintenance_window = "Sun:17:00-Sun:18:00" + apply_immediately = false + skip_final_snapshot = true + vpc_security_group_ids = [aws_security_group.db.id] + db_subnet_group_name = aws_db_subnet_group.default.name + enabled_cloudwatch_logs_exports = ["error", "slowquery"] + storage_encrypted = false + deletion_protection = false + + lifecycle { + prevent_destroy = true + ignore_changes = [master_password, engine_version, availability_zones] + } + + tags = { + Name = "aurora-cluster" + } +} diff --git a/eventbridge-scheduler-stop-rds/src/rds_cluster_instance.tf b/eventbridge-scheduler-stop-rds/src/rds_cluster_instance.tf new file mode 100644 index 0000000..8cad367 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/rds_cluster_instance.tf @@ -0,0 +1,18 @@ +resource "aws_rds_cluster_instance" "cluster_instances" { + count = 2 + identifier = "aurora-cluster-${count.index}" + cluster_identifier = aws_rds_cluster.default.id + instance_class = "db.t3.medium" + engine = aws_rds_cluster.default.engine + engine_version = aws_rds_cluster.default.engine_version + auto_minor_version_upgrade = false + apply_immediately = false + db_subnet_group_name = aws_db_subnet_group.default.name + // 例: 日本時間で月曜 02:00〜03:00 を想定 + preferred_maintenance_window = "Sun:17:00-Sun:18:00" + monitoring_interval = 0 + + tags = { + Name = "aurora-instance" + } +} diff --git a/eventbridge-scheduler-stop-rds/src/rds_subnet_group.tf b/eventbridge-scheduler-stop-rds/src/rds_subnet_group.tf new file mode 100644 index 0000000..5824c86 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/rds_subnet_group.tf @@ -0,0 +1,8 @@ +resource "aws_db_subnet_group" "default" { + name = "db-subnet-group" + subnet_ids = local.db_subnet_ids + + tags = { + Name = "db-subnet-group" + } +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/variables.tf b/eventbridge-scheduler-stop-rds/src/variables.tf new file mode 100644 index 0000000..4954599 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/variables.tf @@ -0,0 +1,43 @@ +variable "db_database" { + type = string +} + +variable "db_username" { + type = string +} + +variable "db_exec_username" { + type = string +} + + +variable "az_count" { + description = "使用するAZの数" + type = number + // AZ数を指定する + default = 2 +} + +locals { + // 利用可能なAZのリスト + aws_az_list = [ + "us-east-2a", + "us-east-2b", + "us-east-2c" + ] + aws_az_codes = slice(local.aws_az_list, 0, var.az_count) + + // DBサーバ + db_subnet_map = { + for az in local.aws_az_codes : + "db_${az}" => aws_subnet.db[az] + } + // Private Subnets + private_subnet_map = merge( + local.db_subnet_map, + ) + + db_subnet_ids = [ + for key, subnet in local.db_subnet_map : subnet.id + ] +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/vpc.tf b/eventbridge-scheduler-stop-rds/src/vpc.tf new file mode 100644 index 0000000..a01698e --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/vpc.tf @@ -0,0 +1,9 @@ +resource "aws_vpc" "vpc_01" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + instance_tenancy = "default" + tags = { + "Name" = "vpc-01" + } +} \ No newline at end of file diff --git a/eventbridge-scheduler-stop-rds/src/vpc_security_group.tf b/eventbridge-scheduler-stop-rds/src/vpc_security_group.tf new file mode 100644 index 0000000..bca4784 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/vpc_security_group.tf @@ -0,0 +1,16 @@ +resource "aws_security_group" "db" { + name = "db-sg" + description = "DB" + vpc_id = aws_vpc.vpc_01.id + + ingress { + from_port = 3306 + to_port = 3306 + protocol = "tcp" + self = false + } + + tags = { + "Name" = "db-sg" + } +} diff --git a/eventbridge-scheduler-stop-rds/src/vpc_subnet.tf b/eventbridge-scheduler-stop-rds/src/vpc_subnet.tf new file mode 100644 index 0000000..6d54ca6 --- /dev/null +++ b/eventbridge-scheduler-stop-rds/src/vpc_subnet.tf @@ -0,0 +1,20 @@ +// DB subnets +resource "aws_subnet" "db" { + for_each = { + for idx, az in local.aws_az_codes : + az => { + cidr_block = cidrsubnet("10.0.0.0/16", 8, idx + 6) + availability_zone = az + name_suffix = idx + 1 + } + } + + vpc_id = aws_vpc.vpc_01.id + cidr_block = each.value.cidr_block + availability_zone = each.value.availability_zone + map_public_ip_on_launch = false + + tags = { + "Name" = "db-subnet-${each.value.name_suffix}" + } +}