先週1週間、リフレッシュ休暇(当社の年次休暇)を頂き、宮古島旅行に行ってきました。
天気にも恵まれ、海もとてもキレイだったので、テンションが上がりっぱなしだった菅野です。 
最近、AWSのリソースを作成する際に、CDKを利用することが増えてきました。
AWSのリソース定義としては、CloudFormationがありますが、CDKは、以下のようなメリットがあります。
今回はAWS CDKに不慣れな私が生成AIを利用してどこまで簡単に作成できるか試してみます。
最初に、ChatGPTを利用して、CDKのコード出力を行おうとしたのですが、CDKの最新バージョン(v2)は2021年12月にリリースされており、2021年9月までの情報を学習しているChatGPTのGPT-4ではCDKv2のコードを生成できません。
そこで今回は、Bing Chat Enterprise を利用して、CDKv2のコード生成を行ってみました。
AWS Cloud Development Kit の略です。
Pyton,TypeScript等様々な言語でAWSのリソースをソースコードとして管理できるフレームワークです。
ソースコードで作成したリソースはAWS CDK Cliを用いることでデプロイすることができます。
docs.aws.amazon.com
作成するAWSリソースの説明
本記事ではPetShopのペットを管理する簡単なWebアプリケーションを例として以下のようなリソースを作成してもらいます。
- S3の静的ホスティングでWebクライアントを作成。
- APIGateway、Lambda、DynamoDBを用いたAPIを作成。
- 上記Webクライアント、API双方の前段にCloudFrontを配置する。

Bing Chat EnterpriseでCDKのリソースを作成
では早速Bing Chat EnterpriseにCDKコードを作成してもらいましょう。

(中略)

それぞれ以下のコードを出力してくれました。
CDKコード
from aws_cdk import core
from aws_cdk import (
aws_s3 as s3,
aws_s3_deployment as s3_deploy,
aws_lambda as _lambda,
aws_apigateway as api_gw,
aws_dynamodb as ddb,
aws_cloudfront as cf
)
class PetShopCdkStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
website_bucket = s3.Bucket(self, "WebsiteBucket",
website_index_document="index.html",
removal_policy=core.RemovalPolicy.DESTROY
)
pet_table = ddb.Table(self, "PetTable",
partition_key=ddb.Attribute(name="id", type=ddb.AttributeType.STRING),
removal_policy=core.RemovalPolicy.DESTROY
)
pet_lambda = _lambda.Function(self, "PetLambda",
runtime=_lambda.Runtime.PYTHON_3_9,
handler="handler.handler",
code=_lambda.Code.asset("lambda"),
environment={
"TABLE_NAME": pet_table.table_name
}
)
pet_table.grant_read_write_data(pet_lambda)
pet_api = api_gw.LambdaRestApi(self, "PetApi",
handler=pet_lambda,
default_cors_preflight_options=api_gw.CorsOptions(
allow_origins=["*"]
)
)
distribution = cf.CloudFrontWebDistribution(self, "Distribution",
origin_configs=[
cf.SourceConfiguration(
s3_origin_source=cf.S3OriginConfig(
s3_bucket_source=website_bucket
),
behaviors=[cf.Behavior(is_default_behavior=True)]
),
cf.SourceConfiguration(
custom_origin_source=cf.CustomOriginConfig(
domain_name=pet_api.domain_name,
origin_protocol_policy=cf.OriginProtocolPolicy.HTTPS_ONLY
),
behaviors=[cf.Behavior(path_pattern="/api/*")]
)
]
)
静的ホスティングクライアント
<html>
<head>
<title>Pet Shop</title>
</head>
<body>
<h1>Welcome to the Pet Shop!</h1>
<button onclick="fetchData()">Fetch Data</button>
<div id="data"></div>
<script>
function fetchData() {
fetch('/api')
.then(response => response.json())
.then(data => document.getElementById('data').textContent = JSON.stringify(data));
}
</script>
</body>
</html>
ちょっとWebUIがシンプルすぎるのと、S3、APIGatewayはCloudFront側からのみのリクエストを受け付けるようになっていないため、変更を加えてもらいます。
また、Lambda関数も出力してもらえなかったので追加で出力するよう依頼していきます。

(中略)
(中略)

