Skip to content

imrajankumar95/the-migration-arc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

The Migration Arc

Multi-cloud container deployment pipeline: from local Docker development to production Kubernetes — deployed across AWS (ECS + EKS) and Azure (AKS).


What This Project Does

Takes a Flask API through four deployment stages, each increasing in complexity:

Phase Platform How It Runs
Phase 1 Local (WSL2) Docker container on localhost
Phase 2 AWS ECS Fargate serverless container via ECR
Phase 3 AWS EKS Kubernetes pods with LoadBalancer
Phase 4 Azure AKS ACR + AKS + GitHub Actions CI/CD

Each phase deploys the same containerized app to a progressively more complex environment.


Architecture

                    ┌──────────────────────────────────┐
                    │         Flask API (app.py)       │
                    │    /  /health  /info  /ping      │
                    └──────────────┬───────────────────┘
                                   │
                            Docker Image
                           (multi-stage build)
                                        │
       ┌──────────────┬─────────────────┼───────────────────────┐
       │              │                 │                       │
  Phase 1: Local     Phase 2: ECS      Phase 3: EKS       Phase 4: AKS
  ──────────────    ─────────────     ─────────────      ─────────────
  Docker run       ECR → Fargate      ECR → K8s Pods      ACR → K8s Pods
  localhost:5000   Public IP:5000     LoadBalancer:80     LoadBalancer:80
                   1 task, 0.25       2 replicas          2 replicas
                   vCPU, 0.5 GB       t3.small nodes      D2s_v3 node

Tech Stack

Tool Purpose
Flask Python API (5 endpoints)
Docker Multi-stage container build
Gunicorn Production WSGI server
GitHub Actions CI: lint, test, build, push to GHCR + ACR
AWS ECR Private container registry (Phase 2-3)
AWS ECS (Fargate) Serverless container deployment
AWS EKS Managed Kubernetes cluster
Azure ACR Private container registry (Phase 4)
Azure AKS Managed Kubernetes cluster
kubectl Kubernetes deployment management
eksctl EKS cluster provisioning

Project Structure

the-migration-arc/
├── app/
│   ├── app.py              # Flask API (5 routes)
│   ├── Dockerfile          # Multi-stage build (builder + runtime)
│   └── requirements.txt    # flask + gunicorn
├── k8s/
│   ├── aws/
│   │   ├── deployment.yaml # K8s Deployment (ECR image, 2 replicas)
│   │   └── service.yaml    # LoadBalancer Service (80 → 5000)
│   └── azure/
│       ├── deployment.yaml # K8s Deployment (ACR image, 2 replicas)
│       └── service.yaml    # LoadBalancer Service (80 → 5000)
├── images/
│   ├── aws/                # ECS + EKS deployment screenshots
│   └── azure/              # AKS deployment screenshots
├── tests/
│   └── test_app.py         # 5 unit tests (pytest)
├── .github/workflows/
│   ├── ci.yml              # CI: lint → test → build → push to GHCR
│   └── azure-deploy.yml    # CI: lint → test → build → push to ACR
├── Makefile                # Local dev commands (build/run/stop/test)
├── DECISIONS.md            # Technical decision log per phase
└── README.md

API Endpoints

Route Response
GET / Service name, status, message
GET /health Health check with UTC timestamp
GET /info Hostname, OS, Python version, app version
GET /ping {"pong": true}
GET /metrics/custom Request count, uptime, environment

Phase Details

Phase 1 - Local Docker (WSL2)

Built and ran the container locally:

make build        # docker build -t migration-arc-app:local ./app
make run          # docker run --rm -p 5000:5000 migration-arc-app:local
curl localhost:5000

Key decisions:

  • Multi-stage Dockerfile - build dependencies stay out of runtime image
  • Non-root user (appuser) for container security
  • Gunicorn with 2 workers instead of Flask dev server

Phase 2 - AWS ECS (Fargate)

Pushed container to ECR, deployed as Fargate task:

  1. Created private ECR repo (migration-arc-flask)
  2. Authenticated Docker to ECR, tagged and pushed image
  3. Created ECS cluster (migration-arc-project-cluster)
  4. Defined task (0.25 vCPU, 0.5 GB, port 5000)
  5. Created service with public IP + security group (TCP 5000 open)

Result: Flask API accessible via public IP on port 5000.

Phase 3 - AWS EKS (Kubernetes)

Created managed Kubernetes cluster, deployed as pods:

  1. Provisioned EKS cluster with eksctl (2x t3.small nodes)
  2. Created Deployment (2 replicas pulling from ECR)
  3. Created LoadBalancer Service (port 80 → container 5000)
  4. App accessible via AWS ELB URL

Result: Flask API running on Kubernetes with load balancing across 2 pods.

Phase 4 - Azure AKS (Kubernetes + CI/CD)

Deployed to Azure Kubernetes Service with container registry and automated pipeline:

  1. Created resource group (migration-arc-rg) in Canada Central
  2. Created Azure Container Registry (migrationarcacr) — Basic SKU
  3. Built and pushed Docker image to ACR (migration-arc-app:v1)
  4. Provisioned AKS cluster (migration-arc-aks) — 1 node, Standard_D2s_v3, Free tier
  5. Attached ACR to AKS via managed identity (AcrPull role)
  6. Applied K8s manifests — 2 replicas + LoadBalancer Service
  7. App accessible via Azure Load Balancer public IP
  8. Created GitHub Actions pipeline (azure-deploy.yml) — auto builds and pushes to ACR on every merge to main

Result: Flask API running on AKS with CI/CD pipeline. Every push to main triggers lint → test → build → push to ACR.


CI/CD Pipelines

