Amazon EC2 インスタンス(Ubuntu)を AWS CDK で構築してみた

2024.04.11

こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。

今回は、Amazon EC2Ubuntu のインスタンスを AWS CDK で構築する機会があったので、方法をご紹介します。

やってみた

AMI の確認

まず Ubuntu の AMI(Amazon machine image)の ID を確認します。EC2 インスタンスを作成する際には、この AMI ID が必要です。

マネジメントコンソールから AMI ID を簡単に確認できるメニューが AMI Catalog です。今回は Ubuntu を使用したいので、Ubuntu で検索すると、[Quickstart AMIs]によく使われる Ubuntu の AMI 一覧が表示されます。

今回は「Ubuntu Server 22.04 LTS (HVM), SSD Volume Type (64-bit (x86))」の AMI を使用してみます。AMI ID を確認して控えておきます。

CDK での構築

先ほど控えた AMI ID を使って、CDK で EC2 インスタンスを構築します。

次のようなスタック定義のコード(TypeScript)で CDK アプリケーションを実装します。必須と成る VPC とセキュリティグループを作成し、それらを使用して EC2 インスタンスを作成します。Ubuntu のインスタンスの場合は MachineImage クラスの genericLinux() を使用する点がポイントです。

lib/cdk-sample-stack.ts

import { aws_ec2, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const vpcCidr = '10.100.0.0/16';
    const instanceAmiId = 'ami-0eba6c58b7918d3a1'; // カタログで確認した AMI ID を指定
    const region = 'ap-northeast-1';

    const vpc = new aws_ec2.Vpc(this, 'Vpc', {
      ipAddresses: aws_ec2.IpAddresses.cidr(vpcCidr),
    });

    const securityGroup = new aws_ec2.SecurityGroup(this, 'SecurityGroup', {
      vpc,
    });

    new aws_ec2.Instance(this, 'Instance', {
      vpc,
      securityGroup,
      instanceType: aws_ec2.InstanceType.of(
        aws_ec2.InstanceClass.T2,
        aws_ec2.InstanceSize.MICRO
      ),
      machineImage: aws_ec2.MachineImage.genericLinux({
        [region]: instanceAmiId,
      }),
      requireImdsv2: true,
    });
  }
}

CDK デプロイすると、Ubuntu の EC2 インスタンスを作成できました。

パブリックサブネットに配置する

先ほどの定義で作成したインスタンスは既定の配置としてプライベートサブネットに作成されていたため、パブリック IP アドレスはなし、プライベート IP アドレスのみが割り当てられていました。

次のように subnetConfiguration プロパティを設定することで、パブリックサブネットなどの任意のサブネットに配置できます。

lib/cdk-sample-stack.ts

import { aws_ec2, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const vpcCidr = '10.100.0.0/16';
    const instanceAmiId = 'ami-0eba6c58b7918d3a1'; // カタログで確認した AMI ID を指定
    const region = 'ap-northeast-1';

    const vpc = new aws_ec2.Vpc(this, 'Vpc2', {
      ipAddresses: aws_ec2.IpAddresses.cidr(vpcCidr),
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'public',
          subnetType: aws_ec2.SubnetType.PUBLIC,
        },
      ],
    });

    // 中略
  }
}

上記をデプロイすると、インスタンスがパブリックサブネットに配置され、パブリックおよびプライベートアドレスの両方が利用可能になりました。これで外部からの通信をこのパブリック ID アドレスで受け付けることができます。

サブネットを再作成しない場合

先ほどのコードで VPC のコンストラクト ID を Vpc2 に変更してデプロイしていましたが、これは VPC およびそれに含まれるサブネットを再作成するためです。VPC を再作成しない場合は、下記のように The CIDR '10.100.1.0/24' conflicts with another subnet というサブネット重複エラーが発生します。

$ npm run deploy

> cdk_sample_app@0.1.0 deploy
> cdk deploy --require-approval never --method=direct


✨  Synthesis time: 4.1s

