← Cloudflare Workers / workers / platform
Инфраструктура как код (IaC)
Хотя Wrangler упрощает загрузку и управление Workers, иногда требуется более программный подход. Это может означать использование инструментов инфраструктуры как кода (IaC) или прямое взаимодействие с API Workers. Примеры включают скрипты сборки и развёртывания, конвейеры CI/CD, собственные инструменты разработчика и автоматическое тестирование.
Чтобы упростить эту задачу, Cloudflare предоставляет библиотеки SDK для популярных языков, включая cloudflare-typescript ↗ и cloudflare-python ↗. Для IaC можно использовать такие инструменты, как HashiCorp Terraform и Cloudflare Terraform Provider для управления ресурсами Workers.
Ниже приведены примеры развёртывания Worker с использованием различных инструментов и языков, а также важные аспекты управления Workers с помощью IaC.
Для всех этих примеров нужен ID аккаунта и API-токен (не Global API key), чтобы это работало.
Сборка (бандлинг) Workers
Ни один из приведенных ниже примеров не Сборка (бандлинг) Workers. Обычно это делается с помощью Wrangler или такого инструмента, как esbuild ↗.
Как правило, этот этап сборки выполняется перед применением плана Terraform или загрузкой скрипта через API:
wrangler deploy --dry-run --outdir buildЕсли вы используете Wrangler для сборки, а для загрузки другой способ, обязательно скопируйте всю конфигурацию из wrangler.json в вашу конфигурацию Terraform или API-запрос. Это особенно важно при использовании compatibility_date или флагов, от которых зависит ваш скрипт.
Terraform
В этом примере вам понадобится локальный файл с именем my-script.mjs с содержимым скрипта, похожим на приведенные ниже примеры. Подробнее о Cloudflare Terraform Provider, и обратитесь к Пример ресурса скрипта Workers ↗ для всех доступных настроек ресурсов.
variable "account_id" {
default = "replace_me"
}
resource "cloudflare_worker" "my_worker" {
account_id = var.account_id
name = "my-worker"
observability = {
enabled = true
}
}
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
compatibility_date = "2025-02-21" # Set this to today's date
main_module = "my-script.mjs"
modules = [
{
name = "my-script.mjs"
content_type = "application/javascript+module"
# Replacement (version creation) is triggered whenever this file changes
content_file = "my-script.mjs"
}
]
}
resource "cloudflare_workers_deployment" "my_worker_deployment" {
account_id = var.account_id
script_name = cloudflare_worker.my_worker.name
strategy = "percentage"
versions = [{
percentage = 100
version_id = cloudflare_worker_version.my_worker_version.id
}]
}Обратите внимание, что управлять всеми этими ресурсами в Terraform не обязательно. Например, можно использовать только cloudflare_worker ресурс и без проблем использовать Wrangler или собственные инструменты развёртывания для Versions или Deployments.
Привязки в Terraform
Bindings позволяют вашему Worker взаимодействовать с ресурсами Cloudflare Developer Platform. В Terraform привязки настраиваются иначе, чем в Wrangler. Вместо отдельных свойств верхнего уровня для каждого типа привязки (например, kv_namespaces, r2_buckets, и т. д.), Terraform использует единый bindings массив, где каждая привязка имеет type свойство, а также свойства, специфичные для типа.
Ниже приведены примеры каждого типа привязки и обязательные для них свойства:
Привязка пространства имён KV
Привязка к Пространство имён KV для хранилища ключ-значение:
bindings = [{
type = "kv_namespace"
name = "MY_KV"
namespace_id = "your-kv-namespace-id"
}]Свойства:
type:"kv_namespace"name: Имя переменной для привязки, доступной черезenv.MY_KVnamespace_id: ID вашего пространства имён KV
Привязка бакета R2
Привязка к Бакет R2 для объектного хранилища:
bindings = [{
type = "r2_bucket"
name = "MY_BUCKET"
bucket_name = "my-bucket-name"
}]Свойства:
type:"r2_bucket"name: Имя привязки для доступа черезenv.MY_BUCKETbucket_name: Имя вашего бакета R2
Привязка базы данных D1
Привязка к База данных D1 для хранения SQL:
bindings = [{
type = "d1"
name = "DB"
id = "your-database-id"
}]Свойства:
type:"d1"name: Имя привязки для доступа черезenv.DBid: ID вашей базы данных D1
Привязка Durable Object
Привязка к Durable Object класс:
bindings = [{
type = "durable_object_namespace"
name = "MY_DURABLE_OBJECT"
class_name = "MyDurableObjectClass"
}]Свойства:
type:"durable_object_namespace"name: Имя привязки для доступа черезenv.MY_DURABLE_OBJECTclass_name: Экспортируемое имя класса Durable Objectscript_name: (Необязательно) скрипт Worker, который экспортирует этот класс Durable Object. Опустите, если класс определён в этом же Worker.
Service Binding
Привязка к другому Worker для обмена данными между Workers:
bindings = [{
type = "service"
name = "MY_SERVICE"
service = "other-worker-name"
}]Свойства:
type:"service"name: Имя привязки для доступа черезenv.MY_SERVICEservice: Имя целевого Workerentrypoint: (Необязательно) именованный entrypoint для привязки
Привязка Queue
Привязка к Queue для передачи сообщений:
Для отправки сообщений:
bindings = [{
type = "queue"
name = "MY_QUEUE"
queue_name = "my-queue"
}]Свойства:
type:"queue"name: Имя привязки для доступа черезenv.MY_QUEUEqueue_name: Имя вашей Queue
Чтобы получать сообщения, настройте Worker как consumer непосредственно в самом ресурсе очереди, а не через привязки.
Vectorize Binding
Привязка к Индекс Vectorize для векторного поиска:
bindings = [{
type = "vectorize"
name = "VECTORIZE_INDEX"
index_name = "my-index"
}]Свойства:
type:"vectorize"name: Имя привязки для доступа черезenv.VECTORIZE_INDEXindex_name: Имя вашего индекса Vectorize
Привязка Workers AI
Привязка к Workers AI для инференса ИИ:
bindings = [{
type = "ai"
name = "AI"
}]Свойства:
type:"ai"name: Имя привязки для доступа черезenv.AI
Привязка Hyperdrive
Привязка к Hyperdrive конфигурация пула соединений с базой данных:
bindings = [{
type = "hyperdrive"
name = "HYPERDRIVE"
id = "your-hyperdrive-config-id"
}]Свойства:
type:"hyperdrive"name: Имя привязки для доступа черезenv.HYPERDRIVEid: ID вашей конфигурации Hyperdrive
VPC Service Binding
Привязка к VPC Service для доступа к ресурсам в вашей частной сети:
bindings = [{
type = "vpc_service"
name = "PRIVATE_API"
service_id = "your-vpc-service-id"
}]Свойства:
type:"vpc_service"name: Имя привязки для доступа черезenv.PRIVATE_APIservice_id: ID вашего VPC Service (изcloudflare_connectivity_directory_serviceили панели управления)
VPC Service можно создать с помощью Terraform, используя cloudflare_connectivity_directory_service ресурс. Полное руководство см. в Настройте VPC Services с помощью Terraform.
Analytics Engine Binding
Привязка к Analytics Engine набор данных:
bindings = [{
type = "analytics_engine"
name = "ANALYTICS"
dataset = "my_dataset"
}]Свойства:
type:"analytics_engine"name: Имя привязки для доступа черезenv.ANALYTICSdataset: Имя вашего набора данных Analytics Engine
Переменные окружения
Для текстовых переменных окружения используйте plain_text тип привязки:
bindings = [{
type = "plain_text"
name = "MY_VARIABLE"
text = "my-value"
}]Свойства:
type:"plain_text"name: Имя привязки для доступа черезenv.MY_VARIABLEtext: Значение переменной окружения
Secret Text Binding
Для зашифрованных секретов используйте secret_text тип привязки:
bindings = [{
type = "secret_text"
name = "API_KEY"
text = var.api_key
}]Свойства:
type:"secret_text"name: Имя привязки для доступа черезenv.API_KEYtext: Значение секрета (будет зашифровано)
Полный пример
Пример, объединяющий несколько типов привязок:
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
compatibility_date = "2025-08-06"
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_file = "worker.js"
}]
bindings = [
{
type = "kv_namespace"
name = "MY_KV"
namespace_id = var.kv_namespace_id
},
{
type = "r2_bucket"
name = "MY_BUCKET"
bucket_name = "my-bucket"
},
{
type = "d1"
name = "DB"
id = var.d1_database_id
},
{
type = "service"
name = "AUTH_SERVICE"
service = "auth-worker"
},
{
type = "plain_text"
name = "ENVIRONMENT"
text = "production"
},
{
type = "secret_text"
name = "API_KEY"
text = var.api_key
},
{
type = "vpc_service"
name = "PRIVATE_API"
service_id = var.vpc_service_id
}
]
}Библиотеки Cloudflare API
В этом примере используется cloudflare-typescript ↗ SDK, который обеспечивает удобный доступ к Cloudflare REST API из серверного JavaScript или TypeScript.
#!/usr/bin/env -S npm run tsn -T
/**
* Create and deploy a Worker
*
* Docs:
* - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/
* - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/
*
* Prerequisites:
* 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
* 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Environment variables:
* - CLOUDFLARE_API_TOKEN (required)
* - CLOUDFLARE_ACCOUNT_ID (required)
* - CLOUDFLARE_SUBDOMAIN (optional)
*
* Usage:
* Run this script to deploy a simple "Hello World" Worker.
* Access it at: my-hello-world-worker.$subdomain.workers.dev
*/
import { exit } from "node:process";
import Cloudflare from "cloudflare";
const WORKER_NAME = "my-hello-world-worker";
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;
function loadConfig() {
const apiToken = process.env["CLOUDFLARE_API_TOKEN"];
if (!apiToken) {
throw new Error(
"Missing required environment variable: CLOUDFLARE_API_TOKEN",
);
}
const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"];
if (!accountId) {
throw new Error(
"Missing required environment variable: CLOUDFLARE_ACCOUNT_ID",
);
}
const subdomain = process.env["CLOUDFLARE_SUBDOMAIN"];
return {
apiToken,
accountId,
subdomain: subdomain || undefined,
workerName: WORKER_NAME,
};
}
const config = loadConfig();
const client = new Cloudflare({
apiToken: config.apiToken,
});
async function main() {
try {
console.log("🚀 Starting Worker creation and deployment...");
const scriptContent = `
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
},
}`.trim();
let worker;
try {
worker = await client.workers.beta.workers.get(config.workerName, {
account_id: config.accountId,
});
console.log(`♻️ Worker ${config.workerName} already exists. Using it.`);
} catch (error) {
if (!(error instanceof Cloudflare.NotFoundError)) {
throw error;
}
console.log(`✏️ Creating Worker ${config.workerName}...`);
worker = await client.workers.beta.workers.create({
account_id: config.accountId,
name: config.workerName,
subdomain: {
enabled: config.subdomain !== undefined,
},
observability: {
enabled: true,
},
});
}
console.log(`⚙️ Worker id: ${worker.id}`);
console.log("✏️ Creating Worker version...");
// Create the first version of the Worker
const version = await client.workers.beta.workers.versions.create(
worker.id,
{
account_id: config.accountId,
main_module: SCRIPT_FILENAME,
compatibility_date: new Date().toISOString().split("T")[0],
bindings: [
{
type: "plain_text",
name: "MESSAGE",
text: "Hello World!",
},
],
modules: [
{
name: SCRIPT_FILENAME,
content_type: "application/javascript+module",
content_base64: Buffer.from(scriptContent).toString("base64"),
},
],
},
);
console.log(`⚙️ Version id: ${version.id}`);
console.log("🚚 Creating Worker deployment...");
// Create a deployment and point all traffic to the version we created
await client.workers.scripts.deployments.create(config.workerName, {
account_id: config.accountId,
strategy: "percentage",
versions: [
{
percentage: 100,
version_id: version.id,
},
],
});
console.log("✅ Deployment successful!");
if (config.subdomain) {
console.log(`
🌍 Your Worker is live!
📍 URL: https://${config.workerName}.${config.subdomain}.workers.dev/
`);
} else {
console.log(`
⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
}
} catch (error) {
console.error("❌ Deployment failed:", error);
exit(1);
}
}
main();#!/usr/bin/env -S npm run tsn -T
/**
* Create and deploy a Worker
*
* Docs:
* - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/
* - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/
*
* Prerequisites:
* 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
* 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Environment variables:
* - CLOUDFLARE_API_TOKEN (required)
* - CLOUDFLARE_ACCOUNT_ID (required)
* - CLOUDFLARE_SUBDOMAIN (optional)
*
* Usage:
* Run this script to deploy a simple "Hello World" Worker.
* Access it at: my-hello-world-worker.$subdomain.workers.dev
*/
import { exit } from 'node:process';
import Cloudflare from 'cloudflare';
interface Config {
apiToken: string;
accountId: string;
subdomain: string | undefined;
workerName: string;
}
const WORKER_NAME = 'my-hello-world-worker';
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;
function loadConfig(): Config {
const apiToken = process.env['CLOUDFLARE_API_TOKEN'];
if (!apiToken) {
throw new Error('Missing required environment variable: CLOUDFLARE_API_TOKEN');
}
const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
if (!accountId) {
throw new Error('Missing required environment variable: CLOUDFLARE_ACCOUNT_ID');
}
const subdomain = process.env['CLOUDFLARE_SUBDOMAIN'];
return {
apiToken,
accountId,
subdomain: subdomain || undefined,
workerName: WORKER_NAME,
};
}
const config = loadConfig();
const client = new Cloudflare({
apiToken: config.apiToken,
});
async function main(): Promise<void> {
try {
console.log('🚀 Starting Worker creation and deployment...');
const scriptContent = `
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
},
}`.trim();
let worker;
try {
worker = await client.workers.beta.workers.get(config.workerName, {
account_id: config.accountId,
});
console.log(`♻️ Worker ${config.workerName} already exists. Using it.`);
} catch (error) {
if (!(error instanceof Cloudflare.NotFoundError)) { throw error; }
console.log(`✏️ Creating Worker ${config.workerName}...`);
worker = await client.workers.beta.workers.create({
account_id: config.accountId,
name: config.workerName,
subdomain: {
enabled: config.subdomain !== undefined,
},
observability: {
enabled: true,
},
});
}
console.log(`⚙️ Worker id: ${worker.id}`);
console.log('✏️ Creating Worker version...');
// Create the first version of the Worker
const version = await client.workers.beta.workers.versions.create(worker.id, {
account_id: config.accountId,
main_module: SCRIPT_FILENAME,
compatibility_date: new Date().toISOString().split('T')[0]!,
bindings: [
{
type: 'plain_text',
name: 'MESSAGE',
text: 'Hello World!',
},
],
modules: [
{
name: SCRIPT_FILENAME,
content_type: 'application/javascript+module',
content_base64: Buffer.from(scriptContent).toString('base64'),
},
],
});
console.log(`⚙️ Version id: ${version.id}`);
console.log('🚚 Creating Worker deployment...');
// Create a deployment and point all traffic to the version we created
await client.workers.scripts.deployments.create(config.workerName, {
account_id: config.accountId,
strategy: 'percentage',
versions: [
{
percentage: 100,
version_id: version.id,
},
],
});
console.log('✅ Deployment successful!');
if (config.subdomain) {
console.log(`
🌍 Your Worker is live!
📍 URL: https://${config.workerName}.${config.subdomain}.workers.dev/
`);
} else {
console.log(`
⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
}
} catch (error) {
console.error('❌ Deployment failed:', error);
exit(1);
}
}
main();Cloudflare REST API
Откройте терминал или создайте shell-скрипт, чтобы загрузить Worker и управлять версиями и деплоями с помощью curl. Скрипты Workers представляют собой JavaScript ES Modules ↗, но мы также поддерживаем Python Workers (открытая бета) и Workers на Rust.
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-worker"
worker_script_base64=$(echo '
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};
' | base64)
# Note the below will fail if the worker already exists!
# Here's how to delete the Worker
#
# worker_id="replace-me"
# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \
# -X DELETE \
# -H "Authorization: Bearer $api_token"
# Create the Worker
worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"name": "'$worker_name'"
}' \
| jq -r '.result.id')
echo "\nWorker ID: $worker_id\n"
# Upload the Worker's first version
version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"compatibility_date": "2025-08-06",
"main_module": "'$worker_name'.mjs",
"modules": [
{
"name": "'$worker_name'.mjs",
"content_type": "application/javascript+module",
"content_base64": "'$worker_script_base64'"
}
],
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
]
}' \
| jq -r '.result.id')
echo "\nVersion ID: $version_id\n"
# Create a deployment for the Worker
deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"strategy": "percentage",
"versions": [
{
"percentage": 100,
"version_id": "'$version_id'"
}
]
}' \
| jq -r '.result.id')
echo "\nDeployment ID: $deployment_id\n"Python Workers имеют собственный специальный text/x-python тип содержимого и python_workers флаг совместимости.
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-worker"
worker_script_base64=$(echo '
from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response(self.env.MESSAGE)
' | base64)
# Note the below will fail if the worker already exists!
# Here's how to delete the Worker
#
# worker_id="replace-me"
# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \
# -X DELETE \
# -H "Authorization: Bearer $api_token"
# Create the Worker
worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"name": "'$worker_name'"
}' \
| jq -r '.result.id')
echo "\nWorker ID: $worker_id\n"
# Upload the Worker's first version
version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"compatibility_date": "2025-08-06",
"compatibility_flags": [
"python_workers"
],
"main_module": "'$worker_name'.py",
"modules": [
{
"name": "'$worker_name'.py",
"content_type": "text/x-python",
"content_base64": "'$worker_script_base64'"
}
],
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
]
}' \
| jq -r '.result.id')
echo "\nVersion ID: $version_id\n"
# Create a deployment for the Worker
deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"strategy": "percentage",
"versions": [
{
"percentage": 100,
"version_id": "'$version_id'"
}
]
}' \
| jq -r '.result.id')
echo "\nDeployment ID: $deployment_id\n"API для загрузки multipart/form-data
Этот API использует multipart/form-data ↗ для загрузки Worker, при этом неявно создается версия и развертывание. Указанный выше API рекомендуется для прямого управления версиями и развертываниями.
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-script"
script_content='export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};'
# Upload the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name" \
-X PUT \
-H "Authorization: Bearer $api_token" \
-F "metadata={
'main_module': '"$worker_name".mjs',
'bindings': [
{
'type': 'plain_text',
'name': 'MESSAGE',
'text': 'Hello World!'
}
],
'compatibility_date': '$today'
};type=application/json" \
-F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF
$script_content
EOFДля Workers for Platforms, вы можете загрузить Пользовательский Worker к пространство имён диспетчеризации. Обратите внимание на Конечная точка API находится на /workers/dispatch/namespaces/$DISPATCH_NAMESPACE/scripts/$SCRIPT_NAME.
account_id="replace_me"
api_token="replace_me"
dispatch_namespace="replace_me"
worker_name="my-hello-world-script"
script_content='export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};'
# Create a dispatch namespace
curl https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $api_token" \
-d '{
"name": "'$dispatch_namespace'"
}'
# Upload the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces/$dispatch_namespace/scripts/$worker_name" \
-X PUT \
-H "Authorization: Bearer $api_token" \
-F "metadata={
'main_module': '"$worker_name".mjs',
'bindings': [
{
'type': 'plain_text',
'name': 'MESSAGE',
'text': 'Hello World!'
}
],
'compatibility_date': '$today'
};type=application/json" \
-F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF
$script_content
EOFPython Workers
Python Workers (открытая бета) имеют собственный специальный text/x-python тип содержимого и python_workers флаг совместимости для загрузки через multipart/form-data API.
curl https://api.cloudflare.com/client/v4/accounts/<account_id>/workers/scripts/my-hello-world-script \
-X PUT \
-H 'Authorization: Bearer <api_token>' \
-F 'metadata={
"main_module": "my-hello-world-script.py",
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
],
"compatibility_date": "$today",
"compatibility_flags": [
"python_workers"
]
};type=application/json' \
-F 'my-hello-world-script.py=@-;filename=my-hello-world-script.py;type=text/x-python' <<EOF
from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response(self.env.MESSAGE)
EOFОсобенности работы с Durable Objects
Durable Object миграции применяются вместе с деплоями. Это значит, что нельзя привязаться к Durable Object в Version, если деплой не существует, то есть миграции ещё не применены. Например, при первом применении плана в Terraform выполнение завершится ошибкой:
resource "cloudflare_worker" "my_worker" {
account_id = var.account_id
name = "my-worker"
}
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
bindings = [
{
type = "durable_object_namespace"
name = "my_durable_object"
class_name = "MyDurableObjectClass"
}
]
migrations = {
new_sqlite_classes = [
"MyDurableObjectClass"
]
}
# ...version props omitted for brevity
}
resource "cloudflare_workers_deployment" "my_worker_deployment" {
# ...deployment props omitted for brevity
}Чтобы это сработало, сначала закомментируйте durable_object блок привязки, примените план, раскомментируйте его, закомментируйте migrations блок, затем примените план снова. На этот раз план выполнится успешно. Это также применимо к API или SDK. Это пример случая, когда есть смысл просто управлять cloudflare_worker и/или cloudflare_workers_deployment ресурсы при использовании Wrangler для сборки и управления версиями.
Особенности работы с версиями Worker
Неизменяемость ресурса
Версии Worker неизменяемы на уровне API: их нельзя обновить после создания, можно только пересоздать с нужными изменениями. Это значит, что значимые изменения в cloudflare_worker_version ресурс Terraform всегда будет вызывать замену. Когда cloudflare_worker_version ресурс заменяется, создаётся новая версия с нужными изменениями, но предыдущая версия не удаляется. Это гарантирует, что при управлении через Terraform у Worker сохраняется полная история версий. Иными словами, версии одновременно неизменяемы и допускают только добавление новых. Когда родительский cloudflare_worker ресурс удаляется, все существующие версии, связанные с этим Worker, также удаляются.
Содержимое модуля
Модули версии Worker поддерживают два взаимоисключающих способа предоставления содержимого:
content_file- Указывает на локальный файлcontent_base64- Встроенное содержимое в кодировке base64
В обоих случаях изменения исходного содержимого отслеживаются с помощью вычисляемого content_sha256 атрибут. Указание содержимого с помощью content_file атрибут предпочтителен почти во всех случаях, поскольку он позволяет не хранить само содержимое в состоянии. Содержимое модуля может быть довольно большим (до нескольких десятков мегабайт), и его хранение в состоянии раздувает файл состояния и негативно влияет на производительность операций Terraform. Основной сценарий использования content_base64 атрибут импортирует cloudflare_worker_version ресурс Terraform из API, о котором пойдёт речь ниже.
Поведение импорта
При импорте Terraform всегда заполняет content_base64 атрибут в состоянии, независимо от атрибута, используемого в вашей конфигурации.
terraform import cloudflare_worker_version.my_worker_version <account_id>/<worker_id>/<version_id>Если ваша конфигурация использует content_file, после импорта возникнет несоответствие (состояние использует content_base64, конфигурация использует content_file). Это ожидаемое поведение.
Если предположить, что содержимое локального файла, на который ссылается content_file соответствует импортированному содержимому и их content_sha256 значения совпадают, это приведет к обновлению на месте cloudflare_worker_version ресурс Terraform. Это должно быть обновление на месте, а не замена, потому что базовое содержимое не меняется (значение content_sha256 атрибут одинаков в обоих случаях), и ресурс не нужно обновлять на уровне API. Единственное, что требует обновления, это состояние Terraform, которое переключится с использования content_base64 к content_file после обновления.
Если Terraform вместо этого хочет заменить ресурс, ссылаясь на различие в вычисляемом content_sha256 значения, то содержимое локального файла, на который ссылается content_file не соответствует импортированному содержимому, и ресурс невозможно корректно импортировать без обновления локального файла в соответствии с ожидаемым значением API.
Примеры
Использование content_file:
resource "cloudflare_worker_version" "content_file_example" {
account_id = var.account_id
worker_id = cloudflare_worker.example.id
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_file = "build/worker.js"
}]
}Использование content_base64:
resource "cloudflare_worker_version" "content_base64_example" {
account_id = var.account_id
worker_id = cloudflare_worker.example.id
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_base64 = base64encode("export default { async fetch() { return new Response('Hello world!') } }")
}]
}