@kapeta/local-cluster-service 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/containerManager.js +66 -29
- package/src/operatorManager.js +1 -7
- package/src/utils/BlockInstanceRunner.js +25 -5
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.2.0](https://github.com/kapetacom/local-cluster-service/compare/v0.1.2...v0.2.0) (2023-05-07)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* Add support for health checks and mounts ([cac607b](https://github.com/kapetacom/local-cluster-service/commit/cac607bc8b592e27c8b6c2ff09476f90b2e4c3f3))
|
7
|
+
|
1
8
|
## [0.1.2](https://github.com/kapetacom/local-cluster-service/compare/v0.1.1...v0.1.2) (2023-05-06)
|
2
9
|
|
3
10
|
|
package/package.json
CHANGED
package/src/containerManager.js
CHANGED
@@ -3,10 +3,14 @@ const path = require("path");
|
|
3
3
|
const _ = require('lodash');
|
4
4
|
const FS = require("node:fs");
|
5
5
|
const os = require("os");
|
6
|
+
const Path = require("path");
|
7
|
+
const storageService = require("./storageService");
|
8
|
+
const mkdirp = require("mkdirp");
|
9
|
+
const {parseKapetaUri} = require("@kapeta/nodejs-utils");
|
6
10
|
const LABEL_PORT_PREFIX = "kapeta_port-";
|
7
11
|
|
8
12
|
const NANO_SECOND = 1000000;
|
9
|
-
const HEALTH_CHECK_INTERVAL =
|
13
|
+
const HEALTH_CHECK_INTERVAL = 2000;
|
10
14
|
const HEALTH_CHECK_MAX = 20;
|
11
15
|
|
12
16
|
const promisifyStream = (stream) =>
|
@@ -20,12 +24,32 @@ class ContainerManager {
|
|
20
24
|
constructor() {
|
21
25
|
this._docker = null;
|
22
26
|
this._alive = false;
|
27
|
+
this._mountDir = Path.join(storageService.getKapetaBasedir(), 'mounts');
|
28
|
+
mkdirp.sync(this._mountDir);
|
23
29
|
}
|
30
|
+
|
31
|
+
|
24
32
|
|
25
33
|
isAlive() {
|
26
34
|
return this._alive;
|
27
35
|
}
|
28
36
|
|
37
|
+
getMountPoint(kind, mountName) {
|
38
|
+
const kindUri = parseKapetaUri(kind);
|
39
|
+
return Path.join(this._mountDir, kindUri.handle, kindUri.name, mountName);
|
40
|
+
}
|
41
|
+
|
42
|
+
createMounts(kind, mountOpts) {
|
43
|
+
const mounts = {};
|
44
|
+
|
45
|
+
_.forEach(mountOpts, (containerPath, mountName) => {
|
46
|
+
const hostPath = this.getMountPoint(kind, mountName);
|
47
|
+
mkdirp.sync(hostPath);
|
48
|
+
mounts[containerPath] = hostPath;
|
49
|
+
});
|
50
|
+
return mounts;
|
51
|
+
}
|
52
|
+
|
29
53
|
async initialize() {
|
30
54
|
// try
|
31
55
|
const connectOptions = [
|
@@ -94,6 +118,34 @@ class ContainerManager {
|
|
94
118
|
)
|
95
119
|
.then((stream) => promisifyStream(stream));
|
96
120
|
}
|
121
|
+
|
122
|
+
toDockerMounts(mounts) {
|
123
|
+
const Mounts = [];
|
124
|
+
_.forEach(mounts, (Source, Target) => {
|
125
|
+
Mounts.push({
|
126
|
+
Target,
|
127
|
+
Source,
|
128
|
+
Type: "bind",
|
129
|
+
ReadOnly: false,
|
130
|
+
Consistency: "consistent",
|
131
|
+
});
|
132
|
+
});
|
133
|
+
|
134
|
+
return Mounts;
|
135
|
+
}
|
136
|
+
|
137
|
+
toDockerHealth(health) {
|
138
|
+
return {
|
139
|
+
Test: ["CMD-SHELL", health.cmd],
|
140
|
+
Interval: health.interval
|
141
|
+
? health.interval * NANO_SECOND
|
142
|
+
: 5000 * NANO_SECOND,
|
143
|
+
Timeout: health.timeout
|
144
|
+
? health.timeout * NANO_SECOND
|
145
|
+
: 15000 * NANO_SECOND,
|
146
|
+
Retries: health.retries || 10,
|
147
|
+
};
|
148
|
+
}
|
97
149
|
|
98
150
|
/**
|
99
151
|
*
|
@@ -103,7 +155,7 @@ class ContainerManager {
|
|
103
155
|
* @return {Promise<ContainerInfo>}
|
104
156
|
*/
|
105
157
|
async run(image, name, opts) {
|
106
|
-
|
158
|
+
|
107
159
|
const PortBindings = {};
|
108
160
|
const Env = [];
|
109
161
|
const Labels = {
|
@@ -127,15 +179,7 @@ class ContainerManager {
|
|
127
179
|
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
128
180
|
});
|
129
181
|
|
130
|
-
|
131
|
-
Mounts.push({
|
132
|
-
Target,
|
133
|
-
Source,
|
134
|
-
Type: "bind",
|
135
|
-
ReadOnly: false,
|
136
|
-
Consistency: "consistent",
|
137
|
-
});
|
138
|
-
});
|
182
|
+
const Mounts = this.toDockerMounts(opts.mounts);
|
139
183
|
|
140
184
|
_.forEach(opts.env, (value, name) => {
|
141
185
|
Env.push(name + "=" + value);
|
@@ -144,16 +188,7 @@ class ContainerManager {
|
|
144
188
|
let HealthCheck = undefined;
|
145
189
|
|
146
190
|
if (opts.health) {
|
147
|
-
HealthCheck =
|
148
|
-
Test: ["CMD-SHELL", opts.health.cmd],
|
149
|
-
Interval: opts.health.interval
|
150
|
-
? opts.health.interval * NANO_SECOND
|
151
|
-
: 5000 * NANO_SECOND,
|
152
|
-
Timeout: opts.health.timeout
|
153
|
-
? opts.health.timeout * NANO_SECOND
|
154
|
-
: 15000 * NANO_SECOND,
|
155
|
-
Retries: opts.health.retries || 10,
|
156
|
-
};
|
191
|
+
HealthCheck = this.toDockerHealth(opts.health);
|
157
192
|
|
158
193
|
console.log("Adding health check", HealthCheck);
|
159
194
|
}
|
@@ -172,7 +207,7 @@ class ContainerManager {
|
|
172
207
|
});
|
173
208
|
|
174
209
|
if (opts.health) {
|
175
|
-
await this.
|
210
|
+
await this.waitForHealthy(dockerContainer);
|
176
211
|
}
|
177
212
|
|
178
213
|
return new ContainerInfo(dockerContainer);
|
@@ -187,31 +222,33 @@ class ContainerManager {
|
|
187
222
|
}
|
188
223
|
|
189
224
|
|
190
|
-
async
|
225
|
+
async waitForHealthy(container, attempt) {
|
191
226
|
if (!attempt) {
|
192
227
|
attempt = 0;
|
193
228
|
}
|
194
229
|
|
195
230
|
if (attempt >= HEALTH_CHECK_MAX) {
|
196
|
-
throw new Error("
|
231
|
+
throw new Error("Container did not become healthy within the timeout");
|
197
232
|
}
|
198
233
|
|
199
234
|
if (await this._isHealthy(container)) {
|
200
|
-
console.log("Container became healthy");
|
201
235
|
return;
|
202
236
|
}
|
203
237
|
|
204
|
-
return new Promise((resolve) => {
|
238
|
+
return new Promise((resolve, reject) => {
|
205
239
|
setTimeout(async () => {
|
206
|
-
|
207
|
-
|
240
|
+
try {
|
241
|
+
await this.waitForHealthy(container, attempt + 1);
|
242
|
+
resolve();
|
243
|
+
} catch (err) {
|
244
|
+
reject(err);
|
245
|
+
}
|
208
246
|
}, HEALTH_CHECK_INTERVAL);
|
209
247
|
});
|
210
248
|
}
|
211
249
|
|
212
250
|
async _isHealthy(container) {
|
213
251
|
const info = await container.status();
|
214
|
-
|
215
252
|
return info?.data?.State?.Health?.Status === "healthy";
|
216
253
|
}
|
217
254
|
|
package/src/operatorManager.js
CHANGED
@@ -144,13 +144,7 @@ class OperatorManager {
|
|
144
144
|
};
|
145
145
|
}
|
146
146
|
|
147
|
-
const mounts =
|
148
|
-
|
149
|
-
_.forEach(operatorData.mounts, (containerPath, mountName) => {
|
150
|
-
const hostPath = this._getMountPoint(resourceType, mountName);
|
151
|
-
mkdirp.sync(hostPath);
|
152
|
-
mounts[containerPath] = hostPath;
|
153
|
-
});
|
147
|
+
const mounts = containerManager.createMounts(resourceType, operatorData.mounts);
|
154
148
|
|
155
149
|
const containerName = containerBaseName + '-' + md5(nameParts.join('_'));
|
156
150
|
let container = await containerManager.get(containerName);
|
@@ -9,6 +9,7 @@ const serviceManager = require("../serviceManager");
|
|
9
9
|
const containerManager = require("../containerManager");
|
10
10
|
const LogData = require("./LogData");
|
11
11
|
const EventEmitter = require("events");
|
12
|
+
const md5 = require('md5');
|
12
13
|
const {execSync} = require("child_process");
|
13
14
|
|
14
15
|
const KIND_BLOCK_TYPE_OPERATOR = 'core/block-type-operator';
|
@@ -25,10 +26,6 @@ const DOCKER_ENV_VARS = [
|
|
25
26
|
`KAPETA_ENVIRONMENT_TYPE=docker`,
|
26
27
|
]
|
27
28
|
|
28
|
-
function md5(data) {
|
29
|
-
return require('crypto').createHash('md5').update(data).digest("hex");
|
30
|
-
}
|
31
|
-
|
32
29
|
|
33
30
|
class BlockInstanceRunner {
|
34
31
|
/**
|
@@ -386,6 +383,8 @@ class BlockInstanceRunner {
|
|
386
383
|
const ExposedPorts = {};
|
387
384
|
const addonEnv = {};
|
388
385
|
const PortBindings = {};
|
386
|
+
let HealthCheck = undefined;
|
387
|
+
let Mounts = [];
|
389
388
|
const promises = Object.entries(spec.local.ports)
|
390
389
|
.map(async ([portType, value]) => {
|
391
390
|
const dockerPort = `${value.port}/${value.type}`;
|
@@ -402,17 +401,34 @@ class BlockInstanceRunner {
|
|
402
401
|
|
403
402
|
await Promise.all(promises);
|
404
403
|
|
404
|
+
if (spec.local?.env) {
|
405
|
+
Object.entries(spec.local.env).forEach(([key, value]) => {
|
406
|
+
addonEnv[key] = value;
|
407
|
+
});
|
408
|
+
}
|
409
|
+
|
410
|
+
if (spec.local?.mounts) {
|
411
|
+
const mounts = containerManager.createMounts(blockUri.id, spec.local.mounts);
|
412
|
+
Mounts = containerManager.toDockerMounts(mounts);
|
413
|
+
}
|
414
|
+
|
415
|
+
if (spec.local?.health) {
|
416
|
+
HealthCheck = containerManager.toDockerHealth(spec.local?.health);
|
417
|
+
}
|
418
|
+
|
405
419
|
logs.addLog(`Creating new container for block: ${containerName}`);
|
406
420
|
container = await containerManager.startContainer({
|
407
421
|
Image: dockerImage,
|
408
422
|
name: containerName,
|
409
423
|
ExposedPorts,
|
424
|
+
HealthCheck,
|
410
425
|
HostConfig: {
|
411
426
|
Binds: [
|
412
427
|
`${kapetaYmlPath}:/kapeta.yml:ro`,
|
413
428
|
`${ClusterConfig.getKapetaBasedir()}:${ClusterConfig.getKapetaBasedir()}`
|
414
429
|
],
|
415
|
-
PortBindings
|
430
|
+
PortBindings,
|
431
|
+
Mounts
|
416
432
|
},
|
417
433
|
Labels: {
|
418
434
|
'instance': blockInstance.id
|
@@ -426,6 +442,10 @@ class BlockInstanceRunner {
|
|
426
442
|
}).map(([key, value]) => `${key}=${value}`)
|
427
443
|
]
|
428
444
|
});
|
445
|
+
|
446
|
+
if (HealthCheck) {
|
447
|
+
await containerManager.waitForHealthy(container);
|
448
|
+
}
|
429
449
|
}
|
430
450
|
|
431
451
|
return this._handleContainer(container, logs, true);
|