Skip to content

Commit

Permalink
feat: admin migration run book
Browse files Browse the repository at this point in the history
  • Loading branch information
kirinnee committed Aug 25, 2024
1 parent 9072129 commit 5c8032e
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 57 deletions.
53 changes: 53 additions & 0 deletions src/books/admin-cluster-migration/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { RunBook } from "../run-book.ts";
import type { ServiceTreePrompter } from "../../lib/prompts/landscape.ts";
import type { ServiceTreePrinter } from "../../lib/utility/service-tree-printer.ts";
import type { BareAdminClusterCloudCreator } from "../bare-admin-cluster-creation/cloud.ts";
import type { GenericGracefulAdminClusterDestructor } from "../graceful-admin-cluster-destruction/generic.ts";
import type { AdminClusterTransitioner } from "./transition.ts";

class AdminClusterMigrator implements RunBook {
name: string = "Admin Cluster Migration";
desc: string = "Migrate an admin cluster to a new cluster";

constructor(
private stp: ServiceTreePrompter,
private printer: ServiceTreePrinter,
private clouds: BareAdminClusterCloudCreator[],
private destructors: GenericGracefulAdminClusterDestructor,
private transition: AdminClusterTransitioner,
) {}

async Run(): Promise<void> {
const [fromLandscape, fromCluster] = await this.stp.AdminLandscapeCluster(
"Select the admin landscape to migrate from",
"Select the admin cloud to migrate from",
"Select the admin cluster to migrate from",
);
const [toLandscape, toCluster] = await this.stp.AdminLandscapeCluster(
"Select the admin landscape to migrate to",
"Select the admin cloud to migrate to",
"Select the admin cluster to migrate to",
);

console.log("🎯 Selected Service Tree to migrate");
this.printer.Print("From", [fromLandscape, fromCluster]);
this.printer.Print("To ", [toLandscape, toCluster]);

// select the cloud to create the new cluster in
const c = this.clouds.find((x) => x.slug === toCluster.cloud.slug);
if (!c) return console.log("⚠️ Cloud not supported");

await c.Run([toLandscape, toCluster]);

// perform transition
await this.transition.Run(
[fromLandscape, fromCluster],
[toLandscape, toCluster],
);

// destroy old cluster
await this.destructors.Run([fromLandscape, fromCluster]);
}
}

export { AdminClusterMigrator };
147 changes: 147 additions & 0 deletions src/books/admin-cluster-migration/transition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import type {
LandscapeCluster,
ServiceTreeService,
} from "../../lib/service-tree-def.ts";
import type { TaskRunner } from "../../tasks/tasks.ts";
import type { KubectlUtil, Resource } from "../../lib/utility/kubectl-util.ts";
import { $ } from "bun";
import type { SulfoxideHeliumWaiter } from "../../tasks/sulfoxide-helium-waiter.ts";
import type { SulfoxideBoronWaiter } from "../../tasks/sulfoxide-boron-waiter.ts";

class AdminClusterTransitioner {
constructor(
private t: TaskRunner,
private k: KubectlUtil,
private sulfoxideHelium: ServiceTreeService,
private sulfoxideFluorine: ServiceTreeService,
private sulfoxideBoron: ServiceTreeService,
private sulfoxideHeliumWaiter: SulfoxideHeliumWaiter,
private sulfoxideBoronWaiter: SulfoxideBoronWaiter,
) {}

async Run(
[fL, fC]: LandscapeCluster,
[tL, tC]: LandscapeCluster,
): Promise<void> {
// common variables

// services
const He = this.sulfoxideHelium;
const F = this.sulfoxideFluorine;
const B = this.sulfoxideBoron;

// contexts
const fCtx = `${fL.slug}-${fC.principal.slug}`;
const tCtx = `${tL.slug}-${tC.principal.slug}`;

// namespaces
const He_NS = `${He.platform.slug}-${He.principal.slug}`;
const B_NS = `${B.platform.slug}-${B.principal.slug}`;

// dir
const F_Dir = `./platforms/${F.platform.slug}/${F.principal.slug}`;

console.log("🎯 Both systems are operational, performing transition...");
const prefix = `${He_NS}-argocd`;
const resources: Resource[] = [
...[
`${prefix}-applicationset-controller`,
`${prefix}-notifications-controller`,
`${prefix}-redis`,
`${prefix}-server`,
`${prefix}-repo-server`,
].map((name) => ({
kind: "deployment",
context: fCtx,
namespace: He_NS,
name,
})),
{
kind: "statefulset",
context: fCtx,
namespace: He_NS,
name: `${prefix}-application-controller`,
},
];
const replicas: Record<string, number> = {};

await this.t.Run([
"Get Replicas",
async () => {
for (const resource of resources)
replicas[resource.name] = await this.k.GetReplica(resource);
},
]);

console.log("🔀 Replicas before scaling down:", replicas);

await this.t.Run([
"Scale Down Old Cluster",
async () => {
for (const resource of resources) await this.k.Scale(resource, 0);
},
]);

// perform migration via velero
await this.t.Run([
"Velero Migration",
async () => {
await $`nix develop -c pls migrate -- ${fCtx} ${tCtx}`.cwd(F_Dir);
},
]);

await this.t.Exec([
"Wait for Helium to be migrated",
async () => {
await this.k.Wait(5, 5, {
kind: "deployment",
context: tCtx,
namespace: He_NS,
selector: [["app.kubernetes.io/part-of", `argocd`]],
});
await this.k.Wait(1, 5, {
kind: "statefulset",
context: tCtx,
namespace: He_NS,
selector: [["app.kubernetes.io/part-of", `argocd`]],
});
},
]);

await this.t.Exec([
"Wait for Boron to be migrated",
async () => {
await this.k.Wait(1, 5, {
kind: "deployment",
context: tCtx,
namespace: B_NS,
fieldSelector: [
["metadata.name", `${B.platform.slug}-${B.principal.slug}`],
],
});
},
]);

// Scale back up the new cluster
await this.t.Run([
"Scale Up New Cluster",
async () => {
for (const resource of resources) {
resource.context = tCtx;
await this.k.Scale(resource, replicas[resource.name]);
}
},
]);

// wait for new cluster to be ready
const waitForHelium = this.sulfoxideHeliumWaiter.task(tCtx, He_NS);
await this.t.Exec(waitForHelium);

const waitForBoron = this.sulfoxideBoronWaiter.task(tCtx, B_NS);
await this.t.Run(waitForBoron);

console.log("🎉 Transition completed!");
}
}

