AWS Cloud9 環境を AWS CDK で構築してみた

2024.01.03

こんにちは、CX 事業本部 Delivery 部の若槻です。

AWS Samples などにある Getting Started を試す際に、デモ環境として AWS Cloud9 環境を作成することがあります。

この AWS Cloud9 環境を毎回手作業で作成および削除する手間を省くために、AWS Cloud9 環境を CDK で構築する方法を確認してみました。

試してみた

AWS Cloud9 には L2 Construct として利用できる Alpha モジュールがあるのでこちらを利用します。

CDK コード

CDK スタックのコードは以下の通りです。

lib/cdk-sample-stack.ts

import { Construct } from 'constructs';
import { aws_ec2, Stack, StackProps, CfnOutput, Duration } from 'aws-cdk-lib';
import * as aws_cloud9_alpha from '@aws-cdk/aws-cloud9-alpha';

interface CdkSampleStackProps extends StackProps {
  assumeRoleName: string;
}

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

    const { assumeRoleName } = props;
    const accounId = this.account;

    // 既存の VPC を取得
    const defaultVpc = aws_ec2.Vpc.fromLookup(this, 'DefaultVPC', {
      isDefault: true,
    });

    // Cloud9 環境のオーナーとして使用する IAM Assume された Role
    const envOwner = aws_cloud9_alpha.Owner.assumedRole(
      accounId,
      assumeRoleName
    );

    // Cloud9 環境を作成
    const c9envAlpha = new aws_cloud9_alpha.Ec2Environment(
      this,
      'Cloud9EnvironmentAlpha',
      {
        instanceType: new aws_ec2.InstanceType('t2.large'), // 既定値は t2.micro
        imageId: aws_cloud9_alpha.ImageId.AMAZON_LINUX_2023,
        vpc: defaultVpc,
        connectionType: aws_cloud9_alpha.ConnectionType.CONNECT_SSM, // 既定値は CONNECT_SSH
        owner: envOwner, // 既定では CloudFormation デプロイ時の実行権限となる
        automaticStop: Duration.minutes(30), // 既定では自動停止は無効となる
      }
    );

    // Cloud9 環境の IDE の接続 URL を出力
    new CfnOutput(this, 'IdeUrl', { value: c9envAlpha.ideUrl });
  }
}

CDK アプリケーションのコードは以下の通りです。

bin/cdk_sample_app.ts

import { App } from 'aws-cdk-lib';
import { CdkSampleStack } from '../lib/cdk-sample-stack';

const app = new App();

new CdkSampleStack(app, 'CdkSampleStack', {
  env: {
    account: 'XXXXXXXXXXXX',
    region: 'ap-northeast-1',
  },
  assumeRoleName: 'cm-wakatsuki.ryuta/cm-wakatsuki.ryuta',
});

  • 前述した Owner.assumedRole() で使用する Assume された Role の名前を指定しています。
  • env の指定は Vpc.fromLookup を使用する際に必要となります。
    • 未指定の場合は Error: Cannot retrieve value from context provider vpc-provider since account/region are not specified at the stack level. Configure "env" with an account and region when you define your stack.See https://docs.aws.amazon.com/cdk/latest/guide/environments.html for more details. というエラーとなります。

CDK デプロイする

CDK デプロイにより作成されたスタックです。AWS::Cloud9::EnvironmentEC2 リソースのみが作成されています。

実体となる EC2 インスタンスとセキュリティグループは、自動的に作成された別のスタックで作成されます。これは AWS::Cloud9::EnvironmentEC2 リソースの挙動です。

最初のスタックの Outputs から Cloud9 環境の IDE に接続を開きます。

Cloud9 環境の IDE に接続できました。

EC2 インスタンス内でのスクリプト実行自動化したい

Cloud9 環境の構成により作成された EC2 インスタンス内でスクリプトを自動的に実行したい場合があります。インスタンス内でスクリプトを実行するタイミングとしては、インスタンス作成(起動)時と作成(起動)後の 2 つが考えられます。

インスタンス作成時

インスタンス作成時の場合は、下記のようにユーザーデータを使用することになります。

