Open8

cdktfドキュメント翻訳

utamoriutamori

https://www.terraform.io/docs/cdktf/test/unit-tests.html

CDKアプリケーションのテスト

アプリケーションをテストすることで、フィードバックサイクルが早くなり、望ましくない変更から守ることができます。

Unit Tests

ユニットテストは、現在、typescript の jest のみサポートしています。
CDKでは、cdktf initを実行する際に、Jeffの実行に必要な全てのファイルを生成しますので、すぐにテストを書き始めることができます。既存のプロジェクトに jest を追加したい場合は、彼らのガイドに従ってください。それができたら、以下の行を setup.js に追加する必要があります (これは jest の設定の中の setupFilesAfterEnv キーで設定でき、入力としてファイルパスの配列を受け取ります)。

const cdktf = require("cdktf");
cdktf.Testing.setupJest();

アサーションを書く

import { Testing } from "cdktf";
import { Image, Container } from "../.gen/providers/docker";
import MyApplicationsAbstraction from "../app"; // cdktf.Resourceを拡張したクラスである可能性があります。
import "cdktf/lib/testing/adapters/jest"; //  これは、新しいマッチャーのための Typescript タイプを取得するために必要です。
describe("アサーションを用いた単体テスト", () => {
  it("コンテナが含まれていること", () => {
    expect(
      Testing.synthScope((scope) => {
        new MyApplicationsAbstraction(scope, "my-app", {});
      })
    ).toHaveResource(Container);
  });
  it("ubuntuのイメージが含まれていること", () => {
    expect(
      Testing.synthScope((scope) => {
        new MyApplicationsAbstraction(scope, "my-app", {});
      })
    ).toHaveResourceWithProperties(Image, { name: "ubuntu:latest" });
  });
});

上の例では、Testing.synthScope を使用して、アプリケーションの一部をテストしています。これは、アプリケーションのサブセットをテストするために使用できるスコープを作成し、合成されたHCL-JSONを表すJSON文字列を返します。そして、カスタムマッチャーを使用して、コードが意図したとおりに動作することを確認できます。

  • toHaveResource: 特定のリソースが存在するかチェックする
  • toHaveResourceWithProperties: 特定のリソースが、渡されたすべてのプロパティを持って存在するかをチェックする
  • toHaveDataSource: 特定のデータソースが存在するかチェックする
  • toHaveDataSourceWithProperties: 特定のデータソースが、渡されたすべてのプロパティを持って存在するかをチェックする

私たちはjestを使用しているので、次のような非対称マッチャーも使用できます。 expect.anything(), expect.arrayContaining([...]), expect.objectContaining({...}):

expect(synthesized).toHaveResourceWithProperties(aws.S3Bucket, {
  bucketPrefix: `sls-example-frontend-test`,
  website: [
    expect.objectContaining({
      indexDocument: "index.html",
      errorDocument: "index.html",
    }),
  ],
});

スナップショットテスト

スナップショットテストは、インフラが予期せず変更されないことを確認したい場合に非常に便利なツールです。スナップショットテストについての詳細は Jest docsを読んでください

import { Testing } from "cdktf";
import { Image, Container } from "../.gen/providers/docker";
import MyApplicationsAbstraction from "../app"; // Could be a class extending from cdktf.Resource

describe("Unit testing using snapshots", () => {
  it("Tests a custom abstraction", () => {
    expect(
      Testing.synthScope((stack) => {
        const app = new MyApplicationsAbstraction(scope, "my-app", {});
        app.addEndpoint("127.0.0.1"); // This could be a method your class exposes
      })
    ).toMatchInlineSnapshot(); // There is also .toMatchSnapshot() to write the snapshot to a file
  });
});

Terraformとの統合

リモートバックエンドの設定やリソースの属性をオーバーライドする際にエスケープハッチを使用することがあります。
CDK for Terraformアプリケーションでエスケープハッチを使用している場合、無効なTerraform設定を生成する可能性があります。

これをテストするには、cdktf planまたはcdktf deployを実行する前に、アプリケーションの全部または一部でterraform validateまたはterraform planが正常に実行できることを確認してください。

toBeValidTerraform(), toPlanSuccessfullyでテスト可能です

