diff --git a/config/secrets b/config/secrets index ad87e45..490b934 160000 --- a/config/secrets +++ b/config/secrets @@ -1 +1 @@ -Subproject commit ad87e450f7a7e9b718ed9c1cc32908884011f80c +Subproject commit 490b9349618bf38aa9d3cfe7d622908e53a851fe diff --git a/environment/prod/main.tf b/environment/prod/main.tf index 960ead5..9a82f2b 100644 --- a/environment/prod/main.tf +++ b/environment/prod/main.tf @@ -21,6 +21,12 @@ module "prod_stack" { instance_type = var.server_instance_type db_instance_class = var.db_instance_class + # DB EC2 설정 + enable_db_ec2 = true + db_instance_type = var.db_ec2_instance_type + db_ami_id = var.db_ec2_ami_id + db_subnet_id = var.db_ec2_subnet_id + # 보안 그룹 규칙 api_ingress_rules = var.api_ingress_rules db_ingress_rules = var.db_ingress_rules diff --git a/environment/prod/variables.tf b/environment/prod/variables.tf index c81bcf2..ce43938 100644 --- a/environment/prod/variables.tf +++ b/environment/prod/variables.tf @@ -18,6 +18,21 @@ variable "db_instance_class" { type = string } +variable "db_ec2_instance_type" { + description = "DB EC2 인스턴스 타입" + type = string +} + +variable "db_ec2_ami_id" { + description = "DB EC2에 사용할 커스텀 AMI ID" + type = string +} + +variable "db_ec2_subnet_id" { + description = "DB EC2를 배치할 Private Subnet ID" + type = string +} + variable "api_ingress_rules" { description = "List of ingress rules for API Server" type = list(object({ diff --git a/modules/app_stack/db_ec2.tf b/modules/app_stack/db_ec2.tf new file mode 100644 index 0000000..a3b26b1 --- /dev/null +++ b/modules/app_stack/db_ec2.tf @@ -0,0 +1,58 @@ +data "cloudinit_config" "db_init" { + count = var.enable_db_ec2 ? 1 : 0 + gzip = true + base64_encode = true + + part { + content_type = "text/x-shellscript" + content = templatefile("${path.module}/scripts/mysql_setup.sh.tftpl", { + db_root_username_b64 = base64encode(var.db_username) + db_root_password_b64 = base64encode(var.db_password) + mysql_config_content = file("${path.module}/templates/mysql_tuning.cnf") + }) + filename = "mysql_setup.sh" + } +} + +resource "aws_instance" "db_server" { + count = var.enable_db_ec2 ? 1 : 0 + + ami = var.db_ami_id + instance_type = var.db_instance_type + subnet_id = var.db_subnet_id + + vpc_security_group_ids = [aws_security_group.db_ec2_sg[count.index].id] + associate_public_ip_address = false + iam_instance_profile = var.ec2_iam_instance_profile + key_name = var.key_name + + user_data_base64 = data.cloudinit_config.db_init[count.index].rendered + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 1 + } + + root_block_device { + volume_size = 8 + volume_type = "gp3" + encrypted = true + delete_on_termination = true + } + + tags = { + Name = "solid-connection-db-mysql-${var.env_name}" + } + + user_data_replace_on_change = false + + lifecycle { + ignore_changes = [ + user_data, + user_data_base64, + user_data_replace_on_change, + key_name, + ] + } +} diff --git a/modules/app_stack/output.tf b/modules/app_stack/output.tf index e69de29..523c77a 100644 --- a/modules/app_stack/output.tf +++ b/modules/app_stack/output.tf @@ -0,0 +1,9 @@ +output "db_server_private_ip" { + description = "DB EC2 서버 private IP" + value = try(aws_instance.db_server[0].private_ip, null) +} + +output "db_server_instance_id" { + description = "DB EC2 서버 인스턴스 ID" + value = try(aws_instance.db_server[0].id, null) +} diff --git a/modules/app_stack/scripts/mysql_setup.sh.tftpl b/modules/app_stack/scripts/mysql_setup.sh.tftpl new file mode 100644 index 0000000..ff208ac --- /dev/null +++ b/modules/app_stack/scripts/mysql_setup.sh.tftpl @@ -0,0 +1,61 @@ +#!/bin/bash +set -euo pipefail + +DB_ROOT_USER="$(printf '%s' '${db_root_username_b64}' | base64 -d)" +DB_ROOT_PASS="$(printf '%s' '${db_root_password_b64}' | base64 -d)" + +mysql_escape() { + local value="$1" + value="$${value//\\/\\\\}" + value="$${value//\'/\\\'}" + printf '%s' "$value" +} + +DB_ROOT_USER_SQL="$(mysql_escape "$DB_ROOT_USER")" +DB_ROOT_PASS_SQL="$(mysql_escape "$DB_ROOT_PASS")" + +command -v docker >/dev/null +systemctl enable --now docker +docker image inspect mysql:8.4 >/dev/null + +mkdir -p /var/lib/mysql +chown -R 999:999 /var/lib/mysql +chmod 750 /var/lib/mysql + +mkdir -p /etc/mysql/conf.d +cat > /etc/mysql/conf.d/tuning.cnf <<'CNFEOF' +${mysql_config_content} +CNFEOF +chmod 644 /etc/mysql/conf.d/tuning.cnf + +docker rm -f mysql-server 2>/dev/null || true + +docker run -d \ + --name mysql-server \ + --restart always \ + -p 3306:3306 \ + -v /var/lib/mysql:/var/lib/mysql \ + -v /etc/mysql/conf.d:/etc/mysql/conf.d \ + -e MYSQL_ROOT_PASSWORD="$DB_ROOT_PASS" \ + mysql:8.4 + +MYSQL_READY=false +for i in $(seq 1 30); do + if docker exec mysql-server mysqladmin ping -uroot -p"$DB_ROOT_PASS" 2>/dev/null; then + MYSQL_READY=true + break + fi + sleep 2 +done + +if [ "$MYSQL_READY" != "true" ]; then + echo "MySQL container did not become ready within 60 seconds." >&2 + docker logs --tail 100 mysql-server >&2 || true + exit 1 +fi + +docker exec -i mysql-server mysql -uroot -p"$DB_ROOT_PASS" <