AWS、React、GolangによるWebアプリ開発~本番環境構築編~
@nerusanです。
前回の記事では、環境構築の方法とポイントを述べました。
今回は、AWSでの環境構築について述べます。
今回も前回と同様に具体的な手順を述べるのはなく、
具体的なポイントや用語について述べていきます。
ECS/Fargateについて
まず、ECS/Fargateについて軽く述べたいと思います。
Amazon Elastic Container Service(ECS)は、フルマネージドなコンテナオーケストレータです。
つまり、コンテナを管理する機能を提供するものです。
オーケストレーションとしてよく挙げられるのが、kubernetes(k8s)があります。
k8sは、オープンソースとなっており、その特性上、クリティカルな課題が発生した時に自身で解決するか、コミュニティでIssueとして取り上げ、解決するということになります。
その点、ECSは、AWSが提供する独自のオーケストレータなので、AWSのサポートが受けられます。そのため導入のためのハードルが低く、スタートアップ企業などで採用されやすいです。
オーケストレータが解決するのは、コンテナの配置管理であり、具体的には以下のような点があります。
- コンテナの負荷分散
- 処理量に応じてリクエストを分散させ、可用性やパフォーマンスを高めます
- コンテナの状態監視と自動復旧
- コンテナの状態を監視し、異常発生を検知してコンテナの切り離しや自動復旧によるコンテナ数を維持することでサービスの安定稼働させます
- コンテナのデプロイ
- アプリケーションを更新する際、すでに稼働中のアプリケーションを停止しつつ、新しいコンテナに自動に入れ替えることがスムーズにできます
次は、AWS Fargateについて述べます。
AWS Fargate(Fargate)は、ECSで動作するコンテナ向けサーバーレスコンピューティングエンジンです。
つまり、コンテナが実際に動作するサーバーとなります。
Fargate単体で利用することはできず、ECSとセットで利用する必要があります。
Fargateでは、ホストの管理が不要となります。サーバーのスケーリング、パッチ管理、保護など運用上の管理は発生しません。
コンテナが実行されるホストのインフラはAWSによって常に最新の状態に保たれます。
利用者は、ホストの管理から解放され、自分たちのビジネスに注力できるのはメリットです。
バックエンド側ネットワークの構築
公開構築する全体像を示します。
▼図1:全体像
ここで、いろいろな用語が出てきます。
まずは簡単に用語の説明をします。
- VPC(Virtual Private Cloud)
- AWSアカウント専用の仮装ネットワーク
- インターネットゲートウェイ
- AWS VPC領域から外、つまりインターネット領域へ出るための出入り口の役割を担う
- 1つのVPCに1つのインターネットゲートウェイをアタッチすることができる
- パブリックサブネット
- インターネットゲートウェイにルーティングすることができるサブネット
- インターネットに繋げる必要があるロードバランサーやNatGatewayはパブリックサブネットに属させる
- プライベートサブネット
- ルーティングされておらず、非公開となっているサブネット
- 外から自由にアクセスさせたくないDBやCache、containerなどはプライベートサブネットに属させる
- NATGateway(Network Address Transfer Gateway)
- プライベートサブネットからインターネットに繋ぐ場合に利用する
- https://milestone-of-se.nesuke.com/sv-advanced/aws/internet-nat-gateway/ がわかりやすかったかも
- ECR(Elastic Container Registry)
- フルマネージドなDockerコンテナレジストリサービス
- Dockerイメージを管理する
VPCの中にサブネットを配置して、各サブネット間が通信することでアプリケーションを動作させます。
また、外部の通信はインターネットゲートウェイ、NATGatewayを設定することで可能となります。
また、サブネット間の通信経路は、ルートテーブルとセキュリティグループによって制御することができます。
図1のサブネット名の説明は以下です。
- Ingress
- インターネットからリクエストを受け付ける Ingress(Application Load Balancer)用サブネット
- NATGatewayを配置用サブネット
- container
- アプリ稼働(go起動サーバー)用サブネット
- db
- データベース(MySQL)用サブネット
- cache
- キャッシュサーバー(Redis)用サブネット
- management
- 運用管理用サブネット
- キャッシュ、DBに直接アクセスしたりする踏み台サーバーを置く
- egress
- ECR など外部にリクエストするインターフェース型 VPC エンドポイント用サブネット
ルートテーブル
ルートテーブルとは、ネットワークの経路を設定するためのリソースです。
新規でルートテーブルを作成した時点では、VPC内のサブネット間の通信は設定されていますが、
インターネットと通信するためのルーティングは設定されていません。
パブリックサブネットがインターネット側と通信できるようにルーティングの設定を行う必要があります。
宛先としてデフォルトゲートウェイ(0.0.0.0/0)が指定された場合にインターネットゲートウェイへ向けるルールを設定します。
セキュリティグループ
アクセスの制御を行います。
アウトバウンドルールは「0.0.0.0/0」を許可し、インバウンドルールは最小限にします。
インバウンドルールを最小にすることで、セキュリティを向上させます。
詳しくは、以下のサイトを見るとわかりやすいです。
NATGateway
今回GolangのアプリケーションではAmason SES API v1を利用して、メールを送信します。こちらのAPIは、HTTPSを利用して外部インターネットを通して、SESにアクセスし、メール送信を依頼します。つまり、プライベートサブネットであるcontainerからインターネットにアクセスする必要があります。
プライベートサブネットは、インターネットに繋げることができないため、NATGatewayを設定してあげる必要があります。
IaC化
ネットワークの構築は、IaC(Infrastructure as Code)化することで、効率化につながります。
つまり、Dockerfileやdocker-compose.ymlと同様に、インフラ周りの設定などをコードとして残すことで、環境の再利用、人的ミスを減らすことができます。
IaCは有名どころとしてterraformなどがありますが、今回はAWSを利用するということで、Cloud Formationを利用しました。
NAT Gateway以外のIacは以下に示します。VPC,サブネット、ルートテーブル、インターネットゲートウェイ、セキュリティグループの設定をしています。
IaC(cloud formation)
AWSTemplateFormatVersion: "2010-09-09"
Description: Network resource template part1
Resources:
# VPCの設定
pointAppVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: pointAppVpc
############### Subnet, RouteTable, IGW ###############
# コンテナ周りの設定
## コンテナアプリ用のプライベートサブネット
pointAppSubnetPrivateContainer1A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.8.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-container-1a
- Key: Type
Value: Isolated
pointAppSubnetPrivateContainer1C:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.9.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-container-1c
- Key: Type
Value: Isolated
## コンテナアプリ用のルートテーブル
pointAppRouteApp:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: pointAppVpc
Tags:
- Key: Name
Value: piint-app-route-app
## コンテナサブネットへルート紐付け
pointAppRouteAppAssociation1A:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteApp
SubnetId: !Ref pointAppSubnetPrivateContainer1A
pointAppRouteAppAssociation1C:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteApp
SubnetId:
Ref: pointAppSubnetPrivateContainer1C
# DB周りの設定
## DB用のプライベートサブネット
pointAppSubnetPrivateDb1A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.16.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-db-1a
- Key: Type
Value: Isolated
pointAppSubnetPrivateDb1C:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.17.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-db-1c
- Key: Type
Value: Isolated
## DB用のルートテーブル
pointAppRouteDb:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: pointAppVpc
Tags:
- Key: Name
Value: point-app-route-db
## DBサブネットへルート紐付け
pointAppRouteDbAssociation1A:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteDb
SubnetId:
Ref: pointAppSubnetPrivateDb1A
pointAppRouteDbAssociation1C:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteDb
SubnetId:
Ref: pointAppSubnetPrivateDb1C
# Cacheの設定
# Cache Server用のプライベートサブネット
pointAppSubnetPrivateCache1A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.24.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-cache-1a
- Key: Type
Value: Isolated
pointAppSubnetPrivateCache1C:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.25.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-cache-1c
- Key: Type
Value: Isolated
## Cache用のルートテーブル
pointAppRouteCache:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: pointAppVpc
Tags:
- Key: Name
Value: point-app-route-cache
## Cacheサブネットへルート紐付け
pointAppRouteCacheAssociation1A:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteCache
SubnetId:
Ref: pointAppSubnetPrivateCache1A
pointAppRouteCacheAssociation1C:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteCache
SubnetId:
Ref: pointAppSubnetPrivateCache1C
# Ingress周りの設定
## Ingress用のパブリックサブネット
pointAppSubnetPublicIngress1A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.0.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: point-app-subnet-public-ingress-1a
- Key: Type
Value: Public
pointAppSubnetPublicIngress1C:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.1.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: point-app-subnet-public-ingress-1c
- Key: Type
Value: Public
## Ingress用のルートテーブル
pointAppRouteIngress:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: pointAppVpc
Tags:
- Key: Name
Value: point-app-route-ingress
## Ingressサブネットへルート紐付け
pointAppRouteIngressAssociation1A:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteIngress
SubnetId:
Ref: pointAppSubnetPublicIngress1A
pointAppRouteIngressAssociation1C:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteIngress
SubnetId:
Ref: pointAppSubnetPublicIngress1C
## Ingress用ルートテーブルのデフォルトルート
## 0.0.0.0/0が指定されたらインターネットゲートウェイに紐づけるルール
pointAppRouteIngressDefault:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: pointAppRouteIngress
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: pointAppIgw
DependsOn:
- pointAppVpcgwAttachment
# 管理用サーバ周りの設定
## 管理用のパブリックサブネット
pointAppSubnetPublicManagement1A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.240.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: point-app-subnet-public-management-1a
- Key: Type
Value: Public
pointAppSubnetPublicManagement1C:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.241.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: point-app-subnet-public-management-1c
- Key: Type
Value: Public
## 管理用サブネットのルートはIngressと同様として作成する
pointAppRouteManagementAssociation1A:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteIngress
SubnetId:
Ref: pointAppSubnetPublicManagement1A
pointAppRouteManagementAssociation1C:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: pointAppRouteIngress
SubnetId:
Ref: pointAppSubnetPublicManagement1C
# インターネットへ通信するためのゲートウェイの作成
pointAppIgw:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: point-app-igw
pointAppVpcgwAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: pointAppVpc
InternetGatewayId:
Ref: pointAppIgw
# VPCエンドポイント周りの設定
## VPCエンドポイント(Egress通信)用のプライベートサブネット
pointAppSubnetPrivateEgress1A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.248.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-egress-1a
- Key: Type
Value: Isolated
pointAppSubnetPrivateEgress1C:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.249.0/24
VpcId:
Ref: pointAppVpc
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ""
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: point-app-subnet-private-egress-1c
- Key: Type
Value: Isolated
############### Security groups ###############
# セキュリティグループの生成
## インターネット公開のセキュリティグループの生成
pointAppSgIngress:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ingress
GroupName: ingress
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
Description: from 0.0.0.0/0:80
FromPort: 80
IpProtocol: tcp
ToPort: 80
- CidrIpv6: ::/0
Description: from ::/0:80
FromPort: 80
IpProtocol: tcp
ToPort: 80
Tags:
- Key: Name
Value: point-app-sg-ingress
VpcId:
Ref: pointAppVpc
## 管理用サーバ向けのセキュリティグループの生成
pointAppSgManagement:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group of management server
GroupName: management
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
Tags:
- Key: Name
Value: point-app-sg-management
VpcId:
Ref: pointAppVpc
## バックエンドコンテナアプリ用セキュリティグループの生成
pointAppSgContainer:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group of backend app
GroupName: container
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
Tags:
- Key: Name
Value: point-app-sg-container
VpcId:
Ref: pointAppVpc
## DB用セキュリティグループの生成
pointAppSgDb:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group of database
GroupName: database
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
Tags:
- Key: Name
Value: point-app-sg-db
VpcId:
Ref: pointAppVpc
## VPCエンドポイント用セキュリティグループの生成
pointAppSgEgress:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group of VPC Endpoint
GroupName: egress
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
Tags:
- Key: Name
Value: point-app-sg-vpce
VpcId:
Ref: pointAppVpc
## Cache用セキュリティグループの生成
pointAppSgCache:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group of VPC Endpoint
GroupName: cache
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
Tags:
- Key: Name
Value: point-app-sg-cache
VpcId:
Ref: pointAppVpc
# ルール紐付け
## Internet LB -> Back Container
pointAppSgFrontContainerFromsSgIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: HTTPS for Ingress
FromPort: 80
GroupId:
Fn::GetAtt:
- pointAppSgContainer
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgIngress
- GroupId
ToPort: 80
## Back container -> DB
pointAppSgDbFromSgContainerTCP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: MySQL protocol from backend App
FromPort: 3306
GroupId:
Fn::GetAtt:
- pointAppSgDb
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgContainer
- GroupId
ToPort: 3306
## Back container -> Cache
pointAppSgCacheFromSgContainerTCP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: Redis protocol from backend App
FromPort: 6379
GroupId:
Fn::GetAtt:
- pointAppSgCache
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgContainer
- GroupId
ToPort: 6379
## Management server -> DB
pointAppSgDbFromSgManagementTCP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: MySQL protocol from management server
FromPort: 3306
GroupId:
Fn::GetAtt:
- pointAppSgDb
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgManagement
- GroupId
ToPort: 3306
## Management Server -> Cache
pointAppSgCacheFromSgManagementTCP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: Redis protocol from management server
FromPort: 6379
GroupId:
Fn::GetAtt:
- pointAppSgCache
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgManagement
- GroupId
ToPort: 6379
## Back container -> VPC endpoint
pointAppSgVpceFromSgContainerTCP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: HTTPS for Container App
FromPort: 443
GroupId:
Fn::GetAtt:
- pointAppSgEgress
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgContainer
- GroupId
ToPort: 443
## Management Server -> VPC endpoint
pointAppSgVpceFromSgManagementTCP:
Type: AWS::EC2::SecurityGroupIngress
Properties:
IpProtocol: tcp
Description: HTTPS for management server
FromPort: 443
GroupId:
Fn::GetAtt:
- pointAppSgEgress
- GroupId
SourceSecurityGroupId:
Fn::GetAtt:
- pointAppSgManagement
- GroupId
ToPort: 443
NATGatewayの設定は以下の記事を参考に手動で設定しました。(IaCに落とし込めてなくてすみません。)
ECS/Fargateへのデプロイ
デプロイの流れは以下のようになります。
- dockerコマンドでdockerイメージを作成しlatestタグを付与して、ECRに登録
- ECSのコンテナ定義書を更新
- コンテナ定義書に従い、Dockerイメージを取得し、Fargate上にコンテナを配置
今回は、CI/CDツールとしてGitHub ActionとAWS CodePipelineの2種類を採用しました。
CI/CDとは「Continuous Integration(継続的インテグレーション)/ Continuous Delivery(継続的デリバリー)」の略称であり、テストやビルド、デプロイを自動化するツールです。
先ほど述べた、デプロイの流れをCI/CDに置き換えると以下の手順になります。
(GitHubAction)
- mainブランチにマージをトリガーとして、Dockerイメージを生成およびlatestタグを付与
- ECRに登録
(CodePipeline)
3. ECRにおいてlatestタグのイメージが更新されたことをトリガーに、コンテナ定義書を更新
4. コンテナ定義書に従い、ECRから取得したDockerイメージをFargate上にデプロイ
CodepipelineのみCI/CDを組むことができますが、
今回コードの管理はGitHubで行いたかったため、このような手順でCI/CDを組みました。
GitHubActionでのみでも、CI/CDを組むことはできますが、ワークフローが複雑になる上、実際に動作するのかを確認するのがいちいちマージしないといけないので、大変になります。
なので、今回採用した方法が一番簡単かなって思う、かつ、一番採用されている方法ではないでしょうか。
▼参考
以下、環境構築した際のメモを残しております。
Blue/Greenデプロイ
デプロイ方法は、Blue/Greenデプロイを採用しました。
こちらは、既存のアプリケーションからスムーズに切り替えるためのデプロイ方法になります。
リリース用コンテナ(Blue側)と既存コンテナ(Green側)を両方起動させた状態で、トラフィックの転送を行うので、切り替えのダウンタイムがすごく少ないです。
また、切り替え後もある一定期間は、切り替え前のコンテナも起動しているので切り戻しもダウンタイムなしで切り替えることが可能になります。
リリース後すぐに不具合が起きても安心ですね。
▼Blue/Greenデプロイ(以下の参考記事より参照)
詳しくは以下の記事がわかりやすいかったです。
▼参考
フロントエンド側のデプロイ環境
フロントエンド側のデプロイ環境はAmplifyを利用することでバックエンドで比較にならないほど簡単にデプロイを行うことができます。
デプロイ環境だけではなく、CI/CDの構築もGUIベースで簡単に行ってくれます。
GUIベースで設定ファイルを記述することができます。
▼ビルド設定
環境変数の設定もGUIベースで簡単に設定できます。
▼環境変数設定
また、プレビューも用意されており、PR中の動作確認もローカルで確認する必要がないため、かなりDXが上がるかなって思います。
また、ステージング環境を構築する際は、そのURLは誰でも見られないようにすべきです。
その際のペーシック認証の設定も簡単にできます。
構築方法も公式のチュートリアル参考にすると良いでしょう。
本番の環境も30分もかからず構築できると思います。
まとめ
本番環境の構築を簡単にまとめてみました。
みなさんの参考になれば幸いです。
参考
Discussion