import { Testing } from "cdktf";

describe("Checking validity", () => {
  it("check if the produced terraform configuration is valid", () => {
    const app = Testing.app();
    const stack = new TerraformStack(app, "test");

    const myAbstraction = new MyApplicationsAbstraction(stack, "my-app", {});
    myAbstraction.addEndpoint("127.0.0.1"); // This could be a method your class exposes

    // We need to do a full synth to validate the terraform configuration
    expect(Testing.fullSynth(stack)).toBeValidTerraform();
  });

  it("check if this can be planned", () => {
    const app = Testing.app();
    const stack = new TerraformStack(app, "test");

    const myAbstraction = new MyApplicationsAbstraction(stack, "my-app", {});
    myAbstraction.addEndpoint("127.0.0.1"); // This could be a method your class exposes

    // We need to do a full synth to plan the terraform configuration
    expect(Testing.fullSynth(stack)).toPlanSuccessfully();
  });
});

結合テスト

現在、結合テスト用のヘルパーはあまりありませんが、いくつかの例を参考にすることができます。

utamoriutamori

Terraform Outputs

Terraform outputはTerraform applyが終わった後にユーザーに表示される値を定義します

TypeScriptでは、AWSインスタンスのパブリックIPのTerraform outputはTerraformOutputで表されます

const instance = new Instance(this, "hello", {
  ami: "ami-abcde123",
  instanceType: "t2.micro",
});
new TerraformOutput(this, "public_ip", {
  value: instance.publicIp,
});

TerraformOutputは以下のように合成されます

"output": {
    "examplesimplepublicipE5F943EE": {
      "value": "${aws_instance.examplesimpleHelloF6D4983C.public_ip}"
    }
}
utamoriutamori

Terraform CloudとCDK for Terraformの接続

このドキュメントでは、Terraform CloudとCDK for Terraformのコードベースを接続し、インフラを継続的にデプロイする2つの方法について説明します

Terraform Cloud VCSインテグレーションの利用

Terraform CloudはVCSプロバイダへの接続をサポートしています。VCSインテグレーションを使用するには、生成されたTerraform config(cdktf.outディレクトリ)をコードと一緒にコミットし、Terraform Cloudがインフラストラクチャのデプロイに使用できるようにします。Terraform Cloud WorkspaceのGeneral SettingsページでTerraform Working Directoryの設定をデプロイしたいスタックの出力ディレクトリに設定します。例えば、スタックの名前がdevであれば、cdktf.out/stacks/devとします。

外部CIサービスの利用

生成されたアーティファクトをリポジトリに残さないようにするには、代わりにCI(継続的インテグレーション)サービスを使ってビルドとデプロイを行います。CDK for Terraform CLIは、ローカルまたはリモートの実行モードを使用したTerraform Cloudへのデプロイをサポートしています。Terraform Cloudでの実行の動作については、Terraform Runs and Remote Operationsをご参照ください。
CIステップでは、cdktf-cliコマンドを使用してコードを合成し、インフラをデプロイします。

cdktf deploy --auto-approve
utamoriutamori

https://www.terraform.io/docs/cdktf/concepts/variables-and-outputs.html

Variables と Outputs

Terraformは、HashiCorp Configuration Language (HCL)構文またはJSONで書かれた設定を理解することができます。どちらもプログラミング言語ではないため、Terraformではユーザーが名前付きの値を要求・公開できる方法を開発しました。これは

  • input Variables(入力変数)。関数の引数のようなものです。
  • Local Values(ローカル値)。関数の一時的なローカル変数のようなもの。
  • Output Values(出力値)。関数の戻り値のようなものです。

CDK for Terraform (CDKTF) アプリケーションでは、お好みのプログラミング言語で利用可能な規約でデータを渡す代わりに、これらの要素を使用する必要がある場合があります。

Input Variables

スタックモジュールをカスタマイズするための入力パラメーターとしてTerraform変数を定義することができます。例えば、プロビジョニングするAWS EC2インスタンスの数や種類をハードコーディングするのではなく、ユーザーがニーズに応じてこれらのパラメータを変更できるように変数を定義することができます。

いつ Input Variables を使うべきか

