🎉

【実例】これで解決!AWS CDKのバージョン違いで起こったデプロイ障害とその対策

2024/07/19に公開

【実例】これで解決!AWS CDKのバージョン違いで起こったデプロイ障害とその対策

Contents

概要

AWS CDKを使ったデプロイでEC2インスタンスに接続できなくなったり、プライベートサブネットからインターネットに出られなくなった経験はありませんか?今回はAWS CDKのバージョンが原因で発生した障害の事例を紹介し、その解決方法を解説します。

error

結論と教訓

単純に AWS CDK のバージョンが低い環境(v2.130.0)で cdk deploy を実行したことによる先祖返り障害です。CDKのバージョンアップを行ってから再デプロイすることで解決できました。

今回のことで得られた教訓は次のとおりです。

  • どんな環境でも、差分の確認は必ず行うべき

  • いつもと違う作業環境でデプロイを実施するときは特に、CDKのバージョン確認は怠らないことが重要

トラブルの概要

  1. S3バケットを追加する必要があったのでバケット作成のコードを追加
  2. プライベートな検証環境で cdk deploy コマンドを使用して追加リソースをデプロイ
  3. EC2インスタンスにセッションマネージャーで接続不可
  4. トラブル調査
    • VPC Reachability Analyzerで接続状態を確認
    • CloudFormationの変更セットで差分チェック

変更内容がS3バケットの追加のみだったため、詳細な差分を確認せずにデプロイを実行してしまいました。
※個人のプライベートAWSアカウントだからといって、差分チェックを怠ったことが適切ではありませんでした。

デプロイログには、NATインスタンスの入れ替えがありました。前回デプロイが4月なのでNATインスタンスに使用している Amazon Linux 2023 の新しいAMIがリリースされたためだろうと考えました。

しばらくして、追加したS3バケットの作成も終わり、デプロイが完了しました。

その後、EC2にセッションマネージャーでWindows Serverにリモートデスクトップ接続を行いましたが、繋がりません。

EC2コンソールから確認しても、セッションマネージャーと接続できなくなっていました。再起動しても接続できませんでした。

トラブル調査

VPC Reachability Analyzerで接続状態を確認

NATインスタンスが入れ替わったことが原因なのは予想できたので、VPC Reachability Analyzerを使って接続状態を確認してみました。このツールは非常に便利です。

プライベートサブネットにあるEC2からインターネットに出られるかを確認しましたが、結果は 到達不能 でした。
result

正しく接続できる場合は、EC2インスタンス→NATインスタンス→インターネットゲートウェイとなります。

success

CloudFormationの変更セットで差分チェック

次に、デプロイしたCloudFormationの変更セットを確認しました。
NATインスタンスのAMIのほかに、UserDataの文字列に差分がありました。CloudFormationテンプレート上ではBase64でエンコードされているのでデコードしました。

< 変更前 >
1 #!/bin/bash
2 yum install iptables-services -y
3 systemctl enable iptables
4 systemctl start iptables
5 echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/custom-ip-forwarding.conf
6 sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf
★7 sudo /sbin/iptables -t nat -A POSTROUTING -o $(route | awk '/^default/{print $NF}') -j MASQUERADE
8 sudo /sbin/iptables -F FORWARD
9 sudo service iptables save

< 変更後 >
1 #!/bin/bash
2 yum install iptables-services -y
3 systemctl enable iptables
4 systemctl start iptables
5 echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/custom-ip-forwarding.conf
6 sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf
★7 sudo /sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
8 sudo /sbin/iptables -F FORWARD
9 sudo service iptables save

7行目(★)に注目してください。コマンドが違います。
この違いは、aws-ec2: NatProvider.instanceV2 primary network interface #29720の issue によるものです。

このUserDataのコマンドは、AWSドキュメントのNATインスタンスの部分に記載があります。そこには下記注意があります。

プライマリネットワークインターフェイスが eth0 でない場合は、eth0 を、
前のステップでメモしたプライマリネットワークインターフェイスに置き換えます。

NatProvider.instanceV2として、AL2023に対応されたときのコードは下記です。

    const userData = UserData.forLinux();
    userData.addCommands(
      'yum install iptables-services -y',
      'systemctl enable iptables',
      'systemctl start iptables',
      'echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/custom-ip-forwarding.conf',
      'sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf',
      'sudo /sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE',
      'sudo /sbin/iptables -F FORWARD',
      'sudo service iptables save',
    );

これが、#29720 の後には次のように変更されていました。「プライマリネットワークインターフェイスが eth0 でない場合」が考慮されています。

  public static readonly DEFAULT_USER_DATA_COMMANDS = [
    'yum install iptables-services -y',
    'systemctl enable iptables',
    'systemctl start iptables',
    'echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/custom-ip-forwarding.conf',
    'sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf',
    "sudo /sbin/iptables -t nat -A POSTROUTING -o $(route | awk '/^default/{print $NF}') -j MASQUERADE",
    'sudo /sbin/iptables -F FORWARD',
    'sudo service iptables save',
  ];
:
:
    let userData = this.props.userData;
    if (!userData) {
      userData = UserData.forLinux();
      userData.addCommands(...NatInstanceProviderV2.DEFAULT_USER_DATA_COMMANDS);
    }

つまり、NATインスタンスを作成したときの環境では#29720が対応済みバージョンのCDKでしたが、今回使用した環境では#29720が対応されていないバージョンのCDKだったということで先祖返りしてしまいました。

まとめ

NatProvider.instanceは廃止されていますが、開発環境や個人環境などでコスト削減目的でNATインスタンスを利用したい場合は、NatProvider.instanceV2を利用することで簡単に作成できます。

ただし、CDKのバージョンに注意しないと、思わぬところでハマってしまいます。

// NATインスタンスを使うか、NATインスタンスを使わない(=NATゲートウェイを使うか)をパラメータで制御できます。
// NATインスタンスに使用するインスタンスタイプがパラメータで指定できます。
    const natInstance = ec2.NatProvider.instanceV2({
      instanceType: props.natInstanceType
                    ? new ec2.InstanceType(props.natInstanceType)
                    : ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO),
      machineImage: ec2.MachineImage.latestAmazonLinux2023({
        edition: ec2.AmazonLinuxEdition.STANDARD,
        cpuType: ec2.AmazonLinuxCpuType.ARM_64, //X86_64
      }),
      defaultAllowedTraffic: ec2.NatTrafficDirection.OUTBOUND_ONLY,
    });

    // VPC
    this.vpc = new ec2.Vpc(this, 'MyVpc', {
      vpcName: [id, 'VPC', accountId].join('/') ,
      ipAddresses: ec2.IpAddresses.cidr(props.vpcCIDR),
      maxAzs: props.maxAzs ?? 2, // 2 Availability Zones
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'PublicSubnet',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'PrivateSubnet',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        },
      ],
      natGateways: props.natgateways ?? 0;,
      natGatewaySubnets: {subnetType: ec2.SubnetType.PUBLIC},
      natGatewayProvider: props.isNatInstance ? natInstance : ec2.NatProvider.gateway(),
      // Security Hub EC2.2
      // https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/ec2-controls.html#ec2-2
      restrictDefaultSecurityGroup: true, 
    });

補足・参考文献

aws-ec2: NatProvider.instanceV2 primary network interface #29720

上記が反映されたバージョンはこちら→v2.137.0

AWSドキュメント: DEFAULT_USER_DATA_COMMANDS

GitHub: aws-cdk/packages/aws-cdk-lib/aws-ec2/lib/nat.ts > DEFAULT_USER_DATA_COMMANDS

Discussion