export { AdminClusterTransitioner };
28 changes: 22 additions & 6 deletions src/books/bare-admin-cluster-creation/digital-ocean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { input } from "@inquirer/prompts";
import { $ } from "bun";
import type { BareAdminClusterCloudCreator } from "./cloud.ts";
import type { KubectlUtil } from "../../lib/utility/kubectl-util.ts";
import type {SulfoxideXenonWaiter} from "../../tasks/sulfoxide-xenon-waiter.ts";

class DigitalOceanBareAdminClusterCreator
implements BareAdminClusterCloudCreator
Expand All @@ -20,6 +21,8 @@ class DigitalOceanBareAdminClusterCreator
private k: KubectlUtil,
private sulfoxideTofu: ServiceTreeService,
private sulfoxideFluorine: ServiceTreeService,
private sulfoxideXenon: ServiceTreeService,
private sulfoxideXenonWaiter: SulfoxideXenonWaiter,
slug: string,
) {
this.slug = slug;
Expand All @@ -29,9 +32,11 @@ class DigitalOceanBareAdminClusterCreator
// constants
const admin = { landscape, cluster };
const tofu = this.sulfoxideTofu;
const fluorine = this.sulfoxideFluorine;
const F = this.sulfoxideFluorine;
const Xe = this.sulfoxideXenon;
const tofuDir = `./platforms/${tofu.platform.slug}/${tofu.principal.slug}`;
const fluorineDir = `./platforms/${fluorine.platform.slug}/${fluorine.principal.slug}`;
const F_Dir = `./platforms/${F.platform.slug}/${F.principal.slug}`;
const Xe_Dir = `./platforms/${Xe.platform.slug}/${Xe.principal.slug}`;
const context = `${admin.landscape.slug}-${admin.cluster.principal.slug}`;

// Check if we want to inject the DO secrets
Expand Down Expand Up @@ -95,7 +100,7 @@ class DigitalOceanBareAdminClusterCreator
await this.task.Run([
"Setup Velero Backup Engine",
async () => {
await $`nix develop -c pls setup`.cwd(fluorineDir);
await $`nix develop -c pls setup`.cwd(F_Dir);
},
]);

Expand All @@ -104,15 +109,15 @@ class DigitalOceanBareAdminClusterCreator
"Install Velero Backup Engine",
async () => {
await $`nix develop -c pls velero:${{ raw: admin.landscape.slug }}:install -- --kubecontext ${{ raw: context }}`.cwd(
fluorineDir,
F_Dir,
);
},
]);

