【AWS】CloudFormationで爆速環境構築🌀
概要
会社で AWS を触ることになり、基本から学んでいこうと思ったため備忘録として記事を書き始めました。
今回は AWS の CloudFormation を使用して 3 種類の異なるシステムをさくっと構築してみようと思います。
もし理解が違うよというところ等ありましたら優しく教えて頂けると幸いです 🙇♀️
今回作る物
よくある長々とした手順書を手動で順々に実行して環境構築するのではなく、環境構築用の設定ファイル設置とクリックのみで環境構築を行います。
例えば、EC2 上に React アプリケーションを立ち上げるために下の様なながーい手順書を実行して環境構築しなければいけないところ…。
以下設定ファイル設置と環境構築実行のボタンクリックのみで爆速で環境構築を行える様になります。
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/23
Tags:
- Key: Name
Value: react-vpc
Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
CidrBlock: 10.0.0.0/24
Tags:
- Key: Name
Value: public-react-subnet
AvailabilityZone: ap-northeast-1a
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: react-internet-gateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId:
Ref: InternetGateway
VpcId:
Ref: VPC
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: public-react-route-table
VpcId:
Ref: VPC
InternetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet
RouteTableId:
Ref: RouteTable
KeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: react-server-key
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: AllowSSHAndHTTPSecurityGroup
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp: [自分のIPアドレス]
- IpProtocol: tcp
FromPort: "80"
ToPort: "80"
CidrIp: [自分のIPアドレス]
ElasticIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
EIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId:
Ref: EC2Instance
EIP:
Ref: ElasticIP
EC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: react-server
InstanceType: t2.micro
ImageId: ami-04beabd6a4fb6ab6f
KeyName:
Ref: KeyPair
SecurityGroupIds:
- Fn::GetAtt:
- SecurityGroup
- GroupId
SubnetId:
Ref: Subnet
UserData:
Fn::Base64: |
# nginxをインストールするスクリプト
sudo yum install -y nginx
sudo systemctl start nginx
# reactアプリケーションを作成するスクリプト
sudo yum install -y nodejs npm
npx -y create-react-app /home/ec2-user/sample-app
cd /home/ec2-user/sample-app
npm run build
# nginxにreactアプリケーションを載せるスクリプト
sudo rm -rf /usr/share/nginx/html/*
sudo cp -r /home/ec2-user/sample-app/build/* /usr/share/nginx/html/
※上記手順書の例として挙げているのは私が前に書いた記事です 😗
手動で環境構築してみるのも結構勉強になったのでよかったらご覧ください。
CloudFormation とは
以下 CloudFormation の説明です。
少し長くなるので、CloudFormation での環境構築方法だけ知りたい方は CloudFormation で環境構築省略してみるの章のみご覧ください 😌
CloudFormation とは
公式ドキュメント引用。
CloudFormation とは AWS のサービスによる環境構築を設定ファイルとボタン押下のみで行なってくれるサービスです。
設定ファイルはテンプレートと呼ばれ、上記の様に記述されます。
例えば、上記のテンプレートは EC2 上に React アプリケーションを立ち上げるためのテンプレートですがもし CloudFormation を使用しなかった場合 AWS のマネジメントコンソールで私が前に書いた記事の様に順々に手動でサービスを立ち上げなければなりません。
この様なことを毎回実行していると、下記の様なことが起こりえます。
- 一つ手順書の手順を飛ばしてしまっただけでサービスが立ち上げられなくなる
- 手動で実行しているので、同じ環境を 10 個作りたいといった要望が出てきた時に 10 回手動でサービスを立ち上げなくてはならなくなる
- 1 ヶ月前の環境に戻したくなった時再環境構築が記憶頼りになる
これらの問題の解決策となるのが CloudFormation です。
テンプレート一つ用意しておけば全く同じ環境がボタン押下のみで立ち上げられるため手順をとばすといったことがあり得ません。
また、複数回の環境構築における手間も軽減することができます。
更に、テンプレートを保存しておけば過去の環境構築方法を覚えていなくてもボタン押下のみで環境構築をすることができます。
テンプレートは上記の様に yml ファイルで記述することができます。
上記テンプレートを記載し CloudFormation に設置してボタンを押下すれば、一発で React アプリケーションが立ち上がる様になります。
CloudFormation で環境構築省略してみる
今回は以下三つのタイプの異なるシステムの基盤環境を CloudFormation でさくっと構築してみます。
- EC2 + Nginx + React で作成する Web アプリ
- React + Express + MySQL で作成する 3 層 Web システム
- Amplify + Nextjs + WebSocket API で作成するリアルタイムチャット
EC2 + Nginx + React で作成する Web アプリ
- システムの説明
上記がこれから作成する Web システムのアーキテクチャ図です。
EC2 上に Web サーバー提供用ソフトの Nginx をのせその中に React アプリケーションを格納し、Web 上に React アプリケーションを公開します。
手動でこのアプリを作成した記事はこちらです。
- 環境構築に使用する CloudFormation の yml ファイル
以下が上記システムの基盤環境構築を行うための yml ファイルです。
こちらを CloudFormation に設置して基盤環境の構築を行います。
一枚目のテンプレートのため、コメントで各プロパティの簡単な説明を記載しておきます。
react-server.yml
# Resources:配下に立ち上げるリソース(AWS上のサービス)を指定。
# VPC、Subnet、EC2インスタンス…等Reactアプリケーションを立ち上げる際に必要になるリソースを指定している。
Resources:
# このリソースに設定する名前を指定。
# VPCと指定しているが、どんな名前でも指定することが可能
VPC:
# Type:立ち上げるAWSのリソースを指定。
# ここではVPCを指定している。
Type: AWS::EC2::VPC
# Properties:リソースに対しての設定を指定。
# ここではVPCに紐づけるCidrBlockや名前タグ等を指定する。
Properties:
CidrBlock: 10.0.0.0/23
Tags:
- Key: Name
Value: react-vpc
Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
CidrBlock: 10.0.0.0/24
Tags:
- Key: Name
Value: public-react-subnet
AvailabilityZone: ap-northeast-1a
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: react-internet-gateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId:
Ref: InternetGateway
VpcId:
# Ref関数を使用することでテンプレート内の他リソースを引用して指定することが可能
Ref: VPC
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: public-react-route-table
VpcId:
Ref: VPC
InternetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet
RouteTableId:
Ref: RouteTable
KeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: react-server-key
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: AllowSSHAndHTTPSecurityGroup
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp: [自分のIPアドレス]
- IpProtocol: tcp
FromPort: "80"
ToPort: "80"
CidrIp: [自分のIPアドレス]
ElasticIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
EIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId:
Ref: EC2Instance
EIP:
Ref: ElasticIP
EC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: react-server
InstanceType: t2.micro
ImageId: ami-04beabd6a4fb6ab6f
KeyName:
Ref: KeyPair
SecurityGroupIds:
# GetAtt関数を使用することでテンプレート内の他リソース配下の変数を指定することが可能
- Fn::GetAtt:
- SecurityGroup
- GroupId
SubnetId:
Ref: Subnet
# UserDataプロパティーでEC2インスタンス立ち上げ時に実行するスクリプトを指定
UserData:
Fn::Base64: |
# nginxをインストールするスクリプト
sudo yum install -y nginx
sudo systemctl start nginx
# reactアプリケーションを作成するスクリプト
sudo yum install -y nodejs npm
npx -y create-react-app /home/ec2-user/sample-app
cd /home/ec2-user/sample-app
npm run build
# nginxにreactアプリケーションを載せるスクリプト
sudo rm -rf /usr/share/nginx/html/*
sudo cp -r /home/ec2-user/sample-app/build/* /usr/share/nginx/html/
- 環境構築してみる
上記 yml ファイルを使用してシステムの環境構築を行います。
AWS マネジメントコンソールで CloudFormation を開いてスタックの作成を押下してください。
テンプレートの指定 > テンプレートファイルのアップロード > ファイルの選択で上記で作成した yml ファイルを指定し、次へを押下してください。
スタック名に yml ファイル名などを設定し次へを押下してください。
この後も引き続き次へを押下し、最後は送信を押下してスタックを作成してください。
下記イベントタブで順次リソースが作成されているのがわかると思います。
最終的に作成されたスタックの下に CREATE_COMPLETE の文字がでたら環境構築終了です。
ほぼ設定ファイルの設置とボタン押下のみで環境構築ができました。
下記リソースタブで論理 ID が ElasticIP の物理 ID として指定されている IP アドレスをブラウザの検索バーに入力してください。
下記の様に React アプリケーションが表示されるはずです。
React + Express + MySQL で作成する 3 層 Web システム
- システムの説明
上記がこれから作成する 3 層 Web システムのアーキテクチャ図です。
ざっくり説明すると
- VPC 上にパブリックサブネットとプライベートサブネットを作成
- 各サブネットに Web サーバー、API サーバー、DB サーバーの役割を担う EC2 インスタンスを配置
- Web サーバーに Nginx と React アプリケーション、API サーバーに Node.js と Express、DB サーバーに MySQL を格納
- NAT ゲートウェイとインターネットゲートウェイを配置することで システムを Web 上からアクセス可能にする
という流れで作成されたシステムになっています。
手動でこのシステムを作成した記事はこちらです。
- 環境構築に使用する CloudFormation の yml ファイル
以下が上記システムの基盤環境構築を行うための yml ファイルです。
こちらを CloudFormation に設置して基盤環境の構築を行います。
web3-server.yml
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/23
Tags:
- Key: Name
Value: web3-vpc
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
CidrBlock: 10.0.0.0/24
Tags:
- Key: Name
Value: public-web3-subnet
AvailabilityZone: ap-northeast-1a
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
CidrBlock: 10.0.1.0/24
Tags:
- Key: Name
Value: private-web3-subnet
AvailabilityZone: ap-northeast-1a
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: web3-internet-gateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId:
Ref: InternetGateway
VpcId:
Ref: VPC
NatGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NatGatewayEIP
- AllocationId
SubnetId:
Ref: PublicSubnet
Tags:
- Key: Name
Value: web3-nat-gateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: public-web3-route-table
VpcId:
Ref: VPC
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet
RouteTableId:
Ref: PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: private-web3-route-table
VpcId:
Ref: VPC
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: NatGateway
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PrivateSubnet
RouteTableId:
Ref: PrivateRouteTable
WebKeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: web3-webserver-key
WebSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: AllowSSHAndHTTPSecurityGroup
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp: [自分のIPアドレス]
- IpProtocol: tcp
FromPort: "80"
ToPort: "80"
CidrIp: [自分のIPアドレス]
Tags:
- Key: Name
Value: web3-webserver-sg
WebElasticIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
WebEIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId:
Ref: WebEC2Instance
EIP:
Ref: WebElasticIP
WebEC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: web3-webserver
InstanceType: t2.micro
ImageId: ami-04beabd6a4fb6ab6f
KeyName:
Ref: WebKeyPair
SecurityGroupIds:
- Fn::GetAtt:
- WebSecurityGroup
- GroupId
SubnetId:
Ref: PublicSubnet
UserData:
Fn::Base64: |
# nginxとnodejsをインストールするスクリプト
sudo yum install -y nginx
sudo systemctl start nginx
sudo yum install -y nodejs npm
npx -y create-react-app /home/ec2-user/sample-app
cd /home/ec2-user/sample-app
npm install axios
APIKeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: web3-apiserver-key
APISecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: AllowSSHAndHTTPSecurityGroup
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp:
Fn::Join:
- ""
- - Fn::GetAtt:
- WebEC2Instance
- PrivateIp
- "/32"
- IpProtocol: tcp
FromPort: "8000"
ToPort: "8000"
CidrIp:
Fn::Join:
- ""
- - Fn::GetAtt:
- WebEC2Instance
- PrivateIp
- "/32"
Tags:
- Key: Name
Value: web3-apiserver-sg
APIEC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: web3-apiserver
InstanceType: t2.micro
ImageId: ami-04beabd6a4fb6ab6f
KeyName:
Ref: APIKeyPair
SecurityGroupIds:
- Fn::GetAtt:
- APISecurityGroup
- GroupId
SubnetId:
Ref: PrivateSubnet
UserData:
Fn::Base64: |
# nodejsをインストールするスクリプト
sudo yum install -y nodejs npm
mkdir /home/ec2-user/sample-api
cd /home/ec2-user/sample-api
npm init
npm install express mysql2
DBKeyPair:
Type: AWS::EC2::KeyPair
Properties:
KeyName: web3-dbserver-key
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: AllowSSHAndMySQLSecurityGroup
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp:
Fn::Join:
- ""
- - Fn::GetAtt:
- APIEC2Instance
- PrivateIp
- "/32"
- IpProtocol: tcp
FromPort: "3306"
ToPort: "3306"
CidrIp:
Fn::Join:
- ""
- - Fn::GetAtt:
- APIEC2Instance
- PrivateIp
- "/32"
Tags:
- Key: Name
Value: web3-dbserver-sg
DBEC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
- Key: Name
Value: web3-dbserver
InstanceType: t2.micro
ImageId: ami-04beabd6a4fb6ab6f
KeyName:
Ref: DBKeyPair
SecurityGroupIds:
- Fn::GetAtt:
- DBSecurityGroup
- GroupId
SubnetId:
Ref: PrivateSubnet
UserData:
Fn::Base64: |
# mysqlをインストールするスクリプト
sudo yum install -y https://dev.mysql.com/get/mysql80-community-release-el9-3.noarch.rpm
sudo yum install -y mysql-community-server
sudo systemctl start mysqld
- 環境構築してみる
上記 EC2 + Nginx + React で作成する Web アプリ
の3. 環境構築してみる
で行なった手順で環境構築を行なってください。
設定するスタック名と yml ファイルのみ上記の物に変更してください。
こちらの yml ファイルで行っているのは EC2 インスタンスの立ち上げと必要なソフトウェア(Node.js や MySQL)のインストールのみなので、もし実際にシステムを完成させたい場合以下記事VPC を使用して 3 層アーキテクチャの Web システムを作成してみよう
の
-
- Web サーバを立ち上げて、React アプリケーションを載せる
-
- データベースサーバを立ち上げて、データベースを載せる
-
- API サーバを立ち上げて、WebAPI を載せる
で React アプリケーションの立ち上げ、データベースへのデータ投入、API アプリケーションの作成等を行ってみてください 😌
Amplify + Next + WebSocket API で作成するリアルタイムチャット
- システムの説明
上記がこれから作成する Web システムのアーキテクチャ図です。
ざっくり説明すると
- チャット画面を Next アプリケーションで作成し Ampify でホスティング
- WebSocket API + Lambda で DB とユーザーのやり取り部分のロジックを作成
- DynamoDB でメッセージや通信上情報を格納
というシステムになっています。
手動でこのシステムを作成した記事はこちらです。
- 環境構築に使用する CloudFormation の yml ファイル
以下が上記システムの基盤環境構築を行うための yml ファイルです。
こちらを CloudFormation に設置して基盤環境の構築を行います。
chat-app.yml
Resources:
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: message_rooms
AttributeDefinitions:
- AttributeName: room_id
AttributeType: N
KeySchema:
- AttributeName: room_id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: chat-rest-api
ApiGatewayMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
ResourceId:
Fn::GetAtt:
- RestApi
- RootResourceId
RestApiId:
Ref: RestApi
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetAllMessagesLambdaFunction.Arn}/invocations
GetAllMessagesLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: getAllMessages
Runtime: nodejs18.x
Handler: index.handler
Role:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Code:
ZipFile: |
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, GetCommand } = require("@aws-sdk/lib-dynamodb");
exports.handler = async (event) => {
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const getCommand = new GetCommand({
TableName: "message_rooms",
Key: {
room_id: 1,
},
});
const docResponse = await docClient.send(getCommand);
const response = {
statusCode: 200,
body: JSON.stringify({
messages: docResponse.Item.messages,
}),
};
return response;
};
GetAllMessagesLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName:
Ref: GetAllMessagesLambdaFunction
Principal: apigateway.amazonaws.com
ApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId:
Ref: RestApi
DependsOn:
- ApiGatewayMethod
ApiGatewayStage:
Type: AWS::ApiGateway::Stage
Properties:
RestApiId:
Ref: RestApi
DeploymentId:
Ref: ApiGatewayDeployment
StageName: production
WebSocketApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: chat-websocket
ProtocolType: WEBSOCKET
RouteSelectionExpression: $request.body.action
ConnectWebSocketRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId:
Ref: WebSocketApi
RouteKey: $connect
Target:
Fn::Sub: integrations/${ConnectLambdaIntegration}
SendMessageWebSocketRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId:
Ref: WebSocketApi
RouteKey: sendMessage
Target:
Fn::Sub: integrations/${SendMessageLambdaIntegration}
DisconnectWebSocketRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId:
Ref: WebSocketApi
RouteKey: $disconnect
Target:
Fn::Sub: integrations/${DisconnectLambdaIntegration}
WebSocketStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId:
Ref: WebSocketApi
StageName: production
AutoDeploy: true
ConnectLambdaIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId:
Ref: WebSocketApi
IntegrationType: AWS_PROXY
IntegrationUri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectLambdaFunction.Arn}/invocations
SendMessageLambdaIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId:
Ref: WebSocketApi
IntegrationType: AWS_PROXY
IntegrationUri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SendMessageLambdaFunction.Arn}/invocations
DisconnectLambdaIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId:
Ref: WebSocketApi
IntegrationType: AWS_PROXY
IntegrationUri:
Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DisconnectLambdaFunction.Arn}/invocations
ConnectLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: connect
Runtime: nodejs18.x
Handler: index.handler
Role:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Code:
ZipFile: |
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const {
PutCommand,
DynamoDBDocumentClient,
GetCommand,
} = require("@aws-sdk/lib-dynamodb");
exports.handler = async (event) => {
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const getCommand = new GetCommand({
TableName: "message_rooms",
Key: {
room_id: 1,
},
});
const response = await docClient.send(getCommand);
const connectionId = event.requestContext.connectionId;
let connectionIds = response.Item.connection_ids;
connectionIds.push(connectionId);
const putCommand = new PutCommand({
TableName: "message_rooms",
Item: {
...response.Item,
connection_ids: connectionIds,
},
});
await docClient.send(putCommand);
return {
statusCode: 200,
};
};
SendMessageLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: sendMessage2
Runtime: nodejs18.x
Handler: index.handler
Role:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Code:
ZipFile: |
import {
ApiGatewayManagementApiClient,
PostToConnectionCommand,
} from "@aws-sdk/client-apigatewaymanagementapi";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
PutCommand,
DynamoDBDocumentClient,
GetCommand,
} from "@aws-sdk/lib-dynamodb";
export const handler = async (event) => {
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const getCommand = new GetCommand({
TableName: "message_rooms",
Key: {
room_id: 1,
},
});
const response = await docClient.send(getCommand);
const body = JSON.parse(event.body);
const connectionIds = response.Item.connection_ids;
let messages = response.Item.messages;
messages.push({
send_user: body.data.send_user,
message: body.data.message,
});
const putCommand = new PutCommand({
TableName: "message_rooms",
Item: {
...response.Item,
messages,
},
});
await docClient.send(putCommand);
const apigClient = new ApiGatewayManagementApiClient({
endpoint:
"https://" +
event.requestContext.domainName +
"/" +
event.requestContext.stage,
});
const sendMessages = connectionIds.map(async (connectionId) => {
const apigCommand = new PostToConnectionCommand({
ConnectionId: connectionId,
Data: JSON.stringify({ messages }),
});
await apigClient.send(apigCommand);
});
await Promise.all(sendMessages);
return {
statusCode: 200,
};
};
DisconnectLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: disconnect
Runtime: nodejs18.x
Handler: index.handler
Role:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Code:
ZipFile: |
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
PutCommand,
DynamoDBDocumentClient,
GetCommand,
} from "@aws-sdk/lib-dynamodb";
export const handler = async (event) => {
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const getCommand = new GetCommand({
TableName: "message_rooms",
Key: {
room_id: 1,
},
});
const response = await docClient.send(getCommand);
const connectionId = event.requestContext.connectionId;
let connectionIds = response.Item.connection_ids;
const index = connectionIds.indexOf(connectionId);
connectionIds.splice(index, 1);
const putCommand = new PutCommand({
TableName: "message_rooms",
Item: {
...response.Item,
connection_ids: connectionIds,
},
});
await docClient.send(putCommand);
return {
statusCode: 200,
};
};
ConnectLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName:
Ref: ConnectLambdaFunction
Principal: apigateway.amazonaws.com
SendMessageLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName:
Ref: SendMessageLambdaFunction
Principal: apigateway.amazonaws.com
DisconnectLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName:
Ref: DisconnectLambdaFunction
Principal: apigateway.amazonaws.com
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: LambdaExecutionRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: LambdaPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- "dynamodb:*"
Resource: "*"
- Effect: Allow
Action:
- "execute-api:*"
Resource: "*"
CodeCommitRepository:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: chat-front
AmplifyApp:
Type: AWS::Amplify::App
Properties:
Name: chat-front
Repository:
Fn::GetAtt:
- CodeCommitRepository
- CloneUrlHttp
BuildSpec: |
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
AmplifyBranch:
Type: AWS::Amplify::Branch
Properties:
AppId:
Fn::GetAtt:
- AmplifyApp
- AppId
BranchName: main
EnableAutoBuild: true
- 環境構築してみる
上記 EC2 + Nginx + React で作成する Web アプリ
の3. 環境構築してみる
で行なった手順で環境構築を行なってください。
設定するスタック名と yml ファイルのみ上記の物に変更してください。
こちらの yml ファイルで行っているのは Amplify でのホスティング環境構築、API の構築、DynamoDB の立ち上げのみなので、もし実際にシステムを完成させたい場合以下記事WebSocket APIでリアルタイムチャット作ってみた(Amplify+Next+WebSocket API使用)
の
-
- チャットのデータを保管する DynamoDB を構築する
-
- Next+Amplify+CodeCommit でチャット画面を作成して web 上に公開する
で Next.js アプリケーションの CodCommit への push、データベースへのデータ投入等を行ってみてください 😌
終わりに
以上、CloudFormation で異なる三つのタイプのシステムの環境構築を行なってみました。
お役に立つことができていたら幸いです。
ここまで読んでいただき本当にありがとうございます 🙇♀️
参照
Discussion