Amazon Web Services ブログ

CloudFormation テンプレートから AWS Cloud Development Kit への移行

AWS CloudFormation を使用すると、開発者やシステム管理者は、関連する AWS リソースのコレクションを簡単に作成および管理し、それらを整然かつ予測可能な方法でプロビジョニングおよび更新できます。AWS CloudFormation のサンプルテンプレートの使用、または独自のテンプレートの作成によって、アプリケーションの実行に必要な AWS リソース、相互の依存関係、および実行時パラメーターを定義できます。AWS サービスのプロビジョニングの順序や、それらの依存関係を解決するための詳細を把握する必要はありません。CloudFormation はあなたに代わってこれを処理します。AWS リソースをデプロイしたら、制御され予測可能な方法で変更し更新できます。これにより、ソフトウェアと同じ方法で AWS インフラストラクチャにバージョン管理を適用できます。

2019 年 7 月、AWS Cloud Development Kit(CDK)がリリースされました。開発者が AWS インフラストラクチャを TypeScript、JavaScript、Python、C#、Java などの使い慣れたプログラミング言語のコードで定義できるようになったことで、お客様の選択肢が増えました。CDK では、より高いレベルの抽象化を使用してインフラストラクチャを定義できますが、その背後では CloudFormation が使われています。つまり、自動ロールバックやドリフト検出など、CloudFormation のすべてのメリットを享受できます。

多くの開発者は、アプリケーションコードに使用するのと同じ使い慣れたプログラミング言語でインフラストラクチャを定義することを好むため、CDKを使用したいと考えています。しかし一方で、実証済みのインフラストラクチャパターンと重要な構成設定をエンコードした CloudFormation テンプレートの広範なライブラリをすでにお持ちです。これらのテンプレートは、構築と維持に時間と労力を要します。一方で既存のスタックを再現する CDK コードを手動で記述することは、面倒でエラーが発生しやすくなります。

今日まで、既存のテンプレートで定義されたインフラストラクチャを活用する唯一の方法は、コアモジュールの CFnInclude クラスでした。これにより、CDK アプリケーションに CloudFormation テンプレートを組み込み、変更せずに出力することができました。ただし、このソリューションにはいくつかの制限があります。

  • テンプレートに含まれるリソースは、インクルードした後で変更することはできません。
  • テンプレートから取り込んだリソースは、その論理 ID を使用して手動で参照します。これはエラーが発生しやすく、この方法で作成された参照は、CDKが提供する強力な機能である CDK の自動クロススタックリファレンス生成機能を利用できません。

CloudFormation-Include CDK モジュールの発表

既存の CloudFormation スタックを CDK コードに移行するために特別に開発された CloudFormation-include CDK モジュール の開発者プレビューリリースをお知らせします。このモジュールは CloudFormation テンプレートファイルを解析し、テンプレートで見つかったすべての要素(リソース、パラメータ、出力など)を CDK アプリケーションに読み込みます。その後、テンプレートで定義されたオブジェクトをCDKコードで直接変更したり、新しいCDKコンストラクトを作成するときに既存のテンプレートリソースを参照したりすることができます。

前提条件

  • AWS アカウント
  • ローカルにインストールした CDK CLI

移行方法

まず、CDK CLI をローカルにインストールする必要があります。まだお持ちでない場合は、AWS CDK 開発者ガイドの手順を参照してください。インストールが正常に動作することを確認するには、ターミナルで cdk —version コマンドを実行します。次のような出力が表示されます。

$ cdk --version
1.63.0 (build 7a68125)

このブログ記事では、TypeScriptでCDKを使用します。ただし、cloudformation-include モジュールは、すべてのCDKでサポートされている言語で利用でき、他の言語のコードは、TypeScriptコードと同様の構造に従います。参考までに、他の言語用の cloudformation-include モジュールのドキュメントは次のとおりです。

