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 |
+
}
|