AWS CDK/CloudFormation、リソースを変更せずにスタックドリフトを解消する

CloudFormationを使って開発していた場合に、DBアップデートがスタックドリフトを発生させるので、対応方法を検討してみました。
2020.09.07

CDKやCloudFormationでスタックドリフトが発生した場合に、リソースを変更することなくドリフトを解消する方法について記載します。

スタックドリフトとは

CloudFormation のテンプレートと、実際のリソースとの間に差異が発生している状態のことを指します。 検出方法については、こちらを参照してください。

スタックドリフト解消方法

スタックドリフトを解消するには、以下のような方法があります。

  • テンプレートをリソースに一致するように変更する
  • リソースをテンプレートに一致するように変更する
  • 一度テンプレートからリソースを切り離してスタックドリフトを解消し、再度、インポートする

最初の2つが使えればいいのですが、それができない場合があります。 たとえば、データベースが絡む場合です。テンプレートを更新すると、インスタンスを再作成してしまいます。かといって、DBインスタンスを変更することもできません。その場合は3番目の方法が使えるようです。

今回は、3番目の方法を試してみたいと思います。

実施したシナリオ

Aurora mysqlのクラスターをCDKで作成していた状態で、Auroraの自動マイナーバージョンアップグレードが走ったために、スタックドリフトが発生した、というシナリオでスタックドリフトの解消を行ってみたいと思います。

1. スタックの構築

RDSクラスターだけのサンプルスタックをCDKで作成しました。この時点でのAuroraのバージョンは、2.08.0です。

export class RemediationDriftStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const vpc = new ec2.Vpc(this, 'TestDriftVPC', {
      cidr: '10.0.0.0/16',
      enableDnsHostnames: false,
      enableDnsSupport: false,
      maxAzs: 2,
      subnetConfiguration: [{
        cidrMask: 24,
        subnetType: SubnetType.PRIVATE,
        name: 'TestDriftPrivateSubnet'
      }, {
        cidrMask: 24,
        subnetType: SubnetType.PUBLIC,
        name: 'TestDriftPublicSubnet'
      }]
    })

    new rds.DatabaseCluster(this, 'TestDriftRDSCluster', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: rds.AuroraMysqlEngineVersion.VER_2_08_0, // VERSION 2.08.0
      }),
      instances: 1,
      masterUser: {
        username: 'clusteradmin',
        password: cdk.SecretValue.plainText('pass_012345')
      },
      instanceProps: {
        instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.SMALL),
        vpcSubnets: vpc.selectSubnets({
          subnetType: SubnetType.PRIVATE
        }),
        vpc,
      },
    })
  }
}

2. スタックドリフトを発生させる

自動アップデートの想定ですが、擬似的にマネージメントコンソールから手動で強制アップデートを行います。 2.08.1にアップデートしました。

アップデート後スタックドリフトが発生しました。

3. ドリフトしたリソースをスタックから切り離す

3-1. 対象リソースの削除ポリシーをRetainに変更

CDKからやりたかったのですが、うまく必要なリソースを分離できなかったので、CDKからoutputオプションで出力したテンプレートファイルを直接編集します。
DBクラスターと、DBインスタンスに削除ポリシーRetainを追加します。

{
  "Resources": {

    ~ 略 ~

    "TestDriftRDSCluster75E0B209": {
      "Type": "AWS::RDS::DBCluster",
      ~ 略 ~
      "DeletionPolicy": "Retain" ← 追加
    },
    "TestDriftRDSClusterInstance1C388D7E5": {
      "Type": "AWS::RDS::DBInstance",
      ~ 略 ~
      "DeletionPolicy": "Retain" ← 追加
    }
  }
}

Cloudformation のテンプレートを更新します。

aws cloudformation update-stack --stack-name remediation-drift-stack --template-body file://template.json

3-2. Retainに設定したリソースをスタックから削除

対象のリソースを削除します。

{
  "Resources": {

    ~ 略 ~

// ここを削除
//    "TestDriftRDSCluster75E0B209": {
//      "Type": "AWS::RDS::DBCluster",
//      ~ 略 ~
//      "DeletionPolicy": "Retain"
//    },
//    "TestDriftRDSClusterInstance1C388D7E5": {
//      "Type": "AWS::RDS::DBInstance",
//      ~ 略 ~
//      "DeletionPolicy": "Retain"
//    }
  }
}

CloudFormation を再度更新します。

aws cloudformation update-stack --stack-name remediation-drift-stack --template-body file://template.json

これで、DBクラスター及び、DBインスタンスは削除されずにCloudFormationのスタックから切り離されます。 この時点で、スタックとリソースの差異はなくなり、スタックドリフトは解消された状態になります。

4. 切り離したリソースをインポートする

4-1.切り離したリソースをインポートするために、テンプレートを修正する

削除したDBクラスターと、DBインスタンスを元に戻し、バージョンをリソースの状態に合わせます。

{
  "Resources": {

    ~ 略 ~

    "TestDriftRDSCluster75E0B209": {
      "Type": "AWS::RDS::DBCluster",
      "Properties": {
        "Engine": "aurora-mysql",
        "EngineVersion": "5.7.mysql_aurora.2.08.1", ← バージョンを2.08.0から2.08.1に変更
        ~ 略 ~
        },
      ~ 略 ~
      "DeletionPolicy": "Retain"
    },
    "TestDriftRDSClusterInstance1C388D7E5": {
      "Type": "AWS::RDS::DBInstance",
      "Properties": {
        "Engine": "aurora-mysql",
        "EngineVersion": "5.7.mysql_aurora.2.08.1", ← バージョンを2.08.0から2.08.1に変更
        ~ 略 ~
      },
      ~ 略 ~
      "DeletionPolicy": "Retain"
    }
  }
}

4-2.編集したテンプレートをインポートする

修正したテンプレートをコントロールパネルCloudFormation「スタックへのリソースのインポート」からインポートします。

一度切り離した、リソースのDBクラスターIDDBインスタンスIDを設定して、インポートします

以上で、インポート終了です、あとは、"DeletionPolicy": "Retain" を削除してテンプレートを更新し、CDK上のAuroraバージョンをVER_2_08_1 に変更すれば、CDKのdiffもない状態になります。

まとめ

リソースを維持したままスタックドリフトを解消する方法を行ってみました。
こちらの方法は、RDSに関わらず、スタックにインポートできるリソースであれば実施可能だと思います。

ただ、実際に運用中の本番環境でこれを行うのはちょっと怖いと思いました。検証環境で十分な検証を行ってから実施する必要があると思います。
また、これらのリソースはスタックから切り離したまま運用するという選択肢もあるかもしれません。

将来的には、スタックドリフト検証ツールだけでなく、解消ツールもできるといいなと思いました。

参考

Remediate drift via resource import with AWS CloudFormation