ソフトウェアをリリースしました

かいふく という訪問介護の副業に特化した求人サイトを公開しています。

お近くの介護資格保有者にご紹介頂けると嬉しいです 👏

https://kai-fuku.com/

このサイトにはプロモーションが含まれます

CDKで管理しているRDSを暗号化したい

どうも、PHPerです。

先日、業務にて「既存のRDSのストレージを暗号化したい」という、シンプルかつ重たい要件が降ってきました。

これの何が困るかというと、皆様ご存知の通りRDSの仕様です。 RDSは作成後の暗号化ステータスの変更ができません。つまり、既存のRDSを暗号化するためには、「暗号化有効」設定で作り直す(再作成する)必要があります。

参考: Amazon RDS DB インスタンスおよびクラスターの暗号化 - AWS Prescriptive Guidance

さらに今回はインフラを AWS CDK で管理しています。
単純にCDKのコードを書き換えてデプロイすると、CloudFormationの仕様により「リソースの置換」が発生し、予期せぬダウンタイムやデータ損失のリスクがあります。
また、よくある「スナップショットから復元して差し替え」という手法も、CDKのState管理との兼ね合いで今回は採用できませんでした。

様々な制約がある中、「既存データを維持しつつ」「CDK管理のまま」暗号化リソースへ移行できたので、その手法を共有します。

今回の要件と制約

今回のミッションにおける前提条件は以下の通りです。

  • 既存RDSの暗号化(最重要)
  • 既存データをそのまま移行できること
  • アプリ側の変更を避けるため、エンドポイント(またはDNS)の変更がないこと
  • ダウンタイムは許容(ALBでメンテナンス画面を挟む前提)
  • バッチ処理が稼働していない夜間帯での作業
  • CDKコードの変更は最小限に留め、今後もCDKで管理し続けること

やんやん

プログラマーとしてLEMP環境に主に生息しており、DevOps 的な立ち回りをしながらご飯を食べている当ブログの管理人のやんやんと申します。
最近はTmux使うのを辞めました。

結論:CDKデプロイを3回行う

結論から言うと、「削除ポリシーを変更して既存RDSをCDK管理から切り離し、新規で暗号化RDSを作成する」という力技で解決しました。

具体的には以下の3ステップでデプロイを行います。

  1. RemovalPolicy.RETAIN を設定(既存保護)
  2. instanceIdentifier を変更(既存切り離し)
  3. storageEncrypted: true を指定(新規作成)

なぜこの手順が必要なのか?(ハマりポイント)

通常、CDKで管理されているリソースに対し、instanceIdentifier(物理ID)を固定したまま storageEncrypted: true に変更してデプロイしようとすると、以下のエラーが発生して失敗します。

CloudFormation cannot update a stack when a custom-named resource requires replacing

これは、「カスタム名(明示的な名前)がついているリソースを置換(削除&作成)することはできない」というCloudFormationの安全装置です。これを回避するために、あえて識別子を変更して別リソースとして認識させる必要があります。


手順詳細

1. 削除ポリシーの変更

まず、既存のRDSがCDKのスタック操作によって削除されないようにします。 これをやらないと、次の手順で「既存RDSの削除」が走ってしまい、データが消えます(超重要)。

コード スニペット

new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db', // 現在の識別子
  // ...
  removalPolicy: cdk.RemovalPolicy.RETAIN, // ここを追加!
});

この状態で一度 cdk deploy します。 これで、このRDSはスタックから削除される際も、AWS上にはリソースが残るようになります。

2. instanceIdentifier の変更

次に、既存のRDSをCDKの管理下から外す(Orphanにする)作業です。 CDK上の識別子を、既存のものとは別の名前に書き換えます。

コード スニペット

new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db-temp', // 3と被らない一時的な名前に変更
  // ...
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});

この状態で cdk deploy します。

何が起きるか:
CloudFormationは「my-prod-db を管理から外し、新しく my-prod-db-temp を作る」という挙動をします。 ステップ1で RETAIN を設定しているため、旧RDS(データ入り)は削除されずにそのまま残ります。
※この時点でアプリはまだ旧RDSを見ています。

3. いよいよ暗号化RDSの構築

最後に、本命の「暗号化されたRDS」を作成します。

コード スニペット

new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db', // 元の名前に戻す(ここが変わるとエンドポイント変わるので注意)
  storageEncrypted: true,           // 暗号化をON
  removalPolicy: cdk.RemovalPolicy.RETAIN,
  // ...
});

この状態で cdk deploy します。

注意点:
もし instanceIdentifier を手順1と同じ名前(my-prod-db)に戻したい場合、手順2の時点でAWS上に古い my-prod-db が残っているため、名前重複でエラーになる可能性があります。
その場合は、AWSマネジメントコンソールまたはCLIから、古いRDS(手順2で切り離されたもの)の識別子を手動で my-prod-db-old などにリネーム してからデプロイしてください。

これで、暗号化された空っぽのRDSが立ち上がりました。

データ移行

新旧2つのRDSが存在している状態になりました。

  • 旧RDS
    • データあり
    • 非暗号化
    • CDK管理外
  • 新RDS
    • データなし
    • 暗号化済み
    • CDK管理下

データ量がそれほど多くない(数GB〜数十GB程度)ため、今回はシンプルに mysqldump で移行を行いました。TB級の場合は AWS DMS (Database Migration Service) の利用を検討してください。

# SSM Session Manager 等で踏み台サーバーに接続

# 1. 旧RDSからデータをエクスポート(パイプで圧縮して転送時間を短縮)
mysqldump -h [旧RDSエンドポイント] -u [ユーザー名] -p \
  --single-transaction --routines --triggers \
  --databases [対象のスキーマ] \
  | gzip > dump.sql.gz

# 2. 新RDSへインポート
zcat dump.sql.gz | mysqldump -h [新RDSエンドポイント] -u [ユーザー名] -p

データの移行が完了したら、アプリケーションの接続先が新RDSに向いていることを確認し、メンテナンスを解除します。

後片付け

無事稼働確認が取れたら、不要なリソースを削除してお財布を守りましょう。

  1. 手順2で立ち上がった一時的なRDS(CDKの管理からは外れているはずですが、念の為確認して削除)
  2. 手順1以前から存在していた旧RDS(リネームした my-prod-db-old など)

スナップショットが取れていることを確認した上で、マネジメントコンソールから削除しました。

(おまけ) 試したけどうまくいかなかったこと

cdk import での既存リソース取り込み

既存のリソースをリネームして、新しいスタックに取り込もうと cdk import を試みましたが、以下のようなメッセージが出てスキップされました。

TmpStack: no new resources compared to the currently deployed stack, skipping import.

また、定義と実リソースのプロパティ(暗号化の有無)が食い違っているため、インポート自体が整合性エラーになる可能性が高いです。

DatabaseInstanceFromSnapshot の利用

スナップショットから復元するCDKのクラスですが、これをメインの定義にしてしまうと、常に「スナップショットから作られた状態」が正となり、パラメータグループやバージョン更新などの運用時にドリフト(設定乖離)の管理が面倒になると判断し、今回は見送りました。

まとめ

CDKで管理しているステートフルなリソース(RDSなど)の「再作成必須な変更」は非常に神経を使います。 「Retainで保護」→「Identifier変更で管理から切り離し」→「新規作成」 というフローは、RDSに限らず他のリソースでも応用が効くテクニックなので、覚えておいて損はないはずです。

なにはともあれですが、RDSは最初から暗号化しましょう。これだけは覚えて頂けると。。。

同じ悩みを抱えるPHPer(およびAWS使い)の助けになれば幸いです!

おすすめの記事