【実例】これで解決!AWS CDKのバージョン違いで起こったデプロイ障害とその対策
Contents
概要
AWS CDK を使ったデプロイで EC2 インスタンスに接続できなくなったり、プライベートサブネットからインターネットに出られなくなった経験はありませんか?
今回は AWS CDK のバージョンが原因で発生した障害の事例を紹介し、その解決方法を解説します。
結論と教訓
単純に AWS CDK のバージョンが低い環境(v2.130.0)で cdk deploy
を実行したことによる先祖返り障害です。CDK のバージョンアップを実施してから再デプロイすることで解決できました。
今回のことで得られた教訓は次のとおりです。
-
どんな環境でも、差分の確認は必ずするべき
-
いつもと違う作業環境でデプロイを実施するときは特に、CDK のバージョン確認は怠らないことが重要
トラブルの概要
- S3 バケットを追加する必要があったのでバケット作成のコードを追加
- プライベートな検証環境で
cdk deploy
コマンドを使用して追加リソースをデプロイ - EC2 インスタンスにセッションマネージャーで接続不可
- トラブル調査
- 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 からインターネットに出られるかを確認しましたが、結果は 到達不能
でした。
正しく接続できる場合は、EC2 インスタンス →NAT インスタンス → インターネットゲートウェイとなります。
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