await this.task.Run([
await this.task.Exec([
"Wait for Velero to be ready",
async () => {
await this.k.WaitForReplicas({
await this.k.WaitForReplica({
namespace: "velero",
name: "velero",
context: context,
Expand All @@ -121,6 +126,17 @@ class DigitalOceanBareAdminClusterCreator
await $`kubectl --context ${{ raw: context }} -n velero wait --for=jsonpath=.status.phase=Available --timeout=6000s BackupStorageLocation default`;
},
]);

await this.task.Run([
"Deploy Metrics Server",
async () => {
const plsXe = `${landscape.slug}:${cluster.set.slug}`;
await $`nix develop -c pls ${{ raw: plsXe }}:install -- --kube-context ${context} -n kube-system`.cwd(Xe_Dir);
},
]);

const xenonWaiter = this.sulfoxideXenonWaiter.task(context, "kube-system");
await this.task.Exec(xenonWaiter);
}
}

Expand Down
55 changes: 10 additions & 45 deletions src/books/full-admin-cluster-creation/digital-ocean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type {
} from "../../lib/service-tree-def.ts";
import type { FullAdminClusterCloudCreator } from "./cloud.ts";
import type { TaskRunner } from "../../tasks/tasks.ts";
import type { KubectlUtil } from "../../lib/utility/kubectl-util.ts";
import type { SulfoxideHeliumWaiter } from "../../tasks/sulfoxide-helium-waiter.ts";
import type { SulfoxideBoronWaiter } from "../../tasks/sulfoxide-boron-waiter.ts";

class DigitalOceanFullAdminClusterCreator
implements FullAdminClusterCloudCreator
Expand All @@ -14,9 +15,10 @@ class DigitalOceanFullAdminClusterCreator

constructor(
private task: TaskRunner,
private k: KubectlUtil,
private sulfoxideHelium: ServiceTreeService,
private sulfoxideBoron: ServiceTreeService,
private sulfoxideHeliumWaiter: SulfoxideHeliumWaiter,
private sulfoxideBoronWaiter: SulfoxideBoronWaiter,
slug: string,
) {
this.slug = slug;
Expand Down Expand Up @@ -45,45 +47,14 @@ class DigitalOceanFullAdminClusterCreator
"Create Helium Helm Release",
async () => {
const heliumPls = `${admin.landscape.slug}:${admin.cluster.set.slug}`;
await $`pls ${{ raw: heliumPls }}:install -- --kube-context ${{ raw: context }} -n ${{ raw: namespace }}`.cwd(
await $`pls ${{ raw: heliumPls }}:install -- --kube-context ${context} -n ${namespace}`.cwd(
heliumDir,
);
},
]);

console.log("⏱️ Waiting for Helium to be ready...");
const prefix = `${helium.platform.slug}-${helium.principal.slug}-argocd`;
await this.k.WaitForReplicas({
kind: "deployment",
context,
namespace,
name: `${prefix}-server`,
});
await this.k.WaitForReplicas({
kind: "deployment",
context,
namespace,
name: `${prefix}-repo-server`,
});
await this.k.WaitForReplicas({
kind: "deployment",
context,
namespace,
name: `${prefix}-redis`,
});
await this.k.WaitForReplicas({
kind: "deployment",
context,
namespace,
name: `${prefix}-notifications-controller`,
});
await this.k.WaitForReplicas({
kind: "deployment",
context,
namespace,
name: `${prefix}-applicationset-controller`,
});
console.log("✅ Helium is ready");
const waitForHelium = this.sulfoxideHeliumWaiter.task(context, namespace);
await this.task.Run(waitForHelium);

await this.task.Run([
"Create Boron Namespace",
Expand All @@ -96,20 +67,14 @@ class DigitalOceanFullAdminClusterCreator
"Create Boron Helm Release",
async () => {
const boronPls = `${admin.landscape.slug}:${admin.cluster.set.slug}`;
await $`pls ${{ raw: boronPls }}:install -- --kube-context ${{ raw: context }} -n ${{ raw: boronNS }}`.cwd(
await $`pls ${{ raw: boronPls }}:install -- --kube-context ${context} -n ${boronNS}`.cwd(
boronDir,
);
},
]);

console.log("⏱️ Waiting for Boron to be ready...");
await this.k.WaitForReplicas({
kind: "deployment",
context,
namespace,
name: `${boron.platform.slug}-${boron.principal.slug}`,
});
console.log("✅ Boron is ready");
const waitForBoron = this.sulfoxideBoronWaiter.task(context, namespace);
await this.task.Run(waitForBoron);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/books/graceful-admin-cluster-destruction/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class GenericGracefulAdminClusterDestructor {
await this.task.Run([
"Destroy L1 Infrastructure",
async () => {
await $`pls ${{ raw: L1 }}:init`.cwd(tofuDir);
await $`pls ${{ raw: L1 }}:state:rm -- 'kubernetes_namespace.sulfoxide'`
.cwd(tofuDir)
.nothrow();
Expand Down
4 changes: 2 additions & 2 deletions src/books/physical-cluster-creation/digital-ocean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class DigitalOceanPhysicalClusterCreator
"Wait for statefulset (etcd) to be ready",
async () => {
for (const ns of ["pichu", "pikachu", "raichu"]) {
await this.k.WaitForReplicas({
await this.k.WaitForReplica({
kind: "statefulset",
context: `${phyLandscape.slug}-${phyCluster.principal.slug}`,
namespace: ns,
Expand All @@ -216,7 +216,7 @@ class DigitalOceanPhysicalClusterCreator
"Wait for deployment (iodine) to be ready",
async () => {
for (const ns of ["pichu", "pikachu", "raichu"]) {
await this.k.WaitForReplicas({
await this.k.WaitForReplica({
kind: "deployment",
context: `${phyLandscape.slug}-${phyCluster.principal.slug}`,
namespace: ns,
Expand Down
Loading

0 comments on commit 5c8032e

Please sign in to comment.