@chimoney.io/iaas-k8s-deployment
Version:
Kubernetes Infrastructure as a Service deployment package for streamlined multi-cloud environments
689 lines • 29.7 kB
JavaScript
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