変数は、CDKTFアプリケーションをTerraform用のJSON設定ファイルに合成することを計画している場合に便利です。例えば、Terraform Cloud内に設定を保存してTerraformを実行することを計画している場合などです。

CDKTFを使用してインフラを管理する予定の場合、通常Terraformの変数を介して渡すデータを消費するために、言語のAPIを使用することをお勧めします。通常のプログラムと同じように、ディスクから(同期)または環境変数から読み取ることができます。

Input Variablesを定義する方法

HCLの設定ファイルと全く同じ方法で値を指定する必要があります。詳細はTerraform variablesのドキュメントを参照してください。CDKTF CLIは現在、環境変数による設定もサポートしています。

以下のTypeScriptの例では、TerraformVariableを使用してリソースへの入力を行います。

const imageId = new TerraformVariable(this, "imageId", {
  type: "string",
  default: "ami-abcde123",
  description: "What AMI to use to create an instance",
});
new EC2.Instance(this, "hello", {
  ami: imageId.value,
  instanceType: "t2.micro",
});

Local Values

Terraformのローカルは、繰り返し使用できるように式に名前を割り当てます。これは、プログラミング言語のローカル変数に似ています。

いつLocal Valuesを使うのか

ローカル値を使用するのは、Terraformが設定を適用したときにしか利用できないデータをTerraformの関数で変換する必要がある場合です。例えば、クラウド事業者が作成時に割り当てるインスタンスIDなどです。

コードを合成する前に値が得られる場合は、代わりにネイティブプログラミング言語の機能を使って値を修正することをお勧めします。

ローカル値の定義

以下のTypeScriptの例では、TerraformLocalを使用してローカル値を作成しています。

const commonTags = new TerraformLocal(this, "common_tags", {
  Service: "service_name",
  Owner: "owner",
});

new EC2.Instance(this, "example", {
  tags: commonTags.expression,
});

cdktf synthを実行すると、上記のTerraformLocalが以下のJSONに合成されます。

"locals": {
    "common_tags": {
      "Service": "service_name",
      "Owner": "owner"
    }
}
...
"resource": {
  "aws_instance": {
    "example": {
      "tags": "${local.common_tags}"
    }
  }
}

Output Values

Terraform Outputsを定義することで、リソースに関する構造化データを出力することができます。Terraformはインフラの変更を適用した後、ユーザーの出力値を印刷し、この情報を他のTerraformワークスペースのデータソースとして使用することができます。

いつ Output Valuesを使うべきか

出力値を使用して、Terraformリソースおよびデータソースからのデータをさらに消費できるようにしたり、スタック間でデータを共有したりします。出力値は、Terraformが設定を適用した後でしかわからないデータにアクセスする必要がある場合に特に有効です。例えば、新しくプロビジョニングされたサーバーのURLを取得したい場合などです。

コードを合成する前に値が得られる場合は、好みのプログラミング言語の機能を使って、このデータを直接入力として供給することをお勧めします。

以下のTypeScriptの例では、TerraformOutputを使って出力を作成しています。

import { Construct } from "constructs";
import { App, TerraformStack, TerraformOutput } from "cdktf";

export interface MyStackProps {
  readonly myDomain: string;
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string, props: MyStackProps) {
    super(scope, name);

    const { myDomain } = props;

    new TerraformOutput(this, "my-domain", {
      value: myDomain,
    });
  }
}

const app = new App();
new MyStack(app, "cdktf-producer", {
  myDomain: "example.com",
});
app.synth();

Output Valuesの定義方法

出力値にアクセスするには、Pythonでは_outputサフィックスを、その他の言語ではOutputサフィックスを使用します。

出力は、基礎となるTerraformリソースを表すHCL式を返すため、戻り値の型は常にstringでなければなりません。TerraformOutputがstring以外の型の場合は、アプリケーションをコンパイルするために型キャストを追加する必要があります(例:mod.numberOutput as number)。モジュールがリストを返す場合、アイテムにアクセスしたり、ループしたりするには、エスケープハッチを使用する必要があります。エスケープハッチの使用方法の詳細については、「リソース」ページを参照してください。

以下のTypescriptの例では、TerraformOutputを使用して、Randomプロバイダリソースの出力を作成しています。

