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
27 changes: 12 additions & 15 deletions environment/prod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ data "aws_vpc" "default" {
module "prod_stack" {
source = "../../modules/app_stack"

env_name = "prod"
vpc_id = data.aws_vpc.default.id
env_name = "prod"
vpc_id = data.aws_vpc.default.id

ami_id = var.ami_id

# IAM Instance Profile (SSM + Parameter Store 접근)
ec2_iam_instance_profile = var.ec2_iam_instance_profile

# 키페어 및 접속 허용
key_name = var.key_name
key_name = var.key_name

# 인스턴스 스펙
instance_type = var.server_instance_type
db_instance_class = var.db_instance_class
Expand All @@ -27,29 +27,26 @@ module "prod_stack" {

# RDS 식별자 설정
rds_identifier = var.rds_identifier

# DB 계정 정보
db_username = var.db_root_username
db_password = var.db_root_password
db_username = var.db_root_username
db_password = var.db_root_password

# DB 엔진 및 암호화 설정
db_engine_version = var.db_engine_version # MySQL 버전 지정
db_engine_version = var.db_engine_version # MySQL 버전 지정
db_parameter_group_name = var.db_parameter_group_name # MySQL 파라미터 그룹 지정
kms_key_arn = var.kms_key_arn # KMS ARN 변수 전달
kms_key_arn = var.kms_key_arn # KMS ARN 변수 전달

# 추가 유저마다 다른 권한 부여
additional_db_users = var.additional_db_users

# Nginx 및 도메인 설정
domain_name = var.domain_name
cert_email = var.cert_email
domain_name = var.domain_name
cert_email = var.cert_email
nginx_conf_name = var.nginx_conf_name

# ssh key 경로 전달
ssh_key_path = var.ssh_key_path

# Side Infra 관련 변수 전달
work_dir = var.work_dir
work_dir = var.work_dir
alloy_env_name = var.alloy_env_name

redis_version = var.redis_version
Expand Down
5 changes: 0 additions & 5 deletions environment/prod/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,6 @@ variable "nginx_conf_name" {
type = string
}

variable "ssh_key_path" {
description = "Path to the SSH private key file for remote-exec"
type = string
}

variable "work_dir" {
description = "Working directory for the application"
type = string
Expand Down
3 changes: 0 additions & 3 deletions environment/stage/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ module "stage_stack" {
cert_email = var.cert_email
nginx_conf_name = var.nginx_conf_name

# ssh key 경로 전달
ssh_key_path = var.ssh_key_path

# Side Infra 관련 변수 전달
work_dir = var.work_dir
alloy_env_name = var.alloy_env_name
Expand Down
5 changes: 0 additions & 5 deletions environment/stage/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ variable "nginx_conf_name" {
type = string
}

variable "ssh_key_path" {
description = "Path to the SSH private key file for remote-exec"
type = string
}

variable "work_dir" {
description = "Working directory for the application"
type = string
Expand Down
151 changes: 97 additions & 54 deletions modules/app_stack/ec2.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,37 @@ resource "aws_instance" "api_server" {
}
}

# 설정 및 컨테이너 실행
locals {
nginx_script_b64 = base64encode(templatefile("${path.module}/scripts/nginx_setup.sh.tftpl", {
domain_name = var.domain_name
email = var.cert_email
conf_file_name = var.nginx_conf_name
}))

alloy_config = templatefile("${path.module}/../../config/side-infra/config.alloy.tftpl", {
loki_ip = data.aws_instance.monitoring_server.private_ip
})

side_infra_script_b64 = base64encode(templatefile("${path.module}/scripts/side_infra_setup.sh.tftpl", {
work_dir = var.work_dir
alloy_env_name = var.alloy_env_name
alloy_config_content = local.alloy_config
redis_version = var.redis_version
redis_exporter_version = var.redis_exporter_version
alloy_version = var.alloy_version
}))

nginx_ssm_params = jsonencode({
commands = ["cloud-init status --wait > /dev/null", "echo ${local.nginx_script_b64} | base64 -d | sudo bash"]
executionTimeout = ["3600"]
})

side_infra_ssm_params = jsonencode({
commands = ["cloud-init status --wait > /dev/null", "echo ${local.side_infra_script_b64} | base64 -d | sudo bash"]
executionTimeout = ["3600"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})
}

# [리소스 1] Nginx 설정 변경 감지 및 실행
resource "null_resource" "update_nginx" {
depends_on = [aws_instance.api_server]
Expand All @@ -68,30 +98,39 @@ resource "null_resource" "update_nginx" {
}))
}

connection {
type = "ssh"
user = "ubuntu"
host = aws_instance.api_server.public_ip
private_key = file(var.ssh_key_path)
}

provisioner "file" {
content = templatefile("${path.module}/scripts/nginx_setup.sh.tftpl", {
domain_name = var.domain_name
email = var.cert_email
conf_file_name = var.nginx_conf_name
})
destination = "/tmp/update_nginx.sh"
}

provisioner "remote-exec" {
inline = [
"cloud-init status --wait > /dev/null", # Docker 설치 대기
"chmod +x /tmp/update_nginx.sh",
"echo 'Running Updated Nginx Script...'",
"sudo /tmp/update_nginx.sh",
"rm /tmp/update_nginx.sh"
]
provisioner "local-exec" {
interpreter = ["bash", "-c"]

@Hexeong Hexeong Jun 13, 2026

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.

이미 저희 운영 방칙 상 액션 러너(우분투)로 진행하도록 합의를 했었기에 괜찮다고 생각합니다. 이미 로컬 apply를 막게 정했기에 문제가 없을 것 같습니다! 해당 부분은 협업 워크플로우 wiki 문서에 문서화되어 있으니 확인부탁드립니다~

command = <<-EOT
set -euo pipefail
INSTANCE_ID='${aws_instance.api_server.id}'
COMMAND_ID=$(aws ssm send-command \

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.

여기서 Terraform 실행 주체가 직접 aws ssm send-command를 호출합니다.

EC2 IAM Instance Profile의 AmazonSSMManagedInstanceCore는 명령을 받는 managed instance 권한이고, 명령을 보내는 GitHub Actions apply role에는 별도로 최소 아래 권한이 필요합니다.

ssm:SendCommand
ssm:GetCommandInvocation

현재 bootstrap 코드에서는 GitHubActionsTerraformInfraPolicy를 수동 관리 policy로 참조만 해서, 해당 권한 포함 여부가 코드상 확인되지 않습니다. 포함되어 있지 않으면 merge 후 apply에서 AccessDeniedException으로 실패할 수 있으니, 실제 policy 반영 여부를 확인하거나 PR 본문에 확인 완료를 명시하는 게 좋겠습니다.

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.

해당 권한은 이미 확인했습니다. PR 설명에 오해가 있었네요;; 해당 설명 수정하였습니다!

--instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \
--parameters '${local.nginx_ssm_params}' \
--output text \
--query "Command.CommandId")
ATTEMPTS=0
while [ "$ATTEMPTS" -lt 360 ]; do
STATUS=$(aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--query "Status" --output text 2>/dev/null || echo "Pending")
case "$STATUS" in
Success) exit 0 ;;
Failed|Cancelled|TimedOut|Undeliverable)
echo "SSM command $STATUS" >&2
aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--query "StandardErrorContent" --output text >&2
exit 1 ;;
esac
ATTEMPTS=$((ATTEMPTS + 1))
sleep 10
done
echo "SSM command timed out after 3600s" >&2
exit 1
EOT
}
}

Expand All @@ -112,34 +151,38 @@ resource "null_resource" "update_side_infra" {
}))
}

