1066 lines
35 KiB
YAML
1066 lines
35 KiB
YAML
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
# SPDX-License-Identifier: MIT-0
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
# software and associated documentation files (the "Software"), to deal in the Software
|
|
# without restriction, including without limitation the rights to use, copy, modify,
|
|
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
AWSTemplateFormatVersion: 2010-09-09
|
|
Description: >-
|
|
Amazon ECS Service for Nextcloud with Serverless RDS, EFS & S3
|
|
|
|
Metadata:
|
|
AWS::CloudFormation::Interface:
|
|
ParameterGroups:
|
|
- Label:
|
|
default: 'Deployment Identifiers'
|
|
Parameters:
|
|
- DeploymentName
|
|
- Label:
|
|
default: 'Nextcloud Configuration'
|
|
Parameters:
|
|
- NextCloudAdminUser
|
|
- NextCloudAdminPassword
|
|
- NextCloudVersion
|
|
- Label:
|
|
default: 'Database Configuration'
|
|
Parameters:
|
|
- DbUserName
|
|
- DbPassword
|
|
- NextCloudDbName
|
|
- DbMinCapacity
|
|
- DbMaxCapacity
|
|
- Label:
|
|
default: 'AWS ECS Configuration'
|
|
Parameters:
|
|
- EcsTaskCpu
|
|
- EcsTaskMem
|
|
- EcsCapacityProvider
|
|
- EcsMinCapacity
|
|
- EcsInitialDesiredCapacity
|
|
- EcsMaxCapacity
|
|
- EcsTargetCpuUtilization
|
|
- SuspendAutoScaling
|
|
- Label:
|
|
default: 'ElastiCache Redis Configuration'
|
|
Parameters:
|
|
- RedisTshirtSize
|
|
- Label:
|
|
default: 'VPC Configuration'
|
|
Parameters:
|
|
- IsolationLevel
|
|
- Label:
|
|
default: 'Custom Domain Configuration (optional)'
|
|
Parameters:
|
|
- Domain
|
|
- Route53Zone
|
|
|
|
Parameters:
|
|
DeploymentName:
|
|
Description: Deployment Name (Unique name, no punctuation)
|
|
Type: String
|
|
Default: nc-serverless
|
|
Route53Zone:
|
|
Description: Route53 Public Hosted Zone ID to configure custom domain
|
|
Type: String
|
|
Default: ""
|
|
Domain:
|
|
Description: Speficy a full quallified domain name to use with this Nextcloud deployment
|
|
Type: String
|
|
Default: ""
|
|
DbUserName:
|
|
Description: Database Master Username
|
|
Type: String
|
|
AllowedPattern: ^[a-zA-Z0-9]{6,32}
|
|
Default: nextcloud
|
|
DbPassword:
|
|
Description: Database Master Password
|
|
Type: String
|
|
NoEcho: True
|
|
DbMinCapacity:
|
|
Description: Minimal Aurora Capacity Units Provisioned
|
|
Type: Number
|
|
MinValue: 2
|
|
MaxValue: 384
|
|
Default: 2
|
|
DbMaxCapacity:
|
|
Description: Maxmimal Aurora Capacity Units Provisioned
|
|
Type: Number
|
|
MinValue: 2
|
|
MaxValue: 384
|
|
Default: 8
|
|
NextCloudAdminUser:
|
|
Description: Initial Nextcloud Admin User
|
|
Type: String
|
|
AllowedPattern: ^[a-zA-Z0-9]{6,32}
|
|
Default: ncadmin
|
|
NextCloudAdminPassword:
|
|
Description: Initial Nextcloud Admin Password (change after first login)
|
|
Type: String
|
|
NoEcho: True
|
|
NextCloudDbName:
|
|
Description: Nextcloud Database Name
|
|
Type: String
|
|
AllowedPattern: ^[a-zA-Z0-9]{6,32}
|
|
Default: nextcloud
|
|
NextCloudVersion:
|
|
Description: Nextcloud Version to be deployed (no downgrade possible)
|
|
Type: String
|
|
Default: 21.0.1
|
|
S3SecretRotationSerial:
|
|
Description: In order to rotate long-term S3 credentials increase by 1
|
|
Type: Number
|
|
Default: 1
|
|
IsolationLevel:
|
|
Description: VPC Isolation Level (see README.md)
|
|
Type: String
|
|
Default: Private
|
|
AllowedValues:
|
|
- PrivateHA
|
|
- Private
|
|
- Public
|
|
EcsCapacityProvider:
|
|
Description: Optional select Spot Fargate Tasks for Development Environments
|
|
Type: String
|
|
Default: FARGATE
|
|
AllowedValues:
|
|
- FARGATE_SPOT
|
|
- FARGATE
|
|
EcsTaskCpu:
|
|
Description: CPU Units to provision per ECS Container
|
|
Type: Number
|
|
Default: 1024
|
|
AllowedValues:
|
|
- 256
|
|
- 512
|
|
- 1024
|
|
- 2048
|
|
- 4096
|
|
EcsTaskMem:
|
|
Description: Memory to provision per ECS Container (MB)
|
|
Type: Number
|
|
Default: 2048
|
|
EcsMinCapacity:
|
|
Description: Minimum number of ECS Containers to provision
|
|
Type: Number
|
|
Default: 1
|
|
EcsInitialDesiredCapacity:
|
|
Description: Initial Desired number of ECS Containers to provision
|
|
Type: Number
|
|
Default: 1
|
|
EcsMaxCapacity:
|
|
Description: Maximum number of ECS Containers to provision
|
|
Type: Number
|
|
Default: 25
|
|
EcsTargetCpuUtilization:
|
|
Description: Threshold to be reached for scaling in/out
|
|
Type: Number
|
|
Default: 50
|
|
SuspendAutoScaling:
|
|
Description: Suspend Auto Scaling for Maintanence
|
|
Type: String
|
|
Default: false
|
|
RedisTshirtSize:
|
|
Description: ElasticCache Redis Node Size
|
|
Type: String
|
|
Default: cache.t3.small
|
|
AllowedValues:
|
|
- cache.t3.micro
|
|
- cache.t3.small
|
|
- cache.t3.medium
|
|
- cache.m5.large
|
|
- cache.m5.xlarge
|
|
- cache.m5.2xlarge
|
|
- cache.m5.4xlarge
|
|
- cache.m5.12xlarge
|
|
- cache.m5.24xlarge
|
|
- cache.r5.large
|
|
- cache.r5.xlarge
|
|
- cache.r5.2xlarge
|
|
- cache.r5.4xlarge
|
|
- cache.r5.12xlarge
|
|
- cache.r5.24xlarge
|
|
|
|
Mappings:
|
|
Config:
|
|
Container:
|
|
Uid: 33
|
|
Gid: 0
|
|
Permission: "0777"
|
|
|
|
Conditions:
|
|
CustomDomain: !Not [!And [!Equals [!Ref Domain, ""], !Equals [!Ref Route53Zone, ""]]]
|
|
PrivateSubnets: !Not [!Equals [!Ref IsolationLevel, Public]]
|
|
|
|
Resources:
|
|
VpcStack:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: https://f7o-quickstart.s3.eu-west-1.amazonaws.com/aws-serverless-nextcloud/simplevpc.yaml
|
|
TimeoutInMinutes: 60
|
|
Parameters:
|
|
EnvironmentName: !Ref DeploymentName
|
|
IsolationLevel: !Ref IsolationLevel
|
|
|
|
RdsStack:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: https://f7o-quickstart.s3.eu-west-1.amazonaws.com/aws-serverless-nextcloud/db-cluster.yaml
|
|
TimeoutInMinutes: 60
|
|
Parameters:
|
|
DbUserName: !Ref DbUserName
|
|
DbPassword: !Ref DbPassword
|
|
DbName: nextcloud
|
|
DbMinCapacity: !Ref DbMinCapacity
|
|
DbMaxCapacity: !Ref DbMaxCapacity
|
|
PrivateSubnets: !GetAtt VpcStack.Outputs.PrivateSubnets
|
|
VpcId: !GetAtt VpcStack.Outputs.VPC
|
|
FargateSecGroupId: !Ref EcsSecurityGroup
|
|
|
|
EcsCluster:
|
|
Type: 'AWS::ECS::Cluster'
|
|
Properties:
|
|
CapacityProviders:
|
|
- FARGATE
|
|
- FARGATE_SPOT
|
|
Configuration:
|
|
ExecuteCommandConfiguration:
|
|
Logging: DEFAULT
|
|
|
|
EcsSecurityGroup:
|
|
Type: 'AWS::EC2::SecurityGroup'
|
|
Properties:
|
|
GroupDescription: ECS Security Group
|
|
VpcId: !GetAtt VpcStack.Outputs.VPC
|
|
|
|
EcsSecurityGroupHTTPinbound:
|
|
Type: 'AWS::EC2::SecurityGroupIngress'
|
|
Properties:
|
|
GroupId: !Ref EcsSecurityGroup
|
|
IpProtocol: tcp
|
|
FromPort: '80'
|
|
ToPort: '80'
|
|
SourceSecurityGroupId: !Ref ElbSecurityGroup
|
|
|
|
CloudwatchLogsGroup:
|
|
Type: 'AWS::Logs::LogGroup'
|
|
Properties:
|
|
LogGroupName: !Join
|
|
- '-'
|
|
- - !Sub ${DeploymentName}
|
|
- !Ref 'AWS::StackName'
|
|
RetentionInDays: 14
|
|
DataBucket:
|
|
DeletionPolicy: Retain
|
|
Type: 'AWS::S3::Bucket'
|
|
Properties:
|
|
VersioningConfiguration:
|
|
Status: Enabled
|
|
BucketEncryption:
|
|
ServerSideEncryptionConfiguration:
|
|
- BucketKeyEnabled: true
|
|
ServerSideEncryptionByDefault:
|
|
SSEAlgorithm: AES256
|
|
BucketUser:
|
|
Type: AWS::IAM::User
|
|
Properties:
|
|
Policies:
|
|
- PolicyName: s3-access
|
|
PolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- s3:*
|
|
Resource: !Sub arn:aws:s3:::${DataBucket}
|
|
- Effect: Allow
|
|
Action:
|
|
- s3:*
|
|
Resource: !Sub arn:aws:s3:::${DataBucket}/*
|
|
- Effect: Deny
|
|
Action:
|
|
- s3:DeleteBucket*
|
|
- s3:PutBucketPolicy
|
|
- s3:PutEncryptionConfiguration
|
|
Resource: "*"
|
|
- Effect: Allow
|
|
Action:
|
|
- s3:GetBucketLocation
|
|
Resource: arn:aws:s3:::*
|
|
BucketUserCredentials:
|
|
Type: AWS::IAM::AccessKey
|
|
Properties:
|
|
Serial: !Ref S3SecretRotationSerial
|
|
Status: Active
|
|
UserName: !Ref BucketUser
|
|
|
|
Efs:
|
|
Type: AWS::EFS::FileSystem
|
|
Properties:
|
|
Encrypted: true
|
|
EfsMountTarget1:
|
|
Type: AWS::EFS::MountTarget
|
|
Properties:
|
|
FileSystemId:
|
|
Ref: Efs
|
|
SubnetId: !Select [0, !Split [ ',', !GetAtt VpcStack.Outputs.PrivateSubnets ]]
|
|
SecurityGroups:
|
|
- Ref: "EfsSecurityGroup"
|
|
EfsMountTarget2:
|
|
Type: AWS::EFS::MountTarget
|
|
Properties:
|
|
FileSystemId:
|
|
Ref: Efs
|
|
SubnetId: !Select [ 1, !Split [ ',', !GetAtt VpcStack.Outputs.PrivateSubnets ] ]
|
|
SecurityGroups:
|
|
- Ref: "EfsSecurityGroup"
|
|
EfsAPNextcloud:
|
|
Type: AWS::EFS::AccessPoint
|
|
Properties:
|
|
FileSystemId: !Ref Efs
|
|
RootDirectory:
|
|
Path: !Sub /${DeploymentName}/nextcloud
|
|
CreationInfo:
|
|
OwnerUid: !FindInMap [Config, Container, Uid]
|
|
OwnerGid: !FindInMap [Config, Container, Gid]
|
|
Permissions: !FindInMap [Config, Container, Permission]
|
|
EfsAPConfig:
|
|
Type: AWS::EFS::AccessPoint
|
|
Properties:
|
|
FileSystemId: !Ref Efs
|
|
RootDirectory:
|
|
Path: !Sub /${DeploymentName}/config
|
|
CreationInfo:
|
|
OwnerUid: !FindInMap [Config, Container, Uid]
|
|
OwnerGid: !FindInMap [Config, Container, Gid]
|
|
Permissions: !FindInMap [Config, Container, Permission]
|
|
EfsAPApps:
|
|
Type: AWS::EFS::AccessPoint
|
|
Properties:
|
|
FileSystemId: !Ref Efs
|
|
RootDirectory:
|
|
Path: !Sub /${DeploymentName}/apps
|
|
CreationInfo:
|
|
OwnerUid: !FindInMap [Config, Container, Uid]
|
|
OwnerGid: !FindInMap [Config, Container, Gid]
|
|
Permissions: !FindInMap [Config, Container, Permission]
|
|
EfsAPData:
|
|
Type: AWS::EFS::AccessPoint
|
|
Properties:
|
|
FileSystemId: !Ref Efs
|
|
RootDirectory:
|
|
Path: !Sub /${DeploymentName}/data
|
|
CreationInfo:
|
|
OwnerUid: !FindInMap [Config, Container, Uid]
|
|
OwnerGid: !FindInMap [Config, Container, Gid]
|
|
Permissions: !FindInMap [Config, Container, Permission]
|
|
EfsSecurityGroup:
|
|
Type: 'AWS::EC2::SecurityGroup'
|
|
Properties:
|
|
GroupDescription: ECS Security Group
|
|
VpcId: !GetAtt VpcStack.Outputs.VPC
|
|
|
|
EfsSecurityGroupNFSinbound:
|
|
Type: 'AWS::EC2::SecurityGroupIngress'
|
|
Properties:
|
|
GroupId: !Ref EfsSecurityGroup
|
|
IpProtocol: tcp
|
|
FromPort: '2049'
|
|
ToPort: '2049'
|
|
SourceSecurityGroupId: !Ref EcsSecurityGroup
|
|
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/bind-mounts.html
|
|
EcsTaskDefinition:
|
|
Type: 'AWS::ECS::TaskDefinition'
|
|
Properties:
|
|
Family: !Join
|
|
- ''
|
|
- - !Ref 'AWS::StackName'
|
|
- '-ecs-nextcloud'
|
|
NetworkMode: awsvpc
|
|
RequiresCompatibilities:
|
|
- "FARGATE"
|
|
ExecutionRoleArn: !GetAtt ECSTaskExecRole.Arn
|
|
TaskRoleArn: !GetAtt ECSTaskRole.Arn
|
|
Cpu: !Ref EcsTaskCpu
|
|
Memory: !Ref EcsTaskMem
|
|
ContainerDefinitions:
|
|
- Name: nextcloud
|
|
LogConfiguration:
|
|
LogDriver: awslogs
|
|
Options:
|
|
awslogs-group: !Ref CloudwatchLogsGroup
|
|
awslogs-region: !Ref AWS::Region
|
|
awslogs-stream-prefix: nextcloud
|
|
Environment:
|
|
- Name: POSTGRES_DB
|
|
Value: !Ref NextCloudDbName
|
|
- Name: POSTGRES_USER
|
|
Value: !Ref DbUserName
|
|
- Name: POSTGRES_PASSWORD
|
|
Value: !Ref DbPassword
|
|
- Name: POSTGRES_HOST
|
|
Value: !GetAtt RdsStack.Outputs.EndpointUrl
|
|
- Name: NEXTCLOUD_TRUSTED_DOMAINS
|
|
Value:
|
|
Fn::Sub:
|
|
- ${Domain} ${ElbDomain}
|
|
- ElbDomain: !GetAtt ElasticLoadBalancer.DNSName
|
|
- Name: NEXTCLOUD_ADMIN_USER
|
|
Value: !Ref NextCloudAdminUser
|
|
- Name: NEXTCLOUD_ADMIN_PASSWORD
|
|
Value: !Ref NextCloudAdminPassword
|
|
- Name: OBJECTSTORE_S3_BUCKET
|
|
Value: !Ref DataBucket
|
|
- Name: OBJECTSTORE_S3_REGION
|
|
Value: !Ref AWS::Region
|
|
- Name: OBJECTSTORE_S3_KEY
|
|
Value: !Ref BucketUserCredentials
|
|
- Name: OBJECTSTORE_S3_SECRET
|
|
Value: !GetAtt BucketUserCredentials.SecretAccessKey
|
|
- Name: OVERWRITEPROTOCOL
|
|
Value: !If [CustomDomain, https, http]
|
|
- Name: REDIS_HOST
|
|
Value: !GetAtt RedisReplicationGroup.PrimaryEndPoint.Address
|
|
- Name: REDIS_PORT
|
|
Value: !GetAtt RedisReplicationGroup.PrimaryEndPoint.Port
|
|
PortMappings:
|
|
- HostPort: 80
|
|
Protocol: tcp
|
|
ContainerPort: 80
|
|
MountPoints:
|
|
- ContainerPath: "/var/www/html"
|
|
SourceVolume: nextcloud
|
|
- ContainerPath: "/var/www/html/custom_apps"
|
|
SourceVolume: apps
|
|
- ContainerPath: "/var/www/html/config"
|
|
SourceVolume: config
|
|
- ContainerPath: "/var/www/html/data"
|
|
SourceVolume: data
|
|
Image: !Sub nextcloud:${NextCloudVersion}-apache
|
|
Essential: true
|
|
Volumes:
|
|
- Name: nextcloud
|
|
EFSVolumeConfiguration:
|
|
FilesystemId: !Ref Efs
|
|
AuthorizationConfig:
|
|
AccessPointId: !Ref EfsAPNextcloud
|
|
IAM: ENABLED
|
|
TransitEncryption: ENABLED
|
|
- Name: apps
|
|
EFSVolumeConfiguration:
|
|
FilesystemId: !Ref Efs
|
|
AuthorizationConfig:
|
|
AccessPointId: !Ref EfsAPApps
|
|
IAM: ENABLED
|
|
TransitEncryption: ENABLED
|
|
- Name: config
|
|
EFSVolumeConfiguration:
|
|
FilesystemId: !Ref Efs
|
|
AuthorizationConfig:
|
|
AccessPointId: !Ref EfsAPConfig
|
|
IAM: ENABLED
|
|
TransitEncryption: ENABLED
|
|
- Name: data
|
|
EFSVolumeConfiguration:
|
|
FilesystemId: !Ref Efs
|
|
AuthorizationConfig:
|
|
AccessPointId: !Ref EfsAPData
|
|
IAM: ENABLED
|
|
TransitEncryption: ENABLED
|
|
|
|
AlbCertificate:
|
|
Condition: CustomDomain
|
|
Type: AWS::CertificateManager::Certificate
|
|
Properties:
|
|
DomainName: !Ref Domain
|
|
ValidationMethod: DNS
|
|
DomainValidationOptions:
|
|
- DomainName: !Ref Domain
|
|
HostedZoneId: !Ref Route53Zone
|
|
|
|
ElbSecurityGroup:
|
|
Type: 'AWS::EC2::SecurityGroup'
|
|
Properties:
|
|
GroupDescription: ELB Security Group
|
|
VpcId: !GetAtt VpcStack.Outputs.VPC
|
|
|
|
ElbSecurityGroupHTTPSinbound:
|
|
Type: 'AWS::EC2::SecurityGroupIngress'
|
|
Properties:
|
|
GroupId: !Ref ElbSecurityGroup
|
|
IpProtocol: tcp
|
|
FromPort: '443'
|
|
ToPort: '443'
|
|
CidrIp: 0.0.0.0/0
|
|
|
|
ElbSecurityGroupHTTPinbound:
|
|
Type: 'AWS::EC2::SecurityGroupIngress'
|
|
Properties:
|
|
GroupId: !Ref ElbSecurityGroup
|
|
IpProtocol: tcp
|
|
FromPort: '80'
|
|
ToPort: '80'
|
|
CidrIp: 0.0.0.0/0
|
|
|
|
ElasticLoadBalancer:
|
|
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
|
|
Properties:
|
|
Scheme: internet-facing
|
|
LoadBalancerAttributes:
|
|
- Key: idle_timeout.timeout_seconds
|
|
Value: '30'
|
|
Subnets: !Split [ ',', !GetAtt VpcStack.Outputs.PublicSubnets ]
|
|
SecurityGroups:
|
|
- !Ref ElbSecurityGroup
|
|
|
|
Route53AliasRecord:
|
|
Condition: CustomDomain
|
|
Type: AWS::Route53::RecordSet
|
|
Properties:
|
|
AliasTarget:
|
|
DNSName: !GetAtt ElasticLoadBalancer.DNSName
|
|
EvaluateTargetHealth: true
|
|
HostedZoneId: !GetAtt ElasticLoadBalancer.CanonicalHostedZoneID
|
|
Comment: Sso Api Gateway
|
|
HostedZoneId: !Ref Route53Zone
|
|
Name: !Ref Domain
|
|
Type: A
|
|
|
|
LoadBalancerListener:
|
|
Type: 'AWS::ElasticLoadBalancingV2::Listener'
|
|
Properties:
|
|
DefaultActions:
|
|
Fn::If:
|
|
- CustomDomain
|
|
- - Type: "redirect"
|
|
RedirectConfig:
|
|
Protocol: "HTTPS"
|
|
Port: 443
|
|
Host: "#{host}"
|
|
Path: "/#{path}"
|
|
Query: "#{query}"
|
|
StatusCode: "HTTP_301"
|
|
- - Type: forward
|
|
TargetGroupArn: !Ref LoadBalancerTargetGroup
|
|
LoadBalancerArn: !Ref ElasticLoadBalancer
|
|
Port: '80'
|
|
Protocol: HTTP
|
|
|
|
HttpsLoadBalancerListener:
|
|
Condition: CustomDomain
|
|
Type: 'AWS::ElasticLoadBalancingV2::Listener'
|
|
Properties:
|
|
Certificates:
|
|
- CertificateArn: !Ref AlbCertificate
|
|
DefaultActions:
|
|
- Type: forward
|
|
TargetGroupArn: !Ref LoadBalancerTargetGroup
|
|
LoadBalancerArn: !Ref ElasticLoadBalancer
|
|
Port: '443'
|
|
Protocol: HTTPS
|
|
|
|
LoadBalancerListenerRule:
|
|
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule'
|
|
DependsOn: LoadBalancerListener
|
|
Properties:
|
|
Actions:
|
|
- Type: forward
|
|
TargetGroupArn: !Ref LoadBalancerTargetGroup
|
|
Conditions:
|
|
- Field: "path-pattern"
|
|
Values:
|
|
- "/"
|
|
ListenerArn: !Ref LoadBalancerListener
|
|
Priority: 1
|
|
|
|
LoadBalancerTargetGroup:
|
|
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
|
|
DependsOn: ElasticLoadBalancer
|
|
Properties:
|
|
HealthCheckIntervalSeconds: 10 # This might be keeping the Serverless RDS awake
|
|
HealthCheckPath: /status.php
|
|
HealthCheckProtocol: HTTP
|
|
HealthCheckTimeoutSeconds: 5
|
|
HealthyThresholdCount: 2
|
|
Port: 80
|
|
Protocol: HTTP
|
|
Matcher:
|
|
HttpCode: 200,400
|
|
UnhealthyThresholdCount: 2
|
|
VpcId: !GetAtt VpcStack.Outputs.VPC
|
|
TargetType: ip
|
|
TargetGroupAttributes:
|
|
- Key: deregistration_delay.timeout_seconds
|
|
Value: 60
|
|
- Key: stickiness.enabled
|
|
Value: false
|
|
|
|
EcsService:
|
|
DependsOn:
|
|
- LoadBalancerListener
|
|
- EfsMountTarget1
|
|
- EfsMountTarget2
|
|
Type: 'AWS::ECS::Service'
|
|
Properties:
|
|
Cluster: !Ref EcsCluster
|
|
DesiredCount: !Ref EcsInitialDesiredCapacity
|
|
CapacityProviderStrategy:
|
|
- Base: 1
|
|
CapacityProvider: !Ref EcsCapacityProvider
|
|
Weight: 1
|
|
DeploymentConfiguration:
|
|
MaximumPercent: 100
|
|
MinimumHealthyPercent: 0 # Allows service interruption rather than scaling up
|
|
NetworkConfiguration:
|
|
AwsvpcConfiguration:
|
|
AssignPublicIp: !If [PrivateSubnets, DISABLED, ENABLED]
|
|
SecurityGroups:
|
|
- !Ref EcsSecurityGroup
|
|
Subnets: !Split [ ',', !If [PrivateSubnets, !GetAtt VpcStack.Outputs.PrivateSubnets, !GetAtt VpcStack.Outputs.PublicSubnets] ]
|
|
HealthCheckGracePeriodSeconds: 1200
|
|
LoadBalancers:
|
|
- ContainerName: 'nextcloud'
|
|
ContainerPort: '80'
|
|
TargetGroupArn: !Ref LoadBalancerTargetGroup
|
|
SchedulingStrategy: REPLICA
|
|
TaskDefinition: !Ref EcsTaskDefinition
|
|
PropagateTags: SERVICE
|
|
|
|
ECSTaskRole:
|
|
Type: 'AWS::IAM::Role'
|
|
Properties:
|
|
AssumeRolePolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service:
|
|
- ecs-tasks.amazonaws.com
|
|
Action:
|
|
- 'sts:AssumeRole'
|
|
Path: /
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
|
|
- 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
|
|
Policies:
|
|
- PolicyName: ecs-service
|
|
PolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer'
|
|
- 'elasticloadbalancing:DeregisterTargets'
|
|
- 'elasticloadbalancing:Describe*'
|
|
- 'elasticloadbalancing:RegisterInstancesWithLoadBalancer'
|
|
- 'elasticloadbalancing:RegisterTargets'
|
|
# TODO: understand permissions needed
|
|
Resource: '*'
|
|
- Effect: Allow
|
|
Action:
|
|
- 'ec2:Describe*'
|
|
- 'ec2:AuthorizeSecurityGroupIngress'
|
|
- "ec2:AttachNetworkInterface"
|
|
- "ec2:CreateNetworkInterface"
|
|
- "ec2:CreateNetworkInterfacePermission"
|
|
- "ec2:DeleteNetworkInterface"
|
|
- "ec2:DeleteNetworkInterfacePermission"
|
|
- "ec2:Describe*"
|
|
- "ec2:DetachNetworkInterface"
|
|
Resource: "*"
|
|
- Effect: Allow
|
|
Action:
|
|
- elasticfilesystem:*
|
|
Resource:
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${Efs}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPNextcloud}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPConfig}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPApps}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPData}
|
|
|
|
ECSTaskExecRole:
|
|
Type: 'AWS::IAM::Role'
|
|
Properties:
|
|
AssumeRolePolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service:
|
|
- ecs-tasks.amazonaws.com
|
|
Action:
|
|
- 'sts:AssumeRole'
|
|
Path: /
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
|
|
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
|
|
Policies:
|
|
- PolicyName: ecs-service
|
|
PolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- elasticfilesystem:*
|
|
Resource:
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:file-system/${Efs}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPNextcloud}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPConfig}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPApps}
|
|
- !Sub arn:aws:elasticfilesystem:${AWS::Region}:${AWS::AccountId}:access-point/${EfsAPData}
|
|
- Effect: Allow
|
|
Action:
|
|
- "ecr:GetAuthorizationToken"
|
|
- "ecr:BatchCheckLayerAvailability"
|
|
- "ecr:GetDownloadUrlForLayer"
|
|
- "ecr:BatchGetImage"
|
|
Resource: "*"
|
|
- Effect: Allow
|
|
Action:
|
|
- s3:*
|
|
Resource: !Sub arn:aws:s3:::${DataBucket}
|
|
- Effect: Allow
|
|
Action:
|
|
- s3:*
|
|
Resource: !Sub arn:aws:s3:::${DataBucket}/*
|
|
- Effect: Deny
|
|
Action:
|
|
- s3:DeleteBucket*
|
|
- s3:PutBucket*
|
|
- s3:PutEncryptionConfiguration
|
|
- s3:CreateBucket
|
|
Resource: "*"
|
|
- Effect: Allow
|
|
Action:
|
|
- s3:GetBucketLocation
|
|
Resource: arn:aws:s3:::*
|
|
|
|
Ec2NcRole:
|
|
Type: 'AWS::IAM::Role'
|
|
Properties:
|
|
AssumeRolePolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service:
|
|
- ec2.amazonaws.com
|
|
Action:
|
|
- 'sts:AssumeRole'
|
|
Path: /
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
|
|
Policies:
|
|
- PolicyName: ecs-service
|
|
PolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Action:
|
|
- logs:*
|
|
- elasticfilesystem:*
|
|
Resource: '*'
|
|
InstanceProfile:
|
|
Type: "AWS::IAM::InstanceProfile"
|
|
Properties:
|
|
Path: "/"
|
|
Roles:
|
|
- Ref: Ec2NcRole
|
|
RedisSecurityGroup:
|
|
Type: 'AWS::EC2::SecurityGroup'
|
|
Properties:
|
|
GroupDescription: Elasticache Security Group
|
|
VpcId: !GetAtt VpcStack.Outputs.VPC
|
|
RedisSecurityGroupinbound:
|
|
Type: 'AWS::EC2::SecurityGroupIngress'
|
|
Properties:
|
|
GroupId: !Ref RedisSecurityGroup
|
|
IpProtocol: tcp
|
|
FromPort: '6379'
|
|
ToPort: '6379'
|
|
SourceSecurityGroupId: !Ref EcsSecurityGroup
|
|
RedisSubnetGroup:
|
|
Type: 'AWS::ElastiCache::SubnetGroup'
|
|
Properties:
|
|
Description: Redis Subnet Group
|
|
SubnetIds: !Split [ ',', !GetAtt VpcStack.Outputs.PrivateSubnets]
|
|
RedisParameterGroup:
|
|
Type: AWS::ElastiCache::ParameterGroup
|
|
Properties:
|
|
Description: nextcloud param group
|
|
CacheParameterGroupFamily: redis6.x
|
|
Properties:
|
|
cluster-enabled: 'no'
|
|
RedisReplicationGroup:
|
|
DeletionPolicy: Snapshot
|
|
UpdateReplacePolicy: Snapshot
|
|
Type: AWS::ElastiCache::ReplicationGroup
|
|
Properties:
|
|
ReplicationGroupDescription: 'redis cache for nextcloud'
|
|
AutomaticFailoverEnabled: false
|
|
NumCacheClusters: 1
|
|
MultiAZEnabled: false
|
|
CacheNodeType: !Ref RedisTshirtSize
|
|
CacheParameterGroupName: !Ref RedisParameterGroup
|
|
CacheSubnetGroupName: !Ref RedisSubnetGroup
|
|
Engine: redis
|
|
EngineVersion: 6.x
|
|
# NumNodeGroups: 1
|
|
# ReplicasPerNodeGroup: 1
|
|
# NodeGroupConfiguration:
|
|
# - ReplicaCount: 1
|
|
PreferredMaintenanceWindow: 'sat:07:00-sat:08:00'
|
|
SecurityGroupIds:
|
|
- !GetAtt
|
|
- RedisSecurityGroup
|
|
- GroupId
|
|
SnapshotRetentionLimit: 35
|
|
SnapshotWindow: '00:00-03:00'
|
|
AtRestEncryptionEnabled: true
|
|
UpdatePolicy:
|
|
UseOnlineResharding: true
|
|
NextcloudAutoScalingRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
AssumeRolePolicyDocument:
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service: ecs-tasks.amazonaws.com
|
|
Action: 'sts:AssumeRole'
|
|
ManagedPolicyArns:
|
|
- 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
|
|
NextcloudAutoScalingTarget:
|
|
Type: AWS::ApplicationAutoScaling::ScalableTarget
|
|
Properties:
|
|
MinCapacity: !Ref EcsMinCapacity
|
|
MaxCapacity: !Ref EcsMaxCapacity
|
|
ResourceId: !Join [ '/', [ service, !Ref EcsCluster, !GetAtt EcsService.Name ] ]
|
|
ScalableDimension: ecs:service:DesiredCount
|
|
ServiceNamespace: ecs
|
|
RoleARN: !GetAtt NextcloudAutoScalingRole.Arn
|
|
SuspendedState:
|
|
DynamicScalingInSuspended: !Ref SuspendAutoScaling
|
|
DynamicScalingOutSuspended: !Ref SuspendAutoScaling
|
|
ScheduledScalingSuspended: !Ref SuspendAutoScaling
|
|
NextcloudAutoScalingPolicy:
|
|
Type: AWS::ApplicationAutoScaling::ScalingPolicy
|
|
Properties:
|
|
PolicyName: !GetAtt EcsService.Name
|
|
PolicyType: TargetTrackingScaling
|
|
ScalingTargetId: !Ref NextcloudAutoScalingTarget
|
|
TargetTrackingScalingPolicyConfiguration:
|
|
PredefinedMetricSpecification:
|
|
PredefinedMetricType: ECSServiceAverageCPUUtilization
|
|
ScaleInCooldown: 10
|
|
ScaleOutCooldown: 10
|
|
# Keep things at or lower than a target CPU utilization, for example
|
|
TargetValue: !Ref EcsTargetCpuUtilization
|
|
NextcloudCWDashbard:
|
|
Type: AWS::CloudWatch::Dashboard
|
|
Properties:
|
|
DashboardName: !Sub ${DeploymentName}-nextcloud
|
|
DashboardBody: !Sub |
|
|
{
|
|
"widgets": [
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 0,
|
|
"x": 0,
|
|
"type": "metric",
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"metrics": [
|
|
[ "AWS/ECS", "CPUUtilization", "ServiceName", "${EcsService.Name}", "ClusterName", "${EcsCluster}" ]
|
|
],
|
|
"region": "${AWS::Region}",
|
|
"title": "ECS-CPUUtilization"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 0,
|
|
"x": 12,
|
|
"type": "metric",
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"region": "${AWS::Region}",
|
|
"stat": "Average",
|
|
"period": 300,
|
|
"metrics": [
|
|
[ "AWS/ECS", "MemoryUtilization", "ServiceName", "${EcsService.Name}", "ClusterName", "${EcsCluster}" ]
|
|
],
|
|
"title": "ECS-MemoryUtilization"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 6,
|
|
"x": 0,
|
|
"type": "metric",
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"region": "${AWS::Region}",
|
|
"stat": "Average",
|
|
"period": 300,
|
|
"metrics": [
|
|
[ "AWS/RDS", "CPUUtilization", "DBClusterIdentifier", "${RdsStack.Outputs.DBIdentifier}" ]
|
|
],
|
|
"title": "RDS-CPUUtilization"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 6,
|
|
"x": 12,
|
|
"type": "metric",
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"region": "${AWS::Region}",
|
|
"stat": "Average",
|
|
"period": 300,
|
|
"metrics": [
|
|
[ "AWS/RDS", "DatabaseConnections", "DBClusterIdentifier", "${RdsStack.Outputs.DBIdentifier}" ]
|
|
],
|
|
"title": "DatabaseConnections"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 12,
|
|
"x": 0,
|
|
"type": "metric",
|
|
"properties": {
|
|
"metrics": [
|
|
[ "AWS/EFS", "StorageBytes", "StorageClass", "Standard", "FileSystemId", "${Efs}" ],
|
|
[ "...", "IA", ".", "." ],
|
|
[ "...", "Total", ".", "." ]
|
|
],
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"region": "${AWS::Region}",
|
|
"stat": "Average",
|
|
"period": 300,
|
|
"title": "EFS-StorageBytes"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 12,
|
|
"x": 12,
|
|
"type": "metric",
|
|
"properties": {
|
|
"metrics": [
|
|
[ { "expression": "(m2*100)/m1", "label": "Data write", "id": "e2", "region": "${AWS::Region}" } ],
|
|
[ { "expression": "(m3*100)/m1", "label": "Data read", "id": "e3", "region": "${AWS::Region}" } ],
|
|
[ { "expression": "(m4*100)/m1", "label": "Metadata", "id": "e4", "region": "${AWS::Region}" } ],
|
|
[ "AWS/EFS", "TotalIOBytes", "FileSystemId", "${Efs}", { "id": "m1", "visible": false, "region": "${AWS::Region}" } ],
|
|
[ "AWS/EFS", "DataWriteIOBytes", "FileSystemId", "${Efs}", { "id": "m2", "visible": false, "region": "${AWS::Region}" } ],
|
|
[ "AWS/EFS", "DataReadIOBytes", "FileSystemId", "${Efs}", { "id": "m3", "visible": false, "region": "${AWS::Region}" } ],
|
|
[ "AWS/EFS", "MetadataIOBytes", "FileSystemId", "${Efs}", { "id": "m4", "visible": false, "region": "${AWS::Region}" } ]
|
|
],
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"region": "${AWS::Region}",
|
|
"stat": "Sum",
|
|
"period": 60,
|
|
"title": "EFS - Throughput by type"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 18,
|
|
"x": 0,
|
|
"type": "metric",
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"start": "-P14D",
|
|
"end": "P0D",
|
|
"region": "${AWS::Region}",
|
|
"metrics": [
|
|
[ "AWS/S3", "BucketSizeBytes", "StorageType", "StandardStorage", "BucketName", "${DataBucket}" ]
|
|
],
|
|
"period": 86400,
|
|
"stat": "Average",
|
|
"title": "Data-BucketSizeBytes"
|
|
}
|
|
},
|
|
{
|
|
"height": 6,
|
|
"width": 12,
|
|
"y": 18,
|
|
"x": 12,
|
|
"type": "metric",
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"start": "-P14D",
|
|
"end": "P0D",
|
|
"region": "${AWS::Region}",
|
|
"metrics": [
|
|
[ "AWS/S3", "NumberOfObjects", "StorageType", "AllStorageTypes", "BucketName", "${DataBucket}", { "period": 86400 } ]
|
|
],
|
|
"period": 86400,
|
|
"stat": "Average",
|
|
"title": "DataBucket-NumberOfObjects"
|
|
}
|
|
},
|
|
{
|
|
"type": "metric",
|
|
"x": 0,
|
|
"y": 24,
|
|
"width": 12,
|
|
"height": 6,
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"metrics": [
|
|
[ "AWS/ElastiCache", "CPUUtilization", "CacheClusterId", "${RedisReplicationGroup}-001", "CacheNodeId", "0001" ]
|
|
],
|
|
"region": "${AWS::Region}",
|
|
"title": "Redis_CPUUtilization"
|
|
}
|
|
},
|
|
{
|
|
"type": "metric",
|
|
"x": 12,
|
|
"y": 24,
|
|
"width": 12,
|
|
"height": 6,
|
|
"properties": {
|
|
"view": "timeSeries",
|
|
"stacked": false,
|
|
"metrics": [
|
|
[ "AWS/ElastiCache", "DatabaseMemoryUsagePercentage", "CacheClusterId", "${RedisReplicationGroup}-001", "CacheNodeId", "0001" ]
|
|
],
|
|
"region": "${AWS::Region}",
|
|
"title": "Redis_DatabaseMemoryUsagePercentage"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
Outputs:
|
|
CloudWatchDashboardUrl:
|
|
Value: !Sub https://${AWS::Region}.console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#dashboards:name=${NextcloudCWDashbard}
|
|
LoadBalancerUrl:
|
|
Value: !GetAtt ElasticLoadBalancer.DNSName
|
|
CustomUrl:
|
|
Condition: CustomDomain
|
|
Value: !Sub https://${Domain}
|