CDKのドリフト検出ツール「cdk-real-drift」を試してみた
はじめに
CDK で管理している環境でも、調査や一時対応のためにコンソールから設定を変更することがあります。CloudFormation テンプレートに明示したプロパティの差分であれば cdk drift で確認できますが、テンプレートに書いていない値や、後から追加された関連リソースまで追えるのかは気になっていました。
そこで今回は、cdk-real-drift を使うと cdk drift では見えにくい変更をどこまで検出できるのか、実際に Lambda / IAM Role / S3 Bucket のスタックで確認してみました。
AWS CDK の cdk drift コマンドは CloudFormation のドリフト検出を利用しています。検出できるのは基本的に、CloudFormation テンプレートで管理されるプロパティの変更とテンプレート定義済みリソースの削除です。
cdk-real-drift(cdkrd)は @365_step_tech 氏が開発した OSS ツール(Apache-2.0 ライセンス)で、この制約を補完します。テンプレート未定義プロパティの変更検出、out-of-band で作成されたリソースの追跡、プロパティ単位での revert / ignore を提供しています。Terraform の state 管理に近い、テンプレート外の実リソース状態も追跡する考え方を CDK エコシステムに持ち込んだツールです。
| 検出対象 | cdkrd | cdk drift |
|---|---|---|
| テンプレート定義済みプロパティの変更 | ✅ CFn-Declared Drift | ✅ |
| テンプレート未定義プロパティの変更 | ✅ Potential Drift(record 後 baseline との差分で CFn-Undeclared Drift) | ❌ |
| out-of-band リソース追加 | ✅ Added Resource / Potential Drift | ❌ |
| プロパティ単位の revert | ✅ | ❌ |
| プロパティ単位の ignore | ✅ | ❌ |
| デプロイ前チェック(local synth vs live) | ✅ --pre-deploy |
❌ |
| AWS の既知のデフォルト値の自動除外 | ✅ atDefault 分類 | — |
※ 「—」は該当機能なし(cdk drift にはその概念自体が存在しない)を意味します。
各分類(atDefault / generated / skipped)の具体例は検証 1 で確認します。
本記事では Lambda Function / IAM Role / S3 Bucket で構成したスタックを対象に、cdkrd の主要機能を試しました。
セットアップ
検証環境は以下のとおりです。
- CDK CLI: 2.1131.0
- aws-cdk-lib: 2.261.0
- Node.js: v22.23.1
- TypeScript: 5.8.3
- constructs: 10.6.0
- cdk-real-drift: v0.22.4
- リージョン: ap-northeast-1
- 検証リソース: Lambda Function + IAM Role + S3 Bucket
cdkrd は npm から開発依存としてインストールします。
npm install -D cdk-real-drift
検証用スタックは Lambda Function、IAM Role、S3 Bucket の3リソースで構成しました。Lambda には 1024 MB のメモリを割り当て、S3 バケットへの読み取り権限を付与しています。
const bucket = new s3.Bucket(this, "VerificationBucket", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
const role = new iam.Role(this, "HandlerRole", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromManagedPolicyArn(
this,
"LambdaBasicExec",
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
),
],
});
bucket.grantRead(role);
new lambda.Function(this, "Handler", {
runtime: lambda.Runtime.NODEJS_22_X,
handler: "index.handler",
code: lambda.Code.fromAsset("lambda"),
role,
memorySize: 1024,
timeout: cdk.Duration.seconds(30),
});
autoDeleteObjects: true を指定すると CDK が内部でカスタムリソース(Custom::S3AutoDeleteObjects)を生成します。このカスタムリソースは後述の検証で skipped として扱われます。
検証内容
検証 1: 初回 check — CFn 定義済みドリフトと自動分類
コンソールから Lambda の MemorySize を 1024 から 512 に手動変更し、npx cdkrd check を実行しました。
=== check: CdkrdVerificationStack (ap-northeast-1) ===
[CFn-Declared Drift: 1] (declared in your CloudFormation template — the live value differs)
Handler.MemorySize (AWS::Lambda::Function)
desired=1024
actual =512
───────────────────────────────
result: 1 drift(s) (declared=1)
───────────────────────────────
info:
- atDefault=22 (undeclared values matching a known AWS default — not drift)
- generated=4 (auto-generated identifiers not in your template, AWS-assigned at deploy — not drift)
- skipped=1 — NOT checked (coverage incomplete: custom resource 1)
run with --verbose for the list
note: CdkrdVerificationStack: no .cdkrd baseline yet — run `cdkrd record` to establish one (future out-of-band changes then report as drift).
note: drift found — exit 0 (report-only). Pass --fail to make drift fail this command.
MemorySize の変更が CFn-Declared Drift として検出されました。desired(テンプレートの期待値)と actual(実際の値)が並記されるため、何がどう変わったかが一目で分かります。
出力の info 行には、テンプレート未定義プロパティの自動分類が表示されています。
| 分類 | 件数 | 内容 |
|---|---|---|
| atDefault | 22 | テンプレート未定義だが AWS デフォルト値と一致(S3 PublicAccessBlockConfiguration, IAM Role Path 等) |
| generated | 4 | AWS が自動生成した値(Lambda LoggingConfig, CodeSha256 等) |
| skipped | 1 | Custom::S3AutoDeleteObjects(カスタムリソース) |
atDefault はテンプレートに書いていないが AWS の既定値と一致しているプロパティで、ドリフトとして報告されません。generated はデプロイ時に AWS 側が割り当てる自動生成の値で、同様に除外されます。skipped はカスタムリソースなど cdkrd が現時点でチェック対象外としているものです。これらを自動で除外することで、確認すべきドリフトに絞り込まれます(なお、後述の検証 2 で登場する baseline 未作成時の Potential Drift は best-effort の判定であり、false positive の可能性もあります)。
同じ状態で cdk drift を実行した結果は次のとおりです。
Stack CdkrdVerificationStack
Modified Resources
[~] AWS::Lambda::Function Handler Handler886CB40B
└─ [~] /MemorySize
├─ [-] 1024
└─ [+] 512
1 resource has drifted from their expected configuration
✨ Number of resources with drift: 1 (3 unchecked)
この段階では、テンプレート定義済みプロパティの変更について cdkrd と cdk drift のどちらも同じく検出できます。差が出るのは次の検証からです。
検証 2: out-of-band リソース追加の検出
Lambda Function に対して Function URL(AuthType=NONE)をコンソールから手動作成し、再度 npx cdkrd check を実行しました。
=== check: CdkrdVerificationStack (ap-northeast-1) ===
No baseline yet — these live-only values can't be confirmed as drift. Record them right from this `cdkrd check` prompt, or run `cdkrd record`.
[CFn-Declared Drift: 1] (declared in your CloudFormation template — the live value differs)
Handler.MemorySize (AWS::Lambda::Function)
desired=1024
actual =512
[Potential Drift: 1] (live-only and not yet in your .cdkrd baseline, so cdkrd can't tell whether it's intended or an out-of-band change — Record to accept it, or Revert to remove it)
Handler ▸ NONE arn:aws:lambda:ap-northeast-1:123456789012:function:CdkrdVerificationStack-Handler886CB40B-XXXXX (AWS::Lambda::Url) — created out of band — not in your CloudFormation template
actual ={"InvokeMode":"BUFFERED","AuthType":"NONE","TargetFunctionArn":"CdkrdVerificationStack-Handler886CB40B-XXXXX"}
↳ this tier is a best-effort guess and can include false positives — if a value here is really an AWS-managed default or noise, please report it: https://github.com/go-to-k/cdk-real-drift/issues
─────────────────────────────────────────────────────────────
result: 2 findings — 1 drift (declared=1) + 1 potential drift
─────────────────────────────────────────────────────────────
info:
- atDefault=22 (undeclared values matching a known AWS default — not drift)
- generated=4 (auto-generated identifiers not in your template, AWS-assigned at deploy — not drift)
- skipped=1 — NOT checked (coverage incomplete: custom resource 1)
note: drift found — exit 0 (report-only). Pass --fail to make drift fail this command.
手動追加した Function URL が Potential Drift として検出されました。同じ状態で cdk drift を実行しても、検出されるのは検証 1 と同じ MemorySize のみで、Function URL の追加は検出されません。テンプレートに定義されていないリソースだからです。cdk drift ではまったく引っかからないので、ここが一番差を感じたポイントでした。
Potential Drift は baseline 未記録の値であり、record(承認)または revert(削除)で解決します。
なお、IAM Role にインラインポリシーを追加した場合、cdkrd はこれを「out-of-band resource(Added Resource)」として扱いません。Role の Policies プロパティに対する undeclared property drift として分類します。リソースの追加と既存リソースの未定義プロパティ変更を区別しており、baseline を作った後の挙動を理解する上で重要な違いです。
検証 3: record → 再変更 → 確定ドリフト検出
npx cdkrd record を実行して現状を baseline として記録しました。
baseline written: .cdkrd/baselines/CdkrdVerificationStack.123456789012.ap-northeast-1.json (3 recorded entry(ies))
note: CdkrdVerificationStack: record snapshotted undeclared + added state into the baseline only — 1 declared/deleted drift NOT approved (it still reports as drift; resolve with cdkrd revert, cdkrd ignore, or cdk deploy).
commit the baseline file(s) so drift is detected against them going forward.
baseline ファイルが .cdkrd/baselines/ 配下に生成され、3件のエントリが記録されました。record が baseline に取り込むのはテンプレート未定義プロパティと追加リソースの状態のみで、テンプレート定義済みのドリフト(この時点の MemorySize)は承認されません。定義済みプロパティのドリフト解消には revert、ignore、または cdk deploy が必要です。baseline ファイルはリポジトリにコミットして共有することが推奨されています。
続いて、記録済みの Function URL に対して InvokeMode を BUFFERED から RESPONSE_STREAM に手動変更し、再度 npx cdkrd check を実行しました。
=== check: CdkrdVerificationStack (ap-northeast-1) ===
[CFn-Declared Drift: 1] (declared in your CloudFormation template — the live value differs)
Handler.MemorySize (AWS::Lambda::Function)
desired=1024
actual =512
[Added Resource: 1] (a WHOLE live resource not in your CloudFormation template (the resource-level counterpart of CFn-Undeclared) — created out of band under a declared parent, changed from your .cdkrd baseline (an unrecorded one is Potential Drift, not confirmed drift))
Handler ▸ NONE arn:aws:lambda:ap-northeast-1:123456789012:function:CdkrdVerificationStack-Handler886CB40B-XXXXX (AWS::Lambda::Url) — created out of band — not in your CloudFormation template; changed since record
~ InvokeMode
baseline="BUFFERED"
actual ="RESPONSE_STREAM"
───────────────────────────────────────
result: 2 drift(s) (declared=1 added=1)
───────────────────────────────────────
info:
- atDefault=22
- generated=2
- skipped=1 (custom resource 1)
note: drift found — exit 0 (report-only). Pass --fail to make drift fail this command.
検証 2 では Potential Drift(未確認)だった Function URL が、baseline との差分が検出されたことで Added Resource(確定ドリフト)に昇格しました。baseline に記録した InvokeMode=BUFFERED に対して実際の値が RESPONSE_STREAM に変わっており、プロパティ単位で差分が表示されています。baseline を作ることで「意図しない変更」を確定ドリフトとして扱えるようになる、という流れです。
検証 4: revert で手動変更を元に戻す
cdkrd はドリフトの検出だけでなく、手動変更を元に戻す revert も備えています。まず npx cdkrd revert を実行しました。
=== revert: CdkrdVerificationStack (ap-northeast-1) ===
Handler (AWS::Lambda::Function)
- MemorySize -> deployed-template value
NOT revertable: 1 (recorded added resource changed since record — reverting DELETES the whole resource; re-record if the change is right, or --remove-unrecorded to delete it)
reverted: Handler
verifying convergence (re-reading 1 resource(s))...
CdkrdVerificationStack: 1 drift(s) remain.
- Handler ▸ NONE arn:aws:lambda:ap-northeast-1:123456789012:function:CdkrdVerificationStack-Handler886CB40B-XXXXX (added)
MemorySize はデプロイ済みテンプレートの値に戻され、revert 後に convergence check(対象リソースの再読み込み)が走り、MemorySize がテンプレート値へ復元されたことを確認できます。一方、Function URL のような Added Resource は revert 対象外(NOT revertable)として扱われました。リソースまるごとの削除につながるため、明示的なオプションなしには実行されない安全側の設計です。
Function URL を削除するには --remove-unrecorded を付けて実行します。
=== revert: CdkrdVerificationStack (ap-northeast-1) ===
Handler ▸ NONE arn:aws:lambda:ap-northeast-1:123456789012:function:CdkrdVerificationStack-Handler886CB40B-XXXXX (AWS::Lambda::Url)
- DELETE out-of-band AWS::Lambda::Url (not in your template)
reverted: Handler ▸ NONE arn:aws:lambda:ap-northeast-1:123456789012:function:CdkrdVerificationStack-Handler886CB40B-XXXXX
verifying convergence (re-reading 1 resource(s))...
CdkrdVerificationStack: 1 drift(s) remain.
- Handler886CB40B/arn:aws:lambda:ap-northeast-1:123456789012:function:CdkrdVerificationStack-Handler886CB40B-XXXXX (deleted)
Function URL は削除されました。convergence check では、削除したリソースが baseline にはまだ記録されているため「deleted」のドリフトが残ります。baseline を現状に合わせるため npx cdkrd record で再記録しました。
baseline written: .cdkrd/baselines/CdkrdVerificationStack.123456789012.ap-northeast-1.json (2 recorded entry(ies))
再チェックすると CLEAN になりました。
=== check: CdkrdVerificationStack (ap-northeast-1) ===
result: CLEAN
info:
- atDefault=22
- generated=2
- skipped=1 (custom resource 1)
revert は(1)今回の検証ではテンプレート定義済みプロパティ(MemorySize)をデプロイ済みテンプレートの値へ即座に復元、(2)Added Resource は --remove-unrecorded なしでは削除しない、という挙動でした。意図しないリソース削除を防ぐ設計です。
検証 5: ignore で特定プロパティを無視
運用では、意図的な手動変更をドリフトとして扱いたくないケースもあります。インシデント対応のホットフィックスなどです。ここでは Lambda の MemorySize を 1024 から 2048 に変更したケースを、意図的なホットフィックスと想定しました。
ignore 前のチェックでは、通常どおり CFn-Declared Drift として検出されます。
=== check: CdkrdVerificationStack (ap-northeast-1) ===
[CFn-Declared Drift: 1] (declared in your CloudFormation template — the live value differs)
Handler.MemorySize (AWS::Lambda::Function)
desired=1024
actual =2048
───────────────────────────────
result: 1 drift(s) (declared=1)
───────────────────────────────
npx cdkrd ignore を実行すると .cdkrd/ignore.yaml に無視ルールが追記されます。ルール直前にコメントで「なぜ無視するのか」を記録しておけます。
# cdkrd ignore rules — properties cdkrd should stop reporting as drift.
# Each rule: { path, stack?, account?, region? }; `path` is a
# "<constructPath|logicalId>.<property>" glob, the scopes narrow WHERE it applies.
# Add a comment above a rule to record WHY it is ignored.
ignore:
# インシデント対応のホットフィックス。CDKコードへの反映は次スプリントで対応予定
- path: Handler.MemorySize
stack: CdkrdVerificationStack
account: "123456789012"
region: ap-northeast-1
path は「construct パスまたは論理 ID + プロパティ名」の glob で指定し、stack / account / region で適用範囲を絞れます。ignore 後にチェックすると CLEAN になり、info 行に ignored=1 として無視した件数が明示されます。
=== check: CdkrdVerificationStack (ap-northeast-1) ===
result: CLEAN
info:
- atDefault=22
- generated=2
- ignored=1 ("Handler.MemorySize (stack:CdkrdVerificationStack, account:123456789012, region:ap-northeast-1)" 1)
- skipped=1 (custom resource 1)
無視した事実が件数として残るため、ホットフィックスをいったん許容しつつ後から棚卸しする運用に使えます。
検証 6: --pre-deploy でデプロイ前チェック
--pre-deploy はローカルで synth したテンプレートと live の状態を突き合わせ、デプロイ前に差分を検出して意図しない上書きを防げます。
※ 検証 5 で追加した ignore ルールは .cdkrd/ignore.yaml から削除し、MemorySize の差分が再び検出対象になる状態で実行しています。
コンソールで MemorySize を 2048 に変更済み、CDK コードは 1024 のままという状況で実行しました。この状態では live(2048)とローカル synth テンプレート(1024)が食い違っており、--pre-deploy がその差分を事前に報告します。
(--pre-deploy) comparing live state against the LOCAL synth template
note: CdkrdVerificationStack: --pre-deploy reports declared drift only (undeclared tiers are evaluated against the deployed template — run check without --pre-deploy)
=== check: CdkrdVerificationStack (ap-northeast-1) ===
[CFn-Declared Drift: 1] (declared in your CloudFormation template — the live value differs)
Handler.MemorySize (AWS::Lambda::Function)
desired=1024
actual =2048
───────────────────────────────
result: 1 drift(s) (declared=1)
───────────────────────────────
info: skipped=1 — NOT checked (coverage incomplete: custom resource 1)
note: drift found — exit 0 (report-only). Pass --fail to make drift fail this command.
CDK コード側を 2048 に修正してから再実行すると CLEAN になりました。--pre-deploy が CLEAN であれば、テンプレート定義済みプロパティについては live とローカル synth の食い違いがない状態と判断できます(undeclared 層は対象外)。CI/CD ではデプロイ前ゲートとして npx cdkrd check --pre-deploy --fail を利用すれば、テンプレート定義済みプロパティに関する意図しない上書きを検出・ブロックできます。
まとめ
cdk-real-drift は、cdk drift だけでは捉えにくいテンプレート未定義プロパティの変更や、out-of-band で追加されたリソースを追跡できるツールです。今回の Lambda・IAM Role・S3 を対象にした検証では、baseline を起点として Potential Drift と確定したドリフトを区別し、record / revert / ignore によって扱いを選べることを確認できました。
CDK を使っていても、調査や一時対応でコンソールから設定を変更する場面はあります。そうした変更を後から可視化し、意図したものか戻すべきものかを整理する手段として、cdkrd の baseline / record / revert / ignore は便利に使えそうだと感じました。
すべてのリソースタイプ・プロパティに対する網羅性までは今回確認していません。ただ、IaC を維持しながらも、現実に発生する out-of-band な変更を可視化・整理したい CDK プロジェクトにとって、有力な選択肢になりそうです。今後の対応リソースや分類精度も含めて、引き続き注目していきたいと思います。








