Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: admin migration run book #1

Merged
merged 1 commit into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading