- Fix HTTP listener in spicy-alb.ts missing default action when no certificate is provided, which would cause CDK synth to fail - Auto-import numberOfAzs from VPC stack exports (NumberOfAZs) in cluster, service, and ALB stacks when not provided via context - Fix CDK_SYNTH_EXAMPLES.md ALB examples using raw vpcId/subnetIds that don't match the actual fromContext() implementation (requires clusterName) - Fix docs overstating "only clusterName required" to list actual required params - Remove package-lock.json and add to .gitignore (project uses pnpm) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
927 lines
39 KiB
Markdown
927 lines
39 KiB
Markdown
# CDK Synth Command Examples
|
|
|
|
This document provides comprehensive examples of `pnpm exec cdk synth` commands for all stack types and configurations.
|
|
|
|
## Prerequisites
|
|
|
|
```bash
|
|
cd spicy-automation
|
|
pnpm install
|
|
pnpm run build
|
|
```
|
|
|
|
## Common Warnings
|
|
|
|
The following warnings are **expected** and **non-critical**:
|
|
|
|
1. **Route Table ID Warnings**: When importing VPCs, route table IDs are optional. These warnings can be ignored.
|
|
|
|
```
|
|
[Warning] No routeTableId was provided to the subnet 'subnet-xxx'
|
|
```
|
|
|
|
2. **Construct Metadata Warnings**: CDK internal warnings about managed policies. These are informational only.
|
|
|
|
```
|
|
[Warning] Failed to add construct metadata for node [ExecutionRole]
|
|
```
|
|
|
|
## CloudFormation Import Pattern (Minimal Props)
|
|
|
|
The CDK implementation supports the same CloudFormation import pattern as the old Ansible/CloudFormation templates:
|
|
|
|
- **Minimal props**: Only `clusterName` required for most deployments
|
|
- **Auto-imports**: VPC ID, listener ARNs, and logs bucket auto-import from CloudFormation exports
|
|
- **No defaults**: All required values must exist in exports or be provided explicitly - fails early if missing
|
|
|
|
**Export chain:**
|
|
|
|
- VPC stack exports: `${vpcStackName}-VPCID`, `${vpcStackName}-VPCCIDR`, `${vpcStackName}-PrivateSubnetA1ID`, etc.
|
|
- Cluster stack exports: `${clusterName}-VPC`, `${clusterName}-internet-facing-https-listener`, `${clusterName}-logs-s3-bucket`
|
|
- ALB stack exports: `${albStackName}-internet-facing-arn`, `${albStackName}-internet-facing-https-listener`, `${albStackName}-internet-facing-http-listener`
|
|
|
|
**Important:** The implementation **fails early** if required exports don't exist. There are no defaults - either the data exists in CloudFormation exports, or the synth fails with a clear error message.
|
|
|
|
## VPC Stack
|
|
|
|
### Minimal VPC (Default Configuration)
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=vpc \
|
|
-c stackName=my-vpc \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=vpc
|
|
```
|
|
|
|
**Creates:**
|
|
|
|
- VPC with default CIDR (172.1.0.0/16)
|
|
- 4 Availability Zones
|
|
- Public subnets (one per AZ)
|
|
- Private subnets Type 1 (one per AZ with NAT Gateways)
|
|
- Private subnets Type 2 (one per AZ with NACLs)
|
|
- S3 Gateway Endpoint
|
|
|
|
### Custom VPC Configuration
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=vpc \
|
|
-c stackName=production-vpc \
|
|
-c ownerTag=Platform \
|
|
-c productTag=spicy \
|
|
-c componentTag=network \
|
|
-c vpcCidr=10.0.0.0/16 \
|
|
-c numberOfAzs=3 \
|
|
-c availabilityZones=ca-central-1a,ca-central-1b,ca-central-1c \
|
|
-c createPrivateSubnets=true \
|
|
-c createAdditionalPrivateSubnets=true
|
|
```
|
|
|
|
### Development VPC (2 AZs, No NACL Subnets)
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=vpc \
|
|
-c stackName=dev-vpc \
|
|
-c ownerTag=Dev \
|
|
-c productTag=myapp \
|
|
-c componentTag=vpc \
|
|
-c numberOfAzs=2 \
|
|
-c createAdditionalPrivateSubnets=false
|
|
```
|
|
|
|
### Public-Only VPC (No NAT Gateways)
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=vpc \
|
|
-c stackName=public-vpc \
|
|
-c ownerTag=Dev \
|
|
-c productTag=myapp \
|
|
-c componentTag=vpc \
|
|
-c createPrivateSubnets=false
|
|
```
|
|
|
|
---
|
|
|
|
## ECS Cluster Stack
|
|
|
|
### Minimal Cluster (No Load Balancers) - Using CloudFormation Imports
|
|
|
|
**Minimal required props:** `vpcStackName` and tags (`ownerTag`, `productTag`). All VPC details auto-import from VPC stack exports, including `numberOfAzs`.
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-cluster \
|
|
-c stackName=my-cluster \
|
|
-c vpcStackName=my-vpc \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=ecs-cluster \
|
|
-c environment=dev
|
|
```
|
|
|
|
**What auto-imports from VPC stack:**
|
|
|
|
- VPC ID from `${vpcStackName}-VPCID`
|
|
- VPC CIDR from `${vpcStackName}-VPCCIDR`
|
|
- Number of AZs from `${vpcStackName}-NumberOfAZs` (or override with `-c numberOfAzs=N`)
|
|
- Private subnet IDs from `${vpcStackName}-PrivateSubnetA1ID`, `${vpcStackName}-PrivateSubnetB1ID`, etc.
|
|
- Public subnet IDs from `${vpcStackName}-PublicSubnetAID`, `${vpcStackName}-PublicSubnetBID`, etc. (if `createExternalLoadBalancer=true`)
|
|
- Availability zones auto-derived from region and number of AZs
|
|
|
|
### Cluster with External ALB - Using CloudFormation Imports
|
|
|
|
**Minimal props:**
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-cluster \
|
|
-c stackName=my-cluster \
|
|
-c vpcStackName=my-vpc \
|
|
-c createExternalLoadBalancer=true \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=ecs-cluster \
|
|
-c environment=dev
|
|
```
|
|
|
|
**What auto-imports:**
|
|
|
|
- All VPC details (VPC ID, CIDR, subnets, AZs) from VPC stack exports
|
|
- Public subnets auto-imported for external ALB when `createExternalLoadBalancer=true`
|
|
|
|
### Cluster with Both ALBs
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-cluster \
|
|
-c stackName=production-cluster \
|
|
-c vpcStackName=production-vpc \
|
|
-c createExternalLoadBalancer=true \
|
|
-c createInternalLoadBalancer=true \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c ownerTag=Platform \
|
|
-c productTag=spicy \
|
|
-c componentTag=ecs-cluster \
|
|
-c environment=prod
|
|
```
|
|
|
|
### Cluster with Spot Instances
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-cluster \
|
|
-c stackName=dev-cluster \
|
|
-c vpcStackName=dev-vpc \
|
|
-c spotEnabled=true \
|
|
-c onDemandPercentage=20 \
|
|
-c spotAllocationStrategy=capacity-optimized \
|
|
-c instanceType=m5a.large \
|
|
-c minClusterSize=2 \
|
|
-c maxClusterSize=4 \
|
|
-c ownerTag=Dev \
|
|
-c productTag=myapp \
|
|
-c componentTag=ecs-cluster \
|
|
-c environment=dev
|
|
```
|
|
|
|
### Cluster with Fargate Enabled
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-cluster \
|
|
-c stackName=hybrid-cluster \
|
|
-c vpcStackName=my-vpc \
|
|
-c enableFargate=true \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=ecs-cluster \
|
|
-c environment=dev
|
|
```
|
|
|
|
---
|
|
|
|
---
|
|
|
|
## ALB Stack (Persistent bg-common ALB)
|
|
|
|
The ALB stack creates a persistent Application Load Balancer that can be shared across multiple service deployments. This is the "bg-common" pattern where the ALB persists even when service stacks are deleted and recreated.
|
|
|
|
**Important:** The ALB stack must be deployed **before** service stacks that reference it. The ALB ARN and listener ARNs from the ALB stack outputs are required as inputs to the service stack.
|
|
|
|
### Internet-Facing ALB
|
|
|
|
**Uses CloudFormation imports:** Only `clusterName` (or `clusterStackName`) and `numberOfAzs` required. VPC details and subnets are auto-imported from cluster/VPC stack exports. `numberOfAzs` is also auto-imported from the VPC stack if not provided.
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=my-service-alb \
|
|
-c clusterName=my-cluster \
|
|
-c scheme=internet-facing \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c logsBucketName=my-cluster-logs \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=alb \
|
|
-c environment=dev
|
|
```
|
|
|
|
**What auto-imports:**
|
|
|
|
- VPC stack name from `${clusterStackName}-VPCStackName` (cluster stack export)
|
|
- VPC ID from `${clusterStackName}-VPC` (cluster stack export)
|
|
- VPC CIDR from `${vpcStackName}-VPCCIDR` (VPC stack export)
|
|
- Number of AZs from `${vpcStackName}-NumberOfAZs` (VPC stack export)
|
|
- Public subnet IDs from `${vpcStackName}-PublicSubnetAID`, etc. (for internet-facing)
|
|
- Private subnet IDs from `${vpcStackName}-PrivateSubnetA1ID`, etc. (for internal)
|
|
|
|
**Creates:**
|
|
|
|
- Application Load Balancer (internet-facing)
|
|
- Security Group for ALB
|
|
- HTTP Listener (port 80) with HTTP→HTTPS redirect
|
|
- HTTPS Listener (port 443) with SSL certificate
|
|
- ALB access logs to S3 (if logsBucketName provided)
|
|
|
|
### Internal ALB
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=my-service-alb \
|
|
-c clusterName=my-cluster \
|
|
-c scheme=internal \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c logsBucketName=my-cluster-logs \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=alb \
|
|
-c environment=dev
|
|
```
|
|
|
|
### ALB with Blue/Green DNS
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=my-service-alb \
|
|
-c clusterName=my-cluster \
|
|
-c scheme=internet-facing \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c logsBucketName=my-cluster-logs \
|
|
-c activeHostname=api.example.com \
|
|
-c inactiveHostname=inactive-api.example.com \
|
|
-c bgHostedZoneId=Z1234567890 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=alb \
|
|
-c environment=prod
|
|
```
|
|
|
|
**Creates:**
|
|
|
|
- All resources from internet-facing ALB example
|
|
- Route53 A record for active hostname (api.example.com)
|
|
- Route53 A record for inactive hostname (inactive-api.example.com)
|
|
|
|
---
|
|
|
|
## ECS Service Stack
|
|
|
|
### Minimal Service (No Load Balancer) - Using CloudFormation Imports
|
|
|
|
**Minimal required props:** `clusterName`, `serviceName`, `image`, `containerPort`, and tags (`ownerTag`, `productTag`). All VPC info auto-imports from cluster/VPC stack exports.
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
**What auto-imports:**
|
|
|
|
- VPC stack name from `${clusterStackName}-VPCStackName` (cluster stack export)
|
|
- VPC ID from `${clusterStackName}-VPC` (cluster stack export)
|
|
- VPC CIDR from `${vpcStackName}-VPCCIDR` (VPC stack export, using imported VPC stack name)
|
|
- Number of AZs from `${vpcStackName}-NumberOfAZs` (VPC stack export)
|
|
- Private subnets from `${vpcStackName}-PrivateSubnetA1ID`, etc. (VPC stack exports)
|
|
- Logs bucket from `${clusterStackName}-logs-s3-bucket` (cluster stack export)
|
|
|
|
**Note:** The service stack imports the VPC stack name from the cluster stack export `${clusterStackName}-VPCStackName`, then uses that to import all VPC details (CIDR, subnets, AZs) from the VPC stack exports. `numberOfAzs` can be provided explicitly via `-c numberOfAzs=N` but will auto-import from the VPC stack if omitted.
|
|
|
|
### Service with Cluster ALB - Using CloudFormation Imports
|
|
|
|
**Minimal props:** Listener ARN auto-imports from cluster stack.
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c hostHeader=api.example.com \
|
|
-c priority=100 \
|
|
-c useExternalALB=true \
|
|
-c healthCheckPath=/health \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
**What auto-imports:**
|
|
|
|
- VPC stack name from `${clusterStackName}-VPCStackName` (cluster stack export)
|
|
- VPC ID from `${clusterStackName}-VPC` (cluster stack export)
|
|
- VPC CIDR from `${vpcStackName}-VPCCIDR` (VPC stack export, using imported VPC stack name)
|
|
- Private subnets from `${vpcStackName}-PrivateSubnetA1ID`, etc. (VPC stack exports)
|
|
- Number of AZs from `${vpcStackName}-NumberOfAZs` (VPC stack export)
|
|
- External listener ARN from `${clusterStackName}-internet-facing-https-listener` (when `useExternalALB=true`)
|
|
- Internal listener ARN from `${clusterStackName}-internal-https-listener` (when `useInternalALB=true`)
|
|
|
|
**Explicit listener ARN (backward compatible):**
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c vpcId=vpc-12345678 \
|
|
-c vpcCidrBlock=10.0.0.0/16 \
|
|
-c availabilityZones=ca-central-1a,ca-central-1b \
|
|
-c privateSubnetIds=subnet-aaa,subnet-bbb \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c hostHeader=api.example.com \
|
|
-c priority=100 \
|
|
-c useExternalALB=true \
|
|
-c externalListenerArn=arn:aws:elasticloadbalancing:ca-central-1:123456789:listener/app/test-alb/1234567890123456/1234567890123456 \
|
|
-c healthCheckPath=/health \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
### Service with bg-common ALB (Default - useClusterAlb=false)
|
|
|
|
**Important:** When using bg-common ALB, the ALB and Route53 DNS records are created in a **separate ALB stack**, not in the service stack. The service stack only creates listener rules on the existing ALB.
|
|
|
|
**Step 1: Deploy ALB Stack** (creates ALB and Route53 records)
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=my-service-alb \
|
|
-c vpcId=vpc-12345678 \
|
|
-c vpcCidrBlock=10.0.0.0/16 \
|
|
-c availabilityZones=ca-central-1a,ca-central-1b \
|
|
-c subnetIds=subnet-xxx,subnet-yyy \
|
|
-c scheme=internet-facing \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c logsBucketName=my-cluster-logs \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=alb \
|
|
-c environment=dev
|
|
```
|
|
|
|
**Step 2: Deploy Service Stack** (creates listener rules on existing ALB)
|
|
|
|
**Minimal props using CloudFormation imports:**
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c albStackName=my-service-alb \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c healthCheckPath=/health \
|
|
-c hostHeader=api.example.com \
|
|
-c priority=100 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
**What auto-imports:**
|
|
|
|
- VPC ID from `${clusterName}-VPC` (cluster stack export)
|
|
- VPC CIDR from `${clusterName}-VPCCIDR` (cluster stack export)
|
|
- Private subnets from `${clusterName}-PrivateSubnetA1ID`, etc. (cluster stack exports)
|
|
- Availability zones from `${clusterName}-AvailabilityZones` (cluster stack export)
|
|
- ALB ARN from `${albStackName}-internet-facing-arn` (ALB stack export)
|
|
- HTTPS listener ARN from `${albStackName}-internet-facing-https-listener` (ALB stack export)
|
|
- HTTP listener ARN from `${albStackName}-internet-facing-http-listener` (ALB stack export)
|
|
- Logs bucket from `${clusterName}-logs-s3-bucket` (cluster stack export)
|
|
|
|
**Note:** `vpcStackName` is optional - the service stack prefers importing from the cluster stack. The ALB stack name (`albStackName`) is automatically derived from the service stackName (e.g., `my-service-dev` → `my-service-dev-alb`).
|
|
|
|
**Note:** The service creates listener rules on **both** HTTP and HTTPS listeners (like the old template), so both listeners are imported if available.
|
|
|
|
**Note:** In the Jenkins pipeline, the ALB stack is created automatically. For manual CDK synth, you must deploy the ALB stack first and use `albStackName` for auto-imports.
|
|
|
|
### Service with bg-common ALB (Internal)
|
|
|
|
**Step 1: Deploy Internal ALB Stack**
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=my-service-alb \
|
|
-c vpcId=vpc-12345678 \
|
|
-c vpcCidrBlock=10.0.0.0/16 \
|
|
-c availabilityZones=ca-central-1a,ca-central-1b \
|
|
-c subnetIds=subnet-aaa,subnet-bbb \
|
|
-c scheme=internal \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c logsBucketName=my-cluster-logs \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=alb \
|
|
-c environment=dev
|
|
```
|
|
|
|
**Step 2: Deploy Service Stack**
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c albStackName=my-service-alb \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c healthCheckPath=/health \
|
|
-c hostHeader=api-internal.example.com \
|
|
-c priority=100 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
### Service with bg-common ALB + Blue/Green Deployment
|
|
|
|
**Note:** Blue/green deployments are handled automatically by the Jenkins pipeline. The pipeline:
|
|
|
|
1. Automatically creates the bg-common ALB stack (if it doesn't exist)
|
|
2. Deploys to the inactive color (blue or green)
|
|
3. Runs `blueGreenTest` against the inactive hostname
|
|
4. Waits for deployment to stabilize
|
|
5. Swaps hostnames to make the new deployment active
|
|
6. Runs `smokeTest` against the new active hostname
|
|
7. Keeps the old stack for rollback window (default 2 hours)
|
|
|
|
**For manual CDK synth, deploy both stacks:**
|
|
|
|
**Step 1: Deploy ALB Stack with Blue/Green DNS**
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=my-service-alb \
|
|
-c vpcId=vpc-12345678 \
|
|
-c vpcCidrBlock=10.0.0.0/16 \
|
|
-c availabilityZones=ca-central-1a,ca-central-1b \
|
|
-c subnetIds=subnet-xxx,subnet-yyy \
|
|
-c scheme=internet-facing \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
|
|
-c logsBucketName=my-cluster-logs \
|
|
-c activeHostname=api.example.com \
|
|
-c inactiveHostname=inactive-api.example.com \
|
|
-c bgHostedZoneId=Z1234567890 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=alb \
|
|
-c environment=prod
|
|
```
|
|
|
|
**Step 2: Deploy Service Stack (Blue or Green)**
|
|
|
|
After deploying the ALB stack, use `albStackName` for auto-imports:
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service-blue \
|
|
-c clusterName=my-cluster \
|
|
-c albStackName=my-service-alb \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c activeHostname=api.example.com \
|
|
-c inactiveHostname=inactive-api.example.com \
|
|
-c bgHostedZoneId=Z1234567890 \
|
|
-c healthCheckPath=/health \
|
|
-c hostHeader=api.example.com \
|
|
-c priority=100 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=prod
|
|
```
|
|
|
|
**How blue/green works:**
|
|
|
|
1. ALB stack creates DNS records for both active and inactive hostnames (both point to the same ALB)
|
|
2. Pipeline determines current active color from SSM Parameter Store (defaults to 'blue')
|
|
3. Pipeline deploys new version to inactive color (green if blue is active, blue if green is active)
|
|
4. Pipeline runs `blueGreenTest` hook against inactive hostname
|
|
5. Pipeline swaps hostnames by updating listener rule priorities
|
|
6. Pipeline runs `smokeTest` hook against new active hostname
|
|
7. Old stack is retained for rollback window (configurable via `rollbackWindowHours`)
|
|
8. Rollback is instant - just swaps hostnames back (no new deployment needed)
|
|
|
|
### Service with Auto-Scaling
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c desiredCount=2 \
|
|
-c minCapacity=2 \
|
|
-c maxCapacity=20 \
|
|
-c targetCpuUtilization=70 \
|
|
-c targetMemoryUtilization=80 \
|
|
-c targetRequestsPerTarget=1000 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
### Service with Environment Variables and Secrets
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c environment_vars='{"NODE_ENV":"production","LOG_LEVEL":"info"}' \
|
|
-c secrets='{"DATABASE_URL":"arn:aws:secretsmanager:ca-central-1:123456789:secret:db-url","API_KEY":"arn:aws:secretsmanager:ca-central-1:123456789:secret:api-key"}' \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
### Service with Mixed Capacity Strategy (EC2 + Fargate)
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=80 \
|
|
-c cpu=512 \
|
|
-c memory=1024 \
|
|
-c capacityProviderStrategy='[{"capacityProvider":"my-cluster-ec2","base":2,"weight":3},{"capacityProvider":"FARGATE_SPOT","weight":1}]' \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=dev
|
|
```
|
|
|
|
### Service with Custom Resource Limits
|
|
|
|
```bash
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=my-service \
|
|
-c clusterName=my-cluster \
|
|
-c serviceName=my-service \
|
|
-c image=nginx:latest \
|
|
-c containerPort=3000 \
|
|
-c cpu=1024 \
|
|
-c memory=2048 \
|
|
-c desiredCount=4 \
|
|
-c ownerTag=MyTeam \
|
|
-c productTag=myproduct \
|
|
-c componentTag=service \
|
|
-c environment=prod
|
|
```
|
|
|
|
---
|
|
|
|
## Complete Production Example
|
|
|
|
### Full Stack Deployment Sequence
|
|
|
|
```bash
|
|
# 1. Deploy VPC
|
|
pnpm exec cdk synth \
|
|
-c stackType=vpc \
|
|
-c stackName=production-vpc \
|
|
-c ownerTag=Platform \
|
|
-c productTag=spicy \
|
|
-c componentTag=vpc \
|
|
-c numberOfAzs=4 \
|
|
-c availabilityZones=ca-central-1a,ca-central-1b,ca-central-1c,ca-central-1d
|
|
|
|
# 2. Deploy ECS Cluster
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-cluster \
|
|
-c stackName=production-cluster \
|
|
-c vpcStackName=production-vpc \
|
|
-c createExternalLoadBalancer=true \
|
|
-c createInternalLoadBalancer=true \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/prod \
|
|
-c instanceType=m5a.xlarge \
|
|
-c minClusterSize=3 \
|
|
-c maxClusterSize=10 \
|
|
-c spotEnabled=true \
|
|
-c onDemandPercentage=50 \
|
|
-c ownerTag=Platform \
|
|
-c productTag=spicy \
|
|
-c componentTag=ecs-cluster \
|
|
-c environment=prod
|
|
|
|
# 3. Deploy ALB Stack (bg-common pattern)
|
|
pnpm exec cdk synth \
|
|
-c stackType=alb \
|
|
-c stackName=api-service-prod-alb \
|
|
-c vpcStackName=production-vpc \
|
|
-c numberOfAzs=3 \
|
|
-c scheme=internet-facing \
|
|
-c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/prod \
|
|
-c logsBucketName=production-cluster-ca-central-1-alb-logs \
|
|
-c activeHostname=api.example.com \
|
|
-c inactiveHostname=inactive-api.example.com \
|
|
-c bgHostedZoneId=Z1234567890 \
|
|
-c ownerTag=Platform \
|
|
-c productTag=spicy \
|
|
-c componentTag=alb \
|
|
-c environment=prod
|
|
|
|
# 4. Deploy Service Stack (references existing ALB via albStackName)
|
|
pnpm exec cdk synth \
|
|
-c stackType=ecs-service \
|
|
-c stackName=api-service-prod-blue \
|
|
-c clusterName=production-cluster \
|
|
-c albStackName=api-service-prod-alb \
|
|
-c serviceName=api-service \
|
|
-c image=nexus.kodeniks.com/docker-hosted/api-service:v1.2.3 \
|
|
-c containerPort=3000 \
|
|
-c cpu=512 \
|
|
-c memory=1024 \
|
|
-c activeHostname=api.example.com \
|
|
-c inactiveHostname=inactive-api.example.com \
|
|
-c bgHostedZoneId=Z1234567890 \
|
|
-c desiredCount=4 \
|
|
-c minCapacity=4 \
|
|
-c maxCapacity=24 \
|
|
-c targetCpuUtilization=70 \
|
|
-c targetMemoryUtilization=80 \
|
|
-c targetRequestsPerTarget=1000 \
|
|
-c healthCheckPath=/health \
|
|
-c hostHeader=api.example.com \
|
|
-c priority=100 \
|
|
-c ownerTag=Platform \
|
|
-c productTag=spicy \
|
|
-c componentTag=api \
|
|
-c environment=prod
|
|
```
|
|
|
|
---
|
|
|
|
## Parameter Reference
|
|
|
|
### Common Parameters (All Stacks)
|
|
|
|
| Parameter | Required | Description | Example |
|
|
| -------------- | -------- | ----------------------------------------------- | ----------- |
|
|
| `stackType` | ✅ | Stack type: `vpc`, `ecs-cluster`, `ecs-service` | `vpc` |
|
|
| `stackName` | ✅ | CloudFormation stack name | `my-vpc` |
|
|
| `ownerTag` | ✅ | Owner tag | `MyTeam` |
|
|
| `productTag` | ✅ | Product tag | `myproduct` |
|
|
| `componentTag` | ✅ | Component tag | `vpc` |
|
|
| `environment` | ⚠️ | Environment (required for cluster/service) | `dev` |
|
|
|
|
### VPC-Specific Parameters
|
|
|
|
| Parameter | Default | Description |
|
|
| -------------------------------- | -------------- | ----------------------------- |
|
|
| `vpcCidr` | `172.1.0.0/16` | VPC CIDR block |
|
|
| `numberOfAzs` | `4` | Number of AZs (2, 3, or 4) |
|
|
| `availabilityZones` | auto | Comma-separated AZ list |
|
|
| `createPrivateSubnets` | `true` | Create private subnets |
|
|
| `createAdditionalPrivateSubnets` | `true` | Create NACL-protected subnets |
|
|
|
|
### ECS Cluster Parameters
|
|
|
|
| Parameter | Default | Description |
|
|
| ---------------------------- | --------------- | ---------------------------------------------------------------------------------------------------- |
|
|
| `vpcStackName` | ✅ **Required** | VPC stack name - **all VPC details** (VPC ID, CIDR, subnets, AZs) auto-import from VPC stack exports |
|
|
| `instanceType` | `m5a.large` | EC2 instance type |
|
|
| `minClusterSize` | `2` | Minimum instances |
|
|
| `maxClusterSize` | `4` | Maximum instances |
|
|
| `createExternalLoadBalancer` | `false` | Create external ALB (public subnets auto-imported from VPC stack if enabled) |
|
|
| `createInternalLoadBalancer` | `false` | Create internal ALB |
|
|
| `certificateArn` | - | SSL certificate ARN |
|
|
| `spotEnabled` | `false` | Enable Spot instances |
|
|
| `onDemandPercentage` | `100` | On-demand percentage |
|
|
| `enableFargate` | `false` | Enable Fargate providers |
|
|
|
|
### ALB Stack Parameters
|
|
|
|
| Parameter | Default | Description |
|
|
| --------------------- | ----------------- | -------------------------------------------------------------------------------------------------------- |
|
|
| `vpcId` | - | VPC ID (required, or use `vpcStackName` to import) |
|
|
| `vpcCidrBlock` | - | VPC CIDR (required, or use `vpcStackName` to import) |
|
|
| `availabilityZones` | - | Comma-separated AZs (required, or use `vpcStackName` + `numberOfAzs` to import) |
|
|
| `subnetIds` | - | Subnet IDs for ALB (required) |
|
|
| `scheme` | `internet-facing` | ALB scheme: `internet-facing` or `internal` |
|
|
| `certificateArn` | - | SSL certificate ARN (required for HTTPS) |
|
|
| `logsBucketName` | - | S3 bucket name for ALB access logs |
|
|
| `logsPrefix` | - | S3 prefix for ALB access logs |
|
|
| `idleTimeout` | `60` | ALB idle timeout in seconds |
|
|
| `redirectHttpToHttps` | `true` | Enable HTTP→HTTPS redirect |
|
|
| `hostName` | - | Simple hostname (e.g., api.example.com) - auto-generates active/inactive hostnames (like old `HostName`) |
|
|
| `activeHostname` | - | Active hostname for blue/green DNS |
|
|
| `inactiveHostname` | - | Inactive hostname for blue/green DNS |
|
|
| `bgHostedZoneId` | - | Route53 hosted zone ID for blue/green DNS (optional - only needed if creating DNS records) |
|
|
|
|
### ECS Service Parameters
|
|
|
|
| Parameter | Default | Description |
|
|
| ------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
| `clusterName` | - | ECS cluster name (required) - used to auto-import VPC stack name, VPC ID, and listener ARNs from cluster stack exports |
|
|
| `clusterStackName` | - | Cluster stack name (defaults to `clusterName`) - used for CloudFormation imports |
|
|
| `vpcStackName` | - | VPC stack name (auto-imported from `${clusterStackName}-VPCStackName`, or provide explicitly) |
|
|
| `albStackName` | - | ALB stack name (for bg-common ALB) - used to auto-import ALB ARN and listener ARNs from ALB stack |
|
|
| `vpcId` | - | VPC ID (required, or auto-imported from `${clusterStackName}-VPC`) |
|
|
| `vpcCidrBlock` | - | VPC CIDR (required, or auto-imported from `${vpcStackName}-VPCCIDR` using imported VPC stack name) |
|
|
| `availabilityZones` | - | Comma-separated AZs (required, or auto-derived from VPC stack imports with `numberOfAzs`) |
|
|
| `privateSubnetIds` | - | Private subnet IDs (required, or auto-imported from `${vpcStackName}-PrivateSubnetA1ID`, etc.) |
|
|
| `numberOfAzs` | auto | Number of AZs (auto-imported from VPC stack, or override explicitly) |
|
|
| `publicSubnetIds` | - | Public subnet IDs (for individual ALB) |
|
|
| `serviceName` | - | Service name (required) |
|
|
| `image` | - | Docker image URI (required) |
|
|
| `containerPort` | `3000` | Container port |
|
|
| `cpu` | `256` | CPU units |
|
|
| `memory` | `512` | Memory in MiB |
|
|
| `desiredCount` | `2` | Desired task count |
|
|
|
|
#### bg-common ALB Parameters (useClusterAlb=false)
|
|
|
|
When `useClusterAlb` is false or not set, the service uses a bg-common ALB from a separate ALB stack. These parameters reference the existing ALB:
|
|
|
|
**Minimal props using CloudFormation imports:**
|
|
|
|
- Provide `albStackName` to auto-import ALB ARN and listener ARNs
|
|
- Service creates listener rules on **both** HTTP and HTTPS listeners (like the old template)
|
|
|
|
| Parameter | Default | Description |
|
|
| ----------------------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
|
|
| `albStackName` | - | ALB stack name - used to auto-import ALB ARN and listener ARNs (preferred) |
|
|
| `existingAlbArn` | - | ALB ARN (auto-imported from `${albStackName}-internet-facing-arn`, or provide explicitly) |
|
|
| `existingAlbHttpsListenerArn` | - | HTTPS listener ARN (auto-imported from `${albStackName}-internet-facing-https-listener`, or provide explicitly) |
|
|
| `existingAlbHttpListenerArn` | - | HTTP listener ARN (auto-imported from `${albStackName}-internet-facing-http-listener`, or provide explicitly) |
|
|
| `hostHeader` | - | Host header for listener rule |
|
|
| `priority` | `100` | Listener rule priority (lower = higher precedence) |
|
|
| `stickiness` | `true` | Enable session stickiness on target group |
|
|
| `stickinessDuration` | `86400` | Session stickiness duration in seconds |
|
|
| `deregistrationDelay` | `60` | Target deregistration delay in seconds |
|
|
| `hostName` | - | Simple hostname (e.g., api.example.com) - auto-generates active/inactive (like old `HostName`) |
|
|
| `activeHostname` | - | Active hostname for blue/green DNS (must match ALB stack config) |
|
|
| `inactiveHostname` | - | Inactive hostname for blue/green DNS (must match ALB stack config) |
|
|
| `bgHostedZoneId` | - | Route53 hosted zone ID (optional - only needed if creating DNS records) |
|
|
|
|
#### Blue/Green Deployment Parameters
|
|
|
|
| Parameter | Default | Description |
|
|
| --------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
| `blueGreen` | `false` | Enable blue/green deployment (pipeline handles color switching automatically) |
|
|
| `hostName` | - | Simple hostname (e.g., api.example.com) - auto-generates `activeHostname` and `inactiveHostname` (like old `HostName`) |
|
|
| `activeHostname` | - | Active hostname (e.g., api.example.com) - used by bg-common ALB stack to create DNS records |
|
|
| `inactiveHostname` | - | Inactive hostname (e.g., inactive-api.example.com) - used by bg-common ALB stack to create DNS records |
|
|
| `bgHostedZoneId` | - | Route53 hosted zone ID (for bg-common ALB stack DNS records) - required for DNS records (hostnames are optional) |
|
|
| `rollbackWindowHours` | `2` | Hours to keep old stack for rollback (default 2 hours) |
|
|
|
|
**Note:** Hostnames are **optional**. Services without hostnames (pub/sub workers, background jobs) don't need DNS records. Only provide `bgHostedZoneId` if you want Route53 DNS records created.
|
|
|
|
**Cloudflare DNS Delegation:** If using Cloudflare for DNS, you can delegate a subdomain to AWS Route53:
|
|
|
|
1. Create a Route53 hosted zone for the subdomain (e.g., `production.mydomain.com`)
|
|
2. Point Cloudflare NS records for `*.production.mydomain.com` to the Route53 name servers
|
|
3. Use the Route53 hosted zone ID as `bgHostedZoneId`
|
|
4. AWS will manage DNS records for all services under that subdomain
|
|
|
|
#### Cluster ALB Parameters (useClusterAlb=true)
|
|
|
|
When `useClusterAlb` is true, the service uses the cluster's shared ALB:
|
|
|
|
**Minimal props using CloudFormation imports:**
|
|
|
|
- Provide `clusterName` to auto-import listener ARNs from cluster stack exports
|
|
- Listener ARNs auto-import from `${clusterName}-internet-facing-https-listener` or `${clusterName}-internal-https-listener`
|
|
|
|
| Parameter | Default | Description |
|
|
| --------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
|
|
| `useClusterAlb` | `false` | Set to `true` to use cluster ALB |
|
|
| `hostHeader` | - | Host header for routing |
|
|
| `pathPatterns` | - | Comma-separated path patterns |
|
|
| `priority` | `100` | Listener rule priority |
|
|
| `useExternalALB` | `false` | Use external cluster ALB (auto-imports listener ARN from `${clusterName}-internet-facing-https-listener`) |
|
|
| `useInternalALB` | `false` | Use internal cluster ALB (auto-imports listener ARN from `${clusterName}-internal-https-listener`) |
|
|
| `externalListenerArn` | - | External listener ARN (auto-imported when `useExternalALB=true`, or provide explicitly) |
|
|
| `internalListenerArn` | - | Internal listener ARN (auto-imported when `useInternalALB=true`, or provide explicitly) |
|
|
|
|
#### Auto-Scaling Parameters
|
|
|
|
| Parameter | Default | Description |
|
|
| ------------------------- | ------- | ----------------------------------- |
|
|
| `minCapacity` | - | Minimum tasks |
|
|
| `maxCapacity` | - | Maximum tasks |
|
|
| `targetCpuUtilization` | - | Target CPU % (0-100) |
|
|
| `targetMemoryUtilization` | - | Target memory % (0-100) |
|
|
| `targetRequestsPerTarget` | - | Target requests per task per minute |
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
When running synth commands, verify:
|
|
|
|
- ✅ No errors (only expected warnings)
|
|
- ✅ Resources are created as expected
|
|
- ✅ Logical IDs match expected patterns
|
|
- ✅ Tags are applied correctly
|
|
- ✅ Outputs are exported correctly
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **Missing required parameters**: Check error message for missing context values
|
|
2. **Invalid subnet IDs**: Ensure subnet IDs exist in the specified VPC
|
|
3. **Certificate ARN format**: Must be valid ACM certificate ARN
|
|
4. **JSON parsing errors**: Ensure JSON strings are properly escaped/quoted
|
|
|
|
### Getting Stack Outputs
|
|
|
|
After deployment, get stack outputs:
|
|
|
|
```bash
|
|
aws cloudformation describe-stacks \
|
|
--stack-name my-stack \
|
|
--query 'Stacks[0].Outputs' \
|
|
--output table
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Minimal Commands
|
|
|
|
**VPC:**
|
|
|
|
```bash
|
|
pnpm exec cdk synth -c stackType=vpc -c stackName=test -c ownerTag=T -c productTag=t -c componentTag=v
|
|
```
|
|
|
|
**Cluster:**
|
|
|
|
```bash
|
|
pnpm exec cdk synth -c stackType=ecs-cluster -c stackName=test -c vpcStackName=test-vpc -c ownerTag=T -c productTag=t -c componentTag=c -c environment=d
|
|
```
|
|
|
|
**Service (using CloudFormation imports):**
|
|
|
|
```bash
|
|
pnpm exec cdk synth -c stackType=ecs-service -c stackName=test -c clusterName=test -c serviceName=test -c image=nginx:latest -c containerPort=80 -c ownerTag=T -c productTag=t -c componentTag=s -c environment=d
|
|
```
|