← Projects
Platform Engineering

A Zero-Downtime AWS→Azure, ECS→AKS Migration

Re-platformed a fintech product across clouds and onto Kubernetes (100+ nodes, zero downtime), and wrote ~40% of its Go services.

zero
downtime during the cross-cloud cutover
100+ nodes
on AKS after migration
2 clouds
AWS to Azure, ECS to AKS, at once
~40%
of LetsBloom's Go services, hand-written

This was two migrations at once: from AWS to Azure, and from ECS to Kubernetes, on a live fintech product that could not go dark. As the sole multi-cloud and Kubernetes engineer on the engagement I led the re-platform end to end, and as one of three application developers I also wrote a large share of the product running on top of it. The trick that made a cross-cloud move survivable was refusing to rebuild the platform in Azure's native shape, and instead standing it on portable primitives so the cloud underneath became something I could swap.

What we were re-platforming

LetsBloom was a security product for a global bank's fintech customers, built at CloudCover. It had grown up on AWS, running on Amazon ECS with the usual supporting cast, and the business needed it on Azure. That is the kind of ask that sounds like a lift-and-shift and turns out to be a re-architecture, because ECS has no equivalent on Azure: moving clouds meant moving orchestrators too.

The product itself: compliant-by-default landing zones
LetsBloom shipped secure-by-design landing zones and blueprints as code. This migration moved the platform they ran on.

Two migrations at once

Because there is no ECS on Azure, the honest target was Kubernetes: re-platform onto AKS and, in the same motion, translate a cloud's worth of ECS-shaped configuration into Kubernetes objects. Every ECS concept needed a home on the other side, and for each one I made a deliberate call: map it to an Azure-native service, or move it to a cloud-neutral tool that would look the same on both clouds.

Every ECS concept had to land somewhere on AKS

Some pieces mapped to Azure-native services; others we deliberately moved to cloud-neutral tools. Tap a row.

AWS / ECSAzure / AKS
ECS task definitionDeployment + Pod spec

The core rewrite. A task definition (container definitions, CPU/memory, environment, roles) becomes a Deployment with a Pod template. The task model gives way to pods managed by a ReplicaSet, with rolling updates handled by Kubernetes instead of ECS.

Each ECS concept had to land somewhere on AKS. The rows marked neutral are the ones I moved off cloud-specific services on purpose.

From a task definition to a Deployment

The unit of work is the clearest example. An ECS task definition and a Kubernetes Deployment describe the same idea, run N copies of these containers, but in different vocabularies. The rewrite is mechanical once you see the mapping: container definitions become a Pod template, desired count becomes replicas, task roles and awslogs give way to workload identity and the cluster's logging path.

yaml
# ECS task definition (before)            # Kubernetes Deployment (after)
{                                          apiVersion: apps/v1
  "family": "bloom-api",                   kind: Deployment
  "cpu": "512",                            metadata:
  "memory": "1024",                          name: bloom-api
  "containerDefinitions": [{               spec:
    "name": "api",                           replicas: 3          # was the ECS service desired count
    "image":                                 template:
      "<acct>.dkr.ecr.../bloom-api",           spec:
    "portMappings": [                            containers:
      { "containerPort": 8080 }                    - name: api
    ]                                                image: <registry>.azurecr.io/bloom-api
  }],                                                ports:
  "taskRoleArn": "arn:aws:iam::..."                    - containerPort: 8080
}                                          # taskRoleArn -> workload identity + Vault

Betting on portable primitives

The reason a two-front migration did not turn into chaos is that the platform never really depended on AWS in the first place. Service discovery ran on HashiCorp Consul, secrets on HashiCorp Vault, network policy on Calico, and every bit of infrastructure was defined in Terraform. Those are the same on ECS and on AKS, so the application never learned its cloud had changed, and the migration became a swap of the substrate under a stable set of primitives.

The migration was really a swap of the substrate

Four cloud-neutral primitives carried the platform, so the cloud underneath became replaceable. Tap one.

HashiCorp Consul· Service discovery + mesh

Services registered with and resolved each other through Consul, not the cloud's own discovery. So the service graph looked identical on ECS and on AKS, and no application had to relearn where its dependencies lived.

Instead of, on AWS
AWS Cloud Map
Instead of, on Azure
Azure DNS-based discovery
The payoff: because the platform stood on these, the AWS to Azure move was a change of substrate under a stable set of primitives, not a rewrite of how the product ran.
Four cloud-neutral tools carried the platform. Tap each to see the AWS and Azure services it let us skip.

Cutting over with zero downtime

A fintech product does not get a maintenance window, so the cutover could never be a single flip. I built the Azure side in parallel with Terraform, replicated state onto it, and validated it under real conditions behind a private endpoint while ECS kept serving. Only then did traffic move, by weight, using DNS and Traffic Manager, with the AWS side held warm the entire time as an instant fail-back. The migration was only called done once there was nothing left to fail back to.

Zero downtime means never betting on a single flip

Both clouds run at once, traffic moves by weight, and rollback stays one control away. Step through it.

Serving: AWS / ECSAzure carries 0%
AWS / ECSAzure / AKS

Terraform stands up the entire Azure side, AKS, ACR, ingress, Consul, Vault, and Calico, while ECS keeps serving every request. Nothing customer-facing has moved yet.

Both clouds run at once and traffic shifts by weight, so rollback is always one control away rather than a rebuild.

The application layer I built on top

I was not only re-platforming it; I was also one of three developers building the product. I wrote roughly 40% of LetsBloom's Go microservices, and two of the pieces I owned were substantial enough to stand on their own.

  • A Casbin-based authentication and authorization framework that centralized access control across the services.
  • Secure Notes, a UI over HashiCorp Vault and its secret engines, so the same Vault the platform trusted also backed a customer-facing feature.
  • The DevSecOps practice and Kubernetes standards the team carried forward, which is what earned two promotions in under two years.
The AuthN/AuthZ framework I built for it
A Casbin-based access-control layer, and how it plugs into an Envoy external-authorization filter.

The task engine behind the workflows

The other piece I owned was orchestration. Bloom's longer-running work, the multi-step jobs that fan out and depend on each other, ran on a distributed task worker I built on Celery, modeling each job as a dependency graph rather than a flat queue. It is the same shape of problem I would later solve again for the landing-zone provisioner.

The distributed DAG task scheduler
How the Celery worker pool executes dependency graphs, with the durable state behind it.

The award, and the culture behind it

CloudCover ran on a simple loop: experiment, learn, know. This migration is the one they handed me the experiment > learn > know spot award for, and I keep the card around because it captures how the place worked. You were rewarded for trying the harder, more correct thing, in this case moving a live product across clouds without anyone noticing, rather than for playing it safe.

CloudCover spot award card reading 'Well done Chris, you've won the experiment > learn > know spot award as your continued pursuit of experimentation has resulted in the smooth migration of Bloom to Azure', dated 05.03.2021.
The experiment > learn > know spot award for the smooth migration of Bloom to Azure, March 2021.
Stack
AzureAKSAzure Container RegistryApplication GatewayAWSAmazon ECSHashiCorp ConsulHashiCorp VaultCalicoTerraformGo