次に、CDK に移行するには CloudFormation スタックが必要です。このブログ記事では、単一の S3 バケットを含む非常に単純な「MigrationStack」用のCloudFormation テンプレートを作成しました。

{
  "Resources": {
    "MyBucket": {
      "Type": "AWS::S3::Bucket"
    }
  }
}

このブログ記事のスタック移行の例に従う場合は、上記の JSON ブロックを使用してスタックを作成するか、AWS アカウントにログインした後に次のリンクをクリックして、MigrationStack を自動的にデプロイします。これにより、CloudFormation の新規スタック作成ウィザードが開き、すべてのデフォルトオプションを受け入れ、CloudFormation スタックが作成されるまでウィザードを順を追って進めることができます。

AWS アカウントに MigrationStack をデプロイする

これで、アカウントに CloudFormation スタックが作成されたので、migration というディレクトリに新しい CDK アプリケーションを作成していきます。これを行うには、ターミナルを開き、次のコマンドを実行します。

$ mkdir migration
$ cd migration
$ cdk init -l typescript

次に行うことは、作成したばかりのプロジェクトにcloudformation-includeモジュールへの依存関係を追加することです。これを行うには、CDK プロジェクトの package.json ファイルの dependencies セクションを変更して、次の行を含めます。

"@aws-cdk/cloudformation-include": "^1.63.0"

使用しているノードのパッケージマネージャに応じて、npm installまたはyarn installのいずれかを実行します。

ここで、先ほどウィザードを使ってデプロイした MigrationStack のテンプレートを取得する必要があります。これを得るには2つの方法があります。次のいずれかです。

  • 上記の CloudFormation JSON ブロックをコピーし、ローカルマシンにファイルとして保存します。
  • AWS コンソールの CloudFormation サービス画面で、MigrationStack の テンプレート タブに移動し、テンプレートの内容をクリップボードにコピーし、ローカルマシン上のファイルに保存します。

どちらの方法を選択しても、ファイル名は migration-template.json という名前で保存してください。このファイルを、プロジェクトに既に存在する cdk.json ファイルの横に配置します。このファイルは、他の CDK ソースコードと同様に、バージョン管理下に置く必要があります。

これは 1 回限りの操作です。移行後は、テンプレートを編集する必要はありません。スタックへの以後の変更はすべて、CDK コードによって行います。

このテンプレートを CDK アプリケーションに含めるには、lib/migration-stack.ts ファイルを開き、// The code that defines your stack goes here というコメントのすぐ下に CFnInclude クラスのインスタンスを作成します。コードは次の例のようになります。

import * as cdk from '@aws-cdk/core';
import * as cfn_inc from '@aws-cdk/cloudformation-include';

export class MigrationStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    const cfnInclude = new cfn_inc.CfnInclude(this, 'Template', { 
      templateFile: 'migration-template.json',
    });
  }
}

bin/migration.ts ファイルに、移行対象スタックの名前(MigrationStack) が正しく記載されていることを確認します。次のようになります。

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { MigrationStack } from '../lib/migration-stack';

const app = new cdk.App();
//'MigrationStack' below needs to be the same name as the CloudFormation name
new MigrationStack(app, 'MigrationStack');

これらのコードを変更したら、MigrationStack をデプロイしたアカウントの AWS 認証情報を持つターミナルで cdk diff コマンドを実行します。次のような出力が表示されます。

アカウントで稼働しているスタックと、CDK コードでモデル化したスタックの唯一の違いは、CDK 内部リソースに使用される CloudFormation Condition オブジェクトに関するものです。ただし、MigrationStack が持つ MyBucket リソースは CDK アプリケーションに含まれているため、diff コマンドにはそれに関連する差分は表示されません。

最後に、cdk deployコマンドを実行し、デプロイメントを終了させ、cdk diffを再度実行すると、次のことが報告されます。

There were no differences.

おめでとうございます! これで CloudFormation テンプレートを CDK コードに移行できました。簡単でしょ??

