@kapeta/local-cluster-service 0.1.1 → 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 +14 -0
- package/package.json +1 -1
- package/src/containerManager.js +75 -49
- package/src/operatorManager.js +1 -7
- package/src/utils/BlockInstanceRunner.js +25 -5
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
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
|
+
|
8
|
+
## [0.1.2](https://github.com/kapetacom/local-cluster-service/compare/v0.1.1...v0.1.2) (2023-05-06)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Moved all docker init things into init - and rely on that ([9a012c3](https://github.com/kapetacom/local-cluster-service/commit/9a012c3a40a6b4e4ef55757a4b8454d48bd3987c))
|
14
|
+
|
1
15
|
## [0.1.1](https://github.com/kapetacom/local-cluster-service/compare/v0.1.0...v0.1.1) (2023-05-06)
|
2
16
|
|
3
17
|
|
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 = [
|
@@ -44,6 +68,7 @@ class ContainerManager {
|
|
44
68
|
const client = new Docker(opts);
|
45
69
|
await client.ping();
|
46
70
|
this._docker = client;
|
71
|
+
this._alive = true;
|
47
72
|
return;
|
48
73
|
} catch (err) {
|
49
74
|
// silently ignore bad configs
|
@@ -52,38 +77,26 @@ class ContainerManager {
|
|
52
77
|
throw new Error("Unable to connect to docker");
|
53
78
|
}
|
54
79
|
|
55
|
-
async ping() {
|
56
|
-
await this._docker.ping();
|
57
|
-
this._alive = true;
|
58
|
-
}
|
59
|
-
|
60
80
|
async ping() {
|
61
81
|
|
62
82
|
try {
|
63
|
-
const pingResult = await this.
|
83
|
+
const pingResult = await this.docker().ping();
|
64
84
|
if (pingResult !== 'OK') {
|
65
85
|
throw new Error(`Ping failed: ${pingResult}`);
|
66
86
|
}
|
67
87
|
} catch (e) {
|
68
88
|
throw new Error(`Docker not running. Please start the docker daemon before running this command. Error: ${e.message}`);
|
69
89
|
}
|
70
|
-
|
71
|
-
this._alive = true;
|
72
90
|
}
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
await this.ping();
|
91
|
+
docker() {
|
92
|
+
if (!this._docker) {
|
93
|
+
throw new Error(`Docker not running`);
|
77
94
|
}
|
78
|
-
}
|
79
|
-
|
80
|
-
async docker() {
|
81
|
-
await this.ensureAlive();
|
82
95
|
return this._docker;
|
83
96
|
}
|
84
97
|
|
85
98
|
async getContainerByName(containerName) {
|
86
|
-
const containers = await this.
|
99
|
+
const containers = await this.docker().container.list({all: true});
|
87
100
|
return containers.find(container => {
|
88
101
|
return container.data.Names.indexOf(`/${containerName}`) > -1;
|
89
102
|
});
|
@@ -95,7 +108,7 @@ class ContainerManager {
|
|
95
108
|
tag = 'latest';
|
96
109
|
}
|
97
110
|
|
98
|
-
await this.
|
111
|
+
await this.docker().image
|
99
112
|
.create(
|
100
113
|
{},
|
101
114
|
{
|
@@ -105,6 +118,34 @@ class ContainerManager {
|
|
105
118
|
)
|
106
119
|
.then((stream) => promisifyStream(stream));
|
107
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
|
+
}
|
108
149
|
|
109
150
|
/**
|
110
151
|
*
|
@@ -114,7 +155,7 @@ class ContainerManager {
|
|
114
155
|
* @return {Promise<ContainerInfo>}
|
115
156
|
*/
|
116
157
|
async run(image, name, opts) {
|
117
|
-
|
158
|
+
|
118
159
|
const PortBindings = {};
|
119
160
|
const Env = [];
|
120
161
|
const Labels = {
|
@@ -138,15 +179,7 @@ class ContainerManager {
|
|
138
179
|
Labels[LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
|
139
180
|
});
|
140
181
|
|
141
|
-
|
142
|
-
Mounts.push({
|
143
|
-
Target,
|
144
|
-
Source,
|
145
|
-
Type: "bind",
|
146
|
-
ReadOnly: false,
|
147
|
-
Consistency: "consistent",
|
148
|
-
});
|
149
|
-
});
|
182
|
+
const Mounts = this.toDockerMounts(opts.mounts);
|
150
183
|
|
151
184
|
_.forEach(opts.env, (value, name) => {
|
152
185
|
Env.push(name + "=" + value);
|
@@ -155,16 +188,7 @@ class ContainerManager {
|
|
155
188
|
let HealthCheck = undefined;
|
156
189
|
|
157
190
|
if (opts.health) {
|
158
|
-
HealthCheck =
|
159
|
-
Test: ["CMD-SHELL", opts.health.cmd],
|
160
|
-
Interval: opts.health.interval
|
161
|
-
? opts.health.interval * NANO_SECOND
|
162
|
-
: 5000 * NANO_SECOND,
|
163
|
-
Timeout: opts.health.timeout
|
164
|
-
? opts.health.timeout * NANO_SECOND
|
165
|
-
: 15000 * NANO_SECOND,
|
166
|
-
Retries: opts.health.retries || 10,
|
167
|
-
};
|
191
|
+
HealthCheck = this.toDockerHealth(opts.health);
|
168
192
|
|
169
193
|
console.log("Adding health check", HealthCheck);
|
170
194
|
}
|
@@ -183,14 +207,14 @@ class ContainerManager {
|
|
183
207
|
});
|
184
208
|
|
185
209
|
if (opts.health) {
|
186
|
-
await this.
|
210
|
+
await this.waitForHealthy(dockerContainer);
|
187
211
|
}
|
188
212
|
|
189
213
|
return new ContainerInfo(dockerContainer);
|
190
214
|
}
|
191
215
|
|
192
216
|
async startContainer(opts) {
|
193
|
-
const dockerContainer = await this.
|
217
|
+
const dockerContainer = await this.docker().container.create(opts);
|
194
218
|
|
195
219
|
await dockerContainer.start();
|
196
220
|
|
@@ -198,31 +222,33 @@ class ContainerManager {
|
|
198
222
|
}
|
199
223
|
|
200
224
|
|
201
|
-
async
|
225
|
+
async waitForHealthy(container, attempt) {
|
202
226
|
if (!attempt) {
|
203
227
|
attempt = 0;
|
204
228
|
}
|
205
229
|
|
206
230
|
if (attempt >= HEALTH_CHECK_MAX) {
|
207
|
-
throw new Error("
|
231
|
+
throw new Error("Container did not become healthy within the timeout");
|
208
232
|
}
|
209
233
|
|
210
234
|
if (await this._isHealthy(container)) {
|
211
|
-
console.log("Container became healthy");
|
212
235
|
return;
|
213
236
|
}
|
214
237
|
|
215
|
-
return new Promise((resolve) => {
|
238
|
+
return new Promise((resolve, reject) => {
|
216
239
|
setTimeout(async () => {
|
217
|
-
|
218
|
-
|
240
|
+
try {
|
241
|
+
await this.waitForHealthy(container, attempt + 1);
|
242
|
+
resolve();
|
243
|
+
} catch (err) {
|
244
|
+
reject(err);
|
245
|
+
}
|
219
246
|
}, HEALTH_CHECK_INTERVAL);
|
220
247
|
});
|
221
248
|
}
|
222
249
|
|
223
250
|
async _isHealthy(container) {
|
224
251
|
const info = await container.status();
|
225
|
-
|
226
252
|
return info?.data?.State?.Health?.Status === "healthy";
|
227
253
|
}
|
228
254
|
|
@@ -235,7 +261,7 @@ class ContainerManager {
|
|
235
261
|
let dockerContainer = null;
|
236
262
|
|
237
263
|
try {
|
238
|
-
dockerContainer = await this.
|
264
|
+
dockerContainer = await this.docker().container.get(name);
|
239
265
|
await dockerContainer.status();
|
240
266
|
} catch (err) {
|
241
267
|
//Ignore
|
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);
|