CDKを使ってSSM(Session Manager)で接続できるEC2を作成する方法について。
参考
EC2 に Session Manager で接続の方法については以下を参照。
基本
SSM Agent
SSM(Session Manager)での接続は、対象のEC2インスタンスでSSM Agentが動作している必要があります。 ここではSSM Agentが最初から組み込まれているAmazon Linux 2023を使うことで、SSM Agentの設定について省略します。
例 Amazon Linux 2023 で SSM Agent のステータス確認
$ sudo systemctl status amazon-ssm-agent ● amazon-ssm-agent.service - amazon-ssm-agent Loaded: loaded (/usr/lib/systemd/system/amazon-ssm-agent.service; enabled; preset: enabled) Active: active (running) since Tue 2025-01-14 12:38:26 UTC; 4min 39s ago Main PID: 1604 (amazon-ssm-agen) Tasks: 43 (limit: 1058) Memory: 99.5M CPU: 1.454s CGroup: /system.slice/amazon-ssm-agent.service ├─1604 /usr/bin/amazon-ssm-agent ├─1657 /usr/bin/ssm-agent-worker ├─1762 /usr/bin/ssm-session-worker user-abc123defghijklmnopqrstuvw ├─1783 sh ├─2010 /usr/bin/ssm-session-worker awscli-test-123456789abcdefghijklmnopq └─2024 sh
IAMロール
SSM(Session Manager)での接続は、対象のEC2インスタンスにSSM用のロール AmazonSSMManagedInstanceCore を割り当てる必要があります。 CDKの場合、以下のようなロールを作成してEC2に設定する必要があります。
const thisRole = new iam.Role(this, "Ec2RoleId", {
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName(
"AmazonSSMManagedInstanceCore"
),
],
});
パブリックIP + Public subnet
CDKでEC2を作成する場合、パブリックIP(IPv4)を割り当てることができます。 パブリックIPが割り当てられたEC2を Public subnetに配置すると、NatGateway無しでSSM(セッションマネージャ)による接続ができるようになります。
必要条件
- パブリックIP (CDKでEC2を作成する時、associatePublicIpAddress を無しにするか true にする)
- EC2 を Public subnet に配置
例
import * as cdk from "aws-cdk-lib"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as iam from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; export class CdkEc2Stack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const stackName = "CdkEc2_"; // VPC const thisVpc = new ec2.Vpc(this, `${stackName}Vpc`, { natGateways: 0, maxAzs: 1, ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"), subnetConfiguration: [ { name: `${stackName}publicSubnet`, subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, ], }); // セキュリティグループ const thisSecurityGroup = new ec2.SecurityGroup( this, `${stackName}SecurityGroup`, { vpc: thisVpc, description: "Allow all outbound", allowAllOutbound: true, } ); // ロール const thisRole = new iam.Role(this, `${stackName}Role`, { assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( "AmazonSSMManagedInstanceCore" ), ], }); // EC2インスタンス const instance = new ec2.Instance(this, `${stackName}AmazonLinux2023`, { vpc: thisVpc, vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, role: thisRole, securityGroup: thisSecurityGroup, instanceName: `${stackName}AmazonLinux2023`, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.MICRO ), machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023, cpuType: ec2.AmazonLinuxCpuType.X86_64, }), // 秘密鍵 keyPair: ec2.KeyPair.fromKeyPairName(this, "test_keypair", "test_keypair"), // SSMを有効 ssmSessionPermissions: true, // パブリックIPv4アドレスを付与する。デフォルトの設定なので無くて良い // associatePublicIpAddress: true, }); } }
NatGateway + Private subnet
SSM(セッションマネージャ)の接続をNatGateway経由で行うEC2を作成する方法。 EC2を配置する Private subnet で、 subnetType を PRIVATE_WITH_EGRESS にすると、 NatGateway経由でインターネットにアクセスできるようになります。 この場合、EC2にパブリックIPは必要ありません。
import * as cdk from "aws-cdk-lib"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as iam from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; export class CdkEc2Stack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const stackName = "CdkEc2_"; // VPC const thisVpc = new ec2.Vpc(this, `${stackName}Vpc`, { natGateways: 1, maxAzs: 1, ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"), subnetConfiguration: [ { name: `${stackName}PublicSubnet`, subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, { name: `${stackName}PrivateSubnet`, subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24, }, ], }); const thisSecurityGroup = new ec2.SecurityGroup( this, `${stackName}SecurityGroup`, { vpc: thisVpc, description: "Allow all outbound", allowAllOutbound: true, } ); const thisRole = new iam.Role(this, `${stackName}Role`, { assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( "AmazonSSMManagedInstanceCore" ), ], }); const instance = new ec2.Instance(this, `${stackName}AmazonLinux2023`, { vpc: thisVpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, role: thisRole, securityGroup: thisSecurityGroup, instanceName: `${stackName}AmazonLinux2023`, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.MICRO ), machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023, cpuType: ec2.AmazonLinuxCpuType.X86_64, }), // 秘密鍵 keyPair: ec2.KeyPair.fromKeyPairName(this, "jp_test", "jp_test_keypair"), // SSMを有効 ssmSessionPermissions: true, // パブリックIPv4アドレスを付与しない associatePublicIpAddress: false, }); } }
SSM用VPCエンドポイント + Private subnet
SSM用VPCエンドポイントを作成すると、それらのエンドポイント経由で Private Subnet のEC2にSSMでの接続ができます。 この場合、EC2にパブリックIPは必要ありません。
注意 EC2は外部への経路がないためインターネットにアクセスできません。 パッケージ等のインストールをしたい場合、S3へのアクセス設定を追加してS3経由で行うなどの手間が必要です。
必要条件
- SSM用VPCエンドポイン
- EC2からSSMエンドポイントへのHTTPS (ポート 443)を許可
参考
- インターネットにアクセスしなくても、 VPC エンドポイントを作成して、Systems Manager でプライベート EC2 インスタンスを管理するにはどうすればよいですか?
- Session Manager の前提条件を満たす
- AWS CDKでセッションマネージャーから接続できる踏み台サーバーを定義する
SSMでVPCエンドポイントを使用する場合、最低でも以下の3つのサービスのエンドポイントが必要です。 さらにEC2から下記エンドポイントへ HTTPS (ポート 443) アウトバウンドを許可する必要があります:
・ec2messages.region.amazonaws.com ・ssm.region.amazonaws.com ・smmessages.region.amazonaws.com
3個の必須以外に、以下のようなサービスのエンドポイントが必要な場合があります。
・ec2.InterfaceVpcEndpointAwsService.KMS ・ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS ・ec2.GatewayVpcEndpointAwsService.S3
例
import * as cdk from "aws-cdk-lib"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as iam from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; export class CdkEc2Stack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const stackName = "CdkEc2_"; // VPC const thisVpc = new ec2.Vpc(this, `${stackName}Vpc`, { natGateways: 0, maxAzs: 1, ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"), subnetConfiguration: [ { name: `${stackName}PrivateSubnet`, subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 24, }, ], }); // EC2のセキュリティグループ const ec2SecurityGroup = new ec2.SecurityGroup( this, `${stackName}Ec2SecurityGroup`, { vpc: thisVpc, description: "Allow all outbound", allowAllOutbound: true, } ); const thisRole = new iam.Role(this, `${stackName}Role`, { assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( "AmazonSSMManagedInstanceCore" ), ], }); // EC2インスタンス const instance = new ec2.Instance(this, `${stackName}AmazonLinux2023`, { vpc: thisVpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, role: thisRole, securityGroup: ec2SecurityGroup, instanceName: `${stackName}AmazonLinux2023`, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.MICRO ), machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023, cpuType: ec2.AmazonLinuxCpuType.X86_64, }), // 秘密鍵 keyPair: ec2.KeyPair.fromKeyPairName(this, "test_keypair", "test_keypair"), // SSMを有効 ssmSessionPermissions: true, // パブリックIPv4アドレスを付与しない associatePublicIpAddress: false, }); // SSMエンドポイント用セキュリティグループ const ssmEpSecurityGroup = new ec2.SecurityGroup( this, `${stackName}SsmEpSecurityGroup`, { vpc: thisVpc, description: "Allow all outbound", allowAllOutbound: true, } ); // SSMエンドポイントの設定 thisVpc.addInterfaceEndpoint("SSMEndpoint", { service: ec2.InterfaceVpcEndpointAwsService.SSM, securityGroups: [ssmEpSecurityGroup], }); thisVpc.addInterfaceEndpoint("SSMMessagesEndpoint", { service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES, securityGroups: [ssmEpSecurityGroup], }); thisVpc.addInterfaceEndpoint("EC2MessagesEndpoint", { service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES, securityGroups: [ssmEpSecurityGroup], }); // EC2からSSMエンドポイントへの HTTPS (ポート 443)を許可 ssmEpSecurityGroup.addIngressRule( ec2.Peer.securityGroupId(ec2SecurityGroup.securityGroupId), ec2.Port.tcp(443), "https from EC2", false ); } }