INTEGRITY Документация

Интерфейс Container

Container класс ↗ от @cloudflare/containers ↗ это наиболее распространённый способ взаимодействия с экземплярами контейнеров из Worker.

Container расширяет DurableObject. Durable Object управляет маршрутизацией, постоянным состоянием и хуками жизненного цикла, а процесс контейнера выполняет ваш образ внутри виртуальной машины Linux. Поскольку ваш подкласс является Durable Object, вам доступен полный API Durable Object, включая this.ctx.storage для постоянного хранения на основе SQLite и this.ctx.id для уникального идентификатора экземпляра. Используйте хранилище Durable Object для сохранения состояния, которое должно пережить перезапуск контейнера, например конфигурации, пользовательских данных или результатов задач.

npm i @cloudflare/containers

Затем определите класс, который расширяет Container и задайте общие свойства класса:

import { Container, getContainer } from "@cloudflare/containers";

export class SandboxContainer extends Container {
	defaultPort = 8080;
	requiredPorts = [8080, 9222];
	sleepAfter = "5m";
	envVars = {
		NODE_ENV: "production",
		LOG_LEVEL: "info",
	};
	entrypoint = ["npm", "run", "start"];
	enableInternet = false;
	pingEndpoint = "localhost/ready";
}

export default {
	async fetch(request, env) {
		return getContainer(env.SANDBOX_CONTAINER, "workspace-123").fetch(request);
	},
};
import { Container, getContainer } from "@cloudflare/containers";

export class SandboxContainer extends Container {
	defaultPort = 8080;
	requiredPorts = [8080, 9222];
	sleepAfter = "5m";
	envVars = {
		NODE_ENV: "production",
		LOG_LEVEL: "info",
	};
	entrypoint = ["npm", "run", "start"];
	enableInternet = false;
	pingEndpoint = "localhost/ready";
}

export default {
	async fetch(request: Request, env) {
		return getContainer(env.SANDBOX_CONTAINER, "workspace-123").fetch(request);
	},
};

Container класс расширяет DurableObject, поэтому все Durable Object функциональность доступна, включая Хранилище SQLite, alarms, а также Методы RPC. Диск Container по умолчанию является временным, но хранилище Durable Object сохраняется при перезапусках контейнера.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async runAndPersist() {
		const res = await this.containerFetch("/run-task");
		const body = await res.text();
		this.ctx.storage.sql.exec(
			"INSERT OR REPLACE INTO results (value) VALUES (?)",
			body,
		);
		return body;
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async runAndPersist() {
		const res = await this.containerFetch("/run-task");
		const body = await res.text();
		this.ctx.storage.sql.exec(
			"INSERT OR REPLACE INTO results (value) VALUES (?)",
			body,
		);
		return body;
	}
}

Выполнять команды

Используйте this.ctx.container.exec() чтобы запустить другой процесс внутри работающего Container. См. Выполнять команды примеры запуска, потоковой передачи, вывода и управления процессом.

Свойства

Настройте их как поля класса в вашем подклассе. Они применяются к каждому экземпляру контейнера.

Хуки жизненного цикла

Переопределите эти методы, чтобы выполнять код Worker при изменении состояния контейнера. См. пример хуков статуса для исчерпывающего примера.

onStart

Выполняет код Worker после запуска контейнера.

onStart(): void | Promise<void>

Возвращает: void | Promise<void>. Разрешается после завершения любой логики запуска.

Используйте это для журналирования запуска, начального заполнения данных или планирования повторяющихся задач с помощью schedule().

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async onStart() {
		await this.containerFetch("http://localhost/bootstrap", {
			method: "POST",
		});
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    override async onStart() {
    	await this.containerFetch("http://localhost/bootstrap", {
    		method: "POST",
    	});
    }

}

onStop

Выполняет код Worker после завершения процесса контейнера.

onStop(params: StopParams): void | Promise<void>

Параметры:

Возвращает: void | Promise<void>. Разрешается после завершения логики завершения работы.

Используйте это для журналирования, оповещений или перезапуска контейнера.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	onStop({ exitCode, reason }) {
		console.log("Container stopped", { exitCode, reason });
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	override onStop({ exitCode, reason }) {
		console.log("Container stopped", { exitCode, reason });
	}
}