Pipeline 1: ci.yml (GitHub Container Registry)

  1. On push/PR to main: Lint with flake8 → Run 5 pytest tests
  2. On merge to main: Build Docker image → Push to GitHub Container Registry (GHCR)
  3. Uses Docker layer caching for fast builds

Pipeline 2: azure-deploy.yml (Azure Container Registry)

  1. On push to main: Lint with flake8 → Run 5 pytest tests
  2. If tests pass: Build Docker image → Push to ACR with commit SHA + latest tags
  3. ACR credentials stored as GitHub Actions secrets
  4. Supports manual trigger via workflow_dispatch

Issues & Solutions

# Issue Root Cause Fix
1 docker build failed - "no such file or directory" Dockerfile is in ./app/, not root. Build context wrong Used make build which runs docker build ./app
2 ECS cluster creation failed - "Unable to assume service linked role" New AWS account, ECS service-linked role did not exist Ran aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com
3 ECS cluster creation failed again - CloudFormation stack conflict First failed attempt left orphaned CloudFormation stack Deleted failed stack in CloudFormation console, retried with new cluster name
4 eksctl create cluster - AccessDeniedException IAM user rajan-admin lacked EKS permissions Added EKS policies + AdministratorAccess
5 YAML parse error - "could not find expected ':'" Mixed tabs and spaces from Windows editor (Notepad) Recreated YAML files in terminal using heredoc
6 Pods stuck in Pending - FailedScheduling t3.micro nodes too small - system pods consumed all capacity Upgraded to t3.small nodes via eksctl create nodegroup
7 AKS node pool rejected B-series VMs B-series (burstable) not allowed for AKS system node pools Switched to D2s_v3 (general purpose)
8 AKS node pool - vCPU quota error Azure for Students has 0 quota for v5 VM families Used v3 family (D2s_v3) which had 10 vCPU quota
9 Service principal creation failed Azure for Students blocks directory-level permissions Used ACR admin credentials for CI/CD instead of service principal
10 GitHub Actions flake8 lint failure PEP 8: missing 2 blank lines before function definitions Fixed spacing in app.py

Cost Notes

Resource Cost (24/7) Notes
AWS
ECS Fargate (0.25 vCPU) ~$9/month Serverless, pay per task
EKS control plane $72/month Fixed cost regardless of nodes
EKS nodes (2x t3.small) ~$30/month EC2 instances
EKS LoadBalancer ~$18/month AWS NLB/ALB
ECR storage ~$0.01/month Just image storage
Azure
AKS control plane $0/month Free tier
AKS node (1x D2s_v3) ~$68/month VM instance
AKS LoadBalancer ~$18/month Azure LB + public IP
ACR Basic ~$5/month Registry storage
Total (all live) ~$220/month
After cleanup ~$0.01/month Only ECR repo kept

Infrastructure was deployed for demonstration, verified working, then torn down to manage costs. Screenshots and deployment evidence in images/ and commit history.


What I Learned

  • Docker multi-stage builds reduce image size and separate build-time from runtime dependencies
  • ECS vs EKS tradeoff: ECS simpler for single containers; EKS worth it when you need scaling, rolling updates, and multi-container orchestration
  • Fargate eliminates server management but costs more per unit than EC2
  • Kubernetes YAML - indentation matters, never edit with editors that mix tabs/spaces
  • IAM least privilege is ideal but impractical for eksctl - it creates VPCs, roles, CloudFormation stacks, and EC2 instances
  • Node sizing matters - t3.micro cannot run app pods because AWS system pods consume most capacity
  • Service-linked roles are auto-created on first use in established accounts but may need manual creation in new accounts
  • AKS Free tier makes Azure competitive for learning - no control plane cost vs EKS $72/month
  • Azure for Students has VM quota limits per family - v5 series had 0 quota, v3 had 10
  • ACR + AKS integration via managed identity is cleaner than manual docker login - one attachment handles auth
  • GitHub Actions secrets keep credentials out of code - ACR admin password never touches the repo
  • Multi-cloud K8s manifests are nearly identical - same Deployment/Service YAML, only the image registry URL changes

Deployment Evidence

Infrastructure was deployed, verified working, then torn down to manage costs. Screenshots below serve as proof of successful deployment.

ECS (Phase 2)

Screenshot What It Shows
ECS Cluster ECS cluster running on Fargate
ECS Service Overview Fargate service with desired count and running tasks
ECS Task Individual Fargate task with public IP assigned
ECS Flask Response Flask API responding on ECS public IP:5000

EKS (Phase 3)

Screenshot What It Shows
CloudFormation Stacks CloudFormation stacks created by eksctl for EKS cluster + node groups

AKS (Phase 4)

Screenshot What It Shows
AKS Cluster AKS cluster running in Canada Central
ACR Repository Docker image pushed to ACR with v1 tag
Resource Group Resource group with AKS + ACR resources
kubectl pods 2 pods running on AKS node
kubectl service LoadBalancer service with external IP
Flask Root Flask API root endpoint on AKS
Flask Health Health endpoint with timestamp
Flask Info Info endpoint showing K8s pod hostname
Flask Ping Ping endpoint responding

Supporting Infrastructure

Screenshot What It Shows
ECR Image Docker image pushed to ECR private registry
IAM Policies IAM user with ECR, ECS, and EKS policies attached
Budget Alert $5/month budget alert configured

How to Run Locally

git clone https://github.com/imrajankumar95/the-migration-arc.git
cd the-migration-arc
make build
make run
# Visit http://localhost:5000

Run tests:

make test

Author

Rajan Kumar - Cloud Computing student at George Brown College, Toronto. Building toward a Cloud/DevOps co-op role (Fall 2026).

About

Multi-cloud container pipeline: Docker → AWS ECR/ECS/EKS + Azure ACR/AKS, provisioned with Terraform. CI/CD via Azure DevOps. Local dev with Vagrant + K3s.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors