Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/secrets
9 changes: 5 additions & 4 deletions environment/prod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ module "prod_stack" {
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
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
db_data_volume_size = var.db_data_volume_size

# 보안 그룹 규칙
api_ingress_rules = var.api_ingress_rules
Expand Down
5 changes: 5 additions & 0 deletions environment/prod/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ variable "db_ec2_subnet_id" {
type = string
}

variable "db_data_volume_size" {
description = "DB EC2 MySQL data volume 크기 (GiB)"
type = number
}

variable "api_ingress_rules" {
description = "List of ingress rules for API Server"
type = list(object({
Expand Down
38 changes: 34 additions & 4 deletions modules/app_stack/db_ec2.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
data "aws_subnet" "db_ec2" {
count = var.enable_db_ec2 ? 1 : 0

id = var.db_subnet_id
}

resource "aws_ebs_volume" "db_data" {
count = var.enable_db_ec2 ? 1 : 0

availability_zone = data.aws_subnet.db_ec2[count.index].availability_zone
size = var.db_data_volume_size
type = "gp3"
encrypted = true

tags = {
Name = "solid-connection-db-mysql-data-${var.env_name}"
}

lifecycle {
prevent_destroy = true
}
}

data "cloudinit_config" "db_init" {
count = var.enable_db_ec2 ? 1 : 0
gzip = true
Expand All @@ -8,6 +31,7 @@ data "cloudinit_config" "db_init" {
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)
db_data_volume_id = aws_ebs_volume.db_data[count.index].id
mysql_config_content = file("${path.module}/templates/mysql_tuning.cnf")
})
filename = "mysql_setup.sh"
Expand Down Expand Up @@ -45,14 +69,20 @@ resource "aws_instance" "db_server" {
Name = "solid-connection-db-mysql-${var.env_name}"
}

user_data_replace_on_change = false
user_data_replace_on_change = true

lifecycle {
ignore_changes = [
user_data,
user_data_base64,
user_data_replace_on_change,
key_name,
]
}
}

resource "aws_volume_attachment" "db_data" {
count = var.enable_db_ec2 ? 1 : 0

device_name = "/dev/sdf"
volume_id = aws_ebs_volume.db_data[count.index].id
instance_id = aws_instance.db_server[count.index].id
stop_instance_before_detaching = true
}
5 changes: 5 additions & 0 deletions modules/app_stack/output.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ output "db_server_instance_id" {
description = "DB EC2 서버 인스턴스 ID"
value = try(aws_instance.db_server[0].id, null)
}

output "db_server_data_volume_id" {
description = "DB EC2 MySQL data volume ID"
value = try(aws_ebs_volume.db_data[0].id, null)
}
59 changes: 54 additions & 5 deletions modules/app_stack/scripts/mysql_setup.sh.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,61 @@ 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
command -v blkid >/dev/null
command -v mkfs.ext4 >/dev/null
command -v mountpoint >/dev/null

systemctl enable docker

DATA_VOLUME_ID="${db_data_volume_id}"
DATA_VOLUME_SERIAL="$(printf '%s' "$DATA_VOLUME_ID" | tr -d '-')"
DATA_VOLUME_DEVICE="/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_$DATA_VOLUME_SERIAL"
Comment thread
Hexeong marked this conversation as resolved.
DATA_VOLUME_MOUNT="/mnt/mysql-data"
MYSQL_DATA_DIR="$DATA_VOLUME_MOUNT/mysql"

for i in $(seq 1 120); do
if [ -e "$DATA_VOLUME_DEVICE" ]; then
break
fi
sleep 2
done

if [ ! -e "$DATA_VOLUME_DEVICE" ]; then
echo "Data volume $DATA_VOLUME_ID was not attached within 240 seconds." >&2
ls -la /dev/disk/by-id >&2 || true
exit 1
fi

if ! blkid "$DATA_VOLUME_DEVICE" >/dev/null 2>&1; then
mkfs.ext4 -F "$DATA_VOLUME_DEVICE"
fi

mkdir -p "$DATA_VOLUME_MOUNT"
DATA_VOLUME_UUID="$(blkid -s UUID -o value "$DATA_VOLUME_DEVICE")"

awk -v mount="$DATA_VOLUME_MOUNT" '$2 != mount { print }' /etc/fstab > /etc/fstab.tmp
printf 'UUID=%s %s ext4 defaults,nofail 0 2\n' "$DATA_VOLUME_UUID" "$DATA_VOLUME_MOUNT" >> /etc/fstab.tmp
mv /etc/fstab.tmp /etc/fstab
Comment on lines +50 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 /etc/fstabnofail로 등록하고 있고, MySQL 컨테이너는 --restart always입니다. 첫 부팅 때는 bootstrap script가 EBS attachment를 기다린 뒤 mount하지만, 이후 재부팅에서는 cloud-init이 다시 돌지 않고 Docker가 컨테이너를 자동 시작할 수 있습니다.

이때 EBS mount가 실패하거나 지연되면 /var/lib/mysql이 root volume의 빈 디렉터리로 열리고, MySQL이 다른 datadir로 떠버릴 가능성이 있습니다. 운영 DB에서는 꽤 위험한 케이스라서 nofail 제거 또는 MySQL 컨테이너 systemd unit/drop-in에 RequiresMountsFor=/var/lib/mysql 같은 mount 의존성을 추가하는 방식이 필요해 보입니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지적해주신 부분 감사합니다! mount 의존성을 추가하는 방식이 가장 안정적일 것 같아서 해당 내용 반영했습니다!


mountpoint -q "$DATA_VOLUME_MOUNT" || mount "$DATA_VOLUME_MOUNT"
mountpoint -q "$DATA_VOLUME_MOUNT" || {
echo "Failed to mount data volume $DATA_VOLUME_ID at $DATA_VOLUME_MOUNT." >&2
exit 1
}

mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/10-require-mysql-data.conf <<EOF
[Unit]
RequiresMountsFor=$DATA_VOLUME_MOUNT
After=local-fs.target
EOF
systemctl daemon-reload
systemctl restart docker
docker image inspect mysql:8.4.8 >/dev/null

mkdir -p /var/lib/mysql
chown -R 999:999 /var/lib/mysql
chmod 750 /var/lib/mysql
mkdir -p "$MYSQL_DATA_DIR"
chown -R 999:999 "$MYSQL_DATA_DIR"
chmod 750 "$MYSQL_DATA_DIR"

mkdir -p /etc/mysql/conf.d
cat > /etc/mysql/conf.d/tuning.cnf <<'CNFEOF'
Expand All @@ -34,7 +83,7 @@ docker run -d \
--name mysql-server \
--restart always \
-p 3306:3306 \
-v /var/lib/mysql:/var/lib/mysql \
-v "$MYSQL_DATA_DIR:/var/lib/mysql" \
-v /etc/mysql/conf.d:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD="$DB_ROOT_PASS" \
mysql:8.4.8
Expand Down
16 changes: 16 additions & 0 deletions modules/app_stack/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ variable "db_subnet_id" {
default = null
}

variable "db_data_volume_size" {
description = "DB EC2 MySQL data volume 크기 (GiB)"
type = number
default = null

validation {
condition = !var.enable_db_ec2 || var.db_data_volume_size != null
error_message = "enable_db_ec2가 true이면 db_data_volume_size를 지정해야 합니다."
}

validation {
condition = var.db_data_volume_size == null ? true : var.db_data_volume_size >= 1
error_message = "db_data_volume_size는 1GiB 이상이어야 합니다."
}
}

variable "ec2_iam_instance_profile" {
description = "EC2에 연결할 IAM Instance Profile 이름"
type = string
Expand Down
Loading