onError

Обработка ошибок запуска и проверки портов.

onError(error: unknown): any

Параметры:

Возвращает: any. Реализация по умолчанию записывает ошибку в лог и выбрасывает её повторно.

Переопределите этот метод, чтобы подавлять ошибки, уведомлять внешний сервис или пытаться перезапустить контейнер.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	onError(error) {
		console.error("Container failed to start", error);
		throw error;
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	override onError(error: unknown) {
		console.error("Container failed to start", error);
		throw error;
	}
}

onActivityExpired

Выполняет код Worker, когда sleepAfter таймер истекает.

onActivityExpired(): Promise<void>

Возвращает: Promise<void>. Разрешается после завершения логики простоя.

Вызывается, когда sleepAfter тайм-аут истекает без входящих запросов. Реализация по умолчанию вызывает stop().

Если вы переопределите этот метод, не остановив контейнер, таймер обновится, и хук снова сработает по истечении следующего периода.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	sleepAfter = "2m";

	async onActivityExpired() {
		const state = await this.getState();
		console.log("Container is idle, stopping it now", state.status);

		await this.stop();
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	sleepAfter = "2m";

    override async onActivityExpired() {
    	const state = await this.getState();
    	console.log("Container is idle, stopping it now", state.status);

    	await this.stop();
    }

}

Методы запроса

fetch

Обработка входящих запросов HTTP или WebSocket.

fetch(request: Request): Promise<Response>

Параметры:

Возвращает: Promise<Response> из контейнера или из вашей собственной логики маршрутизации.

По умолчанию fetch перенаправляет запрос к процессу контейнера по адресу defaultPort. Контейнер запускается автоматически, если он ещё не запущен.

Переопределить fetch если перед передачей запроса контейнеру нужна логика маршрутизации, аутентификация или другое промежуточное ПО. Внутри переопределённого метода вызовите this.containerFetch() вместо this.fetch() чтобы избежать бесконечной рекурсии:

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async fetch(request) {
		const url = new URL(request.url);

		if (url.pathname === "/health") {
			return new Response("ok");
		}

		return this.containerFetch(request);
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    override async fetch(request: Request): Promise<Response> {
    	const url = new URL(request.url);

    	if (url.pathname === "/health") {
    		return new Response("ok");
    	}

    	return this.containerFetch(request);
    }

}

fetch это единственный метод, поддерживающий проксирование WebSocket. См. Пример WebSocket для исчерпывающего примера.

containerFetch

Отправляет HTTP-запрос напрямую процессу контейнера. Как правило, пользователям следует предпочитать использовать fetch если оно не было переопределено.

containerFetch(request: Request, port?: number): Promise<Response>
containerFetch(url: string | URL, init?: RequestInit, port?: number): Promise<Response>

Параметры:

Возвращает: Promise<Response> из контейнера.

Именно так выглядит стандартный fetch() реализация вызывает внутренне, и именно её нужно вызывать из переопределённого fetch() метод, чтобы избежать бесконечной рекурсии. Он также принимает стандартную сигнатуру в стиле fetch со строкой URL и RequestInit, что полезно, когда вы создаёте новый запрос, а не пересылаете существующий.

Не поддерживает WebSockets. Используйте fetch() с switchPort() для этого.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async fetch(request) {
		const url = new URL(request.url);

		if (url.pathname === "/metrics") {
			return this.containerFetch(
				"http://localhost/internal/metrics",
				{
					headers: {
						authorization: request.headers.get("authorization") ?? "",
					},
				},
				9090,
			);
		}

		return this.containerFetch(request);
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    override async fetch(request: Request): Promise<Response> {
    	const url = new URL(request.url);

    	if (url.pathname === "/metrics") {
    		return this.containerFetch(
    			"http://localhost/internal/metrics",
    			{
    				headers: {
    					authorization: request.headers.get("authorization") ?? "",
    				},
    			},
    			9090,
    		);
    	}

    	return this.containerFetch(request);
    }

}

Запуск и остановка

В большинстве случаев вам не нужно вызывать эти методы напрямую. fetch() и containerFetch() запускают контейнер автоматически. Вызывайте их явно, если нужно заранее прогреть контейнер, выполнить задачу по расписанию или управлять жизненным циклом из хука жизненного цикла.

startAndWaitForPorts

Запускает контейнер и ожидает, пока целевые порты не начнут принимать соединения.

startAndWaitForPorts(args?: StartAndWaitForPortsOptions): Promise<void>
startAndWaitForPorts(
  ports?: number | number[],
  cancellationOptions?: CancellationOptions,
  startOptions?: ContainerStartConfigOptions,
): Promise<void>

Параметры:

Возвращает: Promise<void>. Разрешается после готовности целевых портов и onStart() был выполнен.

Это самый безопасный способ явно запустить контейнер, когда нужно убедиться, что он готов принимать трафик.

Этот метод также поддерживает позиционные ports, cancellationOptions, а также startOptions аргументы, но объектная форма читается легче.

import { getContainer } from "@cloudflare/containers";

export default {
	async scheduled(_event, env) {
		const container = getContainer(env.API_CONTAINER, "tenant-42");

		await container.startAndWaitForPorts({
			ports: [8080, 9222],
			startOptions: {
				envVars: {
					API_KEY: env.API_KEY,
					TENANT_ID: "tenant-42",
				},
			},
			cancellationOptions: {
				portReadyTimeoutMS: 30_000,
			},
		});
	},
};
import { getContainer } from "@cloudflare/containers";

export default {
	async scheduled(_event, env) {
		const container = getContainer(env.API_CONTAINER, "tenant-42");

    	await container.startAndWaitForPorts({
    		ports: [8080, 9222],
    		startOptions: {
    			envVars: {
    				API_KEY: env.API_KEY,
    				TENANT_ID: "tenant-42",
    			},
    		},
    		cancellationOptions: {
    			portReadyTimeoutMS: 30_000,
    		},
    	});
    },

};

См. Пример переменных окружения и секретов для исчерпывающего примера.

start

Запускает контейнер, не дожидаясь готовности всех портов.

start(startOptions?: ContainerStartConfigOptions, waitOptions?: WaitOptions): Promise<void>

Параметры:

Возвращает: Promise<void>. Разрешается после успешной попытки запуска и onStart() был выполнен.

Используйте это, когда контейнер не открывает порты, например для пакетной задачи или cron-задачи, или когда нужно самостоятельно управлять готовностью с помощью waitForPort(). Если нужно дождаться готовности всех портов, используйте startAndWaitForPorts() взамен.

import { getContainer } from "@cloudflare/containers";

export default {
	async scheduled(_event, env) {
		const container = getContainer(env.JOB_CONTAINER, "nightly-report");

		await container.start({
			entrypoint: ["node", "scripts/nightly-report.js"],
			envVars: {
				REPORT_DATE: new Date().toISOString(),
			},
			enableInternet: false,
		});
	},
};
import { getContainer } from "@cloudflare/containers";

export default {
	async scheduled(_event, env) {
		const container = getContainer(env.JOB_CONTAINER, "nightly-report");

    	await container.start({
    		entrypoint: ["node", "scripts/nightly-report.js"],
    		envVars: {
    			REPORT_DATE: new Date().toISOString(),
    		},
    		enableInternet: false,
    	});
    },

};

См. Пример cron для исчерпывающего примера.

waitForPort

Опрашивайте один порт, пока он не начнёт принимать подключения.

waitForPort(waitOptions: WaitOptions): Promise<number>

Параметры:

Возвращает: Promise<number>. Числовое возвращаемое значение полезно в первую очередь тогда, когда вы координируете собственную логику готовности между несколькими ожиданиями.

Выбрасывает исключение, если порт не становится доступен в пределах лимита повторных попыток. Используйте это после start() если нужно проверять несколько портов независимо друг от друга или в определённом порядке.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	async warmInspector() {
		await this.start();

		const retryCount = await this.waitForPort({
			portToCheck: 9222,
			retries: 20,
			waitInterval: 500,
		});

		console.log("Inspector port became ready:", retryCount);
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	async warmInspector() {
		await this.start();

    	const retryCount = await this.waitForPort({
    		portToCheck: 9222,
    		retries: 20,
    		waitInterval: 500,
    	});

    	console.log("Inspector port became ready:", retryCount);
    }

}

