AWS CDK Deployment
The platform's AWS infrastructure is defined as code using AWS CDK v2, in Python: the app is infrastructure/cdk/app.py, and infrastructure/cdk/cdk.json runs it with python3 app.py. cdk deploy --all provisions the API, the dashboard and the data stores they need in your AWS account.
The Fargate stack runs two services behind one Application Load Balancer: the API, and the dashboard as its own ECS service (experimentation-dashboard-<env>). The HTTPS listener sends /api/*, /health, /health/* and /metrics to the API and everything else to the dashboard. cdk deploy starts the dashboard on the experimentation-platform/web:bootstrap image, which you push before the first deploy (deployment guide §1.3). After that, the Deploy workflow rolls each release onto the dashboard: it registers a dashboard task definition with the release's image, rolls the dashboard service once the API is serving the same release, and smoke-tests it through the public URL.
Prerequisites
| Tool | Version | Install |
|---|---|---|
| AWS Account | Any | Sign up |
| Python | 3.11+ | The CDK app is Python |
| AWS CDK | v2 | npm install -g aws-cdk |
| Docker | For the bootstrap images the deployment guide pushes |
One-Time Bootstrap
CDK bootstrap provisions the S3 bucket and IAM roles that CDK needs to deploy assets. Run this once per AWS account/region combination:
Example:
You can find your account ID with:
Required Environment Variables
infrastructure/cdk/app.py reads these from the environment. cdk synth, cdk diff, cdk deploy and cdk destroy fail without the ones marked required, ALARM_EMAIL is required for staging and prod, and the three secret ARNs are required in every environment (below).
One region. The Deploy, Rollback and Database Migration workflows act in
us-west-2 (AWS_REGION at the top of each), so deploy the stacks, the ECR
repositories, the secrets and the certificate there too. To use another region,
change it in all three workflows and use it everywhere below.
ENVIRONMENT is dev (the default), staging, prod or demo:
Required: the ACM certificate for the load balancer's HTTPS listeners:
Required: the absolute https:// origin users reach the platform at (one host, app.<domain>):
ALARM_EMAIL is the one address every CloudWatch alarm emails, through the
SNS topic experimentation-alerts-<env>. That includes the API's two 5xx
alarms: while either is in ALARM, CodeDeploy rolls a deployment back by itself,
and this email is how you hear about it.
- Required for
stagingandprod. Without it, synth stops with aValueErrornamingALARM_EMAILand saying how to set it. - Optional for
devanddemo. Leave it unset and the topic gets no subscriber at all, not a placeholder. - Refused, in any environment, when it is longer than 254 characters,
contains whitespace, has other than exactly one
@, has a domain with no dot, or is atexample.com,example.netorexample.org, or a subdomain of one (in any case). - Needed every time the app is synthesised. That includes
cdk diffandcdk destroy, not onlycdk deploy. Changing the value replaces the subscription, which then has to be confirmed again.
The subscription does nothing until it is confirmed. After the first
cdk deploy of the monitoring stack, SNS emails a confirmation link to the
address. Until it is confirmed, the alarms fire and nobody is told, and the
link does not last for ever, so confirm it the same day. Do not just click it:
a subscription confirmed that way can be cancelled by the unsubscribe link in
any alarm email, by anyone the email reaches. Instead, copy the Token= value
out of the link and confirm it with --authenticate-on-unsubscribe true, after
which unsubscribing takes an authenticated AWS call:
TOPIC_ARN=$(aws sns list-topics --output text \
--query "Topics[?ends_with(TopicArn, ':experimentation-alerts-$ENVIRONMENT')].TopicArn")
aws sns confirm-subscription --topic-arn "$TOPIC_ARN" \
--token "$TOKEN_FROM_THE_LINK" --authenticate-on-unsubscribe true
aws sns list-subscriptions-by-topic --topic-arn "$TOPIC_ARN" \
--query "Subscriptions[].[Protocol,Endpoint,SubscriptionArn]" --output text
The last command must show the email subscription with a real ARN, not
PendingConfirmation.
Required in every environment: the complete ARN of each secret the task definitions read. The secrets themselves must exist first (Secrets Management); read each ARN by name, read-only, in the region the stacks deploy to (the third only for the full profile):
export JWT_SECRET_ARN=$(aws secretsmanager describe-secret \
--secret-id "/$ENVIRONMENT/experimentation/jwt-secret" --query ARN --output text)
export FIRST_SUPERUSER_PASSWORD_SECRET_ARN=$(aws secretsmanager describe-secret \
--secret-id "/$ENVIRONMENT/experimentation/first-superuser-password" --query ARN --output text)
export AUDIT_HMAC_KEY_SECRET_ARN=$(aws secretsmanager describe-secret \
--secret-id "/$ENVIRONMENT/experimentation/audit-hmac-key" --query ARN --output text)
JWT_SECRET_ARN,FIRST_SUPERUSER_PASSWORD_SECRET_ARN: always.AUDIT_HMAC_KEY_SECRET_ARN: the full profile only (checked on core too if you set it).- Each must be the complete ARN,
arn:aws:secretsmanager:<region>:<account>:secret:/<env>/experimentation/<name>-<suffix>, in the stack's own account and region. Synth refuses a missing value, a partial ARN (one that stops at the name: ECS cannot resolve it), an ARN for a different environment (/prod/...in staging), or one variable's secret given in another. The refusal names the variable and the command above, and never prints the value. - Recreating a secret changes its ARN. Update the input and run
cdk deployagain, or the next task start fails. Changing a secret's value keeps its ARN.
The application's own secret values (database password, JWT secret and the
rest) are not read from your shell. The task definition takes them from Secrets
Manager when each task starts; see the list in the
Deployment README. The API image is pulled from
the ECR repository experimentation-platform/backend, which the CDK does not
create.
Deploy All Stacks
Point the hostname at the load balancer
No stack creates a DNS record. After cdk deploy the load balancer is up,
but the host in PUBLIC_BASE_URL (app.<domain>) does not resolve until you
add an alias record for it in your Route 53 hosted zone. Until then curl
reports 000, and the Deploy workflow's smoke test cannot reach the platform.
Do this once per environment, after the first cdk deploy of the Fargate stack.
Add a record for every hostname the platform answers on: the host in
PUBLIC_BASE_URL always, and any other name on the certificate that clients
use (the commands below also add api.<domain>; leave that entry out if you
do not use it).
Set the hosted zone's name and the two hostnames:
Look up the zone, and the load balancer by its name. The Fargate stack names
it experimentation-<env>, so this finds the right one in an account with
more than one load balancer; do not take the first internet-facing one. Both
commands must print exactly one value:
ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name "$ZONE_NAME" \
--query "HostedZones[?Name=='$ZONE_NAME.' && Config.PrivateZone==\`false\`].Id" --output text)
ZONE_ID=${ZONE_ID#/hostedzone/}
read -r ALB_DNS ALB_ZONE < <(aws elbv2 describe-load-balancers \
--names "experimentation-$ENVIRONMENT" \
--query 'LoadBalancers[0].[DNSName,CanonicalHostedZoneId]' --output text)
printf 'zone %s\nalb %s %s\n' "$ZONE_ID" "$ALB_DNS" "$ALB_ZONE"
ALB_DNS is also the ALBDnsName output of experimentation-fargate-<env>.
The alias records below take ALB_ZONE as their HostedZoneId, not
ZONE_ID.
Create, or update, an alias A record for each hostname, and wait for Route 53 to apply the change:
cat > alias.json <<EOF
{"Changes": [
{"Action": "UPSERT", "ResourceRecordSet": {"Name": "$APP_HOST", "Type": "A",
"AliasTarget": {"HostedZoneId": "$ALB_ZONE", "DNSName": "$ALB_DNS", "EvaluateTargetHealth": false}}},
{"Action": "UPSERT", "ResourceRecordSet": {"Name": "$API_HOST", "Type": "A",
"AliasTarget": {"HostedZoneId": "$ALB_ZONE", "DNSName": "$ALB_DNS", "EvaluateTargetHealth": false}}}
]}
EOF
CHANGE_ID=$(aws route53 change-resource-record-sets --hosted-zone-id "$ZONE_ID" \
--change-batch file://alias.json --query ChangeInfo.Id --output text)
aws route53 wait resource-record-sets-changed --id "$CHANGE_ID"
Check that each hostname reaches the platform:
curl -s -o /dev/null -w '%{http_code}\n' "https://$APP_HOST/health"
curl -s -o /dev/null -w '%{http_code}\n' "https://$API_HOST/health"
Each prints 200. 000 means the name does not resolve yet, or resolves to
something other than this load balancer: check the record and the zone id, and
allow for DNS caches that still hold an earlier answer.
UPSERT makes the commands safe to run again. Run them again whenever the load
balancer is replaced (for example after destroying and redeploying the Fargate
stack), because its DNS name changes with it.
cdk destroy does not remove these records: the stacks did not create them.
When you tear an environment down, delete them (the same change with
"Action": "DELETE"), or they are deleted with the hosted zone if you delete
the zone itself.
cdk deploy does not create the schema
The API tasks never run database migrations: their task definition sets
RUN_MIGRATIONS=false and SEED= (empty). The schema is written only by the
migration task, which the Deploy workflow runs before it shifts traffic
(and the Database Migration workflow when run by hand). So after the first
cdk deploy of an environment the API runs against an empty database until the
first Deploy:
/healthanswers 200 through the hostname once its alias record exists (above), because it checks that the database answers, and the load balancer's health checks pass;- real requests answer 500, and the API logs errors. This is expected until the first Deploy creates the schema.
Run the first Deploy soon after the first cdk deploy. Requests that reach the
API in between, from scanners as much as from people, can put one of the API's
5xx alarms into ALARM, and Deploy refuses to start while one is. The way
through is Deploy's existing break-glass
(rollback runbook, "Fix forward while an alarm is firing");
whether to use it is decided by a person at that moment.
Rolling the API back runs the older release against the newer schema, which
works only for backward-compatible migrations; for one that is not, undo the
migration while the release that contains it is still serving, before rolling
the API back
(rollback runbook, Database Rollback Procedure).
After the API has been rolled back, the emergency route is a point-in-time
restore with scripts/restore_repoint.sh
(Disaster Recovery, Scenario 4). See
On AWS, the API does not migrate
for why, and for an environment whose Fargate stack was deployed before this
setting existed. Deploy refuses to deploy an API revision that lacks the
setting, and says to cdk deploy the Fargate stack from a current checkout
first.
Which stacks you get: core or full
The CDK app deploys the stacks your checkout has, the same way the API loads
the modules it finds. A core checkout has no modules/ directory and gets
the core stacks below. A full checkout also has
modules/infrastructure/cdk/stacks, and cdk deploy --all then adds three
more:
| Stack | Module | What it is |
|---|---|---|
experimentation-dynamodb-counters-<env> |
counters |
The real-time counter table the bandit scheduler reads |
experimentation-analytics-<env> |
etl |
Kinesis stream, Firehose delivery, OpenSearch domain, data-lake bucket |
experimentation-glue-etl-<env> |
etl |
Glue crawler and ETL jobs over the data-lake bucket |
The first line the app prints says which profile it picked:
EXPERIMENTLY_PROFILE — the same variable the container images and the API
use — overrides the choice:
EXPERIMENTLY_PROFILE=core cdk deploy --all: core stacks only, from a full checkoutEXPERIMENTLY_PROFILE=full cdk deploy --all: fail if the module stacks are absent
Two things follow from the profile, so a core deployment is consistent rather than half-configured:
- The monitoring stack's Kinesis widget and its iterator-age alarm are created only alongside the analytics stack, and they watch the stream that stack actually creates. A core deployment has neither.
- Without the
countersstack there is no counter table: the bandit scheduler falls back to PostgreSQL for its statistics, which is the core behaviour.
Individual Stack Deployment
Deploy specific stacks during development or when updating a single component:
cdk deploy takes a stack id, which cdk list prints for your
environment -- not a class name:
Deploy only the API service (faster for code changes):
Deploy only monitoring resources:
Redeploying experimentation-fargate-<env> on a running environment needs
two pins, printed by two read-only checks run from the repository root:
python3 scripts/check_live_target_group.py --env <env>
python3 scripts/check_dashboard_image.py --env <env>
cdk deploy experimentation-fargate-<env> \
-c api_live_target_group=<blue|green> -c dashboard_image_tag=sha256:<hex>
python3 scripts/check_live_target_group.py --env <env>: printsapi_live_target_grouppython3 scripts/check_dashboard_image.py --env <env>: printsdashboard_image_tag
Without them the deploy undoes what the release workflow did, and every probe
stays green: the API's routes can point at the empty one of its blue and green
target groups, and a change to the dashboard's task definition
puts it back on the web:bootstrap placeholder image. The Deployment Guide, section 1.6
has the full sequence, including backend_image_tag and what to do when the
dashboard is still on :bootstrap.
What Gets Deployed
experimentation-vpc-
- VPC with public and private subnets across 2 availability zones
- NAT gateways for outbound internet access from private subnets: two in
prod(one per availability zone), one in every other environment
The VPC is 10.0.0.0/16 across two availability zones, with a public, a
private and an isolated /24 in each: 10.0.0.0/24 and 10.0.1.0/24 public,
10.0.2.0/24 and 10.0.3.0/24 private, 10.0.4.0/24 and 10.0.5.0/24
isolated. It has gateway endpoints for S3 and DynamoDB, and interface
endpoints, with private DNS, for Secrets Manager, ECR (API and Docker) and
CloudWatch Logs. AWS bills each interface endpoint by the hour, used or not. The load
balancer is the only resource open to the internet: its security group admits
TCP 80 and 443 from anywhere, and no other security group rule admits the
whole internet. It reaches the API tasks on 8000 and the dashboard tasks on
8080, and nothing else. Aurora admits PostgreSQL (5432) from the API task
group, which the migration task also runs in.
The VPC stack also creates a bastion security group, which admits nobody
unless you deploy with -c bastion_ssh_cidr=<cidr>.
experimentation-database- and experimentation-redis-
- Aurora PostgreSQL cluster: writer + 1 reader on
db.r5.largeforprod, a singledb.t3.mediumin every other environment - ElastiCache Redis replication group: 3 nodes on
cache.r6g.largeforprod(automatic failover, Multi-AZ); a single node elsewhere,cache.t4g.smallforstagingandcache.t4g.mediumotherwise, with no replica and so no failover - Subnet groups and parameter groups
- Redis snapshots kept 7 days in
prod, 3 instaging, 1 otherwise
experimentation-compute- and experimentation-fargate-
- ECS Fargate cluster
- ECS task definition for the API container (1 vCPU, 2 GB memory)
- ECS service for the API with 3 tasks in
prod, 2 indevandstagingand 1 indemo, auto-scaling from that count up to 10 - Application Load Balancer with an HTTPS listener and an HTTP-to-HTTPS redirect, in front of the API and the dashboard:
/api/*,/health,/health/*and/metricsgo to the API, everything else to the dashboard - The dashboard's own ECS service,
experimentation-dashboard-<env>(rolling deployment with the circuit breaker; 1 task instaging, 2 inprod), started onexperimentation-platform/web:bootstrap - AWS CodeDeploy deployment group for blue/green deployments
- IAM task role for the API: the
CloudWatchLogsFullAccessmanaged policy,secretsmanager:GetSecretValueon the secrets the task reads, and thessmmessagesandlogsactions ECS Exec uses. The full profile addsdynamodb:Queryanddynamodb:UpdateItemon theexperiment-counters-<env>table alone, and setsDYNAMODB_COUNTERS_TABLEandAWS_DEFAULT_REGIONon the API container. It also adds, for the ETL routes,glue:StartJobRunandglue:GetJobRunon this environment's two Glue jobs,glue:StartCrawlerandglue:GetCrawleron its crawler, andglue:GetTableandglue:BatchCreatePartitionon the catalog, theexperimentation_<env>database and its tables, and sets the fourGLUE_*names on the API container. There is no Kinesis, Cognito, Athena, S3 or other DynamoDB permission
experimentation-dynamodb- and experimentation-dynamodb-counters-
- Core: the events, feature-flags and overrides tables
- Full profile only (the
countersmodule): the experiment-counters DynamoDB table with on-demand billing and a GSI for querying by experiment
experimentation-analytics- (full profile only — the etl module)
etl module)- Kinesis Data Stream (name generated by CloudFormation; it used to be
exp-events-<random>, regenerated on every synth -- see #96) - Firehose delivery stream into the data-lake bucket
- OpenSearch Service domain
experimentation-monitoring-
- CloudWatch dashboards
experimentation-platform-<env>andexperimentation-application-metrics-<env>(some of their widgets graph metrics nothing publishes, #424) - CloudWatch alarms:
AuroraHighCPU-<env>on the Aurora writer, and oneRedisHighCPU-00N-<env>per Redis node. The Aurora alarm reads the cluster's identifier from the SSM parameter/experimentation/<env>/database/aurora-cluster-identifier, which the database stack writes, so this stack depends onexperimentation-database-<env>. - SNS topic for alarm notifications,
experimentation-alerts-<env>, with one email subscriber:ALARM_EMAIL(none indevordemowithout it). The fargate stack's alarms publish to it too: the two API 5xx alarms, which roll a deployment back,experimentation-api-no-healthy-task-<env>, which fires when no API task is healthy, andexperimentation-api-error-logs-<env>, which fires on 10 or more lines in 5 minutes whose JSONleveliserrororcritical. That is whyexperimentation-fargate-<env>depends on this stack. - With the
etlmodule: a Kinesis widget and an iterator-age alarm on that module's event stream. A core deployment gets neither, rather than an alarm on a stream that does not exist.
What is not deployed
- CloudFront. No stack creates a distribution. The split-URL module ships a
construct for one (
modules/infrastructure/constructs/split_url_distribution.py), butapp.pydoes not use it; see Split URL testing.
Blue/Green Deployment for Zero-Downtime Updates
The API service uses blue/green deployment through AWS CodeDeploy, and the
Deploy workflow is what drives it (deployment guide, section 3).
The API's revision moves through CodeDeploy, not through cdk deploy: the
service has a CODE_DEPLOY deployment controller, and ECS refuses a
task-definition change through UpdateService on such a service. For each
deploy:
- The workflow registers a new task definition and creates a CodeDeploy deployment
- CodeDeploy starts the new tasks in the target group that is not live, and reports
Ready - When every new target is healthy, the workflow approves the shift
- The canary sends 10% of traffic to the new tasks, waits 15 minutes (why 15), then sends the rest
- The old tasks are kept for an hour, so Rollback can put them back, and then terminated
Alarms watch the API's canary and the hour after it. The Fargate stack creates
two CloudWatch alarms, experimentation-api-5xx-blue-<env> and
experimentation-api-5xx-green-<env>, one per target group, and attaches both
to the deployment group. While either is in ALARM, CodeDeploy stops the
deployment and rolls the API back by itself; it also rolls back a deployment
that fails (new tasks that never become healthy) or is stopped. The alarms
watch the API's target 5xx only: a release that answers wrongly with a 2xx,
the load balancer's own 502 and 504, and the dashboard are not covered. See the
rollback runbook.
Preview Changes with cdk diff
Before deploying, preview what will change. On an environment that is already running, pass exactly the pins the deploy will pass (Deployment Guide, section 1.6 shows how to read them), or the diff shows the very changes those pins exist to prevent. Write the diff to a file rather than piping it:
cdk diff "experimentation-fargate-$ENVIRONMENT" \
-c backend_image_tag="$BACKEND_TAG" -c api_live_target_group="$API_LIVE" \
-c dashboard_image_tag="$DASHBOARD_DIGEST" > "cdk-diff-$ENVIRONMENT.txt" 2>&1
Do not deploy a diff with any line that names
AWS::EC2::SecurityGroupEgress or AWS::EC2::SecurityGroupIngress: section
1.6 of the Deployment Guide gives the stop rule and why.
Destroying an environment
The teardown has not been rehearsed on AWS as of v0.31.1: this section comes from the stacks' templates and AWS's documentation.
Only prod keeps its data when its stacks are destroyed. Every other
environment (dev, staging, demo) is disposable: its database, tables,
buckets, stream, search domain, user pool, key and log groups are deleted with
it, so the next deploy of the same environment starts clean instead of failing
on the names the last one left behind. The rule is one line,
retains_data(env) == (env == "prod"), in
infrastructure/cdk/stacks/environments.py.
Before you destroy prod, export anything you need that the retained resources
below do not already keep: pg_dump the database, export audit logs
(GET /api/v1/compliance/export, full profile only), archive CloudWatch logs.
Tearing an environment down
cdk destroy --all destroys the stacks in dependency order. By hand, the same
order is: a stack that imports from another, or depends on it, goes first --
so monitoring and the Glue stack go before analytics, and fargate before
compute and monitoring. cdk destroy synthesises the app like any other
command, so a staging or prod teardown needs ALARM_EMAIL set too.
cdk destroy experimentation-migrations-<env>
cdk destroy experimentation-fargate-<env>
cdk destroy experimentation-glue-etl-<env>
cdk destroy experimentation-monitoring-<env>
cdk destroy experimentation-analytics-<env>
cdk destroy experimentation-compute-<env>
cdk destroy experimentation-redis-<env>
cdk destroy experimentation-database-<env>
cdk destroy experimentation-dynamodb-counters-<env>
cdk destroy experimentation-dynamodb-<env>
cdk destroy experimentation-auth-<env>
cdk destroy experimentation-vpc-<env>
The glue, analytics and counters stacks exist only in a full checkout; skip their lines in a core one.
The buckets outside prod are emptied before they are deleted:
auto_delete_objects=True adds a custom resource backed by a CDK-provided
Lambda (Custom::S3AutoDeleteObjectsCustomResourceProvider, with its own IAM
role) that, when the stack is deleted, denies new writes to the bucket and
deletes every object version and delete marker. It acts only on a bucket
carrying the aws-cdk:auto-delete-objects tag.
What cdk destroy leaves behind and bills
Everything below survives cdk destroy --all. The first group is kept on
purpose, in prod only; the rest is created outside the stacks' templates and
is yours to remove.
| What | Where it comes from | Environments | Billed while it exists | How to remove it |
|---|---|---|---|---|
| The final snapshot CloudFormation takes of the Aurora cluster | the cluster's SNAPSHOT policy | prod | snapshot storage | aws rds delete-db-cluster-snapshot |
| KMS key (the alias is deleted) | RETAIN; the final snapshot is encrypted with it | prod | per key per month | aws kms schedule-key-deletion -- only after the snapshot is gone |
Cognito user pool experimentation-platform-users-prod |
RETAIN | prod | per monthly active user | aws cognito-idp delete-user-pool |
DynamoDB tables experimentation-{events,feature-flags,overrides}-prod, experiment-counters-prod |
RETAIN | prod | capacity (the core tables are provisioned in prod) and storage | aws dynamodb delete-table |
DynamoDB tables experimentation-{assignments,experiments}-prod, in a production environment deployed before #605 |
no stack creates them any more, and nothing reads them | prod | provisioned capacity and storage | aws dynamodb create-backup if you want a copy, then aws dynamodb delete-table |
Log groups /ecs/experimentation-{backend,dashboard,migrate}-prod |
RETAIN | prod | log storage | aws logs delete-log-group |
| Data-lake, Athena-results and Glue-scripts buckets | RETAIN | prod (full) | storage, every object version | empty (all versions), then aws s3 rb |
| Kinesis stream and OpenSearch domain | RETAIN | prod (full) | per shard-hour; per instance-hour | aws kinesis delete-stream; aws opensearch delete-domain |
pre-migration-* and pre-deploy-* Aurora cluster snapshots |
taken by the deploy and migrate workflows, not by CloudFormation | every environment they ran against | snapshot storage | aws rds delete-db-cluster-snapshot. Outside prod the KMS key they are encrypted with is deleted with the stack, so they cannot be restored afterwards |
The alias A records for app.<domain> (and api.<domain>) |
created by hand after the first deploy (Point the hostname at the load balancer) | every environment | nothing for the records; the hosted zone is billed per month | the same change-resource-record-sets with "Action": "DELETE", or delete the hosted zone |
The secrets under /<env>/experimentation/ |
created by hand before the first deploy (Secrets Management) | every environment | per secret per month | aws secretsmanager delete-secret |
ECR repositories experimentation-platform/backend and experimentation-platform/web, and their images |
created by hand once per account (Deployment Guide) | shared by all environments | image storage | aws ecr delete-repository --force, once no environment needs them |
The CDK bootstrap stack CDKToolkit: its cdk-*-assets-<account>-<region> bucket and cdk-*-container-assets-* repository |
cdk bootstrap, once per account and region |
shared by all environments | storage | aws cloudformation delete-stack --stack-name CDKToolkit, after emptying the bucket |
Log groups AWS services create at run time: /aws/lambda/<function> for the functions without a managed log group, /aws/ecs/containerinsights/<cluster>/performance, /aws-glue/* |
the services themselves, not CloudFormation | every environment | log storage | aws logs delete-log-group |
Two things are deleted but not immediately:
- The database credentials secret (
experimentation-database-<env>-aurora-credentials) may be scheduled for deletion with a recovery window rather than removed. If the next deploy of the same environment fails with "a secret with this name is already scheduled for deletion", remove it withaws secretsmanager delete-secret --secret-id <name> --force-delete-without-recovery. - The KMS key outside prod is scheduled for deletion after the 30-day waiting period, and its alias is removed at once, so it does not block a redeploy.
Moving an environment deployed before the rename
This version names the ECS cluster from ENVIRONMENT (experimentation-<env>;
it was experimentation-dev in every environment, #142) and renames the
CodeDeploy application to experimentation-platform-<env> (#139). An
environment deployed by an earlier version cannot be updated in place:
cdk deploy --all fails on the compute stack.
The fargate stack is the only importer of that export -- the analytics stack imports nothing from compute and is not touched. So, for each environment deployed before this change:
cdk destroy --exclusively experimentation-fargate-<env>
aws logs delete-log-group --log-group-name /ecs/experimentation-backend-<env>
aws logs delete-log-group --log-group-name /ecs/experimentation-dashboard-<env>
cdk deploy experimentation-compute-<env>
cdk deploy experimentation-fargate-<env>
cdk deploy --all
--exclusivelymatters: without it the CDK CLI also destroys the stacks that depend on fargate, which is the migrations stack.- Run the two
delete-log-grouplines: a recreated fargate stack would otherwise fail on the names. Skip the dashboard line if that log group does not exist. - Destroying fargate stops the API and the dashboard until the last step finishes; the database, Redis and the data stores are not touched.