(中略)

(中略)

実装が中途半端なままファイル出力したり、変更点のみの部分的な出力であったので、何度か応答を繰り返して更新していってもらいました。
最終的な結果は以下のようになりました。
CDKコード
from constructs import Construct
from aws_cdk.core import Stack, RemovalPolicy, CfnOutput
from aws_cdk.aws_s3 import Bucket, BlockPublicAccess
from aws_cdk.aws_s3_deployment import BucketDeployment, Source
from aws_cdk.aws_lambda import Function, Runtime, Code
from aws_cdk.aws_apigateway import LambdaRestApi, CorsOptions, MethodOptions, RestApi
from aws_cdk.aws_dynamodb import Table, Attribute, AttributeType
from aws_cdk.aws_cloudfront import Distribution, SourceConfiguration, S3OriginConfig, Behavior, CustomOriginConfig, OriginAccessIdentity
class PetshopCdkStack(Stack):
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
pet_table = Table(self, "PetTable",
partition_key=Attribute(name="id", type=AttributeType.STRING),
removal_policy=RemovalPolicy.DESTROY
)
pet_lambda = Function(self, "PetHandler",
runtime=Runtime.PYTHON_3_8,
handler="handler.handler",
code=Code.from_asset("lambda"),
environment={
'PET_TABLE_NAME': pet_table.table_name
}
)
pet_table.grant_read_write_data(pet_lambda)
pet_api = RestApi(self, "Endpoint",
default_cors_preflight_options=CorsOptions(
allow_origins=["*"],
allow_methods=["GET", "POST", "PUT", "DELETE"]
),
handler=pet_lambda,
proxy=False
)
pet_resource = pet_api.root.add_resource('api')
website_bucket = Bucket(self, "WebsiteBucket",
website_index_document="index.html",
auto_delete_objects=True,
removal_policy=RemovalPolicy.DESTROY,
block_public_access=BlockPublicAccess.BLOCK_ALL
)
BucketDeployment(self, "DeployWebsite",
sources=[Source.asset('./website')],
destination_bucket=website_bucket,
distribution_paths=["/*"]
)
oai = OriginAccessIdentity(self, "OAI")
website_bucket.grant_read(oai)
distribution = Distribution(self, "Distribution",
default_behavior_options={
"origin": S3Origin(website_bucket),
"origin_access_identity": oai,
},
additional_behaviors={
"/api/*": {
"origin": HttpOrigin(f"{pet_api.rest_api_id}.execute-api.{self.region}.amazonaws.com"),
"origin_protocol_policy": OriginProtocolPolicy.HTTPS_ONLY,
}
}
)
CfnOutput(self, "CloudFrontURL",
value=distribution.domain_name,
description="The CloudFront distribution URL"
)
OAIを用いてアクセス制限をかけるようにしてくれています。
静的ホスティングWebクライアント
<html>
<head>
<title>Pet Shop</title>
<script>
const API_ENDPOINT = "/api";
async function createPet() {
const name = document.getElementById('name').value;
const type = document.getElementById('type').value;
const data = { id: Date.now().toString(), name, type };
await fetch(API_ENDPOINT, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
loadPets();
}
async function updatePet() {
const id = document.getElementById('updateId').value;
const name = document.getElementById('updateName').value;
const type = document.getElementById('updateType').value;
const data = { id, name, type };
await fetch(API_ENDPOINT, {
method: 'PUT',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
loadPets();
}
async function deletePet() {
const id = document.getElementById('deleteId').value;
await fetch(`${API_ENDPOINT}?id=${id}`, {
method: 'DELETE'
});
loadPets();
}
async function loadPets() {
const petsList = document.getElementById('petsList');
petsList.innerHTML = '';
const pets = await getAllPets();
pets.forEach(pet => {
const petItem = document.createElement('li');
petItem.textContent = `${pet.name} (${pet.type}) - ID: ${pet.id}`;
petsList.appendChild(petItem);
});
}
async function getAllPets() {
const response = await fetch(API_ENDPOINT);
return response.json();
}
window.onload = () => {
loadPets();
};
</script>
</head>
<body>
<h1>Welcome to Pet Shop</h1>
<h2>Add a new pet</h2>
<div>
<label>Name: <input type="text" id="name"></label>
<label>Type: <input type="text" id="type"></label>
<button onclick="createPet()">Add Pet</button>
</div>
<h2>Update a pet</h2>
<div>
<label>ID: <input type="text" id="updateId"></label>
<label>Name: <input type="text" id="updateName"></label>
<label>Type: <input type="text" id="updateType"></label>
<button onclick="updatePet()">Update Pet</button>
</div>
<h2>Delete a pet</h2>
<div>
<label>ID: <input type="text" id="deleteId"></label>
<button onclick="deletePet()">Delete Pet</button>
</div>
<h2>List of pets</h2>
<ul id="petsList"></ul>
</body>
</html>
CRUD処理も各フォームから出力するようになっています。
Lambdaハンドラ
import json
import boto3
import os
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
def handler(event, context):
try:
if event['httpMethod'] == 'GET':
pet_id = event['queryStringParameters'].get('id')
if pet_id:
response = table.get_item(Key={'id': pet_id})
return {
'statusCode': 200,
'body': json.dumps(response.get('Item'))
}
else:
response = table.scan()
return {
'statusCode': 200,
'body': json.dumps(response['Items'])
}
elif event['httpMethod'] == 'POST':
body = json.loads(event['body'])
table.put_item(Item=body)
return {
'statusCode': 200,
'body': json.dumps({'message': 'Pet created successfully!'})
}
elif event['httpMethod'] == 'PUT':
body = json.loads(event['body'])
table.update_item(Key={'id': body['id']}, UpdateExpression="set info=:r", ExpressionAttributeValues={':r': body['info']})
return {
'statusCode': 200,
'body': json.dumps({'message': 'Pet updated successfully!'})
}
elif event['httpMethod'] == 'DELETE':
pet_id = event['queryStringParameters'].get('id')
if pet_id:
table.delete_item(Key={'id': pet_id})
return {
'statusCode': 200,
'body': json.dumps({'message': 'Pet deleted successfully!'})
}
else:
return {
'statusCode': 400,
'body': json.dumps({'message': 'Pet ID is required!'})
}
else:
return {
'statusCode': 400,
'body': json.dumps({'message': 'Invalid request method!'})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'message': str(e)})
}
手動での修正
Bing Chat Enterprise で生成したのみで作成したリソースファイルでは一部エラーが出てデプロイできない部分が出たり、疎通できない部分があったので手動で修正をかけてあげましょう。
CDKコード
from constructs import Construct
from aws_cdk import Stack, RemovalPolicy, CfnOutput, Duration
from aws_cdk.aws_s3 import Bucket, BlockPublicAccess
from aws_cdk.aws_s3_deployment import BucketDeployment, Source
from aws_cdk.aws_lambda import Function, Runtime, Code
from aws_cdk.aws_apigateway import LambdaRestApi
from aws_cdk.aws_dynamodb import Table, Attribute, AttributeType
from aws_cdk.aws_cloudfront import CloudFrontWebDistribution, SourceConfiguration, S3OriginConfig, Behavior, CustomOriginConfig, OriginAccessIdentity, CloudFrontAllowedMethods, OriginProtocolPolicy
class PetshopCdkStack(Stack):
def __init__(self, scope: Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
pet_table = Table(self, "PetTable",
partition_key=Attribute(name="id", type=AttributeType.STRING),
removal_policy=RemovalPolicy.DESTROY
)
pet_lambda = Function(self, "PetHandler",
runtime=Runtime.PYTHON_3_8,
handler="handler.handler",
code=Code.from_asset("lambda"),
environment={
'PET_TABLE_NAME': pet_table.table_name
}
)
pet_table.grant_read_write_data(pet_lambda)
pet_api = LambdaRestApi(self, "Endpoint",
handler=pet_lambda,
proxy=False
)
api_resource = pet_api.root.add_resource("api")
pet_resource = api_resource.add_resource("pet")
pet_id_resource = pet_resource.add_resource("{id}")
pet_resource.add_method('GET')
pet_resource.add_method('POST')
pet_resource.add_method('PUT')
pet_resource.add_method('DELETE')
pet_id_resource.add_method('GET')
website_bucket = Bucket(self, "WebsiteBucket",
website_index_document="index.html",
auto_delete_objects=True,
removal_policy=RemovalPolicy.DESTROY,
block_public_access=BlockPublicAccess.BLOCK_ALL
)
oai = OriginAccessIdentity(self, "OAI")
website_bucket.grant_read(oai)
distribution = CloudFrontWebDistribution(self, "Distribution",
origin_configs=[
SourceConfiguration(
s3_origin_source=S3OriginConfig(
s3_bucket_source=website_bucket,
origin_access_identity=oai
),
behaviors=[Behavior(is_default_behavior=True)]
),
SourceConfiguration(
custom_origin_source=CustomOriginConfig(
origin_path='/prod',
domain_name=f"{pet_api.rest_api_id}.execute-api.{self.region}.amazonaws.com",
origin_protocol_policy=OriginProtocolPolicy.HTTPS_ONLY
),
behaviors=[Behavior(path_pattern="api/*", allowed_methods=CloudFrontAllowedMethods.ALL,default_ttl=Duration.seconds(0),max_ttl=Duration.seconds(0),min_ttl=Duration.seconds(0))]
)
]
)
BucketDeployment(self, "DeployWebsite",
sources=[Source.asset('./website')],
destination_bucket=website_bucket,
distribution_paths=["/*"],
distribution=distribution
)
CfnOutput(self, "CloudFrontURL",
value=distribution.distribution_domain_name,
description="The CloudFront distribution URL"
)
Lambdaハンドラ
- GetのメソッドについてQueryStrinParameterではなくPathParameterを利用するように修正
- 判定条件を修正してPathParameterが定義されていない際に参照エラーが出ないように変更
import json
import boto3
import os
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['PET_TABLE_NAME'])
def handler(event, context):
try:
if event['httpMethod'] == 'GET':
if event['pathParameters'] and event['pathParameters'].get('id'):
response = table.get_item(Key={'id': event['pathParameters'].get('id')})
return {
'statusCode': 200,
'body': json.dumps(response.get('Item'))
}
else:
response = table.scan()
return {
'statusCode': 200,
'body': json.dumps(response['Items'])
}
elif event['httpMethod'] == 'POST':
body = json.loads(event['body'])
table.put_item(Item=body)
return {
'statusCode': 200,
'body': json.dumps({'message': 'Pet created successfully!'})
}
elif event['httpMethod'] == 'PUT':
body = json.loads(event['body'])
table.update_item(Key={'id': body['id']}, UpdateExpression="set info=:r", ExpressionAttributeValues={':r': body['info']})
return {
'statusCode': 200,
'body': json.dumps({'message': 'Pet updated successfully!'})
}
elif event['httpMethod'] == 'DELETE':
pet_id = event['queryStringParameters'].get('id')
if pet_id:
table.delete_item(Key={'id': pet_id})
return {
'statusCode': 200,
'body': json.dumps({'message': 'Pet deleted successfully!'})
}
else:
return {
'statusCode': 400,
'body': json.dumps({'message': 'Pet ID is required!'})
}
else:
return {
'statusCode': 400,
'body': json.dumps({'message': 'Invalid request method!'})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'message': str(e)})
}
デプロイ、Webページへのアクセス
以下のコマンドでデプロイしましょう。
cdk bootstrap
cdk deploy
以下のようにコンソール出力されたらデプロイ成功です。

PetshopCdkStack.CloudFrontURLに表示されているURLへアクセスしてみましょう。


必要情報を入力して、Add Petボタンを押すと
無事追加したペットが表示されました。
まとめ
Bing Chat Enterpriseを活用してAWS CDKのコードを作成、デプロイしてAWSのリソースを作成しました。
他のPythonコードとは異なり、1回のプロンプト入力で動作するコードを出力することは難しく、手動での修正や細かい調整が必要になりそうですが、ある程度の形は作成できるのでCDKに不慣れなエンジニアでも作業時間は大幅に短縮できそうです。
今後もChatGPTの可能性に迫っていこうと思います。
Acroquest Technologyでは、キャリア採用を行っています。
- ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
- Elasticsearch等を使ったデータ収集/分析/可視化
- マイクロサービス、DevOps、最新のOSSやクラウドサービスを利用する開発プロジェクト
- 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
www.wantedly.com