はじめに
#2022/9/23一部修正しました。
#User DataをStackにベタ書きしていましたが、Shell(.sh)ファイルにして読み込むようにしました。
やること
こんにちは。あつしです。最近はIaC最高!って感じです。
今回はAWS CDKを使って、ほぼ一発でWordPressサイトを構築してみます。
概要
パブリックサブネットにALBを配置してユーザーからのリクエストを受け付けます。
その後ろにプライベートサブネット(NATでインターネット接続可能)を作成し、EC2とEFSを配置します。EFSにWordPressモジュールを配置し、EC2からマウントして使用します。これによりEC2をステートレスにすることができ、Auto Scalingで負荷に応じてスケールアウトすることができるようにします。
Apache、PHPなどのインストールと、WordPressモジュールのダウンロードはすべてEC2のユーザーデータで行います(かなり強引ですが)。インストールが完了したら、そのEC2からカスタムAMIを作成します。最終的にはカスタムAMIからEC2が起動するように設定します。
プライベートサブネット(インターネット接続不可)も作成し、こちらにはRDS MySQLを配置します。AZ障害に備えてマルチAZ構成とします。
細かい設計は後述します。
ゴール
ゴールは以下のことが達成されることです。
- ELBのDNS名でアクセスしてWordPressサイトが表示されること。
- WordPressの管理画面にログインできること。
- カスタムAMIからEC2が起動してマルチAZ構成となっていること。
- ボリュームが多いため、記事を分割してお送りします。
- 本ページでは構成図と設計をご紹介します。
設計
本件の設計をご説明します。
構成図
構成図を示します。完成形は以下のようになっています。
- EC2を配置しているプライベートサブネットは「プライベートサブネット(APP)」と記載。
- RDSを配置しているプライベートサブネットは「プライベートサブネット(DB)」と記載。
AWSリソース
使用するAWSリソースと設計の概要を説明します。
AWSリソース | 説明 |
---|---|
VPC | CIDRは10.0.0.0/16。サブネットは/24で分割。 AZは2つ。 ●パブリックサブネット✕2 ●プライベートサブネット(NATでインターネット接続可能)✕2 →「プライベートサブネット(APP)」と記載します。 ●プライベートサブネット(インターネット接続不可)✕2 →「プライベートサブネット(DB)」と記載します。 |
Nat Gateway | 1つ作成(普段は使用しないため)。EC2がインターネットへ接続するときに使用する。 WordPressサーバーが完成した後は削除する。WordPressやプラグインをアップデートするときは都度作成する。 |
セキュリティグループ | 以下の5つを作成。 ●ELB用 ●EC2用 ●EFS用 ●RDS用 ●セッションマネージャーエンドポイント用 |
Systems Manager | セッションマネージャーでEC2に接続するため作成。EC2にポート22は開けない。 |
エンドポイント | セッションマネージャーでEC2に接続するため以下を作成。 ●ec2messages.[region].amazonaws.com ●ssm.[region].amazonaws.com ●ssmmessages.[region].amazonaws.com |
IAMロール | EC2に付与するロールを作成。以下のポリシーをアタッチ。 ●AmazonSSMManagedInstanceCore →セッションマネージャー使用のため。 ●AmazonElasticFileSystemReadOnlyAccess →EFSのファイルシステムID取得のため(詳細は次回の記事) ●SecretsManagerReadWrite →Secrets ManagerからRDS認証情報を取得するため(詳細は次回の記事) ●ElasticLoadBalancingReadOnly →ELBのDNS名取得のため(詳細は次回の記事) |
EFS | WordPressのモジュールを保存。EC2からマウントして使用。これにより、EC2をステートレスな状態にする。 ※プライベートサブネット(APP)に配置。インターネットから接続不可。 |
RDS | MySQL 5.7.37を使用。 AZ障害に備えて、マルチAZの高可用性な設計とする。 ※プライベートサブネット(DB)に配置。インターネットから接続不可。 |
Secrets Manager | RDSの認証情報を保存。 |
EC2 | 起動テンプレートから起動する。 Auto Scalingグループに組み込みマルチAZ構成とする。負荷に応じてスケールアウトする。 ※プライベートサブネット(APP)に配置。WordPressのアップデートなど必要なときだけNat Gatewayを通してインターネットに接続する。 |
ELB | ALBを使用。 HTTPでユーザーからのリクエストを受け付ける。(証明書は使用しません) |
CDKバージョン
CDKはPythonを使用します。CDKとPythonのバージョンは以下のようになっています。
% python --version
Python 3.9.13
% cdk --version
2.28.1 (build d035432)
AWS CDKコード(Network、Storage)
AWS CDKでコーディングしていきます。
コーディング規約(変数名など)
簡単ですが、変数名とAWSリソースに付与する名前について、本件のコーディング規約を記載します。
- 変数名
wp_[リソース名](_[リソース名])
※例1)VPC:wp_vpc
※例2)EC2セキュリティグループ:wp_sg_ec2
- AWSリソース名
wp-dev-[リソース名](_[リソース名]_[リソース名])
※例1)VPC:wp-dev-vpc
※例2)EC2セキュリティグループ:wp-dev-sg-ec2
フォルダ構成
フォルダ構成は以下のようになっています。
├── Network
│ └── Network_stack.py
│
├── Storage
│ │── Storage_stack.py
│
├── Database
│ └── Database_stack.py
│
├── Computing
│ │── Computing_stack.py
│ └── User_data.sh
│
app.py
スタックを4つに分けています。
- Network_stack.py
- Storage_stack.py
- Database_stack.py
- Computing_stack.py
これを順番にデプロイしていくことでWordPressサイトが構築されます。
※User_data.shはEC2で初回起動時に実行するユーザーデータです。Computing_stack.pyから読み込まれます。
今回は❶と❷のスタックを説明します。
コード量が多いため、❸と❹は次回の記事で説明します。
- ❸、❹のDatabaseスタックとComputingスタックの解説記事はこちら。
エントリポイント(app.py)
まずエントリポイントを簡単に説明します。
- app.py
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from Network.Network_stack import NetworkStack
from Storage.Storage_stack import StorageStack
from Database.Database_stack import DatabaseStack
from Computing.Computing_stack import ComputingStack
my_account = "XXXXXXXXXXXX"
my_region = "ap-northeast-1"
envJP = cdk.Environment(account=my_account, region=my_region)
app = cdk.App()
NetworkStack(app, "Network", env=envJP)
StorageStack(app, "Storage", env=envJP)
DatabaseStack(app, "Database", env=envJP)
ComputingStack(app, "Computing", env=envJP)
app.synth()
コード解説(app.py)
my_account
変数にAWSアカウントNoを入れて、my_region
変数にデプロイしたいAWSリージョンを入れます。
それを各スタックに渡してデプロイするようにしています。
Networkスタック(Network_stack.py)
1番最初にデプロイするスタックです。
- Network_stack.py
from aws_cdk import (
Stack,
aws_ec2 as ec2,
aws_iam as iam
)
from constructs import Construct
class NetworkStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
###############################################
############### Network Section ###############
###############################################
# VPC & Subnet
wp_vpc = ec2.Vpc(self, "Vpc",
vpc_name="wp-dev-vpc",
cidr="10.0.0.0/16",
max_azs=2,
nat_gateways=1,
subnet_configuration=[
ec2.SubnetConfiguration(
name="wp-dev-public",
cidr_mask=24,
subnet_type=ec2.SubnetType.PUBLIC
),
ec2.SubnetConfiguration(
name="wp-dev-private-with-nat",
cidr_mask=24,
subnet_type=ec2.SubnetType.PRIVATE_WITH_NAT
),
ec2.SubnetConfiguration(
name="wp-dev-private-isolated",
cidr_mask=24,
subnet_type=ec2.SubnetType.PRIVATE_ISOLATED
)
]
)
######################################################
############### Security Group Section ###############
######################################################
# Security Group(ELB)
wp_sg_elb = ec2.SecurityGroup(self, "SgELB",
vpc=wp_vpc,
security_group_name="wp-dev-sg-elb",
description="Security Group for ELB",
allow_all_outbound=False
)
# Security Group(EC2)
wp_sg_ec2 = ec2.SecurityGroup(self, "SgEC2",
vpc=wp_vpc,
security_group_name="wp-dev-sg-ec2",
description="Security Group for EC2",
allow_all_outbound=False
)
# Security Group(EFS)
wp_sg_efs = ec2.SecurityGroup(self, "SgEFS",
vpc=wp_vpc,
security_group_name="wp-dev-sg-efs",
description="Security Group for EFS",
allow_all_outbound=False
)
# Security Group(RDS)
wp_sg_rds = ec2.SecurityGroup(self, "SgRDS",
vpc=wp_vpc,
security_group_name="wp-dev-sg-rds",
description="Security Group for RDS",
allow_all_outbound=False
)
# Security Group(SSM)
wp_sg_ssm = ec2.SecurityGroup(self, "SgSSM",
vpc=wp_vpc,
security_group_name="wp-dev-sg-ssm",
description="Security Group for SSM",
allow_all_outbound=False
)
################################################
############### Endpoint Section ###############
################################################
# SSM Endpoint
ec2.InterfaceVpcEndpoint(self, "SSMEndpoint",
vpc=wp_vpc,
service=ec2.InterfaceVpcEndpointAwsService.SSM,
security_groups=[wp_sg_ssm],
subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_NAT)
)
# SSM_MESSAGES Endpoint
ec2.InterfaceVpcEndpoint(self, "SSM_MESSAGESEndpoint",
vpc=wp_vpc,
service=ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
security_groups=[wp_sg_ssm],
subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_NAT)
)
# EC2_MESSAGES Endpoint
ec2.InterfaceVpcEndpoint(self, "EC2_MESSAGESEndpoint",
vpc=wp_vpc,
service=ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
security_groups=[wp_sg_ssm],
subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_NAT)
)
###########################################
############### IAM Section ###############
###########################################
# Role(EC2)
wp_role_ec2 = iam.Role(self, "RoleEC2",
role_name="wp-dev-role-ec2",
description="Role for EC2",
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com")
)
# Add Policy To Role(EC2)
wp_role_ec2.add_managed_policy(
iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name="AmazonSSMManagedInstanceCore")
)
wp_role_ec2.add_managed_policy(
iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name="AmazonElasticFileSystemReadOnlyAccess")
)
wp_role_ec2.add_managed_policy(
iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name="SecretsManagerReadWrite")
)
wp_role_ec2.add_managed_policy(
iam.ManagedPolicy.from_aws_managed_policy_name(managed_policy_name="ElasticLoadBalancingReadOnly")
)
コード解説(Network_stack.py)
- 17〜39行目
VPCを作成しています。max_azs=2
でAZを2つに指定しています。nat_gateways=1
でNat Gatewayを1つに指定しています。subnet_configuration
でサブネットの定義を記述しています。ec2.SubnetType.PUBLIC
でパブリックサブネットを使用すること、ec2.SubnetType.PRIVATE_WITH_NAT
でプライベートサブネット(NATでインターネット接続可能)を使用すること、ec2.SubnetType.PRIVATE_ISOLATED
でプライベートサブネット(インターネット接続不可)を使用することを記述しています。それぞれのサブネットマスクは/24
です。
- 46〜83行目
セキュリティグループを作成しています。(ELB、EC2、EFS、RDS、セッションマネージャーのセキュリティグループをそれぞれ作成しています)allow_all_outbound=False
でアウトバウンドはすべてブロックするようにしています。
- 90〜109行目
セッションマネージャーでEC2に接続するための、エンドポイントを作成しています。security_groups
に先ほど作成したセッションマネージャー用のセキュリティグループを渡しています。subnets
にプライベートサブネット(APP)を指定しています。(EC2と同じサブネットを指定)
- 116〜120行目
EC2にアタッチするIAMロールを作成しています。
- 123〜134行目
IAMロールにポリシーを追加しています。AmazonSSMManagedInstanceCore
はセッションマネージャーでEC2にアクセスするために必要です。
以下のポリシーはEC2のユーザーデータでaws cli
を実行して情報を取得するためにアタッチしています。(ユーザーデータについては次回の記事で触れます)AmazonElasticFileSystemReadOnlyAccess
はEFSのファイルシステムIDを取得するため必要です。SecretsManagerReadWrite
はRDSのシークレット情報を取得するために必要です。ElasticLoadBalancingReadOnly
はALBのDNS名を取得するために必要です。
Storageスタック(Storage_stack.py)
EFSのデプロイが記述されたスタックです。
- Storage_stack.py
from aws_cdk import (
Stack,
aws_ec2 as ec2,
aws_efs as efs
)
from constructs import Construct
import aws_cdk as cdk
class StorageStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
##############################################
############### Import Section ###############
##############################################
# Import VPC
wp_vpc = ec2.Vpc.from_lookup(self, "Vpc",
vpc_name="wp-dev-vpc"
)
# Import Security Group(EFS)
wp_sg_efs = ec2.SecurityGroup.from_lookup_by_name(self, "SgEFS",
vpc=wp_vpc,
security_group_name="wp-dev-sg-efs"
)
# Import Security Group(EC2)
wp_sg_ec2 = ec2.SecurityGroup.from_lookup_by_name(self, "SgEC2",
vpc=wp_vpc,
security_group_name="wp-dev-sg-ec2"
)
###############################################
############### Storage Section ###############
###############################################
# EFS File System
wp_efs = efs.FileSystem(self, "Efs",
vpc=wp_vpc,
encrypted=True,
file_system_name="wp-dev-efs",
security_group=wp_sg_efs,
vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_NAT),
removal_policy=cdk.RemovalPolicy.DESTROY
)
######################################################
############### Add Allow Rule Section ###############
######################################################
# Add Allow Connection to EFS
wp_efs.connections.allow_from(wp_sg_ec2, ec2.Port.tcp(2049))
コード解説(Storage_stack.py)
- 18〜20行目
Networkスタックで作成したVPCをfrom_lookup
でStorageスタックにインポートしています。
- 23〜32行目
Networkスタックで作成したセキュリティグループ(EFSとEC2)をfrom_lookup_by_name
でインポートしています。
- 39〜46行目
EFSファイルシステムを作成しています。security_group
にインポートしたEFSのセキュリティグループを渡しています。vpc_subnets
でプライベートサブネット(APP)を指定しています(EC2と同じサブネット)。
CDKでdestroyしたときにファイルシステムが削除されるように、removal_policy
にcdk.RemovalPolicy.DESTROY
を渡しています。
- 53行目
EFSに対してEC2からの2049
ポートでのアクセスを許可しています。
次回は
次回はDatabaseスタックとComputingスタックの解説をします。
コメント