UNPKG

@chimoney.io/iaas-k8s-deployment

Version:

Kubernetes Infrastructure as a Service deployment package for streamlined multi-cloud environments

689 lines 29.7 kB
import { PlanTier, getTierConfiguration, validateTierConfiguration, PLAN_TIER_DEFINITIONS, } from "../types/plans.js"; export class TierMigrationManager { constructor(tierCalculator, kubecostClient, logger) { this.tierCalculator = tierCalculator; this.kubecostClient = kubecostClient; this.logger = logger; } /** * Plan a migration between tiers */ async planMigration(companyName, fromTier, toTier, namespace) { this.logger.info(`Planning migration for ${companyName}: ${fromTier} -> ${toTier}`); if (fromTier === toTier) { throw new Error("Source and target tiers cannot be the same"); } const targetNamespace = namespace || `shared-${companyName.toLowerCase()}`; const migrationType = this.getMigrationType(fromTier, toTier); const plan = { companyName, fromTier, toTier, namespace: targetNamespace, migrationType, requiresDowntime: migrationType === "downgrade", estimatedDuration: this.estimateMigrationDuration(fromTier, toTier), preChecks: [], steps: [], rollbackSteps: [], warnings: [], }; // Add pre-checks plan.preChecks.push(`Validate current ${fromTier} tier resources`); plan.preChecks.push(`Check cluster capacity for ${toTier} tier`); plan.preChecks.push("Backup current configuration"); plan.preChecks.push("Verify current resource usage"); plan.preChecks.push("Check for pending operations"); // Add migration steps if (migrationType === "upgrade") { plan.steps.push("Create backup of current configuration"); plan.steps.push(`Generate ${toTier} tier configuration`); plan.steps.push("Update resource quotas to accommodate new resources"); plan.steps.push(`Scale up services to ${toTier} tier specifications`); plan.steps.push("Update limit ranges"); plan.steps.push("Apply new tier labels and annotations"); plan.steps.push("Verify all services are running with new resources"); plan.steps.push("Update Kubecost configuration if enabled"); plan.steps.push("Verify monitoring and alerting configuration"); } else { plan.steps.push("Create backup of current configuration"); plan.steps.push("Verify resource usage fits within target tier limits"); plan.steps.push("Validate that current workload can run on smaller resources"); plan.steps.push(`Scale down services to ${toTier} tier specifications`); plan.steps.push("Update resource quotas and limits"); plan.steps.push("Apply new tier labels and annotations"); plan.steps.push("Monitor service health during downscale"); plan.steps.push("Update Kubecost configuration if enabled"); plan.steps.push("Verify all services remain healthy"); } // Add rollback steps plan.rollbackSteps.push(`Restore ${fromTier} tier configuration from backup`); plan.rollbackSteps.push("Revert resource quotas and limits"); plan.rollbackSteps.push("Restore original service configurations"); plan.rollbackSteps.push("Remove new tier labels and annotations"); plan.rollbackSteps.push("Verify service health after rollback"); plan.rollbackSteps.push("Restore Kubecost configuration"); // Add warnings if (migrationType === "downgrade") { plan.warnings.push("Downgrade may result in service degradation"); plan.warnings.push("Ensure current resource usage fits within target tier limits"); plan.warnings.push("Some features available in higher tiers may be disabled"); plan.warnings.push("Performance may be reduced due to lower resource allocation"); } if (this.tierRequiresDowntime(fromTier, toTier)) { plan.warnings.push("This migration may cause brief service interruption"); } // Calculate cost impact try { const currentCost = this.tierCalculator.calculateMonthlyCost(fromTier); const newCost = this.tierCalculator.calculateMonthlyCost(toTier); plan.costImpact = { currentMonthlyCost: currentCost, newMonthlyCost: newCost, difference: newCost - currentCost, }; if (plan.costImpact.difference > 0) { plan.warnings.push(`Migration will increase monthly cost by $${plan.costImpact.difference}`); } else if (plan.costImpact.difference < 0) { plan.warnings.push(`Migration will reduce monthly cost by $${Math.abs(plan.costImpact.difference)}`); } } catch (error) { plan.warnings.push(`Could not calculate cost impact: ${error instanceof Error ? error.message : String(error)}`); } return plan; } /** * Execute a migration plan */ async executeMigration(companyName, fromTier, toTier, namespace, options = {}) { const startTime = Date.now(); try { this.logger.info(`Executing migration for ${companyName}: ${fromTier} -> ${toTier}`); const plan = await this.planMigration(companyName, fromTier, toTier, namespace); if (options.dryRun) { this.logger.info("Dry run mode - no actual changes will be made"); return { success: true, duration: "0s", details: { dryRun: true, plan, }, }; } // Run pre-checks unless skipped if (!options.skipPreChecks) { this.logger.info("Running pre-checks..."); const preCheckResults = await this.runPreChecks(plan, options.k8sProvider); if (!preCheckResults.every((check) => check.passed) && !options.force) { const failedChecks = preCheckResults.filter((check) => !check.passed); throw new Error(`Pre-checks failed: ${failedChecks.map((c) => c.error).join(", ")}`); } } // Execute migration steps const executionResults = []; for (const [index, step] of plan.steps.entries()) { this.logger.info(`Executing step ${index + 1}/${plan.steps.length}: ${step}`); try { await this.executeStep(step, plan, options.k8sProvider); executionResults.push({ step, success: true }); // Add delay between steps for safety if (index < plan.steps.length - 1) { await new Promise((resolve) => setTimeout(resolve, 2000)); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`Step failed: ${step} - ${errorMessage}`); executionResults.push({ step, success: false, error: errorMessage }); // If a critical step fails, attempt rollback throw new Error(`Migration failed at step: ${step} - ${errorMessage}`); } } const duration = `${Math.round((Date.now() - startTime) / 1000)}s`; this.logger.info(`Migration completed successfully in ${duration}`); return { success: true, duration, details: { fromTier, toTier, namespace: plan.namespace, stepsExecuted: executionResults.length, executionResults, costImpact: plan.costImpact, }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`Failed to execute migration: ${errorMessage}`); return { success: false, error: errorMessage, rollbackRequired: true, details: { fromTier, toTier, failurePoint: errorMessage, }, }; } } /** * Execute rollback of a failed migration */ async rollbackMigration(companyName, fromTier, toTier, namespace, options = {}) { const startTime = Date.now(); try { this.logger.info(`Rolling back migration for ${companyName}: ${toTier} -> ${fromTier}`); const plan = await this.planMigration(companyName, toTier, fromTier, namespace); // Execute rollback steps for (const [index, step] of plan.rollbackSteps.entries()) { this.logger.info(`Executing rollback step ${index + 1}/${plan.rollbackSteps.length}: ${step}`); try { await this.executeStep(step, plan, options.k8sProvider); // Add delay between steps if (index < plan.rollbackSteps.length - 1) { await new Promise((resolve) => setTimeout(resolve, 2000)); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`Rollback step failed: ${step} - ${errorMessage}`); // Continue with other rollback steps even if one fails } } const duration = `${Math.round((Date.now() - startTime) / 1000)}s`; this.logger.info(`Rollback completed in ${duration}`); return { success: true, duration, details: { rollbackFrom: toTier, rollbackTo: fromTier, namespace: plan.namespace, }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); this.logger.error(`Rollback failed: ${errorMessage}`); return { success: false, error: errorMessage, details: { rollbackFailure: true, error: errorMessage, }, }; } } /** * Validate if a migration is possible and safe */ async validateMigration(companyName, fromTier, toTier, namespace, options = {}) { const errors = []; const warnings = []; const recommendations = []; try { // Basic validation if (fromTier === toTier) { errors.push("Source and target tiers cannot be the same"); } if (!Object.values(PlanTier).includes(fromTier)) { errors.push(`Invalid source tier: ${fromTier}`); } if (!Object.values(PlanTier).includes(toTier)) { errors.push(`Invalid target tier: ${toTier}`); } // Get tier configurations const fromConfig = getTierConfiguration(fromTier); const toConfig = getTierConfiguration(toTier); // Validate tier configurations const fromValidation = validateTierConfiguration(fromConfig); const toValidation = validateTierConfiguration(toConfig); if (fromValidation.length > 0) { errors.push(`Source tier configuration invalid: ${fromValidation.join(", ")}`); } if (toValidation.length > 0) { errors.push(`Target tier configuration invalid: ${toValidation.join(", ")}`); } // Check migration direction const migrationType = this.getMigrationType(fromTier, toTier); if (migrationType === "downgrade") { warnings.push("Downgrade migrations require careful resource usage validation"); recommendations.push("Monitor current resource usage before proceeding"); recommendations.push("Consider running a dry-run first"); } // Resource usage validation for downgrades if (migrationType === "downgrade" && options.checkResourceUsage) { try { const currentUsage = await this.getCurrentResourceUsage(companyName, namespace); const targetLimits = this.getTierResourceLimits(toTier); if (currentUsage.cpu > targetLimits.cpu) { errors.push(`Current CPU usage (${currentUsage.cpu}m) exceeds target tier limit (${targetLimits.cpu}m)`); } if (currentUsage.memory > targetLimits.memory) { errors.push(`Current memory usage (${currentUsage.memory}Mi) exceeds target tier limit (${targetLimits.memory}Mi)`); } if (currentUsage.storage > targetLimits.storage) { errors.push(`Current storage usage (${currentUsage.storage}Gi) exceeds target tier limit (${targetLimits.storage}Gi)`); } } catch (error) { warnings.push("Could not validate current resource usage"); } } // Network policies validation if (fromConfig.resourceAllocation.networkPoliciesEnabled && !toConfig.resourceAllocation.networkPoliciesEnabled) { warnings.push("Target tier disables network policies - this may reduce security"); recommendations.push("Consider upgrading to a tier with network policies enabled"); } // Cost impact validation try { const fromCost = this.tierCalculator.calculateMonthlyCost(fromTier); const toCost = this.tierCalculator.calculateMonthlyCost(toTier); const costDelta = toCost - fromCost; if (costDelta > 500) { warnings.push(`Migration will significantly increase costs by $${costDelta}/month`); } } catch (error) { warnings.push("Could not calculate cost impact"); } // General recommendations if (migrationType === "upgrade") { recommendations.push("Upgrade migrations are generally safe and can be performed with minimal downtime"); } recommendations.push("Always backup your configuration before performing migrations"); recommendations.push("Monitor service health during and after migration"); } catch (error) { errors.push(`Validation failed: ${error instanceof Error ? error.message : String(error)}`); } return { isValid: errors.length === 0, errors, warnings, recommendations, }; } /** * Get current resource usage for a company/namespace */ async getCurrentResourceUsage(companyName, namespace) { const targetNamespace = namespace || `shared-${companyName.toLowerCase()}`; try { // Use Kubecost client to get current usage // Note: Getting usage by company name isn't directly supported in current getTierCostSummary method // This is a placeholder implementation that would need actual tier information // For now, we'll return conservative estimates this.logger.warn("getCurrentResourceUsage: Actual usage retrieval not implemented, returning conservative estimates"); // In real implementation, you'd need to determine the current tier or have a different method // const costs = await this.kubecostClient.getTierCostSummary( // currentTier, // Need to determine current tier first // targetNamespace, // companyName, // "1d" // Last 24 hours // ); return { cpu: 0, // Conservative estimate - would use costs.usage.cpuCoreHours * 1000 memory: 0, // Conservative estimate - would use costs.usage.ramGBHours * 1024 storage: 0, // Conservative estimate - would use costs.usage.storageGBHours }; } catch (error) { this.logger.warn(`Could not get current resource usage: ${error instanceof Error ? error.message : String(error)}`); // Return conservative estimates return { cpu: 0, memory: 0, storage: 0, }; } } /** * Get resource limits for a specific tier */ getTierResourceLimits(tier) { const tierDef = PLAN_TIER_DEFINITIONS[tier]; const quota = tierDef.quota; return { cpu: parseInt(quota.limits.cpu.replace("m", "")), memory: parseInt(quota.limits.memory.replace(/[^0-9]/g, "")), storage: parseInt(quota.limits.storage.replace(/[^0-9]/g, "")), }; } /** * Run pre-checks before migration */ async runPreChecks(plan, k8sProvider) { const checks = []; // Check 1: Validate tier configurations checks.push({ name: "Tier Configuration Validation", description: "Validate source and target tier configurations", passed: true, // This would have failed earlier if invalid }); // Check 2: Check namespace exists (for shared deployments) if (k8sProvider) { try { // This is a simplified check - in real implementation you'd use k8s API checks.push({ name: "Namespace Validation", description: `Verify namespace ${plan.namespace} exists and is accessible`, passed: true, }); } catch (error) { checks.push({ name: "Namespace Validation", description: `Verify namespace ${plan.namespace} exists and is accessible`, passed: false, error: error instanceof Error ? error.message : String(error), }); } } // Check 3: Verify no other migrations in progress checks.push({ name: "Migration Lock Check", description: "Ensure no other migrations are currently in progress", passed: true, // In real implementation, you'd check for migration locks }); // Check 4: Resource usage validation for downgrades if (plan.migrationType === "downgrade") { try { const currentUsage = await this.getCurrentResourceUsage(plan.companyName, plan.namespace); const targetLimits = this.getTierResourceLimits(plan.toTier); const resourcesWithinLimits = currentUsage.cpu <= targetLimits.cpu && currentUsage.memory <= targetLimits.memory && currentUsage.storage <= targetLimits.storage; checks.push({ name: "Resource Usage Validation", description: "Verify current usage fits within target tier limits", passed: resourcesWithinLimits, error: resourcesWithinLimits ? undefined : "Current resource usage exceeds target tier limits", details: { currentUsage, targetLimits, }, }); } catch (error) { checks.push({ name: "Resource Usage Validation", description: "Verify current usage fits within target tier limits", passed: false, error: `Failed to check resource usage: ${error instanceof Error ? error.message : String(error)}`, }); } } return checks; } /** * Execute a single migration step */ async executeStep(step, plan, k8sProvider) { // This is a simplified implementation - in a real system you'd implement // actual Kubernetes operations for each step type if (step.includes("backup")) { await this.createConfigurationBackup(plan.companyName, plan.namespace); } else if (step.includes("resource quota")) { await this.updateResourceQuota(plan, k8sProvider); } else if (step.includes("scale")) { await this.scaleServices(plan, k8sProvider); } else if (step.includes("labels")) { await this.updateTierLabels(plan, k8sProvider); } else if (step.includes("limit range")) { await this.updateLimitRange(plan, k8sProvider); } else if (step.includes("Kubecost")) { await this.updateKubecostConfiguration(plan); } else { // Generic step - simulate work await new Promise((resolve) => setTimeout(resolve, 1000)); } this.logger.debug(`Completed step: ${step}`); } /** * Create configuration backup */ async createConfigurationBackup(companyName, namespace) { this.logger.info(`Creating configuration backup for ${companyName} in namespace ${namespace}`); // In real implementation, this would backup current Kubernetes resources await new Promise((resolve) => setTimeout(resolve, 1000)); } /** * Update resource quota for new tier */ async updateResourceQuota(plan, k8sProvider) { this.logger.info(`Updating resource quota for ${plan.toTier} tier`); if (k8sProvider) { const newQuota = this.tierCalculator.generateResourceQuota(getTierConfiguration(plan.toTier), plan.namespace); // In real implementation, apply the new resource quota this.logger.debug(`Generated new resource quota`, newQuota); } await new Promise((resolve) => setTimeout(resolve, 2000)); } /** * Scale services to new tier specifications */ async scaleServices(plan, k8sProvider) { this.logger.info(`Scaling services to ${plan.toTier} tier specifications`); const targetConfig = getTierConfiguration(plan.toTier); const services = targetConfig.resourceAllocation.services; // In real implementation, update each service's resource allocation for (const [serviceName, serviceConfig] of Object.entries(services)) { this.logger.debug(`Scaling ${serviceName} to ${serviceConfig.replicas} replicas`); await new Promise((resolve) => setTimeout(resolve, 500)); } } /** * Update tier labels and annotations */ async updateTierLabels(plan, k8sProvider) { this.logger.info(`Updating tier labels to ${plan.toTier}`); // In real implementation, update labels on all resources await new Promise((resolve) => setTimeout(resolve, 1000)); } /** * Update limit range for new tier */ async updateLimitRange(plan, k8sProvider) { this.logger.info(`Updating limit range for ${plan.toTier} tier`); if (k8sProvider) { const newLimitRange = this.tierCalculator.generateLimitRange(getTierConfiguration(plan.toTier), plan.namespace); // In real implementation, apply the new limit range this.logger.debug(`Generated new limit range`, newLimitRange); } await new Promise((resolve) => setTimeout(resolve, 1000)); } /** * Update Kubecost configuration */ async updateKubecostConfiguration(plan) { this.logger.info(`Updating Kubecost configuration for ${plan.toTier} tier`); try { // In real implementation, update Kubecost labels and configuration await new Promise((resolve) => setTimeout(resolve, 1000)); } catch (error) { this.logger.warn(`Failed to update Kubecost configuration: ${error instanceof Error ? error.message : String(error)}`); } } /** * Check if migration requires downtime */ tierRequiresDowntime(fromTier, toTier) { const migrationType = this.getMigrationType(fromTier, toTier); // Downgrades typically require more careful handling and potential downtime if (migrationType === "downgrade") { return true; } // Large tier jumps may require downtime const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const fromIndex = tiers.indexOf(fromTier); const toIndex = tiers.indexOf(toTier); return Math.abs(toIndex - fromIndex) > 1; } /** * Check if a tier can be upgraded */ canUpgradeTier(tier) { const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const currentIndex = tiers.indexOf(tier); return currentIndex >= 0 && currentIndex < tiers.length - 1; } /** * Check if a tier can be downgraded */ canDowngradeTier(tier) { const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const currentIndex = tiers.indexOf(tier); return currentIndex > 0; } /** * Get the next higher tier */ getNextTier(tier) { const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const currentIndex = tiers.indexOf(tier); if (currentIndex >= 0 && currentIndex < tiers.length - 1) { return tiers[currentIndex + 1]; } return null; } /** * Get the next lower tier */ getPreviousTier(tier) { const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const currentIndex = tiers.indexOf(tier); if (currentIndex > 0) { return tiers[currentIndex - 1]; } return null; } /** * Get all possible migration paths from a tier */ getPossibleMigrations(tier) { const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const currentIndex = tiers.indexOf(tier); const migrations = []; for (let i = 0; i < tiers.length; i++) { if (i === currentIndex) continue; const targetTier = tiers[i]; const migrationType = i > currentIndex ? "upgrade" : "downgrade"; const tierDifference = Math.abs(i - currentIndex); migrations.push({ targetTier, migrationType, estimatedDuration: this.estimateMigrationDuration(tier, targetTier), complexity: tierDifference === 1 ? "low" : tierDifference === 2 ? "medium" : "high", }); } return migrations; } /** * Determine migration type */ getMigrationType(fromTier, toTier) { const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const fromIndex = tiers.indexOf(fromTier); const toIndex = tiers.indexOf(toTier); return toIndex > fromIndex ? "upgrade" : "downgrade"; } /** * Estimate migration duration */ estimateMigrationDuration(fromTier, toTier) { const migrationType = this.getMigrationType(fromTier, toTier); const tiers = [ PlanTier.BASIC, PlanTier.STANDARD, PlanTier.PREMIUM, PlanTier.ENTERPRISE, ]; const tierDifference = Math.abs(tiers.indexOf(toTier) - tiers.indexOf(fromTier)); // Base duration on migration type and complexity if (migrationType === "upgrade") { switch (tierDifference) { case 1: return "5-10 minutes"; case 2: return "10-15 minutes"; case 3: return "15-20 minutes"; default: return "10-15 minutes"; } } else { // Downgrades take longer due to validation requirements switch (tierDifference) { case 1: return "10-15 minutes"; case 2: return "15-25 minutes"; case 3: return "20-30 minutes"; default: return "15-25 minutes"; } } } } //# sourceMappingURL=tier-migration.js.map