lesson ── terraform-advanced ── ~18 мин ── 7 шагов
CDKTF, пишешь инфру на TypeScript, на выходе HCL и terraform.tfstate.
В образе уже стоит cdktf-cli рядом с node и npm. На уроке нагенеришь
stack, синтезируешь, посмотришь что получилось в HCL, deploy'нешь.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/cdktf-demo
which cdktf
cdktf --version
node --version
cdktf-cli забейкан в образ глобально (/usr/local/bin/cdktf),
рядом с node 20 LTS и npm 10. Без npm install -g, экономит
минуту установки и убирает зависимость от npm registry в рантайме.
✓ cdktf-cli на месте, версия отображается.
cdktf init --template=typescript --local --project-name=cdktf-demo --project-description="demo" 2>&1 | tail -10
ls
cat cdktf.json
--local, state локально (не в HCP Terraform). В проекте
появились main.ts, cdktf.json, package.json, node_modules/.
cdktf.json, главный config: какой язык, какие провайдеры
подтягивать.
✓ TypeScript-проект готов.
# отредактируй cdktf.json, добавь aws-provider
python3 - <<'EOF'
import json
with open('cdktf.json') as f:c = json.load(f)
c['terraformProviders'] = ['aws@~> 5.60']
with open('cdktf.json', 'w') as f:json.dump(c, f, indent=2)
EOF
cat cdktf.json
cdktf get 2>&1 | tail -5
ls .gen/providers/aws/
cdktf get сгенерировал TypeScript-типы для AWS-provider. Они
лежат в .gen/. Без них TS не знает про S3Bucket, IamRole и
т.д.
✓ AWS-провайдер type-bindings готовы.
cat > main.ts <<'EOF'
import { App, TerraformStack, TerraformOutput } from "cdktf";import { Construct } from "constructs";import { AwsProvider } from "./.gen/providers/aws/provider";import { S3Bucket } from "./.gen/providers/aws/s3-bucket";class CdktfDemoStack extends TerraformStack { constructor(scope: Construct, id: string) {super(scope, id);
new AwsProvider(this, "aws", {region: "us-east-1",
accessKey: "test",
secretKey: "test",
s3UsePathStyle: true,
skipCredentialsValidation: true,
skipMetadataApiCheck: "true",
skipRequestingAccountId: true,
endpoints: [{s3: "http://localstack:4566",
iam: "http://localstack:4566",
sts: "http://localstack:4566",
}],
});
const envs = ["dev", "stage", "prod"];
const buckets = envs.map((env) =>
new S3Bucket(this, `bucket-${env}`, { bucket: `linuxlab-cdktf-${env}`, tags: { Environment: env, ManagedBy: "cdktf" },})
);
new TerraformOutput(this, "bucket-ids", {value: buckets.map((b) => b.id),
});
}
}
const app = new App();
new CdktfDemoStack(app, "demo");
app.synth();
EOF
Три бакета через Array.map, это и есть преимущество CDKTF над
нативным HCL. На HCL, for_each = toset(...), на TS, обычный
цикл с типами.
✓ Stack на TypeScript готов. Теперь синтезируем.
cdktf synth 2>&1 | tail -5
cat cdktf.out/stacks/demo/cdk.tf.json | jq '.resource.aws_s3_bucket | keys'
Видишь три ключа, bucket-dev, bucket-stage, bucket-prod.
Это обычный JSON-формат HCL, Terraform его понимает.
✓ HCL сгенерирован. 3 бакета из одного array.map.
cdktf deploy demo --auto-approve 2>&1 | tail -10
aws --endpoint-url=http://localstack:4566 s3 ls
Видишь три бакета созданы. Под капотом cdktf deploy = synth +
terraform init + terraform apply в cdktf.out/stacks/demo/.
State, обычный terraform.tfstate в той же папке. Это значит:
можно бросить CDKTF, удалить main.ts, написать HCL на ходу
state совместим, ничего не теряешь.
✓ Три бакета развёрнуты через TypeScript.
OpenTofu держит CLI и state совместимыми с Terraform по командам
этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе
сделай backup state и прогон на feature-branch - расхождения
концентрируются в новых фичах (variables в backend,
state-encryption, OCI registry-backed модули). См.
tf-opentofu-parity для полной матрицы.
Когда нативный HCL коротче, он выбор:
# HCL, 5 строк
resource "aws_s3_bucket" "envs" {for_each = toset(["dev", "stage", "prod"])
bucket = "linuxlab-${each.key}"}
Когда генерация сложнее, CDKTF выигрывает:
// 100 tenants из JSON, разная conf per tenant
const tenants = require("./tenants.json");tenants.forEach((t: any) => { if (t.tier === "premium") { new PremiumTenantStack(app, `tenant-${t.id}`, t); } else { new BasicTenantStack(app, `tenant-${t.id}`, t);}
});
В HCL такое бы потребовало 3-4 модуля и hairy count = condition ? 1 : 0 логики. На TS, линейно.
Запомни smell:
forEach, if, классы) → CDKTF.Cleanup:
cdktf destroy demo --auto-approve 2>&1 | tail -3
✓ CDKTF использован осознанно. Дальше, свой provider на Go.
Аналог Terraform-модуля в CDKTF, Construct:
import { Construct } from "constructs";import { S3Bucket } from "./.gen/providers/aws/s3-bucket";import { S3BucketVersioningA } from "./.gen/providers/aws/s3-bucket-versioning";interface AuditedBucketProps {name: string;
tags?: { [key: string]: string };}
export class AuditedBucket extends Construct {public readonly bucket: S3Bucket;
constructor(scope: Construct, id: string, props: AuditedBucketProps) {super(scope, id);
this.bucket = new S3Bucket(this, "bucket", {bucket: props.name,
tags: props.tags,
});
new S3BucketVersioningA(this, "versioning", {bucket: this.bucket.id,
versioningConfiguration: { status: "Enabled" },});
}
}
// использование
new AuditedBucket(this, "logs", { name: "linuxlab-logs" });Преимущества над Terraform-модулем: type-safe props,
наследование (extends), композиция через TypeScript.
cdktf уже в образе. cdktf init --template=typescript, скелет.
main.ts, App + Stack + Constructs. cdktf get, type bindings для
провайдеров. cdktf synth, генерирует cdktf.out/stacks/<name>/cdk.tf.json.
cdktf deploy, apply.
команды
cdktf --versioncdktf-cli предустановлен в sandbox-образе.cdktf init --template=typescript --localскелет проекта.cdktf getподтянуть type-bindings для провайдеров.cdktf synthсгенерировать HCL.cdktf deploy <stack>synth + apply одной командой.концепции