CDK は論理 ID にハッシュ値のサフィックスを自動付与します。

  • 例:WebVpc → WebVpc46147648
  • 例外:スタック直下に置いた L1(Cfn で始まるクラス)は、パスが 1 階層のためハッシュが付かず、construct ID がそのまま論理 ID になる

サフィックスのハッシュ値について

ハッシュは、スタックからの相対パス(スタック ID は含まない)から生成されます。 L2 コンストラクトの場合は、内部で作られる L1 の ID である Resource までを含んだパス(例:SampleVpc/Resource)が対象です。

  • 同じコードならパスは同じなので、常に同じハッシュになる
  • コンストラクト ID を変更するとパスが変わり、ハッシュ(と論理 ID)が変わるので、リソースの再作成(置換)が発生する
  • 親のコンストラクトの ID を変えたり、間に階層を挟んだりしても、パスが変わるので同様に置換が発生する
  • スタック ID を変更しても論理 ID は変わらない。ただしスタック自体が別物として新規作成されるため、旧スタックのリソースは引き継がれない

これが 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.pathSampleStack/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 に置き換える
this.renameLogicalId('NewGeneratedLogicalId', 'OldLogicalId');
  • どちらの方法でも、変更前後の論理 ID を cdk diff で確認してから使う
  • 物理名(bucketName など)を明示したリソースは、置換時に「新リソース作成 → 旧リソース削除」の順で処理されるため、同名のリソースがすでに存在して失敗する。物理名は CloudFormation の自動生成に任せるのが安全
  • cdk diffreplace と表示されたリソースは、置換が発生する