Cloud Migration: from On-Premise and Between Providers
Production server with an 8-year history: three generations of developers, no documentation, crontab -l shows 47 tasks, half of which are unclear. MySQL 5.6, EOL more than two years ago. PHP 7.2. A bunch of bash scripts in /opt/scripts/ without git. The task is to move this to Yandex Cloud in two months without service interruption. Here's where we start.
Pre-Migration Audit: What Actually Lives on the Server
The first thing we do is a full inventory. Not from the team lead's memory, but with tools.
# What is running and listening on ports ss -tlnp # All processes with their cmdline ps auxf # Cron of all users for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; crontab -u $user -l 2>/dev/null; done # PHP application dependencies composer show --installed # Installed packages with date dpkg-query -W -f='${Package} ${Version} ${Status}\n' | grep installed # Hardcoded localhost in configs grep -r "127.0.0.1\|localhost" /etc/app/ /var/www/ --include='*.{php,yml,conf,env}' Systemd units, initd scripts, supervisor configs, nginx virtual hosts — everything is recorded. Non-obvious point: ss -tlnp often shows services that no one on the team knows about. Memcached on a non-standard port, internal HTTP metrics server, old Zookeeper. Each such service is a dependency that must be either migrated or deliberately turned off.
Load profile. We are interested in peak values over the last 90 days — not averages. If Grafana or Zabbix is not configured, we look at sar (if sysstat is installed), /var/log/mysql/slow_query.log, vmstat 1 60 during peak load. Without understanding peaks, it's impossible to choose the right size of cloud instances.
Dependencies between services. On monolithic servers, everything is connected directly via localhost. In the cloud, services will be spread across different IPs — we need a diagram: who connects to whom, which ports, are there hardcoded 127.0.0.1 in configs. grep -r finds this quickly, but also check env files, config/*.php, and application.yml.
How to Choose a Migration Strategy?
Two main approaches — lift-and-shift and re-platform. The choice between them is an architectural decision, not a technical one.
Lift-and-shift — take what is and move it "as is" to cloud VMs. Fast, low risk, minimal code changes. The result is the same monolith, only in the cloud. No autoscaling, managed services, elastic pricing. Justified if: deadline is tight, codebase hasn't changed for years, team is small.
Re-platform — at the migration stage, switch from self-managed components to managed services. MySQL 5.6 → Yandex Managed PostgreSQL 15. Redis on VM → Yandex Managed Redis. File storage → Object Storage. This takes more time, but the result is a truly cloud architecture. Risk: incompatibilities when changing databases, code changes that need testing.
Third option — re-architect (containerization, microservices) — is not advisable within migration. It's a separate project. Combining cloud migration with rewriting architecture is a direct path to failure of both.
Comparison of approaches:
| Criteria | Lift-and-shift | Re-platform |
|---|---|---|
| Execution time | 2-3 weeks | 4-8 weeks |
| Code changes | Minimal | Moderate (DB driver changes, file paths) |
| Risks | Low | Medium (DB incompatibilities) |
| Cloud benefit | Low (same VMs) | High (managed services, autoscaling) |
| Typical savings | 10-20% | 30-50% on infrastructure |
Data Migration Patterns
This is the most difficult part. Data is the only thing that cannot be rolled back.
Strangler Fig for Gradual Transfer
Strangler Fig pattern (described in Martin Fowler's book "Patterns of Enterprise Application Architecture"): new requests are gradually redirected to the new infrastructure, while the old one runs in parallel. A load balancer (nginx, HAProxy) directs part of the traffic to new servers, part to old ones. The ratio changes as functionality is verified.
upstream backend_old { server 192.168.1.100:8080 weight=9; } upstream backend_new { server 10.0.1.50:8080 weight=1; } # canary: 10% to new, 90% to old split_clients "${remote_addr}${date_gmt}" $backend { 10% backend_new; * backend_old; } For stateless services this works well. For stateful — bidirectional data replication is needed during the parallel operation period.
MySQL → Managed PostgreSQL: Real Pitfalls
Direct dump does not work between different DBMS. Tools: pgloader, AWS Schema Conversion Tool (works even outside AWS), manual DDL conversion.
pgloader can read from MySQL and write to PostgreSQL directly:
LOAD DATABASE FROM mysql://user:pass@old-server/mydb INTO postgresql://user:pass@new-server/mydb WITH include drop, create tables, create indexes, reset sequences, foreign keys SET MySQL PARAMETERS net_read_timeout = '120', net_write_timeout = '120' CAST type bigint to bigint drop typemod, type varchar to text; But pgloader does not handle MySQL specifics incompatible with PostgreSQL:
-
ENUMin MySQL converts toTEXTor custom type — must be decided before migration - MySQL-specific functions in stored procedures (
GROUP_CONCAT,IFNULL,DATE_FORMAT) — rewrite manually to PostgreSQL equivalents -
utf8mb4in MySQL =utf8in PostgreSQL (PostgreSQL always uses 4-byte UTF-8), butCOLLATIONmust be explicitly set - Auto-increment fields: in PostgreSQL it's
SERIALorGENERATED ALWAYS AS IDENTITY— after import, reset sequences viasetval
Post-migration check: SELECT COUNT(*) FROM for each table on both servers, compare hashes of key records. A discrepancy of 1 row out of a million is a catastrophe if it's an order or transaction.
Real-Time Synchronization During Cut-Over
Cut-over is the moment of switching DNS/balancer from old to new server. Goal: minimize the window when data on the old server changes and does not reach the new one.
For MySQL → MySQL (lift-and-shift): set up replication old-master → new-slave during migration. Cut-over:
- Enable maintenance mode in the application (or switch balancer to a "technical works" page)
- Wait until
SHOW SLAVE STATUSon the new server showsSeconds_Behind_Master: 0 - On old:
FLUSH TABLES WITH READ LOCK - Check
SHOW MASTER STATUS— log position - On new:
STOP SLAVEafter reaching the same position - Change connection string in the application → restart
-
UNLOCK TABLESon old - Switch DNS/balancer
Read-lock window — seconds, not minutes. If longer — something went wrong, rollback.
File and Object Storage Migration
Static files, user uploads, backups — usually gigabytes or terabytes. rsync with progress and checksumming:
rsync -avz --checksum --progress \ --exclude='*.tmp' \ /var/www/uploads/ \ user@new-server:/var/www/uploads/ For terabyte volumes — rsync with --bwlimit to avoid saturating the old server's channel during operation. First run is done in advance (a day or two before cut-over), second — right before switching, only delta.
When moving to Object Storage, use rclone sync with 16 parallel streams. After transfer — CDN in front of Object Storage. Yandex Cloud CDN is configured with the bucket as origin. Public application URL changes: https://cdn.example.com/uploads/file.jpg instead of /uploads/file.jpg. This is a code change that needs testing.
How to Migrate Between Cloud Providers?
Transition from AWS to Hetzner or between Russian providers is a separate scenario. Kubernetes workloads move relatively cleanly: export manifests via kubectl get all -A -o yaml, replace cloud-specific annotations (StorageClass, LoadBalancer annotations), deploy to the new cluster.
Velero for backup+restore of Kubernetes resources:
# Backup on source velero backup create full-backup \ --include-namespaces=production,staging \ --storage-location=aws-s3 # Restore on target cluster velero restore create --from-backup full-backup \ --namespace-mappings "production:production" PersistentVolumes — trickier. Velero does volume snapshots via CSI driver, but snapshots are provider-specific. Between providers: stop pods, copy data via a temporary rsync pod, create PVC on new cluster, restore data, start pods.
Managed services (RDS, ElastiCache, managed Kafka) when changing provider become self-managed or other managed analogues. This is not a technical task — it's an architectural decision with feature parity analysis.
Why is a Rollback Plan Mandatory?
Any migration without a rollback plan is an adventure. The rollback plan is written before work begins, not during.
Minimum rollback plan:
- Rollback conditions: what exactly must break to trigger rollback
- Point of no return: after which actions rollback is impossible or costs more than fixing the problem
- Specific commands to switch back to the old server
- Person responsible for the rollback decision
- Time limit: if after X hours the problem is not solved — automatic rollback
Example cut-over checklist
- [ ] Full data backup (DB dump + files) before start
- [ ] Check replication:
Seconds_Behind_Master: 0 - [ ] Enable maintenance page
- [ ] Execute
FLUSH TABLES WITH READ LOCK - [ ] Record binlog position
- [ ] Switch connection string in application
- [ ] Release read lock
- [ ] Switch DNS TTL to 60 seconds (in advance)
- [ ] Verify functionality on new server (smoke test)
- [ ] Disconnect old server from network (not shutdown, but isolation)
- [ ] Monitor for 24 hours
Typical Timelines
| Migration type | Scale | Timeline |
|---|---|---|
| Lift-and-shift, 1-3 services | Small (< 50 GB data) | 2-3 weeks |
| Re-platform, DB change | Medium (50-500 GB) | 4-8 weeks |
| Full infrastructure with Kubernetes | Large | 2-4 months |
| Migration between cloud providers | Any | +30-50% to base |
Cost is estimated after audit of current infrastructure. Key factors: data volume, number of dependencies, allowable downtime window, and rollback requirements.
What's Included in the Work
- Detailed audit of current infrastructure with documentation of all dependencies
- Selection of migration strategy (lift-and-shift or re-platform) with justification
- Phased data transfer with integrity verification
- Configuration of managed services (DBMS, Redis, Object Storage, CDN)
- Update of connection strings, env files, and CI/CD pipelines
- Development and testing of rollback plan
- Monitoring after cut-over for 48 hours
- Documentation of new infrastructure (diagrams, runbook)
- Training of client's team on working with cloud services
Our Experience and Guarantees
Migrations are handled by engineers with AWS Solutions Architect, CKA and Terraform Associate certifications. Average experience in cloud infrastructure — 7+ years. Over the last 5 years, we have successfully completed more than 60 transfers, including projects with 99.99% fault tolerance. We guarantee:
- Data integrity at every stage (checksums, hash verification)
- Downtime not exceeding the agreed window (usually 15–30 minutes)
- Full support for 2 weeks after migration completion
We will evaluate your project — send us a description of your current infrastructure, and we will propose a strategy with approximate timelines.
Order an audit of your current infrastructure — we will identify all hidden dependencies and prepare a detailed cloud migration plan. Get a consultation on provider and strategy selection.







