← Containers / containers
Spouštění příkazů
Použijte exec() ke spuštění dalšího procesu uvnitř běžícího Kontejner. Příklady volají this.ctx.container.exec() uvnitř třídy, která rozšiřuje Container z @cloudflare/containers.
exec() nespouští zastavený Container. V metodách vzdáleného volání procedur (RPC) zkontrolujte this.ctx.container.running a volejte await this.start() v případě potřeby. Můžete také použít onStart() hook pro spuštění libovolné sady příkazů při každém startu kontejneru.
Spuštění procesu po startu
Následující hook spustí přípravný příkaz pokaždé, když se kontejner spustí. Z tohoto hooku můžete spustit libovolnou sérii startovacích příkazů. output() ukládá standardní výstup a standardní chybový výstup do bufferu jako samostatné ArrayBuffer hodnoty.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async onStart() {
const process = await this.ctx.container.exec([
"node",
"scripts/prepare.js",
]);
const output = await process.output();
const decoder = new TextDecoder();
if (output.exitCode !== 0) {
throw new Error(
`Container preparation failed: ${decoder.decode(output.stderr)}`,
);
}
console.log(decoder.decode(output.stdout));
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
override async onStart() {
const process = await this.ctx.container.exec([
"node",
"scripts/prepare.js",
]);
const output = await process.output();
const decoder = new TextDecoder();
if (output.exitCode !== 0) {
throw new Error(
`Container preparation failed: ${decoder.decode(output.stderr)}`,
);
}
console.log(decoder.decode(output.stdout));
}
}V metodě RPC se ujistěte, že Container běží, než zavoláte exec(). Standardní výstup ve výchozím nastavení používá čitelný stream.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async readVersion() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["node", "--version"]);
const stdout = process.stdout
? await new Response(process.stdout).text()
: "";
const exitCode = await process.exitCode;
return { pid: process.pid, stdout, exitCode };
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async readVersion() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["node", "--version"]);
const stdout = process.stdout
? await new Response(process.stdout).text()
: "";
const exitCode = await process.exitCode;
return { pid: process.pid, stdout, exitCode };
}
}Vrácený pid identifikuje nový proces. exitCode promise se vyřeší ve chvíli, kdy tento proces skončí.
Přímé předání argumentů
exec() operace spustí spustitelný soubor přímo se zadaným polem argumentů. Shell přitom nejprve nespouští.
Každá položka pole se stane jedním argumentem. Funkce shellu, jako jsou roury, přesměrování, expanze zástupných znaků a proměnných, se implicitně nespouštějí.
Pokud váš příkaz tyto funkce potřebuje, spusťte shell. Použijte ["bash", "-lc", "<COMMAND>"] pokud je v image dostupný Bash. Použijte ["sh", "-c", "<COMMAND>"] pokud image poskytuje pouze shell podle Portable Operating System Interface (POSIX). Nedůvěryhodné hodnoty předávejte jako samostatné argumenty, nikoli vložené do řetězce příkazu shellu.
Odeslat standardní vstup
Předejte ReadableStream k odeslání existujících dat. Nastavení stdout na "ignore" zahazuje standardní výstup.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async importData(data) {
if (!this.ctx.container.running) {
await this.start();
}
const stdin = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(data));
controller.close();
},
});
const process = await this.ctx.container.exec(["cat"], {
stdin,
stdout: "ignore",
});
const output = await process.output();
return {
stdoutBytes: output.stdout.byteLength,
exitCode: output.exitCode,
};
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async importData(data: string) {
if (!this.ctx.container.running) {
await this.start();
}
const stdin = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode(data));
controller.close();
},
});
const process = await this.ctx.container.exec(["cat"], {
stdin,
stdout: "ignore",
});
const output = await process.output();
return {
stdoutBytes: output.stdout.byteLength,
exitCode: output.exitCode,
};
}
}Ignorovaný standardní výstup vytvoří prázdný buffer z output(). Nastavte stdin na "pipe" pro průběžný zápis dat.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async concatenateInput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["cat"], {
stdin: "pipe",
});
const writer = process.stdin?.getWriter();
if (!writer) {
throw new Error("Standard input is unavailable");
}
const encoder = new TextEncoder();
await writer.write(encoder.encode("first\n"));
await writer.write(encoder.encode("second\n"));
await writer.close();
const output = await process.output();
return new TextDecoder().decode(output.stdout);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async concatenateInput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["cat"], {
stdin: "pipe",
});
const writer = process.stdin?.getWriter();
if (!writer) {
throw new Error("Standard input is unavailable");
}
const encoder = new TextEncoder();
await writer.write(encoder.encode("first\n"));
await writer.write(encoder.encode("second\n"));
await writer.close();
const output = await process.output();
return new TextDecoder().decode(output.stdout);
}
}Zavřete writer, čímž odešlete konec souboru (EOF). Pokud vynecháte stdin, exec() uzavírá standardní vstup a okamžitě odesílá EOF.
Předání streamu RPC na standardní vstup
Metody RPC mohou přijímat bajtově orientované ReadableStream hodnoty, jejichž podkladový zdroj používá type: "bytes". Request tělo tento požadavek splňuje. Přijatý stream můžete předat přímo do exec() bez bufferování celého streamu v Durable Object. Další informace najdete v Streamy přes RPC.
import { Container, getContainer } from "@cloudflare/containers";
export class MyContainer extends Container {
async writeFile(input) {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["tee", "/tmp/upload.bin"], {
stdin: input,
stdout: "ignore",
});
return process.exitCode;
}
}
export default {
async fetch(request, env) {
if (!request.body) {
return new Response("Request body required", { status: 400 });
}
const container = getContainer(env.MY_CONTAINER, "upload-worker");
const exitCode = await container.writeFile(request.body);
return Response.json({ exitCode });
},
};import { Container, getContainer } from "@cloudflare/containers";
export class MyContainer extends Container {
async writeFile(input: ReadableStream<Uint8Array>) {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
["tee", "/tmp/upload.bin"],
{
stdin: input,
stdout: "ignore",
},
);
return process.exitCode;
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (!request.body) {
return new Response("Request body required", { status: 400 });
}
const container = getContainer(env.MY_CONTAINER, "upload-worker");
const exitCode = await container.writeFile(request.body);
return Response.json({ exitCode });
},
};RPC předává vlastnictví streamu objektu Durable Object. Volající Worker jej nemůže číst poté, co ho předá do writeFile().
Následující cat proces skončí, protože standardní vstup je vynechán:
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async verifyEndOfFile() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["cat"]);
const output = await process.output();
return {
stdoutBytes: output.stdout.byteLength,
exitCode: output.exitCode,
};
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async verifyEndOfFile() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["cat"]);
const output = await process.output();
return {
stdoutBytes: output.stdout.byteLength,
exitCode: output.exitCode,
};
}
}Nastavení kontextu procesu
Použijte cwd, env, a user k nastavení kontextu procesu. Proces dědí prostředí kontejneru nastavené pomocí envVars. Na jedno spuštění env hodnoty přidávají proměnné nebo přepisují odpovídající klíče.
Tento příklad používá sh protože vyžaduje expanzi a přesměrování. Zároveň zachytává standardní výstup a standardní chybový výstup odděleně.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
envVars = {
BASE_VALUE: "inherited",
MODE: "default",
};
async inspectWorkspace() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
[
"sh",
"-c",
'printf "%s:%s:%s:%s" "$PWD" "$BASE_VALUE" "$MODE" "$EXTRA_VALUE"; printf "diagnostic" >&2',
],
{
cwd: "/workspace",
env: {
MODE: "inspection",
EXTRA_VALUE: "added",
},
},
);
const output = await process.output();
const decoder = new TextDecoder();
return {
stdout: decoder.decode(output.stdout),
stderr: decoder.decode(output.stderr),
};
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
envVars = {
BASE_VALUE: "inherited",
MODE: "default",
};
async inspectWorkspace() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
[
"sh",
"-c",
'printf "%s:%s:%s:%s" "$PWD" "$BASE_VALUE" "$MODE" "$EXTRA_VALUE"; printf "diagnostic" >&2',
],
{
cwd: "/workspace",
env: {
MODE: "inspection",
EXTRA_VALUE: "added",
},
},
);
const output = await process.output();
const decoder = new TextDecoder();
return {
stdout: decoder.decode(output.stdout),
stderr: decoder.decode(output.stderr),
};
}
} user nastaví pro proces uživatelské jméno nebo číselné ID uživatele (UID). Container runtime překládá uživatelská jména z image kontejneru.
Sloučit standardní chybový výstup
Nastavte stderr na "combined" ke sloučení standardního chybového výstupu se standardním výstupem. Kombinovaný výstup vyžaduje stdout: "pipe".
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async readCombinedOutput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
[
"bash",
"-lc",
'printf "standard output\n"; printf "standard error\n" >&2',
],
{
stdout: "pipe",
stderr: "combined",
},
);
const output = await process.output();
return new TextDecoder().decode(output.stdout);
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async readCombinedOutput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
[
"bash",
"-lc",
'printf "standard output\n"; printf "standard error\n" >&2',
],
{
stdout: "pipe",
stderr: "combined",
},
);
const output = await process.output();
return new TextDecoder().decode(output.stdout);
}
}Sloučený stream nezaručuje pořadí mezi zdrojovými streamy. V tomto režimu process.stderr je null, a output.stderr je prázdný ArrayBuffer. Tento příklad předpokládá, že Bash je v obrazu k dispozici.
Zpracování nenulových kódů ukončení
Nenulový ukončovací kód způsobí, že se exitCode normálně. Promise se neodmítne.
Tento příklad zachovává standardní chybový výstup a ignoruje standardní výstup:
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async runCheck() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
[
"sh",
"-c",
'printf "not captured"; printf "check failed\n" >&2; exit 7',
],
{ stdout: "ignore" },
);
const output = await process.output();
return {
exitCode: output.exitCode,
stdoutBytes: output.stdout.byteLength,
stderr: new TextDecoder().decode(output.stderr),
};
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async runCheck() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
[
"sh",
"-c",
'printf "not captured"; printf "check failed\n" >&2; exit 7',
],
{ stdout: "ignore" },
);
const output = await process.output();
return {
exitCode: output.exitCode,
stdoutBytes: output.stdout.byteLength,
stderr: new TextDecoder().decode(output.stderr),
};
}
}Výsledek obsahuje návratový kód 7 a text standardního chybového výstupu. Jeho ignorovaný buffer standardního výstupu má nula bajtů.
Streamovat velký výstup
output() ukládá oba streamy do bufferu v paměti. Pro velký výstup vyprázdněte stdout a stderr místo toho souběžně.
import { Container } from "@cloudflare/containers";
async function countBytes(stream) {
if (!stream) {
return 0;
}
let bytes = 0;
for await (const chunk of stream) {
bytes += chunk.byteLength;
}
return bytes;
}
export class MyContainer extends Container {
async generateLargeOutput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec([
"sh",
"-c",
'i=0; while [ "$i" -lt 100000 ]; do printf "output %s\n" "$i"; printf "error %s\n" "$i" >&2; i=$((i + 1)); done',
]);
const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
countBytes(process.stdout),
countBytes(process.stderr),
process.exitCode,
]);
return { stdoutBytes, stderrBytes, exitCode };
}
}import { Container } from "@cloudflare/containers";
async function countBytes(stream: ReadableStream<Uint8Array> | null) {
if (!stream) {
return 0;
}
let bytes = 0;
for await (const chunk of stream) {
bytes += chunk.byteLength;
}
return bytes;
}
export class MyContainer extends Container {
async generateLargeOutput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec([
"sh",
"-c",
'i=0; while [ "$i" -lt 100000 ]; do printf "output %s\n" "$i"; printf "error %s\n" "$i" >&2; i=$((i + 1)); done',
]);
const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
countBytes(process.stdout),
countBytes(process.stderr),
process.exitCode,
]);
return { stdoutBytes, stderrBytes, exitCode };
}
}Streamování a output() jsou alternativní metody spotřeby. output() vyvolá TypeError pokud se z některého streamu už začalo číst. Druhé volání output() také vyvolá TypeError.
Vrátit standardní výstup přes RPC
Vrátí ReadableStream z metody RPC pro streamování výstupu volajícímu Workeru. Kombinace standardního chybového výstupu poskytuje jeden stream pro oba výstupní kanály.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async streamCommandOutput() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
["sh", "-c", 'printf "starting\n"; run-report'],
{ stderr: "combined" },
);
return process.stdout;
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async streamCommandOutput(): Promise<ReadableStream<Uint8Array>> {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(
["sh", "-c", 'printf "starting\n"; run-report'],
{ stderr: "combined" },
);
return process.stdout!;
}
}RPC předává vlastnictví streamu volajícímu a zachovává řízení toku. Volající musí stream zkonzumovat nebo zrušit. Pokud volající přestane číst, backpressure může pozastavit proces, který pokračuje v zápisu.
Tato metoda přenáší výstup, nikoli ExecProcess popisovač. Pokud volající potřebuje metadata o dokončení nebo řízení procesu, definujte samostatný aplikační protokol.
Zastavení procesu
exec() nemá žádný vestavěný časový limit. Ukončení po uplynutí zadané doby vyžádáte pomocí kill() a poté počkejte na exitCode.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async runWithTimeout() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["sleep", "120"]);
const timer = setTimeout(() => process.kill(), 30_000);
try {
return await process.exitCode;
} finally {
clearTimeout(timer);
}
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async runWithTimeout() {
if (!this.ctx.container.running) {
await this.start();
}
const process = await this.ctx.container.exec(["sleep", "120"]);
const timer = setTimeout(() => process.kill(), 30_000);
try {
return await process.exitCode;
} finally {
clearTimeout(timer);
}
}
}Volání kill() bez argumentu zařadí do fronty SIGTERM, signál 15. Pokud to proces vyžaduje, můžete odeslat i jiný signál. Proces může signál zpracovat nebo ignorovat, takže se nejedná o pevný časový limit pro dokončení. Dokončení sledujte přes exitCode, a neodvozujte z signálu konkrétní ukončovací kód.
Koordinace operací
Umístěte exec() volání v Durable Object, který řídí Container. Durable Object může koordinovat stav procesu a životní cyklus Containeru.
Jedna aplikační metoda RPC může provést více exec() operace. Každý příkaz zůstává samostatnou operací exec, ale volající provede jediné RPC volání Durable Object. Tím se snižuje počet zpátečních volání mezi volajícím a Durable Object a zároveň zůstávají rozhodnutí o životním cyklu pohromadě.
import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async runDiagnostics() {
if (!this.ctx.container.running) {
await this.start();
}
const commands = [
["uname", "-a"],
["node", "--version"],
];
const decoder = new TextDecoder();
const results = [];
for (const command of commands) {
const process = await this.ctx.container.exec(command);
const output = await process.output();
results.push({
command,
exitCode: output.exitCode,
stdout: decoder.decode(output.stdout),
stderr: decoder.decode(output.stderr),
});
}
return results;
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
async runDiagnostics() {
if (!this.ctx.container.running) {
await this.start();
}
const commands = [
["uname", "-a"],
["node", "--version"],
];
const decoder = new TextDecoder();
const results = [];
for (const command of commands) {
const process = await this.ctx.container.exec(command);
const output = await process.output();
results.push({
command,
exitCode: output.exitCode,
stdout: decoder.decode(output.stdout),
stderr: decoder.decode(output.stderr),
});
}
return results;
}
}Všechna pole a návratové typy najdete v exec() kontrakt API.