stop

Отправляет сигнал процессу контейнера.

stop(signal?: 'SIGTERM' | 'SIGINT' | 'SIGKILL' | number): Promise<void>

Параметры:

Возвращает: Promise<void>. Разрешается после отправки сигнала и завершения обработки остановки.

По умолчанию: SIGTERM, что даёт процессу возможность корректно завершиться. Вызывает onStop().

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async fetch(request) {
		if (new URL(request.url).pathname === "/admin/stop") {
			await this.stop();
			return new Response("Container is stopping");
		}

		return this.containerFetch(request);
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    override async fetch(request: Request): Promise<Response> {
    	if (new URL(request.url).pathname === "/admin/stop") {
    		await this.stop();
    		return new Response("Container is stopping");
    	}

    	return this.containerFetch(request);
    }

}

destroy

Немедленно завершить процесс контейнера.

destroy(): Promise<void>

Возвращает: Promise<void>. Разрешается после того, как среда выполнения уничтожит контейнер.

Это отправляет SIGKILL. Используйте это, когда контейнер нужно удалить немедленно и нет возможности дождаться корректного завершения работы. Вызывает onStop().

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async fetch(request) {
		if (new URL(request.url).pathname === "/admin/destroy") {
			await this.destroy();
			return new Response("Container destroyed");
		}

		return this.containerFetch(request);
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    override async fetch(request: Request): Promise<Response> {
    	if (new URL(request.url).pathname === "/admin/destroy") {
    		await this.destroy();
    		return new Response("Container destroyed");
    	}

    	return this.containerFetch(request);
    }

}

Состояние и мониторинг

getState

Чтение текущего состояния контейнера.

getState(): Promise<State>

Возвращает: Promise<State> с:

running означает, что контейнер запускается и ещё не прошёл проверку работоспособности. healthy означает, что он запущен и принимает запросы.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	async logState() {
		const state = await this.getState();

		if (state.status === "stopped_with_code") {
			console.error("Container exited with code", state.exitCode);
			return;
		}

		console.log("Container status:", state.status);
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	async logState() {
		const state = await this.getState();

    	if (state.status === "stopped_with_code") {
    		console.error("Container exited with code", state.exitCode);
    		return;
    	}

    	console.log("Container status:", state.status);
    }

}

renewActivityTimeout

Сбросьте sleepAfter таймер.

renewActivityTimeout(): void

Возвращает: void.

Входящие запросы автоматически сбрасывают таймер. Вызывайте этот метод вручную из фоновой работы, например из запланированной задачи или длительной операции, которая должна засчитываться как активность и не давать контейнеру перейти в спящий режим.

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async processJobs(jobIds) {
		for (const jobId of jobIds) {
			this.renewActivityTimeout();

			await this.containerFetch(`http://localhost/jobs/${jobId}`, {
				method: "POST",
			});
		}
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    async processJobs(jobIds: string[]) {
    	for (const jobId of jobIds) {
    		this.renewActivityTimeout();

    		await this.containerFetch(`http://localhost/jobs/${jobId}`, {
    			method: "POST",
    		});
    	}
    }

}

Планирование

schedule

Планирует выполнение метода класса позже.

schedule<T>(when: Date | number, callback: string, payload?: T): Promise<Schedule<T>>

Параметры:

Возвращает: Promise<Schedule<T>> с:

Не переопределяйте alarm() ↗ напрямую. Container класс использует обработчик alarm для управления жизненным циклом контейнера, поэтому используйте schedule() взамен.

В следующем примере планируется регулярный отчёт о состоянии, начиная с запуска контейнера:

import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

	async onStart() {
		await this.schedule(60, "healthReport");
	}

	async healthReport() {
		const state = await this.getState();
		console.log("Container status:", state.status);
		await this.schedule(60, "healthReport");
	}
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;

    override async onStart() {
    	await this.schedule(60, "healthReport");
    }

    async healthReport() {
    	const state = await this.getState();
    	console.log("Container status:", state.status);
    	await this.schedule(60, "healthReport");
    }

}