もちろん、単にスタックを変更せずに移行することだけが、アプリケーションでやりたいことでないでしょう。おそらく、そのスタックにいくつかの変更を行ったり、追加したりしたいと思うでしょう!次のセクションでは、その方法を示します。

テンプレートから取り込んだリソースの変更

cloudformation-include モジュールを使用すると、その論理 ID (Logical ID) で参照することで、テンプレートから取り込んだ任意のリソースを変更できます。例として、MigrationStack テンプレートでは論理 ID MyBucket の S3 バケットが定義されているので、CfnInclude クラスの getResource() メソッドを使用して、そのリソースへの参照を取得できます。

const cfnBucket = cfnInclude.getResource('MyBucket');

getResource() メソッドは CfnResource 型を返します。ただし、取得したリソースタイプに対応する CDK クラスがわかっている場合は、結果をCDK上の正しい型にキャストできます。たとえば、CloudFormationのリソースタイプ AWS::S3::Bucket は CDKでは @aws-cdk/aws-s3 モジュールの CfnBucket クラスに対応しています。この場合、getResource() によって返されたオブジェクトは次のようにキャストできます。

import * as s3 from '@aws-cdk/aws-s3';

// ...

    const cfnBucket = cfnInclude.getResource('MyBucket') as s3.CfnBucket;
    // cfnBucket is now of type s3.CfnBucket

リソースへの参照を作成したら、そのプロパティを変更できます。たとえば、バケットへのパブリックアクセスをブロックするように設定を変更できます。

cfnBucket.publicAccessBlockConfiguration = {
  blockPublicAcls: true,
};

では cdk diff を実行しましょう。次のような出力が得られます。

$ cdk diff

Stack MigrationStack
Resources
[~] AWS::S3::Bucket Template/MyBucket MyBucket 
└─ [+] PublicAccessBlockConfiguration
└─ {"BlockPublicAcls":true}

この後 cdk deploy コマンドを実行すると、MigrationStack スタックの MyBucket バケットのプロパティ PublicAccessBlockConfiguration が変更されます。

このように、インポートしたテンプレートで定義されているリソースのプロパティを、CDK コードから直接変更できます。このとき自動補完やプログラミング言語の型チェックなどの機能を使用することもできます。テンプレートを変更するために JSON または YAML ファイルを編集する必要がなくなります。

テンプレートからリソースを参照する

同様に、新しいリソースを作成するときにも、取り込んだテンプレートの任意のリソースを参照できます。

この仕組みを確認するために、新しい IAM ロールを作成して、さきほどの MyBucket リソースへの読み取りアクセスを許可することを考えます。

import * as cdk from '@aws-cdk/core';
import * as cfn_inc from '@aws-cdk/cloudformation-include';
import * as s3 from '@aws-cdk/aws-s3';
import * as iam from '@aws-cdk/aws-iam';

export class MigrationStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    const cfnInclude = new cfn_inc.CfnInclude(this, 'Template', {
      templateFile: 'migration-template.json',
    });
    const cfnBucket = cfnInclude.getResource('MyBucket') as s3.CfnBucket;

    const role = new iam.Role(this, 'Role', {
      assumedBy: new iam.AccountRootPrincipal(),
    });
    role.addToPolicy(new iam.PolicyStatement({
      actions: [
        's3:GetObject*',
        's3:GetBucket*',
        's3:List*',
      ],
      resources: [cfnBucket.attrArn],
    }));
  }
}

cdk diff を実行すると、CloudFormation テンプレートで定義されている MyBucket リソースを参照する、新しいリソース (IAM Role) が追加されていることがわかります。

CFN リソースからの上位レベルのリソースの作成

