From d2911298186dcd23aea6de4615ccf0fc5694b590 Mon Sep 17 00:00:00 2001 From: Hexeong <123macanic@naver.com> Date: Wed, 8 Jul 2026 14:09:10 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20monitoring=20nginx=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=84=9C=20=EC=9E=90=EB=8F=99=20=EA=B0=B1=EC=8B=A0=20?= =?UTF-8?q?=EC=95=88=EC=A0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- environment/monitoring/main.tf | 15 ++-- environment/monitoring/variables.tf | 7 +- modules/monitoring_stack/ec2.tf | 86 +++++++++++++++++-- .../scripts/nginx_setup.sh.tftpl | 61 ++++++++++--- modules/monitoring_stack/variables.tf | 8 +- 5 files changed, 150 insertions(+), 27 deletions(-) diff --git a/environment/monitoring/main.tf b/environment/monitoring/main.tf index 778cd4d..2b74f8b 100644 --- a/environment/monitoring/main.tf +++ b/environment/monitoring/main.tf @@ -7,20 +7,21 @@ module "monitoring_stack" { # 기존 app_stack 모듈을 재사용하거나, 모니터링 전용 모듈이 있다면 경로 수정 source = "../../modules/monitoring_stack" - env_name = "monitoring" - vpc_id = data.aws_vpc.default.id + env_name = "monitoring" + vpc_id = data.aws_vpc.default.id - ami_id = var.ami_id + ami_id = var.ami_id - key_name = var.key_name + key_name = var.key_name + ec2_iam_instance_profile = var.ec2_iam_instance_profile - instance_type = var.monitoring_instance_type + instance_type = var.monitoring_instance_type private_ip = var.private_ip # 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 diff --git a/environment/monitoring/variables.tf b/environment/monitoring/variables.tf index b22341c..723dac1 100644 --- a/environment/monitoring/variables.tf +++ b/environment/monitoring/variables.tf @@ -13,6 +13,11 @@ variable "key_name" { type = string } +variable "ec2_iam_instance_profile" { + description = "EC2 instance profile name for SSM RunCommand" + type = string +} + variable "monitoring_ingress_rules" { description = "Ingress rules for Grafana(3000), Prometheus(9090), Loki(3100)" type = list(object({ @@ -26,7 +31,7 @@ variable "monitoring_ingress_rules" { variable "private_ip" { description = "Fixed private ip for alloy config" - type = string + type = string } variable "domain_name" { diff --git a/modules/monitoring_stack/ec2.tf b/modules/monitoring_stack/ec2.tf index 9de07dd..e7f5d88 100644 --- a/modules/monitoring_stack/ec2.tf +++ b/modules/monitoring_stack/ec2.tf @@ -1,3 +1,18 @@ +locals { + nginx_setup_script = templatefile("${path.module}/scripts/nginx_setup.sh.tftpl", { + domain_name = var.domain_name + email = var.cert_email + conf_file_name = var.nginx_conf_name + }) + + nginx_script_b64 = base64encode(local.nginx_setup_script) + + nginx_ssm_params = jsonencode({ + commands = ["cloud-init status --wait > /dev/null", "echo ${local.nginx_script_b64} | base64 -d | sudo bash"] + executionTimeout = ["3600"] + }) +} + data "cloudinit_config" "app_init" { gzip = true base64_encode = true @@ -12,17 +27,13 @@ data "cloudinit_config" "app_init" { # [Part 2] Nginx 설정 스크립트 파일 생성 (실행 안 함, 파일만 생성) part { content_type = "text/cloud-config" - content = </dev/null || echo "Offline") + if [ "$PING_STATUS" = "Online" ]; then + break + fi + ATTEMPTS=$((ATTEMPTS + 1)) + sleep 10 + done + if [ "$PING_STATUS" != "Online" ]; then + echo "SSM agent did not become online after 600s" >&2 + exit 1 + fi + + COMMAND_ID=$(aws ssm send-command \ + --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 + } +} diff --git a/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl b/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl index 251860c..938d59f 100644 --- a/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl +++ b/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl @@ -5,8 +5,9 @@ set -e DOMAIN="${domain_name}" EMAIL="${email}" CONF_NAME="${conf_file_name}" +CERTBOT_WEBROOT="/var/www/certbot" -echo ">>> [수동 실행] $DOMAIN 에 대한 Nginx 및 SSL 설정을 시작합니다." +echo ">>> $DOMAIN 에 대한 Nginx 및 SSL 설정을 시작합니다." # 1. 패키지 설치 sudo apt-get update @@ -18,19 +19,52 @@ sudo /opt/certbot/bin/pip install --upgrade pip sudo /opt/certbot/bin/pip install certbot certbot-nginx sudo ln -sf /opt/certbot/bin/certbot /usr/bin/certbot -# 3. SSL 인증서 발급 -sudo systemctl stop nginx -sudo certbot certonly --standalone \ +# 3. HTTP-01 challenge를 nginx가 처리할 수 있도록 webroot 설정 +sudo mkdir -p "$CERTBOT_WEBROOT" + +sudo tee /etc/nginx/sites-available/$CONF_NAME > /dev/null < /dev/null </dev/null | grep -q "certbot renew"; then - (crontab -l 2>/dev/null; echo "0 0,12 * * * /usr/bin/certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab - -fi +# 7. 자동 갱신 크론 등록 +sudo tee /etc/cron.d/certbot-$CONF_NAME > /dev/null < Date: Wed, 8 Jul 2026 14:17:41 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20app=20nginx=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=EC=84=9C=20=EC=9E=90=EB=8F=99=20=EA=B0=B1=EC=8B=A0=20=EC=95=88?= =?UTF-8?q?=EC=A0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app_stack/scripts/nginx_setup.sh.tftpl | 127 +++++++++++++----- 1 file changed, 95 insertions(+), 32 deletions(-) diff --git a/modules/app_stack/scripts/nginx_setup.sh.tftpl b/modules/app_stack/scripts/nginx_setup.sh.tftpl index f9e1211..5719679 100644 --- a/modules/app_stack/scripts/nginx_setup.sh.tftpl +++ b/modules/app_stack/scripts/nginx_setup.sh.tftpl @@ -7,43 +7,46 @@ DOMAIN="${domain_name}" EMAIL="${email}" CONF_NAME="${conf_file_name}" UPSTREAM_CONF="/etc/nginx/conf.d/upstream.conf" +CERTBOT_WEBROOT="/var/www/certbot" +CERT_FULLCHAIN="/etc/letsencrypt/live/$DOMAIN/fullchain.pem" +CERT_PRIVKEY="/etc/letsencrypt/live/$DOMAIN/privkey.pem" echo "Start Nginx Setup for $DOMAIN with config file: $CONF_NAME" -# 1. Install necessary packages for Nginx and Certbot -apt-get update -apt-get install -y nginx python3 python3-venv libaugeas0 - -# 2. Install Certbot (using pip) -python3 -m venv /opt/certbot/ -/opt/certbot/bin/pip install --upgrade pip -/opt/certbot/bin/pip install certbot certbot-nginx -ln -sf /opt/certbot/bin/certbot /usr/bin/certbot +reload_nginx() { + nginx -t + systemctl reload nginx || systemctl restart nginx +} -# 3. Issue SSL certificate (Non-interactive mode) -systemctl stop nginx +write_http_challenge_conf() { + cat < /etc/nginx/sites-available/$CONF_NAME +# 1차 차단: 도메인과 일치하지 않는 요청 차단 (IP 직접 접근, 알 수 없는 Host 헤더) +# 응답 없이 연결을 즉시 종료하여 봇이 서버 존재를 인식하지 못하게 함 +server { + listen 80 default_server; + server_name _; + return 444; +} -certbot certonly --standalone \ - --non-interactive \ - --agree-tos \ - --email "$EMAIL" \ - -d "$DOMAIN" +server { + listen 80; + server_name $DOMAIN; -echo "Certificate obtained successfully." + location ^~ /.well-known/acme-challenge/ { + root $CERTBOT_WEBROOT; + default_type "text/plain"; + try_files \$uri =404; + } -# 4. Create upstream config file only on first provisioning (initial active slot: blue on port 8080) -# Blue-Green 배포 시 이 파일만 교체하고 nginx -s reload 로 트래픽 전환 -# 이미 존재하면 덮어쓰지 않음 — 재프로비저닝 시 현재 active 슬롯 유지 -if [ ! -f "$UPSTREAM_CONF" ]; then - cat < $UPSTREAM_CONF -upstream app_backend { - server 127.0.0.1:8080; + location / { + return 301 https://\$host\$request_uri; + } +} +EOF } -UPSTREAM_EOF -fi -# 5. Create Nginx configuration file -cat < /etc/nginx/sites-available/$CONF_NAME +write_full_nginx_conf() { + cat < /etc/nginx/sites-available/$CONF_NAME map \$http_upgrade \$connection_upgrade { default upgrade; '' ''; @@ -70,8 +73,15 @@ server { server { listen 80; server_name $DOMAIN; + + location ^~ /.well-known/acme-challenge/ { + root $CERTBOT_WEBROOT; + default_type "text/plain"; + try_files \$uri =404; + } + location / { - return 301 https://\$host\$request_uri; + return 301 https://\$host\$request_uri; } } @@ -115,16 +125,69 @@ server { } } EOF +} + +# 1. Install necessary packages for Nginx and Certbot +apt-get update +apt-get install -y nginx python3 python3-venv libaugeas0 + +# 2. Install Certbot (using pip) +python3 -m venv /opt/certbot/ +/opt/certbot/bin/pip install --upgrade pip +/opt/certbot/bin/pip install certbot certbot-nginx +ln -sf /opt/certbot/bin/certbot /usr/bin/certbot + +# 3. Prepare Nginx webroot for HTTP-01 challenge +mkdir -p "$CERTBOT_WEBROOT" + +# Create upstream config file only on first provisioning (initial active slot: blue on port 8080) +# Blue-Green 배포 시 이 파일만 교체하고 nginx -s reload 로 트래픽 전환 +# 이미 존재하면 덮어쓰지 않음 — 재프로비저닝 시 현재 active 슬롯 유지 +if [ ! -f "$UPSTREAM_CONF" ]; then + cat < $UPSTREAM_CONF +upstream app_backend { + server 127.0.0.1:8080; +} +UPSTREAM_EOF +fi + +if [ -f "$CERT_FULLCHAIN" ] && [ -f "$CERT_PRIVKEY" ]; then + write_full_nginx_conf +else + write_http_challenge_conf +fi + +ln -sf /etc/nginx/sites-available/$CONF_NAME /etc/nginx/sites-enabled/$CONF_NAME +rm -f /etc/nginx/sites-enabled/default +reload_nginx + +# 4. Issue or renew SSL certificate (Non-interactive mode) +certbot certonly --webroot \ + --webroot-path "$CERTBOT_WEBROOT" \ + --non-interactive \ + --agree-tos \ + --keep-until-expiring \ + --email "$EMAIL" \ + -d "$DOMAIN" + +echo "Certificate obtained successfully." + +# 5. Create Nginx configuration file +write_full_nginx_conf # 6. Create symbolic link and remove default configuration ln -sf /etc/nginx/sites-available/$CONF_NAME /etc/nginx/sites-enabled/$CONF_NAME rm -f /etc/nginx/sites-enabled/default # 7. Register auto-renewal cron job -echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | tee -a /etc/crontab > /dev/null +cat < /etc/cron.d/certbot-$CONF_NAME +SHELL=/bin/bash +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +0 0,12 * * * root /opt/certbot/bin/python -c 'import random,time; time.sleep(random.random() * 3600)' && /usr/bin/certbot renew --webroot --webroot-path $CERTBOT_WEBROOT --quiet --deploy-hook "/usr/bin/systemctl reload nginx" +EOF # 8. Nginx restart -nginx -t -systemctl restart nginx +reload_nginx echo "Nginx setup complete!" From 7b05728b975bb4de3d77972a7f6fbb5d16332ce0 Mon Sep 17 00:00:00 2001 From: Hexeong <123macanic@naver.com> Date: Wed, 8 Jul 2026 14:37:36 +0900 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20monitoring=20=EC=8B=9C=ED=81=AC?= =?UTF-8?q?=EB=A6=BF=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/secrets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/secrets b/config/secrets index ad87e45..14aa1cf 160000 --- a/config/secrets +++ b/config/secrets @@ -1 +1 @@ -Subproject commit ad87e450f7a7e9b718ed9c1cc32908884011f80c +Subproject commit 14aa1cfa4161c55b2548cc62aa54d98bd3a21484 From e5aaa1dc414a451dbf8b990b62d8021f468f61f8 Mon Sep 17 00:00:00 2001 From: Hexeong <123macanic@naver.com> Date: Wed, 8 Jul 2026 15:26:14 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20nginx=20=EC=9D=B8=EC=A6=9D=EC=84=9C?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=20=EA=B0=B1=EC=8B=A0=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app_stack/scripts/nginx_setup.sh.tftpl | 19 +++- .../scripts/nginx_setup.sh.tftpl | 97 ++++++++++++------- 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/modules/app_stack/scripts/nginx_setup.sh.tftpl b/modules/app_stack/scripts/nginx_setup.sh.tftpl index 5719679..6bd8808 100644 --- a/modules/app_stack/scripts/nginx_setup.sh.tftpl +++ b/modules/app_stack/scripts/nginx_setup.sh.tftpl @@ -6,6 +6,7 @@ set -e DOMAIN="${domain_name}" EMAIL="${email}" CONF_NAME="${conf_file_name}" +CRON_NAME=$(printf '%s' "$CONF_NAME" | tr -c 'A-Za-z0-9_-' '-') UPSTREAM_CONF="/etc/nginx/conf.d/upstream.conf" CERTBOT_WEBROOT="/var/www/certbot" CERT_FULLCHAIN="/etc/letsencrypt/live/$DOMAIN/fullchain.pem" @@ -13,6 +14,10 @@ CERT_PRIVKEY="/etc/letsencrypt/live/$DOMAIN/privkey.pem" echo "Start Nginx Setup for $DOMAIN with config file: $CONF_NAME" +if [ -z "$CRON_NAME" ]; then + CRON_NAME="nginx" +fi + reload_nginx() { nginx -t systemctl reload nginx || systemctl restart nginx @@ -151,7 +156,9 @@ upstream app_backend { UPSTREAM_EOF fi +CERT_EXISTS=false if [ -f "$CERT_FULLCHAIN" ] && [ -f "$CERT_PRIVKEY" ]; then + CERT_EXISTS=true write_full_nginx_conf else write_http_challenge_conf @@ -173,14 +180,16 @@ certbot certonly --webroot \ echo "Certificate obtained successfully." # 5. Create Nginx configuration file -write_full_nginx_conf +if [ "$CERT_EXISTS" != "true" ]; then + write_full_nginx_conf -# 6. Create symbolic link and remove default configuration -ln -sf /etc/nginx/sites-available/$CONF_NAME /etc/nginx/sites-enabled/$CONF_NAME -rm -f /etc/nginx/sites-enabled/default + # 6. Create symbolic link and remove default configuration + ln -sf /etc/nginx/sites-available/$CONF_NAME /etc/nginx/sites-enabled/$CONF_NAME + rm -f /etc/nginx/sites-enabled/default +fi # 7. Register auto-renewal cron job -cat < /etc/cron.d/certbot-$CONF_NAME +cat < /etc/cron.d/certbot-$CRON_NAME SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin diff --git a/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl b/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl index 938d59f..3bb994e 100644 --- a/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl +++ b/modules/monitoring_stack/scripts/nginx_setup.sh.tftpl @@ -5,24 +5,24 @@ set -e DOMAIN="${domain_name}" EMAIL="${email}" CONF_NAME="${conf_file_name}" +CRON_NAME=$(printf '%s' "$CONF_NAME" | tr -c 'A-Za-z0-9_-' '-') CERTBOT_WEBROOT="/var/www/certbot" +CERT_FULLCHAIN="/etc/letsencrypt/live/$DOMAIN/fullchain.pem" +CERT_PRIVKEY="/etc/letsencrypt/live/$DOMAIN/privkey.pem" echo ">>> $DOMAIN 에 대한 Nginx 및 SSL 설정을 시작합니다." -# 1. 패키지 설치 -sudo apt-get update -sudo apt-get install -y nginx python3 python3-venv libaugeas0 - -# 2. Certbot 설치 및 링크 -sudo python3 -m venv /opt/certbot/ -sudo /opt/certbot/bin/pip install --upgrade pip -sudo /opt/certbot/bin/pip install certbot certbot-nginx -sudo ln -sf /opt/certbot/bin/certbot /usr/bin/certbot +if [ -z "$CRON_NAME" ]; then + CRON_NAME="nginx" +fi -# 3. HTTP-01 challenge를 nginx가 처리할 수 있도록 webroot 설정 -sudo mkdir -p "$CERTBOT_WEBROOT" +reload_nginx() { + sudo nginx -t + sudo systemctl reload nginx || sudo systemctl restart nginx +} -sudo tee /etc/nginx/sites-available/$CONF_NAME > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <