Compare commits

..

No commits in common. "663c7854585dbf3f51a0ae3fcb9c34bf3330a04a" and "e349bc7cadf0ce967b72ed8e56ae766e39eb2fec" have entirely different histories.

6 changed files with 21 additions and 129 deletions

View file

@ -4,8 +4,6 @@
una API para correr cosas aisladas en una VM [firecracker] que inicia rápido a partir de una imágen de contenedor OCI. inspirado en [Fly Machines] y otras cosas. una API para correr cosas aisladas en una VM [firecracker] que inicia rápido a partir de una imágen de contenedor OCI. inspirado en [Fly Machines] y otras cosas.
[notas y referencias sobre firecracker](https://nulo.ar/Firecracker.html) en mi sitio
[firecracker]: <https://github.com/firecracker-microvm/firecracker> [firecracker]: <https://github.com/firecracker-microvm/firecracker>
[Fly Machines]: <https://fly.io/docs/reference/machines/> [Fly Machines]: <https://fly.io/docs/reference/machines/>

3
server/.gitignore vendored
View file

@ -3,6 +3,3 @@ container-baby.js
# cache, will move later # cache, will move later
cache/ cache/
# jailer
jailer/

View file

@ -1,13 +1,11 @@
import { delay } from "nanodelay"; import { delay } from "nanodelay";
import { customAlphabet as nanoidCustomAlphabet, nanoid } from "nanoid"; import { nanoid } from "nanoid";
import which from "which";
import { spawn, execFile as _execFile } from "node:child_process"; import { spawn, execFile as _execFile } from "node:child_process";
import { createServer, request as _request } from "node:http"; import { createServer, request as _request } from "node:http";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { basename, join } from "node:path"; import { join } from "node:path";
import { downloadImage, parseImageRef } from "./container-baby.js"; import { downloadImage, parseImageRef } from "./container-baby.js";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { access, chmod, chown, link, mkdir } from "node:fs/promises";
const execFile = promisify(_execFile); const execFile = promisify(_execFile);
const server = createServer(listener); const server = createServer(listener);
@ -57,115 +55,52 @@ async function runVm(image) {
* @prop {boolean} is_read_only * @prop {boolean} is_read_only
*/ */
/**
* gets absolute path to firecracker executable
* @returns {Promise<string>}
*/
async function getFirecrackerExe() {
const firecrackerExe = await which("firecracker", { nothrow: true });
console.debug(
`[fireactions] Using firecracker executable '${firecrackerExe}'`
);
return firecrackerExe;
}
const jailerDir = "./jailer";
const [jailerUid, jailerGid] = [65534, 65534];
// we need to use a custom alphabet because jailer is picky
const jailerNanoid = nanoidCustomAlphabet(
"0123456789abcdefghijklmnopqrstuvwxyz-",
64
);
class FirecrackerInstance { class FirecrackerInstance {
vmid;
socketPath;
proc; proc;
/** socketPath;
* @param {{ firecrackerExe: string }} param0 constructor() {
* @private const vmid = nanoid();
*/ this.socketPath = join(tmpdir(), `firecracker-${vmid}.sock`);
constructor({ firecrackerExe }) {
this.vmid = jailerNanoid();
this.socketPath = join(this.chrootPath, `firecracker.sock`);
console.debug(this.socketPath); console.debug(this.socketPath);
// TODO: jailer
this.proc = spawn( this.proc = spawn(
"jailer", "firecracker",
[ [
...["--id", this.vmid], ...["--api-sock", this.socketPath],
...["--exec-file", firecrackerExe], ...["--level", "Debug"],
...["--uid", "" + jailerUid], ...["--log-path", "/dev/stderr"],
...["--gid", "" + jailerGid], "--show-level",
...["--chroot-base-dir", jailerDir], "--show-log-origin",
"--",
...["--api-sock", "/firecracker.sock"],
// ...["--level", "Debug"],
// ...["--log-path", "/stderr.log"],
// "--show-level",
// "--show-log-origin",
], ],
{ {
stdio: "inherit", stdio: "inherit",
} }
); );
console.debug(
`[fireactions] Started firecracker with jailer at ${this.chrootPath}`
);
} }
get chrootPath() {
return join(jailerDir, "firecracker", this.vmid, "root");
}
/**
* @param {string} path path to file to hardlink to root of chroot
* @param {number} mod mod inside the chroot
*/
async hardlinkToChroot(path, mod = 0o600) {
const chrootPath = join(this.chrootPath, basename(path));
let accesed = false;
try {
await access(chrootPath);
accesed = true;
} catch {}
if (accesed)
throw new Error("file with same name already exists inside chroot");
await link(path, chrootPath);
await chown(chrootPath, jailerUid, jailerGid);
await chmod(chrootPath, mod);
}
/** /**
* @param [opts] {{ drives?: Drive[] }} * @param [opts] {{ drives?: Drive[] }}
*/ */
static async run(opts) { static async run(opts) {
await mkdir(jailerDir, { recursive: true }); const self = new FirecrackerInstance();
const self = new FirecrackerInstance({
firecrackerExe: await getFirecrackerExe(),
});
await mkdir(self.chrootPath, { recursive: true });
// TODO: retry until success // TODO: retry until success
await delay(15); await delay(15);
const { ifname, hostAddr, guestAddr } = await createNetworkInterface(); const { ifname, hostAddr, guestAddr } = await createNetworkInterface();
await self.hardlinkToChroot("../vmlinux.bin", 0o400);
await self.request("PUT", "/boot-source", { await self.request("PUT", "/boot-source", {
kernel_image_path: "/vmlinux.bin", kernel_image_path: "../vmlinux.bin",
boot_args: `console=ttyS0 reboot=k panic=1 pci=off fireactions_ip=${guestAddr}:${hostAddr}`, boot_args: `console=ttyS0 reboot=k panic=1 pci=off fireactions_ip=${guestAddr}:${hostAddr}`,
}); });
// TODO: readonly await self.request("PUT", "/drives/rootfs", {
self.attachDrive({
drive_id: "rootfs", drive_id: "rootfs",
path_on_host: "/rootfs.ext4", path_on_host: "../rootfs.ext4",
is_root_device: true, is_root_device: true,
// TODO: readonly
is_read_only: false, is_read_only: false,
}); });
if (opts?.drives) { if (opts?.drives) {
for (const drive of opts.drives) { for (const drive of opts.drives) {
await self.attachDrive(drive); await self.request("PUT", "/drives/" + drive.drive_id, drive);
} }
} }
await self.request("PUT", "/network-interfaces/eth0", { await self.request("PUT", "/network-interfaces/eth0", {
@ -184,18 +119,6 @@ class FirecrackerInstance {
}); });
} }
/**
* @param {Drive} drive
*/
async attachDrive(drive) {
const perms = drive.is_read_only ? 0o400 : 0o600;
await this.hardlinkToChroot(drive.path_on_host, perms);
await this.request("PUT", "/drives/" + drive.drive_id, {
...drive,
path_on_host: "/" + basename(drive.path_on_host),
});
}
/** /**
* @param {string} method * @param {string} method
* @param {string} path * @param {string} path

View file

@ -5,7 +5,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "esbuild --bundle index.js --platform=node --sourcemap > build.cjs && sudo env PATH=\"$PATH:$HOME/.local/bin\" node --enable-source-maps build.cjs" "start": "esbuild --bundle index.js --platform=node --sourcemap > build.cjs && sudo node --enable-source-maps build.cjs"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -15,14 +15,12 @@
"@types/gunzip-maybe": "^1.4.0", "@types/gunzip-maybe": "^1.4.0",
"@types/node": "^20.2.5", "@types/node": "^20.2.5",
"@types/tar-stream": "^2.2.2", "@types/tar-stream": "^2.2.2",
"@types/which": "^3.0.0",
"undici": "^5.22.1" "undici": "^5.22.1"
}, },
"dependencies": { "dependencies": {
"gunzip-maybe": "^1.4.2", "gunzip-maybe": "^1.4.2",
"nanodelay": "^2.0.2", "nanodelay": "^2.0.2",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"tar-stream": "^3.0.0", "tar-stream": "^3.0.0"
"which": "^3.0.1"
} }
} }

View file

@ -17,9 +17,6 @@ dependencies:
tar-stream: tar-stream:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
which:
specifier: ^3.0.1
version: 3.0.1
devDependencies: devDependencies:
'@tsconfig/node16': '@tsconfig/node16':
@ -34,9 +31,6 @@ devDependencies:
'@types/tar-stream': '@types/tar-stream':
specifier: ^2.2.2 specifier: ^2.2.2
version: 2.2.2 version: 2.2.2
'@types/which':
specifier: ^3.0.0
version: 3.0.0
undici: undici:
specifier: ^5.22.1 specifier: ^5.22.1
version: 5.22.1 version: 5.22.1
@ -63,10 +57,6 @@ packages:
'@types/node': 20.2.5 '@types/node': 20.2.5
dev: true dev: true
/@types/which@3.0.0:
resolution: {integrity: sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==}
dev: true
/abort-controller@3.0.0: /abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'} engines: {node: '>=6.5'}
@ -180,10 +170,6 @@ packages:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: false dev: false
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: false
/nanodelay@2.0.2: /nanodelay@2.0.2:
resolution: {integrity: sha512-6AS5aCSXsjoxq2Jr9CdaAeT60yoYDOTp6po9ziqeOeY6vf6uTEHYSqWql6EFILrM3fEfXgkZ4KqE9L0rTm/wlA==} resolution: {integrity: sha512-6AS5aCSXsjoxq2Jr9CdaAeT60yoYDOTp6po9ziqeOeY6vf6uTEHYSqWql6EFILrM3fEfXgkZ4KqE9L0rTm/wlA==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@ -315,14 +301,6 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false dev: false
/which@3.0.1:
resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
hasBin: true
dependencies:
isexe: 2.0.0
dev: false
/wrappy@1.0.2: /wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: false dev: false

View file

@ -2,8 +2,6 @@
// TODO: sandboxear mkquashfs // TODO: sandboxear mkquashfs
// TODO: fijarse como hace firecracker-containerd // TODO: fijarse como hace firecracker-containerd
// TODO: fijarse como hace [ignite](https://github.com/weaveworks/ignite) // TODO: fijarse como hace [ignite](https://github.com/weaveworks/ignite)
// TODO: fijarse como hace [thi](https://github.com/thi-startup/init)
// TODO: fijarse como hace [firebuild](https://combust-labs.github.io/firebuild-docs/)
import gunzip from "gunzip-maybe"; import gunzip from "gunzip-maybe";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";