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
);
}
}