Полный SAM-шаблон CRC
SAM (Serverless Application Model) -- расширение CloudFormation с упрощённым синтаксисом для serverless-ресурсов.
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Cloud Resume Challenge - Complete Infrastructure
Parameters:
DomainName:
Type: String
Default: resume.ivanpetrov.com
HostedZoneId:
Type: String
Description: Route53 Hosted Zone ID
Globals:
Function:
Runtime: python3.12
Architectures: [arm64]
Timeout: 10
MemorySize: 128
Environment:
Variables:
TABLE_NAME: !Ref VisitorCounterTable
Resources:
# ========== BACKEND ==========
VisitorCounterTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: visitor-counter
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Tags:
- Key: project
Value: crc
VisitorCounterFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: visitor-counter
CodeUri: backend/
Handler: lambda_function.lambda_handler
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref VisitorCounterTable
Events:
GetCount:
Type: HttpApi
Properties:
Path: /count
Method: GET
ApiId: !Ref CrcApi
CrcApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: prod
CorsConfiguration:
AllowOrigins:
- !Sub 'https://${DomainName}'
AllowMethods:
- GET
- OPTIONS
AllowHeaders:
- Content-Type
MaxAge: 86400
# ========== FRONTEND ==========
FrontendBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub '${DomainName}-frontend'
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: project
Value: crc
FrontendBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref FrontendBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowCloudFrontOAC
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub '${FrontendBucket.Arn}/*'
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CdnDistribution}'
OAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: crc-oac
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
Certificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Ref DomainName
HostedZoneId: !Ref HostedZoneId
CdnDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
DefaultRootObject: index.html
HttpVersion: http2and3
PriceClass: PriceClass_100
Aliases:
- !Ref DomainName
ViewerCertificate:
AcmCertificateArn: !Ref Certificate
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.2_2021
Origins:
- Id: S3Origin
DomainName: !GetAtt FrontendBucket.RegionalDomainName
OriginAccessControlId: !Ref OAC
S3OriginConfig:
OriginAccessIdentity: ''
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
Compress: true
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
AllowedMethods: [GET, HEAD]
CachedMethods: [GET, HEAD]
CustomErrorResponses:
- ErrorCode: 403
ResponsePagePath: /index.html
ResponseCode: '200'
ErrorCachingMinTTL: 300
DnsRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref HostedZoneId
Name: !Ref DomainName
Type: A
AliasTarget:
DNSName: !GetAtt CdnDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
Outputs:
ApiUrl:
Value: !Sub 'https://${CrcApi}.execute-api.${AWS::Region}.amazonaws.com/prod/count'
CloudFrontUrl:
Value: !Sub 'https://${DomainName}'
CloudFrontDistributionId:
Value: !Ref CdnDistribution
S3BucketName:
Value: !Ref FrontendBucket
Деплой
# Build
sam build
# Первый деплой (guided)
sam deploy --guided
# Stack Name: crc-production
# Region: us-east-1
# Parameter DomainName: resume.ivanpetrov.com
# Parameter HostedZoneId: Z1234567890
# Последующие деплои
sam deploy
# Удаление
sam delete --stack-name crc-production
CDK как альтернатива
# cdk/crc_stack.py
from aws_cdk import (
Stack, Duration, RemovalPolicy, CfnOutput,
aws_dynamodb as dynamodb,
aws_lambda as _lambda,
aws_apigatewayv2 as apigwv2,
aws_s3 as s3,
aws_cloudfront as cloudfront,
aws_cloudfront_origins as origins,
aws_certificatemanager as acm,
aws_route53 as route53,
aws_route53_targets as targets,
)
from constructs import Construct
class CrcStack(Stack):
def __init__(self, scope: Construct, id: str, domain: str, **kwargs):
super().__init__(scope, id, **kwargs)
# DynamoDB
table = dynamodb.Table(self, 'Counter',
table_name='visitor-counter',
partition_key=dynamodb.Attribute(name='id', type=dynamodb.AttributeType.STRING),
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
removal_policy=RemovalPolicy.DESTROY
)
# Lambda
fn = _lambda.Function(self, 'CounterFn',
runtime=_lambda.Runtime.PYTHON_3_12,
handler='lambda_function.lambda_handler',
code=_lambda.Code.from_asset('backend/'),
architecture=_lambda.Architecture.ARM_64,
timeout=Duration.seconds(10),
environment={'TABLE_NAME': table.table_name}
)
table.grant_read_write_data(fn)
# S3
bucket = s3.Bucket(self, 'Frontend',
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True
)
# CloudFront
dist = cloudfront.Distribution(self, 'CDN',
default_behavior=cloudfront.BehaviorOptions(
origin=origins.S3BucketOrigin.with_origin_access_control(bucket)
),
default_root_object='index.html'
)
CfnOutput(self, 'DistributionUrl', value=dist.distribution_domain_name)
Управление стеком
# Просмотр ресурсов стека
aws cloudformation describe-stack-resources --stack-name crc-production
# Просмотр outputs
aws cloudformation describe-stacks --stack-name crc-production \
--query 'Stacks[0].Outputs'
# Detect drift
aws cloudformation detect-stack-drift --stack-name crc-production
# Rollback
aws cloudformation rollback-stack --stack-name crc-production