AWS CDKを使った基本的な環境を構築したくなったので、記事を投稿してみました。
また、退職して少しの間ニートになったので、CDK関連の記事を今後投稿してみようと思ってます。
作成する環境構成
以下のような環境を構築するためのCDKソースコードを作成します。
基本的な構成なのですが、CDKでコーディングすると楽しいのでおすすめです。
- CDKバージョン: v1.18.0
- TypeScript: v3.7.3
AWS CDKのインストール
sudo npm install -g aws-cdk
cdk ---version
1.18.0 (build bc924bc)
mkdir mini-cdk
cd mini-cdk
cdk init app --language=typescript
npm install @aws-cdk/aws-ec2 @aws-cdk/aws-s3 @aws-cdk/aws-s3-deployment @aws-cdk/aws-iam
# 作成するスタッックのファイル
touch lib/mini-vpc-stack.ts
touch lib/mini-ec2-stack.ts
touch lib/mini-s3-stack.ts
# 都合上削除
rm test/mini-cdk.test.ts
VPCスタックの作成
VPCスタックを作成します。
注意点として、利用するAZの数とNatGatewayに気を付けた方がよいです。natGateways: 0
として明示しないと、AZごとにNatGatewayが作成されてしまいます。個人で使う分には利用コストが高くなってしまうので注意が必要です。
import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");
export class MiniVpcStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPCを作成する
this.vpc = this.createVpc();
// GatewayEndpointをセットアップする
this.setupGatewayEndpoint();
// InterfaceEndpointをセットアップする
this.setupInterfaceEndpoint();
}
private createVpc() {
return new ec2.Vpc(this, "MiniVpc", {
cidr: "172.17.0.0/16",
natGateways: 0, // NatGatewayはお金がかかるので使わない
maxAzs: 1, // Availability Zoneの数
subnetConfiguration: [
{ name: "Public", cidrMask: 20, subnetType: ec2.SubnetType.PUBLIC },
{ name: "Isolated", cidrMask: 20, subnetType: ec2.SubnetType.ISOLATED }
]
});
}
private setupGatewayEndpoint() {
// IsolatedのサブネットからS3に直接アクセスできるようにVPCエンドポイントを利用する
this.vpc.addGatewayEndpoint("S3EndpointForIsolatedSubnet", {
service: ec2.GatewayVpcEndpointAwsService.S3,
subnets: [{ subnetType: ec2.SubnetType.ISOLATED }]
});
// IsolatedのサブネットからDynamoDBに直接アクセスできるようにVPCエンドポイントを利用する
// 今回は利用しません。
this.vpc.addGatewayEndpoint("DynamoDbEndpointForIsolatedSubnet", {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
subnets: [{ subnetType: ec2.SubnetType.ISOLATED }]
});
}
private setupInterfaceEndpoint() {
// InterafceEndpoint用のセキュリティグループ
const interfaceEndpointSecurityGroup = new ec2.SecurityGroup(
this,
"InterfaceEndpointSecurityGroup",
{
securityGroupName: "InterfaceEndpointSecurityGroup",
vpc: this.vpc
}
);
interfaceEndpointSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443)
);
// Isolated SubnetからECRに接続するためのエンドポイント
// 今回は利用しません。
this.vpc.addInterfaceEndpoint("ECRInterfaceEndpoint", {
securityGroups: [interfaceEndpointSecurityGroup],
service: ec2.InterfaceVpcEndpointAwsService.ECR,
subnets: {
subnetType: ec2.SubnetType.ISOLATED
}
});
this.vpc.addInterfaceEndpoint("ECR_DockerInterfaceEndpoint", {
securityGroups: [interfaceEndpointSecurityGroup],
service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER
});
}
}
setupInterfaceEndpoint
メソッドでECRにアクセスするためのエンドポイントを作成していますが、今回は関係ありません。実装の参考情報として解釈してください。
EC2スタックの作成
Publicサブネットに1つ、Isolatedサブネットに1つインスタンスを作成するスタックを作成します。
IsolatedサブネットのEC2インスタンスからS3バケットにアクセスできるようにロールをアタッチしています。
import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");
import iam = require("@aws-cdk/aws-iam");
interface MiniEc2StackProps extends cdk.StackProps {
readonly vpc: ec2.Vpc; // MiniVpcStackのプロパティをvpcを受け取りたいので設定している
readonly keyName: string; // キーペア
}
export class MiniEc2Stack extends cdk.Stack {
private readonly vpc: ec2.Vpc;
private readonly keyName: string;
private publicEc2SecurityGroup: ec2.SecurityGroup;
private isolatedEc2SecurityGroup: ec2.SecurityGroup;
private isolatedEc2Role: iam.Role;
constructor(scope: cdk.Construct, id: string, props: MiniEc2StackProps) {
super(scope, id, props);
this.vpc = props.vpc;
this.keyName = props.keyName;
// PublicSubnetのEC2インスタンスをセットアップする
this.setupPublicEc2Instance();
// IsolatedSubnetのEC2インスタンスをセットアップする
this.setupIsolatedEc2Role();
this.setupIsolatedEc2Instance();
}
// PublicSubnetにEC2インスタンスを作成する
private setupPublicEc2Instance() {
this.publicEc2SecurityGroup = this.createPublicEc2InstanceSecurityGroup();
const userData = ec2.UserData.forLinux();
userData.addCommands("sudo yum update -y");
new ec2.Instance(this, "MiniPublicEc2Instance", {
vpc: this.vpc,
instanceName: "MiniPublicEc2Instance",
keyName: this.keyName,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.NANO
),
machineImage: new ec2.AmazonLinuxImage({
edition: ec2.AmazonLinuxEdition.STANDARD,
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
}),
vpcSubnets: {
subnetType: ec2.SubnetType.PUBLIC
},
userData,
securityGroup: this.publicEc2SecurityGroup
});
}
// IsolatedSubnetにEC2インスタンスを作成する
private setupIsolatedEc2Instance() {
this.isolatedEc2SecurityGroup = this.createIsolatedEc2InstanceSecurityGroup();
const userData = ec2.UserData.forLinux();
userData.addCommands("sudo yum update -y");
new ec2.Instance(this, "MiniIsolatedEc2Instance", {
instanceName: "MiniIsolatedEc2Instance",
vpc: this.vpc,
keyName: this.keyName,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.NANO
),
machineImage: new ec2.AmazonLinuxImage({
edition: ec2.AmazonLinuxEdition.STANDARD,
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
}),
vpcSubnets: {
subnetType: ec2.SubnetType.ISOLATED
},
securityGroup: this.isolatedEc2SecurityGroup,
role: this.isolatedEc2Role
});
}
private setupIsolatedEc2Role() {
this.isolatedEc2Role = new iam.Role(this, "IsolatedEc2InstanceRole", {
roleName: "IsolatedEc2InstanceRole",
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com")
});
// ロールにS3へのアクセスするポリシーをアタッチする
this.isolatedEc2Role.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket",
"s3:ListAllMyBuckets"
],
resources: ["*"]
})
);
}
// PublicサブネットのEC2インスタンスのセキュリティグループを作成する
private createPublicEc2InstanceSecurityGroup() {
const sg = new ec2.SecurityGroup(this, "MiniPublicEc2InstanceSg", {
vpc: this.vpc,
securityGroupName: "MiniPublicEc2InstanceSg"
});
sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22));
return sg;
}
// IsolatedサブネットのEC2インスタンスのセキュリティグループを作成する
private createIsolatedEc2InstanceSecurityGroup() {
const sg = new ec2.SecurityGroup(this, "MiniIsolatedEc2InstanceSg", {
vpc: this.vpc,
securityGroupName: "MiniIsolatedEc2InstanceSg"
});
sg.addIngressRule(this.publicEc2SecurityGroup, ec2.Port.allTraffic());
return sg;
}
}
S3バケットスタックの作成
ここでは、バケットを2つ作成することにしましたw。
1つはホスティング用のバケット、もうひとつは内部用のバケットです。
データをあとで入れるのがめんどくさかったので、都合上、@aws-s3-deployment
を使うことにしました。
以下のコマンドで適当なファイルを初期生成しています。
mkdir -p data/hosting
mkdir -p data/internal
echo "Hello MiniCdk!" > data/hosting/index.html
echo "This is Internal Bucket!" > data/internal/test.txt
S3バケットスタックを作成するソースコードは以下の通りです。
import cdk = require("@aws-cdk/core");
import s3 = require("@aws-cdk/aws-s3");
import s3Deploy = require("@aws-cdk/aws-s3-deployment");
export class MiniS3BucketStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.setupInternalBucket();
this.setupHostingBucket();
}
// 内部で使うバケットを作成する
setupInternalBucket() {
const bucket = new s3.Bucket(this, "InternalBucket");
new s3Deploy.BucketDeployment(this, "DeployInternalBucket", {
sources: [s3Deploy.Source.asset("./data/internal")],
destinationBucket: bucket
});
}
// ホスティング用に使うバケットを作成する
setupHostingBucket() {
const bucket = new s3.Bucket(this, "HostingBucket", {
websiteIndexDocument: "index.html",
publicReadAccess: true,
versioned: true,
removalPolicy: cdk.RemovalPolicy.DESTROY
});
new s3Deploy.BucketDeployment(this, "DeployHostingBucket", {
sources: [s3Deploy.Source.asset("./data/hosting")],
destinationBucket: bucket
});
}
}
エンドポイントの作成
ここまで作成したスタックのエンドポイントとなるファイルを作成します。
# !/usr/bin/env node
import "source-map-support/register";
import cdk = require("@aws-cdk/core");
import { MiniVpcStack } from "../lib/mini-vpc-stack";
import { MiniEc2Stack } from "../lib/mini-ec2-stack";
import { MiniS3BucketStack } from "../lib/mini-s3-stack";
main();
function main() {
const app = new cdk.App();
// EC2インスタンスにアクセスするためのキーペアを指定する
// マネジメントコンソールから事前にキーペアを作成しておいてください。
const keyName = app.node.tryGetContext("keyName");
// VPCスタック
const miniVpcStack = new MiniVpcStack(app, "MiniVpcStack");
// EC2スタック
new MiniEc2Stack(app, "MiniEc2Stack", {
vpc: miniVpcStack.vpc,
keyName
});
// S3バケットスタック
new MiniS3BucketStack(app, "MiniS3BucketStack");
app.synth();
}
キーペアをデプロイ時に指定するので、マネジメントコンソールかCLIから作成しておいてください。
ここでは、mini-cdk
という名前で作成しました。

