diff --git a/README.md b/README.md index 6d5ae6d..fc3e2ce 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,16 @@ solid-connection 서비스의 AWS, terraform 기반 IaC 레포지토리입니다 ``` solid-connection-infra/ ├── config/ -│ └── secrets/ # 민감한 data 관리 -│ └── ... +│ ├── secrets/ # 민감한 data 관리 +│ │ └── ... +│ └── side-infra/ # [side infra 관련 설정] +│ └── config.alloy ├── modules/ │ └── app_stack/ # [Prod/Stage 환경의 공통 모듈] +│ ├── scripts +│ │ └── docker_setup.sh +│ │ └── nginx_setup.sh.tftpl +│ │ └── side_infra_setup.sh.tftpl │ ├── security_groups.tf │ ├── ec2.tf │ ├── rds.tf diff --git a/config/secrets b/config/secrets index c1cf69a..83f1768 160000 --- a/config/secrets +++ b/config/secrets @@ -1 +1 @@ -Subproject commit c1cf69a9de6f6b766750395875cd5bdcb16a0e96 +Subproject commit 83f176821a253bb18f5ca36f1f24e8ce2e7c91d7 diff --git a/config/side-infra/config.alloy.tftpl b/config/side-infra/config.alloy.tftpl new file mode 100644 index 0000000..2c2eafd --- /dev/null +++ b/config/side-infra/config.alloy.tftpl @@ -0,0 +1,37 @@ +livedebugging { + enabled = true +} + +logging { + level = "info" + format = "logfmt" +} + +local.file_match "spring_logs" { + path_targets = [{ __path__ = "/var/log/spring/*.log" }] // 서비스 로그 파일 경로 +} + +loki.source.file "spring_source" { + targets = local.file_match.spring_logs.targets // 위에서 정의한 로그 파일 경로 사용 + forward_to = [loki.process.spring_labels.receiver] // 읽은 로그를 처리 단계로 전달 +} + +loki.process "spring_labels" { + forward_to = [loki.write.grafana_loki.receiver] // 처리된 로그를 Loki로 전송 + + stage.static_labels { + values = { + service = "backend", + env = sys.env("ALLOY_ENV"), + } + } +} + +loki.write "grafana_loki" { + endpoint { + url = "http://${loki_ip}:3100/loki/api/v1/push" + tenant_id = "fake" // Loki 테넌트 ID (싱글 테넌시이기에 fake로 설정) + batch_wait = "1s" // 로그 배치 전송 대기 시간 + batch_size = "1MB" // 로그 배치 크기 + } +} diff --git a/environment/prod/main.tf b/environment/prod/main.tf index 49adda0..658385e 100644 --- a/environment/prod/main.tf +++ b/environment/prod/main.tf @@ -13,7 +13,7 @@ module "prod_stack" { # 키페어 및 접속 허용 key_name = var.key_name - + # 인스턴스 스펙 instance_type = var.server_instance_type db_instance_class = var.db_instance_class @@ -24,7 +24,7 @@ module "prod_stack" { # RDS 식별자 설정 rds_identifier = var.rds_identifier - + # DB 계정 정보 db_username = var.db_root_username db_password = var.db_root_password @@ -45,4 +45,15 @@ module "prod_stack" { # S3 버킷 이름 전달 s3_default_bucket_name = var.s3_default_bucket_name s3_upload_bucket_name = var.s3_upload_bucket_name + + # ssh key 경로 전달 + ssh_key_path = var.ssh_key_path + + # Side Infra 관련 변수 전달 + work_dir = var.work_dir + alloy_env_name = var.alloy_env_name + + redis_version = var.redis_version + redis_exporter_version = var.redis_exporter_version + alloy_version = var.alloy_version } diff --git a/environment/prod/variables.tf b/environment/prod/variables.tf index dfcf981..7100dca 100644 --- a/environment/prod/variables.tf +++ b/environment/prod/variables.tf @@ -103,3 +103,33 @@ variable "s3_upload_bucket_name" { description = "Name of the upload S3 bucket" 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 +} + +variable "alloy_env_name" { + description = "Alloy Env Name" + type = string +} + +variable "redis_version" { + description = "Docker image tag for Redis" + type = string +} + +variable "redis_exporter_version" { + description = "Docker image tag for Redis Exporter" + type = string +} + +variable "alloy_version" { + description = "Docker image tag for Grafana Alloy" + type = string +} diff --git a/environment/stage/main.tf b/environment/stage/main.tf index 5fd2308..b09b382 100644 --- a/environment/stage/main.tf +++ b/environment/stage/main.tf @@ -13,9 +13,9 @@ module "stage_stack" { # 키페어 및 접속 허용 key_name = var.key_name - + # 인스턴스 스펙 - instance_type = var.server_instance_type + instance_type = var.server_instance_type db_instance_class = var.db_instance_class # 보안 그룹 규칙 @@ -24,7 +24,7 @@ module "stage_stack" { # RDS 식별자 설정 rds_identifier = var.rds_identifier - + # DB 계정 정보 db_username = var.db_root_username db_password = var.db_root_password @@ -45,4 +45,15 @@ module "stage_stack" { # S3 버킷 이름 전달 s3_default_bucket_name = var.s3_default_bucket_name s3_upload_bucket_name = var.s3_upload_bucket_name + + # ssh key 경로 전달 + ssh_key_path = var.ssh_key_path + + # Side Infra 관련 변수 전달 + work_dir = var.work_dir + alloy_env_name = var.alloy_env_name + + redis_version = var.redis_version + redis_exporter_version = var.redis_exporter_version + alloy_version = var.alloy_version } diff --git a/environment/stage/variables.tf b/environment/stage/variables.tf index 2e18f2a..0c31600 100644 --- a/environment/stage/variables.tf +++ b/environment/stage/variables.tf @@ -103,3 +103,33 @@ variable "s3_upload_bucket_name" { description = "Name of the upload S3 bucket" 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 +} + +variable "alloy_env_name" { + description = "Alloy Env Name" + type = string +} + +variable "redis_version" { + description = "Docker image tag for Redis" + type = string +} + +variable "redis_exporter_version" { + description = "Docker image tag for Redis Exporter" + type = string +} + +variable "alloy_version" { + description = "Docker image tag for Grafana Alloy" + type = string +} diff --git a/modules/app_stack/ec2.tf b/modules/app_stack/ec2.tf index 704e676..18a5f5c 100644 --- a/modules/app_stack/ec2.tf +++ b/modules/app_stack/ec2.tf @@ -1,4 +1,16 @@ -# 3. CloudInit을 이용한 User Data 스크립트 구성 +data "aws_instance" "monitoring_server" { + filter { + name = "tag:Name" + values = ["solid-connection-monitoring"] + } + + filter { + name = "instance-state-name" + values = ["running"] + } +} + +# CloudInit을 이용한 User Data 스크립트 구성 data "cloudinit_config" "app_init" { gzip = true base64_encode = true @@ -9,20 +21,9 @@ data "cloudinit_config" "app_init" { content = file("${path.module}/scripts/docker_setup.sh") filename = "1_docker_install.sh" } - - # [Part 2] Nginx 설정 스크립트 - part { - content_type = "text/x-shellscript" - 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 - }) - filename = "2_nginx_setup.sh" - } } -# 4. API Server (EC2) +# API Server (EC2) resource "aws_instance" "api_server" { ami = var.ami_id instance_type = var.instance_type @@ -51,3 +52,92 @@ resource "aws_instance" "api_server" { ] } } + +# 설정 및 컨테이너 실행 +# [리소스 1] Nginx 설정 변경 감지 및 실행 +resource "null_resource" "update_nginx" { + depends_on = [aws_instance.api_server] + + triggers = { + script_hash = sha256(templatefile("${path.module}/scripts/nginx_setup.sh.tftpl", { + domain_name = var.domain_name + email = var.cert_email + conf_file_name = var.nginx_conf_name + })) + } + + 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" + ] + } +} + +# [리소스 2] Side Infra 설정 변경 감지 및 실행 +resource "null_resource" "update_side_infra" { + depends_on = [aws_instance.api_server] + + triggers = { + script_hash = sha256(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 + })) + } + + 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" + ] + } +} diff --git a/modules/app_stack/scripts/docker_setup.sh b/modules/app_stack/scripts/docker_setup.sh index 1254eaa..a36799e 100644 --- a/modules/app_stack/scripts/docker_setup.sh +++ b/modules/app_stack/scripts/docker_setup.sh @@ -1,13 +1,4 @@ -wait_for_apt_lock() { - echo "Checking for apt locks..." - while fuser /var/lib/dpkg/lock >/dev/null 2>&1 || \ - fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || \ - fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do - echo "Waiting for apt lock..." - sleep 5 - done - echo "Apt lock acquired." -} +#!/bin/bash # 1. 필수 패키지 설치 apt-get update diff --git a/modules/app_stack/scripts/nginx_setup.sh.tftpl b/modules/app_stack/scripts/nginx_setup.sh.tftpl index bce1b2e..4e28002 100644 --- a/modules/app_stack/scripts/nginx_setup.sh.tftpl +++ b/modules/app_stack/scripts/nginx_setup.sh.tftpl @@ -1,34 +1,25 @@ -set -e +#!/bin/bash -wait_for_apt_lock() { - echo "Checking for apt locks..." - while fuser /var/lib/dpkg/lock >/dev/null 2>&1 || \ - fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || \ - fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do - echo "Waiting for apt lock..." - sleep 5 - done - echo "Apt lock acquired." -} +set -e -# --- 변수 설정 --- +# --- variables setting --- DOMAIN="${domain_name}" EMAIL="${email}" CONF_NAME="${conf_file_name}" echo "Start Nginx Setup for $DOMAIN with config file: $CONF_NAME" -# 1. Nginx 및 Certbot 필수 패키지 설치 +# 1. Install necessary packages for Nginx and Certbot apt-get update apt-get install -y nginx python3 python3-venv libaugeas0 -# 2. Certbot 설치 (pip 이용) +# 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 -s /opt/certbot/bin/certbot /usr/bin/certbot +ln -sf /opt/certbot/bin/certbot /usr/bin/certbot -# 3. SSL 인증서 발급 (비대화형 모드) +# 3. Issue SSL certificate (Non-interactive mode) systemctl stop nginx certbot certonly --standalone \ @@ -39,7 +30,7 @@ certbot certonly --standalone \ echo "Certificate obtained successfully." -# 4. Nginx 설정 파일 작성 +# 4. Create Nginx configuration file cat < /etc/nginx/sites-available/$CONF_NAME server { listen 80; @@ -76,14 +67,14 @@ server { } EOF -# 5. 심볼릭 링크 연결 및 기본 설정 삭제 +# 5. 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. 자동 갱신 크론탭 등록 +# 6. 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 -# 7. Nginx 재시작 +# 7. Nginx restart nginx -t systemctl restart nginx diff --git a/modules/app_stack/scripts/side_infra_setup.sh.tftpl b/modules/app_stack/scripts/side_infra_setup.sh.tftpl new file mode 100644 index 0000000..6e87f45 --- /dev/null +++ b/modules/app_stack/scripts/side_infra_setup.sh.tftpl @@ -0,0 +1,77 @@ +#!/bin/bash + +set -e + +# --- Variable Configuration --- +WORK_DIR='${work_dir}' +ALLOY_ENV_NAME='${alloy_env_name}' # production or dev +ALLOY_CONFIG_CONTENT='${alloy_config_content}' + +REDIS_VERSION='${redis_version}' +REDIS_EXPORTER_VERSION='${redis_exporter_version}' +ALLOY_VERSION='${alloy_version}' + +echo "Start Side Infrastructure Setup..." + +# 1. Create working and log directories +mkdir -p "$WORK_DIR/config/side-infra" +mkdir -p "$WORK_DIR/logs" +# Set log directory permissions (Shared by App and Alloy) +chmod 777 "$WORK_DIR/logs" +chown -R ubuntu:ubuntu "$WORK_DIR" + +# 2. Create Alloy configuration file +cat < "$WORK_DIR/config/side-infra/config.alloy" +$ALLOY_CONFIG_CONTENT +EOF + +# 3. Create docker-compose.yml for side infrastructure +cat < "$WORK_DIR/docker-compose.side-infra.yml" +version: '3.8' + +services: + redis: + image: redis:$REDIS_VERSION + container_name: redis + network_mode: "host" + restart: always + command: redis-server --bind 127.0.0.1 --protected-mode yes + + redis-exporter: + image: oliver006/redis_exporter:$REDIS_EXPORTER_VERSION + container_name: redis-exporter + environment: + REDIS_ADDR: "127.0.0.1:6379" + depends_on: + - redis + network_mode: "host" + restart: always + + alloy: + image: grafana/alloy:$ALLOY_VERSION + container_name: alloy + volumes: + - ./logs:/var/log/spring:ro + - ./config/side-infra/config.alloy:/etc/alloy/config.alloy:ro + environment: + - ALLOY_ENV=$ALLOY_ENV_NAME + network_mode: "host" + restart: always + +EOF + +# 4. Start containers +cd "$WORK_DIR" + +# Cleanup potential conflicts where ports might be locked by other Docker Compose instances. +# 'rm -f' is executed first to ensure cleanup of any lingering network resources attached to existing containers. +echo "Cleanup: Force removing leftover containers..." +docker rm -f redis redis-exporter alloy || true + +echo "Stopping previous containers if running..." +docker compose -f docker-compose.side-infra.yml down || true + +echo "Starting containers..." +docker compose -f docker-compose.side-infra.yml up -d + +echo "Side Infrastructure Setup Complete!" \ No newline at end of file diff --git a/modules/app_stack/security_groups.tf b/modules/app_stack/security_groups.tf index db984f2..5ec8b31 100644 --- a/modules/app_stack/security_groups.tf +++ b/modules/app_stack/security_groups.tf @@ -1,3 +1,15 @@ +data "aws_instance" "monitoring_ec2" { + filter { + name = "tag:Name" + values = ["solid-connection-monitoring"] + } + + filter { + name = "instance-state-name" + values = ["running"] + } +} + # 1. API Server용 보안 그룹 (SSH 연결 허용) resource "aws_security_group" "api_sg" { name = "sc-${var.env_name}-api-sg" @@ -15,6 +27,15 @@ resource "aws_security_group" "api_sg" { } } + ingress { + description = "Allow 8081 from EC2: (${data.aws_instance.monitoring_ec2.tags.Name})" + from_port = 8081 + to_port = 8081 + protocol = "tcp" + + cidr_blocks = ["${data.aws_instance.monitoring_ec2.private_ip}/32"] + } + # [Outbound] 모든 트래픽 허용 egress { from_port = 0 diff --git a/modules/app_stack/variables.tf b/modules/app_stack/variables.tf index 878b0fe..695d914 100644 --- a/modules/app_stack/variables.tf +++ b/modules/app_stack/variables.tf @@ -1,13 +1,13 @@ -variable "env_name" { - description = "환경 이름" +variable "env_name" { + description = "환경 이름 (prod/stage)" } -variable "instance_type" { - description = "EC2 인스턴스 타입" +variable "instance_type" { + description = "EC2 인스턴스 타입" } -variable "db_instance_class" { - description = "RDS 인스턴스 타입" +variable "db_instance_class" { + description = "RDS 인스턴스 타입" } variable "api_ingress_rules" { @@ -74,8 +74,8 @@ variable "kms_key_arn" { type = string } -variable "vpc_id" { - description = "배포할 VPC ID" +variable "vpc_id" { + description = "배포할 VPC ID" } variable "ami_id" { @@ -114,3 +114,35 @@ variable "s3_upload_bucket_name" { description = "Name of the upload S3 bucket" 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" + type = string +} + +variable "alloy_env_name" { + description = "Alloy Env Name" + type = string +} + +variable "redis_version" { + description = "Docker image tag for Redis" + type = string +} + +variable "redis_exporter_version" { + description = "Docker image tag for Redis Exporter" + type = string +} + +variable "alloy_version" { + description = "Docker image tag for Grafana Alloy" + type = string +}