論理 ID
CDK は 論理 ID にハッシュ値のサフィックスを自動付与します。
- 例:SampleVpc → SampleVpc07DAD426
- 例外:スタック直下に置いた L1(
Cfnで始まるクラス)は、パスが 1 階層(Defaultを除いて数える)のためハッシュが付かず、construct ID がそのまま論理 IDになる- 例:
CDKMetadata/Default→CDKMetadata
- 例:
論理 ID の組み立て
論理 ID は「人が読める部分」と「ハッシュ」をつなげたものです。
- スタックからの相対パス(スタック ID は含まない)を
/で分割する Defaultの要素を取り除く(Defaultと完全一致。DefaultSample などは取り除かない)- 要素が 1 つだけなら、英数字以外を除いた文字列をそのまま論理 ID とする(ハッシュなし)
- ハッシュ:残った要素を
/でつないだ文字列の MD5 の先頭 8 桁(大文字) - 人が読める部分:重複を除き(直前の要素がその要素で終わっていればスキップ)、
Resourceを除き、英数字以外を削除してつなげる(最大 240 文字) - 人が読める部分 + ハッシュ
Default と Resource の扱いの違いは以下のとおりです。
| ハッシュ | 人が読める部分 | |
|---|---|---|
Default |
除く | 除く |
Resource |
含める | 除く |
例:SampleJob/ExecutionRole/DefaultPolicy/Resource
- ハッシュ:
md5("SampleJob/ExecutionRole/DefaultPolicy/Resource")→5F0F8C58 - 人が読める部分:
SampleJobExecutionRoleDefaultPolicy - 論理 ID:
SampleJobExecutionRoleDefaultPolicy5F0F8C58
実装は aws-cdk-lib/core/lib/private/uniqueid.js の makeUniqueId です。
サフィックスのハッシュ値について
ハッシュは、スタックからの相対パス(スタック ID は含まない)から生成されます。
多くの L2 コンストラクトは、内部で作る L1 に Resource という ID を付けるため、Resource までを含んだパス(例:SampleVpc/Resource)が対象になります。
ただし Resource 以外の ID を付ける L2 もあります。
ecs.FargateService→FargateService/Serviceec2.Vpc→Vpc/PublicSubnet1/Subnet、Vpc/IGWなど(1 つの L2 から複数の L1 を作る)
実際のパスは cdk.out/<Stack>.template.json の各リソースの Metadata にある aws:cdk:path で確認できます。
- 同じコードならパスは同じなので、常に同じハッシュになる
- コンストラクト ID を変更するとパスが変わり、ハッシュ(と論理 ID)が変わるので、リソースの再作成(置換)が発生する
- 親のコンストラクトの ID を変えたり、間に階層を挟んだりしても、パスが変わるので同様に置換が発生する
- 例外:挟んだコンストラクトの子の ID を
Defaultにすると、Defaultはパスから除かれるため論理 ID は変わらない
- 例外:挟んだコンストラクトの子の ID を
- スタック ID を変更しても論理 ID は変わらない。ただしスタック自体が別物として新規作成されるため、旧スタックのリソースは引き継がれない
- 旧スタックは自動では削除されず残る
- CDK / CloudFormation が自動生成する物理名にはスタック名が含まれるため(例:ECS タスク定義のファミリー名
SampleStackSampleJobTaskDefinition287C5E0B)、新スタックでは別名のリソースになる
物理名の自動生成との違い
論理 ID とは別に、L2 が自動で付ける物理名もパスから生成されることがあります。こちらはスタック ID を含むフルパスで、Resource を含まない L2 自身のパスから計算されます。
| 項目 | 元になるパス | 値 |
|---|---|---|
| 論理 ID | SampleJob/TaskDefinition/Resource |
SampleJobTaskDefinitionC9937766 |
| ファミリー名 | SampleStack/SampleJob/TaskDefinition |
SampleStackSampleJobTaskDefinition287C5E0B |
これが CDK でコンストラクト ID を安易に変更してはいけない理由です。
例
Stack や L2 コンストラクトなど多くの Construct のコンストラクタは (scope: Construct, id: string, props?) という形を取り、scope に親を渡すことで実行時にツリー構造が組まれる仕組みです(constructs ライブラリの Composite pattern)。
例外として App は scope を取りません。
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// this(SampleStackインスタンス自身)をscopeとして渡す
// → SampleVpcはSampleStackの子になる
const vpc = new ec2.Vpc(this, 'SampleVpc', { /* ... */ });
}
}
const app = new cdk.App();
new SampleStack(app, 'SampleStack');
このとき vpc.node.path は SampleStack/SampleVpc になります。this を渡すことで 今のクラスの中に子コンストラクトを吊るす という親子関係が作られ、さらに深くネストしたい場合は自作の Construct クラスを間に挟んで scope を渡していけば、SampleStack/MyConstruct/SampleVpc のように何段階でも階層を作れます。
論理 ID のハッシュ計算に使われるのは、スタックを除いたパスです。この例の VPC は SampleVpc/Resource から計算されるので、論理 ID は SampleVpc にハッシュが付いた形になります。
論理 ID の変更を避ける方法
コンストラクト ID の変更やリファクタリングが避けられない場合は、論理 ID を固定して、置換を防ぎます。
// 1. L1 の論理 ID を固定する
const cfnVpc = vpc.node.defaultChild as ec2.CfnVPC;
cfnVpc.overrideLogicalId('SampleVpcXXXXXXXX'); // 変更前の論理 ID
// 2. スタックで、生成される論理 ID を別の ID に置き換える
// シグネチャは renameLogicalId(oldId, newId)
// oldId = CDK が新しく生成する論理 ID、newId = 使いたい(変更前の)論理 ID
this.renameLogicalId('NewGeneratedLogicalId', 'OldLogicalId');
- どちらの方法でも、変更前後の論理 ID を
cdk diffで確認してから使う - 物理名(
bucketNameなど)を明示したリソースは、置換時に「新リソース作成 → 旧リソース削除」の順で処理されるため、同名のリソースがすでに存在して失敗する。物理名は CloudFormation の自動生成に任せるのが安全
cdk refactor
cdk refactor は CloudFormation のスタックリファクタリング機能を使い、リソースを作り直さずに論理 ID の変更やスタック間の移動を行うコマンドです。比較的新しい機能のため、利用前に公式ドキュメントで制約を確認してください。
cdk diff での見え方
置換は cdk diff で以下のように表示されます。
- 論理 ID が変わった場合:
replaceではなく、旧論理 ID の削除([-])と新論理 ID の追加([+])の組として表示される - 論理 ID は同じで、置換が必要なプロパティを変更した場合:
replace(requires replacement)と表示される
どちらも実際にはリソースが再作成されます。