From 5fa3700722d424629d672b0070b23c225fce55ff Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Thu, 12 Feb 2026 17:33:03 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat(#332):=20=EA=B8=B0=EC=A1=B4=20ecs?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20tf=EC=9D=84=20eks=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/terraform/modules/api-gateway/main.tf | 224 ------ .../terraform/modules/api-gateway/outputs.tf | 42 -- .../modules/api-gateway/variables.tf | 64 -- infra/terraform/modules/dns/main.tf | 35 +- infra/terraform/modules/dns/outputs.tf | 14 +- infra/terraform/modules/dns/variables.tf | 21 +- infra/terraform/modules/ecs/codedeploy.tf | 93 --- infra/terraform/modules/ecs/main.tf | 684 ------------------ infra/terraform/modules/ecs/outputs.tf | 57 -- infra/terraform/modules/ecs/variables.tf | 261 ------- infra/terraform/modules/elasticache/main.tf | 2 +- infra/terraform/modules/kafka/main.tf | 2 +- infra/terraform/modules/monitoring/main.tf | 15 +- infra/terraform/modules/monitoring/outputs.tf | 8 +- .../terraform/modules/monitoring/variables.tf | 18 + infra/terraform/modules/network/main.tf | 17 +- infra/terraform/modules/network/variables.tf | 9 + .../modules/parameter-store/outputs.tf | 12 +- infra/terraform/modules/s3/main.tf | 16 + infra/terraform/modules/waf/main.tf | 12 +- infra/terraform/modules/waf/variables.tf | 5 - 21 files changed, 116 insertions(+), 1495 deletions(-) delete mode 100644 infra/terraform/modules/api-gateway/main.tf delete mode 100644 infra/terraform/modules/api-gateway/outputs.tf delete mode 100644 infra/terraform/modules/api-gateway/variables.tf delete mode 100644 infra/terraform/modules/ecs/codedeploy.tf delete mode 100644 infra/terraform/modules/ecs/main.tf delete mode 100644 infra/terraform/modules/ecs/outputs.tf delete mode 100644 infra/terraform/modules/ecs/variables.tf diff --git a/infra/terraform/modules/api-gateway/main.tf b/infra/terraform/modules/api-gateway/main.tf deleted file mode 100644 index 27713dd5..00000000 --- a/infra/terraform/modules/api-gateway/main.tf +++ /dev/null @@ -1,224 +0,0 @@ -# ============================================================================= -# API Gateway (HTTP API) -# ============================================================================= -resource "aws_apigatewayv2_api" "main" { - name = "${var.name_prefix}-api" - protocol_type = "HTTP" - - cors_configuration { - allow_origins = ["*"] - allow_methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] - allow_headers = ["Content-Type", "Authorization", "X-Requested-With"] - expose_headers = ["X-Request-Id"] - max_age = 3600 - allow_credentials = false - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-api" }) -} - -# ============================================================================= -# Cognito User Pool -# ============================================================================= -resource "aws_cognito_user_pool" "main" { - count = var.enable_cognito ? 1 : 0 - name = var.cognito_user_pool_name != null ? var.cognito_user_pool_name : "${var.name_prefix}-user-pool" - - username_attributes = ["email"] - auto_verified_attributes = ["email"] - - password_policy { - minimum_length = 8 - require_lowercase = true - require_numbers = true - require_symbols = true - require_uppercase = true - temporary_password_validity_days = 7 - } - - email_configuration { - email_sending_account = "COGNITO_DEFAULT" - } - - mfa_configuration = "OPTIONAL" - - software_token_mfa_configuration { - enabled = true - } - - account_recovery_setting { - recovery_mechanism { - name = "verified_email" - priority = 1 - } - } - - schema { - name = "email" - attribute_data_type = "String" - mutable = true - required = true - string_attribute_constraints { - min_length = 1 - max_length = 256 - } - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-user-pool" }) -} - -resource "aws_cognito_user_pool_client" "main" { - count = var.enable_cognito ? 1 : 0 - name = "${var.name_prefix}-api-client" - user_pool_id = aws_cognito_user_pool.main[0].id - - generate_secret = false - allowed_oauth_flows_user_pool_client = true - allowed_oauth_flows = ["code", "implicit"] - allowed_oauth_scopes = ["email", "openid", "profile"] - callback_urls = var.cognito_callback_urls - logout_urls = var.cognito_logout_urls - supported_identity_providers = ["COGNITO"] - - explicit_auth_flows = [ - "ALLOW_REFRESH_TOKEN_AUTH", - "ALLOW_USER_SRP_AUTH", - "ALLOW_USER_PASSWORD_AUTH" - ] - - access_token_validity = 1 - id_token_validity = 1 - refresh_token_validity = 30 - - token_validity_units { - access_token = "hours" - id_token = "hours" - refresh_token = "days" - } -} - -resource "aws_cognito_user_pool_domain" "main" { - count = var.enable_cognito ? 1 : 0 - domain = var.name_prefix - user_pool_id = aws_cognito_user_pool.main[0].id -} - -# ============================================================================= -# JWT Authorizer -# ============================================================================= -resource "aws_apigatewayv2_authorizer" "cognito" { - count = var.enable_cognito ? 1 : 0 - - api_id = aws_apigatewayv2_api.main.id - authorizer_type = "JWT" - name = "${var.name_prefix}-cognito-authorizer" - identity_sources = ["$request.header.Authorization"] - - jwt_configuration { - audience = [aws_cognito_user_pool_client.main[0].id] - issuer = "https://${aws_cognito_user_pool.main[0].endpoint}" - } -} - -# ============================================================================= -# VPC Link -# ============================================================================= -resource "aws_apigatewayv2_vpc_link" "main" { - name = "${var.name_prefix}-vpc-link" - security_group_ids = [var.ecs_security_group_id] - subnet_ids = var.subnet_ids - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-vpc-link" }) -} - -# ============================================================================= -# Integration (VPC Link -> ALB) -# ============================================================================= -resource "aws_apigatewayv2_integration" "main" { - api_id = aws_apigatewayv2_api.main.id - integration_type = "HTTP_PROXY" - integration_method = "ANY" - integration_uri = var.alb_listener_arn - connection_type = "VPC_LINK" - connection_id = aws_apigatewayv2_vpc_link.main.id - - payload_format_version = "1.0" -} - -# ============================================================================= -# Routes (Public - No Auth Required) -# ============================================================================= -resource "aws_apigatewayv2_route" "public" { - for_each = var.enable_cognito ? toset(var.public_routes) : toset([]) - - api_id = aws_apigatewayv2_api.main.id - route_key = "ANY ${each.value}" - target = "integrations/${aws_apigatewayv2_integration.main.id}" -} - -# ============================================================================= -# Routes (Protected - Auth Required) -# ============================================================================= -resource "aws_apigatewayv2_route" "protected" { - for_each = var.enable_cognito ? toset(var.protected_route_patterns) : toset([]) - - api_id = aws_apigatewayv2_api.main.id - route_key = "ANY ${each.value}" - target = "integrations/${aws_apigatewayv2_integration.main.id}" - authorizer_id = aws_apigatewayv2_authorizer.cognito[0].id - authorization_type = "JWT" -} - -# ============================================================================= -# Route (Fallback - When Cognito Disabled) -# ============================================================================= -resource "aws_apigatewayv2_route" "main" { - count = var.enable_cognito ? 0 : 1 - - api_id = aws_apigatewayv2_api.main.id - route_key = "ANY /{proxy+}" - target = "integrations/${aws_apigatewayv2_integration.main.id}" -} - -# ============================================================================= -# Stage -# ============================================================================= -resource "aws_apigatewayv2_stage" "main" { - api_id = aws_apigatewayv2_api.main.id - name = "$default" - auto_deploy = true - - access_log_settings { - destination_arn = aws_cloudwatch_log_group.api_logs.arn - format = jsonencode({ - requestId = "$context.requestId" - ip = "$context.identity.sourceIp" - requestTime = "$context.requestTime" - httpMethod = "$context.httpMethod" - routeKey = "$context.routeKey" - status = "$context.status" - protocol = "$context.protocol" - responseLength = "$context.responseLength" - integrationError = "$context.integrationErrorMessage" - authorizerError = "$context.authorizer.error" - }) - } - - default_route_settings { - detailed_metrics_enabled = true - throttling_burst_limit = 5000 - throttling_rate_limit = 2000 - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-stage" }) -} - -# ============================================================================= -# CloudWatch Log Group for API Gateway -# ============================================================================= -resource "aws_cloudwatch_log_group" "api_logs" { - name = "/aws/apigateway/${var.name_prefix}" - retention_in_days = 30 - - tags = var.common_tags -} diff --git a/infra/terraform/modules/api-gateway/outputs.tf b/infra/terraform/modules/api-gateway/outputs.tf deleted file mode 100644 index a098edd1..00000000 --- a/infra/terraform/modules/api-gateway/outputs.tf +++ /dev/null @@ -1,42 +0,0 @@ -output "api_endpoint" { - description = "API Gateway 엔드포인트 URL" - value = aws_apigatewayv2_api.main.api_endpoint -} - -output "api_id" { - description = "API Gateway ID" - value = aws_apigatewayv2_api.main.id -} - -output "stage_arn" { - description = "API Gateway Stage ARN (WAF 연결용)" - value = aws_apigatewayv2_stage.main.arn -} - -output "execution_arn" { - description = "API Gateway Execution ARN" - value = aws_apigatewayv2_api.main.execution_arn -} - -# ============================================================================= -# Cognito Outputs -# ============================================================================= -output "cognito_user_pool_id" { - description = "Cognito User Pool ID" - value = var.enable_cognito ? aws_cognito_user_pool.main[0].id : null -} - -output "cognito_user_pool_client_id" { - description = "Cognito User Pool Client ID" - value = var.enable_cognito ? aws_cognito_user_pool_client.main[0].id : null -} - -output "cognito_user_pool_endpoint" { - description = "Cognito User Pool Endpoint" - value = var.enable_cognito ? aws_cognito_user_pool.main[0].endpoint : null -} - -output "cognito_domain" { - description = "Cognito Domain URL" - value = var.enable_cognito ? "https://${aws_cognito_user_pool_domain.main[0].domain}.auth.ap-northeast-2.amazoncognito.com" : null -} diff --git a/infra/terraform/modules/api-gateway/variables.tf b/infra/terraform/modules/api-gateway/variables.tf deleted file mode 100644 index 98af3525..00000000 --- a/infra/terraform/modules/api-gateway/variables.tf +++ /dev/null @@ -1,64 +0,0 @@ -variable "name_prefix" { - description = "리소스 네이밍 프리픽스" - type = string -} - -variable "common_tags" { - description = "공통 태그" - type = map(string) - default = {} -} - -variable "subnet_ids" { - description = "VPC Link 서브넷 ID 목록" - type = list(string) -} - -variable "ecs_security_group_id" { - description = "ECS 보안그룹 ID" - type = string -} - -variable "alb_listener_arn" { - description = "ALB Listener ARN" - type = string -} - -# ============================================================================= -# Cognito Settings -# ============================================================================= -variable "enable_cognito" { - description = "Cognito 인증 활성화" - type = bool - default = false -} - -variable "cognito_user_pool_name" { - description = "Cognito User Pool 이름" - type = string - default = null -} - -variable "cognito_callback_urls" { - description = "OAuth 콜백 URL 목록" - type = list(string) - default = ["https://localhost:3000/callback"] -} - -variable "cognito_logout_urls" { - description = "로그아웃 URL 목록" - type = list(string) - default = ["https://localhost:3000"] -} - -variable "public_routes" { - description = "인증이 필요없는 공개 라우트 패턴" - type = list(string) - default = ["/api/auth/*", "/health", "/actuator/health"] -} - -variable "protected_route_patterns" { - description = "보호된 라우트 패턴 목록" - type = list(string) - default = ["/api/*"] -} diff --git a/infra/terraform/modules/dns/main.tf b/infra/terraform/modules/dns/main.tf index b4e09cb3..3b12e790 100644 --- a/infra/terraform/modules/dns/main.tf +++ b/infra/terraform/modules/dns/main.tf @@ -50,41 +50,20 @@ resource "aws_acm_certificate_validation" "main" { validation_record_fqdns = [for record in aws_route53_record.acm_validation : record.fqdn] } + # ============================================================================= -# API Gateway Custom Domain (Optional) +# EKS(ALB) Record # ============================================================================= -resource "aws_apigatewayv2_domain_name" "api" { - count = var.create_api_domain ? 1 : 0 - - domain_name = "api.${var.domain_name}" - - domain_name_configuration { - certificate_arn = aws_acm_certificate_validation.main.certificate_arn - endpoint_type = "REGIONAL" - security_policy = "TLS_1_2" - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-api-domain" }) -} - -resource "aws_apigatewayv2_api_mapping" "api" { - count = var.create_api_domain ? 1 : 0 - - api_id = var.api_gateway_id - domain_name = aws_apigatewayv2_domain_name.api[0].id - stage = "$default" -} - -resource "aws_route53_record" "api" { - count = var.create_api_domain ? 1 : 0 +resource "aws_route53_record" "alb" { + count = var.create_alb_record ? 1 : 0 zone_id = aws_route53_zone.main.zone_id - name = "api.${var.domain_name}" + name = var.alb_record_name type = "A" alias { - name = aws_apigatewayv2_domain_name.api[0].domain_name_configuration[0].target_domain_name - zone_id = aws_apigatewayv2_domain_name.api[0].domain_name_configuration[0].hosted_zone_id + name = var.alb_dns_name + zone_id = var.alb_zone_id evaluate_target_health = false } } diff --git a/infra/terraform/modules/dns/outputs.tf b/infra/terraform/modules/dns/outputs.tf index 7e827d95..c41e6cab 100644 --- a/infra/terraform/modules/dns/outputs.tf +++ b/infra/terraform/modules/dns/outputs.tf @@ -18,12 +18,8 @@ output "certificate_arn" { value = aws_acm_certificate_validation.main.certificate_arn } -output "api_domain" { - description = "API 커스텀 도메인" - value = var.create_api_domain ? "api.${var.domain_name}" : null -} - -output "api_domain_target" { - description = "API Gateway 커스텀 도메인의 target domain name" - value = var.create_api_domain ? aws_apigatewayv2_domain_name.api[0].domain_name_configuration[0].target_domain_name : null -} +# ALB 레코드 FQDN +output "alb_record_fqdn" { + description = "ALB Alias 레코드 FQDN" + value = var.create_alb_record ? aws_route53_record.alb[0].fqdn : null +} \ No newline at end of file diff --git a/infra/terraform/modules/dns/variables.tf b/infra/terraform/modules/dns/variables.tf index 357eb59e..e452ae32 100644 --- a/infra/terraform/modules/dns/variables.tf +++ b/infra/terraform/modules/dns/variables.tf @@ -14,14 +14,27 @@ variable "domain_name" { type = string } -variable "create_api_domain" { - description = "API Gateway 커스텀 도메인 생성 여부" +# ALB alias record 생성 여부 +variable "create_alb_record" { + description = "ALB(Route53 Alias) 레코드 생성 여부" type = bool default = true } -variable "api_gateway_id" { - description = "API Gateway ID" +variable "alb_record_name" { + description = "생성할 레코드 이름" + type = string + default = "" +} + +variable "alb_dns_name" { + description = "ALB DNS name (ex: xxx.ap-northeast-2.elb.amazonaws.com)" + type = string + default = "" +} + +variable "alb_zone_id" { + description = "ALB Hosted Zone ID" type = string default = "" } diff --git a/infra/terraform/modules/ecs/codedeploy.tf b/infra/terraform/modules/ecs/codedeploy.tf deleted file mode 100644 index 93757b8f..00000000 --- a/infra/terraform/modules/ecs/codedeploy.tf +++ /dev/null @@ -1,93 +0,0 @@ -# ============================================================================= -# CodeDeploy Application (for Blue/Green ECS Deployment) -# ============================================================================= -resource "aws_codedeploy_app" "main" { - count = var.enable_blue_green ? 1 : 0 - compute_platform = "ECS" - name = "${var.name_prefix}-ecs-app" - - tags = var.common_tags -} - -# ============================================================================= -# CodeDeploy IAM Role -# ============================================================================= -resource "aws_iam_role" "codedeploy" { - count = var.enable_blue_green ? 1 : 0 - name = "${var.name_prefix}-codedeploy-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { - Service = "codedeploy.amazonaws.com" - } - }] - }) - - tags = var.common_tags -} - -resource "aws_iam_role_policy_attachment" "codedeploy" { - count = var.enable_blue_green ? 1 : 0 - role = aws_iam_role.codedeploy[0].name - policy_arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS" -} - -# ============================================================================= -# CodeDeploy Deployment Groups (per service) -# ============================================================================= -resource "aws_codedeploy_deployment_group" "services" { - for_each = var.enable_blue_green ? local.active_services : {} - - app_name = aws_codedeploy_app.main[0].name - deployment_group_name = "${var.name_prefix}-${each.key}-dg" - deployment_config_name = var.deployment_config - service_role_arn = aws_iam_role.codedeploy[0].arn - - auto_rollback_configuration { - enabled = true - events = ["DEPLOYMENT_FAILURE"] - } - - blue_green_deployment_config { - deployment_ready_option { - action_on_timeout = "CONTINUE_DEPLOYMENT" - } - - terminate_blue_instances_on_deployment_success { - action = "TERMINATE" - termination_wait_time_in_minutes = var.termination_wait_time - } - } - - deployment_style { - deployment_option = "WITH_TRAFFIC_CONTROL" - deployment_type = "BLUE_GREEN" - } - - ecs_service { - cluster_name = aws_ecs_cluster.main.name - service_name = aws_ecs_service.services[each.key].name - } - - load_balancer_info { - target_group_pair_info { - prod_traffic_route { - listener_arns = [var.alb_listener_arn] - } - - target_group { - name = var.target_group_names[each.key] - } - - target_group { - name = lookup(var.target_group_names, "${each.key}-green", "${var.name_prefix}-${each.key}-tg-g") - } - } - } - - tags = merge(var.common_tags, { Service = each.key }) -} diff --git a/infra/terraform/modules/ecs/main.tf b/infra/terraform/modules/ecs/main.tf deleted file mode 100644 index ed6cfbf7..00000000 --- a/infra/terraform/modules/ecs/main.tf +++ /dev/null @@ -1,684 +0,0 @@ -# ============================================================================= -# Local Variables -# ============================================================================= -locals { - # Gateway 및 excluded_services에 포함된 서비스 필터링 - active_services = { - for k, v in var.services : k => v - if !contains(var.excluded_services, k) - } -} - -# ============================================================================= -# Cloud Map (Service Discovery Namespace) -# ============================================================================= -resource "aws_service_discovery_private_dns_namespace" "main" { - name = "${var.project}.local" - vpc = var.vpc_id - - tags = merge(var.common_tags, { Name = "${var.project}.local" }) -} - -# ============================================================================= -# Cloud Map Services (Multiple) - Only when Service Connect is disabled -# ============================================================================= -resource "aws_service_discovery_service" "services" { - for_each = var.enable_service_connect ? {} : var.services - - name = each.key - - dns_config { - namespace_id = aws_service_discovery_private_dns_namespace.main.id - routing_policy = "MULTIVALUE" - - dns_records { - ttl = 10 - type = "A" - } - dns_records { - ttl = 10 - type = "SRV" - } - } - - health_check_custom_config { - failure_threshold = 1 - } - - tags = merge(var.common_tags, { Service = each.key }) -} - -# ============================================================================= -# ECS Cluster (Single shared cluster) -# ============================================================================= -resource "aws_ecs_cluster" "main" { - name = "${var.name_prefix}-cluster" - - service_connect_defaults { - namespace = aws_service_discovery_private_dns_namespace.main.arn - } - - setting { - name = "containerInsights" - value = "enabled" - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-cluster" }) -} - -resource "aws_ecs_cluster_capacity_providers" "main" { - cluster_name = aws_ecs_cluster.main.name - - capacity_providers = ["FARGATE", "FARGATE_SPOT"] - - default_capacity_provider_strategy { - base = 1 - weight = 100 - capacity_provider = "FARGATE" - } -} - -# ============================================================================= -# MSA Security Group (Shared by all services) -# ============================================================================= -resource "aws_security_group" "msa_sg" { - name = "${var.name_prefix}-msa-sg" - description = "Security group for MSA services" - vpc_id = var.vpc_id - - # ALB에서 들어오는 트래픽 허용 (Gateway: 8080) - ingress { - description = "Traffic from ALB" - from_port = 8080 - to_port = 8080 - protocol = "tcp" - security_groups = [var.alb_security_group_id] - } - - # Service Connect를 위한 자기 참조 규칙 (서비스 간 통신: 8080-8084) - ingress { - description = "Inter-service communication" - from_port = 8080 - to_port = 8084 - protocol = "tcp" - self = true - } - - # Service Connect proxy port - ingress { - description = "Service Connect proxy" - from_port = 15000 - to_port = 15001 - protocol = "tcp" - self = true - } - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-msa-sg" }) -} - -# ============================================================================= -# IAM Role for ECS Task Execution -# ============================================================================= -resource "aws_iam_role" "ecs_task_execution_role" { - name = "${var.name_prefix}-ecs-task-execution-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { Service = "ecs-tasks.amazonaws.com" } - }] - }) - - tags = var.common_tags -} - -resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" { - role = aws_iam_role.ecs_task_execution_role.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" -} - -# ============================================================================= -# SSM Parameter Store 읽기 권한 (Secrets 주입용) -# ============================================================================= -resource "aws_iam_role_policy" "ecs_task_execution_ssm" { - name = "${var.name_prefix}-ecs-ssm-policy" - role = aws_iam_role.ecs_task_execution_role.id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "ssm:GetParameters", - "ssm:GetParameter" - ] - Resource = "arn:aws:ssm:${var.region}:*:parameter/${var.project}/${var.environment}/*" - }, - { - Effect = "Allow" - Action = [ - "kms:Decrypt" - ] - Resource = "*" - Condition = { - StringEquals = { - "kms:ViaService" = "ssm.${var.region}.amazonaws.com" - } - } - } - ] - }) -} - -# ============================================================================= -# IAM Role for ECS Task (Application level) -# ============================================================================= -resource "aws_iam_role" "ecs_task_role" { - name = "${var.name_prefix}-ecs-task-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [{ - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { Service = "ecs-tasks.amazonaws.com" } - }] - }) - - tags = var.common_tags -} - -# Service Connect requires CloudWatch permissions -resource "aws_iam_role_policy" "ecs_service_connect" { - name = "${var.name_prefix}-service-connect-policy" - role = aws_iam_role.ecs_task_role.id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ] - Resource = "*" - } - ] - }) -} - -# ============================================================================= -# CloudWatch Log Groups (per service) -# ============================================================================= -resource "aws_cloudwatch_log_group" "services" { - for_each = var.services - - name = "/ecs/${var.project}-${each.key}" - retention_in_days = var.log_retention_days - - tags = merge(var.common_tags, { Service = each.key }) -} - -# ============================================================================= -# ECS Task Definitions (per service) -# ============================================================================= -resource "aws_ecs_task_definition" "services" { - for_each = var.services - - family = "${var.project}-${each.key}-task" - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = each.value.cpu - memory = each.value.memory - execution_role_arn = aws_iam_role.ecs_task_execution_role.arn - task_role_arn = aws_iam_role.ecs_task_role.arn - - container_definitions = jsonencode([ - { - name = "${var.project}-${each.key}-container" - image = "${var.ecr_repository_urls[each.key]}:latest" - essential = true - - portMappings = [{ - name = each.key - containerPort = each.value.container_port - hostPort = each.value.container_port - protocol = "tcp" - appProtocol = "http" - }] - - environment = concat( - # 공통 환경 변수 - [ - { - name = "SPRING_PROFILES_ACTIVE" - value = var.environment - }, - { - name = "SPRING_DATA_REDIS_HOST" - value = var.redis_endpoint - }, - { - name = "SPRING_DATA_REDIS_PORT" - value = "6379" - } - ], - # Kafka 환경 변수 (gateway 제외) - each.key != "gateway" && var.kafka_bootstrap_servers != "" ? [ - { - name = "SPRING_KAFKA_BOOTSTRAP_SERVERS" - value = var.kafka_bootstrap_servers - }, - { - name = "SPRING_KAFKA_CONSUMER_GROUP_ID" - value = "${var.project}-${each.key}" - }, - { - name = "SPRING_KAFKA_CONSUMER_AUTO_OFFSET_RESET" - value = "earliest" - } - ] : [], - # 백엔드 서비스 전용 (gateway 제외) - DB, JPA, JWT 설정 - each.key != "gateway" ? [ - { - name = "SPRING_DATASOURCE_URL" - value = "jdbc:postgresql://${var.db_endpoint}/${var.db_name}?currentSchema=${lookup(each.value.environment_vars, "DB_SCHEMA", each.key)}" - }, - { - name = "SPRING_DATASOURCE_USERNAME" - value = var.db_username - }, - { - name = "SPRING_JPA_HIBERNATE_DDL_AUTO" - value = "update" - }, - { - name = "SPRING_JPA_SHOW_SQL" - value = "false" - }, - { - name = "SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT" - value = "org.hibernate.dialect.PostgreSQLDialect" - }, - { - name = "SPRING_JWT_EXPIRE_MS" - value = tostring(var.jwt_expire_ms) - }, - { - name = "SPRING_SECURITY_REFRESH_TOKEN_EXPIRE_DAYS" - value = tostring(var.refresh_token_expire_days) - }, - { - name = "SERVICE_ACTIVE_REGIONS" - value = var.service_active_regions - } - ] : [], - # Service Discovery 환경 변수 (Feign Client URLs) - each.key != "gateway" ? [ - { - name = "FEIGN_ORDER_URL" - value = "http://order.${var.project}.local:${var.services["order"].container_port}" - }, - { - name = "FEIGN_PAYMENT_URL" - value = "http://payment.${var.project}.local:${var.services["payment"].container_port}" - }, - { - name = "FEIGN_STORE_URL" - value = "http://store.${var.project}.local:${var.services["store"].container_port}" - }, - { - name = "FEIGN_USER_URL" - value = "http://user.${var.project}.local:${var.services["user"].container_port}" - } - ] : [], - # Mail 설정 (user 서비스용) - each.key == "user" ? [ - { - name = "SPRING_MAIL_HOST" - value = var.mail_host - }, - { - name = "SPRING_MAIL_PORT" - value = tostring(var.mail_port) - }, - { - name = "SPRING_MAIL_USERNAME" - value = var.mail_username - }, - { - name = "SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH" - value = "true" - }, - { - name = "SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE" - value = "true" - } - ] : [], - # Toss 결제 설정 (payment 서비스용) - each.key == "payment" ? [ - { - name = "TOSS_PAYMENTS_BASE_URL" - value = var.toss_base_url - }, - { - name = "TOSS_PAYMENTS_CUSTOMER_KEY" - value = var.toss_customer_key - } - ] : [], - # Gateway 전용 설정 - Spring Cloud Gateway 라우트 (WebFlux 버전용 새 property 이름) - each.key == "gateway" ? [ - # User Service - Auth 관련 - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_0_ID" - value = "user-login" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_0_URI" - value = "http://user.${var.project}.local:${var.services["user"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_0_PREDICATES_0" - value = "Path=/api/login" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_1_ID" - value = "user-join" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_1_URI" - value = "http://user.${var.project}.local:${var.services["user"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_1_PREDICATES_0" - value = "Path=/api/join" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_2_ID" - value = "user-auth" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_2_URI" - value = "http://user.${var.project}.local:${var.services["user"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_2_PREDICATES_0" - value = "Path=/api/auth/**" - }, - # User Service - Users & Admin - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_3_ID" - value = "user-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_3_URI" - value = "http://user.${var.project}.local:${var.services["user"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_3_PREDICATES_0" - value = "Path=/api/users/**" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_4_ID" - value = "admin-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_4_URI" - value = "http://user.${var.project}.local:${var.services["user"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_4_PREDICATES_0" - value = "Path=/api/admin/**" - }, - # Store Service - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_5_ID" - value = "store-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_5_URI" - value = "http://store.${var.project}.local:${var.services["store"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_5_PREDICATES_0" - value = "Path=/api/stores/**" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_6_ID" - value = "category-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_6_URI" - value = "http://store.${var.project}.local:${var.services["store"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_6_PREDICATES_0" - value = "Path=/api/categories/**" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_7_ID" - value = "review-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_7_URI" - value = "http://store.${var.project}.local:${var.services["store"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_7_PREDICATES_0" - value = "Path=/api/reviews/**" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_8_ID" - value = "menu-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_8_URI" - value = "http://store.${var.project}.local:${var.services["store"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_8_PREDICATES_0" - value = "Path=/api/menus/**" - }, - # Order Service - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_9_ID" - value = "order-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_9_URI" - value = "http://order.${var.project}.local:${var.services["order"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_9_PREDICATES_0" - value = "Path=/api/orders/**" - }, - # Payment Service - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_10_ID" - value = "payment-service" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_10_URI" - value = "http://payment.${var.project}.local:${var.services["payment"].container_port}" - }, - { - name = "SPRING_CLOUD_GATEWAY_SERVER_WEBFLUX_ROUTES_10_PREDICATES_0" - value = "Path=/api/payments/**" - }, - # Actuator 설정 (새 property 이름) - { - name = "MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE" - value = "health,info,gateway" - }, - { - name = "MANAGEMENT_ENDPOINT_GATEWAY_ACCESS" - value = "unrestricted" - }, - # 디버깅용 로깅 - { - name = "LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_GATEWAY" - value = "DEBUG" - } - ] : [], - # 서비스별 커스텀 환경 변수 - [for k, v in each.value.environment_vars : { - name = k - value = v - }] - ) - - # ============================================================= - # Secrets (Parameter Store에서 주입) - # ============================================================= - secrets = concat( - # 백엔드 서비스 (gateway 제외) - DB 비밀번호, JWT 시크릿 - each.key != "gateway" ? [ - { - name = "SPRING_DATASOURCE_PASSWORD" - valueFrom = var.parameter_arns.db_password - }, - { - name = "SPRING_JWT_SECRET" - valueFrom = var.parameter_arns.jwt_secret - } - ] : [], - # Mail 비밀번호 (user 서비스) - each.key == "user" && var.parameter_arns.mail_password != null ? [ - { - name = "SPRING_MAIL_PASSWORD" - valueFrom = var.parameter_arns.mail_password - } - ] : [], - # Toss 시크릿 키 (payment 서비스) - each.key == "payment" && var.parameter_arns.toss_secret_key != null ? [ - { - name = "TOSS_PAYMENTS_SECRET_KEY" - valueFrom = var.parameter_arns.toss_secret_key - } - ] : [] - ) - - logConfiguration = { - logDriver = "awslogs" - options = { - "awslogs-group" = aws_cloudwatch_log_group.services[each.key].name - "awslogs-region" = var.region - "awslogs-stream-prefix" = "ecs" - } - } - - healthCheck = { - command = ["CMD-SHELL", "curl -f http://localhost:${each.value.container_port}${each.value.health_check_path} || exit 1"] - interval = 30 - timeout = 5 - retries = 3 - startPeriod = 60 - } - } - ]) - - tags = merge(var.common_tags, { Service = each.key }) -} - -# ============================================================================= -# ECS Services (per service) - Active Services Only -# ============================================================================= -resource "aws_ecs_service" "services" { - for_each = local.active_services - - name = "${var.project}-${each.key}-service" - cluster = aws_ecs_cluster.main.id - task_definition = aws_ecs_task_definition.services[each.key].arn - desired_count = var.standby_mode ? 0 : each.value.desired_count - launch_type = "FARGATE" - - # 모든 active 서비스를 ALB에 연결 - dynamic "load_balancer" { - for_each = contains(keys(var.target_group_arns), each.key) ? [1] : [] - content { - target_group_arn = var.target_group_arns[each.key] - container_name = "${var.project}-${each.key}-container" - container_port = each.value.container_port - } - } - - network_configuration { - subnets = var.subnet_ids - security_groups = [aws_security_group.msa_sg.id] - assign_public_ip = var.assign_public_ip - } - - # Blue/Green 배포 컨트롤러 - dynamic "deployment_controller" { - for_each = var.enable_blue_green ? [1] : [] - content { - type = "CODE_DEPLOY" - } - } - - # 기본 롤링 배포 설정 (Blue/Green 비활성화시) - dynamic "deployment_circuit_breaker" { - for_each = var.enable_blue_green ? [] : [1] - content { - enable = true - rollback = true - } - } - - # Service Connect Configuration - dynamic "service_connect_configuration" { - for_each = var.enable_service_connect ? [1] : [] - content { - enabled = true - namespace = aws_service_discovery_private_dns_namespace.main.arn - - service { - port_name = each.key - discovery_name = each.key - - client_alias { - port = each.value.container_port - dns_name = "${each.key}.${var.project}.local" - } - } - - log_configuration { - log_driver = "awslogs" - options = { - "awslogs-group" = aws_cloudwatch_log_group.services[each.key].name - "awslogs-region" = var.region - "awslogs-stream-prefix" = "service-connect" - } - } - } - } - - # Service Discovery Registration (only when Service Connect is disabled) - dynamic "service_registries" { - for_each = var.enable_service_connect ? [] : [1] - content { - registry_arn = aws_service_discovery_service.services[each.key].arn - container_name = "${var.project}-${each.key}-container" - container_port = each.value.container_port - } - } - - depends_on = [var.alb_listener_arn] - - tags = merge(var.common_tags, { Service = each.key }) - - lifecycle { - ignore_changes = var.enable_blue_green ? [task_definition, load_balancer] : [] - } -} diff --git a/infra/terraform/modules/ecs/outputs.tf b/infra/terraform/modules/ecs/outputs.tf deleted file mode 100644 index fc8710db..00000000 --- a/infra/terraform/modules/ecs/outputs.tf +++ /dev/null @@ -1,57 +0,0 @@ -output "cluster_name" { - description = "ECS 클러스터 이름" - value = aws_ecs_cluster.main.name -} - -output "cluster_arn" { - description = "ECS 클러스터 ARN" - value = aws_ecs_cluster.main.arn -} - -output "service_names" { - description = "ECS 서비스 이름 맵" - value = { for k, v in aws_ecs_service.services : k => v.name } -} - -output "service_arns" { - description = "ECS 서비스 ARN 맵" - value = { for k, v in aws_ecs_service.services : k => v.id } -} - -output "security_group_id" { - description = "MSA 보안그룹 ID" - value = aws_security_group.msa_sg.id -} - -output "task_definition_arns" { - description = "Task Definition ARN 맵" - value = { for k, v in aws_ecs_task_definition.services : k => v.arn } -} - -output "cloudwatch_log_groups" { - description = "CloudWatch Log Group 이름 맵" - value = { for k, v in aws_cloudwatch_log_group.services : k => v.name } -} - -output "service_discovery_namespace_id" { - description = "Service Discovery Namespace ID" - value = aws_service_discovery_private_dns_namespace.main.id -} - -output "service_discovery_namespace_arn" { - description = "Service Discovery Namespace ARN" - value = aws_service_discovery_private_dns_namespace.main.arn -} - -# ============================================================================= -# CodeDeploy Outputs -# ============================================================================= -output "codedeploy_app_name" { - description = "CodeDeploy Application 이름" - value = var.enable_blue_green ? aws_codedeploy_app.main[0].name : null -} - -output "codedeploy_deployment_group_names" { - description = "CodeDeploy Deployment Group 이름 맵" - value = var.enable_blue_green ? { for k, v in aws_codedeploy_deployment_group.services : k => v.deployment_group_name } : {} -} diff --git a/infra/terraform/modules/ecs/variables.tf b/infra/terraform/modules/ecs/variables.tf deleted file mode 100644 index 3ecbf149..00000000 --- a/infra/terraform/modules/ecs/variables.tf +++ /dev/null @@ -1,261 +0,0 @@ -# ============================================================================= -# Project Settings -# ============================================================================= -variable "project" { - description = "프로젝트 이름" - type = string -} - -variable "environment" { - description = "환경 (dev, prod)" - type = string - default = "dev" -} - -variable "name_prefix" { - description = "리소스 네이밍 프리픽스" - type = string -} - -variable "common_tags" { - description = "공통 태그" - type = map(string) - default = {} -} - -variable "region" { - description = "AWS 리전" - type = string -} - -# ============================================================================= -# Network Settings -# ============================================================================= -variable "vpc_id" { - description = "VPC ID" - type = string -} - -variable "subnet_ids" { - description = "ECS 서비스 서브넷 ID 목록" - type = list(string) -} - -variable "assign_public_ip" { - description = "Public IP 할당 여부" - type = bool - default = true -} - -# ============================================================================= -# ALB Integration -# ============================================================================= -variable "alb_security_group_id" { - description = "ALB 보안그룹 ID" - type = string -} - -variable "target_group_arns" { - description = "ALB Target Group ARN 맵" - type = map(string) -} - -variable "alb_listener_arn" { - description = "ALB Listener ARN (의존성용)" - type = string -} - -# ============================================================================= -# ECR Integration -# ============================================================================= -variable "ecr_repository_urls" { - description = "ECR 저장소 URL 맵" - type = map(string) -} - -# ============================================================================= -# Services Configuration -# ============================================================================= -variable "services" { - description = "서비스 구성 맵" - type = map(object({ - container_port = number - cpu = string - memory = string - desired_count = number - health_check_path = string - path_patterns = list(string) - priority = number - environment_vars = map(string) - })) -} - -variable "enable_service_connect" { - description = "ECS Service Connect 활성화 여부" - type = bool - default = true -} - -variable "log_retention_days" { - description = "로그 보관 일수" - type = number - default = 30 -} - -# ============================================================================= -# Parameter Store ARNs (Secrets 주입용) -# ============================================================================= -variable "parameter_arns" { - description = "Parameter Store ARN 맵" - type = object({ - db_password = string - jwt_secret = string - mail_password = optional(string) - toss_secret_key = optional(string) - }) -} - -# ============================================================================= -# Database Settings -# ============================================================================= -variable "db_endpoint" { - description = "RDS 엔드포인트" - type = string -} - -variable "db_name" { - description = "데이터베이스 이름" - type = string -} - -variable "db_username" { - description = "데이터베이스 사용자 이름" - type = string - sensitive = true -} - -# ============================================================================= -# Redis Settings -# ============================================================================= -variable "redis_endpoint" { - description = "Redis 엔드포인트" - type = string - default = "" -} - -# ============================================================================= -# Kafka Settings -# ============================================================================= -variable "kafka_bootstrap_servers" { - description = "Kafka Bootstrap Servers" - type = string - default = "" -} - -# ============================================================================= -# JWT Settings -# ============================================================================= -variable "jwt_expire_ms" { - description = "JWT 만료 시간 (밀리초)" - type = number - default = 3600000 -} - -variable "refresh_token_expire_days" { - description = "리프레시 토큰 만료 일수" - type = number - default = 14 -} - -# ============================================================================= -# Mail Settings -# ============================================================================= -variable "mail_host" { - description = "SMTP 호스트" - type = string - default = "smtp.gmail.com" -} - -variable "mail_port" { - description = "SMTP 포트" - type = number - default = 587 -} - -variable "mail_username" { - description = "SMTP 사용자 이름" - type = string - default = "" -} - -# ============================================================================= -# Toss Payments Settings -# ============================================================================= -variable "toss_base_url" { - description = "Toss Payments API URL" - type = string - default = "https://api.tosspayments.com" -} - -variable "toss_customer_key" { - description = "Toss Payments 고객 키" - type = string - default = "customer_1" -} - -# ============================================================================= -# Service Settings -# ============================================================================= -variable "service_active_regions" { - description = "서비스 활성 지역" - type = string - default = "종로구" -} - -# ============================================================================= -# Standby Mode (비용 절감) -# ============================================================================= -variable "standby_mode" { - description = "스탠바이 모드 (true면 모든 서비스 desired_count = 0)" - type = bool - default = false -} - -# ============================================================================= -# Blue/Green Deployment -# ============================================================================= -variable "enable_blue_green" { - description = "Blue/Green 배포 활성화 (CodeDeploy)" - type = bool - default = false -} - -variable "excluded_services" { - description = "배포에서 제외할 서비스 목록 (예: gateway)" - type = list(string) - default = [] -} - -variable "target_group_names" { - description = "ALB Target Group 이름 맵" - type = map(string) - default = {} -} - -variable "green_target_group_arns" { - description = "Green Target Group ARN 맵 (Blue/Green용)" - type = map(string) - default = {} -} - -variable "deployment_config" { - description = "CodeDeploy 배포 구성" - type = string - default = "CodeDeployDefault.ECSAllAtOnce" -} - -variable "termination_wait_time" { - description = "이전 태스크 종료 대기 시간 (분)" - type = number - default = 5 -} diff --git a/infra/terraform/modules/elasticache/main.tf b/infra/terraform/modules/elasticache/main.tf index 67ab2806..74425a8b 100644 --- a/infra/terraform/modules/elasticache/main.tf +++ b/infra/terraform/modules/elasticache/main.tf @@ -21,7 +21,7 @@ resource "aws_security_group" "redis" { vpc_id = var.vpc_id ingress { - description = "Redis from ECS" + description = "Redis from EKS nodes" from_port = 6379 to_port = 6379 protocol = "tcp" diff --git a/infra/terraform/modules/kafka/main.tf b/infra/terraform/modules/kafka/main.tf index 0bc11bb5..61cb1534 100644 --- a/infra/terraform/modules/kafka/main.tf +++ b/infra/terraform/modules/kafka/main.tf @@ -27,7 +27,7 @@ resource "aws_security_group" "kafka" { name = "${var.name_prefix}-kafka-sg" vpc_id = var.vpc_id - # Kafka 클라이언트 포트 (ECS에서 접근) + # Kafka 클라이언트 포트 (EKS에서 접근) ingress { from_port = local.kafka_port to_port = local.kafka_port diff --git a/infra/terraform/modules/monitoring/main.tf b/infra/terraform/modules/monitoring/main.tf index 8a83e87f..c9a86536 100644 --- a/infra/terraform/modules/monitoring/main.tf +++ b/infra/terraform/modules/monitoring/main.tf @@ -21,7 +21,12 @@ resource "aws_sns_topic_subscription" "email" { # ----------------------------------------------------------------------------- # ECS Alarms # ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# ECS Alarms (Optional) +# ----------------------------------------------------------------------------- resource "aws_cloudwatch_metric_alarm" "ecs_cpu_high" { + count = var.enable_ecs_alarms ? 1 : 0 + alarm_name = "${var.name_prefix}-ecs-cpu-high" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 @@ -43,6 +48,8 @@ resource "aws_cloudwatch_metric_alarm" "ecs_cpu_high" { } resource "aws_cloudwatch_metric_alarm" "ecs_memory_high" { + count = var.enable_ecs_alarms ? 1 : 0 + alarm_name = "${var.name_prefix}-ecs-memory-high" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 @@ -63,6 +70,7 @@ resource "aws_cloudwatch_metric_alarm" "ecs_memory_high" { tags = var.common_tags } + # ----------------------------------------------------------------------------- # RDS Alarms # ----------------------------------------------------------------------------- @@ -127,9 +135,11 @@ resource "aws_cloudwatch_metric_alarm" "rds_storage_low" { } # ----------------------------------------------------------------------------- -# ALB Alarms +# ALB Alarms (Optional) # ----------------------------------------------------------------------------- resource "aws_cloudwatch_metric_alarm" "alb_5xx_errors" { + count = var.enable_alb_alarms ? 1 : 0 + alarm_name = "${var.name_prefix}-alb-5xx-errors" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 @@ -151,6 +161,8 @@ resource "aws_cloudwatch_metric_alarm" "alb_5xx_errors" { } resource "aws_cloudwatch_metric_alarm" "alb_response_time" { + count = var.enable_alb_alarms ? 1 : 0 + alarm_name = "${var.name_prefix}-alb-response-time" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 @@ -170,6 +182,7 @@ resource "aws_cloudwatch_metric_alarm" "alb_response_time" { tags = var.common_tags } + # ----------------------------------------------------------------------------- # ElastiCache (Redis) Alarms # ----------------------------------------------------------------------------- diff --git a/infra/terraform/modules/monitoring/outputs.tf b/infra/terraform/modules/monitoring/outputs.tf index e315af4e..5b5e3332 100644 --- a/infra/terraform/modules/monitoring/outputs.tf +++ b/infra/terraform/modules/monitoring/outputs.tf @@ -14,12 +14,12 @@ output "sns_topic_name" { output "alarm_arns" { description = "생성된 CloudWatch Alarm ARN 목록" value = { - ecs_cpu = aws_cloudwatch_metric_alarm.ecs_cpu_high.arn - ecs_memory = aws_cloudwatch_metric_alarm.ecs_memory_high.arn + ecs_cpu = var.enable_ecs_alarms ? aws_cloudwatch_metric_alarm.ecs_cpu_high[0].arn : null + ecs_memory = var.enable_ecs_alarms ? aws_cloudwatch_metric_alarm.ecs_memory_high[0].arn : null rds_cpu = aws_cloudwatch_metric_alarm.rds_cpu_high.arn rds_connections = aws_cloudwatch_metric_alarm.rds_connections_high.arn rds_storage = aws_cloudwatch_metric_alarm.rds_storage_low.arn - alb_5xx = aws_cloudwatch_metric_alarm.alb_5xx_errors.arn - alb_response = aws_cloudwatch_metric_alarm.alb_response_time.arn + alb_5xx = var.enable_alb_alarms ? aws_cloudwatch_metric_alarm.alb_5xx_errors[0].arn : null + alb_response = var.enable_alb_alarms ? aws_cloudwatch_metric_alarm.alb_response_time[0].arn : null } } diff --git a/infra/terraform/modules/monitoring/variables.tf b/infra/terraform/modules/monitoring/variables.tf index c153416e..ab4e813e 100644 --- a/infra/terraform/modules/monitoring/variables.tf +++ b/infra/terraform/modules/monitoring/variables.tf @@ -26,11 +26,13 @@ variable "alert_email" { variable "ecs_cluster_name" { description = "ECS 클러스터 이름" type = string + default = "" } variable "ecs_service_name" { description = "ECS 서비스 이름" type = string + default = "" } variable "ecs_cpu_threshold" { @@ -77,6 +79,7 @@ variable "rds_storage_threshold_bytes" { variable "alb_arn_suffix" { description = "ALB ARN suffix (app/xxx/xxx 형식)" type = string + default = "" } variable "alb_5xx_threshold" { @@ -111,3 +114,18 @@ variable "redis_memory_threshold" { type = number default = 80 } + +# ============================================================================= +# Monitoring 옵션 +# ============================================================================= +variable "enable_ecs_alarms" { + description = "ECS 알람 생성 여부 (EKS 전환 시 보통 false)" + type = bool + default = false +} + +variable "enable_alb_alarms" { + description = "ALB 알람 생성 여부 (ALB ARN suffix가 확정된 경우에만 true)" + type = bool + default = false +} diff --git a/infra/terraform/modules/network/main.tf b/infra/terraform/modules/network/main.tf index 0831a44f..87044db9 100644 --- a/infra/terraform/modules/network/main.tf +++ b/infra/terraform/modules/network/main.tf @@ -20,6 +20,9 @@ resource "aws_subnet" "public_a" { tags = merge(var.common_tags, { Name = "${var.name_prefix}-public-a" Tier = "public" + + "kubernetes.io/cluster/${var.eks_cluster_name}" = "shared" + "kubernetes.io/role/elb" = "1" }) } @@ -32,6 +35,9 @@ resource "aws_subnet" "public_c" { tags = merge(var.common_tags, { Name = "${var.name_prefix}-public-c" Tier = "public" + + "kubernetes.io/cluster/${var.eks_cluster_name}" = "shared" + "kubernetes.io/role/elb" = "1" }) } @@ -46,6 +52,9 @@ resource "aws_subnet" "private_a" { tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-a" Tier = "private" + + "kubernetes.io/cluster/${var.eks_cluster_name}" = "shared" + "kubernetes.io/role/internal-elb" = "1" }) } @@ -54,7 +63,13 @@ resource "aws_subnet" "private_c" { cidr_block = var.private_subnet_cidrs["c"] availability_zone = var.availability_zones["c"] - tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-c" }) + tags = merge(var.common_tags, { + Name = "${var.name_prefix}-private-c" + Tier = "private" + + "kubernetes.io/cluster/${var.eks_cluster_name}" = "shared" + "kubernetes.io/role/internal-elb" = "1" + }) } # ============================================================================= diff --git a/infra/terraform/modules/network/variables.tf b/infra/terraform/modules/network/variables.tf index fba795a0..0b0011c8 100644 --- a/infra/terraform/modules/network/variables.tf +++ b/infra/terraform/modules/network/variables.tf @@ -49,3 +49,12 @@ variable "single_nat_gateway" { type = bool default = true } + +# ============================================================================= +# EKS Cluster Name +# ============================================================================= +variable "eks_cluster_name" { + description = "EKS 클러스터 이름 (Subnet tag 용도)" + type = string + default = "spot-eks" +} diff --git a/infra/terraform/modules/parameter-store/outputs.tf b/infra/terraform/modules/parameter-store/outputs.tf index a1d19c00..00c777f0 100644 --- a/infra/terraform/modules/parameter-store/outputs.tf +++ b/infra/terraform/modules/parameter-store/outputs.tf @@ -3,7 +3,7 @@ # ============================================================================= # ============================================================================= -# Parameter ARNs (ECS Task Definition secrets 블록에서 사용) +# Parameter ARNs (EKS에서는 IRSA로 Pod에서 SSM GetParameter 권한 부여 후 사용) # ============================================================================= output "db_password_arn" { description = "DB Password Parameter ARN" @@ -39,14 +39,14 @@ output "redis_endpoint_arn" { # All Parameter ARNs (IAM Policy용) # ============================================================================= output "all_parameter_arns" { - description = "모든 Parameter ARN 목록 (IAM Policy용)" + description = "모든 Parameter ARN 목록 (EKS IRSA/IAM Policy용)" value = compact([ aws_ssm_parameter.db_password.arn, aws_ssm_parameter.jwt_secret.arn, - var.mail_password != "" ? aws_ssm_parameter.mail_password[0].arn : null, - var.toss_secret_key != "" ? aws_ssm_parameter.toss_secret_key[0].arn : null, + var.mail_password != "" ? aws_ssm_parameter.mail_password[0].arn : null, + var.toss_secret_key != "" ? aws_ssm_parameter.toss_secret_key[0].arn : null, aws_ssm_parameter.db_endpoint.arn, - var.redis_endpoint != "" ? aws_ssm_parameter.redis_endpoint[0].arn : null, + var.redis_endpoint != "" ? aws_ssm_parameter.redis_endpoint[0].arn : null, ]) } @@ -54,6 +54,6 @@ output "all_parameter_arns" { # Parameter Name Prefix (for wildcard IAM policies) # ============================================================================= output "parameter_prefix" { - description = "Parameter Store prefix for IAM policies" + description = "Parameter Store prefix (EKS IRSA/IAM wildcard policy에서 사용)" value = "/${var.project}/${var.environment}" } diff --git a/infra/terraform/modules/s3/main.tf b/infra/terraform/modules/s3/main.tf index 402c5cf7..db7747e5 100644 --- a/infra/terraform/modules/s3/main.tf +++ b/infra/terraform/modules/s3/main.tf @@ -206,7 +206,23 @@ resource "aws_s3_bucket_policy" "logs" { "aws:SourceAccount" = var.account_id } } + }, + # ALB Access Logs + { + Sid = "AllowALBAccessLogs" + Effect = "Allow" + Principal = { + Service = "elasticloadbalancing.amazonaws.com" + } + Action = "s3:PutObject" + Resource = "${aws_s3_bucket.logs.arn}/alb-logs/*" + Condition = { + StringEquals = { + "aws:SourceAccount" = var.account_id + } + } } + ] }) } diff --git a/infra/terraform/modules/waf/main.tf b/infra/terraform/modules/waf/main.tf index 5c7f91a0..6edb0165 100644 --- a/infra/terraform/modules/waf/main.tf +++ b/infra/terraform/modules/waf/main.tf @@ -1,5 +1,5 @@ # ============================================================================= -# WAF Web ACL for API Gateway +# WAF Web ALB (EKS Ingress via AWS Load Balancer Controller) # ============================================================================= resource "aws_wafv2_web_acl" "main" { name = "${var.name_prefix}-waf" @@ -111,15 +111,7 @@ resource "aws_wafv2_web_acl" "main" { tags = merge(var.common_tags, { Name = "${var.name_prefix}-waf" }) } -# ============================================================================= -# WAF Association with API Gateway -# ============================================================================= -#resource "aws_wafv2_web_acl_association" "api_gateway" { -# count = var.api_gateway_stage_arn != "" ? 1 : 0 -# -# resource_arn = var.api_gateway_stage_arn -# web_acl_arn = aws_wafv2_web_acl.main.arn -#} + # ============================================================================= # CloudWatch Log Group for WAF diff --git a/infra/terraform/modules/waf/variables.tf b/infra/terraform/modules/waf/variables.tf index 6b92f8a7..4de79d9c 100644 --- a/infra/terraform/modules/waf/variables.tf +++ b/infra/terraform/modules/waf/variables.tf @@ -9,11 +9,6 @@ variable "common_tags" { default = {} } -variable "api_gateway_stage_arn" { - description = "API Gateway Stage ARN" - type = string - default = "" -} variable "rate_limit" { description = "5분당 최대 요청 수 (Rate Limiting)" From f38e2f56b9ae5057411b5c76ef02515dda720706 Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Fri, 13 Feb 2026 10:07:43 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat(#322):=20control=20plane=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/terraform/environments/dev/main.tf | 59 +++++++ infra/terraform/environments/dev/outputs.tf | 30 ++++ infra/terraform/environments/dev/provider.tf | 4 + infra/terraform/environments/dev/variables.tf | 18 +++ infra/terraform/modules/eks-addons/main.tf | 41 +++++ infra/terraform/modules/eks-addons/output.tf | 8 + .../terraform/modules/eks-addons/variables.tf | 14 ++ infra/terraform/modules/eks/main.tf | 151 ++++++++++++++++++ infra/terraform/modules/eks/output.tf | 27 ++++ infra/terraform/modules/eks/variables.tf | 37 +++++ infra/terraform/modules/elasticache/main.tf | 72 ++++----- infra/terraform/modules/irsa/main.tf | 46 ++++++ infra/terraform/modules/irsa/output.tf | 7 + infra/terraform/modules/irsa/variables.tf | 13 ++ 14 files changed, 491 insertions(+), 36 deletions(-) create mode 100644 infra/terraform/modules/eks-addons/main.tf create mode 100644 infra/terraform/modules/eks-addons/output.tf create mode 100644 infra/terraform/modules/eks-addons/variables.tf create mode 100644 infra/terraform/modules/eks/main.tf create mode 100644 infra/terraform/modules/eks/output.tf create mode 100644 infra/terraform/modules/eks/variables.tf create mode 100644 infra/terraform/modules/irsa/main.tf create mode 100644 infra/terraform/modules/irsa/output.tf create mode 100644 infra/terraform/modules/irsa/variables.tf diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf index 1cb06bfa..33eb2a54 100644 --- a/infra/terraform/environments/dev/main.tf +++ b/infra/terraform/environments/dev/main.tf @@ -262,3 +262,62 @@ module "monitoring" { # Redis 모니터링 (선택) redis_cluster_id = "${local.name_prefix}-redis-001" } + +# ============================================================================= +# eks +# ============================================================================= +module "eks" { + source = "../../modules/eks" + + name_prefix = local.name_prefix + common_tags = local.common_tags + + cluster_name = var.cluster_name + cluster_version = var.cluster_version + + vpc_id = module.network.vpc_id + + subnet_ids = module.network.private_subnet_ids + node_subnet_ids = module.network.private_subnet_ids + + endpoint_private_access = true + endpoint_public_access = false + + enable_node_group = false + + enable_node_ssm = true +} + + +data "aws_eks_cluster" "this" { + name = module.eks.cluster_name +} + +module "irsa" { + source = "../../modules/irsa" + + name_prefix = local.name_prefix + common_tags = local.common_tags + + oidc_issuer_url = module.eks.oidc_issuer_url + + service_accounts = { + aws_load_balancer_controller = { + namespace = "kube-system" + service_account = "aws-load-balancer-controller" + policy_json = var.alb_controller_policy_json + } + } +} + +module "eks_addons" { + source = "../../modules/eks-addons" + + common_tags = local.common_tags + cluster_name = module.eks.cluster_name + + enable_vpc_cni = true + enable_coredns = true + enable_kube_proxy = true + enable_ebs_csi = true +} diff --git a/infra/terraform/environments/dev/outputs.tf b/infra/terraform/environments/dev/outputs.tf index 66efc4be..a74ef49e 100644 --- a/infra/terraform/environments/dev/outputs.tf +++ b/infra/terraform/environments/dev/outputs.tf @@ -115,3 +115,33 @@ output "sns_alerts_topic_arn" { description = "알람 알림 SNS Topic ARN" value = module.monitoring.sns_topic_arn } + + +# ============================================================================= +# eks +# ============================================================================= + +output "eks_cluster_name" { + value = module.eks.cluster_name +} + +output "eks_cluster_endpoint" { + value = module.eks.cluster_endpoint +} + +output "eks_cluster_ca" { + value = module.eks.cluster_ca +} + +output "eks_node_security_group_id" { + value = module.eks.node_security_group_id +} + +output "oidc_issuer_url" { + value = try(data.aws_eks_cluster.this.identity[0].oidc[0].issuer, null) +} + + +output "irsa_role_arns" { + value = module.irsa.service_account_role_arns +} diff --git a/infra/terraform/environments/dev/provider.tf b/infra/terraform/environments/dev/provider.tf index a6e68b57..18c133d5 100644 --- a/infra/terraform/environments/dev/provider.tf +++ b/infra/terraform/environments/dev/provider.tf @@ -6,6 +6,10 @@ terraform { source = "hashicorp/aws" version = "~> 5.0" } + tls = { + source = "hashicorp/tls" + version = ">= 4.0" + } # postgresql = { # source = "cyrilgdn/postgresql" # version = "~> 1.21" diff --git a/infra/terraform/environments/dev/variables.tf b/infra/terraform/environments/dev/variables.tf index 09471ef0..e5e752a4 100644 --- a/infra/terraform/environments/dev/variables.tf +++ b/infra/terraform/environments/dev/variables.tf @@ -349,3 +349,21 @@ variable "kafka_log_retention_hours" { type = number default = 168 # 7일 } + + +# ============================================================================= +# eks 설정 +# ============================================================================= +variable "cluster_name" { + cluster_name = "spot-cluster-test" + type = string +} + +variable "cluster_version" { + type = string + default = "1.29" +} + +variable "alb_controller_policy_json" { + type = string +} diff --git a/infra/terraform/modules/eks-addons/main.tf b/infra/terraform/modules/eks-addons/main.tf new file mode 100644 index 00000000..77e5d91d --- /dev/null +++ b/infra/terraform/modules/eks-addons/main.tf @@ -0,0 +1,41 @@ +resource "aws_eks_addon" "vpc_cni" { + count = var.enable_vpc_cni ? 1 : 0 + cluster_name = var.cluster_name + addon_name = "vpc-cni" + addon_version = var.vpc_cni_version != "" ? var.vpc_cni_version : null + + resolve_conflicts_on_update = "OVERWRITE" + tags = var.common_tags +} + +resource "aws_eks_addon" "coredns" { + count = var.enable_coredns ? 1 : 0 + cluster_name = var.cluster_name + addon_name = "coredns" + addon_version = var.coredns_version != "" ? var.coredns_version : null + + resolve_conflicts_on_update = "OVERWRITE" + tags = var.common_tags +} + +resource "aws_eks_addon" "kube_proxy" { + count = var.enable_kube_proxy ? 1 : 0 + cluster_name = var.cluster_name + addon_name = "kube-proxy" + addon_version = var.kube_proxy_version != "" ? var.kube_proxy_version : null + + resolve_conflicts_on_update = "OVERWRITE" + tags = var.common_tags +} + +resource "aws_eks_addon" "ebs_csi" { + count = var.enable_ebs_csi ? 1 : 0 + cluster_name = var.cluster_name + addon_name = "aws-ebs-csi-driver" + addon_version = var.ebs_csi_version != "" ? var.ebs_csi_version : null + + resolve_conflicts_on_update = "OVERWRITE" + service_account_role_arn = var.ebs_csi_irsa_role_arn != "" ? var.ebs_csi_irsa_role_arn : null + + tags = var.common_tags +} diff --git a/infra/terraform/modules/eks-addons/output.tf b/infra/terraform/modules/eks-addons/output.tf new file mode 100644 index 00000000..5100729c --- /dev/null +++ b/infra/terraform/modules/eks-addons/output.tf @@ -0,0 +1,8 @@ +output "enabled_addons" { + value = { + vpc_cni = var.enable_vpc_cni + coredns = var.enable_coredns + kube_proxy = var.enable_kube_proxy + ebs_csi = var.enable_ebs_csi + } +} diff --git a/infra/terraform/modules/eks-addons/variables.tf b/infra/terraform/modules/eks-addons/variables.tf new file mode 100644 index 00000000..8d9004d4 --- /dev/null +++ b/infra/terraform/modules/eks-addons/variables.tf @@ -0,0 +1,14 @@ +variable "common_tags" { type = map(string) default = {} } +variable "cluster_name" { type = string } + +variable "enable_vpc_cni" { type = bool default = true } +variable "enable_coredns" { type = bool default = true } +variable "enable_kube_proxy" { type = bool default = true } +variable "enable_ebs_csi" { type = bool default = true } + +variable "vpc_cni_version" { type = string default = "" } +variable "coredns_version" { type = string default = "" } +variable "kube_proxy_version" { type = string default = "" } +variable "ebs_csi_version" { type = string default = "" } + +variable "ebs_csi_irsa_role_arn" { type = string default = "" } diff --git a/infra/terraform/modules/eks/main.tf b/infra/terraform/modules/eks/main.tf new file mode 100644 index 00000000..8cbe9969 --- /dev/null +++ b/infra/terraform/modules/eks/main.tf @@ -0,0 +1,151 @@ +resource "aws_iam_role" "cluster" { + name = "${var.name_prefix}-eks-cluster-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "eks.amazonaws.com" } + }] + }) + + tags = var.common_tags +} + +resource "aws_iam_role_policy_attachment" "cluster_policy" { + role = aws_iam_role.cluster.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" +} + +resource "aws_iam_role_policy_attachment" "vpc_resource_controller" { + role = aws_iam_role.cluster.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController" +} + +resource "aws_eks_cluster" "this" { + name = var.cluster_name + role_arn = aws_iam_role.cluster.arn + version = var.cluster_version + + vpc_config { + subnet_ids = var.subnet_ids + endpoint_private_access = var.endpoint_private_access + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + } + + enabled_cluster_log_types = var.enabled_cluster_log_types + + tags = merge(var.common_tags, { Name = var.cluster_name }) + + depends_on = [ + aws_iam_role_policy_attachment.cluster_policy, + aws_iam_role_policy_attachment.vpc_resource_controller + ] +} + +resource "aws_security_group" "node" { + name = "${var.name_prefix}-eks-node-sg" + description = "EKS node security group" + vpc_id = var.vpc_id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge(var.common_tags, { + Name = "${var.name_prefix}-eks-node-sg" + }) +} + +resource "aws_iam_role" "node" { + name = "${var.name_prefix}-eks-node-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { Service = "ec2.amazonaws.com" } + }] + }) + + tags = var.common_tags +} + +resource "aws_iam_role_policy_attachment" "node_worker" { + role = aws_iam_role.node.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" +} + +resource "aws_iam_role_policy_attachment" "node_cni" { + role = aws_iam_role.node.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" +} + +resource "aws_iam_role_policy_attachment" "node_ecr" { + role = aws_iam_role.node.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" +} + +resource "aws_iam_role_policy_attachment" "node_ssm" { + count = var.enable_node_ssm ? 1 : 0 + role = aws_iam_role.node.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_launch_template" "node" { + name_prefix = "${var.name_prefix}-eks-ng-" + + vpc_security_group_ids = [aws_security_group.node.id] + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 2 + } + + block_device_mappings { + device_name = "/dev/xvda" + ebs { + volume_size = var.node_disk_size + volume_type = "gp3" + encrypted = true + delete_on_termination = true + } + } + + tag_specifications { + resource_type = "instance" + tags = merge(var.common_tags, { + Name = "${var.name_prefix}-eks-node" + }) + } + + tags = var.common_tags +} + +resource "aws_eks_node_group" "default" { + count = var.enable_node_group && var.node_desired_size > 0 ? 1 : 0 + + cluster_name = aws_eks_cluster.this.name + node_group_name = "${var.name_prefix}-node-group" + node_role_arn = aws_iam_role.node.arn + subnet_ids = var.node_subnet_ids + + instance_types = var.node_instance_types + + scaling_config { + desired_size = var.node_desired_size + min_size = var.node_min_size + max_size = var.node_max_size + } + + tags = merge(var.common_tags, { + Name = "${var.name_prefix}-node-group" + }) +} diff --git a/infra/terraform/modules/eks/output.tf b/infra/terraform/modules/eks/output.tf new file mode 100644 index 00000000..a3212482 --- /dev/null +++ b/infra/terraform/modules/eks/output.tf @@ -0,0 +1,27 @@ +output "cluster_name" { + value = aws_eks_cluster.this.name +} + +output "cluster_arn" { + value = aws_eks_cluster.this.arn +} + +output "cluster_endpoint" { + value = aws_eks_cluster.this.endpoint +} + +output "cluster_ca" { + value = aws_eks_cluster.this.certificate_authority[0].data +} + +output "oidc_issuer_url" { + value = aws_eks_cluster.this.identity[0].oidc[0].issuer +} + +output "node_security_group_id" { + value = aws_security_group.node.id +} + +output "node_role_arn" { + value = aws_iam_role.node.arn +} diff --git a/infra/terraform/modules/eks/variables.tf b/infra/terraform/modules/eks/variables.tf new file mode 100644 index 00000000..bab1d3c6 --- /dev/null +++ b/infra/terraform/modules/eks/variables.tf @@ -0,0 +1,37 @@ +variable "name_prefix" { type = string } +variable "common_tags" { type = map(string) default = {} } + +variable "cluster_name" { type = string } +variable "cluster_version" { type = string default = "1.29" } + +variable "vpc_id" { type = string } + +variable "subnet_ids" { type = list(string) } +variable "node_subnet_ids" { type = list(string) } + +variable "endpoint_private_access" { type = bool default = true } +variable "endpoint_public_access" { type = bool default = false } +variable "public_access_cidrs" { type = list(string) default = ["0.0.0.0/0"] } + +variable "enabled_cluster_log_types" { + type = list(string) + default = ["api", "audit", "authenticator", "controllerManager", "scheduler"] +} + +variable "node_instance_types" { type = list(string) default = ["t3.medium"] } +variable "node_capacity_type" { type = string default = "ON_DEMAND" } +variable "node_ami_type" { type = string default = "AL2_x86_64" } + +variable "node_disk_size" { type = number default = 50 } + +variable "node_desired_size" { type = number default = 2 } +variable "node_min_size" { type = number default = 2 } +variable "node_max_size" { type = number default = 4 } + +variable "enable_node_ssm" { type = bool default = true } + +variable "enable_node_group" { + description = "Worker Node Group 생성 여부" + type = bool + default = false +} diff --git a/infra/terraform/modules/elasticache/main.tf b/infra/terraform/modules/elasticache/main.tf index 74425a8b..82b9dbf2 100644 --- a/infra/terraform/modules/elasticache/main.tf +++ b/infra/terraform/modules/elasticache/main.tf @@ -1,42 +1,42 @@ # ============================================================================= # ElastiCache Redis - 캐시 및 세션 저장소 # ============================================================================= - -# ----------------------------------------------------------------------------- -# Subnet Group -# ----------------------------------------------------------------------------- -resource "aws_elasticache_subnet_group" "redis" { - name = "${var.name_prefix}-redis-subnet" - subnet_ids = var.subnet_ids - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-subnet" }) -} - -# ----------------------------------------------------------------------------- -# Security Group -# ----------------------------------------------------------------------------- -resource "aws_security_group" "redis" { - name = "${var.name_prefix}-redis-sg" - description = "Security group for ElastiCache Redis" - vpc_id = var.vpc_id - - ingress { - description = "Redis from EKS nodes" - from_port = 6379 - to_port = 6379 - protocol = "tcp" - security_groups = var.allowed_security_group_ids - } - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-sg" }) -} +# +# # ----------------------------------------------------------------------------- +# # Subnet Group +# # ----------------------------------------------------------------------------- +# resource "aws_elasticache_subnet_group" "redis" { +# name = "${var.name_prefix}-redis-subnet" +# subnet_ids = var.subnet_ids +# +# tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-subnet" }) +# } +# +# # ----------------------------------------------------------------------------- +# # Security Group +# # ----------------------------------------------------------------------------- +# resource "aws_security_group" "redis" { +# name = "${var.name_prefix}-redis-sg" +# description = "Security group for ElastiCache Redis" +# vpc_id = var.vpc_id +# +# ingress { +# description = "Redis from EKS nodes" +# from_port = 6379 +# to_port = 6379 +# protocol = "tcp" +# security_groups = var.allowed_security_group_ids +# } +# +# egress { +# from_port = 0 +# to_port = 0 +# protocol = "-1" +# cidr_blocks = ["0.0.0.0/0"] +# } +# +# tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-sg" }) +# } # ----------------------------------------------------------------------------- # Parameter Group (Redis 설정 커스터마이징) diff --git a/infra/terraform/modules/irsa/main.tf b/infra/terraform/modules/irsa/main.tf new file mode 100644 index 00000000..16ccaf35 --- /dev/null +++ b/infra/terraform/modules/irsa/main.tf @@ -0,0 +1,46 @@ +data "tls_certificate" "oidc" { + url = var.oidc_issuer_url +} + +resource "aws_iam_openid_connect_provider" "this" { + url = var.oidc_issuer_url + client_id_list = ["sts.amazonaws.com"] + thumbprint_list = [data.tls_certificate.oidc.certificates[0].sha1_fingerprint] + + tags = var.common_tags +} + +resource "aws_iam_role" "service_account" { + for_each = var.service_accounts + + name = "${var.name_prefix}-${each.key}-irsa" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "sts:AssumeRoleWithWebIdentity" + Principal = { Federated = aws_iam_openid_connect_provider.this.arn } + Condition = { + StringEquals = { + "${replace(var.oidc_issuer_url, "https://", "")}:sub" = "system:serviceaccount:${each.value.namespace}:${each.value.service_account}" + "${replace(var.oidc_issuer_url, "https://", "")}:aud" = "sts.amazonaws.com" + } + } + }] + }) + + tags = var.common_tags +} + +resource "aws_iam_role_policy" "inline" { + for_each = { + for k, v in var.service_accounts : k => v + if try(length(v.policy_json), 0) > 0 + } + + name = "${var.name_prefix}-${each.key}-policy" + role = aws_iam_role.service_account[each.key].id + + policy = each.value.policy_json +} diff --git a/infra/terraform/modules/irsa/output.tf b/infra/terraform/modules/irsa/output.tf new file mode 100644 index 00000000..f3a39ca7 --- /dev/null +++ b/infra/terraform/modules/irsa/output.tf @@ -0,0 +1,7 @@ +output "oidc_provider_arn" { + value = aws_iam_openid_connect_provider.this.arn +} + +output "service_account_role_arns" { + value = { for k, r in aws_iam_role.service_account : k => r.arn } +} diff --git a/infra/terraform/modules/irsa/variables.tf b/infra/terraform/modules/irsa/variables.tf new file mode 100644 index 00000000..ebe8c37a --- /dev/null +++ b/infra/terraform/modules/irsa/variables.tf @@ -0,0 +1,13 @@ +variable "name_prefix" { type = string } +variable "common_tags" { type = map(string) default = {} } + +variable "oidc_issuer_url" { type = string } + +variable "service_accounts" { + type = map(object({ + namespace = string + service_account = string + policy_json = string + })) + default = {} +} From 06c4bded4f0c31afed36fbf5caf8bb02a049a556 Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Wed, 18 Feb 2026 19:39:49 +0900 Subject: [PATCH 3/7] feat(#332): eks_v1(DB: postgres, redis --- infra/k8s/base/kafka/kafka-connect-init.yaml | 35 ------ infra/k8s/base/storageclass-gp3.yaml | 10 ++ infra/k8s/config/common.yml | 100 +++++++++++++++ infra/k8s/config/kafka-topics.yml | 47 +++++++ infra/k8s/config/spot-gateway.yml | 112 +++++++++++++++++ infra/k8s/config/spot-order.yml | 104 ++++++++++++++++ infra/k8s/config/spot-payment.yml | 121 +++++++++++++++++++ infra/k8s/config/spot-store.yml | 73 +++++++++++ infra/k8s/config/spot-user.yml | 43 +++++++ infra/k8s/connectors/order-outbox.json | 41 +++++++ infra/k8s/connectors/payment-outbox.json | 41 +++++++ infra/k8s/connectors/register-connectors.sh | 27 +++++ 12 files changed, 719 insertions(+), 35 deletions(-) delete mode 100644 infra/k8s/base/kafka/kafka-connect-init.yaml create mode 100644 infra/k8s/base/storageclass-gp3.yaml create mode 100644 infra/k8s/config/common.yml create mode 100644 infra/k8s/config/kafka-topics.yml create mode 100644 infra/k8s/config/spot-gateway.yml create mode 100644 infra/k8s/config/spot-order.yml create mode 100644 infra/k8s/config/spot-payment.yml create mode 100644 infra/k8s/config/spot-store.yml create mode 100644 infra/k8s/config/spot-user.yml create mode 100644 infra/k8s/connectors/order-outbox.json create mode 100644 infra/k8s/connectors/payment-outbox.json create mode 100644 infra/k8s/connectors/register-connectors.sh diff --git a/infra/k8s/base/kafka/kafka-connect-init.yaml b/infra/k8s/base/kafka/kafka-connect-init.yaml deleted file mode 100644 index d4eb3977..00000000 --- a/infra/k8s/base/kafka/kafka-connect-init.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: kafka-connect-init - namespace: spot -spec: - ttlSecondsAfterFinished: 60 # 해당 job 성공 후 60초 뒤에 pod 자동 삭제 - template: - spec: - restartPolicy: OnFailure - containers: - - name: kafka-connect-init - image: curlimages/curl:latest - env: - - name: CONNECT_URL - value: "http://kafka-connect-svc:8083" - envFrom: - - secretRef: - name: spot-secrets - command: ["/bin/sh"] - args: ["/configs/register-connectors.sh"] - volumeMounts: - - name: config-volume - mountPath: /configs - resources: - requests: - memory: "32Mi" - cpu: "10m" - limits: - memory: "64Mi" - cpu: "50m" - volumes: - - name: config-volume - configMap: - name: kafka-connect-init-config \ No newline at end of file diff --git a/infra/k8s/base/storageclass-gp3.yaml b/infra/k8s/base/storageclass-gp3.yaml new file mode 100644 index 00000000..d5e6d224 --- /dev/null +++ b/infra/k8s/base/storageclass-gp3.yaml @@ -0,0 +1,10 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: gp3 +provisioner: ebs.csi.aws.com +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true +parameters: + type: gp3 + fsType: ext4 diff --git a/infra/k8s/config/common.yml b/infra/k8s/config/common.yml new file mode 100644 index 00000000..8ba5de70 --- /dev/null +++ b/infra/k8s/config/common.yml @@ -0,0 +1,100 @@ +spring: + jackson: + time-zone: Asia/Seoul + application: + name: spot-common + + datasource: + url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://${DB_HOST:localhost}:5432/myapp_db} + username: ${SPRING_DATASOURCE_USERNAME:admin} + password: ${SPRING_DATASOURCE_PASSWORD:secret} + driver-class-name: org.postgresql.Driver + hikari: + maximum-pool-size: 3 + minimum-idle: 1 + + data: + redis: + host: ${SPRING_DATA_REDIS_HOST:localhost} + port: ${SPRING_DATA_REDIS_PORT:6379} + + temporal: + connection: + target: ${SPRING_TEMPORAL_CONNECTION_TARGET:temporal:7233} + namespace: default + # + jpa: + open-in-view: false + hibernate: + ddl-auto: update # 개발 단계에서는 update, 운영 단계에서는 none 또는 validate 권장 + show-sql: false # 실행되는 SQL을 콘솔에 출력 + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.PostgreSQLDialect + jwt: + secret: ${SPRING_JWT_SECRET:MyVeryStrongSecretKeyForJWT2024!!ThisIsAtLeast32BytesLongForHS256Algorithm} + expire-ms: ${SPRING_JWT_EXPIRE_MS:3600000} + + security: + refresh-token: + expire-days: 14 + + mvc: + cors: + mappings: + '[/**]': + allowedOriginPatterns: + - '*' + allowedMethods: + - GET + - POST + - PATCH + - DELETE + - OPTIONS + allowedHeaders: + - '*' + exposedHeaders: + - Authorization + allowCredentials: true + + mail: + host: smtp.gmail.com + port: 587 + username: ${spring.mail.username:${EMAIL_USERNAME}} + password: ${spring.mail.password:${EMAIL_PASSWORD}} + properties: + mail.debug: true + mail.connectiontimeout: 5000 + mail.smtp.auth: true + mail.smtp.starttls.enable: true + mail.smtp.starttls.required: true + +service: + active-regions: 종로구 + +toss: + payments: + base-url: https://api.tosspayments.com + customerKey: ${spring.toss.customerKey:${TOSS_CUSTOMER_KEY}} + secretKey: ${spring.toss.secretKey:${TOSS_SECRET_KEY}} + timeout: 10 + +management: + endpoints: + web: + exposure: + include: + - health + - info + - metrics + - prometheus + + endpoint: + health: + probes: + enabled: true + + metrics: + tags: + application: ${spring.application.name} \ No newline at end of file diff --git a/infra/k8s/config/kafka-topics.yml b/infra/k8s/config/kafka-topics.yml new file mode 100644 index 00000000..e0b3f031 --- /dev/null +++ b/infra/k8s/config/kafka-topics.yml @@ -0,0 +1,47 @@ +spring: + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} + + listener: + ack-mode: MANUAL + + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + acks: all + retries: 5 + properties: + retry.backoff.ms: 2000 + reconnect.backoff.ms: 5000 + reconnect.backoff.max.ms: 30000 + delivery.timeout.ms: 30000 + enable.idempotence: true + max.in.flight.requests.per.connection: 5 + + consumer: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + enable-auto-commit: false + auto-offset-reset: earliest + max-poll-records: 50 + max.poll.interval.ms: 300000 + session.timeout.ms: 15000 + heartbeat.interval.ms: 3000 + group: + order: order-group + payment: payment-group + customer: customer-group + chef: chef-group + owner: owner-group + + topic: + order: + created: spot.order.created + pending: spot.order.pending + accepted: spot.order.accepted + cancelled: spot.order.cancelled + payment: + succeeded: spot.payment.succeeded + refunded: spot.payment.refunded + payment-auth: + required: spot.payment_auth.required \ No newline at end of file diff --git a/infra/k8s/config/spot-gateway.yml b/infra/k8s/config/spot-gateway.yml new file mode 100644 index 00000000..ec48d0d5 --- /dev/null +++ b/infra/k8s/config/spot-gateway.yml @@ -0,0 +1,112 @@ +spring: + cloud: + gateway: + server: + webflux: + routes: + - id: user-auth + uri: ${SPOT_USER_URI:http://spot-user:8081} + predicates: + - Path=/api/login,/api/join,/api/auth/refresh + + - id: user-service + uri: ${SPOT_USER_URI:http://spot-user:8081} + predicates: + - Path=/api/users/**,/api/admin/** + + - id: store-service + uri: ${SPOT_STORE_URI:http://spot-store:8083} + predicates: + - Path=/api/stores/**,/api/categories/**,/api/reviews/** + + - id: order-service + uri: ${SPOT_ORDER_URI:http://spot-order:8082} + predicates: + - Path=/api/orders/** + + - id: payment-service + uri: ${SPOT_PAYMENT_URI:http://spot-payment:8084} + predicates: + - Path=/api/payments/** + + - id: block-internal + uri: http://localhost:9999 + predicates: + - Path=/internal/** + filters: + - SetStatus=403 + # ==================== OpenAPI Routes ==================== + - id: user-api-docs + uri: http://spot-user:8081 + predicates: + - Path=/v3/api-docs/user + filters: + - RewritePath=/v3/api-docs/user, /v3/api-docs + + - id: order-api-docs + uri: http://spot-order:8082 + predicates: + - Path=/v3/api-docs/order + filters: + - RewritePath=/v3/api-docs/order, /v3/api-docs + + - id: store-api-docs + uri: http://spot-store:8083 + predicates: + - Path=/v3/api-docs/store + filters: + - RewritePath=/v3/api-docs/store, /v3/api-docs + + - id: payment-api-docs + uri: http://spot-payment:8084 + predicates: + - Path=/v3/api-docs/payment + filters: + - RewritePath=/v3/api-docs/payment, /v3/api-docs + metrics: + enabled: true + httpclient: + wiretap: false + response-timeout: 5s + httpserver: + wiretap: false + + + +# ==================== SpringDoc OpenAPI (통합 Swagger) ==================== +springdoc: + swagger-ui: + urls: + - name: User Service + url: /v3/api-docs/user + - name: Store Service + url: /v3/api-docs/store + - name: Order Service + url: /v3/api-docs/order + - name: Payment Service + url: /v3/api-docs/payment + urls-primary-name: User Service + +test: + marker: spot-gateway-yml-loaded + +logging: + level: + org.springframework.boot.context.config: DEBUG + org.springframework.cloud.gateway: DEBUG + org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator: DEBUG + org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping: TRACE + +management: + endpoints: + web: + exposure: + include: + - health + - info + - metrics + - gateway + - prometheus + endpoint: + gateway: + access: unrestricted \ No newline at end of file diff --git a/infra/k8s/config/spot-order.yml b/infra/k8s/config/spot-order.yml new file mode 100644 index 00000000..80ac090d --- /dev/null +++ b/infra/k8s/config/spot-order.yml @@ -0,0 +1,104 @@ +server: + port: 8082 + +spring: + application: + name: spot-order + temporal: + workers-auto-discovery: + packages: + - "com.example.Spot" + workers: + - task-queue: ORDER_TASK_QUEUE + name: order-worker + threads: + virtual: + enabled: true + +feign: + user: + url: ${FEIGN_USER_URL:http://spot-user:8081} + store: + url: ${FEIGN_STORE_URL:http://spot-store:8083} + payment: + url: ${FEIGN_PAYMENT_URL:http://spot-payment:8084} + client: + config: + spot-user: + connectTimeout: 100 + readTimeout: 500 + loggerLevel: BASIC + + + +resilience4j: + timelimiter: + configs: + default: + cancelRunningFuture: true + instances: + user_validate_activeUser: + timeoutDuration: 250ms + + bulkhead: + instances: + user_validate_activeUser: + maxConcurrentCalls: 40 + maxWaitDuration: 0 + + retry: + instances: + user_validate_activeUser: + maxAttempts: 1 + waitDuration: 0ms + + circuitbreaker: + configs: + cb_slowCall_user: + slidingWindowType: COUNT_BASED + slidingWindowSize: 20 + minimumNumberOfCalls: 10 + slowCallRateThreshold: 50 + slowCallDurationThreshold: 250ms + failureRateThreshold: 50 + waitDurationInOpenState: 10s + permittedNumberOfCallsInHalfOpenState: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + recordExceptions: + - java.io.IOException + - java.net.SocketTimeoutException + - feign.RetryableException + - feign.FeignException + instances: + user_validate_activeUser: + baseConfig: cb_slowCall_user + +management: + endpoints: + web: + exposure: + include: + - health + - info + - metrics + - beans + - mappings + - env + - configprops + - prometheus + + endpoint: + health: + show-details: always + metrics: + tags: + application: ${spring.application.name} + +logging: + level: + root: INFO + com.example.Spot: INFO + feign: INFO + io.github.resilience4j.circuitbreaker: INFO + io.github.resilience4j.retry: INFO + io.github.resilience4j.bulkhead: INFO diff --git a/infra/k8s/config/spot-payment.yml b/infra/k8s/config/spot-payment.yml new file mode 100644 index 00000000..27cd4ac0 --- /dev/null +++ b/infra/k8s/config/spot-payment.yml @@ -0,0 +1,121 @@ +server: + port: 8084 + +spring: + application: + name: spot-payment + temporal: + workers-auto-discovery: + packages: + - "com.example.Spot" + workers: + - task-queue: PAYMENT_TASK_QUEUE + name: payment-worker + threads: + virtual: + enabled: true + +feign: + user: + url: ${FEIGN_USER_URL:http://spot-user:8081} + order: + url: ${FEIGN_ORDER_URL:http://spot-order:8082} + store: + url: ${FEIGN_STORE_URL:http://spot-store:8083} + payment: + url: ${FEIGN_PAYMENT_URL:http://spot-payment:8084} + client: + config: + spot-store: + connectTimeout: 100 + readTimeout: 600 + loggerLevel: BASIC + spot-payment: + connectTimeout: 100 + readTimeout: 700 + loggerLevel: BASIC + +resilience4j: + retry: + instances: + store_me_ownership: + maxAttempts: 1 + waitDuration: 0ms + store_menus_validation: + maxAttempts: 2 + waitDuration: 50ms + retryExceptions: + - java.io.IOException + - java.net.SocketTimeoutException + - feign.RetryableException + payment_ready_create: + maxAttempts: 1 + waitDuration: 0ms + + circuitbreaker: + configs: + cb_failureRate_short: + slidingWindowType: COUNT_BASED + slidingWindowSize: 20 + minimumNumberOfCalls: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 10s + permittedNumberOfCallsInHalfOpenState: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + recordExceptions: + - java.io.IOException + - java.net.SocketTimeoutException + - feign.RetryableException + - feign.FeignException + + cb_slowCall_short: + slidingWindowType: COUNT_BASED + slidingWindowSize: 20 + minimumNumberOfCalls: 10 + slowCallRateThreshold: 50 + slowCallDurationThreshold: 300ms + failureRateThreshold: 50 + waitDurationInOpenState: 10s + permittedNumberOfCallsInHalfOpenState: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + recordExceptions: + - java.io.IOException + - java.net.SocketTimeoutException + - feign.RetryableException + - feign.FeignException + + instances: + store_me_ownership: + baseConfig: cb_failureRate_short + store_menus_validation: + baseConfig: cb_slowCall_short + payment_ready_create: + baseConfig: cb_slowCall_short + +management: + endpoints: + web: + exposure: + include: + - health + - info + - metrics + - prometheus + + endpoint: + health: + probes: + enabled: true + + metrics: + tags: + application: ${spring.application.name} + +logging: + level: + root: INFO + com.example.Spot: INFO + feign: INFO + io.github.resilience4j.circuitbreaker: INFO + io.github.resilience4j.retry: INFO + io.github.resilience4j.bulkhead: INFO \ No newline at end of file diff --git a/infra/k8s/config/spot-store.yml b/infra/k8s/config/spot-store.yml new file mode 100644 index 00000000..3646e300 --- /dev/null +++ b/infra/k8s/config/spot-store.yml @@ -0,0 +1,73 @@ +server: + port: 8083 + +feign: + user: + url: ${FEIGN_USER_URL:http://spot-user:8081} + order: + url: ${FEIGN_ORDER_URL:http://spot-order:8082} + client: + config: + spot-user: + connectTimeout: 100 + readTimeout: 400 + loggerLevel: BASIC + spot-order: + connectTimeout: 100 + readTimeout: 400 + loggerLevel: BASIC + + +resilience4j: + retry: + instances: + user_validate_activeUser: + maxAttempts: 1 + waitDuration: 0ms + + circuitbreaker: + configs: + cb_failureRate_short: + slidingWindowType: COUNT_BASED + slidingWindowSize: 20 + minimumNumberOfCalls: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 10s + permittedNumberOfCallsInHalfOpenState: 5 + automaticTransitionFromOpenToHalfOpenEnabled: true + recordExceptions: + - java.io.IOException + - java.net.SocketTimeoutException + - feign.RetryableException + - feign.FeignException + instances: + user_validate_activeUser: + baseConfig: cb_failureRate_short + +management: + endpoints: + web: + exposure: + include: + - health + - info + - metrics + - prometheus + + endpoint: + health: + probes: + enabled: true + + metrics: + tags: + application: ${spring.application.name} + +logging: + level: + root: INFO + com.example.Spot: INFO + feign.Logger: DEBUG + io.github.resilience4j.circuitbreaker: DEBUG + io.github.resilience4j.retry: DEBUG + io.github.resilience4j.bulkhead: DEBUG \ No newline at end of file diff --git a/infra/k8s/config/spot-user.yml b/infra/k8s/config/spot-user.yml new file mode 100644 index 00000000..4a8f3d67 --- /dev/null +++ b/infra/k8s/config/spot-user.yml @@ -0,0 +1,43 @@ +server: + port: 8081 + +feign: + order: + url: ${FEIGN_ORDER_URL:http://spot-order:8082} + store: + url: ${FEIGN_STORE_URL:http://spot-store:8083} + payment: + url: ${FEIGN_PAYMENT_URL:http://spot-payment:8084} + client: + config: + default: + loggerLevel: FULL + + +management: + endpoints: + web: + exposure: + include: + - health + - info + - metrics + - prometheus + + endpoint: + health: + probes: + enabled: true + + metrics: + tags: + application: ${spring.application.name} + +logging: + level: + feign: DEBUG + com.example.Spot: DEBUG + com.example.Spot.global.feign: DEBUG + io.github.resilience4j.circuitbreaker: INFO + io.github.resilience4j.retry: INFO + io.github.resilience4j.bulkhead: INFO \ No newline at end of file diff --git a/infra/k8s/connectors/order-outbox.json b/infra/k8s/connectors/order-outbox.json new file mode 100644 index 00000000..1d40ef9c --- /dev/null +++ b/infra/k8s/connectors/order-outbox.json @@ -0,0 +1,41 @@ +{ + "name": "order-outbox-connector", + "config": { + "connector.class": "io.debezium.connector.postgresql.PostgresConnector", + "tasks.max": "1", + "database.hostname": "${env:DB_HOST}", + "database.port": "5432", + "database.user": "${env:SPRING_DATASOURCE_USERNAME}", + "database.password": "${env:SPRING_DATASOURCE_PASSWORD}", + "database.dbname": "${env:DB_NAME}", + "topic.prefix": "order_outbox_cdc", + "plugin.name": "pgoutput", + "slot.name": "order_outbox_slot", + "snapshot.mode": "no_data", + "snapshot.locking.mode": "none", + "table.include.list": "public.p_order_outbox", + "tombstones.on.delete": "false", + "transforms": "outbox", + "transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter", + "transforms.outbox.table.field.event.id": "id", + "transforms.outbox.table.field.event.key": "aggregate_id", + "transforms.outbox.table.field.event.type": "event_type", + "transforms.outbox.table.field.event.payload": "payload", + "transforms.outbox.route.by.field": "event_type", + "transforms.outbox.route.topic.replacement": "${routedByValue}", + "key.converter": "org.apache.kafka.connect.json.JsonConverter", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "key.converter.schemas.enable": "false", + "value.converter.schemas.enable": "false", + "transforms.outbox.table.expand.json.payload": "true", + "producer.acks": "all", + "producer.enable.idempotence": "true", + "producer.max.in.flight.requests.per.connection": "5", + "producer.retries": "100", + "producer.delivery.timeout.ms": "120000", + "producer.retry.backoff.ms": "500", + "producer.compression.type": "lz4", + "producer.linger.ms": "20", + "producer.batch.size": "65536" + } +} \ No newline at end of file diff --git a/infra/k8s/connectors/payment-outbox.json b/infra/k8s/connectors/payment-outbox.json new file mode 100644 index 00000000..0675d4a9 --- /dev/null +++ b/infra/k8s/connectors/payment-outbox.json @@ -0,0 +1,41 @@ +{ + "name": "payment-outbox-connector", + "config": { + "connector.class": "io.debezium.connector.postgresql.PostgresConnector", + "tasks.max": "1", + "database.hostname": "${env:DB_HOST}", + "database.port": "5432", + "database.user": "${env:SPRING_DATASOURCE_USERNAME}", + "database.password": "${env:SPRING_DATASOURCE_PASSWORD}", + "database.dbname": "${env:DB_NAME}", + "topic.prefix": "payment_outbox_cdc", + "plugin.name": "pgoutput", + "slot.name": "payment_outbox_slot", + "snapshot.mode": "no_data", + "snapshot.locking.mode": "none", + "table.include.list": "public.p_payment_outbox", + "tombstones.on.delete": "false", + "transforms": "outbox", + "transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter", + "transforms.outbox.table.field.event.id": "id", + "transforms.outbox.table.field.event.key": "aggregate_id", + "transforms.outbox.table.field.event.type": "event_type", + "transforms.outbox.table.field.event.payload": "payload", + "transforms.outbox.route.by.field": "event_type", + "transforms.outbox.route.topic.replacement": "${routedByValue}", + "key.converter": "org.apache.kafka.connect.json.JsonConverter", + "value.converter": "org.apache.kafka.connect.json.JsonConverter", + "key.converter.schemas.enable": "false", + "value.converter.schemas.enable": "false", + "transforms.outbox.table.expand.json.payload": "true", + "producer.acks": "all", + "producer.enable.idempotence": "true", + "producer.max.in.flight.requests.per.connection": "5", + "producer.retries": "100", + "producer.delivery.timeout.ms": "120000", + "producer.retry.backoff.ms": "500", + "producer.compression.type": "lz4", + "producer.linger.ms": "20", + "producer.batch.size": "65536" + } +} \ No newline at end of file diff --git a/infra/k8s/connectors/register-connectors.sh b/infra/k8s/connectors/register-connectors.sh new file mode 100644 index 00000000..7c4a201f --- /dev/null +++ b/infra/k8s/connectors/register-connectors.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +TARGET_URL=${CONNECT_URL:-"http://connect:8083"} + +echo "Waiting for Kafka Connect..." +while [ $(curl -s -o /dev/null -w "%{http_code}" $TARGET_URL) -ne 200 ]; do + sleep 3 +done + +echo "Registering connectors from /configs..." +for file in /configs/*.json; do + filename=$(basename "$file") + echo "Processing $filename..." + + sed -e "s|\${env:DB_HOST}|$DB_HOST|g" \ + -e "s|\${env:SPRING_DATASOURCE_USERNAME}|$SPRING_DATASOURCE_USERNAME|g" \ + -e "s|\${env:SPRING_DATASOURCE_PASSWORD}|$SPRING_DATASOURCE_PASSWORD|g" \ + -e "s|\${env:DB_NAME}|$DB_NAME|g" \ + "$file" > "/tmp/$filename" + + response=$(curl -s -X POST -H "Content-Type: application/json" \ + -d @"/tmp/$filename" \ + $TARGET_URL/connectors) + echo "Response for $filename: $response" +done + +echo "ALL connectors Created" \ No newline at end of file From 30484ca7ad3f1a563486298a2fe6d30bafdcc117 Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Wed, 18 Feb 2026 19:40:07 +0900 Subject: [PATCH 4/7] feat(#332): eks_v1(DB: postgres, redis --- infra/k8s/apps/spot-gateway.yaml | 6 +- infra/k8s/apps/spot-ingress.yaml | 22 +- infra/k8s/apps/spot-order.yaml | 10 +- infra/k8s/apps/spot-payment.yaml | 10 +- infra/k8s/apps/spot-store.yaml | 6 +- infra/k8s/apps/spot-user.yaml | 10 +- infra/k8s/base/configmap.yaml | 12 +- infra/k8s/base/kafka/kafka-connect.yaml | 117 +++++----- infra/k8s/base/kafka/kafka-ui.yaml | 18 +- infra/k8s/base/kafka/kafka.yaml | 122 +++-------- .../k8s/base/monitoring/grafana/grafana.yaml | 1 + infra/k8s/base/monitoring/loki/loki.yaml | 1 + infra/k8s/base/postgres.yaml | 12 +- infra/k8s/kustomization.yaml | 26 +-- .../environments/dev/.terraform.lock.hcl | 39 ++++ infra/terraform/environments/dev/main.tf | 163 ++++---------- infra/terraform/environments/dev/outputs.tf | 55 +---- infra/terraform/environments/dev/provider.tf | 14 +- infra/terraform/environments/dev/variables.tf | 13 +- infra/terraform/modules/alb/main.tf | 203 ------------------ infra/terraform/modules/alb/outputs.tf | 57 ----- infra/terraform/modules/alb/variables.tf | 65 ------ infra/terraform/modules/dns/main.tf | 9 +- infra/terraform/modules/dns/variables.tf | 10 + .../terraform/modules/eks-addons/variables.tf | 55 ++++- infra/terraform/modules/eks/main.tf | 6 +- infra/terraform/modules/eks/output.tf | 5 +- infra/terraform/modules/eks/variables.tf | 94 ++++++-- infra/terraform/modules/elasticache/main.tf | 64 +++--- infra/terraform/modules/irsa/main.tf | 75 ++++++- infra/terraform/modules/irsa/variables.tf | 15 +- infra/terraform/modules/kafka/main.tf | 29 +-- infra/terraform/modules/kafka/variables.tf | 6 + infra/terraform/modules/network/main.tf | 126 +++++++---- infra/terraform/modules/network/outputs.tf | 9 +- infra/terraform/modules/network/variables.tf | 2 +- infra/terraform/modules/waf/main.tf | 48 ++++- infra/terraform/modules/waf/variables.tf | 5 + 38 files changed, 671 insertions(+), 869 deletions(-) delete mode 100644 infra/terraform/modules/alb/main.tf delete mode 100644 infra/terraform/modules/alb/outputs.tf delete mode 100644 infra/terraform/modules/alb/variables.tf diff --git a/infra/k8s/apps/spot-gateway.yaml b/infra/k8s/apps/spot-gateway.yaml index 5ee338f3..1ef206fe 100644 --- a/infra/k8s/apps/spot-gateway.yaml +++ b/infra/k8s/apps/spot-gateway.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: spot-gateway - image: spot-registry.localhost:5111/spot-gateway:latest + image: 322546275072.dkr.ecr.ap-northeast-2.amazonaws.com/spot-gateway:latest imagePullPolicy: Always ports: - containerPort: 8080 @@ -35,8 +35,8 @@ spec: readOnly: true resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "256Mi" + cpu: "75m" limits: memory: "1Gi" cpu: "800m" diff --git a/infra/k8s/apps/spot-ingress.yaml b/infra/k8s/apps/spot-ingress.yaml index d9477dc4..31f8efde 100644 --- a/infra/k8s/apps/spot-ingress.yaml +++ b/infra/k8s/apps/spot-ingress.yaml @@ -1,15 +1,20 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: spot-ingress + name: aws-ingress namespace: spot annotations: - kubernetes.io/ingress.class: nginx -# nginx.ingress.kubernetes.io/rewrite-target: /$1 # 첫 번째 prefix 제거 + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip # IP 모드 + alb.ingress.kubernetes.io/healthcheck-path: /actuator/health + alb.ingress.kubernetes.io/success-codes: "200" # 헬스 체크 성공 코드 + alb.ingress.kubernetes.io/load-balancer-name: spot-dev-alb # tf위한 이름 고정 + alb.ingress.kubernetes.io/subnets: subnet-00e1be3889eafda18,subnet-03bfda9c6a9c7a75a spec: - ingressClassName: nginx + ingressClassName: alb rules: - - host: spot.localhost + # spot + - host: spotorder.org http: paths: - path: / @@ -17,12 +22,5 @@ spec: backend: service: name: spot-gateway - port: - number: 80 - - path: /kafka-ui - pathType: Prefix - backend: - service: - name: kafka-ui-svc port: number: 80 \ No newline at end of file diff --git a/infra/k8s/apps/spot-order.yaml b/infra/k8s/apps/spot-order.yaml index 145ae186..159055d3 100644 --- a/infra/k8s/apps/spot-order.yaml +++ b/infra/k8s/apps/spot-order.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: spot-order - image: spot-registry.localhost:5111/spot-order:latest + image: 322546275072.dkr.ecr.ap-northeast-2.amazonaws.com/spot-order imagePullPolicy: Always ports: - containerPort: 8082 @@ -35,8 +35,8 @@ spec: readOnly: true resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "256Mi" + cpu: "75m" limits: memory: "1Gi" cpu: "800m" @@ -44,13 +44,13 @@ spec: httpGet: path: /actuator/health port: 8082 - initialDelaySeconds: 30 + initialDelaySeconds: 150 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8082 - initialDelaySeconds: 60 + initialDelaySeconds: 180 periodSeconds: 30 volumes: - name: app-config diff --git a/infra/k8s/apps/spot-payment.yaml b/infra/k8s/apps/spot-payment.yaml index 59b19fcd..2d21e9b4 100644 --- a/infra/k8s/apps/spot-payment.yaml +++ b/infra/k8s/apps/spot-payment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: spot-payment - image: spot-registry.localhost:5111/spot-payment:latest + image: 322546275072.dkr.ecr.ap-northeast-2.amazonaws.com/spot-payment imagePullPolicy: Always ports: - containerPort: 8084 @@ -35,8 +35,8 @@ spec: readOnly: true resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "256Mi" + cpu: "75m" limits: memory: "1Gi" cpu: "800m" @@ -44,13 +44,13 @@ spec: httpGet: path: /actuator/health port: 8084 - initialDelaySeconds: 30 + initialDelaySeconds: 150 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8084 - initialDelaySeconds: 60 + initialDelaySeconds: 180 periodSeconds: 30 volumes: - name: app-config diff --git a/infra/k8s/apps/spot-store.yaml b/infra/k8s/apps/spot-store.yaml index b4dc2b8e..c9dbc22f 100644 --- a/infra/k8s/apps/spot-store.yaml +++ b/infra/k8s/apps/spot-store.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: spot-store - image: spot-registry.localhost:5111/spot-store:latest + image: 322546275072.dkr.ecr.ap-northeast-2.amazonaws.com/spot-store imagePullPolicy: Always ports: - containerPort: 8083 @@ -35,8 +35,8 @@ spec: readOnly: true resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "256Mi" + cpu: "75m" limits: memory: "1Gi" cpu: "800m" diff --git a/infra/k8s/apps/spot-user.yaml b/infra/k8s/apps/spot-user.yaml index d1215c15..b72aaf00 100644 --- a/infra/k8s/apps/spot-user.yaml +++ b/infra/k8s/apps/spot-user.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: spot-user - image: spot-registry.localhost:5111/spot-user:latest + image: 322546275072.dkr.ecr.ap-northeast-2.amazonaws.com/spot-user imagePullPolicy: Always ports: - containerPort: 8081 @@ -35,8 +35,8 @@ spec: readOnly: true resources: requests: - memory: "512Mi" - cpu: "500m" + memory: "256Mi" + cpu: "75m" limits: memory: "1Gi" cpu: "800m" @@ -44,13 +44,13 @@ spec: httpGet: path: /actuator/health port: 8081 - initialDelaySeconds: 30 + initialDelaySeconds: 150 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8081 - initialDelaySeconds: 60 + initialDelaySeconds: 180 periodSeconds: 30 volumes: - name: app-config diff --git a/infra/k8s/base/configmap.yaml b/infra/k8s/base/configmap.yaml index c36d287e..b9a33f55 100644 --- a/infra/k8s/base/configmap.yaml +++ b/infra/k8s/base/configmap.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: spot-common-config + name: spot-app-config namespace: spot data: SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/myapp_db" @@ -10,13 +10,3 @@ data: SPRING_DATA_REDIS_PORT: "6379" KAFKA_BOOTSTRAP_SERVERS: "kafka:9092" --- -apiVersion: v1 -kind: Secret -metadata: - name: monitoring-secrets - namespace: monitoring -type: Opaque -stringData: - GF_SECURITY_ADMIN_USER: "spot" - GF_SECURITY_ADMIN_PASSWORD: "spot-grafana" ---- diff --git a/infra/k8s/base/kafka/kafka-connect.yaml b/infra/k8s/base/kafka/kafka-connect.yaml index 87684cc8..4510e4dc 100644 --- a/infra/k8s/base/kafka/kafka-connect.yaml +++ b/infra/k8s/base/kafka/kafka-connect.yaml @@ -1,61 +1,64 @@ -apiVersion: apps/v1 -kind: Deployment +apiVersion: kafka.strimzi.io/v1 +kind: KafkaConnect metadata: - name: kafka-connect + name: spot-connect namespace: spot + annotations: + strimzi.io/use-connector-resources: "true" spec: - replicas: 1 - selector: - matchLabels: - app: kafka-connect + version: 4.0.0 + replicas: 2 + bootstrapServers: spot-cluster-kafka-bootstrap:9092 + image: spot-registry.localhost:5111/spot-connect-custom:latest + + groupId: spot-connect-group + configStorageTopic: connect_configs + offsetStorageTopic: connect_offsets + statusStorageTopic: connect_status + + config: + config.storage.replication.factor: 3 + offset.storage.replication.factor: 3 + status.storage.replication.factor: 3 + + key.converter: org.apache.kafka.connect.json.JsonConverter + value.converter: org.apache.kafka.connect.json.JsonConverter + key.converter.schemas.enable: false + value.converter.schemas.enable: false + config.providers: env + config.providers.env.class: org.apache.kafka.common.config.provider.EnvVarConfigProvider + log.level: WARN + template: - metadata: - labels: - app: kafka-connect - spec: - containers: - - name: kafka-connect - image: quay.io/debezium/connect:3.4.0.Final - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8083 - env: - - name: CONNECT_CONFIG_PROVIDERS - value: 'env' - - name: CONNECT_CONFIG_PROVIDERS_ENV_CLASS - value: 'org.apache.kafka.common.config.provider.EnvVarConfigProvider' - - name: CONNECT_BOOTSTRAP_SERVERS - value: kafka:9092 - - name: GROUP_ID - value: "1" - - name: CONFIG_STORAGE_TOPIC - value: my_connect_configs - - name: OFFSET_STORAGE_TOPIC - value: my_connect_offsets - - name: STATUS_STORAGE_TOPIC - value: my_connect_statuses - - name: KEY_CONVERTER - value: org.apache.kafka.connect.json.JsonConverter - - name: VALUE_CONVERTER - value: org.apache.kafka.connect.json.JsonConverter - - name: CONNECT_KEY_CONVERTER_SCHEMAS_ENABLE - value: "false" - - name: CONNECT_VALUE_CONVERTER_SCHEMAS_ENABLE - value: "false" - - name: LOGGING_LEVEL - value: 'WARN' - - name: CONNECT_LOG4J_LOGGERS - value: "org.apache.kafka.connect.runtime.rest=WARN,org.reflections=ERROR" ---- -apiVersion: v1 -kind: Service -metadata: - name: kafka-connect-svc - namespace: spot -spec: - type: ClusterIP - selector: - app: kafka-connect - ports: - - port: 8083 - targetPort: 8083 \ No newline at end of file + pod: + enableServiceLinks: false + connectContainer: + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: spot-secrets + key: DB_HOST + - name: DB_NAME + valueFrom: + secretKeyRef: + name: spot-secrets + key: DB_NAME + - name: SPRING_DATASOURCE_USERNAME + valueFrom: + secretKeyRef: + name: spot-secrets + key: SPRING_DATASOURCE_USERNAME + - name: SPRING_DATASOURCE_PASSWORD + valueFrom: + secretKeyRef: + name: spot-secrets + key: SPRING_DATASOURCE_PASSWORD + + resources: + requests: + cpu: "500m" + memory: "512Mi" + limits: + cpu: "1000m" + memory: "1Gi" \ No newline at end of file diff --git a/infra/k8s/base/kafka/kafka-ui.yaml b/infra/k8s/base/kafka/kafka-ui.yaml index c5a4d6ee..8b312cd0 100644 --- a/infra/k8s/base/kafka/kafka-ui.yaml +++ b/infra/k8s/base/kafka/kafka-ui.yaml @@ -18,17 +18,23 @@ spec: image: provectuslabs/kafka-ui:latest ports: - containerPort: 8080 + resources: + requests: + memory: "384Mi" + cpu: "100m" env: - - name: SERVER_SERVLET_CONTEXT_PATH - value: '/kafka-ui' - name: KAFKA_CLUSTERS_0_NAME - value: local-spot-cluster + value: spot-cluster - name: KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS - value: 'kafka:9092' + value: 'spot-cluster-kafka-bootstrap:9092' + - name: KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME - value: connect + value: "spot-connect" - name: KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS - value: 'http://kafka-connect-svc:8083' + value: "http://spot-connect-connect-api.spot.svc:8083" + - name: KAFKA_CLUSTERS_0_KAFKACONNECT_0_TYPE + value: "kafka-connect" + - name: LOGGING_LEVEL_ROOT value: WARN - name: LOGGING_LEVEL_COM_PROVECTUS diff --git a/infra/k8s/base/kafka/kafka.yaml b/infra/k8s/base/kafka/kafka.yaml index 800b81ae..6a082c0f 100644 --- a/infra/k8s/base/kafka/kafka.yaml +++ b/infra/k8s/base/kafka/kafka.yaml @@ -1,98 +1,30 @@ -apiVersion: v1 -kind: PersistentVolumeClaim +apiVersion: kafka.strimzi.io/v1 +kind: Kafka metadata: - name: kafka-pvc + name: spot-cluster namespace: spot spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: kafka - namespace: spot -spec: - replicas: 1 - selector: - matchLabels: - app: kafka - template: - metadata: - labels: - app: kafka - spec: - enableServiceLinks: false - containers: - - name: kafka - image: apache/kafka:4.0.0 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 9092 - - containerPort: 29092 - env: - - name: KAFKA_NODE_ID - value: "1" - - name: KAFKA_PROCESS_ROLES - value: "broker,controller" - - name: KAFKA_CONTROLLER_QUORUM_VOTERS - value: "1@localhost:19093" - - name: KAFKA_LISTENERS - value: "INTERNAL://0.0.0.0:29092,EXTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:19093" - - name: KAFKA_ADVERTISED_LISTENERS - value: "INTERNAL://kafka:29092,EXTERNAL://kafka:9092" - - name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP - value: "INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT" - - name: KAFKA_INTER_BROKER_LISTENER_NAME - value: "INTERNAL" - - name: KAFKA_CONTROLLER_LISTENER_NAMES - value: "CONTROLLER" - - name: CLUSTER_ID - value: "J9Xz7kQPRYyK8VkqH3mW5A" - - name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR - value: "1" - - name: KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR - value: "1" - - name: KAFKA_TRANSACTION_STATE_LOG_MIN_ISR - value: "1" - - name: KAFKA_AUTO_CREATE_TOPICS_ENABLE - value: "true" - - name: KAFKA_NUM_PARTITIONS - value: "3" - - name: KAFKA_HEAP_OPTS - value: "-Xmx512M -Xms512M" - - name: KAFKA_LOG4J_ROOT_LOGLEVEL - value: "WARN" - volumeMounts: - - name: kafka-storage - mountPath: /var/lib/kafka/data - resources: - requests: - memory: "512Mi" - cpu: "250m" - limits: - memory: "1Gi" - cpu: "500m" - volumes: - - name: kafka-storage - persistentVolumeClaim: - claimName: kafka-pvc ---- -apiVersion: v1 -kind: Service -metadata: - name: kafka - namespace: spot -spec: - selector: - app: kafka - ports: - - name: external - port: 9092 - targetPort: 9092 - - name: internal - port: 29092 - targetPort: 29092 \ No newline at end of file + kafka: + version: 4.0.0 + metadataVersion: "3.7-IV4" + listeners: + - name: plain + port: 9092 + type: internal + tls: false + - name: controller + port: 9093 + type: internal + tls: false + config: + process.roles: "broker,controller" + offsets.topic.replication.factor: 3 + transaction.state.log.replication.factor: 3 + transaction.state.log.min.isr: 2 + default.replication.factor: 3 + min.insync.replicas: 2 + auto.create.topics.enable: "true" + num.partitions: 3 + entityOperator: + topicOperator: {} + userOperator: {} diff --git a/infra/k8s/base/monitoring/grafana/grafana.yaml b/infra/k8s/base/monitoring/grafana/grafana.yaml index bdc441ba..93865f75 100644 --- a/infra/k8s/base/monitoring/grafana/grafana.yaml +++ b/infra/k8s/base/monitoring/grafana/grafana.yaml @@ -9,6 +9,7 @@ spec: resources: requests: storage: 1Gi + storageClassName: gp3 --- apiVersion: apps/v1 kind: Deployment diff --git a/infra/k8s/base/monitoring/loki/loki.yaml b/infra/k8s/base/monitoring/loki/loki.yaml index 3d3f1326..d8b0d750 100644 --- a/infra/k8s/base/monitoring/loki/loki.yaml +++ b/infra/k8s/base/monitoring/loki/loki.yaml @@ -9,6 +9,7 @@ spec: resources: requests: storage: 2Gi + storageClassName: gp3 --- apiVersion: apps/v1 kind: Deployment diff --git a/infra/k8s/base/postgres.yaml b/infra/k8s/base/postgres.yaml index 58885ff8..4e8229db 100644 --- a/infra/k8s/base/postgres.yaml +++ b/infra/k8s/base/postgres.yaml @@ -9,6 +9,7 @@ spec: resources: requests: storage: 1Gi + storageClassName: gp3 --- apiVersion: apps/v1 kind: Deployment @@ -41,20 +42,23 @@ spec: secretKeyRef: name: spot-secrets key: SPRING_DATASOURCE_PASSWORD + - name: PGDATA + value: /var/lib/postgresql/data/pgdata volumeMounts: - name: postgres-storage mountPath: /var/lib/postgresql/data resources: requests: - memory: "256Mi" - cpu: "250m" + memory: 256Mi + cpu: 100m limits: - memory: "512Mi" - cpu: "500m" + memory: 512Mi + cpu: 500m volumes: - name: postgres-storage persistentVolumeClaim: claimName: postgres-pvc + --- apiVersion: v1 kind: Service diff --git a/infra/k8s/kustomization.yaml b/infra/k8s/kustomization.yaml index 20282824..1a848507 100644 --- a/infra/k8s/kustomization.yaml +++ b/infra/k8s/kustomization.yaml @@ -6,14 +6,14 @@ kind: Kustomization resources: # Base - base/namespace.yaml - - base/configmap.yaml +# - base/configmap.yaml - base/postgres.yaml - base/redis.yaml + - base/storageclass-gp3.yaml # Kafka - base/kafka/kafka.yaml - base/kafka/kafka-connect.yaml - - base/kafka/kafka-connect-init.yaml - base/kafka/kafka-ui.yaml # Monitoring & Logging @@ -47,22 +47,22 @@ configMapGenerator: - name: spot-app-config namespace: spot files: - - ../../config/common.yml - - ../../config/kafka-topics.yml - - ../../config/spot-gateway.yml - - ../../config/spot-user.yml - - ../../config/spot-store.yml - - ../../config/spot-order.yml - - ../../config/spot-payment.yml + - config/spot-gateway.yml + - config/common.yml + - config/kafka-topics.yml + - config/spot-user.yml + - config/spot-store.yml + - config/spot-order.yml + - config/spot-payment.yml options: disableNameSuffixHash: true - name: kafka-connect-init-config namespace: spot files: - - ../../connectors/order-outbox.json - - ../../connectors/payment-outbox.json - - ../../connectors/register-connectors.sh + - connectors/order-outbox.json + - connectors/payment-outbox.json + - connectors/register-connectors.sh options: disableNameSuffixHash: true @@ -95,7 +95,7 @@ secretGenerator: - name: spot-secrets namespace: spot envs: - - ../../.env + - config/.env options: disableNameSuffixHash: true diff --git a/infra/terraform/environments/dev/.terraform.lock.hcl b/infra/terraform/environments/dev/.terraform.lock.hcl index cdc1668d..0cb89f90 100644 --- a/infra/terraform/environments/dev/.terraform.lock.hcl +++ b/infra/terraform/environments/dev/.terraform.lock.hcl @@ -23,3 +23,42 @@ provider "registry.terraform.io/hashicorp/aws" { "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", ] } + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.0.1" + hashes = [ + "h1:P0c8knzZnouTNFIRij8IS7+pqd0OKaFDYX0j4GRsiqo=", + "zh:02d55b0b2238fd17ffa12d5464593864e80f402b90b31f6e1bd02249b9727281", + "zh:20b93a51bfeed82682b3c12f09bac3031f5bdb4977c47c97a042e4df4fb2f9ba", + "zh:6e14486ecfaee38c09ccf33d4fdaf791409f90795c1b66e026c226fad8bc03c7", + "zh:8d0656ff422df94575668e32c310980193fccb1c28117e5c78dd2d4050a760a6", + "zh:9795119b30ec0c1baa99a79abace56ac850b6e6fbce60e7f6067792f6eb4b5f4", + "zh:b388c87acc40f6bd9620f4e23f01f3c7b41d9b88a68d5255dec0a72f0bdec249", + "zh:b59abd0a980649c2f97f172392f080eaeb18e486b603f83bf95f5d93aeccc090", + "zh:ba6e3060fddf4a022087d8f09e38aa0001c705f21170c2ded3d1c26c12f70d97", + "zh:c12626d044b1d5501cf95ca78cbe507c13ad1dd9f12d4736df66eb8e5f336eb8", + "zh:c55203240d50f4cdeb3df1e1760630d677679f5b1a6ffd9eba23662a4ad05119", + "zh:ea206a5a32d6e0d6e32f1849ad703da9a28355d9c516282a8458b5cf1502b2a1", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.2.1" + constraints = ">= 4.0.0" + hashes = [ + "h1:akFNuHwvrtnYMBofieoeXhPJDhYZzJVu/Q/BgZK2fgg=", + "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", + "zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86", + "zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad", + "zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191", + "zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b", + "zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5", + "zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635", + "zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933", + "zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e", + "zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed", + "zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf index 33eb2a54..40475023 100644 --- a/infra/terraform/environments/dev/main.tf +++ b/infra/terraform/environments/dev/main.tf @@ -49,124 +49,30 @@ module "ecr" { service_names = toset(keys(var.services)) } -# ============================================================================= -# ALB (Gateway Pass-through) -# ============================================================================= -module "alb" { - source = "../../modules/alb" - - name_prefix = local.name_prefix - common_tags = local.common_tags - vpc_id = module.network.vpc_id - vpc_cidr = module.network.vpc_cidr - subnet_ids = module.network.private_subnet_ids - - # Gateway만 ALB에 연결 - 모든 트래픽이 Spring Gateway로 전달됨 - services = { - "gateway" = { - container_port = var.services["gateway"].container_port - health_check_path = var.services["gateway"].health_check_path - path_patterns = ["/*"] - priority = 1 - } - } -} - -# ============================================================================= -# ECS (Multiple Services with Service Connect) -# ============================================================================= -module "ecs" { - source = "../../modules/ecs" - - project = var.project - environment = var.environment - name_prefix = local.name_prefix - common_tags = local.common_tags - region = var.region - vpc_id = module.network.vpc_id - subnet_ids = [module.network.public_subnet_a_id] # NAT 문제로 public 사용 - ecr_repository_urls = module.ecr.repository_urls - alb_security_group_id = module.alb.security_group_id - target_group_arns = module.alb.target_group_arns - alb_listener_arn = module.alb.listener_arn - assign_public_ip = true # NAT 문제로 public IP 사용 - - services = var.services - enable_service_connect = var.enable_service_connect - standby_mode = var.standby_mode - - # Database 연결 정보 - db_endpoint = module.database.endpoint - db_name = var.db_name - db_username = var.db_username - - # Redis 연결 정보 - redis_endpoint = module.elasticache.redis_endpoint - - # Kafka 연결 정보 - kafka_bootstrap_servers = module.kafka.bootstrap_servers - - # Parameter Store ARNs (민감 정보 주입) - parameter_arns = { - db_password = module.parameters.db_password_arn - jwt_secret = module.parameters.jwt_secret_arn - mail_password = module.parameters.mail_password_arn - toss_secret_key = module.parameters.toss_secret_key_arn - } - - # JWT 설정 (비민감 정보) - jwt_expire_ms = var.jwt_expire_ms - refresh_token_expire_days = var.refresh_token_expire_days - - # Mail 설정 (비민감 정보) - mail_username = var.mail_username - - # Toss 결제 설정 (비민감 정보) - toss_customer_key = var.toss_customer_key - - # 서비스 설정 - service_active_regions = var.service_active_regions - - depends_on = [module.parameters] -} - -# ============================================================================= -# API Gateway -# ============================================================================= -module "api_gateway" { - source = "../../modules/api-gateway" - - name_prefix = local.name_prefix - common_tags = local.common_tags - subnet_ids = module.network.private_subnet_ids - ecs_security_group_id = module.ecs.security_group_id - alb_listener_arn = module.alb.listener_arn -} - # ============================================================================= # DNS (Route 53 + ACM) # ============================================================================= -module "dns" { - source = "../../modules/dns" - - name_prefix = local.name_prefix - common_tags = local.common_tags - domain_name = var.domain_name - create_api_domain = var.create_api_domain - api_gateway_id = module.api_gateway.api_id -} +# module "dns" { +# source = "../../modules/dns" +# +# name_prefix = local.name_prefix +# common_tags = local.common_tags +# domain_name = var.domain_name +# create_api_domain = var.create_api_domain +# api_gateway_id = module.api_gateway.api_id +# } # ============================================================================= # WAF (Web Application Firewall) # ============================================================================= -module "waf" { - source = "../../modules/waf" - - name_prefix = local.name_prefix - common_tags = local.common_tags - api_gateway_stage_arn = module.api_gateway.stage_arn - rate_limit = var.waf_rate_limit -} +# module "waf" { +# source = "../../modules/waf" +# +# name_prefix = local.name_prefix +# common_tags = local.common_tags +# api_gateway_stage_arn = module.api_gateway.stage_arn +# rate_limit = var.waf_rate_limit +# } # ============================================================================= # S3 (정적 파일 / 로그 저장) @@ -192,7 +98,7 @@ module "elasticache" { common_tags = local.common_tags vpc_id = module.network.vpc_id subnet_ids = module.network.private_subnet_ids - allowed_security_group_ids = [module.ecs.security_group_id] + allowed_security_group_ids = [module.eks.node_security_group_id] node_type = var.redis_node_type num_cache_clusters = var.redis_num_cache_clusters engine_version = var.redis_engine_version @@ -209,7 +115,7 @@ module "kafka" { vpc_id = module.network.vpc_id vpc_cidr = module.network.vpc_cidr subnet_id = module.network.public_subnet_a_id # NAT 문제로 public 사용 - allowed_security_group_ids = [module.ecs.security_group_id] + allowed_security_group_ids = [module.eks.node_security_group_id] assign_public_ip = true instance_type = var.kafka_instance_type @@ -249,20 +155,14 @@ module "monitoring" { common_tags = local.common_tags alert_email = var.alert_email - # ECS 모니터링 (대표 서비스) - ecs_cluster_name = module.ecs.cluster_name - ecs_service_name = module.ecs.service_names["user"] - # RDS 모니터링 rds_instance_id = module.database.instance_id - # ALB 모니터링 - alb_arn_suffix = module.alb.arn_suffix - - # Redis 모니터링 (선택) + # Redis 모니터링 redis_cluster_id = "${local.name_prefix}-redis-001" } + # ============================================================================= # eks # ============================================================================= @@ -281,9 +181,13 @@ module "eks" { node_subnet_ids = module.network.private_subnet_ids endpoint_private_access = true - endpoint_public_access = false + endpoint_public_access = true + + enable_node_group = true - enable_node_group = false + node_desired_size = 1 + node_min_size = 1 + node_max_size = 1 enable_node_ssm = true } @@ -305,16 +209,23 @@ module "irsa" { aws_load_balancer_controller = { namespace = "kube-system" service_account = "aws-load-balancer-controller" - policy_json = var.alb_controller_policy_json } + ebs_csi_driver = { + namespace = "kube-system" + service_account = "ebs-csi-controller-sa" + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" + } + } } module "eks_addons" { source = "../../modules/eks-addons" - common_tags = local.common_tags - cluster_name = module.eks.cluster_name + common_tags = local.common_tags + cluster_name = module.eks.cluster_name + ebs_csi_irsa_role_arn = module.irsa.service_account_role_arns["ebs_csi_driver"] + enable_vpc_cni = true enable_coredns = true diff --git a/infra/terraform/environments/dev/outputs.tf b/infra/terraform/environments/dev/outputs.tf index a74ef49e..6b35b241 100644 --- a/infra/terraform/environments/dev/outputs.tf +++ b/infra/terraform/environments/dev/outputs.tf @@ -27,60 +27,13 @@ output "ecr_repository_urls" { value = module.ecr.repository_urls } -# ============================================================================= -# ECS -# ============================================================================= -output "ecs_cluster_name" { - description = "ECS 클러스터 이름" - value = module.ecs.cluster_name -} - -output "ecs_service_names" { - description = "ECS 서비스 이름 맵" - value = module.ecs.service_names -} - -# ============================================================================= -# ALB -# ============================================================================= -output "alb_dns" { - description = "ALB DNS" - value = module.alb.alb_dns_name -} - -# ============================================================================= -# API Gateway -# ============================================================================= -output "api_url" { - description = "API Gateway URL" - value = module.api_gateway.api_endpoint -} - -# ============================================================================= -# DNS -# ============================================================================= -output "name_servers" { - description = "Route 53 네임서버 (도메인 등록 기관에 설정 필요)" - value = module.dns.name_servers -} - -output "api_custom_domain" { - description = "API 커스텀 도메인" - value = module.dns.api_domain -} - -output "certificate_arn" { - description = "SSL 인증서 ARN" - value = module.dns.certificate_arn -} - # ============================================================================= # WAF # ============================================================================= -output "waf_web_acl_arn" { - description = "WAF Web ACL ARN" - value = module.waf.web_acl_arn -} +# output "waf_web_acl_arn" { +# description = "WAF Web ACL ARN" +# value = module.waf.web_acl_arn +# } # ============================================================================= # S3 diff --git a/infra/terraform/environments/dev/provider.tf b/infra/terraform/environments/dev/provider.tf index 18c133d5..e63f8559 100644 --- a/infra/terraform/environments/dev/provider.tf +++ b/infra/terraform/environments/dev/provider.tf @@ -45,4 +45,16 @@ provider "aws" { # resource "postgresql_schema" "users" { # name = "users" # var.services["user"].environment_vars["DB_SCHEMA"] 값과 일치해야 함 # owner = var.db_username -# } \ No newline at end of file +# } + +# kubernetes - IRSA 모듈에 SA 추가 +data "aws_eks_cluster_auth" "this" { + name = module.eks.cluster_name +} + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_ca) + token = data.aws_eks_cluster_auth.this.token +} + diff --git a/infra/terraform/environments/dev/variables.tf b/infra/terraform/environments/dev/variables.tf index e5e752a4..892c8112 100644 --- a/infra/terraform/environments/dev/variables.tf +++ b/infra/terraform/environments/dev/variables.tf @@ -33,6 +33,7 @@ variable "public_subnet_cidrs" { type = map(string) default = { "a" = "10.0.1.0/24" + "c" = "10.0.2.0/24" } } @@ -122,8 +123,8 @@ variable "services" { desired_count = 1 health_check_path = "/actuator/health" # 모든 트래픽을 Gateway로 몰아주기 위해 /* 패턴 사용 - path_patterns = ["/*"] - priority = 1 # 가장 높은 우선순위 + path_patterns = ["/*"] + priority = 1 # 가장 높은 우선순위 environment_vars = { SERVICE_NAME = "spot-gateway" } @@ -265,6 +266,7 @@ variable "jwt_secret" { description = "JWT 시크릿 키" type = string sensitive = true + default = "jmXDQDnLqV+lOaPKMR06v4+RQ7aj2cj8LR+zgPOlz/GS989tptPtAmIpyaZHrsLOPqKoVtPus28YeXZTL8O8nw==" } variable "jwt_expire_ms" { @@ -355,8 +357,8 @@ variable "kafka_log_retention_hours" { # eks 설정 # ============================================================================= variable "cluster_name" { - cluster_name = "spot-cluster-test" - type = string + type = string + default = "spot-cluster-test" } variable "cluster_version" { @@ -364,6 +366,3 @@ variable "cluster_version" { default = "1.29" } -variable "alb_controller_policy_json" { - type = string -} diff --git a/infra/terraform/modules/alb/main.tf b/infra/terraform/modules/alb/main.tf deleted file mode 100644 index 8b6c2f68..00000000 --- a/infra/terraform/modules/alb/main.tf +++ /dev/null @@ -1,203 +0,0 @@ -# ============================================================================= -# ALB Security Group -# ============================================================================= -resource "aws_security_group" "alb_sg" { - name = "${var.name_prefix}-alb-sg" - vpc_id = var.vpc_id - - ingress { - from_port = 80 - to_port = 80 - protocol = "tcp" - cidr_blocks = [var.vpc_cidr] - } - - # HTTPS 인바운드 (Production) - dynamic "ingress" { - for_each = var.enable_https ? [1] : [] - content { - from_port = 443 - to_port = 443 - protocol = "tcp" - cidr_blocks = [var.vpc_cidr] - } - } - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-alb-sg" }) -} - - # ============================================================================= - # Application Load Balancer (Internal) - # ============================================================================= - resource "aws_lb" "main" { - name = "${var.name_prefix}-alb" - internal = true - load_balancer_type = "application" - security_groups = [aws_security_group.alb_sg.id] - subnets = var.subnet_ids - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-alb" }) - } - - # ============================================================================= - # Target Groups (Multiple Services) - # ============================================================================= - resource "aws_lb_target_group" "services" { - for_each = var.services - - name = "${var.name_prefix}-${each.key}-tg" - port = each.value.container_port - protocol = "HTTP" - vpc_id = var.vpc_id - target_type = "ip" - - health_check { - enabled = true - healthy_threshold = 2 - unhealthy_threshold = 3 - timeout = 10 - interval = 30 - path = each.value.health_check_path - matcher = "200" - } - - tags = merge(var.common_tags, { - Name = "${var.name_prefix}-${each.key}-tg" - Service = each.key - }) - } - -# ============================================================================= -# Green Target Groups (for Blue/Green Deployment) -# ============================================================================= -resource "aws_lb_target_group" "services_green" { - for_each = var.enable_blue_green ? var.services : {} - - name = "${var.name_prefix}-${each.key}-tg-g" - port = each.value.container_port - protocol = "HTTP" - vpc_id = var.vpc_id - target_type = "ip" - - health_check { - enabled = true - healthy_threshold = 2 - unhealthy_threshold = 3 - timeout = 10 - interval = 30 - path = each.value.health_check_path - matcher = "200" - } - - tags = merge(var.common_tags, { - Name = "${var.name_prefix}-${each.key}-tg-green" - Service = each.key - Color = "green" - }) -} - -# ============================================================================= -# ALB Listener - HTTP (Default action returns 404 or redirects to HTTPS) -# ============================================================================= -resource "aws_lb_listener" "main" { - load_balancer_arn = aws_lb.main.arn - port = 80 - protocol = "HTTP" - - default_action { - type = var.enable_https && var.certificate_arn != null ? "redirect" : "fixed-response" - - dynamic "redirect" { - for_each = var.enable_https && var.certificate_arn != null ? [1] : [] - content { - port = "443" - protocol = "HTTPS" - status_code = "HTTP_301" - } - } - - dynamic "fixed_response" { - for_each = var.enable_https && var.certificate_arn != null ? [] : [1] - content { - content_type = "application/json" - message_body = jsonencode({ error = "Not Found", message = "No matching route" }) - status_code = "404" - } - } - } -} - -# ============================================================================= -# ALB Listener - HTTPS (Production) -# ============================================================================= -resource "aws_lb_listener" "https" { - count = var.enable_https && var.certificate_arn != null ? 1 : 0 - - load_balancer_arn = aws_lb.main.arn - port = 443 - protocol = "HTTPS" - ssl_policy = var.ssl_policy - certificate_arn = var.certificate_arn - - default_action { - type = "fixed-response" - fixed_response { - content_type = "application/json" - message_body = jsonencode({ error = "Not Found", message = "No matching route" }) - status_code = "404" - } - } -} - -# ============================================================================= -# ALB Listener Rules - HTTP (Path-based Routing) -# ============================================================================= -resource "aws_lb_listener_rule" "services" { - for_each = var.enable_https && var.certificate_arn != null ? {} : var.services - - listener_arn = aws_lb_listener.main.arn - priority = each.value.priority - - action { - type = "forward" - target_group_arn = aws_lb_target_group.services[each.key].arn - } - - condition { - path_pattern { - values = each.value.path_patterns - } - } - - tags = merge(var.common_tags, { Service = each.key }) -} - -# ============================================================================= -# ALB Listener Rules - HTTPS (Path-based Routing for Production) -# ============================================================================= -resource "aws_lb_listener_rule" "services_https" { - for_each = var.enable_https && var.certificate_arn != null ? var.services : {} - - listener_arn = aws_lb_listener.https[0].arn - priority = each.value.priority - - action { - type = "forward" - target_group_arn = aws_lb_target_group.services[each.key].arn - } - - condition { - path_pattern { - values = each.value.path_patterns - } - } - - tags = merge(var.common_tags, { Service = each.key }) -} diff --git a/infra/terraform/modules/alb/outputs.tf b/infra/terraform/modules/alb/outputs.tf deleted file mode 100644 index 8c50db0e..00000000 --- a/infra/terraform/modules/alb/outputs.tf +++ /dev/null @@ -1,57 +0,0 @@ -output "alb_arn" { - description = "ALB ARN" - value = aws_lb.main.arn -} - -output "alb_dns_name" { - description = "ALB DNS 이름" - value = aws_lb.main.dns_name -} - -output "target_group_arns" { - description = "Target Group ARN 맵" - value = { for k, v in aws_lb_target_group.services : k => v.arn } -} - -output "target_group_names" { - description = "Target Group 이름 맵" - value = { for k, v in aws_lb_target_group.services : k => v.name } -} - -output "listener_arn" { - description = "Listener ARN (HTTP)" - value = aws_lb_listener.main.arn -} - -output "https_listener_arn" { - description = "HTTPS Listener ARN" - value = var.enable_https && var.certificate_arn != null ? aws_lb_listener.https[0].arn : null -} - -output "security_group_id" { - description = "ALB 보안그룹 ID" - value = aws_security_group.alb_sg.id -} - -output "arn_suffix" { - description = "ALB ARN suffix (CloudWatch용)" - value = aws_lb.main.arn_suffix -} - -output "target_group_arn_suffixes" { - description = "Target Group ARN suffix 맵 (CloudWatch용)" - value = { for k, v in aws_lb_target_group.services : k => v.arn_suffix } -} - -# ============================================================================= -# Blue/Green Outputs -# ============================================================================= -output "green_target_group_arns" { - description = "Green Target Group ARN 맵 (Blue/Green용)" - value = var.enable_blue_green ? { for k, v in aws_lb_target_group.services_green : k => v.arn } : {} -} - -output "green_target_group_names" { - description = "Green Target Group 이름 맵" - value = var.enable_blue_green ? { for k, v in aws_lb_target_group.services_green : k => v.name } : {} -} diff --git a/infra/terraform/modules/alb/variables.tf b/infra/terraform/modules/alb/variables.tf deleted file mode 100644 index f228910a..00000000 --- a/infra/terraform/modules/alb/variables.tf +++ /dev/null @@ -1,65 +0,0 @@ -variable "name_prefix" { - description = "리소스 네이밍 프리픽스" - type = string -} - -variable "common_tags" { - description = "공통 태그" - type = map(string) - default = {} -} - -variable "vpc_id" { - description = "VPC ID" - type = string -} - -variable "vpc_cidr" { - description = "VPC CIDR" - type = string -} - -variable "subnet_ids" { - description = "ALB 서브넷 ID 목록" - type = list(string) -} - -variable "services" { - description = "서비스 구성 맵" - type = map(object({ - container_port = number - health_check_path = string - path_patterns = list(string) - priority = number - })) -} - -# ============================================================================= -# HTTPS Settings -# ============================================================================= -variable "enable_https" { - description = "HTTPS 활성화" - type = bool - default = false -} - -variable "certificate_arn" { - description = "ACM 인증서 ARN" - type = string - default = null -} - -variable "ssl_policy" { - description = "SSL 정책" - type = string - default = "ELBSecurityPolicy-TLS13-1-2-2021-06" -} - -# ============================================================================= -# Blue/Green Deployment Settings -# ============================================================================= -variable "enable_blue_green" { - description = "Blue/Green 배포용 추가 Target Group 생성" - type = bool - default = false -} diff --git a/infra/terraform/modules/dns/main.tf b/infra/terraform/modules/dns/main.tf index 3b12e790..21408942 100644 --- a/infra/terraform/modules/dns/main.tf +++ b/infra/terraform/modules/dns/main.tf @@ -54,6 +54,10 @@ resource "aws_acm_certificate_validation" "main" { # ============================================================================= # EKS(ALB) Record # ============================================================================= +data "aws_lb" "ingress_alb" { + name = var.alb_name +} + resource "aws_route53_record" "alb" { count = var.create_alb_record ? 1 : 0 @@ -62,8 +66,9 @@ resource "aws_route53_record" "alb" { type = "A" alias { - name = var.alb_dns_name - zone_id = var.alb_zone_id + name = data.aws_lb.ingress_alb.dns_name + zone_id = data.aws_lb.ingress_alb.zone_id evaluate_target_health = false } } + diff --git a/infra/terraform/modules/dns/variables.tf b/infra/terraform/modules/dns/variables.tf index e452ae32..d9948782 100644 --- a/infra/terraform/modules/dns/variables.tf +++ b/infra/terraform/modules/dns/variables.tf @@ -38,3 +38,13 @@ variable "alb_zone_id" { type = string default = "" } + +variable "associate_to_alb" { + type = bool + default = true +} + +variable "alb_name" { + description = "Ingress가 생성한 ALB 이름 (Ingress annotation load-balancer-name과 동일)" + type = string +} diff --git a/infra/terraform/modules/eks-addons/variables.tf b/infra/terraform/modules/eks-addons/variables.tf index 8d9004d4..5449817d 100644 --- a/infra/terraform/modules/eks-addons/variables.tf +++ b/infra/terraform/modules/eks-addons/variables.tf @@ -1,14 +1,47 @@ -variable "common_tags" { type = map(string) default = {} } -variable "cluster_name" { type = string } +variable "common_tags" { + type = map(string) + default = {} +} +variable "cluster_name" { + type = string +} -variable "enable_vpc_cni" { type = bool default = true } -variable "enable_coredns" { type = bool default = true } -variable "enable_kube_proxy" { type = bool default = true } -variable "enable_ebs_csi" { type = bool default = true } +variable "enable_vpc_cni" { + type = bool + default = true +} -variable "vpc_cni_version" { type = string default = "" } -variable "coredns_version" { type = string default = "" } -variable "kube_proxy_version" { type = string default = "" } -variable "ebs_csi_version" { type = string default = "" } +variable "enable_coredns" { + type = bool + default = true +} +variable "enable_kube_proxy" { + type = bool + default = true +} +variable "enable_ebs_csi" { + type = bool + default = true +} -variable "ebs_csi_irsa_role_arn" { type = string default = "" } +variable "vpc_cni_version" { + type = string + default = "" +} +variable "coredns_version" { + type = string + default = "" +} +variable "kube_proxy_version" { + type = string + default = "" +} +variable "ebs_csi_version" { + type = string + default = "" +} + +variable "ebs_csi_irsa_role_arn" { + type = string + default = "" +} diff --git a/infra/terraform/modules/eks/main.tf b/infra/terraform/modules/eks/main.tf index 8cbe9969..17143160 100644 --- a/infra/terraform/modules/eks/main.tf +++ b/infra/terraform/modules/eks/main.tf @@ -135,7 +135,11 @@ resource "aws_eks_node_group" "default" { cluster_name = aws_eks_cluster.this.name node_group_name = "${var.name_prefix}-node-group" node_role_arn = aws_iam_role.node.arn - subnet_ids = var.node_subnet_ids + subnet_ids = var.node_subnet_ids + + # ec2spot + capacity_type = "SPOT" + instance_types = var.node_instance_types diff --git a/infra/terraform/modules/eks/output.tf b/infra/terraform/modules/eks/output.tf index a3212482..57e3bf2c 100644 --- a/infra/terraform/modules/eks/output.tf +++ b/infra/terraform/modules/eks/output.tf @@ -10,10 +10,13 @@ output "cluster_endpoint" { value = aws_eks_cluster.this.endpoint } + output "cluster_ca" { - value = aws_eks_cluster.this.certificate_authority[0].data + description = "EKS cluster CA certificate (base64 encoded)" + value = aws_eks_cluster.this.certificate_authority[0].data } + output "oidc_issuer_url" { value = aws_eks_cluster.this.identity[0].oidc[0].issuer } diff --git a/infra/terraform/modules/eks/variables.tf b/infra/terraform/modules/eks/variables.tf index bab1d3c6..90ab639f 100644 --- a/infra/terraform/modules/eks/variables.tf +++ b/infra/terraform/modules/eks/variables.tf @@ -1,34 +1,92 @@ -variable "name_prefix" { type = string } -variable "common_tags" { type = map(string) default = {} } +variable "name_prefix" { + type = string +} + +variable "common_tags" { + type = map(string) + default = {} +} -variable "cluster_name" { type = string } -variable "cluster_version" { type = string default = "1.29" } +variable "cluster_name" { + type = string +} -variable "vpc_id" { type = string } +variable "cluster_version" { + type = string + default = "1.29" +} -variable "subnet_ids" { type = list(string) } -variable "node_subnet_ids" { type = list(string) } +variable "vpc_id" { + type = string +} -variable "endpoint_private_access" { type = bool default = true } -variable "endpoint_public_access" { type = bool default = false } -variable "public_access_cidrs" { type = list(string) default = ["0.0.0.0/0"] } +variable "subnet_ids" { + type = list(string) +} + +variable "node_subnet_ids" { + type = list(string) +} + +variable "endpoint_private_access" { + type = bool + default = true +} + +variable "endpoint_public_access" { + type = bool + default = false +} + +variable "public_access_cidrs" { + type = list(string) + default = ["0.0.0.0/0"] +} variable "enabled_cluster_log_types" { type = list(string) default = ["api", "audit", "authenticator", "controllerManager", "scheduler"] } -variable "node_instance_types" { type = list(string) default = ["t3.medium"] } -variable "node_capacity_type" { type = string default = "ON_DEMAND" } -variable "node_ami_type" { type = string default = "AL2_x86_64" } +variable "node_instance_types" { + type = list(string) + default = ["t3.large"] +} + +variable "node_capacity_type" { + type = string + default = "ON_DEMAND" +} + +variable "node_ami_type" { + type = string + default = "AL2_x86_64" +} + +variable "node_disk_size" { + type = number + default = 50 +} -variable "node_disk_size" { type = number default = 50 } +variable "node_desired_size" { + type = number + default = 2 +} -variable "node_desired_size" { type = number default = 2 } -variable "node_min_size" { type = number default = 2 } -variable "node_max_size" { type = number default = 4 } +variable "node_min_size" { + type = number + default = 2 +} -variable "enable_node_ssm" { type = bool default = true } +variable "node_max_size" { + type = number + default = 4 +} + +variable "enable_node_ssm" { + type = bool + default = true +} variable "enable_node_group" { description = "Worker Node Group 생성 여부" diff --git a/infra/terraform/modules/elasticache/main.tf b/infra/terraform/modules/elasticache/main.tf index 82b9dbf2..2ddd25bf 100644 --- a/infra/terraform/modules/elasticache/main.tf +++ b/infra/terraform/modules/elasticache/main.tf @@ -2,41 +2,41 @@ # ElastiCache Redis - 캐시 및 세션 저장소 # ============================================================================= # -# # ----------------------------------------------------------------------------- -# # Subnet Group -# # ----------------------------------------------------------------------------- -# resource "aws_elasticache_subnet_group" "redis" { -# name = "${var.name_prefix}-redis-subnet" -# subnet_ids = var.subnet_ids -# -# tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-subnet" }) -# } -# +# ----------------------------------------------------------------------------- +# Subnet Group +# ----------------------------------------------------------------------------- +resource "aws_elasticache_subnet_group" "redis" { + name = "${var.name_prefix}-redis-subnet" + subnet_ids = var.subnet_ids + + tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-subnet" }) +} + # # ----------------------------------------------------------------------------- # # Security Group # # ----------------------------------------------------------------------------- -# resource "aws_security_group" "redis" { -# name = "${var.name_prefix}-redis-sg" -# description = "Security group for ElastiCache Redis" -# vpc_id = var.vpc_id -# -# ingress { -# description = "Redis from EKS nodes" -# from_port = 6379 -# to_port = 6379 -# protocol = "tcp" -# security_groups = var.allowed_security_group_ids -# } -# -# egress { -# from_port = 0 -# to_port = 0 -# protocol = "-1" -# cidr_blocks = ["0.0.0.0/0"] -# } -# -# tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-sg" }) -# } +resource "aws_security_group" "redis" { + name = "${var.name_prefix}-redis-sg" + description = "Security group for ElastiCache Redis" + vpc_id = var.vpc_id + + ingress { + description = "Redis from EKS nodes" + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = var.allowed_security_group_ids + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge(var.common_tags, { Name = "${var.name_prefix}-redis-sg" }) +} # ----------------------------------------------------------------------------- # Parameter Group (Redis 설정 커스터마이징) diff --git a/infra/terraform/modules/irsa/main.tf b/infra/terraform/modules/irsa/main.tf index 16ccaf35..f01c85a6 100644 --- a/infra/terraform/modules/irsa/main.tf +++ b/infra/terraform/modules/irsa/main.tf @@ -6,8 +6,14 @@ resource "aws_iam_openid_connect_provider" "this" { url = var.oidc_issuer_url client_id_list = ["sts.amazonaws.com"] thumbprint_list = [data.tls_certificate.oidc.certificates[0].sha1_fingerprint] + tags = var.common_tags +} - tags = var.common_tags +locals { + namespaces = toset([ + for k, v in var.service_accounts : v.namespace + if v.namespace != "kube-system" && v.namespace != "default" + ]) } resource "aws_iam_role" "service_account" { @@ -33,14 +39,71 @@ resource "aws_iam_role" "service_account" { tags = var.common_tags } -resource "aws_iam_role_policy" "inline" { +data "aws_iam_policy_document" "alb_controller" { + statement { + effect = "Allow" + actions = [ + "iam:CreateServiceLinkedRole", + "ec2:Describe*", + "elasticloadbalancing:*", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:DeleteTags", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "acm:DescribeCertificate", + "acm:ListCertificates", + "acm:GetCertificate", + "waf-regional:*", + "wafv2:*", + "shield:*", + "cognito-idp:DescribeUserPoolClient" + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "alb_controller" { + name = "${var.name_prefix}-AWSLoadBalancerControllerIAMPolicy" + description = "IAM policy for AWS Load Balancer Controller" + policy = data.aws_iam_policy_document.alb_controller.json + tags = var.common_tags +} + +resource "aws_iam_role_policy_attachment" "alb_controller" { + role = aws_iam_role.service_account["aws_load_balancer_controller"].name + policy_arn = aws_iam_policy.alb_controller.arn +} + +resource "aws_iam_role_policy_attachment" "managed" { for_each = { for k, v in var.service_accounts : k => v - if try(length(v.policy_json), 0) > 0 + if try(length(v.policy_arn), 0) > 0 } - name = "${var.name_prefix}-${each.key}-policy" - role = aws_iam_role.service_account[each.key].id + role = aws_iam_role.service_account[each.key].name + policy_arn = each.value.policy_arn +} + + +resource "kubernetes_namespace" "this" { + for_each = local.namespaces + + metadata { + name = each.value + } +} + +resource "kubernetes_service_account" "this" { + for_each = var.service_accounts + + metadata { + name = each.value.service_account + namespace = each.value.namespace + annotations = { + "eks.amazonaws.com/role-arn" = aws_iam_role.service_account[each.key].arn + } + } - policy = each.value.policy_json + depends_on = [kubernetes_namespace.this] } diff --git a/infra/terraform/modules/irsa/variables.tf b/infra/terraform/modules/irsa/variables.tf index ebe8c37a..065aaac7 100644 --- a/infra/terraform/modules/irsa/variables.tf +++ b/infra/terraform/modules/irsa/variables.tf @@ -1,13 +1,20 @@ -variable "name_prefix" { type = string } -variable "common_tags" { type = map(string) default = {} } +variable "name_prefix" { + type = string +} +variable "common_tags" { + type = map(string) + default = {} +} -variable "oidc_issuer_url" { type = string } +variable "oidc_issuer_url" { + type = string +} variable "service_accounts" { type = map(object({ namespace = string service_account = string - policy_json = string + policy_arn = optional(string) })) default = {} } diff --git a/infra/terraform/modules/kafka/main.tf b/infra/terraform/modules/kafka/main.tf index 61cb1534..78346d09 100644 --- a/infra/terraform/modules/kafka/main.tf +++ b/infra/terraform/modules/kafka/main.tf @@ -1,23 +1,27 @@ # ============================================================================= # Kafka EC2 Module (KRaft Mode - Single/Multi Broker) # ============================================================================= - locals { kafka_port = 9092 kraft_port = 9093 internal_port = 9094 - # 브로커 배치: AZ-a에 1개, AZ-c에 2개 - brokers = var.broker_count > 1 ? { + effective_subnet_ids = length(var.subnet_ids) > 0 ? var.subnet_ids : (var.subnet_id != null ? [var.subnet_id] : []) + single_subnet = length(local.effective_subnet_ids) <= 1 + + brokers = var.broker_count > 1 ? ( + local.single_subnet ? { + "1" = { subnet_index = 0, az_suffix = "a" } + "2" = { subnet_index = 0, az_suffix = "a" } + "3" = { subnet_index = 0, az_suffix = "a" } + } : { "1" = { subnet_index = 0, az_suffix = "a" } "2" = { subnet_index = 1, az_suffix = "c" } "3" = { subnet_index = 1, az_suffix = "c" } - } : { + } + ) : { "1" = { subnet_index = 0, az_suffix = "a" } } - - # 사용할 서브넷 결정 - effective_subnet_ids = length(var.subnet_ids) > 0 ? var.subnet_ids : (var.subnet_id != null ? [var.subnet_id] : []) } # ============================================================================= @@ -149,12 +153,12 @@ resource "aws_instance" "kafka" { volume_size = var.volume_size iops = 3000 throughput = 125 - delete_on_termination = var.broker_count == 1 # prod에서는 데이터 보존 + delete_on_termination = var.delete_on_termination encrypted = true } user_data = base64encode(templatefile( - var.broker_count > 1 ? "${path.module}/user-data-cluster.sh" : "${path.module}/user-data.sh", + var.broker_count > 1 ? "${path.module}/user-data-cluster.sh" : "${path.module}/user-data.sh", { kafka_version = var.kafka_version kafka_cluster_id = var.cluster_id @@ -180,13 +184,14 @@ resource "aws_instance" "kafka" { } } + # ============================================================================= # Route53 Private DNS (서비스 디스커버리용) # ============================================================================= resource "aws_route53_zone" "kafka" { count = var.create_private_dns ? 1 : 0 - name = "kafka.internal" + name = "${var.name_prefix}.kafka.internal" vpc { vpc_id = var.vpc_id @@ -195,7 +200,6 @@ resource "aws_route53_zone" "kafka" { tags = merge(var.common_tags, { Name = "${var.name_prefix}-kafka-zone" }) } -# 개별 브로커 DNS 레코드 resource "aws_route53_record" "kafka_brokers" { for_each = var.create_private_dns ? local.brokers : {} @@ -206,7 +210,6 @@ resource "aws_route53_record" "kafka_brokers" { records = [aws_instance.kafka[each.key].private_ip] } -# 클러스터 부트스트랩 레코드 (모든 브로커 IP) resource "aws_route53_record" "kafka_bootstrap" { count = var.create_private_dns ? 1 : 0 @@ -215,4 +218,4 @@ resource "aws_route53_record" "kafka_bootstrap" { type = "A" ttl = 60 records = [for k, v in aws_instance.kafka : v.private_ip] -} +} \ No newline at end of file diff --git a/infra/terraform/modules/kafka/variables.tf b/infra/terraform/modules/kafka/variables.tf index 0ae646aa..e333ef82 100644 --- a/infra/terraform/modules/kafka/variables.tf +++ b/infra/terraform/modules/kafka/variables.tf @@ -110,3 +110,9 @@ variable "create_private_dns" { type = bool default = false } + + +variable "delete_on_termination" { + type = bool + default = true +} \ No newline at end of file diff --git a/infra/terraform/modules/network/main.tf b/infra/terraform/modules/network/main.tf index 87044db9..b570f196 100644 --- a/infra/terraform/modules/network/main.tf +++ b/infra/terraform/modules/network/main.tf @@ -13,9 +13,10 @@ resource "aws_vpc" "main" { # Public Subnets # ============================================================================= resource "aws_subnet" "public_a" { - vpc_id = aws_vpc.main.id - cidr_block = var.public_subnet_cidrs["a"] - availability_zone = var.availability_zones["a"] + vpc_id = aws_vpc.main.id + cidr_block = var.public_subnet_cidrs["a"] + availability_zone = var.availability_zones["a"] + map_public_ip_on_launch = true tags = merge(var.common_tags, { Name = "${var.name_prefix}-public-a" @@ -27,10 +28,11 @@ resource "aws_subnet" "public_a" { } resource "aws_subnet" "public_c" { - count = var.use_nat_gateway && !var.single_nat_gateway ? 1 : (contains(keys(var.public_subnet_cidrs), "c") ? 1 : 0) - vpc_id = aws_vpc.main.id - cidr_block = lookup(var.public_subnet_cidrs, "c", "10.1.2.0/24") - availability_zone = var.availability_zones["c"] + count = var.use_nat_gateway && !var.single_nat_gateway ? 1 : (contains(keys(var.public_subnet_cidrs), "c") ? 1 : 0) + vpc_id = aws_vpc.main.id + cidr_block = lookup(var.public_subnet_cidrs, "c", "10.1.2.0/24") + availability_zone = var.availability_zones["c"] + map_public_ip_on_launch = true tags = merge(var.common_tags, { Name = "${var.name_prefix}-public-c" @@ -45,9 +47,10 @@ resource "aws_subnet" "public_c" { # Private Subnets # ============================================================================= resource "aws_subnet" "private_a" { - vpc_id = aws_vpc.main.id - cidr_block = var.private_subnet_cidrs["a"] - availability_zone = var.availability_zones["a"] + vpc_id = aws_vpc.main.id + cidr_block = var.private_subnet_cidrs["a"] + availability_zone = var.availability_zones["a"] + map_public_ip_on_launch = false tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-a" @@ -59,9 +62,10 @@ resource "aws_subnet" "private_a" { } resource "aws_subnet" "private_c" { - vpc_id = aws_vpc.main.id - cidr_block = var.private_subnet_cidrs["c"] - availability_zone = var.availability_zones["c"] + vpc_id = aws_vpc.main.id + cidr_block = var.private_subnet_cidrs["c"] + availability_zone = var.availability_zones["c"] + map_public_ip_on_launch = false tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-c" @@ -133,10 +137,13 @@ resource "aws_instance" "nat_instance" { user_data = <<-EOF #!/bin/bash - sudo sysctl -w net.ipv4.ip_forward=1 - sudo nft add table ip nat - sudo nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; } - sudo nft add rule ip nat postrouting oifname eth0 masquerade + set -euo pipefail + sysctl -w net.ipv4.ip_forward=1 + + # NAT (masquerade) via nftables (AL2023) + nft list table ip nat >/dev/null 2>&1 || nft add table ip nat + nft list chain ip nat postrouting >/dev/null 2>&1 || nft add chain ip nat postrouting '{ type nat hook postrouting priority 100 ; }' + nft add rule ip nat postrouting oifname "eth0" masquerade 2>/dev/null || true EOF tags = merge(var.common_tags, { Name = "${var.name_prefix}-nat-instance" }) @@ -167,19 +174,21 @@ resource "aws_nat_gateway" "main" { depends_on = [aws_internet_gateway.igw] } - # ============================================================================= # Route Tables # ============================================================================= +# ------------------------- +# Public Route Table +# ------------------------- resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id + tags = merge(var.common_tags, { Name = "${var.name_prefix}-public-rt" }) +} - route { - cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.igw.id - } - - tags = merge(var.common_tags, { Name = "${var.name_prefix}-public-rt" }) +resource "aws_route" "public_internet" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id } resource "aws_route_table_association" "public_a" { @@ -187,44 +196,75 @@ resource "aws_route_table_association" "public_a" { route_table_id = aws_route_table.public.id } +locals { + create_public_c = ( + var.use_nat_gateway && !var.single_nat_gateway + ? true + : contains(keys(var.public_subnet_cidrs), "c") + ) +} + resource "aws_route_table_association" "public_c" { - count = length(aws_subnet.public_c) > 0 ? 1 : 0 + count = local.create_public_c ? 1 : 0 subnet_id = aws_subnet.public_c[0].id route_table_id = aws_route_table.public.id } -# Private Route Table for AZ-a + + +# ------------------------- +# Private Route Table (AZ-a) +# ------------------------- resource "aws_route_table" "private_a" { vpc_id = aws_vpc.main.id + tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-rt-a" }) +} - route { - cidr_block = "0.0.0.0/0" - nat_gateway_id = var.use_nat_gateway ? aws_nat_gateway.main[0].id : null - network_interface_id = var.use_nat_gateway ? null : aws_instance.nat_instance[0].primary_network_interface_id - } +# Private default route via NAT Gateway (when enabled) +resource "aws_route" "private_a_nat_gw" { + count = var.use_nat_gateway ? 1 : 0 + route_table_id = aws_route_table.private_a.id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main[0].id +} + +# Private default route via NAT Instance (dev cost optimized) +resource "aws_route" "private_a_nat_instance" { + count = var.use_nat_gateway ? 0 : 1 + route_table_id = aws_route_table.private_a.id + destination_cidr_block = "0.0.0.0/0" + network_interface_id = aws_instance.nat_instance[0].primary_network_interface_id +} - tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-rt-a" }) +resource "aws_route_table_association" "private_a" { + subnet_id = aws_subnet.private_a.id + route_table_id = aws_route_table.private_a.id } -# Private Route Table for AZ-c (separate when using multi NAT Gateway) +# ------------------------- +# Private Route Table (AZ-c) +# - Multi NAT GW이면 별도 RT + NAT GW(1) +# - Single NAT GW이면 private_a RT를 공유 +# ------------------------- resource "aws_route_table" "private_c" { count = var.use_nat_gateway && !var.single_nat_gateway ? 1 : 0 vpc_id = aws_vpc.main.id - route { - cidr_block = "0.0.0.0/0" - nat_gateway_id = aws_nat_gateway.main[1].id - } - tags = merge(var.common_tags, { Name = "${var.name_prefix}-private-rt-c" }) } -resource "aws_route_table_association" "private_a" { - subnet_id = aws_subnet.private_a.id - route_table_id = aws_route_table.private_a.id +resource "aws_route" "private_c_nat_gw" { + count = var.use_nat_gateway && !var.single_nat_gateway ? 1 : 0 + route_table_id = aws_route_table.private_c[0].id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main[1].id } resource "aws_route_table_association" "private_c" { - subnet_id = aws_subnet.private_c.id - route_table_id = var.use_nat_gateway && !var.single_nat_gateway ? aws_route_table.private_c[0].id : aws_route_table.private_a.id + subnet_id = aws_subnet.private_c.id + route_table_id = ( + var.use_nat_gateway && !var.single_nat_gateway + ? aws_route_table.private_c[0].id + : aws_route_table.private_a.id + ) } diff --git a/infra/terraform/modules/network/outputs.tf b/infra/terraform/modules/network/outputs.tf index 008c656d..7770a2b9 100644 --- a/infra/terraform/modules/network/outputs.tf +++ b/infra/terraform/modules/network/outputs.tf @@ -14,13 +14,14 @@ output "public_subnet_a_id" { } output "public_subnet_c_id" { - description = "Public Subnet C ID" - value = length(aws_subnet.public_c) > 0 ? aws_subnet.public_c[0].id : null + value = try(aws_subnet.public_c[0].id, null) } output "public_subnet_ids" { - description = "Public Subnet IDs" - value = length(aws_subnet.public_c) > 0 ? [aws_subnet.public_a.id, aws_subnet.public_c[0].id] : [aws_subnet.public_a.id] + value = compact([ + aws_subnet.public_a.id, + try(aws_subnet.public_c[0].id, null) + ]) } output "private_subnet_a_id" { diff --git a/infra/terraform/modules/network/variables.tf b/infra/terraform/modules/network/variables.tf index 0b0011c8..556a0ae3 100644 --- a/infra/terraform/modules/network/variables.tf +++ b/infra/terraform/modules/network/variables.tf @@ -41,7 +41,7 @@ variable "nat_instance_type" { variable "use_nat_gateway" { description = "NAT Gateway 사용 여부 (false면 NAT Instance)" type = bool - default = false + default = true } variable "single_nat_gateway" { diff --git a/infra/terraform/modules/waf/main.tf b/infra/terraform/modules/waf/main.tf index 6edb0165..0e55a219 100644 --- a/infra/terraform/modules/waf/main.tf +++ b/infra/terraform/modules/waf/main.tf @@ -3,7 +3,7 @@ # ============================================================================= resource "aws_wafv2_web_acl" "main" { name = "${var.name_prefix}-waf" - description = "WAF for API Gateway" + description = "WAF for ALB (EKS Ingress)" scope = "REGIONAL" default_action { @@ -112,15 +112,53 @@ resource "aws_wafv2_web_acl" "main" { } +# ============================================================================= +# Find ALB created by Ingress +# ============================================================================= +data "aws_lb" "ingress_alb" { + count = var.associate_to_alb ? 1 : 0 + name = var.alb_name +} + +resource "aws_wafv2_web_acl_association" "ingress_alb" { + count = var.associate_to_alb ? 1 : 0 + resource_arn = data.aws_lb.ingress_alb[0].arn + web_acl_arn = aws_wafv2_web_acl.main.arn +} + + + + + # ============================================================================= -# CloudWatch Log Group for WAF +# CloudWatch Log Group for WAF logs +# NOTE: WAF requires the log group name to start with "aws-waf-logs-" # ============================================================================= resource "aws_cloudwatch_log_group" "waf" { name = "aws-waf-logs-${var.name_prefix}" retention_in_days = var.log_retention_days + tags = var.common_tags +} - tags = var.common_tags +# ============================================================================= +# Allow WAF to write logs to CloudWatch Logs +# ============================================================================= +resource "aws_cloudwatch_log_resource_policy" "waf" { + policy_name = "aws-waf-logs-${var.name_prefix}" + + policy_document = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AWSWAFLoggingPermissions" + Effect = "Allow" + Principal = { Service = "wafv2.amazonaws.com" } + Action = ["logs:CreateLogStream", "logs:PutLogEvents"] + Resource = "${aws_cloudwatch_log_group.waf.arn}:*" + } + ] + }) } # ============================================================================= @@ -129,4 +167,6 @@ resource "aws_cloudwatch_log_group" "waf" { resource "aws_wafv2_web_acl_logging_configuration" "main" { log_destination_configs = [aws_cloudwatch_log_group.waf.arn] resource_arn = aws_wafv2_web_acl.main.arn -} + + depends_on = [aws_cloudwatch_log_resource_policy.waf] +} \ No newline at end of file diff --git a/infra/terraform/modules/waf/variables.tf b/infra/terraform/modules/waf/variables.tf index 4de79d9c..bcea0f54 100644 --- a/infra/terraform/modules/waf/variables.tf +++ b/infra/terraform/modules/waf/variables.tf @@ -21,3 +21,8 @@ variable "log_retention_days" { type = number default = 30 } + +variable "alb_name" { + type = string + default = "spot-dev-alb" +} From 28570aa8c0edaca91455787028c5fbb477588a01 Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Thu, 19 Feb 2026 09:07:30 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat(#332):=20argo=20cd=20remote=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../environments/dev/.terraform.lock.hcl | 47 +++++++--- .../environments/dev/k8s_bootstrap.tf | 66 ++++++++++++++ infra/terraform/environments/dev/main.tf | 91 +++++++++++-------- infra/terraform/environments/dev/outputs.tf | 7 +- infra/terraform/environments/dev/provider.tf | 22 ++++- infra/terraform/environments/dev/variables.tf | 25 +++++ infra/terraform/modules/database/main.tf | 2 +- infra/terraform/modules/database/variables.tf | 6 ++ infra/terraform/modules/dns/main.tf | 26 ++++-- infra/terraform/modules/dns/outputs.tf | 9 +- infra/terraform/modules/dns/variables.tf | 5 +- infra/terraform/modules/eks/output.tf | 6 ++ infra/terraform/modules/irsa/main.tf | 22 ++++- infra/terraform/modules/irsa/variables.tf | 1 + infra/terraform/modules/network/outputs.tf | 8 ++ infra/terraform/modules/waf/main.tf | 16 ---- 16 files changed, 265 insertions(+), 94 deletions(-) create mode 100644 infra/terraform/environments/dev/k8s_bootstrap.tf diff --git a/infra/terraform/environments/dev/.terraform.lock.hcl b/infra/terraform/environments/dev/.terraform.lock.hcl index 0cb89f90..efd52918 100644 --- a/infra/terraform/environments/dev/.terraform.lock.hcl +++ b/infra/terraform/environments/dev/.terraform.lock.hcl @@ -24,22 +24,43 @@ provider "registry.terraform.io/hashicorp/aws" { ] } +provider "registry.terraform.io/hashicorp/helm" { + version = "2.17.0" + constraints = "~> 2.12" + hashes = [ + "h1:kQMkcPVvHOguOqnxoEU2sm1ND9vCHiT8TvZ2x6v/Rsw=", + "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", + "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", + "zh:129345c82359837bb3f0070ce4891ec232697052f7d5ccf61d43d818912cf5f3", + "zh:3956187ec239f4045975b35e8c30741f701aa494c386aaa04ebabffe7749f81c", + "zh:66a9686d92a6b3ec43de3ca3fde60ef3d89fb76259ed3313ca4eb9bb8c13b7dd", + "zh:88644260090aa621e7e8083585c468c8dd5e09a3c01a432fb05da5c4623af940", + "zh:a248f650d174a883b32c5b94f9e725f4057e623b00f171936dcdcc840fad0b3e", + "zh:aa498c1f1ab93be5c8fbf6d48af51dc6ef0f10b2ea88d67bcb9f02d1d80d3930", + "zh:bf01e0f2ec2468c53596e027d376532a2d30feb72b0b5b810334d043109ae32f", + "zh:c46fa84cc8388e5ca87eb575a534ebcf68819c5a5724142998b487cb11246654", + "zh:d0c0f15ffc115c0965cbfe5c81f18c2e114113e7a1e6829f6bfd879ce5744fbb", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + provider "registry.terraform.io/hashicorp/kubernetes" { - version = "3.0.1" + version = "2.38.0" + constraints = "~> 2.25" hashes = [ - "h1:P0c8knzZnouTNFIRij8IS7+pqd0OKaFDYX0j4GRsiqo=", - "zh:02d55b0b2238fd17ffa12d5464593864e80f402b90b31f6e1bd02249b9727281", - "zh:20b93a51bfeed82682b3c12f09bac3031f5bdb4977c47c97a042e4df4fb2f9ba", - "zh:6e14486ecfaee38c09ccf33d4fdaf791409f90795c1b66e026c226fad8bc03c7", - "zh:8d0656ff422df94575668e32c310980193fccb1c28117e5c78dd2d4050a760a6", - "zh:9795119b30ec0c1baa99a79abace56ac850b6e6fbce60e7f6067792f6eb4b5f4", - "zh:b388c87acc40f6bd9620f4e23f01f3c7b41d9b88a68d5255dec0a72f0bdec249", - "zh:b59abd0a980649c2f97f172392f080eaeb18e486b603f83bf95f5d93aeccc090", - "zh:ba6e3060fddf4a022087d8f09e38aa0001c705f21170c2ded3d1c26c12f70d97", - "zh:c12626d044b1d5501cf95ca78cbe507c13ad1dd9f12d4736df66eb8e5f336eb8", - "zh:c55203240d50f4cdeb3df1e1760630d677679f5b1a6ffd9eba23662a4ad05119", - "zh:ea206a5a32d6e0d6e32f1849ad703da9a28355d9c516282a8458b5cf1502b2a1", + "h1:soK8Lt0SZ6dB+HsypFRDzuX/npqlMU6M0fvyaR1yW0k=", + "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", + "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", + "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", + "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", + "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", + "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", + "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", + "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", + "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", + "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", ] } diff --git a/infra/terraform/environments/dev/k8s_bootstrap.tf b/infra/terraform/environments/dev/k8s_bootstrap.tf new file mode 100644 index 00000000..4efd0881 --- /dev/null +++ b/infra/terraform/environments/dev/k8s_bootstrap.tf @@ -0,0 +1,66 @@ +resource "helm_release" "aws_load_balancer_controller_spot" { + provider = helm + name = "aws-load-balancer-controller" + namespace = "kube-system" + repository = "https://aws.github.io/eks-charts" + chart = "aws-load-balancer-controller" + version = var.alb_controller_chart_version + + set { + name = "clusterName" + value = module.eks.cluster_name + } + + set { + name = "serviceAccount.create" + value = "false" + } + + set { + name = "serviceAccount.name" + value = "aws-load-balancer-controller" + } + + depends_on = [module.irsa] +} + + +# ============================================================================= +# argo cd remote 등록 +# ============================================================================= +resource "kubernetes_namespace_v1" "argocd_access" { + provider = kubernetes.spot + + metadata { + name = "argocd" + } +} + +resource "kubernetes_service_account_v1" "argocd_manager" { + provider = kubernetes.spot + + metadata { + name = "argocd-manager" + namespace = kubernetes_namespace_v1.argocd_access.metadata[0].name + } +} + +resource "kubernetes_cluster_role_binding_v1" "argocd_manager_admin" { + provider = kubernetes.spot + + metadata { + name = "argocd-manager-cluster-admin" + } + + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = "cluster-admin" + } + + subject { + kind = "ServiceAccount" + name = kubernetes_service_account_v1.argocd_manager.metadata[0].name + namespace = kubernetes_service_account_v1.argocd_manager.metadata[0].namespace + } +} \ No newline at end of file diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf index 40475023..6bac1fba 100644 --- a/infra/terraform/environments/dev/main.tf +++ b/infra/terraform/environments/dev/main.tf @@ -4,12 +4,12 @@ data "aws_caller_identity" "current" {} # ============================================================================= -# Network +# Network (SPOT) # ============================================================================= -module "network" { +module "network_spot" { source = "../../modules/network" - name_prefix = local.name_prefix + name_prefix = "${local.name_prefix}-spot" common_tags = local.common_tags vpc_cidr = var.vpc_cidr public_subnet_cidrs = var.public_subnet_cidrs @@ -18,6 +18,8 @@ module "network" { nat_instance_type = var.nat_instance_type } + + # ============================================================================= # Database # ============================================================================= @@ -26,9 +28,12 @@ module "database" { name_prefix = local.name_prefix common_tags = local.common_tags - vpc_id = module.network.vpc_id - vpc_cidr = module.network.vpc_cidr - subnet_ids = module.network.private_subnet_ids + vpc_id = module.network_spot.vpc_id + vpc_cidr = module.network_spot.vpc_cidr + subnet_ids = module.network_spot.private_subnet_ids + + allowed_security_group_ids = [module.eks.node_security_group_id] + db_name = var.db_name username = var.db_username password = var.db_password @@ -37,6 +42,8 @@ module "database" { engine_version = var.db_engine_version } + + # ============================================================================= # ECR (Multiple Repositories) # ============================================================================= @@ -52,27 +59,31 @@ module "ecr" { # ============================================================================= # DNS (Route 53 + ACM) # ============================================================================= -# module "dns" { -# source = "../../modules/dns" -# -# name_prefix = local.name_prefix -# common_tags = local.common_tags -# domain_name = var.domain_name -# create_api_domain = var.create_api_domain -# api_gateway_id = module.api_gateway.api_id -# } +module "dns" { + source = "../../modules/dns" + + name_prefix = local.name_prefix + common_tags = local.common_tags + domain_name = var.domain_name + + create_alb_record = var.create_alb_record + alb_name = "spot-dev-alb" + alb_record_name = "spotorder.org" +} + # ============================================================================= # WAF (Web Application Firewall) # ============================================================================= -# module "waf" { -# source = "../../modules/waf" -# -# name_prefix = local.name_prefix -# common_tags = local.common_tags -# api_gateway_stage_arn = module.api_gateway.stage_arn -# rate_limit = var.waf_rate_limit -# } +module "waf" { + source = "../../modules/waf" + + name_prefix = local.name_prefix + common_tags = local.common_tags + rate_limit = var.waf_rate_limit + log_retention_days = var.waf_log_retention_days +} + # ============================================================================= # S3 (정적 파일 / 로그 저장) @@ -96,8 +107,8 @@ module "elasticache" { name_prefix = local.name_prefix common_tags = local.common_tags - vpc_id = module.network.vpc_id - subnet_ids = module.network.private_subnet_ids + vpc_id = module.network_spot.vpc_id + subnet_ids = module.network_spot.private_subnet_ids allowed_security_group_ids = [module.eks.node_security_group_id] node_type = var.redis_node_type num_cache_clusters = var.redis_num_cache_clusters @@ -112,9 +123,9 @@ module "kafka" { name_prefix = local.name_prefix common_tags = local.common_tags - vpc_id = module.network.vpc_id - vpc_cidr = module.network.vpc_cidr - subnet_id = module.network.public_subnet_a_id # NAT 문제로 public 사용 + vpc_id = module.network_spot.vpc_id + vpc_cidr = module.network_spot.vpc_cidr + subnet_id = module.network_spot.public_subnet_a_id # NAT 문제로 public 사용 allowed_security_group_ids = [module.eks.node_security_group_id] assign_public_ip = true @@ -164,27 +175,27 @@ module "monitoring" { # ============================================================================= -# eks +# eks (SPOT) # ============================================================================= module "eks" { source = "../../modules/eks" - name_prefix = local.name_prefix + name_prefix = "${local.name_prefix}-spot" common_tags = local.common_tags - cluster_name = var.cluster_name + cluster_name = "${var.cluster_name}-spot" cluster_version = var.cluster_version - vpc_id = module.network.vpc_id + vpc_id = module.network_spot.vpc_id - subnet_ids = module.network.private_subnet_ids - node_subnet_ids = module.network.private_subnet_ids + subnet_ids = module.network_spot.private_subnet_ids + node_subnet_ids = module.network_spot.private_subnet_ids endpoint_private_access = true endpoint_public_access = true + public_access_cidrs = var.eks_public_access_cidrs enable_node_group = true - node_desired_size = 1 node_min_size = 1 node_max_size = 1 @@ -193,14 +204,10 @@ module "eks" { } -data "aws_eks_cluster" "this" { - name = module.eks.cluster_name -} - module "irsa" { source = "../../modules/irsa" - name_prefix = local.name_prefix + name_prefix = "${local.name_prefix}-spot" common_tags = local.common_tags oidc_issuer_url = module.eks.oidc_issuer_url @@ -209,11 +216,15 @@ module "irsa" { aws_load_balancer_controller = { namespace = "kube-system" service_account = "aws-load-balancer-controller" + policy_arn = "" + create_k8s_sa = true } ebs_csi_driver = { namespace = "kube-system" service_account = "ebs-csi-controller-sa" policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" + # ebs csi용 k8s sa는 만들지 않기 + create_k8s_sa = false } } diff --git a/infra/terraform/environments/dev/outputs.tf b/infra/terraform/environments/dev/outputs.tf index 6b35b241..68c092ed 100644 --- a/infra/terraform/environments/dev/outputs.tf +++ b/infra/terraform/environments/dev/outputs.tf @@ -3,7 +3,7 @@ # ============================================================================= output "vpc_id" { description = "VPC ID" - value = module.network.vpc_id + value = module.network_spot.vpc_id } # ============================================================================= @@ -90,11 +90,10 @@ output "eks_node_security_group_id" { value = module.eks.node_security_group_id } -output "oidc_issuer_url" { - value = try(data.aws_eks_cluster.this.identity[0].oidc[0].issuer, null) +output "oidc_issuer_url_spot" { + value = module.eks.oidc_issuer_url } - output "irsa_role_arns" { value = module.irsa.service_account_role_arns } diff --git a/infra/terraform/environments/dev/provider.tf b/infra/terraform/environments/dev/provider.tf index e63f8559..7de13cf4 100644 --- a/infra/terraform/environments/dev/provider.tf +++ b/infra/terraform/environments/dev/provider.tf @@ -10,6 +10,14 @@ terraform { source = "hashicorp/tls" version = ">= 4.0" } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.25" + } + helm = { + source = "hashicorp/helm" + version = "~> 2.12" + } # postgresql = { # source = "cyrilgdn/postgresql" # version = "~> 1.21" @@ -48,13 +56,23 @@ provider "aws" { # } # kubernetes - IRSA 모듈에 SA 추가 -data "aws_eks_cluster_auth" "this" { +data "aws_eks_cluster_auth" "spot" { name = module.eks.cluster_name } provider "kubernetes" { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_ca) - token = data.aws_eks_cluster_auth.this.token + token = data.aws_eks_cluster_auth.spot.token } + +provider "helm" { + kubernetes { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_ca) + token = data.aws_eks_cluster_auth.spot.token + } +} + + diff --git a/infra/terraform/environments/dev/variables.tf b/infra/terraform/environments/dev/variables.tf index 892c8112..f99ea981 100644 --- a/infra/terraform/environments/dev/variables.tf +++ b/infra/terraform/environments/dev/variables.tf @@ -214,6 +214,13 @@ variable "waf_rate_limit" { default = 2000 } +variable "waf_log_retention_days" { + description = "WAF 로그 보관 일수" + type = number + default = 30 +} + + # ============================================================================= # S3 설정 # ============================================================================= @@ -366,3 +373,21 @@ variable "cluster_version" { default = "1.29" } +# ============================================================================= +# k8s_bootstrap.tf +# ============================================================================= +variable "alb_controller_chart_version" { + type = string + description = "Helm chart version for aws-load-balancer-controller" + default = "1.7.2" +} + +variable "create_alb_record" { + type = bool + default = false +} +variable "eks_public_access_cidrs" { + description = "CIDRs allowed to access EKS public endpoint" + type = list(string) + default = [] +} diff --git a/infra/terraform/modules/database/main.tf b/infra/terraform/modules/database/main.tf index 7fa13ed9..4b63c713 100644 --- a/infra/terraform/modules/database/main.tf +++ b/infra/terraform/modules/database/main.tf @@ -9,7 +9,7 @@ resource "aws_security_group" "db_sg" { from_port = 5432 to_port = 5432 protocol = "tcp" - cidr_blocks = [var.vpc_cidr] + security_groups = var.allowed_security_group_ids } egress { diff --git a/infra/terraform/modules/database/variables.tf b/infra/terraform/modules/database/variables.tf index 0eb08d5f..7202d761 100644 --- a/infra/terraform/modules/database/variables.tf +++ b/infra/terraform/modules/database/variables.tf @@ -121,3 +121,9 @@ variable "max_allocated_storage" { type = number default = null } + +variable "allowed_security_group_ids" { + type = list(string) + description = "Security group IDs allowed to access the database" + default = [] +} diff --git a/infra/terraform/modules/dns/main.tf b/infra/terraform/modules/dns/main.tf index 21408942..f6dcbcc4 100644 --- a/infra/terraform/modules/dns/main.tf +++ b/infra/terraform/modules/dns/main.tf @@ -2,11 +2,22 @@ # Route 53 Hosted Zone # ============================================================================= resource "aws_route53_zone" "main" { - name = var.domain_name + count = var.create_hosted_zone ? 1 : 0 + name = var.domain_name tags = merge(var.common_tags, { Name = "${var.name_prefix}-zone" }) } +data "aws_route53_zone" "main" { + count = var.create_hosted_zone ? 0 : 1 + name = var.domain_name + private_zone = false +} + +locals { + zone_id = var.create_hosted_zone ? aws_route53_zone.main[0].zone_id : data.aws_route53_zone.main[0].zone_id +} + # ============================================================================= # ACM Certificate # ============================================================================= @@ -34,7 +45,7 @@ resource "aws_route53_record" "acm_validation" { } } - zone_id = aws_route53_zone.main.zone_id + zone_id = local.zone_id name = each.value.name type = each.value.type ttl = 60 @@ -42,6 +53,7 @@ resource "aws_route53_record" "acm_validation" { allow_overwrite = true } + # ============================================================================= # ACM Certificate Validation # ============================================================================= @@ -55,20 +67,22 @@ resource "aws_acm_certificate_validation" "main" { # EKS(ALB) Record # ============================================================================= data "aws_lb" "ingress_alb" { - name = var.alb_name + count = var.create_alb_record ? 1 : 0 + name = var.alb_name } resource "aws_route53_record" "alb" { count = var.create_alb_record ? 1 : 0 - zone_id = aws_route53_zone.main.zone_id + zone_id = local.zone_id name = var.alb_record_name type = "A" alias { - name = data.aws_lb.ingress_alb.dns_name - zone_id = data.aws_lb.ingress_alb.zone_id + name = data.aws_lb.ingress_alb[0].dns_name + zone_id = data.aws_lb.ingress_alb[0].zone_id evaluate_target_health = false } } + diff --git a/infra/terraform/modules/dns/outputs.tf b/infra/terraform/modules/dns/outputs.tf index c41e6cab..cbbb3588 100644 --- a/infra/terraform/modules/dns/outputs.tf +++ b/infra/terraform/modules/dns/outputs.tf @@ -1,16 +1,13 @@ output "zone_id" { - description = "Route 53 Hosted Zone ID" - value = aws_route53_zone.main.zone_id + value = local.zone_id } output "zone_name" { - description = "Route 53 Hosted Zone 이름" - value = aws_route53_zone.main.name + value = var.domain_name } output "name_servers" { - description = "Route 53 네임서버 목록 (도메인 등록 기관에 설정 필요)" - value = aws_route53_zone.main.name_servers + value = var.create_hosted_zone ? aws_route53_zone.main[0].name_servers : null } output "certificate_arn" { diff --git a/infra/terraform/modules/dns/variables.tf b/infra/terraform/modules/dns/variables.tf index d9948782..e77f45c4 100644 --- a/infra/terraform/modules/dns/variables.tf +++ b/infra/terraform/modules/dns/variables.tf @@ -13,7 +13,10 @@ variable "domain_name" { description = "도메인 이름 (ex: spotorder.org)" type = string } - +variable "create_hosted_zone" { + type = bool + default = false +} # ALB alias record 생성 여부 variable "create_alb_record" { description = "ALB(Route53 Alias) 레코드 생성 여부" diff --git a/infra/terraform/modules/eks/output.tf b/infra/terraform/modules/eks/output.tf index 57e3bf2c..130e488b 100644 --- a/infra/terraform/modules/eks/output.tf +++ b/infra/terraform/modules/eks/output.tf @@ -28,3 +28,9 @@ output "node_security_group_id" { output "node_role_arn" { value = aws_iam_role.node.arn } + + + +output "cluster_security_group_id" { + value = aws_eks_cluster.this.vpc_config[0].cluster_security_group_id +} \ No newline at end of file diff --git a/infra/terraform/modules/irsa/main.tf b/infra/terraform/modules/irsa/main.tf index f01c85a6..0385ebaa 100644 --- a/infra/terraform/modules/irsa/main.tf +++ b/infra/terraform/modules/irsa/main.tf @@ -10,12 +10,18 @@ resource "aws_iam_openid_connect_provider" "this" { } locals { + k8s_sas = { + for k, v in var.service_accounts : k => v + if try(v.create_k8s_sa, true) + } + namespaces = toset([ - for k, v in var.service_accounts : v.namespace + for k, v in local.k8s_sas : v.namespace if v.namespace != "kube-system" && v.namespace != "default" ]) } + resource "aws_iam_role" "service_account" { for_each = var.service_accounts @@ -86,7 +92,10 @@ resource "aws_iam_role_policy_attachment" "managed" { } -resource "kubernetes_namespace" "this" { + + + +resource "kubernetes_namespace_v1" "this" { for_each = local.namespaces metadata { @@ -94,8 +103,11 @@ resource "kubernetes_namespace" "this" { } } -resource "kubernetes_service_account" "this" { - for_each = var.service_accounts +resource "kubernetes_service_account_v1" "this" { + for_each = { + for k, v in local.k8s_sas : k => v + if v.create_k8s_sa + } metadata { name = each.value.service_account @@ -105,5 +117,5 @@ resource "kubernetes_service_account" "this" { } } - depends_on = [kubernetes_namespace.this] + depends_on = [kubernetes_namespace_v1.this] } diff --git a/infra/terraform/modules/irsa/variables.tf b/infra/terraform/modules/irsa/variables.tf index 065aaac7..d784c168 100644 --- a/infra/terraform/modules/irsa/variables.tf +++ b/infra/terraform/modules/irsa/variables.tf @@ -15,6 +15,7 @@ variable "service_accounts" { namespace = string service_account = string policy_arn = optional(string) + create_k8s_sa = optional(bool, true) })) default = {} } diff --git a/infra/terraform/modules/network/outputs.tf b/infra/terraform/modules/network/outputs.tf index 7770a2b9..0a3cbad5 100644 --- a/infra/terraform/modules/network/outputs.tf +++ b/infra/terraform/modules/network/outputs.tf @@ -17,6 +17,7 @@ output "public_subnet_c_id" { value = try(aws_subnet.public_c[0].id, null) } + output "public_subnet_ids" { value = compact([ aws_subnet.public_a.id, @@ -56,3 +57,10 @@ output "nat_type" { description = "NAT 유형 (gateway 또는 instance)" value = var.use_nat_gateway ? "gateway" : "instance" } + + + +# ============================================================================= +# argoCD - peer routing +# ============================================================================= + diff --git a/infra/terraform/modules/waf/main.tf b/infra/terraform/modules/waf/main.tf index 0e55a219..60eadfac 100644 --- a/infra/terraform/modules/waf/main.tf +++ b/infra/terraform/modules/waf/main.tf @@ -112,22 +112,6 @@ resource "aws_wafv2_web_acl" "main" { } -# ============================================================================= -# Find ALB created by Ingress -# ============================================================================= -data "aws_lb" "ingress_alb" { - count = var.associate_to_alb ? 1 : 0 - name = var.alb_name -} - -resource "aws_wafv2_web_acl_association" "ingress_alb" { - count = var.associate_to_alb ? 1 : 0 - resource_arn = data.aws_lb.ingress_alb[0].arn - web_acl_arn = aws_wafv2_web_acl.main.arn -} - - - From 70b48dc5294d7e0bca92623f770df198f81763fc Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Thu, 19 Feb 2026 09:54:00 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat(#332):=20irsa=20sa=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../environments/dev/k8s_bootstrap.tf | 66 ----------- infra/terraform/environments/dev/locals.tf | 15 +++ infra/terraform/environments/dev/main.tf | 44 ++++--- infra/terraform/environments/dev/provider.tf | 8 +- infra/terraform/modules/irsa/main.tf | 39 ------- infra/terraform/modules/k8s-bootstrap/main.tf | 108 ++++++++++++++++++ .../modules/k8s-bootstrap/variables.tf | 32 ++++++ 7 files changed, 186 insertions(+), 126 deletions(-) delete mode 100644 infra/terraform/environments/dev/k8s_bootstrap.tf create mode 100644 infra/terraform/modules/k8s-bootstrap/main.tf create mode 100644 infra/terraform/modules/k8s-bootstrap/variables.tf diff --git a/infra/terraform/environments/dev/k8s_bootstrap.tf b/infra/terraform/environments/dev/k8s_bootstrap.tf deleted file mode 100644 index 4efd0881..00000000 --- a/infra/terraform/environments/dev/k8s_bootstrap.tf +++ /dev/null @@ -1,66 +0,0 @@ -resource "helm_release" "aws_load_balancer_controller_spot" { - provider = helm - name = "aws-load-balancer-controller" - namespace = "kube-system" - repository = "https://aws.github.io/eks-charts" - chart = "aws-load-balancer-controller" - version = var.alb_controller_chart_version - - set { - name = "clusterName" - value = module.eks.cluster_name - } - - set { - name = "serviceAccount.create" - value = "false" - } - - set { - name = "serviceAccount.name" - value = "aws-load-balancer-controller" - } - - depends_on = [module.irsa] -} - - -# ============================================================================= -# argo cd remote 등록 -# ============================================================================= -resource "kubernetes_namespace_v1" "argocd_access" { - provider = kubernetes.spot - - metadata { - name = "argocd" - } -} - -resource "kubernetes_service_account_v1" "argocd_manager" { - provider = kubernetes.spot - - metadata { - name = "argocd-manager" - namespace = kubernetes_namespace_v1.argocd_access.metadata[0].name - } -} - -resource "kubernetes_cluster_role_binding_v1" "argocd_manager_admin" { - provider = kubernetes.spot - - metadata { - name = "argocd-manager-cluster-admin" - } - - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "ClusterRole" - name = "cluster-admin" - } - - subject { - kind = "ServiceAccount" - name = kubernetes_service_account_v1.argocd_manager.metadata[0].name - namespace = kubernetes_service_account_v1.argocd_manager.metadata[0].namespace - } -} \ No newline at end of file diff --git a/infra/terraform/environments/dev/locals.tf b/infra/terraform/environments/dev/locals.tf index 9512a84d..cbc91fd8 100644 --- a/infra/terraform/environments/dev/locals.tf +++ b/infra/terraform/environments/dev/locals.tf @@ -6,4 +6,19 @@ locals { Environment = var.environment ManagedBy = "terraform" } + + service_accounts = { + aws_load_balancer_controller = { + namespace = "kube-system" + service_account = "aws-load-balancer-controller" + policy_arn = "" + create_k8s_sa = true + } + ebs_csi_driver = { + namespace = "kube-system" + service_account = "ebs-csi-controller-sa" + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" + create_k8s_sa = false + } + } } diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf index 6bac1fba..cfeeaa83 100644 --- a/infra/terraform/environments/dev/main.tf +++ b/infra/terraform/environments/dev/main.tf @@ -175,7 +175,7 @@ module "monitoring" { # ============================================================================= -# eks (SPOT) +# eks # ============================================================================= module "eks" { source = "../../modules/eks" @@ -211,23 +211,9 @@ module "irsa" { common_tags = local.common_tags oidc_issuer_url = module.eks.oidc_issuer_url + service_accounts = local.service_accounts - service_accounts = { - aws_load_balancer_controller = { - namespace = "kube-system" - service_account = "aws-load-balancer-controller" - policy_arn = "" - create_k8s_sa = true - } - ebs_csi_driver = { - namespace = "kube-system" - service_account = "ebs-csi-controller-sa" - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" - # ebs csi용 k8s sa는 만들지 않기 - create_k8s_sa = false - } - } } module "eks_addons" { @@ -242,4 +228,30 @@ module "eks_addons" { enable_coredns = true enable_kube_proxy = true enable_ebs_csi = true + } + + +module "k8s_bootstrap" { + source = "../../modules/k8s-bootstrap" + + providers = { + kubernetes = kubernetes + helm = helm + } + + cluster_name = module.eks.cluster_name + alb_controller_chart_version = var.alb_controller_chart_version + + service_accounts = local.service_accounts + service_account_role_arns = module.irsa.service_account_role_arns + enable_lbc = true + enable_argocd_access = true + + depends_on = [ + module.eks, + module.irsa, + module.eks_addons + ] +} + diff --git a/infra/terraform/environments/dev/provider.tf b/infra/terraform/environments/dev/provider.tf index 7de13cf4..97e3a371 100644 --- a/infra/terraform/environments/dev/provider.tf +++ b/infra/terraform/environments/dev/provider.tf @@ -55,23 +55,21 @@ provider "aws" { # owner = var.db_username # } -# kubernetes - IRSA 모듈에 SA 추가 -data "aws_eks_cluster_auth" "spot" { +data "aws_eks_cluster_auth" "this" { name = module.eks.cluster_name } provider "kubernetes" { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_ca) - token = data.aws_eks_cluster_auth.spot.token + token = data.aws_eks_cluster_auth.this.token } - provider "helm" { kubernetes { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_ca) - token = data.aws_eks_cluster_auth.spot.token + token = data.aws_eks_cluster_auth.this.token } } diff --git a/infra/terraform/modules/irsa/main.tf b/infra/terraform/modules/irsa/main.tf index 0385ebaa..e2c4d014 100644 --- a/infra/terraform/modules/irsa/main.tf +++ b/infra/terraform/modules/irsa/main.tf @@ -1,7 +1,6 @@ data "tls_certificate" "oidc" { url = var.oidc_issuer_url } - resource "aws_iam_openid_connect_provider" "this" { url = var.oidc_issuer_url client_id_list = ["sts.amazonaws.com"] @@ -9,19 +8,6 @@ resource "aws_iam_openid_connect_provider" "this" { tags = var.common_tags } -locals { - k8s_sas = { - for k, v in var.service_accounts : k => v - if try(v.create_k8s_sa, true) - } - - namespaces = toset([ - for k, v in local.k8s_sas : v.namespace - if v.namespace != "kube-system" && v.namespace != "default" - ]) -} - - resource "aws_iam_role" "service_account" { for_each = var.service_accounts @@ -94,28 +80,3 @@ resource "aws_iam_role_policy_attachment" "managed" { - -resource "kubernetes_namespace_v1" "this" { - for_each = local.namespaces - - metadata { - name = each.value - } -} - -resource "kubernetes_service_account_v1" "this" { - for_each = { - for k, v in local.k8s_sas : k => v - if v.create_k8s_sa - } - - metadata { - name = each.value.service_account - namespace = each.value.namespace - annotations = { - "eks.amazonaws.com/role-arn" = aws_iam_role.service_account[each.key].arn - } - } - - depends_on = [kubernetes_namespace_v1.this] -} diff --git a/infra/terraform/modules/k8s-bootstrap/main.tf b/infra/terraform/modules/k8s-bootstrap/main.tf new file mode 100644 index 00000000..25d57596 --- /dev/null +++ b/infra/terraform/modules/k8s-bootstrap/main.tf @@ -0,0 +1,108 @@ +# ----------------------------------------------------------------------------- +# (A) ArgoCD Remote Access (namespace/sa/crb) +# ----------------------------------------------------------------------------- +resource "kubernetes_namespace_v1" "argocd" { + count = var.enable_argocd_access ? 1 : 0 + + metadata { + name = "argocd" + } +} + +resource "kubernetes_service_account_v1" "argocd_manager" { + count = var.enable_argocd_access ? 1 : 0 + + metadata { + name = "argocd-manager" + namespace = kubernetes_namespace_v1.argocd[0].metadata[0].name + } +} + +resource "kubernetes_cluster_role_binding_v1" "argocd_manager_admin" { + count = var.enable_argocd_access ? 1 : 0 + + metadata { + name = "argocd-manager-cluster-admin" + } + + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = "cluster-admin" + } + + subject { + kind = "ServiceAccount" + name = kubernetes_service_account_v1.argocd_manager[0].metadata[0].name + namespace = kubernetes_service_account_v1.argocd_manager[0].metadata[0].namespace + } +} + +# ----------------------------------------------------------------------------- +# (B) AWS Load Balancer Controller (Helm) +# ----------------------------------------------------------------------------- +resource "helm_release" "aws_load_balancer_controller" { + count = var.enable_lbc ? 1 : 0 + name = "aws-load-balancer-controller" + namespace = "kube-system" + repository = "https://aws.github.io/eks-charts" + chart = "aws-load-balancer-controller" + version = var.alb_controller_chart_version + + set { + name = "clusterName" + value = var.cluster_name + } + + # IRSA에서 SA를 만들고 role-arn annotation 붙일 거면 false 유지 + set { + name = "serviceAccount.create" + value = "false" + } + + set { + name = "serviceAccount.name" + value = "aws-load-balancer-controller" + } +} + + + + +# irsa - SA 생성 +locals { + k8s_sas = { + for k, v in var.service_accounts : k => v + if try(v.create_k8s_sa, true) + } + + namespaces = toset([ + for k, v in local.k8s_sas : v.namespace + if v.namespace != "kube-system" && v.namespace != "default" + ]) +} + +resource "kubernetes_namespace_v1" "this" { + for_each = local.namespaces + + metadata { + name = each.value + } +} + +resource "kubernetes_service_account_v1" "this" { + for_each = { + for k, v in local.k8s_sas : k => v + if try(v.create_k8s_sa, true) + } + + metadata { + name = each.value.service_account + namespace = each.value.namespace + annotations = { + "eks.amazonaws.com/role-arn" = var.service_account_role_arns[each.key] + } + } + + depends_on = [kubernetes_namespace_v1.this] +} diff --git a/infra/terraform/modules/k8s-bootstrap/variables.tf b/infra/terraform/modules/k8s-bootstrap/variables.tf new file mode 100644 index 00000000..696dbf6c --- /dev/null +++ b/infra/terraform/modules/k8s-bootstrap/variables.tf @@ -0,0 +1,32 @@ +variable "cluster_name" { + type = string +} + +variable "alb_controller_chart_version" { + type = string +} + + +variable "enable_lbc" { + type = bool + default = true +} + +variable "enable_argocd_access" { + type = bool + default = true +} + + +# irsa - SA 생성 +variable "service_accounts" { + type = map(object({ + namespace = string + service_account = string + create_k8s_sa = optional(bool, true) + })) +} + +variable "service_account_role_arns" { + type = map(string) +} From 4d981fd05e071f93c884b992c9f06cab8a2d0a6d Mon Sep 17 00:00:00 2001 From: dbswjd7 Date: Thu, 19 Feb 2026 17:53:36 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat(#332):=20aws=20eks=20app=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/k8s/apps/spot-ingress.yaml | 3 +- .../k8s/base/kafka/allow-kafka-ui-netpol.yaml | 2 +- infra/k8s/base/kafka/connectors.yaml | 8 +- infra/k8s/base/kafka/kafka-connect.yaml | 22 ++--- infra/k8s/base/kafka/kafka-nodepool.yml | 6 +- infra/k8s/base/kafka/kafka-ui.yaml | 4 +- infra/k8s/base/kafka/kafka.yaml | 13 +-- infra/k8s/base/temporal/temporal.yaml | 23 +++-- infra/k8s/kustomization.yaml | 83 ++++++++++--------- infra/terraform/environments/dev/variables.tf | 4 +- infra/terraform/modules/waf/main.tf | 2 +- temporal | 1 + 12 files changed, 92 insertions(+), 79 deletions(-) create mode 160000 temporal diff --git a/infra/k8s/apps/spot-ingress.yaml b/infra/k8s/apps/spot-ingress.yaml index 7bf72dca..d0b4be12 100644 --- a/infra/k8s/apps/spot-ingress.yaml +++ b/infra/k8s/apps/spot-ingress.yaml @@ -9,7 +9,6 @@ metadata: alb.ingress.kubernetes.io/healthcheck-path: /actuator/health alb.ingress.kubernetes.io/success-codes: "200" # 헬스 체크 성공 코드 alb.ingress.kubernetes.io/load-balancer-name: spot-dev-alb # tf위한 이름 고정 - alb.ingress.kubernetes.io/subnets: subnet-00e1be3889eafda18,subnet-03bfda9c6a9c7a75a spec: ingressClassName: alb rules: @@ -26,7 +25,7 @@ spec: number: 80 # kafka - - host: kafka.localhost + - host: kafka.spotorder.org http: paths: - path: / diff --git a/infra/k8s/base/kafka/allow-kafka-ui-netpol.yaml b/infra/k8s/base/kafka/allow-kafka-ui-netpol.yaml index 06c25c01..37ca5430 100644 --- a/infra/k8s/base/kafka/allow-kafka-ui-netpol.yaml +++ b/infra/k8s/base/kafka/allow-kafka-ui-netpol.yaml @@ -2,7 +2,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-kafka-ui-to-connect - namespace: spot + namespace: kafka spec: podSelector: matchLabels: diff --git a/infra/k8s/base/kafka/connectors.yaml b/infra/k8s/base/kafka/connectors.yaml index 4db633d7..51310405 100644 --- a/infra/k8s/base/kafka/connectors.yaml +++ b/infra/k8s/base/kafka/connectors.yaml @@ -2,7 +2,7 @@ apiVersion: kafka.strimzi.io/v1 kind: KafkaConnector metadata: name: order-outbox-connector - namespace: spot + namespace: kafka labels: # 이전에 만든 KafkaConnect 리소스 이름(spot-connect)과 반드시 일치해야 함 strimzi.io/cluster: spot-connect @@ -14,7 +14,7 @@ spec: database.port: "5432" database.user: "${env:SPRING_DATASOURCE_USERNAME}" database.password: "${env:SPRING_DATASOURCE_PASSWORD}" - database.dbname: "${env:DB_NAME}" + database.dbname: "spotdb" topic.prefix: "order_outbox_cdc" plugin.name: "pgoutput" slot.name: "order_outbox_slot" @@ -47,7 +47,7 @@ apiVersion: kafka.strimzi.io/v1 kind: KafkaConnector metadata: name: payment-outbox-connector - namespace: spot + namespace: kafka labels: strimzi.io/cluster: spot-connect spec: @@ -58,7 +58,7 @@ spec: database.port: "5432" database.user: "${env:SPRING_DATASOURCE_USERNAME}" database.password: "${env:SPRING_DATASOURCE_PASSWORD}" - database.dbname: "${env:DB_NAME}" + database.dbname: "spotdb" topic.prefix: "payment_outbox_cdc" plugin.name: "pgoutput" slot.name: "payment_outbox_slot" diff --git a/infra/k8s/base/kafka/kafka-connect.yaml b/infra/k8s/base/kafka/kafka-connect.yaml index d22663d7..8993fc1b 100644 --- a/infra/k8s/base/kafka/kafka-connect.yaml +++ b/infra/k8s/base/kafka/kafka-connect.yaml @@ -2,24 +2,26 @@ apiVersion: kafka.strimzi.io/v1 kind: KafkaConnect metadata: name: spot-connect - namespace: spot + namespace: kafka annotations: strimzi.io/use-connector-resources: "true" spec: - version: 4.0.0 - replicas: 2 + replicas: 1 bootstrapServers: spot-cluster-kafka-bootstrap:9092 - image: spot-registry.localhost:5111/spot-connect-custom:latest - + image: 322546275072.dkr.ecr.ap-northeast-2.amazonaws.com/spot-kafka-connect:3.4.0 groupId: spot-connect-group configStorageTopic: connect_configs offsetStorageTopic: connect_offsets statusStorageTopic: connect_status + + config: - config.storage.replication.factor: 3 - offset.storage.replication.factor: 3 - status.storage.replication.factor: 3 + + config.storage.replication.factor: 1 + offset.storage.replication.factor: 1 + status.storage.replication.factor: 1 + key.converter: org.apache.kafka.connect.json.JsonConverter value.converter: org.apache.kafka.connect.json.JsonConverter @@ -57,8 +59,8 @@ spec: resources: requests: - cpu: "500m" - memory: "512Mi" + cpu: "200m" + memory: "128Mi" limits: cpu: "1000m" memory: "1Gi" \ No newline at end of file diff --git a/infra/k8s/base/kafka/kafka-nodepool.yml b/infra/k8s/base/kafka/kafka-nodepool.yml index 195b7696..1ad5f78e 100644 --- a/infra/k8s/base/kafka/kafka-nodepool.yml +++ b/infra/k8s/base/kafka/kafka-nodepool.yml @@ -2,11 +2,11 @@ apiVersion: kafka.strimzi.io/v1 kind: KafkaNodePool metadata: name: kafka-nodes - namespace: spot + namespace: kafka labels: strimzi.io/cluster: spot-cluster spec: - replicas: 3 + replicas: 1 roles: - broker - controller @@ -19,7 +19,7 @@ spec: deleteClaim: true resources: requests: - memory: 512Mi + memory: 128Mi cpu: 250m limits: memory: 1Gi diff --git a/infra/k8s/base/kafka/kafka-ui.yaml b/infra/k8s/base/kafka/kafka-ui.yaml index 5ab3dab2..3a766680 100644 --- a/infra/k8s/base/kafka/kafka-ui.yaml +++ b/infra/k8s/base/kafka/kafka-ui.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: kafka-ui - namespace: spot + namespace: kafka spec: replicas: 1 selector: @@ -20,7 +20,7 @@ spec: - containerPort: 8080 resources: requests: - memory: "384Mi" + memory: "75Mi" cpu: "100m" env: - name: KAFKA_CLUSTERS_0_NAME diff --git a/infra/k8s/base/kafka/kafka.yaml b/infra/k8s/base/kafka/kafka.yaml index 6a082c0f..8d86974d 100644 --- a/infra/k8s/base/kafka/kafka.yaml +++ b/infra/k8s/base/kafka/kafka.yaml @@ -2,7 +2,7 @@ apiVersion: kafka.strimzi.io/v1 kind: Kafka metadata: name: spot-cluster - namespace: spot + namespace: kafka spec: kafka: version: 4.0.0 @@ -18,13 +18,14 @@ spec: tls: false config: process.roles: "broker,controller" - offsets.topic.replication.factor: 3 - transaction.state.log.replication.factor: 3 - transaction.state.log.min.isr: 2 - default.replication.factor: 3 - min.insync.replicas: 2 + offsets.topic.replication.factor: 1 + transaction.state.log.replication.factor: 1 + transaction.state.log.min.isr: 1 + default.replication.factor: 1 + min.insync.replicas: 1 auto.create.topics.enable: "true" num.partitions: 3 entityOperator: topicOperator: {} userOperator: {} + diff --git a/infra/k8s/base/temporal/temporal.yaml b/infra/k8s/base/temporal/temporal.yaml index ab115462..46e47dd8 100644 --- a/infra/k8s/base/temporal/temporal.yaml +++ b/infra/k8s/base/temporal/temporal.yaml @@ -23,11 +23,14 @@ spec: - name: DB value: postgres12 - name: POSTGRES_SEEDS - value: host.k3d.internal + value: spot-dev-db.cj6iq2cow597.ap-northeast-2.rds.amazonaws.com - name: DB_PORT value: "5432" - name: POSTGRES_USER - value: "admin" + valueFrom: + secretKeyRef: + name: spot-secrets + key: SPRING_DATASOURCE_USERNAME - name: POSTGRES_PWD valueFrom: secretKeyRef: @@ -37,14 +40,20 @@ spec: value: "spot_temporal" - name: VISIBILITY_DBNAME value: "spot_visibility" - - name: SKIP_SCHEMA_SETUP + + - name: SKIP_DB_CREATE value: "false" - - name: ENABLE_ES + - name: SKIP_SCHEMA_SETUP value: "false" - - name: SKIP_DEFAULT_NAMESPACE_CREATION + + - name: POSTGRES_TLS_ENABLED + value: "true" + - name: POSTGRES_TLS_DISABLE_HOST_VERIFICATION + value: "true" + - name: SQL_TLS_ENABLED + value: "true" + - name: SQL_HOST_VERIFICATION value: "false" - - name: LOG_LEVEL - value: "warn" resources: requests: memory: "256Mi" diff --git a/infra/k8s/kustomization.yaml b/infra/k8s/kustomization.yaml index fad85146..3dadd683 100644 --- a/infra/k8s/kustomization.yaml +++ b/infra/k8s/kustomization.yaml @@ -22,23 +22,23 @@ resources: - base/temporal/temporal.yaml - base/temporal/temporal-ui.yaml - # Monitoring & Logging - - base/monitoring/loki/loki.yaml - - base/monitoring/loki/loki-config.yaml - - base/monitoring/monitoring-ingress.yaml - - base/monitoring/fluent-bit/fluent-bit.yaml - - base/monitoring/fluent-bit/fluent-bit-config.yaml - - # Grafana - - base/monitoring/grafana/grafana.yaml - - base/monitoring/grafana/grafana-config.yaml - - # Spot Monitoring - - base/monitoring/servicemonitors/spot-gateway-servicemonitor.yaml - - base/monitoring/servicemonitors/spot-user-servicemonitor.yaml - - base/monitoring/servicemonitors/spot-order-servicemonitor.yaml - - base/monitoring/servicemonitors/spot-store-servicemonitor.yaml - - base/monitoring/servicemonitors/spot-payment-servicemonitor.yaml +# # Monitoring & Logging +# - base/monitoring/loki/loki.yaml +# - base/monitoring/loki/loki-config.yaml +# - base/monitoring/monitoring-ingress.yaml +# - base/monitoring/fluent-bit/fluent-bit.yaml +# - base/monitoring/fluent-bit/fluent-bit-config.yaml +# +# # Grafana +# - base/monitoring/grafana/grafana.yaml +# - base/monitoring/grafana/grafana-config.yaml +# +# # Spot Monitoring +# - base/monitoring/servicemonitors/spot-gateway-servicemonitor.yaml +# - base/monitoring/servicemonitors/spot-user-servicemonitor.yaml +# - base/monitoring/servicemonitors/spot-order-servicemonitor.yaml +# - base/monitoring/servicemonitors/spot-store-servicemonitor.yaml +# - base/monitoring/servicemonitors/spot-payment-servicemonitor.yaml # Spot Apps - apps/spot-ingress.yaml @@ -52,6 +52,7 @@ resources: configMapGenerator: - name: spot-app-config namespace: spot + behavior: replace files: - config/spot-gateway.yml - config/common.yml @@ -72,30 +73,30 @@ configMapGenerator: options: disableNameSuffixHash: true - - name: grafana-dashboards-spot - namespace: monitoring - files: - - base/monitoring/grafana/dashboards/spot-logs.json - - - name: grafana-dashboards-9578 - namespace: monitoring - files: - - base/monitoring/grafana/dashboards/9578.json - - - name: grafana-dashboards-15760 - namespace: monitoring - files: - - base/monitoring/grafana/dashboards/15760.json - - - name: grafana-dashboards-15757 - namespace: monitoring - files: - - base/monitoring/grafana/dashboards/15757.json - - - name: grafana-dashboards-15661 - namespace: monitoring - files: - - base/monitoring/grafana/dashboards/15661.json +# - name: grafana-dashboards-spot +# namespace: monitoring +# files: +# - base/monitoring/grafana/dashboards/spot-logs.json +# +# - name: grafana-dashboards-9578 +# namespace: monitoring +# files: +# - base/monitoring/grafana/dashboards/9578.json +# +# - name: grafana-dashboards-15760 +# namespace: monitoring +# files: +# - base/monitoring/grafana/dashboards/15760.json +# +# - name: grafana-dashboards-15757 +# namespace: monitoring +# files: +# - base/monitoring/grafana/dashboards/15757.json +# +# - name: grafana-dashboards-15661 +# namespace: monitoring +# files: +# - base/monitoring/grafana/dashboards/15661.json secretGenerator: - name: spot-secrets diff --git a/infra/terraform/environments/dev/variables.tf b/infra/terraform/environments/dev/variables.tf index f99ea981..a05c4ec5 100644 --- a/infra/terraform/environments/dev/variables.tf +++ b/infra/terraform/environments/dev/variables.tf @@ -350,7 +350,7 @@ variable "kafka_instance_type" { variable "kafka_volume_size" { description = "Kafka EBS 볼륨 크기 (GB)" type = number - default = 20 + default = 30 } variable "kafka_log_retention_hours" { @@ -384,7 +384,7 @@ variable "alb_controller_chart_version" { variable "create_alb_record" { type = bool - default = false + default = true } variable "eks_public_access_cidrs" { description = "CIDRs allowed to access EKS public endpoint" diff --git a/infra/terraform/modules/waf/main.tf b/infra/terraform/modules/waf/main.tf index 60eadfac..05421317 100644 --- a/infra/terraform/modules/waf/main.tf +++ b/infra/terraform/modules/waf/main.tf @@ -3,7 +3,7 @@ # ============================================================================= resource "aws_wafv2_web_acl" "main" { name = "${var.name_prefix}-waf" - description = "WAF for ALB (EKS Ingress)" + description = "WAF for ALB - EKS Ingress" scope = "REGIONAL" default_action { diff --git a/temporal b/temporal new file mode 160000 index 00000000..ca6536ad --- /dev/null +++ b/temporal @@ -0,0 +1 @@ +Subproject commit ca6536ada85941ddec669540f637925cfbc3e6de