Files
spicy-automation/docs/PERSISTENT_ALB.md
Ryan Wilson 68684df471 Initial commit: Spicy CDK automation framework
Jenkins shared library and CDK constructs for AWS infrastructure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-11-18 22:21:00 -08:00

209 lines
7.9 KiB
Markdown

# Persistent ALB Pattern (bg-common)
The persistent ALB pattern ensures that ALB DNS names remain constant even when service stacks are deleted and recreated. This matches the legacy `bg-common` pattern.
## How It Works
The bg-common ALB stack is **always created separately**. Services reference it via `albLoadBalancerArn`:
1. **bg-common ALB stack is deployed separately** (`{serviceStackName}-alb` or via `spicyALB`)
2. **The ALB stack persists** across service deployments
3. **Service stacks reference the existing ALB** via `albLoadBalancerArn` (maps to `existingALB` in construct)
4. **ALB DNS name stays constant** even when service stacks are deleted/recreated
### Stack Naming
- **ALB Stack**: `{serviceStackName}-alb` (shared between blue/green)
- **Service Stack (Rolling)**: `{serviceStackName}`
- **Service Stack (Blue/Green)**: `{serviceStackName}-blue` and `{serviceStackName}-green`
For blue/green deployments, both blue and green services share the same ALB stack.
## Usage
### Rolling Deployment (Non-Blue/Green)
```groovy
@Library(["spicy-automation@main"]) _
spicyECSService(
jenkinsAwsCredentialsId: "aws-credentials",
region: "ca-central-1",
stackName: "my-api-dev",
serviceName: "my-api",
// ... cluster/VPC config ...
// Container
image: "nexus.kodeniks.com/docker-hosted/my-api:latest",
containerPort: 3000,
// Reference bg-common ALB (always created separately) - auto-imports from ALB stack
albStackName: "my-api-dev-alb", // Auto-imports ALB ARN and listener ARNs
// Routing
hostHeader: "api-dev.example.com",
priority: 100,
// Tags
ownerTag: "MyTeam",
productTag: "my-product",
componentTag: "api",
environment: "dev",
)
```
**What happens:**
1. Pipeline deploys `my-api-dev-alb` stack (ALB)
2. Pipeline gets ALB outputs (DNS, listener ARNs)
3. Pipeline deploys `my-api-dev` stack (service) referencing the ALB
4. ALB DNS name stays constant: `my-api-dev-alb-123456789.ca-central-1.elb.amazonaws.com`
### Blue/Green Deployment
```groovy
@Library(["spicy-automation@main"]) _
spicyECSService(
jenkinsAwsCredentialsId: "aws-credentials",
region: "ca-central-1",
stackName: "my-api-prod", // Base name (blue/green suffixes added automatically)
serviceName: "my-api",
// ... cluster/VPC config ...
// Container
image: "nexus.kodeniks.com/docker-hosted/my-api:latest",
containerPort: 3000,
// Blue/Green
blueGreen: true,
activeHostname: "api.example.com",
inactiveHostname: "inactive-api.example.com",
bgHostedZoneId: "Z1234567890", // Route53 hosted zone
// Reference bg-common ALB (always created separately) - auto-imports from ALB stack
albStackName: "my-api-prod-alb", // Auto-imports ALB ARN and listener ARNs
healthCheckPath: "/health",
// Tags
ownerTag: "MyTeam",
productTag: "my-product",
componentTag: "api",
environment: "prod",
)
```
**What happens:**
1. Pipeline deploys `my-api-prod-alb` stack (ALB) - **only once, shared by blue/green**
2. Pipeline gets ALB outputs (or uses `albStackName` for auto-import)
3. Pipeline deploys `my-api-prod-green` stack (inactive service) referencing the ALB
4. Service creates listener rules on **both** HTTP and HTTPS listeners (if both available)
5. Pipeline swaps hostname routing via ALB listener rule priorities (no DNS changes)
6. ALB DNS name stays constant across all deployments
**How Blue/Green Routing Works:**
- **DNS Records**: Both `api.example.com` and `inactive-api.example.com` DNS records are created and always point to the same ALB
- **Traffic Routing**: Controlled by ALB listener rule priorities, not DNS:
- Active service: Listener rule with priority 100 for `api.example.com`
- Inactive service: Listener rule with priority 200 for `inactive-api.example.com`
- **Swapping**: When swapping, only the listener rule priorities are updated (via CDK deployment). DNS records never change.
- **Result**: Zero-downtime swaps in ~30 seconds (just ALB rule updates, no DNS propagation delays)
## Benefits
1. **Static DNS**: ALB DNS name never changes (unless ALB stack is deleted)
2. **No DNS Updates**: Cloudflare/Route53 records set once and never need updating
3. **Faster Deployments**: ALB stack only updates when ALB config changes
4. **Cost Efficient**: ALB persists, avoiding recreation costs
## ALB Stack Lifecycle
- **Created**: Deployed separately via `spicyALB` pipeline function or manually
- **Updated**: When ALB configuration changes (certificate, scheme, etc.)
- **Deleted**: Only when explicitly destroyed (not deleted with service stacks)
## Stack Outputs
The ALB stack exports these outputs (used by service stacks via CloudFormation imports):
| Output Key | Description | Export Name |
| ------------------ | ------------------ | -------------------------------------------- |
| `LoadBalancerDNS` | ALB DNS name | `{stackName}-internet-facing-url` |
| `LoadBalancerArn` | ALB ARN | `{stackName}-internet-facing-arn` |
| `HTTPListenerArn` | HTTP listener ARN | `{stackName}-internet-facing-http-listener` |
| `HTTPSListenerArn` | HTTPS listener ARN | `{stackName}-internet-facing-https-listener` |
**Minimal Props:** When using `albStackName` in service stack, these are auto-imported via CloudFormation `Fn::ImportValue`. The service creates listener rules on **both** HTTP and HTTPS listeners (if both are available), matching the old template behavior.
| `SecurityGroupId` | ALB security group ID | `{stackName}-internet-facing-security-group` |
## Deploying the bg-common ALB Stack
The bg-common ALB stack must be deployed separately before deploying services:
```groovy
@Library(["spicy-automation@main"]) _
// Deploy bg-common ALB stack
spicyALB(
jenkinsAwsCredentialsId: "aws-credentials",
region: "ca-central-1",
stackName: "my-api-dev-alb",
vpcId: "vpc-12345678",
availabilityZones: "ca-central-1a,ca-central-1b",
subnetIds: "subnet-xxx,subnet-yyy",
scheme: "internet-facing",
certificateArn: "arn:aws:acm:...",
ownerTag: "MyTeam",
productTag: "my-product",
componentTag: "alb",
environment: "dev",
)
// Then deploy service referencing the ALB
spicyECSService(
// ... config ...
albLoadBalancerArn: "arn:aws:elasticloadbalancing:...", // From ALB stack outputs
albHttpsListenerArn: "arn:aws:elasticloadbalancing:...", // From ALB stack outputs
)
```
## Comparison with Legacy bg-common
| Feature | Legacy bg-common | New Persistent ALB |
| ------------------- | ---------------------- | ------------------------------------------ |
| **Stack Name** | `{envName}-bg-common` | `{serviceStackName}-alb` |
| **Deployment** | Separate pipeline step | Separate pipeline step (via `spicyALB`) |
| **Blue/Green** | Shared ALB | Shared ALB |
| **DNS Management** | Manual | Automatic (Route53) or Manual (Cloudflare) |
| **Stack Lifecycle** | Manual | Manual (deployed separately) |
## Troubleshooting
### ALB Stack Not Found
If the ALB stack doesn't exist, the pipeline will create it automatically. If you see errors:
1. Check the ALB stack name: `{serviceStackName}-alb`
2. Verify the stack exists: `aws cloudformation describe-stacks --stack-name {stackName}-alb`
3. Check stack outputs: `aws cloudformation describe-stacks --stack-name {stackName}-alb --query 'Stacks[0].Outputs'`
### ALB DNS Name Changed
This should never happen with the persistent ALB pattern. If it does:
1. Check if the ALB stack was deleted/recreated
2. Verify the ALB stack name is consistent
3. Check CloudFormation stack events for ALB replacement
### Service Can't Find ALB
If the service stack can't reference the ALB:
1. Verify ALB stack outputs are available
2. Check that `albLoadBalancerArn` is set in the service context
3. Verify the ALB ARN is correct