Online Recruitment Platform

DevOps Engineer & SRE

2018 – 2020

The Challenge

An online recruitment platform needed to decommission its legacy, high-maintenance on-premises datacenter in Ireland and retire an aging .NET monolith. More than 50 critical business services running on mixed Linux/Windows fleets had to be moved into a multi-account AWS setup (separate production and QA/staging accounts). The primary constraints were zero downtime for end-users, no transactional data loss during database migration, and breaking the monolith apart into independently deployable services without disrupting ongoing sprint delivery.

The Solution

Directed a multi-account AWS migration (EC2, ASG, Terraform, Route 53) across separate production and QA accounts; built golden AMIs with HashiCorp Packer and pushed them to a ~40-instance .NET fleet through AWS CodeDeploy rolling updates so releases never took the site offline. In parallel, decomposed the .NET monolith into Docker-based microservices on Kubernetes (EKS), with images stored in a private Amazon ECR registry. Databases were migrated from the Ireland datacenter to AWS RDS over AWS Direct Connect using AWS Database Migration Service (DMS), with Amazon Redshift maintained for downstream analytics. Delivery was automated through GoCD pipelines enforcing semantic versioning, coordinated with Agile sprint planning, and validated in dedicated QA environments running automated Selenium test suites. Dynatrace was rolled out across the new AWS estate for full-stack observability. Once the AWS environment reached parity, all DNS records were cut over and the Ireland datacenter was fully decommissioned.

Key Business Outcomes

  • Zero Downtime Cutover: Successfully migrated all 50+ business services and cut over all DNS records with 0 minutes of user-facing downtime.
  • Monolith to Microservices: Decomposed the legacy .NET monolith into containerized microservices running on Kubernetes (EKS), improving deployment independence.
  • Automated VM Image Builds: Cut base OS patching and VM configuration times significantly via automated Packer pipeline templates across ~40 ASG-managed instances.
  • Database & Analytics Migration: Migrated production databases via Direct Connect and AWS DMS into RDS, with Redshift maintained for BI/analytics workloads.
  • Full-Stack Observability: Rolled out Dynatrace across the new AWS estate, replacing legacy on-prem monitoring.
  • Release Governance: Established GoCD pipelines with semantic versioning and automated Selenium-driven QA environments, aligned to sprint planning cadence.

Core Technologies

Image & Build Automation

HashiCorp Packer, AWS AMI, Auto Scaling Groups (ASG), .NET, Windows Server Sysprep

Containers & Modernization

Kubernetes (EKS), Docker, Amazon ECR, Monolith Decomposition, Microservices

IaC & Multi-Account

HashiCorp Terraform, AWS Organizations (Multi-Account), AWS Route 53

Database Migration

AWS Direct Connect, AWS DMS, AWS RDS SQL Server (Multi-AZ), Amazon Redshift

CI/CD & QA

GoCD Pipelines, AWS CodeDeploy, Semantic Versioning, Selenium, Agile Sprint Planning

Observability

Dynatrace APM, AWS CloudWatch Alerts, RPO/RTO Verification

Multi-Account Migration & Monolith-to-Microservices Architecture

The diagram below illustrates the full migration: databases moved from the legacy Ireland datacenter into AWS RDS over Direct Connect via AWS DMS; the .NET monolith split into an ASG-managed instance fleet and an EKS microservices tier across separate production and QA/staging AWS accounts; GoCD driving semantically-versioned, Selenium-validated releases; Dynatrace providing full-stack observability; and a final Route 53 DNS cutover retiring the Ireland datacenter.

graph TD
  subgraph LegacyDC ["Legacy Ireland On-Premises Datacenter"]
    Monolith[".NET Monolith App Servers"]
    OldDB["Primary SQL Server (Source)"]
    Monolith --> OldDB
  end
  OldDB -->|AWS Direct Connect| DMS["AWS DMS (Database Migration Service)"]
  DMS --> RDS["AWS RDS SQL Server (Target)"]
  subgraph ProdAccount ["AWS Account: Production"]
    ASG["Auto Scaling Group (~40 .NET EC2 Instances)"]
    EKS["EKS Cluster (Microservices, decomposed from Monolith)"]
    Redshift["Amazon Redshift (Analytics DW)"]
    ASG --> RDS
    EKS --> RDS
    RDS --> Redshift
  end
  subgraph QAAccount ["AWS Account: QA / Staging"]
    Selenium["QA Environments (Selenium Test Suites)"]
  end
  subgraph CICDPipeline ["CI/CD - GoCD"]
    Packer["HashiCorp Packer (Golden AMIs)"]
    Terraform["Terraform (Multi-Account IaC)"]
    GoCD["GoCD Pipelines (Semantic Versioning)"]
    Packer --> GoCD
    Terraform --> GoCD
    GoCD -->|Deploys| ASG
    GoCD -->|Deploys| EKS
    GoCD -->|Deploys| Selenium
  end
  subgraph ObsStack ["Observability"]
    Dynatrace["Dynatrace APM"]
  end
  ASG --> Dynatrace
  EKS --> Dynatrace
  Route53["AWS Route 53 DNS"] -->|Full DNS Cutover| ASG
  Monolith -.->|Decommissioned Post-Cutover| Route53
            

Packer Template for Golden CentOS AMI

The following Packer HCL configuration demonstrates how golden CentOS images were built, hardened, and pushed to AWS for deployment.

centos_golden_image.pkr.hcl
packer {
  required_plugins {
    amazon = {
      version = ">= 1.2.0"
      source  = "github.com/hashicorp/amazon"
    }
  }
}

source "amazon-ebs" "centos" {
  ami_name      = "centos-golden-{{timestamp}}"
  instance_type = "t3.medium"
  region        = "eu-west-1"
  source_ami_filter {
    filters = {
      name                = "CentOS Stream 9*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["123456789012"] # AWS Account ID
  }
  ssh_username = "centos"
}

build {
  sources = ["source.amazon-ebs.centos"]

  provisioner "shell" {
    inline = [
      "sudo dnf update -y",
      "sudo dnf install -y htop wget curl epel-release",
      "sudo systemctl enable firewalld"
    ]
  }
}

Route 53 Weighted Record (Terraform)

Terraform code used to declare weighted routing rules for canary releases during the database switchover.

route53-weighted.tf
resource "aws_route53_record" "app_canary_old" {
  zone_id = var.route53_zone_id
  name    = "app.enterprise-services.com"
  type    = "A"
  ttl     = "60"

  weighted_routing_policy {
    weight = 90
  }

  set_identifier = "legacy-ireland-datacenter"
  records        = [var.legacy_datacenter_public_ip]
}

resource "aws_route53_record" "app_canary_new" {
  zone_id = var.route53_zone_id
  name    = "app.enterprise-services.com"
  type    = "A"
  ttl     = "60"

  weighted_routing_policy {
    weight = 10
  }

  set_identifier = "aws-dublin-cloud"
  records        = [aws_lb.production_alb.dns_name]
}