前のセクションでは、MyBucket に読み取りアクセス可能な IAM ロールを作成するにあたって、ロールが実行できるすべてのアクションを明示的にリストしたポリシーを作成しました。このような実装をするためには、付与するアクセス許可を正確に把握するため、S3 と IAM に関する詳しい知識が必要です。このセクションでは、CDK Construct Library を使用して同じ結果を実現します。Construct Library を使用すると、複雑なインフラストラクチャを非常に少ないコードで表現できます。高レベルの抽象化を使用すると、最小権限かつ最小スコープの IAM ポリシーを生成できる便利なgrant* メソッドが使えます。たとえば、S3 の Construct Library には Bucket クラスがあり、これは CFnBucket クラスよりも上位レベルの抽象化です。

Construct Library 内のリソースの from* スタティックメソッドを使用すると、下位レベルの Cfn* インスタンスから上位レベルのクラスを取得できます。たとえば、S3バケットの場合は、Bucket クラスの fromBucketName メソッドを使用します。

const bucket = s3.Bucket.fromBucketName(this, 'Bucket', cfnBucket.ref);

Bucket インスタンスにより、Construct Library の抽象化機能を最大限に活用できます。たとえば、S3 アクセスに必要な許可設定そのものをハードコーディングするのではなく、grantRead メソッドを使って、対象の Role に MyBucket の読み取り権限を持たせることができます。

import * as cdk from '@aws-cdk/core';
import * as cfn_inc from '@aws-cdk/cloudformation-include';
import * as s3 from '@aws-cdk/aws-s3';
import * as iam from '@aws-cdk/aws-iam';

export class MigrationStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    const cfnInclude = new cfn_inc.CfnInclude(this, 'Template', {
      templateFile: 'migration-template.json',
    });
    const cfnBucket = cfnInclude.getResource('MyBucket') as s3.CfnBucket;
    const bucket = s3.Bucket.fromBucketName(this, 'Bucket', cfnBucket.ref);

    const role = new iam.Role(this, 'Role', {
      assumedBy: new iam.AccountRootPrincipal(),
    });
    bucket.grantRead(role);
  }
}

上記のコードで cdk diff コマンドを実行すると、その出力は ロールにcfnBucketのアクセス許可を明示的に与えたときと同じになります。

クリーンアップ

この記事の指示に従ってデプロイした MigrationStack をクリーンアップするには、CDK プロジェクトのルートディレクトリで次のコマンドを実行します。

$ cdk destroy

これにより、CloudFormation テンプレートと、そのテンプレート内にあるすべてのリソースが AWS アカウントから削除されます。

まとめ

cloudformation-include モジュールを使用することで、core パッケージのCfnIncludeクラスの不自由さの多くを取り除いて、CloudFormationからCDKに移行することができます。リソースは、YAML または JSON 形式で指定されたテンプレートから直接インポートできます、変更も可能ですし、ネストされたスタック全体もインポートできます。Resources、Parameters(テンプレートを取り込む際に値を指定できます)、Outputsなど、テンプレートのすべてのセクションは完全にサポートされており、CDKコード内で直接参照できます。これにより、CloudFormation から CDK へのインフラストラクチャ移行が迅速かつ容易になり、プログラミング言語のフルパワーと CDK の高レベルの抽象化を活用して、インフラストラクチャを変更し続けることができます。

既存のスタックを移行してみてください。ひとたび移行が完了したら、CloudFormation テンプレートを変更する必要はありません。CDK アプリケーションが「真実のソース(Source of Truth)」 となります。スタックへのすべての変更は、CDKコードだけで行うことができます。

既存の CloudFormation テンプレートとスタックを CDK コードに移行する機能は、現時点で開発者プレビューとして利用できます。あなたのスタックで試してみて、GitHubであなたの考えを教えてください!

Calvin Combs

CloudFormation Include Construct Library の開発を支援した CDK チームのサマーインターン、Calvin Combs にシャウトアウトを。Calvinはメリーランド大学カレッジパークのコンピュータサイエンスと数学のダブルメジャーです。

原文はこちら。翻訳はSA大村が行いました。