AWSの静的サイトホスティングパターン構築用CloudFormation作ってみた
やりつくされた感のあるAWSの静的サイトホスティングパターンのCloudFormationでやってみる系のやつです。まぁ先人が色々としてくださっているので今更ネタです。
そしてAWSは5年ほど触ってますが、いまだに初心者を脱出できていないですし、コピペしかできないへっぽこです。なんで急にCloudFormationやってみようと思ったかというと静的サイトホスティング二重化をTerraformで一本でまとめてやりたいなぁと思っていて、その練習がてらどうせならCloudFormation勉強してみようと思ってやってみました。
2021年1月31時点に構築しました。
aws-cli/1.18.39 Python/3.7.7 Linux/5.4.0-64-generic botocore/1.15.3
◆作成したもの
1.ディレクトリ構成
.
|-- create_stacks.sh
|-- delete_stacks.sh
`-- yml
|-- acmsetting.yml
|-- makehostedzone.yml
`-- static-site.yml
2.シェルスクリプト群
「create_stacks.sh sample.com」とか「delete_stacks.sh」で起動すると一気に静的サイトホスティングのCloudFormationテンプレート呼び出してくれます。
・create_stacks.sh
#!/bin/bash
SLEEP_TIME=30
R53_YML_NAME="makehostedzone.yml"
R53_STACK_NAME="route53set"
ACM_YML_NAME="acmsetting.yml"
ACM_STACK_NAME="acmbuild"
STATIC_YML_NAME="static-site.yml"
STATIC_STACK_NAME="staticsitebuild"
BREAK_WORD="CREATE_COMPLETE"
if [ $# != 1 ]; then
echo 'Empty Domain! Please [./create_stacks.sh yourdomain]'
exit 1
fi
domain=$1
echo '*** Created route53 hostedzone ***'
aws cloudformation create-stack --stack-name ${R53_STACK_NAME} \
--template-body file://`pwd`/yml/${R53_YML_NAME} \
--parameters ParameterKey=DomainName,ParameterValue=${domain}
while true
do
sleep ${SLEEP_TIME}
cloudformationstatus=$(aws cloudformation describe-stacks --stack-name ${R53_STACK_NAME})
stat=$(echo ${cloudformationstatus} | jq -r ".Stacks[0].StackStatus")
if [ "$stat"=${BREAK_WORD} ]; then
break
fi
done
#Get hostedzoneId
route53result=$(aws route53 list-hosted-zones-by-name --dns-name ${domain})
hosted=$(echo ${route53result} | jq -r ".HostedZones[0].Id")
hz=$(echo ${hosted} | sed -e "s!/hostedzone/!!g")
echo '*** Create ACM ***'
if [ "$hz" = "" ]; then
echo 'Fail get HostedZoneId'
exit 1
fi
aws cloudformation create-stack \
--region us-east-1 \
--stack-name ${ACM_STACK_NAME} \
--template-body file://`pwd`/yml/${ACM_YML_NAME} \
--parameters ParameterKey=DomainName,ParameterValue=${domain} ParameterKey=HostedZone,ParameterValue=${hz}
echo '*** Please Setting Your Domain Nameserve ***'
route53records=$(aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/${hz})
array=$(echo ${route53records} | jq -r ".ResourceRecordSets[0].ResourceRecords[].Value")
for i in ${array[@]}
do
echo ${i}
done
#Check Certificate
while true
do
sleep ${SLEEP_TIME}
cloudformationstatus=$(aws cloudformation describe-stacks --region us-east-1 --stack-name ${ACM_STACK_NAME})
stat=$(echo ${cloudformationstatus} | jq -r ".Stacks[0].StackStatus")
if [ "$stat"=${BREAK_WORD} ]; then
break
fi
done
cloudformationstatus=$(aws cloudformation describe-stacks --region us-east-1 --stack-name ${ACM_STACK_NAME})
acmarn=$(echo ${cloudformationstatus} | jq -r ".Stacks[0].Outputs[].OutputValue")
echo '*** Create S3 and CloudFront ***'
aws cloudformation create-stack \
--stack-name ${STATIC_STACK_NAME} \
--template-body file://`pwd`/yml/${STATIC_YML_NAME} \
--parameters ParameterKey=SystemName,ParameterValue=prd ParameterKey=HostDomain,ParameterValue=${domain} ParameterKey=ACMCertificate,ParameterValue=${acmarn}
echo 'Static Site Build Completed!'
・delete_stacks.sh
#!/bin/bash
R53_STACK_NAME="route53set"
ACM_STACK_NAME="acmbuild"
STATIC_STACK_NAME="staticsitebuild"
BREAK_WORD="CREATE_COMPLETE"
SLEEP_TIME=30
if [ $# != 1 ]; then
echo 'Empty Domain! Please [./deletestack.sh yourdomain]'
exit 1
fi
domain=$1
BUCKET_NAME=prd-${domain}-logs
echo '*** Delete S3 object ***'
objectdel=$(aws s3 rm s3://${BUCKET_NAME} --recursive)
aws cloudformation delete-stack --stack-name ${STATIC_STACK_NAME}
echo '*** Checked DNS Records ***'
route53result=$(aws route53 list-hosted-zones-by-name --dns-name ${domain})
hosted=$(echo ${route53result} | jq -r ".HostedZones[0].Id")
hz=$(echo ${hosted} | sed -e "s!/hostedzone/!!g")
while true
do
route53records=$(aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/${hz})
array=$(echo ${route53records} | jq -r ".ResourceRecordSets[].Type")
if [[ $(printf '%s\n' "${array[@]}" | grep -qx "A"; echo -n ${?} ) -eq 0 ]]; then
sleep ${SLEEP_TIME}
else
break
fi
done
echo '*** Delete ACM ***'
aws cloudformation delete-stack --region us-east-1 --stack-name ${ACM_STACK_NAME}
echo '*** Delete Route53 ***'
route53records=$(aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/${hz})
RESOURCE_VALUE=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[]| if .Type == "CNAME" then .ResourceRecords[].Value else empty end')
DNS_NAME=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[] | if .Type == "CNAME" then .Name else empty end')
RECORD_TYPE="CNAME"
TTL=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[]| if .Type == "CNAME" then .TTL else empty end')
JSON_FILE=`mktemp`
(
cat < $JSON_FILE
echo "Deleting DNS Record set"
aws route53 change-resource-record-sets --hosted-zone-id ${hz} --change-batch file://$JSON_FILE
aws cloudformation delete-stack --stack-name ${R53_STACK_NAME}
echo 'Delete Complete!'
3.CloudFormationテンプレートYAML
Route53のホストゾーン登録するテンプレートです。
・makehostedzone.yml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
DomainName:
Type: String
Default: sample.com
Resources:
DNS:
Type: "AWS::Route53::HostedZone"
Properties:
Name: !Ref DomainName
HostedZoneConfig:
Comment: !Join
- " "
- ["My hosted zone for", !Ref DomainName]
HostedZoneTags:
- Key: Name
Value: !Sub ${DomainName}-Route53HostedZone
・acmsetting.yml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
SystemName:
Type: String
Default: mysystem
DomainName:
Type: String
Default: devstatic
HostedZone:
Type: String
Default: ZXXXXXXXXXXX
Resources:
Certificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
DomainValidationOptions:
- DomainName: !Ref DomainName
HostedZoneId: !Ref HostedZone
SubjectAlternativeNames:
- !Sub '*.${DomainName}'
ValidationMethod: DNS
Outputs:
ACMCertificateARN:
Value: !Ref Certificate
Export:
Name: !Sub ${SystemName}-CertificateARN
S3+CloudFrontのテンプレートです。Route53のAレコード追加もします。
・static-site.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: "aws static site cloudfront and s3"
Parameters:
SystemName:
Type: String
Default: mysystem
HostDomain:
Type: String
Default: sample.com
HostedZoneID:
Type: String
Default: Z2FDTNDATAQYW2
ACMCertificate:
Type: String
Default: arn:aws:acm:us-east-1:XXXXX:certificate/XXXXXXXX
Resources:
S3BucketLogs:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${SystemName}-${HostDomain}-logs
AccessControl: LogDeliveryWrite
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
S3BucketWebStatic:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${SystemName}-${HostDomain}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
LoggingConfiguration:
DestinationBucketName: !Ref S3BucketLogs
LogFilePrefix: 'origin/'
BucketPolicyWebStatic:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3BucketWebStatic
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource: !Sub '${S3BucketWebStatic.Arn}/*'
Principal:
CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
CloudFrontOriginAccessIdentity:
Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
Properties:
CloudFrontOriginAccessIdentityConfig:
Comment: "CloudFront OAI for Static Web Site"
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
- !Sub ${HostDomain}
- !Sub www.${HostDomain}
CustomErrorResponses:
- ErrorCachingMinTTL: 10
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /404.html
DefaultCacheBehavior:
Compress: true
DefaultTTL: 0
ForwardedValues:
QueryString: true
MaxTTL: 0
MinTTL: 0
TargetOriginId: 'WebStatic-S3'
# httpの通信はhttpsにリダイレクトする
ViewerProtocolPolicy: 'redirect-to-https'
Enabled: true
HttpVersion: 'http2'
DefaultRootObject: 'index.html'
IPV6Enabled: true
Logging:
Bucket: !GetAtt S3BucketLogs.DomainName
IncludeCookies: false
Prefix: 'cdn/'
Origins:
# 作成直後にStatusCode 307とならないように、Region指定の方式
# https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-http-307-response/
- DomainName: !Join ["", [!Ref S3BucketWebStatic, !Sub ".s3-${AWS::Region}.amazonaws.com"]]
Id: 'WebStatic-S3'
S3OriginConfig:
OriginAccessIdentity:
!Join ['', ['origin-access-identity/cloudfront/', !Ref CloudFrontOriginAccessIdentity]]
PriceClass: 'PriceClass_All'
ViewerCertificate:
SslSupportMethod: sni-only
AcmCertificateArn: !Ref ACMCertificate
ZoneApexRecord:
Type: 'AWS::Route53::RecordSetGroup'
Properties:
HostedZoneName: !Sub "${HostDomain}."
RecordSets:
- Name: !Ref HostDomain
Type: A
AliasTarget:
HostedZoneId: !Ref HostedZoneID
DNSName: !GetAtt CloudFrontDistribution.DomainName
HostRecord:
Type: 'AWS::Route53::RecordSetGroup'
Properties:
HostedZoneName: !Sub "${HostDomain}."
RecordSets:
- Name: !Sub www.${HostDomain}
Type: A
AliasTarget:
HostedZoneId: !Ref HostedZoneID
DNSName: !GetAtt CloudFrontDistribution.DomainName
Outputs:
S3BucketLogsName:
Description: "S3 Bucket for keeping access logs."
Value: !Ref S3BucketLogs
S3BucketWebStaticName:
Description: "S3 Bucket for deploying static site."
Value: !Ref S3BucketWebStatic
CloudFrontDomainName:
Description: "CloudFront DomainName."
Value: !GetAtt CloudFrontDistribution.DomainName
ZoneApexRecordName:
Description: "Route53 ZoneApex RecordSet."
Value: !Ref ZoneApexRecord
HostRecordName:
Description: "Route53 Host RecordSet."
Value: !Ref HostRecord
今度はGoogleのCloud Deployment Manager を使ってGoogleCloudのテンプレートやってみようかなと思ったりしています。いつになることやら・・・
没になったけどfreenomのNameServerにShellキックで登録してやりたかった。リセラーしかAPIで登録できないみたい。備忘録で置いときます。使ってもエラーなっちゃうし。
#!/bin/bash
array=($(aws route53 create-hosted-zone --name yourdomain --caller-reference `date +%Y-%m-%d_%H-%M-%S` | jq -r ".DelegationSet.NameServers[]"))
nsstr='&nameserver='
cmd1='curl -X POST https://api.freenom.com/v2/domain/register.xml -d '
cmd2=' "tohonokai.cf'
frontcmd="${cmd1}${cmd2}"
for i in ${array[@]}
do
cmd="${cmd}${nsstr}${i}"
done
backcmd='&email=yourmail@example.com&password=yourpassword&domaintype=FREE"'
fullcmd="${frontcmd}${cmd}${backcmd}"
eval $fullcmd
ほとんど参考サイトのコピペです。これではだめです。もっと修行しないといけない。
コメント