import * as random from "@cdktf/provider-random";

import { Construct } from "constructs";
import { App, TerraformStack, TerraformOutput } from "cdktf";

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new random.RandomProvider(this, "random", {});
    const pet = new random.Pet(this, "pet", {});

    new TerraformOutput(this, "random-pet", {
      value: pet.id,
    });
  }
}

const app = new App();
new MyStack(app, "cdktf-demo");
app.synth();

cdktf synthを実行すると、CDKTFは上記のコードを以下のJSON構成に合成します。

"output": {
  "random-pet": {
    "value": "${random_pet.pet.id}"
  }
}

cdktf deployを実行すると、CDKTFは次のような出力を表示します。

Deploying Stack: cdktf-demo
Resources
 ✔ RANDOM_PET           pet                 random_pet.pet

Summary: 1 created, 0 updated, 0 destroyed.

Output: random-pet = choice-haddock

リモートステートによるOutputの定義と参照

以下のTypeScriptの例では、アウトプットを使ってスタック間でデータを共有していますが、それぞれのスタックにはTerraformのstateファイルをリモートで保存するためのリモートバックエンドがあります。

import * as random from "@cdktf/provider-random";

import { Construct } from "constructs";
import {
  App,
  TerraformStack,
  TerraformOutput,
  RemoteBackend,
  DataTerraformRemoteState,
} from "cdktf";

class Producer extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new RemoteBackend(this, {
      organization: "hashicorp",
      workspaces: {
        name: "producer",
      },
    });

    new random.RandomProvider(this, "random", {});
    const pet = new random.Pet(this, "pet", {});

    new TerraformOutput(this, "random-pet", {
      value: pet.id,
    });
  }
}

class Consumer extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new RemoteBackend(this, {
      organization: "hashicorp",
      workspaces: {
        name: "consumer",
      },
    });

    const remoteState = new DataTerraformRemoteState(this, "remote-pet", {
      organization: "hashicorp",
      workspaces: {
        name: "producer",
      },
    });

    new TerraformOutput(this, "random-remote-pet", {
      value: remoteState.getString("id"),
    });
  }
}

const app = new App();
new Producer(app, "cdktf-producer");
new Consumer(app, "cdktf-consumer");
app.synth();
utamoriutamori

https://www.terraform.io/docs/cdktf/concepts/constructs.html

Constructs

Constructは、CDK for Terraform (CDKTF)プロジェクトの一部を表す一般的な用語です。CDKTFアプリケーションのすべての要素は、Contruct基底クラスの子孫です。アプリケーション全体、各stack、および各リソースはすべてConstructです。

また、プログラミング言語で書かれたインフラストラクチャ構成を表すカスタムConstructクラスをインポートすることもできます。カスタムConstructは、コンフィグレーションのベストプラクティスを実施し、コンフィグレーションを何度も書き直すことなく再利用し、コンフィグレーションの詳細を抽象化することができるので便利です。例えば、Kubernetesのデプロイメントを構成するConstructをインポートするとします。アプリケーションで新しいインスタンスを作成する際に、実装の詳細をすべて知ることなく、公開されたプロパティを使ってdeploymentをカスタマイズすることができます。

このページでは、CDKTFプロジェクト内でConstructをどのように考え、どのように使用するかを説明します。また、カスタムConstructを公開する際の推奨事項についても説明します

Constructの種類

Constructクラスは、さまざまなレベルの粒度でシステムの状態を定義します。例えば、1つのElastic Cloud Computeリソースを定義・設定するカスタムConstructsや、異なるプロバイダーの複数のリソースを含む展開全体を定義・設定するカスタムConstructsを記述できます。

