AWS CDK(v2)で複数EC2を作ってIAM Role(Instance Profile)を共用する

AWS CDK(v2)でEC2を作るときにIAM Role(Instance Profile)を共用する方法を紹介します。TypeScriptのサンプルプログラム付き。
2023.05.30

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を使って、もっと簡単に書けるようになるといいですね。

参考