インフラ

[CDK]EC2が「cdk diff」でリプレース無しだったのにリプレースされた

cdk

CDKでEC2を作りました。
作った後にしばらくして、EC2の設定を変えたいことがありCDKを変更しました。
その際には、設定変更したところだけcdk diffで差分が出ていました。
その際は、リプレースの表示はありませんでした。

しかし、cdk deployしてみるとEC2がリプレースされていました。

こちらの事象の原因と対策についてです。

結論から書きます。

  • 原因: CFnでEC2インスタンスのイメージIDの指定方法がSSMパラメータストアになっていた。パラメータストアの中身の変更は「cdk diff」で検知できなかった。
  • 対策: AMI IDを直接指定する、cachedInContextを使う

事象: CDKで作ったEC2インスタンスが「cdk diff」でリプレース無かったのに置き換えれれた

サンプルコードで事象を説明します。
以下のようなコードでEC2インスタンスを作成しました。

    new ec2.Instance(this, "Instance", {
      vpc,
      machineImage: ec2.MachineImage.latestAmazonLinux({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
      }),
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
    })

何日か経って、セキュリティグループを追加しました。

    new ec2.Instance(this, "Instance", {
      vpc,
      machineImage: ec2.MachineImage.latestAmazonLinux({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
      }),
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
    })
    // 追加
    server.connections.allowFromAnyIpv4(ec2.Port.tcp(80))

cdk diffを確認します。

% npx cdk diff                                                                                                                                                                                                                                       (git)-[master]
Stack CdkEc2ReplaceStack
Security Group Changes┌───┬───────────────────────────────────────────┬─────┬──────────┬─────────────────┐│   │ Group                                     │ Dir │ Protocol │ Peer            │├───┼───────────────────────────────────────────┼─────┼──────────┼─────────────────┤
│ + │ ${Instance/InstanceSecurityGroup.GroupId} │ In  │ TCP 80   │ Everyone (IPv4) │
└───┴───────────────────────────────────────────┴─────┴──────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[~] AWS::EC2::SecurityGroup Instance/InstanceSecurityGroup InstanceInstanceSecurityGroupF0E2D5BE 
 └─ [+] SecurityGroupIngress
     └─ [{"CidrIp":"0.0.0.0/0","Description":"from 0.0.0.0/0:80","FromPort":80,"IpProtocol":"tcp","ToPort":80}]

上記の結果からは、EC2インスタンスのリプレースは発生しなさそうです。
しかし、cdk deployしてみるとEC2インスタンスが置き換えられてしまいました。(以前作成したEC2インスタンスが削除され、新しいEC2インスタンスが作成された)

原因: CFnでEC2インスタンスのイメージIDの指定方法がSSMパラメータストアになっていた

イメージIDが変わってしまって、リプレースされた様子でした。
(CFnのドキュメントにあるようにイメージIDの更新にはリプレースが必要です)
CloudFormation UserGuide AWS::EC2::Instance

上記のコードで生成されたCFnを見てみます。イメージIDの部分はSSM パラメータストアを指している様子です。

        "ImageId": {
          "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter"
        },

SSMパラメータストアの部分を見てみると、AWSが提供しているパラメータストアを参照している様子です。

    "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter": {
      "Type": "AWS::SSM::Parameter::Value",
      "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
    },

パラメータストアの値を見てみると、イメージIDが保存されていることや最終更新日時がわかります。

$ aws ssm get-parameters --name /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
{
    "Parameters": [
        {
            "Name": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2",
            "Type": "String",
            "Value": "ami-0923d9a4d39b22a91",
            "Version": 56,
            "LastModifiedDate": "2022-01-11T06:19:32.876000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1::parameter/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2",
            "DataType": "text"
        }
    ],
    "InvalidParameters": []
}

SSMパラメータストアの中身の値は、実行しているCDKでは管理していません。
そのため、cdk diffで差異を検出できませんでした。

対策: AMI IDの指定方法を変える

AWS管理のSSMパラメータストアから毎回取得しないようにする(キャッシュ)、またはイメージIDにSSMパラメータストアを使わないようにする必要があります。

CDKでEC2にオプションcachedInContextを使う

まずは、cdk.context.jsonに保存してその値を使う方法です。

interface AmazonLinuxImageProps · AWS CDK


      machineImage: ec2.MachineImage.latestAmazonLinux({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
        cachedInContext: true // 追加
      }),

cachedInContextを追加することで、以下のようにイメージIDがキャッシュされます。

"ssm:account=XXXXXXXXXX:parameterName=/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2:region=ap-northeast-1": "ami-0923d9a4d39b22a91"

生成されるCFnでは、イメージIDが直接指定されます。

        "ImageId": "ami-0923d9a4d39b22a91",

lookupなどイメージIDが固定される方法でmachineImageを指定する

イメージの指定方法は色々あると思いますが、例えば以下のように、lookupなどでイメージ名を指定するとイメージIDが固定化されます。


      machineImage:ec2.MachineImage.lookup({
        name: "amzn2-ami-hvm-2.0.20211223.0-x86_64-gp2",
        owners: ['amazon'],
      }),  

まとめ

EC2が「cdk diff」無しで置き換わる事象でした。
ゴールデンAMI作ってそれを指定することが多いと思うので、あまりec2.MachineImage.latestAmazonLinuxなどを使ってイメージを指定することは少ないかもしれません。

AWS管理のSSMパラメータストアからイメージIDを取るようなケースがあったら、cachedInContextなどを使って回避するのが良いと思います。

Amzonで「AWS」の本を見てみる

楽天で「AWS」の本をみてみる!!

【翻訳記事】AWS CDKで複数の環境を設定する4つの方法はじめに(訳者より) 本稿は以下の、ブログ記事の翻訳です。 この翻訳記事を書くことに快諾頂いた@der_rehanさんに感謝します。 ...
cdk
CDKで環境ごとに異なるパラメータを適用するときに気をつけたいことこんにちは、ちゃりおです。 CDKで複数環境を構築する際に、環境差異をどう表現するか悩むことがよくあります。 (例えば、PRDとSTG...
cdk book
[書評]CDK使っている人は一度読んでおきたい「The CDK Book」こんにちは、ちゃりおです。 「The CDK Book」読んでみました。 CDKは比較的新しいIaCツールのため、実践的な内容につい...