ビルドとデプロイ
ビルドしてjs
ファイルを生成します。
npm run build
デプロイ対象のスタック一覧を確認します。
cdk list
MiniS3BucketStack
MiniVpcStack
MiniEc2Stack
ここまでで準備が整ったので、スタックをまとめてデプロイして環境を構築します。
export AWS_DEFAULT_REGION=ap-northeast-1
cdk -c keyName=mini-cdk deploy *Stack --require-approval never --profile dev
-
-c
オプションでコンテキストパラメータとしてキーペア名を指定しています。 -
--require-approval never
は[y/n]を手動で承認するのが手間だった(CodeBuildとかを使った自動化などを想定)ので付けています。 -
--profile
は各自のものに読み替えてください。
構築したあとの確認作業
IsolatedサブネットのEC2インスタンスから確認します。
$ aws s3 ls --region ap-northeast-1
2019-12-15 12:03:53 minis3bucketstack-hostingbucket5dac2127-4r11qt9ir0vr
2019-12-15 12:03:27 minis3bucketstack-internalbucketda21a343-1hy903e2t0zu7
# 内部用に作ったバケット
$ aws s3 ls s3://minis3bucketstack-internalbucketda21a343-1hy903e2t0zu7/ --region ap-northeast-1
2019-12-15 12:04:52 24 test.txt
# ホスティング用に作ったバケット
$ aws s3 ls s3://minis3bucketstack-hostingbucket5dac2127-4r11qt9ir0vr/ --region ap-northeast-1
2019-12-15 12:04:52 15 index.html
他にもホスティングURLでバケットにアクセスできるか等確認がありますが、割愛させていただきます。
以上、CDKを使ったVPC/EC2/S3の構築でした。
次回は、Fargateについて書いてみようと思います。