Cloud Development Kitコミュニティでは、抽象度の高さを示す3つの主要なConstructsタイプを特定しています。

  • L1 Constructsは、単一のリソースや非常に小さな構成単位を定義します。例えば、CDKTFが各Terraformプロバイダ用に生成するコードバインディングはL1コンストラクトとみなされます。他の例としては、Azure仮想マシンの構成を定義するカスタムL1構成を作成することが挙げられます。
  • L2 Constructsは、リソースを定義し、追加のヘルパーメソッド、プロパティ、および機能を備えたインテントドリブンAPIを含みます。たとえば、S3バケットにファイルを追加するメソッドや、特定のユーザーグループにリソースへのアクセスを許可するメソッドを含むカスタムL2構成を作成することができます。
  • L3 Constructsは、一般的なデザインパターンや大規模な機能を定義します。例えば、静的なWebサイトのフロントエンドをデプロイしてホストするために必要なすべてのリソースを設定するカスタムL3コンストラクトを作成することができます。

Modules vs Constructs

Terraformのmodulesとconstructは、どちらも設定を再利用し、公開されたプロパティを使って設定をカスタマイズする機能です。しかし、Constructは、好みのプログラミング言語の組み込み機能を使用できるため、型安全性を維持しながら、より複雑なオブジェクトを構成することができます。

相互運用性

cdktf convertコマンドを使って、HCLで書かれた既存のプロジェクトをCDKTF互換のプロジェクトに変換することができます。そして、その出力を出発点として、カスタムConstructを作成することができます。

既存のCDKTF Constructから直接Terraformモジュールを作成することはできませんが、CDKTFプロジェクトの合成された出力をTerraform moduleとして使用することはできます。詳細については、HCL相互運用性のページを参照してください。

Constructs vs Stacks

スタックとは、CDKTFが専用のTerraform構成として合成するインフラの集合体を表します。スタックを使用することで、アプリケーション内の複数の環境のstate管理を分離することができます。例えば、開発用のインフラを記述したスタックと、テスト用に若干異なる入力をしたスタックを用意することができます。

Constructsは、リソースのセットを論理的に構成する方法を提供しますが、スタックの一部としてのみ使用することができます。1つのスタックには、インフラストラクチャの完全な構成を作成するためのビルディングブロックとして機能する複数のConstructを含めることができます。例えば、あるConstructを使ってKubernetesのデプロイメントを定義し、別のConstructを使ってAWS DynamoDBのテーブルを定義することができます。

Constructsを使ってみる

ハンズオン Deploy Applications with CDK for Terraform | Terraform - HashiCorp Learnでは、カスタムConstrucの使い方を学びます。下記のサンプルコードも含まれています。

プロジェクトのプログラミング言語で利用可能な、CDKTF互換のConstructをインポートすることができます。その後、Constructの新しいインスタンスを作成し、公開されているプロパティを使用してConstructの構成をカスタマイズすることができます。

以下のTypeScriptの例では、KubernetesWebAppDeploymentというConstructをインスタンス化し、利用可能な引数を使用して、deploymentが2つのレプリカを持つことを指定しています。

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import * as kubernetes from "@cdktf/provider-kubernetes";
import * as path from "path";

import { KubernetesWebAppDeployment } from "./custom-constructs";

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new kubernetes.KubernetesProvider(this, "kind", {
      configPath: path.join(__dirname, "../kubeconfig.yaml"),
    });

    new KubernetesWebAppDeployment(this, "deployment", {
      image: "nginx:latest",
      replicas: 2,
      app: "myapp",
      component: "frontend",
      environment: "dev",
    });
  }
}

const app = new App();
new MyStack(app, "app");
app.synth();

Scope

Constructは、インフラ全体で複数回インスタンス化することができます。例えば、異なる構成で複数のS3バケットを作成したい場合などです。CDKTF は、Construct#id と親Constructから、各インスタンスに固有のnameを推論します。同じ親要素を共有するインスタンスは、同じスコープの一部であるとみなされます。同じスコープ内で複数のConstructをインスタンス化する場合は、名前の衝突を避けるために、各インスタンスに異なるnameを設定する必要があります。
以下の例では、3つの異なるS3バケットを作成し、そのうち2つは同じスコープ内にあります。CDKTFがこの構成を合成する際、これらのリソースのTerraform IDは、Construct名の前にスタック名を付け、Constructインスタンスごとにハッシュを付けたものになります。

import { Construct } from "constructs";

