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>
This commit is contained in:
208
docs/PERSISTENT_ALB.md
Normal file
208
docs/PERSISTENT_ALB.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user