#はじめに
以前投稿したAWSでのDjango環境構築をCloudFormationを活用して構築してみたいと思います。
#ポイント
- AWSマネジメントコンソールを使用せずに構築を実行
- CloudFormation, AWS CLIを使用して構築します。
- Systems Managerのパラメータストアを使用
- 一部パラメータをパラメータストアに格納して利用します。
- リソースの種類ごとにテンプレートを分割
- 公式ドキュメントでベストプラクティスとして記載されているリソースのライフサイクルを考慮して、テンプレートを分割します。
- クロススタック参照を使用します。
- 分割したテンプレートを使用して、ネストされたスタックを作成します。
#テンプレート構成
前述の通りテンプレートはリソースのライフサイクルを意識して分けてみました。
今回は以下のように分けています。
- root.yml:ルート(すべてのテンプレートのまとめ)
- network.yml:ネットワーク(VPC,サブネットなど)
- security.yml:セキュリティ(セキュリティグループ,IAMロールなど)
- bastion.yml:踏み台(踏み台用EC2インスタンス)
- database.yml:データベース(RDS,サブネットグループ)
- application.yml:アプリケーション(起動テンプレート,ロードバランサー,Auto Scaling Groupなど)
また、それぞれのテンプレートファイルは1つのS3バケットに配置し、スタック作成時にはroot.ymlのURIを指定して実行しています。
root.yml内でその他のテンプレートを参照し、ネストされたスタックとして作成します。
#テンプレート
実際のテンプレートの中身について以下に記載します。
#####ルート
AWSTemplateFormatVersion: 2010-09-09
Description: Root template for creating Django environment
Parameters:
TemplateNetwork:
Description: Network layer template object URL
Type: String
Default: <network.ymlのURL>
TemplateSecurity:
Description: Security layer template object URL
Type: String
Default: <security.ymlのURL>
TemplateBastion:
Description: Bastion EC2 template object URL
Type: String
Default: <bastion.ymlのURL>
TemplateDatabase:
Description: RDS template object URL
Type: String
Default: <database.ymlのURL>
TemplateApplication:
Description: Application layer object URL
Type: String
Default: <application.ymlのURL>
Resources:
Network:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref TemplateNetwork
Parameters:
VPCCidrBlock: '10.0.0.0/21'
VPCNameTag: 'django-vpc'
IGWNameTag: 'django-igw'
ELBSubnet1aCidr: '10.0.0.0/24'
ELBSubnet1aNameTag: 'django-elb-subnet-1a'
ELBSubnet1cCidr: '10.0.1.0/24'
ELBSubnet1cNameTag: 'django-elb-subnet-1c'
PublicRouteTableNameTag: 'django-public-rt'
WebSubnet1aCidr: '10.0.2.0/24'
WebSubnet1aNameTag: 'django-web-subnet-1a'
WebSubnet1cCidr: '10.0.3.0/24'
WebSubnet1cNameTag: 'django-web-subnet-1c'
DBSubnet1aCidr: '10.0.4.0/24'
DBSubnet1aNameTag: 'django-db-subnet-1a'
DBSubnet1cCidr: '10.0.5.0/24'
DBSubnet1cNameTag: 'django-db-subnet-1c'
PrivateRouteTableNameTag: 'django-private-rt'
NGWNameTag: 'django-ngw'
Security:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref TemplateSecurity
Parameters:
ELBSGName: 'django-elb-sg'
BastionSGName: 'django-bastion-sg'
WebSGName: 'django-web-sg'
RDSSGName: 'django-rds-sg'
HTTPAccessIP: '0.0.0.0/0'
SSHAccessIP: '0.0.0.0/0'
IAMRoleName: 'django-s3-role'
DependsOn: Network
Bastion:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref TemplateBastion
Parameters:
EC2NameTag: 'django-bastion'
DependsOn: Security
Database:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref TemplateDatabase
Parameters:
RDSSubnetGroupName: 'django-subnetgroup'
DBInstanceIdentifier: 'django-database'
MultiAZ: 'true'
AZ: 'ap-northeast-1a'
FirstDBName: 'mysite'
DBNameTag: 'django-db'
DependsOn: Security
Application:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Ref TemplateApplication
Parameters:
LaunchTemplateName: 'django-launch-template'
TargetGroupName: 'django-tg'
HealthCheckPath: '/polls/'
HealthyThresholdCount: '2'
HealthCheckInterval: '10'
LoadBalancerName: 'django-lb'
AutoScalingGroupName: 'django-asg'
InstanceDesiredSize: '2'
InstanceMinSize: '2'
InstanceMaxSize: '4'
CPUHighAlarmName: 'CPU_High'
CPULowAlarmName: 'CPU_Low'
DependsOn: Database
#####ネットワーク
AWSTemplateFormatVersion: 2010-09-09
Description: Network layer template
Parameters:
VPCCidrBlock:
Type: String
Description: CIDRBlock of VPC
VPCNameTag:
Type: String
Description: Name tag of VPC
IGWNameTag:
Type: String
Description: Name tag of IGW
AZ1a:
Type: AWS::EC2::AvailabilityZone::Name
Description: AZ 1a of resources
Default: 'ap-northeast-1a'
AZ1c:
Type: AWS::EC2::AvailabilityZone::Name
Description: AZ 1c of resources
Default: 'ap-northeast-1c'
ELBSubnet1aCidr:
Type: String
Description: CIDR of ELB subnet 1a
ELBSubnet1cCidr:
Type: String
Description: CIDR of ELB subnet 1c
ELBSubnet1aNameTag:
Type: String
Description: Name tag of ELB subnet 1a
ELBSubnet1cNameTag:
Type: String
Description: Name tag of ELB subnet 1c
PublicRouteTableNameTag:
Type: String
Description: Name tag of Public route table
WebSubnet1aCidr:
Type: String
Description: CIDR of Web subnet 1a
WebSubnet1cCidr:
Type: String
Description: CIDR of Web subnet 1c
WebSubnet1aNameTag:
Type: String
Description: Name tag of Web subnet 1a
WebSubnet1cNameTag:
Type: String
Description: Name tag of Web subnet 1c
DBSubnet1aCidr:
Type: String
Description: CIDR of DB subnet 1a
DBSubnet1cCidr:
Type: String
Description: CIDR of DB subnet 1c
DBSubnet1aNameTag:
Type: String
Description: Name tag of DB subnet 1a
DBSubnet1cNameTag:
Type: String
Description: Name tag of DB subnet 1c
PrivateRouteTableNameTag:
Type: String
Description: Name tag of Private route table
NGWNameTag:
Type: String
Description: Name tag of Nat gateway
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Ref VPCNameTag
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Ref IGWNameTag
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
ELBSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1a
VpcId: !Ref VPC
CidrBlock: !Ref ELBSubnet1aCidr
Tags:
- Key: Name
Value: !Ref ELBSubnet1aNameTag
ELBSubnet1c:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1c
VpcId: !Ref VPC
CidrBlock: !Ref ELBSubnet1cCidr
Tags:
- Key: Name
Value: !Ref ELBSubnet1cNameTag
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Ref PublicRouteTableNameTag
DefaultPublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: '0.0.0.0/0'
GatewayId: !Ref IGW
ELBRouteTableAssoc1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref ELBSubnet1a
RouteTableId: !Ref PublicRouteTable
ELBRouteTableAssoc1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref ELBSubnet1c
RouteTableId: !Ref PublicRouteTable
WebSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1a
VpcId: !Ref VPC
CidrBlock: !Ref WebSubnet1aCidr
Tags:
- Key: Name
Value: !Ref WebSubnet1aNameTag
WebSubnet1c:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1c
VpcId: !Ref VPC
CidrBlock: !Ref WebSubnet1cCidr
Tags:
- Key: Name
Value: !Ref WebSubnet1cNameTag
DBSubnet1a:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1a
VpcId: !Ref VPC
CidrBlock: !Ref DBSubnet1aCidr
Tags:
- Key: Name
Value: !Ref DBSubnet1aNameTag
DBSubnet1c:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AZ1c
VpcId: !Ref VPC
CidrBlock: !Ref DBSubnet1cCidr
Tags:
- Key: Name
Value: !Ref DBSubnet1cNameTag
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Ref PrivateRouteTableNameTag
DefaultPrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: '0.0.0.0/0'
NatGatewayId: !Ref NGW
WebRouteTableAssoc1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref WebSubnet1a
RouteTableId: !Ref PrivateRouteTable
WebRouteTableAssoc1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref WebSubnet1c
RouteTableId: !Ref PrivateRouteTable
DBRouteTableAssoc1a:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref DBSubnet1a
RouteTableId: !Ref PrivateRouteTable
DBRouteTableAssoc1c:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref DBSubnet1c
RouteTableId: !Ref PrivateRouteTable
NGW:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGatewayEIP.AllocationId
SubnetId: !Ref ELBSubnet1a
Tags:
- Key: Name
Value: !Ref NGWNameTag
NatGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Outputs:
VPCID:
Value: !Ref VPC
Description: VPC ID
Export:
Name: VPCID
BastionSubnetID:
Value: !Ref ELBSubnet1a
Description: ID of ELB subnet 1a
Export:
Name: BastionSubnetID
DBSubnet1aID:
Value: !Ref DBSubnet1a
Description: ID of DB subnet 1a
Export:
Name: DBSubnet1aID
DBSubnet1cID:
Value: !Ref DBSubnet1c
Description: ID of DB subnet 1c
Export:
Name: DBSubnet1cID
ELBSubnet1aID:
Value: !Ref ELBSubnet1a
Description: ID of ELB subnet 1a
Export:
Name: ELBSubnet1aID
ELBSubnet1cID:
Value: !Ref ELBSubnet1c
Description: ID of ELB subnet 1c
Export:
Name: ELBSubnet1cID
WebSubnet1aID:
Value: !Ref WebSubnet1a
Description: ID of Web subnet 1a
Export:
Name: WebSubnet1aID
WebSubnet1cID:
Value: !Ref WebSubnet1c
Description: ID of Web subnet 1c
Export:
Name: WebSubnet1cID
#####セキュリティ
AWSTemplateFormatVersion: 2010-09-09
Description: Security layer template
Parameters:
ELBSGName:
Type: String
Description: Name of ELB security group
BastionSGName:
Type: String
Description: Name of Bastion security group
WebSGName:
Type: String
Description: Name of Web security group
RDSSGName:
Type: String
Description: Name of RDS security group
HTTPAccessIP:
Type: String
Description: CIDR block that is allowed HTTP access
SSHAccessIP:
Type: String
Description: CIDR block that is allowed SSH access
IAMRoleName:
Type: String
Description: Name of IAM role
Resources:
ELBSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Ref ELBSGName
GroupDescription: !Ref ELBSGName
VpcId: !ImportValue VPCID
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref HTTPAccessIP
Tags:
- Key: Name
Value: !Ref ELBSGName
BastionSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Ref BastionSGName
GroupDescription: !Ref BastionSGName
VpcId: !ImportValue VPCID
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHAccessIP
Tags:
- Key: Name
Value: !Ref BastionSGName
WebSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Ref WebSGName
GroupDescription: !Ref WebSGName
VpcId: !ImportValue VPCID
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ELBSG
-
IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref BastionSG
Tags:
- Key: Name
Value: !Ref WebSGName
RDSSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Ref RDSSGName
GroupDescription: !Ref RDSSGName
VpcId: !ImportValue VPCID
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref WebSG
Tags:
- Key: Name
Value: !Ref RDSSGName
S3AccessRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service: 'ec2.amazonaws.com'
Action: 'sts:AssumeRole'
Description: IAM role for EC2 access to S3
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AmazonS3FullAccess'
RoleName: !Ref IAMRoleName
Tags:
- Key: Name
Value: !Ref IAMRoleName
S3AccessInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Ref IAMRoleName
Roles:
- !Ref S3AccessRole
Outputs:
BastionSecurityGroupID:
Value: !Ref BastionSG
Description: ID of Bastion security group
Export:
Name: BastionSGID
RDSSecurityGroupID:
Value: !Ref RDSSG
Description: ID of RDS security group
Export:
Name: RDSSGID
WebSecurityGroupID:
Value: !Ref WebSG
Description: ID of Web security group
Export:
Name: WebSGID
S3AccessInstanceProfile:
Value: !Ref S3AccessInstanceProfile
Description: Instance profile of S3 access role
Export:
Name: S3AccessInstanceProfile
ELBSecurityGroupID:
Value: !Ref ELBSG
Description: ID of ELB security group
Export:
Name: ELBSGID
#####踏み台
AWSTemplateFormatVersion: 2010-09-09
Description: Bastion EC2 template
Parameters:
ImageID:
Type: String
Description: Bastion EC2 image
Default: 'ami-0404778e217f54308'
InstanceType:
Type: String
Description: Bastion EC2 instance type
Default: 't2.micro'
EC2NameTag:
Type: String
Description: Bastion EC2 name tag
Resources:
BastionEC2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref ImageID
InstanceType: !Ref InstanceType
NetworkInterfaces:
- AssociatePublicIpAddress: 'True'
DeviceIndex: '0'
SubnetId: !ImportValue BastionSubnetID
GroupSet:
- !ImportValue BastionSGID
KeyName: '{{resolve:ssm:ec2_keypair:1}}'
Tags:
- Key: Name
Value: !Ref EC2NameTag
#####データベース
AWSTemplateFormatVersion: 2010-09-09
Description: RDS template
Parameters:
RDSSubnetGroupName:
Type: String
Description: Name of RDS subnet group
DBInstanceIdentifier:
Type: String
Description: DB instance identifier
DBInstanceClass:
Type: String
Description: Instance class of DB
Default: 'db.t2.micro'
StorageType:
Type: String
Description: Storage type of DB
Default: 'gp2'
StorageSize:
Type: String
Description: Storage size of DB
Default: '20'
MaxStorageSize:
Type: String
Description: Max storage size of DB
Default: '1000'
MultiAZ:
Type: String
Description: Multi AZ
Default: 'False'
PublicAccess:
Type: String
Description: Public access
Default: 'False'
AZ:
Type: String
Description: Availability zone of DB
Default: 'ap-northeast-1a'
FirstDBName:
Type: String
Description: First DB name
DBNameTag:
Type: String
Description: Name tag of DB
Resources:
RDSSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: !Ref RDSSubnetGroupName
DBSubnetGroupDescription: !Ref RDSSubnetGroupName
SubnetIds:
- !ImportValue DBSubnet1aID
- !ImportValue DBSubnet1cID
Tags:
- Key: Name
Value: !Ref RDSSubnetGroupName
MySQL:
Type: AWS::RDS::DBInstance
Properties:
Engine: mysql
EngineVersion: '8.0.23'
DBInstanceIdentifier: !Ref DBInstanceIdentifier
MasterUsername: '{{resolve:ssm:db_user:1}}'
MasterUserPassword: '{{resolve:ssm-secure:db_password:1}}'
DBInstanceClass: !Ref DBInstanceClass
StorageType: !Ref StorageType
AllocatedStorage: !Ref StorageSize
MaxAllocatedStorage: !Ref MaxStorageSize
MultiAZ: !Ref MultiAZ
DBSubnetGroupName: !Ref RDSSubnetGroup
PubliclyAccessible: !Ref PublicAccess
VPCSecurityGroups:
- !ImportValue RDSSGID
# AvailabilityZone: !Ref AZ
Port: '3306'
DBName: !Ref FirstDBName
Tags:
- Key: Name
Value: !Ref DBNameTag
#####アプリケーション
AWSTemplateFormatVersion: 2010-09-09
Description: Application layer template
Parameters:
LaunchTemplateName:
Type: String
Description: Name of Launch template
ImageID:
Type: String
Description: Image ID of Launch template
Default: 'ami-0404778e217f54308'
InstanceType:
Type: String
Description: Instance type of Launch template
Default: 't2.micro'
TargetType:
Type: String
Description: Target type of Target group
Default: 'instance'
TargetGroupName:
Type: String
Description: Name of Target group
Protocol:
Type: String
Description: Protocol of Target group
Default: 'HTTP'
Port:
Type: String
Description: Port of Target group
Default: '80'
ProtocolVersion:
Type: String
Description: Protocol version of Target group
Default: 'HTTP1'
HealthCheckProtocol:
Type: String
Description: Health check protocol of Target group
Default: 'HTTP'
HealthCheckPath:
Type: String
Description: Health check path of Target group
Default: '/'
HealthCheckPort:
Type: String
Description: Health check port of Target group
Default: '80'
HealthyThresholdCount:
Type: String
Description: Healthy threshold count of Target group
Default: '5'
UnhealthyThresholdCount:
Type: String
Description: Unhealthy threshold count of Target group
Default: '2'
HealthCheckTimeout:
Type: String
Description: Health check timeout of Target group
Default: '5'
HealthCheckInterval:
Type: String
Description: Health check interval of Target group
Default: '30'
HealthCheckSuccessCode:
Type: String
Description: Health check success code of Target group
Default: '200'
LoadBalancerType:
Type: String
Description: Type of Load balancer
Default: 'application'
LoadBalancerName:
Type: String
Description: Name of Load balancer
LoadBalancerScheme:
Type: String
Description: Scheme of Load balancer
Default: 'internet-facing'
LoadBalancerIpAddressType:
Type: String
Description: IP address type of Load balancer
Default: 'ipv4'
AutoScalingGroupName:
Type: String
Description: Name of Auto scaling group
HealthCheckType:
Type: String
Description: Health check type of Auto scaling group
Default: 'ELB'
HealthCheckGracePeriod:
Type: String
Description: Health check grace period of Auto scaling group
Default: '300'
InstanceDesiredSize:
Type: String
Description: Instance desired size of Auto scaling group
Default: '1'
InstanceMinSize:
Type: String
Description: Instance minimum size of Auto scaling group
Default: '1'
InstanceMaxSize:
Type: String
Description: Instance maximum size of Auto scaling group
Default: '1'
ListenerProtocol:
Type: String
Description: Protocol of Listener
Default: 'HTTP'
ListenerPort:
Type: String
Description: Port of Listener
Default: '80'
ListenerType:
Type: String
Description: Type of Listener
Default: 'forward'
CPUHighAlarmName:
Type: String
Description: Name of CPU high alarm
CPULowAlarmName:
Type: String
Description: Name of CPU high alarm
Resources:
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Ref LaunchTemplateName
LaunchTemplateData:
ImageId: !Ref ImageID
InstanceType: !Ref InstanceType
KeyName: '{{resolve:ssm:ec2_keypair:1}}'
NetworkInterfaces:
- AssociatePublicIpAddress: 'False'
DeviceIndex: '0'
Groups:
- !ImportValue WebSGID
IamInstanceProfile:
Name: !ImportValue S3AccessInstanceProfile
UserData:
Fn::Base64: |
#!/bin/bash
#必要なパッケージ・モジュールをインストール
yum -y update
yum -y install python3 git python3-devel mysql mysql-devel gcc
pip3 install django
pip3 install mysqlclient
pip3 install gunicorn
amazon-linux-extras install nginx1
#AWS CLIプロファイルをダウンロード
aws s3 cp s3://django-config/config /root/.aws/config
aws s3 cp s3://django-config/credentials /root/.aws/credentials
#Nginxサービスの自動起動設定・開始
systemctl enable nginx
systemctl start nginx
#Staicファイル用ディレクトリの作成
mkdir /usr/share/nginx/html/static
#git clone用認証情報ファイルのダウンロード
aws s3 cp s3://django-config/.netrc /root/.netrc
#DjangoプロジェクトをGitHubから取得
git clone https://github.com/zeems31/Django-Tutorial.git /home/ec2-user/mysite
#Djangoプロジェクトの所有者をec2-userに変更
chown -R ec2-user:ec2-user /home/ec2-user/mysite
#settings.py内で使用するパスワード等を記述した外部ファイルをダウンロード・DB情報を置換
aws s3 cp s3://django-config/settings_secret.py /home/ec2-user/mysite/mysite/settings_secret.py
db_user=`aws ssm get-parameter --name db_user --query Parameter.Value`
db_password=`aws ssm get-parameter --name db_password --query Parameter.Value --with-decryption`
sed -i -e "s/ssm_DB_USER/$db_user/g" /home/ec2-user/mysite/mysite/settings_secret.py
sed -i -e "s/ssm_DB_PASSWORD/$db_password/g" /home/ec2-user/mysite/mysite/settings_secret.py
#上記ファイルの所有者をec2-userに変更
chown ec2-user:ec2-user /home/ec2-user/mysite/mysite/settings_secret.py
#Gunicornのサービスユニットファイルをダウンロード
aws s3 cp s3://django-config/gunicorn.service /etc/systemd/system/gunicorn.service
#Gunicornサービスユニットファイルの権限を変更
chmod 644 /etc/systemd/system/gunicorn.service
#Gunicornサービスを自動起動設定・開始
systemctl enable gunicorn.service
systemctl start gunicorn.service
#Nginx設定ファイルをダウンロード・ローカルIPアドレスを置換
aws s3 cp s3://django-config/mysite.conf /etc/nginx/conf.d/mysite.conf
localip=`curl http://169.254.169.254/latest/meta-data/local-ipv4`
sed -i -e "s/ec2-localip/$localip/g" /etc/nginx/conf.d/mysite.conf
#Nginxサービスを再起動
systemctl restart nginx
#StaticファイルをSTATIC_ROOTに記述したパスに配置
cd /home/ec2-user/mysite
python3 manage.py collectstatic
#Staticファイル用ディレクトリの所有者をec2-userに変更
chown -R ec2-user:ec2-user /usr/share/nginx/html/static
#migrateを実行
python3 manage.py migrate
#superuser作成用の環境変数を定義・superuserを作成
django_superuser_username=`aws ssm get-parameter --name django_superuser_username --query Parameter.Value --with-decryption --output text`
django_superuser_email=`aws ssm get-parameter --name django_superuser_email --query Parameter.Value --with-decryption --output text`
django_superuser_password=`aws ssm get-parameter --name django_superuser_password --query Parameter.Value --with-decryption --output text`
export DJANGO_SUPERUSER_USERNAME=$django_superuser_username
export DJANGO_SUPERUSER_EMAIL=$django_superuser_email
export DJANGO_SUPERUSER_PASSWORD=$django_superuser_password
python3 manage.py createsuperuser --noinput
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
TargetType: !Ref TargetType
Name: !Ref TargetGroupName
Protocol: !Ref Protocol
Port: !Ref Port
VpcId: !ImportValue VPCID
ProtocolVersion: !Ref ProtocolVersion
HealthCheckProtocol: !Ref HealthCheckProtocol
HealthCheckPath: !Ref HealthCheckPath
HealthCheckPort: !Ref HealthCheckPort
HealthyThresholdCount: !Ref HealthyThresholdCount
UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
HealthCheckTimeoutSeconds: !Ref HealthCheckTimeout
HealthCheckIntervalSeconds: !Ref HealthCheckInterval
Matcher:
HttpCode: !Ref HealthCheckSuccessCode
Tags:
- Key: Name
Value: !Ref TargetGroupName
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: !Ref LoadBalancerType
Name: !Ref LoadBalancerName
Scheme: !Ref LoadBalancerScheme
Subnets:
- !ImportValue ELBSubnet1aID
- !ImportValue ELBSubnet1cID
SecurityGroups:
- !ImportValue ELBSGID
IpAddressType: !Ref LoadBalancerIpAddressType
Tags:
- Key: Name
Value: !Ref LoadBalancerName
Listner:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref LoadBalancer
Protocol: !Ref ListenerProtocol
Port: !Ref ListenerPort
DefaultActions:
- Type: !Ref ListenerType
TargetGroupArn: !Ref TargetGroup
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: !Ref AutoScalingGroupName
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
VPCZoneIdentifier:
- !ImportValue WebSubnet1aID
- !ImportValue WebSubnet1cID
TargetGroupARNs:
- !Ref TargetGroup
HealthCheckType: !Ref HealthCheckType
HealthCheckGracePeriod: !Ref HealthCheckGracePeriod
DesiredCapacity: !Ref InstanceDesiredSize
MinSize: !Ref InstanceMinSize
MaxSize: !Ref InstanceMaxSize
Tags:
- Key: Name
Value: !Ref AutoScalingGroupName
PropagateAtLaunch: 'True'
CPUHighAutoScalingPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
PolicyType: 'SimpleScaling'
AutoScalingGroupName: !Ref AutoScalingGroup
AdjustmentType: 'ChangeInCapacity'
ScalingAdjustment: 1
Cooldown: '30'
CPULowAutoScalingPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
PolicyType: 'SimpleScaling'
AutoScalingGroupName: !Ref AutoScalingGroup
AdjustmentType: 'ChangeInCapacity'
ScalingAdjustment: -1
Cooldown: '30'
CPUHighAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
Namespace: 'AWS/EC2'
MetricName: 'CPUUtilization'
Dimensions:
- Name: 'AutoScalingGroupName'
Value: !Ref AutoScalingGroup
Statistic: 'Average'
Period: 300
ComparisonOperator: 'GreaterThanThreshold'
Threshold: 70
EvaluationPeriods: 1
TreatMissingData: 'breaching'
AlarmName: !Ref CPUHighAlarmName
AlarmDescription: !Ref CPUHighAlarmName
AlarmActions:
- !Ref CPUHighAutoScalingPolicy
CPULowAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
Namespace: 'AWS/EC2'
MetricName: 'CPUUtilization'
Dimensions:
- Name: 'AutoScalingGroupName'
Value: !Ref AutoScalingGroup
Statistic: 'Average'
Period: 300
ComparisonOperator: 'LessThanThreshold'
Threshold: 30
EvaluationPeriods: 1
TreatMissingData: 'missing'
AlarmName: !Ref CPULowAlarmName
AlarmDescription: !Ref CPULowAlarmName
AlarmActions:
- !Ref CPULowAutoScalingPolicy
#AWS CLIで構築
AWS CLIを使用して、事前準備とスタックの作成を実行します。
今回はWindowsにて以下のようなPowerShellスクリプトを作成して実行してみました。
$db_user = "<データベースのユーザー名>"
$db_password = "<データベースのユーザーのパスワード>"
$django_superuser_username = "<Djangoスーパーユーザーの名前>"
$django_superuser_password = "<Djangoスーパーユーザーのパスワード>"
$django_superuser_email = "<Djangoスーパーユーザーのメールアドレス>"
$ec2_keypair = "<EC2インスタンスで使用するキーペア名>"
$stack_name = "<作成するスタックの名前>"
$template_url = "<テンプレートのURL>"
aws ec2 create-key-pair --key-name $ec2_keypair --query 'KeyMaterial' --output text | out-file -encoding ascii -filepath ($ec2_keypair + ".pem")
aws ssm put-parameter --name "db_user" --value $db_user --type String --overwrite
aws ssm put-parameter --name "db_password" --value $db_password --type SecureString --overwrite
aws ssm put-parameter --name "django_superuser_username" --value $django_superuser_username --type SecureString --overwrite
aws ssm put-parameter --name "django_superuser_password" --value $django_superuser_password --type SecureString --overwrite
aws ssm put-parameter --name "django_superuser_email" --value $django_superuser_email --type SecureString --overwrite
aws ssm put-parameter --name "ec2_keypair" --value $ec2_keypair --type String --overwrite
aws cloudformation create-stack --stack-name $stack_name --template-url $template_url --capabilities CAPABILITY_NAMED_IAM
#トラブルシュート
#####各スタック作成の依存関係
root.ymlで各レイヤーのテンプレートから分割したスタックを作成するにあたって、まだネットワークレイヤーのリソースが作成されていないのに、同時にセキュリティレイヤーの作成が始まってしまいました。(まだVPCがないのでセキュリティグループの作成ができないなど...)
そのため、「DependsOn」を使用して明示的に依存関係を指定することで回避しています。
#####EC2インスタンスへのインスタンスプロファイルの割り当て
AWS マネジメントコンソールからEC2インスタンスにIAMロールをアタッチする時には意識していませんでしたが、実際にはインスタンスプロファイルを介してIAMロールがアタッチされる動作のようです。
そのためCloudFormationでIAMロールを作成する際に併せてインスタンスプロファイルも作成する必要がありました。
ちなみにマネジメントコンソールでIAMロールを作成する時はインスタンスプロファイルが自動的に作成されます。
以下の記事を参考にしました。
#####ユーザーデータ処理で Systems Manager のパラメータを取得できない
ユーザーデータ処理の途中でDB接続情報やDjangoのsuperuser情報を Systems Manager パラメータストアから取得するようにAWS CLIで実行していますが、AWS CLIの設定と認証情報がないことで実行に失敗しました。
そのため、事前にS3バケットにAWS CLIの設定ファイルと認証情報ファイルをアップロードしておき、ユーザーデータ処理の中でダウンロードするようにしました。
#####SecureStringタイプの Systems Manager パラメータの動的参照
root.yml内で Type が「AWS::CloudFormation::Stack」のリソースのパラメータとして SecureStringタイプの Systems Manager パラメータを指定しようとしましたが、エラーが発生しました。
CloudFormation テンプレート内で SecureString の動的参照をサポートするリソースの種類が決まっているようです。
#####マルチAZ
RDS作成時に以下のエラーにより作成に失敗しました。
Requesting a specific availability zone is not valid for Multi-AZ instances.
原因はマルチAZを有効にしたうえで、Availability Zoneを指定していたことでした。
以下の記事を参考にしました。
#あとがき
CloudFormationでテンプレートを作成していると、GUIで何気なくデフォルトで設定していた項目がどういった設定値であるのかを調べる良いきっかけにもなりました。
まだ最適化できる部分が多々あると思うのでブラッシュアップしていければと思います。