Open4
EC2でcdkとかの開発ボックスを用意するとか

ここではとりあえずspotインスタンスを使う
lib/cdk-dev-box-ec2-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3assets from 'aws-cdk-lib/aws-s3-assets';
import { SpotInstance } from 'cdk-ec2-spot-simple';
import * as fs from 'fs';
import * as path from 'path';
export class CdkDevBoxEc2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 設定ファイルを読み込み
const configPath = path.join(__dirname, '../config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// カスタムVPCを作成
const vpc = new ec2.Vpc(this, 'CustomVPC', {
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
}
],
natGateways: 0
});
// キーペア参照を作成
const keyPair = ec2.KeyPair.fromKeyPairName(this, 'KeyPair', config.keyPairName);
// セキュリティグループを作成
const securityGroup = new ec2.SecurityGroup(this, 'SpotInstanceSecurityGroup', {
vpc,
description: 'Security group for CDK development box spot instance',
allowAllOutbound: true
});
// SSH アクセスを許可
securityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(22),
'Allow SSH access'
);
// ユーザーデータスクリプトを作成
const userData = ec2.UserData.forLinux();
userData.addCommands(
'apt-get update -y',
'apt-get install -y curl unzip git jq python3',
// AWS CLIをインストール
'curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"',
'unzip awscliv2.zip',
'sudo ./aws/install',
// Node.jsとnpmをインストール
'curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -',
'apt-get install -y nodejs',
// AWS CDKをグローバルにインストール
'npm install -g aws-cdk',
// OpenAI Codexをインストール
'npm install -g @openai/codex'
);
// 永続的なスポットインスタンスを作成
const spotInstance = new SpotInstance(this, 'PersistentSpotInstance', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC
},
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.SMALL),
machineImage: ec2.MachineImage.lookup({
name: 'debian-12-arm64-*',
owners: [config.debianAmiOwner],
filters: {
'architecture': ['arm64'],
'virtualization-type': ['hvm'],
'root-device-type': ['ebs']
}
}),
keyPair,
securityGroup,
userData,
spotOptions: {
interruptionBehavior: ec2.SpotInstanceInterruption.STOP,
requestType: ec2.SpotRequestType.PERSISTENT
}
});
// インスタンスロールにCloudFormation権限を追加
const role = spotInstance.role;
role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: [
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackResources",
"cloudformation:GetTemplate",
"cloudformation:ListStacks",
"cloudformation:ListStackResources"
],
resources: ["*"]
}));
// インスタンスIDとパブリックIPを出力
new cdk.CfnOutput(this, 'SpotInstanceId', {
value: spotInstance.instanceId,
description: 'CDK development box spot instance ID'
});
new cdk.CfnOutput(this, 'SpotInstancePublicIP', {
value: spotInstance.instancePublicIp,
description: 'CDK development box spot instance public IP'
});
}
}
bin/app.ts
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { CdkDevBoxEc2Stack } from '../lib/cdk-dev-box-ec2-stack';
import * as fs from 'fs';
import * as path from 'path';
const app = new cdk.App();
// 設定ファイルから環境情報を読み込み
const configPath = path.join(__dirname, '..', 'config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// ここでデプロイ先を固定(ENVに無ければ設定ファイルの既定値を使う)
const env: cdk.Environment = {
account: process.env.CDK_DEFAULT_ACCOUNT ?? config.account,
region: process.env.CDK_DEFAULT_REGION ?? config.region,
};
new CdkDevBoxEc2Stack(app, 'CdkDevBoxEc2Stack', { env });
config.json
{
"account": "your_aws_account",
"region": "ap-northeast-1",
"debianAmiOwner": "136693071363",
"keyPairName": "your_key_pair"
}
デプロイ
cdk deploy

ポイント
権限
// インスタンスロールにCloudFormation権限を追加
const role = spotInstance.role;
role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: [
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackResources",
"cloudformation:GetTemplate",
"cloudformation:ListStacks",
"cloudformation:ListStackResources"
],
resources: ["*"]
}));
ここによりcdk diff
に必要な権限がEC2に与えられる
ユーザーデーター
const userData = ec2.UserData.forLinux();
userData.addCommands(
'apt-get update -y',
'apt-get install -y curl unzip git jq python3',
// AWS CLIをインストール
'curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"',
'unzip awscliv2.zip',
'sudo ./aws/install',
// Node.jsとnpmをインストール
'curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -',
'apt-get install -y nodejs',
// AWS CDKをグローバルにインストール
'npm install -g aws-cdk',
// OpenAI Codexをインストール
'npm install -g @openai/codex'
);
ここで以下のツールがインストールされている
AWS関連ツール
- AWS CLI v2 - AWSサービスの操作
- AWS CDK CLI - CDKプロジェクトの管理
- SSM Agent - Systems Manager Session Managerでの接続サポート
ユーティリティツール
- jq - JSONデータの処理・整形
- curl - HTTPクライアント
- unzip - アーカイブ展開ツール
- @openai/codex - OpenAI Codex CLI
- aws-cdk - CDK CLI
ここをユーザーごとの環境にセットアップしていくのだが、userdataが長くなってくるとこの方式では辛いのでscriptに切り出してs3に置いた方がいいかも
lib/cdk-dev-box-ec2-stack.ts
+ import * as s3assets from 'aws-cdk-lib/aws-s3-assets';
@@ -46,23 +46,21 @@ export class CdkDevBoxEc2Stack extends cdk.Stack {
'Allow SSH access'
);
- // ユーザーデータスクリプトを作成
+ // S3アセットとしてuserdataスクリプトを作成
+ const userdataAsset = new s3assets.Asset(this, 'UserdataAsset', {
+ path: path.join(__dirname, '../assets/userdata.sh'),
+ });
+
+ // ユーザーデータを作成してS3からスクリプトをダウンロード・実行
const userData = ec2.UserData.forLinux();
- userData.addCommands(
- 'apt-get update -y',
- 'apt-get install -y curl unzip git jq python3',
- // AWS CLIをインストール
- 'curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"',
- 'unzip awscliv2.zip',
- 'sudo ./aws/install',
- // Node.jsとnpmをインストール
- 'curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -',
- 'apt-get install -y nodejs',
- // AWS CDKをグローバルにインストール
- 'npm install -g aws-cdk',
- // OpenAI Codexをインストール
- 'npm install -g @openai/codex'
- );
+ userData.addS3DownloadCommand({
+ bucket: userdataAsset.bucket,
+ bucketKey: userdataAsset.s3ObjectKey,
+ localFile: '/tmp/userdata.sh',
+ });
+ userData.addExecuteFileCommand({
+ filePath: '/tmp/userdata.sh',
+ });
// 永続的なスポットインスタンスを作成
const spotInstance = new SpotInstance(this, 'PersistentSpotInstance', {
assets/userdata.sh
#!/bin/bash
set -e
# システムの更新
apt-get update -y
apt-get install -y curl unzip git jq python3
# AWS CLI をインストール
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Node.js と npm をインストール
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
apt-get install -y nodejs
# AWS CDK をグローバルにインストール
npm install -g aws-cdk
# OpenAI Codex をインストール
npm install -g @openai/codex
# ログ出力
echo "Setup completed successfully" >> /var/log/userdata.log

userdataをコネる
結局開発環境はどれだけuserdataをキメるかってところでもあるけどキメすぎると逆にわけがわからんくなることもあり...
たとえばclaudecodeも導入し、cdkのデモプロジェクトをinitする場合
assets/userdata.sh
@@ -3,7 +3,7 @@ set -e
# システムの更新
apt-get update -y
-apt-get install -y curl unzip git jq python3
+apt-get install -y curl unzip git jq python3 ripgrep
# AWS CLI をインストール
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
@@ -20,5 +20,14 @@ npm install -g aws-cdk
# OpenAI Codex をインストール
npm install -g @openai/codex
-# ログ出力
-echo "Setup completed successfully" >> /var/log/userdata.log
\ No newline at end of file
+# Claude Code をインストール
+npm install -g @anthropic-ai/claude-code
+
+
+# CDKテストプロジェクト作成用のディレクトリを作成
+mkdir -p /home/admin/cdk-test
+cd /home/admin/cdk-test
+cdk init app --language typescript
+chown admin /home/admin -R
起動てuserdataが完了したら
cd cdk-test
cdk deploy
などを引いてみればよい
問答無用再生成するには
cdk destroy --force && cdk deploy --require-approval never

あとはうまいことvscodeとかで繋ぐ