Fix 6 P1 infrastructure issues from code review (FRE-4574)

- ALB: deploy to public subnets instead of private (adds public_subnet_ids var)
- ECS: fix launch_desired_count → launch_type = FARGATE
- Secrets: accept actual RDS/ElastiCache endpoints from parent module
- Deploy: fix circular dependency (needs.detect → steps.detect)
- Health check: dynamic ALB DNS lookup via aws elbv2 CLI
- Health check: exit 1 on failure so rollback triggers

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-10 02:28:48 -04:00
parent c7df40ac26
commit 4ddd24fd72
4 changed files with 62 additions and 35 deletions

View File

@@ -33,7 +33,7 @@ jobs:
- name: Calculate tag - name: Calculate tag
id: tag id: tag
run: | run: |
if [ "${{ needs.detect-environment.outputs.environment }}" = "production" ]; then if [ "${{ steps.detect.outputs.environment }}" = "production" ]; then
echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
else else
echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT
@@ -169,36 +169,47 @@ jobs:
needs: [detect-environment, deploy-ecs] needs: [detect-environment, deploy-ecs]
environment: ${{ needs.detect-environment.outputs.environment }} environment: ${{ needs.detect-environment.outputs.environment }}
steps: steps:
- name: Configure AWS
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Wait for deployment - name: Wait for deployment
run: sleep 30 run: sleep 30
- name: Health Check - name: Health Check
uses: jasongd/retry-action@v2 id: health
with: run: |
timeout-minutes: 5 ENV="${{ needs.detect-environment.outputs.environment }}"
retry-minutes: 10 CLUSTER="shieldai-${ENV}"
command: |
ALB_DNS=$(aws ecs describe-services \
--cluster "shieldai-${{ needs.detect-environment.outputs.environment }}" \
--services "shieldai-${{ needs.detect-environment.outputs.environment }}-api" \
--query 'services[0].loadBalancers[0].targetGroupArn' --output text)
for service in api darkwatch spamshield voiceprint; do ALB_DNS=$(aws elbv2 describe-load-balancers \
PORT=$(case $service in --query "LoadBalancers[?contains(LoadBalancerName, '${CLUSTER}-alb')].DNSName" \
api) echo 3000;; --output text)
darkwatch) echo 3001;;
spamshield) echo 3002;;
voiceprint) echo 3003;;
esac)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ if [ -z "$ALB_DNS" ]; then
"https://shieldai-${{ needs.detect-environment.outputs.environment }}-alb.us-east-1.elb.amazonaws.com/health" || true) echo "Health check failed: ALB DNS not found"
exit 1
fi
if [ "$HTTP_CODE" = "200" ]; then echo "ALB DNS: $ALB_DNS"
echo "Health check passed: $service"
else FAILED=0
echo "Health check failed: $service (HTTP $HTTP_CODE)" for service in api darkwatch spamshield voiceprint; do
fi HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
done "http://${ALB_DNS}/health" || true)
if [ "$HTTP_CODE" = "200" ]; then
echo "Health check passed: $service"
else
echo "Health check failed: $service (HTTP $HTTP_CODE)"
FAILED=1
fi
done
if [ "$FAILED" -eq 1 ]; then
exit 1
fi
rollback: rollback:
name: Rollback on Failure name: Rollback on Failure

View File

@@ -49,6 +49,7 @@ module "ecs" {
cluster_name = "${var.project_name}-${var.environment}" cluster_name = "${var.project_name}-${var.environment}"
vpc_id = module.vpc.vpc_id vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids subnet_ids = module.vpc.private_subnet_ids
public_subnet_ids = module.vpc.public_subnet_ids
security_group_ids = [module.vpc.ecs_security_group_id] security_group_ids = [module.vpc.ecs_security_group_id]
services = var.services services = var.services
container_images = var.container_images container_images = var.container_images
@@ -91,9 +92,11 @@ module "s3" {
module "secrets" { module "secrets" {
source = "./modules/secrets" source = "./modules/secrets"
environment = var.environment environment = var.environment
project_name = var.project_name project_name = var.project_name
secrets = var.secrets rds_endpoint = module.rds.db_endpoint
elasticache_endpoint = module.elasticache.cache_endpoint
secrets = var.secrets
} }
module "cloudwatch" { module "cloudwatch" {

View File

@@ -14,7 +14,12 @@ variable "vpc_id" {
} }
variable "subnet_ids" { variable "subnet_ids" {
description = "Private subnet IDs" description = "Private subnet IDs for ECS tasks"
type = list(string)
}
variable "public_subnet_ids" {
description = "Public subnet IDs for ALB"
type = list(string) type = list(string)
} }
@@ -273,7 +278,7 @@ resource "aws_ecs_service" "services" {
task_definition = aws_ecs_task_definition.services[each.key].arn task_definition = aws_ecs_task_definition.services[each.key].arn
desired_count = var.environment == "production" ? 3 : 1 desired_count = var.environment == "production" ? 3 : 1
launch_desired_count = "FARGATE" launch_type = "FARGATE"
network_configuration { network_configuration {
subnets = var.subnet_ids subnets = var.subnet_ids
@@ -307,7 +312,7 @@ resource "aws_lb" "main" {
internal = false internal = false
load_balancer_type = "application" load_balancer_type = "application"
security_groups = var.security_group_ids security_groups = var.security_group_ids
subnets = var.subnet_ids subnets = var.public_subnet_ids
tags = { tags = {
Name = "${var.cluster_name}-alb" Name = "${var.cluster_name}-alb"

View File

@@ -8,6 +8,16 @@ variable "project_name" {
type = string type = string
} }
variable "rds_endpoint" {
description = "RDS instance endpoint"
type = string
}
variable "elasticache_endpoint" {
description = "ElastiCache primary endpoint"
type = string
}
variable "secrets" { variable "secrets" {
description = "Secrets to store" description = "Secrets to store"
type = map(string) type = map(string)
@@ -29,15 +39,13 @@ resource "aws_secretsmanager_secret_version" "main" {
secret_id = aws_secretsmanager_secret.main.id secret_id = aws_secretsmanager_secret.main.id
secret_string = jsonencode(merge({ secret_string = jsonencode(merge({
DATABASE_URL = "postgresql://shieldai:${var.project_name}@${var.project_name}-${var.environment}-db.${data.aws_caller_identity.current.account_id}.us-east-1.rds.amazonaws.com:5432/shieldai" DATABASE_URL = "postgresql://shieldai:${var.project_name}@${var.rds_endpoint}:5432/shieldai"
REDIS_URL = "redis://${var.project_name}-${var.environment}-redis.${data.aws_caller_identity.current.account_id}.us-east-1.cache.amazonaws.com:6379" REDIS_URL = "redis://${var.elasticache_endpoint}:6379"
NODE_ENV = var.environment NODE_ENV = var.environment
LOG_LEVEL = var.environment == "production" ? "info" : "debug" LOG_LEVEL = var.environment == "production" ? "info" : "debug"
}, var.secrets)) }, var.secrets))
} }
data "aws_caller_identity" "current" {}
output "secrets_manager_arn" { output "secrets_manager_arn" {
description = "Secrets Manager ARN" description = "Secrets Manager ARN"
value = aws_secretsmanager_secret.main.arn value = aws_secretsmanager_secret.main.arn