AWS CDKでEC2を作るときにIAM Roleを共用したい!なんてことはありませんか? 僕はしょっちゅうあります。
そんなわけで、本ブログではAWS CDK(v2)でEC2を作るときにIAM Role(Instance Profile)を共用する方法を紹介します。
前提条件
AWS CDKはv2を利用しています。
$ cdk --version
2.79.1 (build 2e7f8b7)
CDKでIAM Roleを共用したEC2を作ってみる
まずは、さくっとTypeScriptのcdkでIAM Roleを共用するEC2を作ってみます。
次のプログラムを保存して。
cdk.ts
import {Construct} from 'constructs';
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";
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'MyVpc', {
natGateways: 0,
maxAzs: 1,
subnetConfiguration: [
{
name: 'PublicSubnet',
subnetType: ec2.SubnetType.PUBLIC,
},
],
});
const ec2Role = new iam.Role(this, 'MyRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
description: 'My EC2 role',
});
const instance1 = new ec2.Instance(this, 'MyInstance1', {
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3A,
ec2.InstanceSize.NANO
),
machineImage: ec2.MachineImage.latestAmazonLinux2(),
role: ec2Role,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC,
},
});
const instance2 = new ec2.Instance(this, 'MyInstance2', {
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3A,
ec2.InstanceSize.NANO
),
machineImage: ec2.MachineImage.latestAmazonLinux2(),
role: ec2Role,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC,
},
});
}
}
const app = new cdk.App();
new SampleStack(app, "SampleStack");
次のコマンドを実行してデプロイすると、EC2が2つ構築できます。簡単ですね。
$ cdk deploy SampleStack
CDKでIAM Roleを共用したEC2を作るときの問題点
EC2は2つ構築できたのでこのままでも良いのですが、ちょっと気持ち悪い点があります。
デプロイする前に、cdk diff SampleStack
してみるとわかるのですが、InstanceProfileがEC2ごとに2つ作られていることがわかります。
$ cdk diff SampleStack
Resources
[+] AWS::EC2::VPC MyVpc MyVpcF9F0CA6F
[+] AWS::EC2::Subnet MyVpc/PublicSubnetSubnet1/Subnet MyVpcPublicSubnetSubnet1Subnet60D1320D
[+] AWS::EC2::RouteTable MyVpc/PublicSubnetSubnet1/RouteTable MyVpcPublicSubnetSubnet1RouteTable00654ADB
[+] AWS::EC2::SubnetRouteTableAssociation MyVpc/PublicSubnetSubnet1/RouteTableAssociation MyVpcPublicSubnetSubnet1RouteTableAssociation2CCE9CDC
[+] AWS::EC2::Route MyVpc/PublicSubnetSubnet1/DefaultRoute MyVpcPublicSubnetSubnet1DefaultRoute2D379878
[+] AWS::EC2::InternetGateway MyVpc/IGW MyVpcIGW5C4A4F63
[+] AWS::EC2::VPCGatewayAttachment MyVpc/VPCGW MyVpcVPCGW488ACE0D
[+] AWS::IAM::Role MyRole MyRoleF48FFE04
[+] AWS::EC2::SecurityGroup MyInstance1/InstanceSecurityGroup MyInstance1InstanceSecurityGroupA4C82073
[+] AWS::IAM::InstanceProfile MyInstance1/InstanceProfile MyInstance1InstanceProfile2553CF04
[+] AWS::EC2::Instance MyInstance1 MyInstance12C7E210C
[+] AWS::EC2::SecurityGroup MyInstance2/InstanceSecurityGroup MyInstance2InstanceSecurityGroup4974D913
[+] AWS::IAM::InstanceProfile MyInstance2/InstanceProfile MyInstance2InstanceProfile948D2D68
[+] AWS::EC2::Instance MyInstance2 MyInstance2C26007AF
普段意識することはあまりないのですが、EC2はIAM Roleを直接利用しているわけではなく、InstanceProfileを利用しています。 詳しくはこちらのブログを参照してください。
せっかくIAM Roleを共用したのだから、InstanceProfileも共用したいと思いません??だって、普段EC2ごとにInstanceProfile作らないですよね??
InstanceProfileは無料なんだから、複数作られたところで気にしなくても良いじゃん。 という、意見は無視して先に進みます。
長い前フリでしたが、InstanceProfileも共用するようにCDKを書いてみる方法を共有します。
CDKでInstance Profileを共用したEC2を作ってみる
cdkのInstanceクラスには、InstanceProfileを指定するプロパティがありません。
InstanceProfileは自動で作られてしまうので、 tryRemoveChild
を使ってInstanceProfileの情報を消した後、
addPropertyOverride
を使って、InstanceProfileを書き換えて対応しています。
また、インスタンスを作る前に、InstanceProfileを作っておく必要があるので、 addDependency
を使って、CloudFormationの依存関係を作っています。
cdk.ts
import {Construct} from 'constructs';
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";
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'MyVpc', {
natGateways: 0,
maxAzs: 1,
subnetConfiguration: [
{
name: 'PublicSubnet',
subnetType: ec2.SubnetType.PUBLIC,
},
],
});
const ec2Role = new iam.Role(this, 'MyRole', {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
description: 'My EC2 role',
});
const cfnInstanceProfile = new iam.CfnInstanceProfile(this, 'MyInstanceProfile', {
roles: [
ec2Role.roleName,
],
});
const instance1 = new ec2.Instance(this, 'MyInstance1', {
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3A,
ec2.InstanceSize.NANO
),
machineImage: ec2.MachineImage.latestAmazonLinux2(),
role: ec2Role,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC,
},
});
instance1.node.tryRemoveChild('InstanceProfile');
const cfnInstance1 = instance1.node.tryFindChild('Resource') as ec2.CfnInstance;
cfnInstance1.addDependency(cfnInstanceProfile);
cfnInstance1.addPropertyOverride('IamInstanceProfile', cfnInstanceProfile.ref);
const instance2 = new ec2.Instance(this, 'MyInstance2', {
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3A,
ec2.InstanceSize.NANO
),
machineImage: ec2.MachineImage.latestAmazonLinux2(),
role: ec2Role,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC,
},
});
instance2.node.tryRemoveChild('InstanceProfile');
const cfnInstance2 = instance2.node.tryFindChild('Resource') as ec2.CfnInstance;
cfnInstance2.addDependency(cfnInstanceProfile);
cfnInstance2.addPropertyOverride('IamInstanceProfile', cfnInstanceProfile.ref);
}
}
const app = new cdk.App();
new SampleStack(app, "SampleStack");
これで、InstanceProfileを共用したEC2が作れました。
できれば、CDKのAPIを使って、もっと簡単に書けるようになるといいですね。