project_conquer/aws/templates/nc.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}