class PublicS3Bucket extends Construct {
  constructor(scope: Construct, name: string) {
    super(scope, name); // リソースから拡張するため、これにより新しいスコープが作成されます

   // このバケットは、以下の `MyStack` で定義されているバケットとは異なるスコープにあります。
   // そのため、固有の名前は必要ありません。
    this.bucket = new S3Bucket(this, "bucket", {
      bucketPrefix: name,
      website: [
        {
          indexDocument: "index.html",
          errorDocument: "5xx.html",
        },
      ],
    });
  }
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    // どちらのバケットも同じスタックの中にあり、同じスコープを共有しています。
 // そのため、名前は一意でなければなりません。
    new PublicS3Bucket(this, "first-bucket");
    new PublicS3Bucket(this, "second-bucket");
  }
}

Aspects

Aspectを使用すると、CDTKFアプリケーションを合成する際に、ビジターパターンを実装したり、Constructを動的に追加または削除したり、既存のConstructの属性を自動的に変更したりすることができます。たとえば、動的な条件に基づいてリソースにタグを付けるためにAspectを使用することができます。詳細については、Aspectsのドキュメントを参照してください。

利用できるConstrucs

Amazon Web Services (AWS) Construct Hub を検索すると、既存の CDKTF 互換のコンストラクトが見つかります。また、CDKTFプロジェクトでAWS Constructを使用できるようにするAWS Adapterの構築も進めています。

Constructsを配布する

Constructライブラリを配布したい場合は、コンストラクトをTypeScriptで記述し、jsiiを使って希望するすべての言語ターゲットのネイティブパッケージを生成することをお勧めします。私たちは、オープンソースプロジェクトが、一般的に利用可能なすべての言語をサポートすることを目指すことを推奨します。

パッケージジェネレータprojenを使って、複数のプログラミング言語でConstructをビルドして公開するために必要なGithub Actionsワークフローを備えたGithubプロジェクトを構築することができます。

utamoriutamori

https://www.terraform.io/docs/cdktf/concepts/stacks.html

Stackとは、CDK for Terraform(CDKTF)が専用のTerraform構成として合成するインフラの集合体を表します。Stackを利用することで、アプリケーション内の複数の環境のState管理を分離することができます。

Deploy Applications with CDK for Terraform | Terraform - HashiCorp Learnをお試しください。

Scope

同じリソースを、インフラ全体で複数回インスタンス化することができます。例えば、異なる構成の複数のS3バケットを作成することができます。同じstack親要素を共有するインスタンスは、同じスコープの一部であるとみなされます。命名の衝突を避けるために、各インスタンスに異なるnameプロパティを設定する必要があります。

詳細および例については、Constructsのドキュメントを参照してください

Single Stack

以下の例では、設定したoutputフォルダーに単一のTerraform構成を生成します。cdktf synthを実行すると、合成されたTerraform構成は、cdktf.out/stacks/a-single-stackというフォルダーに入ります。

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, EC2 } from "./.gen/providers/aws";

class MyStack extends TerraformStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    new AwsProvider(this, "aws", {
      region: "us-east-1",
    });

    new EC2.Instance(this, "Hello", {
      ami: "ami-2757f631",
      instanceType: "t2.micro",
    });
  }
}

const app = new App();
new MyStack(app, "a-single-stack");
app.synth();

Multiple Stacks

Deploy Multiple Lambda Functions with TypeScript | Terraform - HashiCorp Learn

アプリケーションでは、複数のStackを指定することができます。例えば、開発環境、テスト環境、本番環境でそれぞれ別の構成にしたい場合などです。

以下の例では、設定したOutputフォルダに複数のTerraformの設定を合成しています。

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, EC2 } from "./.gen/providers/aws";

interface MyStackConfig {
  environment: string;
  region?: string;
}

class MyStack extends TerraformStack {
  constructor(scope: Construct, id: string, config: MyStackConfig) {
    super(scope, id);

    const { region = "us-east-1" } = config;

    new AwsProvider(this, "aws", {
      region,
    });

    new EC2.Instance(this, "Hello", {
      ami: "ami-2757f631",
      instanceType: "t2.micro",
      tags: {
        environment: config.environment,
      },
    });
  }
}