先程確認したように使用する EC2 インスタンスの注入はサポートされておらず、つまり CDK からは隠蔽されているため、ユーザーデータを指定することは難しいです。

インスタンス作成後の場合

インスタンス作成後の場合は、下記の trigger モジュールを使用して Trigger Handler から作成後のインスタンスに対して AWS Systems Manager Run Command を通じてスクリプトを実行する方法が考えられます。

AWS Systems Manager Run Command でコマンドを実行する場合は、対象のインスタンスの ID が必要です。しかし Ec2Environment Construct からは EC2 インスタンス ID を取得することはできません。取得できるのは Cloud9 環境の ID のみです。よってやり方としては TriggerHandler に Cloud9 環境の ID を引数として渡し、それを元にインスタンス ID を取得することになります。

ボリュームサイズの拡張をする場合

私が Cloud9 環境作成時に最も自動化したかったのは、下記の EC2 インスタンスのボリュームサイズ拡張のスクリプト実行です。

上記で紹介されているスクリプトでは下記の処理が行われます。

  • EC2 インスタンス ID の取得
  • EBS ボリューム ID の取得
  • EBS ボリュームの更新(ディスク拡張)
  • EC2 インスタンス上でのパーティションテーブルの上書き

EC2 インスタンスや EBS ボリュームの読み取りや書き込みを行うのですが、具体的な操作対象のリソースは Lambda 実行内で取得するため、事前に幅広い権限の実行ロールを Lambda 関数に付与しておく必要があります。

これを自動化したくはあるのですが、実装が複雑になりそうなので別の機会に挑戦したいと思います。

参考:CfnEnvironmentEC2Props 型定義

参考までに、node_modules に生成された Ec2EnvironmentProps の型定義です。

node_modules/@aws-cdk/aws-cloud9-alpha/lib/environment.d.ts

/**
 * Properties for Ec2Environment
 */
export interface Ec2EnvironmentProps {
    /**
     * Owner of the environment.
     *
     * The owner has full control of the environment and can invite additional members.
     *
     * @default - The identity that CloudFormation executes under will be the owner
     */
    readonly owner?: Owner;
    /**
     * The type of instance to connect to the environment.
     *
     * @default - t2.micro
     */
    readonly instanceType?: ec2.InstanceType;
    /**
     * The subnetSelection of the VPC that AWS Cloud9 will use to communicate with
     * the Amazon EC2 instance.
     *
     * @default - all public subnets of the VPC are selected.
     */
    readonly subnetSelection?: ec2.SubnetSelection;
    /**
     * The VPC that AWS Cloud9 will use to communicate with the Amazon Elastic Compute Cloud (Amazon EC2) instance.
     *
     */
    readonly vpc: ec2.IVpc;
    /**
     * Name of the environment
     *
     * @default - automatically generated name
     */
    readonly ec2EnvironmentName?: string;
    /**
     * Description of the environment
     *
     * @default - no description
     */
    readonly description?: string;
    /**
     * The AWS CodeCommit repository to be cloned
     *
     * @default - do not clone any repository
     */
    readonly clonedRepositories?: CloneRepository[];
    /**
     * The connection type used for connecting to an Amazon EC2 environment.
     *
     * Valid values are: CONNECT_SSH (default) and CONNECT_SSM (connected through AWS Systems Manager)
     *
     * @default - CONNECT_SSH
     */
    readonly connectionType?: ConnectionType;
    /**
     * The image ID used for creating an Amazon EC2 environment.
     *
     */
    readonly imageId: ImageId;
    /**
     * The number of minutes until the running instance is shut down after the
     * environment was last used.
     *
     * Setting a value of 0 means the instance will never be automatically shut down."
     *
     * @default - The instance will not be shut down automatically.
     */
    readonly automaticStop?: cdk.Duration;
}

おわりに

AWS Cloud9 環境を AWS CDK で構築する方法を確認してみました。

L2 Construct としては最低限の機能は整っている印象です。マネジメントコンソールからであればできる既存の EC2 インスタンスの利用がサポートされれば、より便利になると思います。現在は Alpha モジュールであるので今後のアップデートに期待したいです。

以上