CdkSampleStack:  start: Building 3edfe7c4b1461149cfa5d06fbeb05e33e27c764f7c98108c9ecfc95ea3215064:current_account-current_region
CdkSampleStack:  success: Built 3edfe7c4b1461149cfa5d06fbeb05e33e27c764f7c98108c9ecfc95ea3215064:current_account-current_region
CdkSampleStack:  start: Publishing 3edfe7c4b1461149cfa5d06fbeb05e33e27c764f7c98108c9ecfc95ea3215064:current_account-current_region
CdkSampleStack:  success: Published 3edfe7c4b1461149cfa5d06fbeb05e33e27c764f7c98108c9ecfc95ea3215064:current_account-current_region
CdkSampleStack: deploying... [1/1]
CdkSampleStack: updating stack...
3:36:19 AM | CREATE_FAILED        | AWS::EC2::Subnet                      | Vpc/publicSubnet2/Subnet
Resource handler returned message: "The CIDR '10.100.1.0/24' conflicts with another subnet (Service: Ec2, Status Code: 400, Request ID: ecf6c7c5-78fa-41ad-b132-836e6ee9cb74)" (RequestToken: 961d0a70-df3a
-d9dd-9246-4e1d73c5d372, HandlerErrorCode: AlreadyExists)
3:36:19 AM | CREATE_FAILED        | AWS::EC2::Subnet                      | Vpc/publicSubnet1/Subnet
Resource handler returned message: "The CIDR '10.100.0.0/24' conflicts with another subnet (Service: Ec2, Status Code: 400, Request ID: 23b431cd-ce7a-4996-ba2a-2dbc62c94530)" (RequestToken: 0665b32a-93d4
-d221-65e2-505b533ff2bd, HandlerErrorCode: AlreadyExists)
3:36:20 AM | UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack            | CdkSampleStack
The following resource(s) failed to create: [VpcpublicSubnet2SubnetE34B022A, VpcpublicSubnet2RouteTableC5A6DF77, VpcpublicSubnet1RouteTable15C15F8E, VpcpublicSubnet1Subnet2BB74ED7].
3:36:25 AM | UPDATE_ROLLBACK_COMP | AWS::CloudFormation::Stack            | CdkSampleStack

1つの AZ に配置する

ここまでの実装では、ap-northeast-1a および ap-northeast-1c の 2 つの AZ にサブネットが作成されています。

マルチ AZ とする必要がない場合はこのサブネットの最大数を指定することが可能です。

次のように maxAzs プロパティを設定することで、1 を指定した場合は、最大 1 つの AZ にのみサブネットが作成されるようになります。

lib/cdk-sample-stack.ts

import { aws_ec2, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const vpcCidr = '10.100.0.0/16';
    const instanceAmiId = 'ami-0eba6c58b7918d3a1'; // カタログで確認した AMI ID を指定
    const region = 'ap-northeast-1';

    const vpc = new aws_ec2.Vpc(this, 'Vpc2', {
      ipAddresses: aws_ec2.IpAddresses.cidr(vpcCidr),
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'public',
          subnetType: aws_ec2.SubnetType.PUBLIC,
        },
      ],
      maxAzs: 1,
    });

    // 中略
  }
}

上記をデプロイすると、サブネットおよび AZ が 1 つになりました。

タグを設定する

リソース管理やコスト配分のために、インスタンスにタグを設定したい場合は、Tags.of() メソッドを使用します。

lib/cdk-sample-stack.ts

import { aws_ec2, Stack, Tags } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const vpcCidr = '10.100.0.0/16';
    const instanceAmiId = 'ami-0eba6c58b7918d3a1'; // カタログで確認した AMI ID を指定
    const region = 'ap-northeast-1';

    const vpc = new aws_ec2.Vpc(this, 'Vpc2', {
      ipAddresses: aws_ec2.IpAddresses.cidr(vpcCidr),
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'public',
          subnetType: aws_ec2.SubnetType.PUBLIC,
        },
      ],
      maxAzs: 1,
    });

    const securityGroup = new aws_ec2.SecurityGroup(this, 'SecurityGroup', {
      vpc,
    });

    const instance = new aws_ec2.Instance(this, 'Instance', {
      vpc,
      securityGroup,
      instanceType: aws_ec2.InstanceType.of(
        aws_ec2.InstanceClass.T2,
        aws_ec2.InstanceSize.MICRO
      ),
      machineImage: aws_ec2.MachineImage.genericLinux({
        [region]: instanceAmiId,
      }),
      requireImdsv2: true,
    });

    Tags.of(instance).add('hogeKey', 'fugaValue');
  }
}

上記をデプロイすると、インスタンスにタグが設定されました。

おわりに

Amazon EC2 で Ubuntu のインスタンスを AWS CDK で構築する機会があったので、方法をご紹介しました。

CDK の L2 コンストラクトを利用すると、サブネットなどのリソースが暗黙的に作成されるので、コード量は少なくなり便利ですが、意図しないリソースの作成や設定が行われないように挙動を把握しておくことが重要です。

以上