const app = new App();
new MyStack(app, "multiple-stacks-dev", { environment: "dev" });
new MyStack(app, "multiple-stacks-staging", { environment: "staging" });
new MyStack(app, "multiple-stacks-production-us", {
  environment: "production",
  region: "us-east-1",
});
new MyStack(app, "multiple-stacks-production-eu", {
  environment: "production",
  region: "eu-central-1",
});
app.synth();

cdktf synthを実行すると、以下のような合成されたStackが得られます。

$ cdktf list

Stack name                      Path
multiple-stacks-dev             cdktf.out/stacks/multiple-stacks-dev
multiple-stacks-staging         cdktf.out/stacks/multiple-stacks-staging
multiple-stacks-production-us   cdktf.out/stacks/multiple-stacks-production-us
multiple-stacks-production-eu   cdktf.out/stacks/multiple-stacks-production-eu

現在、Terraformのすべての操作は1つのStackに限定されているため、diffdeploydestroyを実行するにはターゲットスタックのディレクトリを指定する必要があります。ターゲットStackを省略して普通にcdktf deployを実行すると、CDKTFはエラーを出します。複数のStackを一度にデプロイすることに興味がある場合は、このIssueをwatchしてください。

複数のStackを一度に実行するには、それらを同じディレクトリに移動し、cdktf deploy を実行します。例えば、multiple-stacks-devというディレクトリを作成し、cdktf deploy multiple-stacks-devを実行すると、すべてのTerraform操作はcdktf.out/stacks/multiple-stacks-devというフォルダで実行されます。

Cross-Stack References

あるスタックのリソースを別のスタックで参照する場合、ソーススタックでリソースを公開し、ターゲットスタックで参照することで行えます。

import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider } from "./.gen/providers/aws";
import { Vpc } from "./my-aws-vpc-construct";
import { DockerBackend } from "./my-docker-backend-construct";
class VPCStack extends TerraformStack {
  public vpc: Vpc;
  constructor(scope: Construct, id: string, public region = "us-east-1") {
    super(scope, id);
    new AwsProvider(this, "aws", {
      region,
    });
    this.vpc = new Vpc(this, "vpc", {});
  }
}
interface BackendStackConfig {
  region: string;
  vpcId: string;
  dockerImage: string;
}
class BackendStack extends TerraformStack {
  constructor(scope: Construct, id: string, config: BackendStackConfig) {
    super(scope, id);
    const { region, vpcId, dockerImage } = config;
    new AwsProvider(this, "aws", {
      region,
    });
    new DockerBackend(this, "docker-backend", {
      vpcId,
      dockerImage,
    });
  }
}
const app = new App();
const origin = new VPCStack(app, "origin-stack");
new BackendStack(app, "target-stack", {
  region: origin.region,
  vpcId: origin.vpc.id,
  dockerImage: "org/my-image:latest",
});
app.synth();

使い方としては、VpcStackorigin-stack インスタンスから vpcid 値にアクセスし、BackendStacktarget-stack インスタンスでそれを参照しているように見えます。
異なるスタックから値にアクセスすると、オリジンスタックに TerraformOutput という名前で値がエクスポートされます。
次に、ターゲットスタックにある TerraformRemoteState を介して値にアクセスします。
両方とも自動的にそれぞれのスタックに追加され、処理を簡単にすることができます。

現在のところ、この機能に関するセーフガードはありません。
クロススタック参照は、今のところローカルとリモートのバックエンドにのみサポートされています。

utamoriutamori

https://www.terraform.io/docs/cdktf/concepts/assets.html

Assets

CDK for Terraform (CDKTF) v0.4+では、Assets Constructにより、template_file、S3バケットオブジェクト、Lambda関数のアーカイブファイルなど、アセットを必要とするリソースのアセットを管理できるようになりました。TerraformのAssetを使って、既存のファイルやディレクトリをCDKTFアプリケーションに移動し、リソース定義で使用できるようになります。

Assetは特に以下のような場合に便利です。

  • 以前に生成したzipファイルをLambda関数でコピーする。
  • 静的なローカルファイルをS3にデプロイする。

使用例

Deploy Multiple Lambda Functions with TypeScriptを試してみましょう。このチュートリアルでは、TerraformAssetを使ってLambda関数をアーカイブし、そのアーカイブをS3バケットにアップロードし、Lambda関数をデプロイする方法を紹介しています。

