# Multi-Provider Batch Workflow Examples This document provides practical examples of orchestrating complex deployments and operations across multiple cloud providers using the batch workflow system. ## Table of Contents - [Overview](#overview) - [Workflow 1: Coordinated Multi-Provider Deployment](#workflow-1-coordinated-multi-provider-deployment) - [Workflow 2: Multi-Provider Disaster Recovery Failover](#workflow-2-multi-provider-disaster-recovery-failover) - [Workflow 3: Cost Optimization Workload Migration](#workflow-3-cost-optimization-workload-migration) - [Workflow 4: Multi-Region Database Replication](#workflow-4-multi-region-database-replication) - [Best Practices](#best-practices) - [Troubleshooting](#troubleshooting) ## Overview The batch workflow system enables declarative orchestration of operations across multiple providers with: - **Dependency Tracking**: Define what must complete before what - **Error Handling**: Automatic rollback on failure - **Idempotency**: Safe to re-run workflows - **Status Tracking**: Real-time progress monitoring - **Recovery Checkpoints**: Resume from failure points ## Workflow 1: Coordinated Multi-Provider Deployment **Use Case**: Deploy web application across DigitalOcean, AWS, and Hetzner with proper sequencing and dependencies. **Workflow Characteristics**: - Database created first (dependencies) - Backup storage ready before compute - Web servers scale once database ready - Health checks before considering complete ### Workflow Definition ```yaml # file: workflows/multi-provider-deployment.yml name: multi-provider-app-deployment version: "1.0" description: "Deploy web app across three cloud providers" parameters: do_region: "nyc3" aws_region: "us-east-1" hetzner_location: "nbg1" web_server_count: 3 phases: # Phase 1: Create backup storage first (independent) - name: "provision-backup-storage" provider: "hetzner" description: "Create backup storage volume in Hetzner" operations: - id: "create-backup-volume" action: "create-volume" config: name: "webapp-backups" size: 500 location: "{{ hetzner_location }}" format: "ext4" tags: ["storage", "backup"] on_failure: "alert" on_success: "proceed" # Phase 2: Create database (independent, but must complete before app) - name: "provision-database" provider: "aws" description: "Create managed PostgreSQL database" depends_on: [] # Can run in parallel with Phase 1 operations: - id: "create-rds-instance" action: "create-db-instance" config: identifier: "webapp-db" engine: "postgres" engine_version: "14.6" instance_class: "db.t3.medium" allocated_storage: 100 multi_az: true backup_retention_days: 30 tags: ["database", "primary"] - id: "create-security-group" action: "create-security-group" config: name: "webapp-db-sg" description: "Security group for RDS" depends_on: ["create-rds-instance"] - id: "configure-db-access" action: "authorize-security-group" config: group_id: "{{ create-security-group.id }}" protocol: "tcp" port: 5432 cidr: "10.0.0.0/8" depends_on: ["create-security-group"] timeout: 60 # Phase 3: Create web tier (depends on database being ready) - name: "provision-web-tier" provider: "digitalocean" description: "Create web servers and load balancer" depends_on: ["provision-database"] # Wait for database operations: - id: "create-droplets" action: "create-droplet" config: name: "web-server" size: "s-2vcpu-4gb" region: "{{ do_region }}" image: "ubuntu-22-04-x64" count: "{{ web_server_count }}" backups: true monitoring: true tags: ["web", "production"] timeout: 300 retry: max_attempts: 3 backoff: exponential - id: "create-firewall" action: "create-firewall" config: name: "web-firewall" inbound_rules: - protocol: "tcp" ports: "22" sources: ["0.0.0.0/0"] - protocol: "tcp" ports: "80" sources: ["0.0.0.0/0"] - protocol: "tcp" ports: "443" sources: ["0.0.0.0/0"] depends_on: ["create-droplets"] - id: "create-load-balancer" action: "create-load-balancer" config: name: "web-lb" algorithm: "round_robin" region: "{{ do_region }}" forwarding_rules: - entry_protocol: "http" entry_port: 80 target_protocol: "http" target_port: 80 - entry_protocol: "https" entry_port: 443 target_protocol: "http" target_port: 80 health_check: protocol: "http" port: 80 path: "/health" interval: 10 depends_on: ["create-droplets"] # Phase 4: Network configuration (depends on all resources) - name: "configure-networking" description: "Setup VPN tunnels and security between providers" depends_on: ["provision-web-tier"] operations: - id: "setup-vpn-tunnel-do-aws" action: "create-vpn-tunnel" config: source_provider: "digitalocean" destination_provider: "aws" protocol: "ipsec" encryption: "aes-256" timeout: 120 - id: "setup-vpn-tunnel-aws-hetzner" action: "create-vpn-tunnel" config: source_provider: "aws" destination_provider: "hetzner" protocol: "ipsec" encryption: "aes-256" # Phase 5: Validation and verification - name: "verify-deployment" description: "Verify all resources are operational" depends_on: ["configure-networking"] operations: - id: "health-check-droplets" action: "run-health-check" config: targets: "{{ create-droplets.ips }}" endpoint: "/health" expected_status: 200 timeout: 30 timeout: 300 - id: "health-check-database" action: "verify-database" config: host: "{{ create-rds-instance.endpoint }}" port: 5432 database: "postgres" timeout: 30 - id: "health-check-backup" action: "verify-volume" config: volume_id: "{{ create-backup-volume.id }}" status: "available" # Rollback strategy: if any phase fails rollback: strategy: "automatic" on_phase_failure: "rollback-previous-phases" preserve_data: true # Notifications notifications: on_start: "slack:#deployments" on_phase_complete: "slack:#deployments" on_failure: "slack:#alerts" on_success: "slack:#deployments" # Validation checks pre_flight: - check: "credentials" description: "Verify all provider credentials" - check: "quotas" description: "Verify sufficient quotas in each provider" - check: "dependencies" description: "Verify all dependencies are available" ``` ### Execution Flow ```yaml ┌─────────────────────────────────────────────────────────┐ │ Start Deployment │ └──────────────────┬──────────────────────────────────────┘ │ ┌──────────┴──────────┐ │ │ ▼ ▼ ┌─────────────┐ ┌──────────────────┐ │ Hetzner │ │ AWS │ │ Backup │ │ Database │ │ (Phase 1) │ │ (Phase 2) │ └──────┬──────┘ └────────┬─────────┘ │ │ │ Ready │ Ready └────────┬───────────┘ │ ▼ ┌──────────────────┐ │ DigitalOcean │ │ Web Tier │ │ (Phase 3) │ │ - Droplets │ │ - Firewall │ │ - Load Balancer │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Network Setup │ │ (Phase 4) │ │ - VPN Tunnels │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Verification │ │ (Phase 5) │ │ - Health Checks │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Deployment OK │ │ (Ready to use) │ └──────────────────┘ ``` ## Workflow 2: Multi-Provider Disaster Recovery Failover **Use Case**: Automated failover from primary provider (DigitalOcean) to backup provider (Hetzner) on detection of failure. **Workflow Characteristics**: - Continuous health monitoring - Automatic failover trigger - Database promotion - DNS update - Verification before considering complete ### Workflow Definition ```yaml # file: workflows/multi-provider-dr-failover.yml name: multi-provider-dr-failover version: "1.0" description: "Automated failover from DigitalOcean to Hetzner" parameters: primary_provider: "digitalocean" backup_provider: "hetzner" dns_provider: "aws" health_check_threshold: 3 phases: # Phase 1: Monitor primary provider - name: "monitor-primary" description: "Continuous health monitoring of primary" operations: - id: "health-check-primary" action: "run-health-check" config: provider: "{{ primary_provider }}" resources: ["web-servers", "load-balancer"] checks: - type: "http" endpoint: "/health" expected_status: 200 - type: "database" host: "db.primary.example.com" query: "SELECT 1" - type: "connectivity" test: "ping" interval: 30 # Check every 30 seconds timeout: 300 - id: "aggregate-health" action: "aggregate-metrics" config: source: "{{ health-check-primary.results }}" failure_threshold: 3 # 3 consecutive failures trigger failover # Phase 2: Trigger failover (conditional on failure) - name: "trigger-failover" description: "Activate disaster recovery if primary fails" depends_on: ["monitor-primary"] condition: "{{ aggregate-health.status }} == 'FAILED'" operations: - id: "alert-on-failure" action: "send-notification" config: type: "critical" message: "Primary provider ({{ primary_provider }}) has failed. Initiating failover..." recipients: ["ops-team@example.com", "slack:#alerts"] - id: "enable-backup-infrastructure" action: "scale-up" config: provider: "{{ backup_provider }}" target: "warm-standby-servers" desired_count: 3 instance_type: "cx31" timeout: 300 retry: max_attempts: 3 - id: "promote-database-replica" action: "promote-read-replica" config: provider: "aws" replica_identifier: "backup-db-replica" to_master: true timeout: 600 # Allow time for promotion # Phase 3: Network failover - name: "network-failover" description: "Switch traffic to backup provider" depends_on: ["trigger-failover"] operations: - id: "update-load-balancer" action: "reconfigure-load-balancer" config: provider: "{{ dns_provider }}" record: "api.example.com" old_backend: "do-lb-{{ primary_provider }}" new_backend: "hz-lb-{{ backup_provider }}" - id: "update-dns" action: "update-dns-record" config: provider: "route53" record: "example.com" old_value: "do-lb-ip" new_value: "hz-lb-ip" ttl: 60 - id: "update-cdn" action: "update-cdn-origin" config: cdn_provider: "cloudfront" distribution_id: "E123456789ABCDEF" new_origin: "backup-lb.hetzner.com" # Phase 4: Verify failover - name: "verify-failover" description: "Verify backup provider is operational" depends_on: ["network-failover"] operations: - id: "health-check-backup" action: "run-health-check" config: provider: "{{ backup_provider }}" resources: ["backup-servers"] endpoint: "/health" expected_status: 200 timeout: 30 timeout: 300 - id: "verify-database" action: "verify-database" config: provider: "aws" database: "backup-db-promoted" query: "SELECT COUNT(*) FROM users" expected_rows: "> 0" - id: "verify-traffic" action: "verify-traffic-flow" config: endpoint: "https://example.com" expected_response_time: "< 500 ms" expected_status: 200 # Phase 5: Activate backup fully - name: "activate-backup" description: "Run at full capacity on backup provider" depends_on: ["verify-failover"] operations: - id: "scale-to-production" action: "scale-up" config: provider: "{{ backup_provider }}" target: "all-backup-servers" desired_count: 6 timeout: 600 - id: "configure-persistence" action: "enable-persistence" config: provider: "{{ backup_provider }}" resources: ["backup-servers"] persistence_type: "volume" # Recovery strategy for primary restoration recovery: description: "Restore primary provider when recovered" phases: - name: "detect-primary-recovery" operation: "health-check" target: "primary-provider" success_criteria: "3 consecutive successful checks" - name: "resync-data" operation: "database-resync" direction: "backup-to-primary" timeout: 3600 - name: "failback" operation: "switch-traffic" target: "primary-provider" verification: "100% traffic restored" # Notifications notifications: on_failover_start: "pagerduty:critical" on_failover_complete: "slack:#ops" on_failover_failed: ["pagerduty:critical", "email:cto@example.com"] on_recovery_start: "slack:#ops" on_recovery_complete: "slack:#ops" ``` ### Failover Timeline ```bash Time Event ──────────────────────────────────────────────────── 00:00 Health check detects failure (3 consecutive failures) 00:01 Alert sent to ops team 00:02 Backup infrastructure scaled to 3 servers 00:05 Database replica promoted to master 00:10 DNS updated (TTL=60s, propagation ~2 minutes) 00:12 Load balancer reconfigured 00:15 Traffic verified flowing through backup 00:20 Backup scaled to full production capacity (6 servers) 00:25 Fully operational on backup provider Total RTO: 25 minutes (including DNS propagation) Data loss (RPO): < 5 minutes (database replication lag) ``` ## Workflow 3: Cost Optimization Workload Migration **Use Case**: Migrate running workloads to cheaper provider (DigitalOcean to Hetzner) for cost reduction. **Workflow Characteristics**: - Parallel deployment on target provider - Gradual traffic migration - Rollback capability - Cost tracking ### Workflow Definition ```yaml # file: workflows/cost-optimization-migration.yml name: cost-optimization-migration version: "1.0" description: "Migrate workload from DigitalOcean to Hetzner for cost savings" parameters: source_provider: "digitalocean" target_provider: "hetzner" migration_speed: "gradual" # or "aggressive" traffic_split: [10, 25, 50, 75, 100] # Gradual percentages phases: # Phase 1: Create target infrastructure - name: "create-target-infrastructure" description: "Deploy identical workload on Hetzner" operations: - id: "provision-servers" action: "create-server" config: provider: "{{ target_provider }}" name: "migration-app" server_type: "cpx21" # Better price/performance than DO count: 3 timeout: 300 # Phase 2: Verify target is ready - name: "verify-target" description: "Health checks on target infrastructure" depends_on: ["create-target-infrastructure"] operations: - id: "health-check" action: "run-health-check" config: provider: "{{ target_provider }}" endpoint: "/health" timeout: 300 # Phase 3: Gradual traffic migration - name: "migrate-traffic" description: "Gradually shift traffic to target provider" depends_on: ["verify-target"] operations: - id: "set-traffic-10" action: "set-traffic-split" config: source: "{{ source_provider }}" target: "{{ target_provider }}" percentage: 10 duration: 300 - id: "verify-10" action: "verify-traffic-flow" config: target_percentage: 10 error_rate_threshold: 0.1 - id: "set-traffic-25" action: "set-traffic-split" config: percentage: 25 duration: 600 - id: "set-traffic-50" action: "set-traffic-split" config: percentage: 50 duration: 900 - id: "set-traffic-75" action: "set-traffic-split" config: percentage: 75 duration: 900 - id: "set-traffic-100" action: "set-traffic-split" config: percentage: 100 duration: 600 # Phase 4: Cleanup source - name: "cleanup-source" description: "Remove old infrastructure from source provider" depends_on: ["migrate-traffic"] operations: - id: "verify-final" action: "run-health-check" config: provider: "{{ target_provider }}" duration: 3600 # Monitor for 1 hour - id: "decommission-source" action: "delete-resources" config: provider: "{{ source_provider }}" resources: ["droplets", "load-balancer"] preserve_backups: true # Cost tracking cost_tracking: before: provider: "{{ source_provider }}" estimated_monthly: "$72" after: provider: "{{ target_provider }}" estimated_monthly: "$42" savings: monthly: "$30" annual: "$360" percentage: "42%" ``` ## Workflow 4: Multi-Region Database Replication **Use Case**: Setup database replication across multiple providers and regions for disaster recovery. **Workflow Characteristics**: - Create primary database - Setup read replicas in other providers - Configure replication - Monitor lag ### Workflow Definition ```yaml # file: workflows/multi-region-replication.yml name: multi-region-replication version: "1.0" description: "Setup database replication across providers" phases: # Primary database - name: "create-primary" provider: "aws" operations: - id: "create-rds" action: "create-db-instance" config: identifier: "app-db-primary" engine: "postgres" instance_class: "db.t3.medium" region: "us-east-1" # Secondary replica - name: "create-secondary-replica" depends_on: ["create-primary"] provider: "aws" operations: - id: "create-replica" action: "create-read-replica" config: source: "app-db-primary" region: "eu-west-1" identifier: "app-db-secondary" # Tertiary replica in different provider - name: "create-tertiary-replica" depends_on: ["create-primary"] operations: - id: "setup-replication" action: "setup-external-replication" config: source_provider: "aws" source_db: "app-db-primary" target_provider: "hetzner" replication_slot: "hetzner_replica" replication_type: "logical" # Monitor replication - name: "monitor-replication" depends_on: ["create-tertiary-replica"] operations: - id: "check-lag" action: "monitor-replication-lag" config: replicas: - name: "secondary" warning_threshold: 300 critical_threshold: 600 - name: "tertiary" warning_threshold: 1000 critical_threshold: 2000 interval: 60 ``` ## Best Practices ### 1. Workflow Design - **Define Clear Dependencies**: Explicitly state what must happen before what - **Use Idempotent Operations**: Workflows should be safe to re-run - **Set Realistic Timeouts**: Account for cloud provider delays - **Plan for Failures**: Define rollback strategies - **Test Workflows**: Run in staging before production ### 2. Orchestration - **Parallel Execution**: Run independent phases in parallel for speed - **Checkpoints**: Add verification at each phase - **Progressive Deployment**: Use gradual traffic shifting - **Monitoring Integration**: Track metrics during workflow - **Notifications**: Alert team at key points ### 3. Cost Management - **Calculate ROI**: Track cost savings from optimizations - **Monitor Resource Usage**: Watch for over-provisioning - **Implement Cleanup**: Remove old resources after migration - **Review Regularly**: Reassess provider choices ## Troubleshooting ### Issue: Workflow Stuck in Phase **Diagnosis**: ```bash provisioning workflow status workflow-id --verbose ``` **Solution**: - Increase timeout if legitimate long operation - Check provider logs for actual status - Manually intervene if necessary - Use `--skip-phase` to skip problematic phase ### Issue: Rollback Failed **Diagnosis**: ```bash provisioning workflow rollback workflow-id --dry-run ``` **Solution**: - Review what resources were created - Manually delete resources if needed - Fix root cause of failure - Re-run workflow ### Issue: Data Inconsistency After Failover **Diagnosis**: ```bash provisioning database verify-consistency ``` **Solution**: - Check replication lag before failover - Manually resync if necessary - Use backup to restore consistency - Run validation queries ## Summary Batch workflows enable complex multi-provider orchestration with: - Coordinated deployment across providers - Automated failover and recovery - Gradual workload migration - Cost optimization - Disaster recovery Start with simple workflows and gradually add complexity as you gain confidence.