Перехват исходящего трафика

Перехват исходящего трафика позволяет перехватывать, подменять или блокировать HTTP-запросы, которые контейнер отправляет на внешние хосты. Это полезно для песочницы, тестирования или проксирования исходящего трафика через код Worker.

import {
	Container,
	ContainerProxy,
	getContainer,
} from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;
	enableInternet = true;

	static outboundByHost = {
		"blocked.example.com": () => {
			return new Response("Blocked", { status: 403 });
		},
	};

	static outbound = async (request, _env, ctx) => {
		console.log(`[${ctx.containerId}] outbound:`, request.url);
		return fetch(request);
	};
}

export { ContainerProxy };

export default {
	async fetch(request, env) {
		return getContainer(env.MY_CONTAINER).fetch(request);
	},
};
import {
	Container,
	ContainerProxy,
	getContainer,
} from "@cloudflare/containers";

export class MyContainer extends Container {
	defaultPort = 8080;
	enableInternet = true;

    static outboundByHost = {
    	"blocked.example.com": () => {
    		return new Response("Blocked", { status: 403 });
    	},
    };

    static outbound = async (request, _env, ctx) => {
    	console.log(`[${ctx.containerId}] outbound:`, request.url);
    	return fetch(request);
    };

}

export { ContainerProxy };

export default {
	async fetch(request: Request, env) {
		return getContainer(env.MY_CONTAINER).fetch(request);
	},
};

Подробнее см. в Обработка исходящего трафика.

Служебные функции

Эти функции экспортируются вместе с Container класс из @cloudflare/containers.

getContainer

Получите stub для именованного экземпляра контейнера.

getContainer<T>(binding: DurableObjectNamespace<T>, name?: string): DurableObjectStub<T>

Параметры:

Возвращает: DurableObjectStub<T> для именованного экземпляра контейнера.

Используйте это, когда нужен один контейнер на логическую сущность, например на сеанс пользователя, документ или игровую комнату, определяемую стабильным именем.

import { getContainer } from "@cloudflare/containers";

export default {
	async fetch(request, env) {
		const { sessionId } = await request.json();
		return getContainer(env.MY_CONTAINER, sessionId).fetch(request);
	},
};
import { getContainer } from "@cloudflare/containers";

export default {
	async fetch(request: Request, env) {
		const { sessionId } = await request.json();
		return getContainer(env.MY_CONTAINER, sessionId).fetch(request);
	},
};

getRandom

Получите stub для случайно выбранного экземпляра контейнера.

getRandom<T>(binding: DurableObjectNamespace<T>, instances?: number): Promise<DurableObjectStub<T>>

Параметры:

Возвращает: Promise<DurableObjectStub<T>> для случайно выбранного экземпляра.

Используйте это для нагрузок без сохранения состояния, где любой контейнер может обработать любой запрос и нужно распределить нагрузку между несколькими экземплярами.

import { getRandom } from "@cloudflare/containers";

export default {
	async fetch(request, env) {
		const container = await getRandom(env.WORKER_POOL, 5);
		return container.fetch(request);
	},
};
import { getRandom } from "@cloudflare/containers";

export default {
	async fetch(request: Request, env) {
		const container = await getRandom(env.WORKER_POOL, 5);
		return container.fetch(request);
	},
};

См. пример экземпляров без сохранения состояния для исчерпывающего примера.

switchPort

Используйте другой порт контейнера, продолжая использовать fetch().

switchPort(request: Request, port: number): Request

Параметры:

Возвращает: Request копию с установленным целевым портом.

Используйте это, когда нужно указать конкретный порт и также нужна поддержка WebSocket. Если поддержка WebSocket не требуется, передавайте порт напрямую в containerFetch() взамен.

import { getContainer, switchPort } from "@cloudflare/containers";

export default {
	async fetch(request, env) {
		const container = getContainer(env.MY_CONTAINER);
		return container.fetch(switchPort(request, 9090));
	},
};
import { getContainer, switchPort } from "@cloudflare/containers";

export default {
	async fetch(request: Request, env) {
		const container = getContainer(env.MY_CONTAINER);
		return container.fetch(switchPort(request, 9090));
	},
};