James McCool
commited on
Commit
Β·
ef06cec
1
Parent(s):
7928ec7
Added QB force option for micro
Browse files- AWS_Load_Balancer_Setup_Guide.md +480 -0
- app.py +13 -0
- launch-template.json +17 -0
AWS_Load_Balancer_Setup_Guide.md
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AWS Load Balancer Setup Guide
|
| 2 |
+
## DFS Portfolio Manager - Production Deployment
|
| 3 |
+
|
| 4 |
+
### Overview
|
| 5 |
+
This guide documents the complete process of migrating from a single EC2 instance to a load-balanced architecture with 3 instances for improved performance, reliability, and cost efficiency.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## π Architecture Comparison
|
| 10 |
+
|
| 11 |
+
### Before (Single Instance)
|
| 12 |
+
- **Instance**: 1x m5.xlarge (4 vCPUs, 16GB RAM)
|
| 13 |
+
- **Cost**: ~$280/month
|
| 14 |
+
- **Issues**: Memory crashes, single point of failure
|
| 15 |
+
- **SSL**: Certbot/Let's Encrypt on instance
|
| 16 |
+
|
| 17 |
+
### After (Load Balanced)
|
| 18 |
+
- **Instances**: 3x m5.large (2 vCPUs, 8GB RAM each)
|
| 19 |
+
- **Load Balancer**: Application Load Balancer (ALB)
|
| 20 |
+
- **Cost**: ~$220/month (20% savings)
|
| 21 |
+
- **Benefits**: Better performance, auto-scaling, high availability
|
| 22 |
+
- **SSL**: AWS Certificate Manager (free, auto-renewing)
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## π Complete Setup Process
|
| 27 |
+
|
| 28 |
+
### Prerequisites
|
| 29 |
+
- AWS CLI configured with appropriate permissions
|
| 30 |
+
- Existing EC2 instance with working Streamlit application
|
| 31 |
+
- Domain managed by Cloudflare
|
| 32 |
+
- PowerShell (Windows) or Bash (Linux/Mac)
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
### Step 1: Gather Current Instance Information
|
| 37 |
+
|
| 38 |
+
```powershell
|
| 39 |
+
# Get current instance ID
|
| 40 |
+
$INSTANCE_ID = aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query 'Reservations[0].Instances[0].InstanceId' --output text
|
| 41 |
+
|
| 42 |
+
Write-Host "Current Instance ID: $INSTANCE_ID"
|
| 43 |
+
|
| 44 |
+
# List all running instances if needed
|
| 45 |
+
aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,Tags[?Key==`Name`].Value|[0],PublicIpAddress]' --output table
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
### Step 2: Create AMI from Current Instance
|
| 51 |
+
|
| 52 |
+
```powershell
|
| 53 |
+
# Create AMI snapshot of optimized setup
|
| 54 |
+
$AMI_ID = aws ec2 create-image --instance-id $INSTANCE_ID --name "DFS-Portfolio-Manager-$(Get-Date -Format 'yyyyMMdd-HHmm')" --description "DFS Portfolio Manager with memory optimizations and supervisord" --no-reboot --query 'ImageId' --output text
|
| 55 |
+
|
| 56 |
+
Write-Host "Creating AMI: $AMI_ID"
|
| 57 |
+
Write-Host "This will take 5-10 minutes..."
|
| 58 |
+
|
| 59 |
+
# Wait for AMI to be ready
|
| 60 |
+
aws ec2 wait image-available --image-ids $AMI_ID
|
| 61 |
+
Write-Host "AMI is ready!"
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
### Step 3: Extract Network Configuration
|
| 67 |
+
|
| 68 |
+
```powershell
|
| 69 |
+
# Get VPC, subnet, and security group info
|
| 70 |
+
$VPC_ID = aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[0].Instances[0].VpcId' --output text
|
| 71 |
+
|
| 72 |
+
$SUBNET_IDS = aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --query 'Subnets[*].SubnetId' --output text
|
| 73 |
+
|
| 74 |
+
$SECURITY_GROUP_ID = aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[0].Instances[0].SecurityGroups[0].GroupId' --output text
|
| 75 |
+
|
| 76 |
+
Write-Host "VPC ID: $VPC_ID"
|
| 77 |
+
Write-Host "Subnet IDs: $SUBNET_IDS"
|
| 78 |
+
Write-Host "Security Group: $SECURITY_GROUP_ID"
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
### Step 4: Create Target Group
|
| 84 |
+
|
| 85 |
+
```powershell
|
| 86 |
+
# Create target group for health checks
|
| 87 |
+
$TARGET_GROUP_ARN = aws elbv2 create-target-group --name "portfolio-manager-targets" --protocol HTTP --port 5000 --vpc-id $VPC_ID --health-check-path "/" --health-check-interval-seconds 30 --health-check-timeout-seconds 10 --healthy-threshold-count 2 --unhealthy-threshold-count 3 --query 'TargetGroups[0].TargetGroupArn' --output text
|
| 88 |
+
|
| 89 |
+
Write-Host "Target Group ARN: $TARGET_GROUP_ARN"
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
**Target Group Configuration:**
|
| 93 |
+
- **Port**: 5000 (Streamlit application port)
|
| 94 |
+
- **Health Check**: Every 30 seconds at root path "/"
|
| 95 |
+
- **Healthy Threshold**: 2 consecutive successful checks
|
| 96 |
+
- **Unhealthy Threshold**: 3 consecutive failed checks
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
### Step 5: Create Application Load Balancer
|
| 101 |
+
|
| 102 |
+
```powershell
|
| 103 |
+
# Create ALB
|
| 104 |
+
$ALB_ARN = aws elbv2 create-load-balancer --name "portfolio-manager-alb" --subnets $SUBNET_IDS.Split() --security-groups $SECURITY_GROUP_ID --scheme internet-facing --type application --ip-address-type ipv4 --query 'LoadBalancers[0].LoadBalancerArn' --output text
|
| 105 |
+
|
| 106 |
+
# Get ALB DNS name
|
| 107 |
+
$ALB_DNS = aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN --query 'LoadBalancers[0].DNSName' --output text
|
| 108 |
+
|
| 109 |
+
Write-Host "ALB ARN: $ALB_ARN"
|
| 110 |
+
Write-Host "ALB DNS: $ALB_DNS"
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
### Step 6: Create HTTP Listener
|
| 116 |
+
|
| 117 |
+
```powershell
|
| 118 |
+
# Create HTTP listener
|
| 119 |
+
aws elbv2 create-listener --load-balancer-arn $ALB_ARN --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN
|
| 120 |
+
|
| 121 |
+
Write-Host "HTTP Listener created successfully"
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
### Step 7: Create Launch Template
|
| 127 |
+
|
| 128 |
+
```powershell
|
| 129 |
+
# Create JSON content for launch template
|
| 130 |
+
$JsonContent = @"
|
| 131 |
+
{
|
| 132 |
+
"ImageId": "$AMI_ID",
|
| 133 |
+
"InstanceType": "m5.large",
|
| 134 |
+
"SecurityGroupIds": ["$SECURITY_GROUP_ID"],
|
| 135 |
+
"UserData": "$USER_DATA",
|
| 136 |
+
"TagSpecifications": [
|
| 137 |
+
{
|
| 138 |
+
"ResourceType": "instance",
|
| 139 |
+
"Tags": [
|
| 140 |
+
{
|
| 141 |
+
"Key": "Name",
|
| 142 |
+
"Value": "Portfolio-Manager-Instance"
|
| 143 |
+
}
|
| 144 |
+
]
|
| 145 |
+
}
|
| 146 |
+
]
|
| 147 |
+
}
|
| 148 |
+
"@
|
| 149 |
+
|
| 150 |
+
# Save launch template data
|
| 151 |
+
[System.IO.File]::WriteAllText("launch-template.json", $JsonContent)
|
| 152 |
+
|
| 153 |
+
# Create launch template
|
| 154 |
+
$LAUNCH_TEMPLATE_ID = aws ec2 create-launch-template --launch-template-name "portfolio-manager-template" --launch-template-data file://launch-template.json --query 'LaunchTemplate.LaunchTemplateId' --output text
|
| 155 |
+
|
| 156 |
+
Write-Host "Launch Template ID: $LAUNCH_TEMPLATE_ID"
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
**Launch Template Configuration:**
|
| 160 |
+
- **Instance Type**: m5.large (2 vCPUs, 8GB RAM)
|
| 161 |
+
- **User Data**: Automatically restarts Streamlit service on boot
|
| 162 |
+
- **Application Path**: `/home/ec2-user/AWS_Portfolio_Manager`
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
### Step 8: Create Auto Scaling Group
|
| 167 |
+
|
| 168 |
+
```powershell
|
| 169 |
+
# Convert subnet IDs to comma-separated format
|
| 170 |
+
$SUBNET_LIST = $SUBNET_IDS -replace '\s+', ','
|
| 171 |
+
|
| 172 |
+
# Create Auto Scaling Group
|
| 173 |
+
aws autoscaling create-auto-scaling-group --auto-scaling-group-name "portfolio-manager-asg" --launch-template LaunchTemplateId=$LAUNCH_TEMPLATE_ID,Version='$Latest' --min-size 3 --max-size 5 --desired-capacity 3 --target-group-arns $TARGET_GROUP_ARN --health-check-type ELB --health-check-grace-period 300 --vpc-zone-identifier $SUBNET_LIST
|
| 174 |
+
|
| 175 |
+
# Add tags to ASG
|
| 176 |
+
aws autoscaling create-or-update-tags --tags ResourceId=portfolio-manager-asg,ResourceType=auto-scaling-group,Key=Name,Value=Portfolio-Manager-ASG,PropagateAtLaunch=true
|
| 177 |
+
|
| 178 |
+
Write-Host "Auto Scaling Group created with 3 instances"
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
**Auto Scaling Group Configuration:**
|
| 182 |
+
- **Desired Capacity**: 3 instances
|
| 183 |
+
- **Minimum**: 3 instances
|
| 184 |
+
- **Maximum**: 5 instances
|
| 185 |
+
- **Health Check**: ELB-based (more reliable)
|
| 186 |
+
- **Grace Period**: 5 minutes for instances to be ready
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
### Step 9: Enable Sticky Sessions
|
| 191 |
+
|
| 192 |
+
```powershell
|
| 193 |
+
# Enable sticky sessions for session state management
|
| 194 |
+
aws elbv2 modify-target-group-attributes --target-group-arn $TARGET_GROUP_ARN --attributes Key=stickiness.enabled,Value=true Key=stickiness.lb_cookie.duration_seconds,Value=86400
|
| 195 |
+
|
| 196 |
+
Write-Host "Sticky sessions enabled - users stay on same instance for 24 hours"
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
**Why Sticky Sessions are Critical:**
|
| 200 |
+
- Streamlit stores user session state locally on each instance
|
| 201 |
+
- File uploads and user data must stay on the same instance
|
| 202 |
+
- Without sticky sessions: 400 errors on file uploads
|
| 203 |
+
- Duration: 24 hours (86400 seconds)
|
| 204 |
+
|
| 205 |
+
---
|
| 206 |
+
|
| 207 |
+
### Step 10: Set Up SSL Certificate
|
| 208 |
+
|
| 209 |
+
```powershell
|
| 210 |
+
# Request SSL certificate from AWS Certificate Manager
|
| 211 |
+
$CERT_ARN = aws acm request-certificate --domain-name "portfolio-manager.paydirtapps.com" --validation-method DNS --query 'CertificateArn' --output text
|
| 212 |
+
|
| 213 |
+
Write-Host "Certificate ARN: $CERT_ARN"
|
| 214 |
+
|
| 215 |
+
# Get DNS validation records
|
| 216 |
+
aws acm describe-certificate --certificate-arn $CERT_ARN --query 'Certificate.DomainValidationOptions[0].ResourceRecord.[Name,Value,Type]' --output table
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
**DNS Validation Process:**
|
| 220 |
+
1. Add the CNAME record shown in the output to Cloudflare
|
| 221 |
+
2. **Name**: `_[hash].portfolio-manager` (without the domain suffix)
|
| 222 |
+
3. **Value**: `_[hash].xlfgrmvvlj.acm-validations.aws.`
|
| 223 |
+
4. **Proxy Status**: DNS only (gray cloud, not orange)
|
| 224 |
+
5. Wait 5-15 minutes for validation
|
| 225 |
+
|
| 226 |
+
```powershell
|
| 227 |
+
# Check certificate status
|
| 228 |
+
aws acm describe-certificate --certificate-arn $CERT_ARN --query 'Certificate.Status' --output text
|
| 229 |
+
# Should show "ISSUED" when ready
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
---
|
| 233 |
+
|
| 234 |
+
### Step 11: Add HTTPS Listener
|
| 235 |
+
|
| 236 |
+
```powershell
|
| 237 |
+
# Add HTTPS listener once certificate is issued
|
| 238 |
+
aws elbv2 create-listener --load-balancer-arn $ALB_ARN --protocol HTTPS --port 443 --certificates CertificateArn=$CERT_ARN --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN
|
| 239 |
+
|
| 240 |
+
Write-Host "HTTPS listener added successfully!"
|
| 241 |
+
|
| 242 |
+
# Optional: Redirect HTTP to HTTPS
|
| 243 |
+
$HTTP_LISTENER_ARN = aws elbv2 describe-listeners --load-balancer-arn $ALB_ARN --query 'Listeners[?Port==`80`].ListenerArn' --output text
|
| 244 |
+
aws elbv2 modify-listener --listener-arn $HTTP_LISTENER_ARN --default-actions Type=redirect,RedirectConfig='{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'
|
| 245 |
+
|
| 246 |
+
Write-Host "HTTP to HTTPS redirect enabled!"
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
### Step 12: Update Cloudflare DNS
|
| 252 |
+
|
| 253 |
+
**Final DNS Configuration:**
|
| 254 |
+
1. Go to Cloudflare Dashboard β Your domain β DNS
|
| 255 |
+
2. Update the main record for `portfolio-manager.paydirtapps.com`:
|
| 256 |
+
- **Type**: CNAME
|
| 257 |
+
- **Name**: `portfolio-manager`
|
| 258 |
+
- **Target**: `[your-alb-dns-name].us-east-2.elb.amazonaws.com`
|
| 259 |
+
- **Proxy Status**: DNS only (gray cloud)
|
| 260 |
+
- **TTL**: Auto
|
| 261 |
+
|
| 262 |
+
---
|
| 263 |
+
|
| 264 |
+
## π§ Verification Commands
|
| 265 |
+
|
| 266 |
+
### Check Instance Health
|
| 267 |
+
```powershell
|
| 268 |
+
# Check Auto Scaling Group status
|
| 269 |
+
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names "portfolio-manager-asg" --query 'AutoScalingGroups[0].Instances[*].[InstanceId,LifecycleState,HealthStatus]' --output table
|
| 270 |
+
|
| 271 |
+
# Check target health
|
| 272 |
+
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State,TargetHealth.Description]' --output table
|
| 273 |
+
```
|
| 274 |
+
|
| 275 |
+
### Check Load Balancer Configuration
|
| 276 |
+
```powershell
|
| 277 |
+
# Verify listeners
|
| 278 |
+
aws elbv2 describe-listeners --load-balancer-arn $ALB_ARN --query 'Listeners[*].[Port,Protocol]' --output table
|
| 279 |
+
|
| 280 |
+
# Check load balancer attributes
|
| 281 |
+
aws elbv2 describe-load-balancer-attributes --load-balancer-arn $ALB_ARN --output table
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
---
|
| 285 |
+
|
| 286 |
+
## π Scaling Options
|
| 287 |
+
|
| 288 |
+
### Manual Scaling
|
| 289 |
+
```powershell
|
| 290 |
+
# Scale up to 5 instances during peak hours
|
| 291 |
+
aws autoscaling set-desired-capacity --auto-scaling-group-name "portfolio-manager-asg" --desired-capacity 5
|
| 292 |
+
|
| 293 |
+
# Scale down to 2 instances during off-hours
|
| 294 |
+
aws autoscaling set-desired-capacity --auto-scaling-group-name "portfolio-manager-asg" --desired-capacity 2
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
### Automatic CPU-Based Scaling
|
| 298 |
+
```powershell
|
| 299 |
+
# Set up auto-scaling based on CPU usage
|
| 300 |
+
aws autoscaling put-scaling-policy --auto-scaling-group-name "portfolio-manager-asg" --policy-name "scale-up" --policy-type "TargetTrackingScaling" --target-tracking-configuration '{
|
| 301 |
+
"TargetValue": 70.0,
|
| 302 |
+
"PredefinedMetricSpecification": {
|
| 303 |
+
"PredefinedMetricType": "ASGAverageCPUUtilization"
|
| 304 |
+
}
|
| 305 |
+
}'
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
### Scheduled Scaling
|
| 309 |
+
```powershell
|
| 310 |
+
# Scale up during peak hours (6 PM EST)
|
| 311 |
+
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name "portfolio-manager-asg" --scheduled-action-name "evening-scale-up" --recurrence "0 22 * * *" --desired-capacity 5
|
| 312 |
+
|
| 313 |
+
# Scale down during off hours (2 AM EST)
|
| 314 |
+
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name "portfolio-manager-asg" --scheduled-action-name "morning-scale-down" --recurrence "0 6 * * *" --desired-capacity 2
|
| 315 |
+
```
|
| 316 |
+
|
| 317 |
+
---
|
| 318 |
+
|
| 319 |
+
## π Instance Type Changes
|
| 320 |
+
|
| 321 |
+
### Upgrade to Larger Instances
|
| 322 |
+
```powershell
|
| 323 |
+
# Create new launch template version with m5.xlarge
|
| 324 |
+
aws ec2 create-launch-template-version --launch-template-id $LAUNCH_TEMPLATE_ID --launch-template-data '{
|
| 325 |
+
"ImageId": "'$AMI_ID'",
|
| 326 |
+
"InstanceType": "m5.xlarge",
|
| 327 |
+
"SecurityGroupIds": ["'$SECURITY_GROUP_ID'"],
|
| 328 |
+
"UserData": "'$USER_DATA'"
|
| 329 |
+
}'
|
| 330 |
+
|
| 331 |
+
# Update ASG to use new version
|
| 332 |
+
aws autoscaling update-auto-scaling-group --auto-scaling-group-name "portfolio-manager-asg" --launch-template LaunchTemplateId=$LAUNCH_TEMPLATE_ID,Version='$Latest'
|
| 333 |
+
|
| 334 |
+
# Force refresh to replace all instances
|
| 335 |
+
aws autoscaling start-instance-refresh --auto-scaling-group-name "portfolio-manager-asg"
|
| 336 |
+
```
|
| 337 |
+
|
| 338 |
+
---
|
| 339 |
+
|
| 340 |
+
## π° Cost Analysis
|
| 341 |
+
|
| 342 |
+
### Monthly Costs (US East 2)
|
| 343 |
+
| Component | Before | After | Savings |
|
| 344 |
+
|-----------|--------|-------|---------|
|
| 345 |
+
| **Compute** | 1x m5.xlarge: $280 | 3x m5.large: $207 | $73 |
|
| 346 |
+
| **Load Balancer** | None: $0 | ALB: $16 | -$16 |
|
| 347 |
+
| **SSL Certificate** | Let's Encrypt: $0 | ACM: $0 | $0 |
|
| 348 |
+
| **Total** | **$280/month** | **$223/month** | **$57/month (20% savings)** |
|
| 349 |
+
|
| 350 |
+
### Additional Benefits
|
| 351 |
+
- **Performance**: 6 total vCPUs vs 4 vCPUs (50% more processing power)
|
| 352 |
+
- **Reliability**: 3 instances vs 1 instance (high availability)
|
| 353 |
+
- **Memory**: 24GB total vs 16GB total (50% more memory)
|
| 354 |
+
- **Auto-scaling**: Can scale up to 5 instances during peak times
|
| 355 |
+
|
| 356 |
+
---
|
| 357 |
+
|
| 358 |
+
## π¨ Troubleshooting
|
| 359 |
+
|
| 360 |
+
### Common Issues
|
| 361 |
+
|
| 362 |
+
#### 1. File Upload 400 Errors
|
| 363 |
+
**Symptom**: First upload fails, retry succeeds
|
| 364 |
+
**Cause**: User routed to different instance without session state
|
| 365 |
+
**Solution**: Enable sticky sessions
|
| 366 |
+
```powershell
|
| 367 |
+
aws elbv2 modify-target-group-attributes --target-group-arn $TARGET_GROUP_ARN --attributes Key=stickiness.enabled,Value=true Key=stickiness.lb_cookie.duration_seconds,Value=86400
|
| 368 |
+
```
|
| 369 |
+
|
| 370 |
+
#### 2. Certificate "Not Secure" Warning
|
| 371 |
+
**Symptom**: Browser shows "Not Secure" despite valid certificate
|
| 372 |
+
**Cause**: Accessing load balancer DNS instead of domain name
|
| 373 |
+
**Solution**: Update Cloudflare DNS to point domain to load balancer
|
| 374 |
+
|
| 375 |
+
#### 3. Health Check Failures
|
| 376 |
+
**Symptom**: Instances show "unhealthy" in target group
|
| 377 |
+
**Cause**: Streamlit not responding on port 5000
|
| 378 |
+
**Solution**: Check supervisord status on instances
|
| 379 |
+
```bash
|
| 380 |
+
# SSH into instance
|
| 381 |
+
sudo supervisorctl status streamlit
|
| 382 |
+
sudo supervisorctl restart streamlit
|
| 383 |
+
```
|
| 384 |
+
|
| 385 |
+
#### 4. SSL Certificate Validation Stuck
|
| 386 |
+
**Symptom**: Certificate stays "PENDING_VALIDATION" for hours
|
| 387 |
+
**Cause**: DNS validation record not added correctly
|
| 388 |
+
**Solution**: Verify CNAME record in Cloudflare, ensure "DNS only" (gray cloud)
|
| 389 |
+
|
| 390 |
+
---
|
| 391 |
+
|
| 392 |
+
## π§ Maintenance Commands
|
| 393 |
+
|
| 394 |
+
### Update Application Code
|
| 395 |
+
```powershell
|
| 396 |
+
# Create new AMI with updated code
|
| 397 |
+
$NEW_AMI_ID = aws ec2 create-image --instance-id $UPDATED_INSTANCE_ID --name "DFS-Portfolio-Manager-$(Get-Date -Format 'yyyyMMdd-HHmm')" --description "Updated application code" --no-reboot --query 'ImageId' --output text
|
| 398 |
+
|
| 399 |
+
# Update launch template
|
| 400 |
+
aws ec2 create-launch-template-version --launch-template-id $LAUNCH_TEMPLATE_ID --launch-template-data '{"ImageId": "'$NEW_AMI_ID'"}'
|
| 401 |
+
|
| 402 |
+
# Refresh instances with new code
|
| 403 |
+
aws autoscaling start-instance-refresh --auto-scaling-group-name "portfolio-manager-asg"
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
### Monitor Performance
|
| 407 |
+
```powershell
|
| 408 |
+
# Check CPU utilization
|
| 409 |
+
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --dimensions Name=AutoScalingGroupName,Value=portfolio-manager-asg --statistics Average --start-time $(Get-Date).AddHours(-1) --end-time $(Get-Date) --period 300
|
| 410 |
+
|
| 411 |
+
# Check load balancer metrics
|
| 412 |
+
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount --dimensions Name=LoadBalancer,Value=app/portfolio-manager-alb/[load-balancer-id] --statistics Sum --start-time $(Get-Date).AddHours(-1) --end-time $(Get-Date) --period 300
|
| 413 |
+
```
|
| 414 |
+
|
| 415 |
+
### Cleanup Old Resources
|
| 416 |
+
```powershell
|
| 417 |
+
# Stop original single instance (once everything is working)
|
| 418 |
+
aws ec2 stop-instances --instance-ids $ORIGINAL_INSTANCE_ID
|
| 419 |
+
|
| 420 |
+
# Delete old AMIs (keep recent ones)
|
| 421 |
+
aws ec2 describe-images --owners self --query 'Images[?Name==`DFS-Portfolio-Manager*`].[ImageId,Name,CreationDate]' --output table
|
| 422 |
+
```
|
| 423 |
+
|
| 424 |
+
---
|
| 425 |
+
|
| 426 |
+
## π Key Variables Reference
|
| 427 |
+
|
| 428 |
+
Save these variables for future maintenance:
|
| 429 |
+
|
| 430 |
+
```powershell
|
| 431 |
+
# Core Infrastructure
|
| 432 |
+
$INSTANCE_ID = "i-xxxxxxxxx" # Original instance
|
| 433 |
+
$AMI_ID = "ami-xxxxxxxxx" # Application AMI
|
| 434 |
+
$VPC_ID = "vpc-xxxxxxxxx" # Virtual Private Cloud
|
| 435 |
+
$SECURITY_GROUP_ID = "sg-xxxxxxxxx" # Security Group
|
| 436 |
+
$SUBNET_IDS = "subnet-xxx subnet-yyy subnet-zzz" # Subnets
|
| 437 |
+
|
| 438 |
+
# Load Balancer
|
| 439 |
+
$ALB_ARN = "arn:aws:elasticloadbalancing:us-east-2:xxxx:loadbalancer/app/portfolio-manager-alb/xxxxxxxxxx"
|
| 440 |
+
$ALB_DNS = "portfolio-manager-alb-xxxxxxxxxx.us-east-2.elb.amazonaws.com"
|
| 441 |
+
$TARGET_GROUP_ARN = "arn:aws:elasticloadbalancing:us-east-2:xxxx:targetgroup/portfolio-manager-targets/xxxxxxxxxx"
|
| 442 |
+
|
| 443 |
+
# Auto Scaling
|
| 444 |
+
$LAUNCH_TEMPLATE_ID = "lt-xxxxxxxxx" # Launch Template
|
| 445 |
+
$ASG_NAME = "portfolio-manager-asg" # Auto Scaling Group
|
| 446 |
+
|
| 447 |
+
# SSL
|
| 448 |
+
$CERT_ARN = "arn:aws:acm:us-east-2:xxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
---
|
| 452 |
+
|
| 453 |
+
## π― Success Metrics
|
| 454 |
+
|
| 455 |
+
After completing this setup, you should have:
|
| 456 |
+
|
| 457 |
+
- β
**3 healthy instances** running your application
|
| 458 |
+
- β
**Load balancer** distributing traffic evenly
|
| 459 |
+
- β
**HTTPS/SSL** working with valid certificate
|
| 460 |
+
- β
**Sticky sessions** preventing upload errors
|
| 461 |
+
- β
**Auto-scaling** capability (3-5 instances)
|
| 462 |
+
- β
**High availability** across multiple availability zones
|
| 463 |
+
- β
**Cost savings** of ~20% compared to single large instance
|
| 464 |
+
- β
**Better performance** with 50% more total CPU and memory
|
| 465 |
+
|
| 466 |
+
---
|
| 467 |
+
|
| 468 |
+
## π Support
|
| 469 |
+
|
| 470 |
+
For issues or questions:
|
| 471 |
+
1. Check the troubleshooting section above
|
| 472 |
+
2. Verify all health checks are passing
|
| 473 |
+
3. Review AWS CloudWatch logs for detailed error information
|
| 474 |
+
4. Ensure Cloudflare DNS settings are correct
|
| 475 |
+
|
| 476 |
+
---
|
| 477 |
+
|
| 478 |
+
*Document created: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')*
|
| 479 |
+
*Architecture: AWS Application Load Balancer + Auto Scaling Group*
|
| 480 |
+
*Application: DFS Portfolio Manager (Streamlit)*
|
app.py
CHANGED
|
@@ -1477,6 +1477,10 @@ if selected_tab == 'Manage Portfolio':
|
|
| 1477 |
size_include = st.multiselect("Include sizes?", options=sorted(list(set(st.session_state['working_frame']['Size'].unique()))), default=[])
|
| 1478 |
else:
|
| 1479 |
size_include = []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1480 |
|
| 1481 |
submitted_col, export_col = st.columns(2)
|
| 1482 |
st.info("Portfolio Button applies to your overall Portfolio, Export button applies to your Custom Export")
|
|
@@ -1600,6 +1604,15 @@ if selected_tab == 'Manage Portfolio':
|
|
| 1600 |
|
| 1601 |
if size_include:
|
| 1602 |
parsed_frame = parsed_frame[parsed_frame['Size'].isin(size_include)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
st.session_state['working_frame'] = parsed_frame.sort_values(by='median', ascending=False).reset_index(drop=True)
|
| 1604 |
st.session_state['export_merge'] = st.session_state['working_frame'].copy()
|
| 1605 |
elif exp_submitted:
|
|
|
|
| 1477 |
size_include = st.multiselect("Include sizes?", options=sorted(list(set(st.session_state['working_frame']['Size'].unique()))), default=[])
|
| 1478 |
else:
|
| 1479 |
size_include = []
|
| 1480 |
+
if sport_var == 'NFL':
|
| 1481 |
+
qb_force = st.selectbox("Force QB Stacks?", options=['No', 'Yes'], index=0)
|
| 1482 |
+
else:
|
| 1483 |
+
qb_force = 'No'
|
| 1484 |
|
| 1485 |
submitted_col, export_col = st.columns(2)
|
| 1486 |
st.info("Portfolio Button applies to your overall Portfolio, Export button applies to your Custom Export")
|
|
|
|
| 1604 |
|
| 1605 |
if size_include:
|
| 1606 |
parsed_frame = parsed_frame[parsed_frame['Size'].isin(size_include)]
|
| 1607 |
+
|
| 1608 |
+
if qb_force == 'Yes':
|
| 1609 |
+
if type_var == 'Classic':
|
| 1610 |
+
# Get team for the first player column for each lineup
|
| 1611 |
+
team_frame = parsed_frame.iloc[:, 0].map(st.session_state['map_dict']['team_map'])
|
| 1612 |
+
|
| 1613 |
+
# Create mask where the first player's team matches the Stack column
|
| 1614 |
+
include_mask = team_frame == parsed_frame['Stack']
|
| 1615 |
+
parsed_frame = parsed_frame[include_mask]
|
| 1616 |
st.session_state['working_frame'] = parsed_frame.sort_values(by='median', ascending=False).reset_index(drop=True)
|
| 1617 |
st.session_state['export_merge'] = st.session_state['working_frame'].copy()
|
| 1618 |
elif exp_submitted:
|
launch-template.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ImageId": "ami-09659b1d5af762870",
|
| 3 |
+
"InstanceType": "m5.large",
|
| 4 |
+
"SecurityGroupIds": ["sg-0af436f2cd59a1ea4"],
|
| 5 |
+
"UserData": "IyEvYmluL2Jhc2gKY2QgL2hvbWUvZWMyLXVzZXIvQVdTX1BvcnRmb2xpb19NYW5hZ2VyCnN1ZG8gc3VwZXJ2aXNvcmN0bCByZXN0YXJ0IHN0cmVhbWxpdA==",
|
| 6 |
+
"TagSpecifications": [
|
| 7 |
+
{
|
| 8 |
+
"ResourceType": "instance",
|
| 9 |
+
"Tags": [
|
| 10 |
+
{
|
| 11 |
+
"Key": "Name",
|
| 12 |
+
"Value": "Portfolio-Manager-Instance"
|
| 13 |
+
}
|
| 14 |
+
]
|
| 15 |
+
}
|
| 16 |
+
]
|
| 17 |
+
}
|