以下のTypeScriptの例では、TerraformAssetを使って、指定したディレクトリのコンテンツをS3Bucketにアップロードしています。TerraformAssetは、ディレクトリがS3BucketObjectが参照できるZIPファイルとして正しいOutputフォルダに終わるようにする役割を果たします。

Stack Oupputディレクトリcdktf.outには、TerraformAssetが必要とするすべてのアセットが含まれています。これは、合成された設定をTerraformで直接使用するワークフローでは重要です。例えば、Terraform CloudやTerraform Enterpriseにstack outputフォルダの内容をアップロードするだけで済みます。

import * as path from "path";
import { Construct } from "constructs";
import { App, TerraformStack, TerraformAsset, AssetType } from "cdktf";
import { AwsProvider } from "./.gen/providers/aws/aws-provider";
import { S3BucketObject } from "./.gen/providers/aws/s3-bucket-object";
import { S3Bucket } from "./.gen/providers/aws/s3-bucket";

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, "provider", {
      region: "us-west-2",
    });

    const bucket = new S3Bucket(this, "bucket", {
      bucket: "demo",
    });

    const asset = new TerraformAsset(this, "lambda-asset", {
      path: path.resolve(__dirname, "../lambda"),
      type: AssetType.ARCHIVE, // 空のままだと、ディレクトリとファイルを推定します。
    });

    new S3BucketObject(this, "lambda-archive", {
      bucket: bucket.bucket,
      key: asset.fileName,
      source: asset.path, // returns a posix path
    });
  }
}

const app = new App();
new MyStack(app, "demo");
app.synth();

utamoriutamori

https://www.terraform.io/docs/cdktf/concepts/remote-backends.html

リモートバックエンド

Terraformは管理対象のインフラに関するステートを保存し、実世界のリソースを設定にマッピングしたり、メタデータを記録したり、パフォーマンスを向上させたりします。Terraformはデフォルトでこのステートをローカルファイルに保存しますが、Terraformリモートバックエンドを使用してリモートで状態を保存することもできます。

デフォルトでは、cdktf initはTerraform Cloudのワークスペースと対応するリモートバックエンドを設定し、新しいプロジェクトの状態を保存します。cdktf init --localを実行して、新規プロジェクトでローカルバックエンドを使用してステートを保存するように設定した場合でも、後からリモートバックエンドにステートを移行することができます。

CDK for Terraform (CDKTF) のリモートバックエンドは、Terraform Cloud、Terraformがサポートする他のバックエンド、またはカスタムロケーションに設定することができます。

リモートバックエンドを使用するタイミング

複数の個人やチームがインフラのステートデータにアクセスする必要がある場合は、リモートバックエンドの使用を検討してください。

リモート ステートでは、すべてのメンバーがリモート ストアの最新のステート データにアクセスできるため、チームでの作業が容易になります。また、他の構成と出力値を共有することで、グループでインフラリソースを共有することができます。例えば、コア・インフラストラクチャ・チームは、コア・マシンの構築を処理し、他のチームが自分たちのインフラストラクチャのために使用できるいくつかの情報を公開することができます。

リモートバックエンドの定義

TerraformBackendサブクラスまたはJSON設定ファイルで、リモートバックエンドのJSON設定を定義できます。

以下のTypeScriptの例では、TerraformBackendのサブクラスRemoteBackendを使用しています。

import { Construct } from "constructs";
import { App, RemoteBackend, TerraformStack, TerraformOutput } from "cdktf";

class MyStack extends TerraformStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    new RemoteBackend(this, {
      hostname: "app.terraform.io",
      organization: "company",
      workspaces: {
        name: "my-app-prod",
      },
    });

    new TerraformOutput(this, "dns-server", {
      value: "hello-world",
    });
  }
}

const app = new App();
new Mystack(app, "hello-terraform");

cdktf synth を呼び出すと、CDKTF は合成された CDKTF コードを含む cdktf.out stack サブディレクトリーに remote.tf.json という JSON ファイルを生成します。例えば、CDKTF は、hello-terraform というスタックの出力を cdktf.out/stacks/hello-terraform に作成します。

以下は、スタック出力ディレクトリです。