connection {
type = "ssh"
user = "ubuntu"
host = aws_instance.api_server.public_ip
private_key = file(var.ssh_key_path)
}

provisioner "file" {
content = templatefile("${path.module}/scripts/side_infra_setup.sh.tftpl", {
work_dir = var.work_dir
alloy_env_name = var.alloy_env_name
alloy_config_content = templatefile("${path.module}/../../config/side-infra/config.alloy.tftpl", {
loki_ip = data.aws_instance.monitoring_server.private_ip
})
redis_version = var.redis_version
redis_exporter_version = var.redis_exporter_version
alloy_version = var.alloy_version
})
destination = "/tmp/update_side_infra.sh"
}

provisioner "remote-exec" {
inline = [
"cloud-init status --wait > /dev/null", # Docker 설치 대기
"chmod +x /tmp/update_side_infra.sh",
"echo 'Running Updated Side Infra Script...'",
"sudo /tmp/update_side_infra.sh",
"rm /tmp/update_side_infra.sh"
]
provisioner "local-exec" {
interpreter = ["bash", "-c"]
command = <<-EOT
set -euo pipefail
INSTANCE_ID='${aws_instance.api_server.id}'
COMMAND_ID=$(aws ssm send-command \
--instance-ids "$INSTANCE_ID" \
--document-name "AWS-RunShellScript" \
--parameters '${local.side_infra_ssm_params}' \
--output text \
--query "Command.CommandId")
ATTEMPTS=0
while [ "$ATTEMPTS" -lt 360 ]; do
STATUS=$(aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--query "Status" --output text 2>/dev/null || echo "Pending")
case "$STATUS" in
Success) exit 0 ;;
Failed|Cancelled|TimedOut|Undeliverable)
echo "SSM command $STATUS" >&2
aws ssm get-command-invocation \
--command-id "$COMMAND_ID" \
--instance-id "$INSTANCE_ID" \
--query "StandardErrorContent" --output text >&2
exit 1 ;;
esac
ATTEMPTS=$((ATTEMPTS + 1))
sleep 10
done
echo "SSM command timed out after 3600s" >&2
exit 1
EOT
}
}
20 changes: 10 additions & 10 deletions modules/app_stack/rds.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
resource "aws_db_instance" "default" {
count = var.enable_rds ? 1 : 0

identifier = var.rds_identifier
allocated_storage = 20
engine = "mysql"
engine_version = var.db_engine_version
instance_class = var.db_instance_class
username = var.db_username
password = var.db_password
parameter_group_name = var.db_parameter_group_name
copy_tags_to_snapshot = true
skip_final_snapshot = true
identifier = var.rds_identifier
allocated_storage = 20
engine = "mysql"
engine_version = var.db_engine_version
instance_class = var.db_instance_class
username = var.db_username
password = var.db_password
parameter_group_name = var.db_parameter_group_name
copy_tags_to_snapshot = true
skip_final_snapshot = true
vpc_security_group_ids = [aws_security_group.db_sg[count.index].id]

storage_encrypted = true
Expand Down
6 changes: 0 additions & 6 deletions modules/app_stack/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,6 @@ variable "nginx_conf_name" {
type = string
}

# [Remote SSH용 변수]
variable "ssh_key_path" {
description = "Path to the SSH private key file for remote-exec"
type = string
}

# [Side Infrastructure 관련 변수]
variable "work_dir" {
description = "Working directory for the application"
Expand Down
Loading