AWS CDK(v2)で複数EC2を作ってIAM Role(Instance Profile)を共用する
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を作ってみます。
次のプログラムを保存して。
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の依存関係を作っています。
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を使って、もっと簡単に書けるようになるといいですね。