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
ほとんど参考サイトのコピペです。これではだめです。もっと修行しないといけない。
コメント