feat: add experimental swarm settings

This commit is contained in:
Mauricio Siu
2024-05-18 03:07:51 -06:00
parent 5806068e2e
commit d52f66a716
14 changed files with 3064 additions and 28 deletions

View File

@@ -10,7 +10,14 @@ import { projects } from "./project";
import { security } from "./security";
import { applicationStatus } from "./shared";
import { ports } from "./port";
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import {
boolean,
integer,
json,
pgEnum,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { generateAppName } from "./utils";
import { registry } from "./registry";
@@ -23,6 +30,58 @@ export const buildType = pgEnum("buildType", [
"nixpacks",
]);
interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
StartPeriod?: number | undefined;
Retries?: number | undefined;
}
interface RestartPolicySwarm {
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}
interface PlacementSwarm {
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
Platforms?:
| Array<{
Architecture: string;
OS: string;
}>
| undefined;
}
interface UpdateConfigSwarm {
Parallelism: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}
interface ServiceModeSwarm {
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
| {
MaxConcurrent?: number | undefined;
TotalCompletions?: number | undefined;
}
| undefined;
GlobalJob?: {} | undefined;
}
interface LabelsSwarm {
[name: string]: string;
}
export const applications = pgTable("application", {
applicationId: text("applicationId")
.notNull()
@@ -61,6 +120,15 @@ export const applications = pgTable("application", {
customGitBuildPath: text("customGitBuildPath"),
customGitSSHKey: text("customGitSSHKey"),
dockerfile: text("dockerfile"),
// Docker swarm json
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
//
replicas: integer("replicas").default(1).notNull(),
applicationStatus: applicationStatus("applicationStatus")
.notNull()

View File

@@ -5,6 +5,7 @@ import type { CreateServiceOptions } from "dockerode";
import {
calculateResources,
generateBindMounts,
generateConfigContainer,
generateFileMounts,
generateVolumeMounts,
prepareEnvironmentVariables,
@@ -72,7 +73,6 @@ export const mechanizeDockerContainer = async (
cpuReservation,
command,
ports,
replicas,
} = application;
const resources = calculateResources({
@@ -83,13 +83,29 @@ export const mechanizeDockerContainer = async (
});
const volumesMount = generateVolumeMounts(mounts);
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
} = generateConfigContainer(application);
const bindsMount = generateBindMounts(mounts);
const filesMount = generateFileMounts(appName, mounts);
const envVariables = prepareEnvironmentVariables(env);
const registry = application.registry;
let image = sourceType === "docker" ? dockerImage! : `${appName}:latest`;
console.log(Labels);
let image =
sourceType === "docker"
? dockerImage || "ERROR-NO-IMAGE-PROVIDED"
: `${appName}:latest`;
if (registry) {
image = `${registry.registryUrl}/${appName}`;
@@ -108,6 +124,7 @@ export const mechanizeDockerContainer = async (
Name: appName,
TaskTemplate: {
ContainerSpec: {
HealthCheck,
Image: image,
Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
@@ -117,20 +134,18 @@ export const mechanizeDockerContainer = async (
Args: ["-c", command],
}
: {}),
Labels,
},
Networks: [{ Target: "dokploy-network" }],
RestartPolicy: {
Condition: "on-failure",
},
RestartPolicy,
Placement,
Resources: {
...resources,
},
},
Mode: {
Replicated: {
Replicas: replicas,
},
},
Mode,
RollbackConfig,
EndpointSpec: {
Ports: ports.map((port) => ({
Protocol: port.protocol,
@@ -138,10 +153,7 @@ export const mechanizeDockerContainer = async (
PublishedPort: port.publishedPort,
})),
},
UpdateConfig: {
Parallelism: 1,
Order: "start-first",
},
UpdateConfig,
};
try {
@@ -156,7 +168,7 @@ export const mechanizeDockerContainer = async (
},
});
} catch (error) {
console.log(error);
await docker.createService(settings);
}
// await cleanUpUnusedImages();
};

View File

@@ -122,10 +122,10 @@ export const cleanUpInactiveContainers = async () => {
for (const container of inactiveContainers) {
await docker.getContainer(container.Id).remove({ force: true });
console.log(`Contenedor eliminado: ${container.Id}`);
console.log(`Cleaning up inactive container: ${container.Id}`);
}
} catch (error) {
console.error("Error al limpiar contenedores inactivos:", error);
console.error("Error cleaning up inactive containers:", error);
throw error;
}
};
@@ -199,6 +199,88 @@ export const calculateResources = ({
};
};
export const generateConfigContainer = (application: ApplicationNested) => {
const { replicas, mounts } = application;
const healthCheckSwarm = JSON.parse(
(application.healthCheckSwarm as string) || "{}",
);
const restartPolicySwarm = JSON.parse(
(application.restartPolicySwarm as string) || "{}",
);
const placementSwarm = JSON.parse(
(application.placementSwarm as string) || "{}",
);
const updateConfigSwarm = JSON.parse(
(application.updateConfigSwarm as unknown as string) || "{}",
);
const rollbackConfigSwarm = JSON.parse(
(application.rollbackConfigSwarm as unknown as string) || "{}",
);
const modeSwarm = JSON.parse((application.modeSwarm as string) || "{}");
const labelsSwarm = JSON.parse(
(application.labelsSwarm as unknown as string) || "{}",
);
const haveMounts = mounts.length > 0;
return {
...(healthCheckSwarm && {
HealthCheck: healthCheckSwarm,
}),
...(restartPolicySwarm
? {
RestartPolicy: restartPolicySwarm,
}
: {
// if no restartPolicySwarm provided use default
RestartPolicy: {
Condition: "on-failure",
},
}),
...(placementSwarm
? {
Placement: placementSwarm,
}
: {
// if app have mounts keep manager as constraint
Placement: {
Constraints: haveMounts ? ["node.role==manager"] : [],
},
}),
...(labelsSwarm && {
Labels: labelsSwarm,
}),
...(modeSwarm
? {
Mode: modeSwarm,
}
: {
// use replicas value if no modeSwarm provided
Mode: {
Replicated: {
Replicas: replicas,
},
},
}),
...(rollbackConfigSwarm && {
RollbackConfig: rollbackConfigSwarm,
}),
...(updateConfigSwarm
? { UpdateConfig: updateConfigSwarm }
: {
// default config if no updateConfigSwarm provided
UpdateConfig: {
Parallelism: 1,
Order: "start-first",
},
}),
};
};
export const generateBindMounts = (mounts: ApplicationNested["mounts"]) => {
if (!mounts || mounts.length === 0) {
return [];