diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54b90c4..f82f5af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,3 +59,24 @@ If you discover a potential security issue in this project we ask that you notif See the [LICENSE](https://github.com/aws-samples/aws-refarch-moodle/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. + +## Authors + +Names in alphabetical orders + +v1.0 +- Diego Magalhães +- gamerf +- Henri Yandell +- jtrollin +- MathieuJeandron +- StephaniePar +- Thiago Pádua + +v2.0 +- Irshad Chohan +- Norman Owens +- Preenesh Nayanasudhan +- Stephen Smith +- Vincent Rioux + diff --git a/NOTICE b/NOTICE index 97a83f4..f48b352 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1 @@ -AWS Refarch Moodle -Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/README.md b/README.md index 35a626f..a985872 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,143 @@ # Hosting Moodle on AWS -### Version 1.0.0 +### Version 2.0.0 ---- +## Overview -© 2018 Amazon Web Services, Inc. and its affiliates. All rights reserved. This work may not be reproduced or redistributed, in whole or in part, without prior written permission from Amazon Web Services, Inc. Commercial copying, lending, or selling is prohibited. +This repository provides set of CloudFormation nested templates that deploy a highly available, elastic, and scalable [Moodle 4.1+](https://docs.moodle.org) environment on AWS. Moodle offers a learning platform that provides educators, administrators and learners a single robust, secure and integrated system for personalized learning environment. -Errors or corrections? Please create an issue and we will repsond to you. +These nested templates can be used to deploy Moodle on AWS using [Amazon Virtual Private Cloud (Amazon VPC)](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Introduction.html), [Amazon Elastic Compute Cloud (Amazon EC2)](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html), [Auto Scaling](http://docs.aws.amazon.com/autoscaling/latest/userguide/WhatIsAutoScaling.html), [Elastic Load Balancing (Application Load Balancer)](http://docs.aws.amazon.com/elasticbalancing/latest/application/introduction.html), [Amazon Aurora](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html), [Amazon ElastiCache](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/WhatIs.html), [Amazon Elastic File System (Amazon EFS)](http://docs.aws.amazon.com/efs/latest/ug/whatisefs.html), [Amazon CloudFront](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html), [Amazon Route 53](http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html), [Amazon Certificate Manager (Amazon ACM)](http://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) with [AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) in yaml format. ---- +This architecture is expansive enough to meet the needs of large institutions / organizations. Smaller organizations can choose to run a subset of the template to meet their needs. These templates can also be run individually and may be modified. -## Overview +This template currently uses [Moodle 4.1+](https://download.moodle.org/download.php/stable401/moodle-latest-401.tgz) stable version downloaded directly from [download.moodle.org](https://download.moodle.org/releases/latest/). Details for downloading are available in the [templates/03-pipelinehelper.yaml](templates/03-pipelinehelper.yaml) template file. -This repository consists of a set of nested templates which deploy a highly available, elastic, and scalable [Moodle](https://docs.moodle.org) environment on AWS. Moodle is a learning platform designed to provide educators, administrators and learners with a single robust, secure and integrated system to create personalized learning environments. This reference architecture provides a set of YAML templates for deploying Moodle on AWS using [Amazon Virtual Private Cloud (Amazon VPC)](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Introduction.html), [Amazon Elastic Compute Cloud (Amazon EC2)](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html), [Auto Scaling](http://docs.aws.amazon.com/autoscaling/latest/userguide/WhatIsAutoScaling.html), [Elastic Load Balancing (Application Load Balancer)](http://docs.aws.amazon.com/elasticbalancing/latest/application/introduction.html), [Amazon Relational Database Service (Amazon RDS)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html), [Amazon ElastiCache](http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/WhatIs.html), [Amazon Elastic File System (Amazon EFS)](http://docs.aws.amazon.com/efs/latest/ug/whatisefs.html), [Amazon CloudFront](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html), [Amazon Route 53](http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html), [Amazon Certificate Manager (Amazon ACM)](http://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) with [AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html). This architecture may be overkill for many Moodle deployments, however the templates can be run individually and/or modified to deploy a subset of the architecture that fits your needs. +## Deployment guide -## TL;DR +Read the [reference architecture](#architecture) and the steps below to understand the deployment scope and options. While following the steps and guidelines to deploy Moodle 4.x, pay careful attention to the parameters and their descriptions. -If you just want to deploy the Moodle stack follow these steps. You can read the detail below to better understand the architecture. +### Pre-requisites +1) Select an [AWS Region](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) (for example: us-east-1) for your deployment. +2) Give a meaningful `Stack Name` that does `not have` any `special characters` including hyphen(-) Eg: MoodleDevDeploy OR MoodleProd +2) If you plan to use HTTPS, you must [create](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html) or [import](https://docs.aws.amazon.com/acm/latest/userguide/import-certificate.html) your certificate into Amazon Certificate Manager (ACM) and provide its [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) when deploying the CloudFormation stack. +3) Alternatively, if you plan to use an SSL Certificate with [Amazon CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html), you must create or import your certificate into Amazon Certificate Manager in the us-east-1 region before launching Moodle and provide it's ARN when deploying the CloudFormation stack. -1) If you plan to use TLS, you must create or import your certificate into Amazon Certificate Manager before launching Moodle. -2) Deploy the 00-master.yaml stack. **Do not enable session caching in ElastiCache and leave both the Min and Max Auto Scaling Group (ASG) size set to one.** The installation wizard will not complete if you have session caching configured. -3) After the stack deployment completes, navigate to the web site to complete the Moodle installation. *NOTE: You may encounter a 504 Gateway Timeout or CloudFront error on the final step of the installation wizard (after setting admin password). You can simply refresh the page to complete the installation.* You may also see "Installation must be finished from the original IP address, sorry." to solve this you will need to update your database and set the lastip field of the mdl_user table to the internal ip address of your ALB (you can find this by looking at the Network Interfaces from the EC2 page in the AWS Console). From the webserver you can run: -psql -h -U -update mdl_user set lastip=''; -4) Configure Application caching in Moodle Site Configuration (see below for details). -5) Now you can update the stack that you just deployed to enable session caching and set the Min and Max Auto Scaling Group size values as desired. +### Steps +1) Deploy the 00-main.yaml stack. You can also click the `Launch Stack` button below to launch the stack in your logged-in AWS Account. +2) After the stack deployment completes, you will see a `DNS Name` entry under the `Outputs` tab under the main CloudFormation template. This DNS Name value will be your Moodle app URL. You can configure aliases or CNAMEs to point to this DNS Name if you want to customize this. +3) Navigate to the Moodle application URL to complete the installation. + 1) *NOTE: You may encounter a 504 Gateway Timeout or CloudFront error on the final step of the Moodle application installation wizard (after configuring the administrator password). You can safely ignore this error and refresh the page to complete the installation.* + 2) You may also see "Installation must be finished from the original IP address, sorry." If this is the case, update your database and set the `lastip` field of the `mdl_user` table to the internal IP address of your Application Load Balancer which can be found under the `Network Interfaces` section of the `EC2` section of the AWS Console. To update the value in the database, run these commands on the EC2 web server: -*Note you can reach the webserver by changing the minimum number of bastion hosts to 1 in the Auto Scaling Group or enable Systems Manager Session Manager by updating the IAM role assigned to the webserver instance (https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html) + psql -h -U + update mdl_user set lastip=''; -You can launch this CloudFormation stack, using your account, in the following AWS Regions. The template will work in other regions as Aurora PostgreSQL is deployed globally. +4) Once the Moodle installation wizard completes successfully, you need to update the value of the SSM Parameter `IsMoodleSetupCompleted` to 'Yes'. + 1) In your main `CloudFormation` template, check `Outputs` tab to see parameter `IsMoodleSetupCompleted`. Click the link under `Value` to get details of the parameter. + 3) Edit the parameter and change the value to `Yes`. + 4) Go back to `Outputs` tab to see link for `MoodleCodePipeline`. Click on the link to open Code Pipeline. Click on the `Release Change` button. This will re-run the deployment pipeline and update the Moodle configurations post-installation, in order to adjust the auto-scaling configuration and the session cache configuration. + +5) This template can optionally deploy [Amazon ElastiCache](https://aws.amazon.com/elasticache/) as the Moodle Session and/or Application cache(s). When this feature is activated, you still need to configure the Application Cache within Moodle after deployment (see [how-to](#application-caching) guide). The cache endpoint is listed under the CloudFormation `Output` tab as `ApplicationCacheServerEndpoint`. + +*`NOTE:` To connect to your EC2 web servers, select an EC2 Instance and click on the `Connect` button in the AWS Console. Open the `Session Manager` tab and click on the `Connect` button. Note that this feature uses the [AWS SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html) that is installed on the instances, allowing you to connect to EC2 Instances without opening the SSH port to Internet traffic. An alternative approach to connect to your instances would be to enable the bastion host through the CloudFormation stack parameters. + +### Launch the CloudFormation Template + +You can launch this CloudFormation template in different AWS Regions. Below are links to help you get started quickly, but note that you can always change the region yourself once you are in the AWS Console. | AWS Region Code | Name | Launch | | --- | --- | --- -| us-east-1 |US East (N. Virginia)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-master.yaml) | -| us-east-2 |US East (Ohio)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-master.yaml) | -| us-west-2 |US West (Oregon)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-master.yaml) | -| eu-west-1 |EU (Ireland)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-master.yaml) | -| eu-central-1 |EU (Frankfurt)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-master.yaml) | -| ap-southeast-2 |AP (Sydney)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-master.yaml) | +| us-east-1 |US East (N. Virginia)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| us-east-2 |US East (Ohio)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| us-west-2 |US West (Oregon)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| eu-west-1 |EU (Ireland)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| eu-central-1 |EU (Frankfurt)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| ap-southeast-1 |AP (Singapore)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| ap-southeast-2 |AP (Sydney)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-southeast-2#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| ap-south-1 |India (Mumbai)| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ap-south-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | +| ca-central-1 |Canada (Central))| [![cloudformation-launch-stack](images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=ca-central-1#/stacks/new?stackName=Moodle&templateURL=https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/00-main.yaml) | ## Architecture -The following sections describe the individual components of the architecture. This architecture borrows liberally from the [WordPress Reference Architecture](https://github.com/awslabs/aws-refarch-wordpress). You may want to review that in addition to the discussion below. +The following sections describe the architecture and its components. This architecture uses a similar approach to the one used in the [WordPress Reference Architecture](https://github.com/awslabs/aws-refarch-wordpress). ![](images/aws-refarch-moodle-architecture.png) ### AWS Certificate Manager -AWS Certificate Manager is a service that lets you easily provision, manage, and deploy Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates for use with AWS services. You should run SSL/TLS to protect sessions and passwords. If you plan to use Transport Layer Security (TLS), you must create or import a TLS certificate in AWS Certificate Manager before you launching the template. In addition, if you are using CloudFront and hosting Moodle in a region other than us-east-1, you must create or import the certificate in both us-east-1 and the region you are hosting Moodle in. CloudFront always uses certificates from us-east-1. +AWS Certificate Manager lets you easily provision, manage, and deploy Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates for use with AWS services. You should use SSL/TLS to protect data in transit, including sessions and passwords. If you plan to use SSL/TLS, you must create or import a certificate using AWS Certificate Manager before you deploy the template. In addition, if wish to use CloudFront and host Moodle in a region other than us-east-1, you must create or import the certificate in both us-east-1 and the region you are hosting Moodle in. CloudFront requires certificates in the us-east-1 region. ### Application Load Balancer -An Application Load Balancer distributes incoming application traffic across multiple EC2 instances in multiple Availability Zones. You achieve high availability by clustering multiple Moodle servers behind a load balancer. Moodle provides an overview of [Server Clustering](https://docs.moodle.org/34/en/Server_cluster) that you should review before proceeding. The template will launch an Application Load Balancer (ALB). +The Application Load Balancer distributes incoming application traffic across multiple EC2 instances in multiple Availability Zones. You achieve high availability by clustering multiple Moodle servers behind this load balancer. You can review Moodle's overview of [Server Clustering](https://docs.moodle.org/en/Server_cluster) before proceeding. ### Amazon Autoscaling -Amazon EC2 Auto Scaling helps you ensure that you have the correct number of Amazon EC2 instances available to handle the load for your application. The template configures autoscaling based on CPU utilization. An additional instance is added when the average CPU utilization exceeds 75% for three minutes and removed when the average CPU utilization is less than 25% for three minutes. Based on the instance type you are running, cache configuration you choose, and other factors, you may find that another metric is a better predictor of load. Feel free to change the metrics as needed. +Amazon EC2 Auto Scaling helps ensure that the appropriate number of Amazon EC2 instances are available to handle the load of the application. The template configures autoscaling based on CPU utilization. An additional instance is added when the average CPU utilization exceeds 75% for three minutes and removed when the average CPU utilization is less than 25% for three minutes. Based on the instance type, cache configuration, and other factors, you may find that other metrics are better predictors of load. You can change the metrics to better meet your operational needs. -*Note that the installation wizard causes spikes in CPU that could cause the cluster to scale unexpectedly. Initially, you should deploy a single server by setting the Min and Max ASG size to one. Then, complete the Moodle configuration wizard, and update CloudFormation stack setting the min and max ASG size to your desired limits.* +*`Note:` that the installation wizard causes spikes in CPU that could cause the cluster to scale unexpectedly. To avoid an issue with this during installation, initial deployment starts with minimum and maximum autoscaling values of 1. Once you complete the Moodle installation wizard, update the SSM parameter `IsMoodleSetupCompleted` and run the Moodle pipeline, the minimum and maximum autoscaling values will be updated according to your parameters.* ### Amazon Elastic File System (EFS) -Amazon Elastic File System (Amazon EFS) provides simple, scalable file storage for use with Amazon EC2 instances in the AWS Cloud. While installing Moodle on EFS would make management (updates, patching, etc.) easier, Moodle does not perform well when run from shared storage. Moodle recommends that dirroot is configured on local storage. Therefore, the template uses a combination of Elastic Block Storage (EBS) and EFS storage. Each web server in the Moodle Cluster employs the following directory structure: +Amazon Elastic File System (Amazon EFS) provides simple, scalable file storage in the AWS Cloud. Using EFS makes Moodle operations and management (shared files, updates, patches, etc.) easier. However, Moodle performance may suffer when the application code itself is run from mounted volumes like EFS. Moodle recommends `dirroot` to be on local or high-performance storage. This template follows that recommendation, and uses a combination of Elastic Block Storage (EBS) and EFS for storage. Each web server in the Moodle Cluster employs the following directory structure: ``` $CFG->dirroot = '/var/www/moodle/html' #Stored on root EBS volume +$CFG->localcachedir = '/var/www/moodle/local' #Stored on root EBS volume + $CFG->dataroot = '/var/www/moodle/data' #Stored on shared EFS filesystem $CFG->cachedir = '/var/www/moodle/cache' #Stored on shared EFS filesystem $CFG->tempdir = '/var/www/moodle/temp' #Stored on shared EFS filesystem -$CFG->localcachedir = '/var/www/moodle/local' #Stored on root EBS volume ``` -Elastic File System throughput scales as the file system grows. Initially EFS will have very little storage. Therefore, the template gives you the option to seed the file system with data to establish baseline performance. In addition, you can monitor the file system performance and add additional data if needed. There is a through discussion of this in the WordPress Reference Architecture. However, because we have chosen to install Moodle on local storage (the WordPress reference architecture installed on EFS) this should be less of a concern that it was for WordPress. +With [elastic](https://docs.aws.amazon.com/efs/latest/ug/performance.html#elastic) throughput type, Amazon EFS automatically scales throughput performance up or down to meet the needs of your workload activity. You don't need to specify or provision the throughput capacity to meet your application needs. + +*Moodle recommends the `dirroot` be set as read only for the apache process in a clustered environment [[Reference]](https://docs.moodle.org/400/en/Server_cluster#.24CFG-.3Edirroot). You should not install plugins to a server cluster from the admin page. `Moodle recommends manually installing plugins on each server during planned maintenance`. To follow the infrastructure-as-code methodology, installation/upgrade of plugins can be managed using AWS CodePipeline scripts. See the `.pipeline` folder inside your AWS CodeCommit Moodle repository. + +### AWS CodePipeline +This CloudFormation templates use AWS Services to create a CI/CD pipeline to help manage your Moodle environment. AWS CodeCommit will host a git repository for your Moodle environment. It initially pulls the source from [download.moodle.org.](https://download.moodle.org/download.php/stable400/moodle-4.0.5.tgz). It also adds files required to automate the deployment pipeline. You can explore these files under the `.pipeline` folder. +This template also creates an AWS CodePipeline configuration that build artifacts to deploy on EC2 with autoscaling groups using AWS CodeBuild and AWS CodeDeploy. It can optionally support a BLUE_GREEN deployment. -*Moodle recommends that when running clustered solutions "[dirroot should be always read only for apache process because otherwise built in plugin installation and uninstallation would get the nodes out of sync.](https://docs.moodle.org/34/en/Server_cluster#.24CFG-.3Edirroot)" As this statement implies, you cannot install plugins to a server cluster from the admin page. Moodle recommends manually installing plugins on each server during planned maintenance. To stay true to the infrastructure-as-code methodology employed by CloudFormation, we should script the installation of plugins. Alternatively, you could install the plugin on a single server, create an AMI, and update the Launch Configuration.* +*You can customize the overall pipeline for your Moodle setup.* -### Caching +### AWS Systems Manager - Parameter Store +This template also uses the Parameter Store to host Moodle environment configurations parameters like the database endpoint, the database credentials, the application and session cache endpoints, etc. This allows easy management of these configuration parameters. You can change these parameters and refresh your deployment to quickly implement them. -Caching can have a dramatic impact on Moodle's performance. The template will configure various forms of caching including OPcache, CloudFront and ElastiCache. +### `Caching` +--- +Caching can have a dramatic impact on Moodle's performance. This template configures various forms of caching including OPcache, CloudFront and ElastiCache. #### OPcache -Opcache speeds up PHP execution caching precompiled scripts in memory. OPcache benefits are increased performance and significantly lower memory usage. The template configures OPcache as described [here](https://docs.moodle.org/34/en/OPcache). +[PHP OPcache](https://www.php.net/manual/book.opcache.php) speeds up PHP execution by caching precompiled scripts in memory. This template configures OPcache as described [here](https://docs.moodle.org/400/en/OPcache). #### Amazon ElastiCache -Amazon ElastiCache for Memcached is a Memcached-compatible in-memory key-value store service that can be used as a cache or a data store. Moodle recommends that you [don't use the same memcached server for both sessions and MUC. Events triggering MUC caches to be purged leads to MUC purging the memcached server](https://docs.moodle.org/26/en/Session_handling). Therefore, the template configures two elasticache clusters, one for session caching and one for application caching. +Amazon ElastiCache for `Memcached` is a Memcached-compatible in-memory key-value store service that can be used as a cache or a data store. Moodle recommends that you `don't use the same memcached server for both sessions and MUC` [Refer](https://docs.moodle.org/26/en/Session_handling). Events triggering MUC caches to be purged leads to MUC purging the memcached server]. This template configures two ElastiCache clusters, one for session caching and one for application caching. + +This template also allows you to create `Amazon ElastiCache for Redis` as Redis compatible in-memory key-value store service that can be used as a cache or a data store. -##### Session Caching +#### Session Caching -Moodle recommends that you [store user sessions in one shared memcached server](https://docs.moodle.org/34/en/Server_cluster#Performance_recommendations). The template configures session caching as described [here](https://docs.moodle.org/34/en/Session_handling#Memcached_session_driver). +Moodle recommends that you [store user sessions in one shared memcached server](https://docs.moodle.org/en/Server_cluster#Performance_recommendations). The template configures session caching as described [here](https://docs.moodle.org/en/Session_handling#Memcached_session_driver). -*Note: Moodle installation wizard fails if memcached session caching is enabled during the initial configuration. Therefore, you must leave session caching disabled during the initial installation, and then update the template to enable session caching after completing the installation.* +*`Note:` This template doesn't configure the Session Cache during initial deployment. It waits for you to finish the initial Moodle installation wizard and update the Parameter `IsMoodleSetupCompleted` value to `Yes` in the SSM Parameter Store. Once the installation is completed, you need to run the Moodle pipeline to enable session caching and finalize the other remaining configuration.* -##### Application Caching +#### Application Caching -The template deploys an ElastiCache cluster for application caching, but the application caching must be configured after launch. You can configure memcache as described [here](https://docs.moodle.org/28/en/Caching#Memcached) filling in the auto-discovery endpoint to the list of Servers under both Store Configuration and Enable Clustered Servers (see image below). You can find the endpoint address in the outputs of the application caching stack. Finally, scroll to the bottom of the caching administration page in Moodle and set elasticache as the default store for application caching. +The template deploys an ElastiCache cluster for application caching, `but the application caching must be configured after the Moodle installation is completed`. You can configure [memcached](https://docs.moodle.org/en/Caching#Memcached) or [Redis](https://docs.moodle.org/en/Redis_cache_store) by filling in the auto-discovery endpoint to the list of Servers under both Store Configuration and Enable Clustered Servers (see image below). You can find the `ApplicationCacheServerEndpoint` address in the `Outputs` of the CloudFormation stack. Finally, scroll to the bottom of the caching administration page in Moodle and set ElastiCache as the default store for application caching. ![](images/aws-refarch-moodle-caching.png) +![](images/aws-refarch-moodle-caching-redis.png) #### Amazon CloudFront -Amazon CloudFront is a global content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to your viewers with low latency and high transfer speeds. The template can optionally configure CloudFront to cache content closer to your users. This is especially beneficial if your users are spread across a large geographic area. For example, remote students in an online program. +Amazon CloudFront is a global content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to your viewers with low latency and high transfer speeds. It also helps in caching content closer to user's geography and reduces loads on the web servers. ### Amazon Route 53 -Amazon Route 53 is a highly available and scalable cloud Domain Name System (DNS) web service. The template will optionally configure a Route53 alias that points to either the Application Load Balancer or CloudFront. If you are using another DNS system, you should create a CNAME record in your DNS system to reference either the Application Load Balancer or CloudFront (if deployed). If you don't have access to DNS you can leave Domain Name blank and the template will configure Moodle to use the auto-generated Application Load Balancer domain name. +Amazon Route 53 is a highly available and scalable cloud Domain Name System (DNS) service. The template will optionally configure a Route53 alias that points to either the Application Load Balancer or CloudFront. If you are using another DNS system, you should create a CNAME record in your DNS system to reference either the Application Load Balancer or CloudFront (if deployed). If you don't have access to DNS you can leave Domain Name blank and the template will configure Moodle to use the auto-generated Application Load Balancer domain name. + +--- ## License @@ -115,4 +148,4 @@ Portions copyright. - Moodle is licensed under the General Public License (GPLv3 or later) from the Free Software Foundation. - OPcache is licensed under PHP License, version 3.01. -Please see LICENSE for applicable license terms and NOTICE for applicable notices. \ No newline at end of file +Please see [LICENSE](LICENSE) for applicable license terms and [NOTICE](NOTICE) for applicable notices. diff --git a/images/aws-refarch-moodle-architecture.png b/images/aws-refarch-moodle-architecture.png index 86b6323..9e40c85 100644 Binary files a/images/aws-refarch-moodle-architecture.png and b/images/aws-refarch-moodle-architecture.png differ diff --git a/templates/00-main.yaml b/templates/00-main.yaml new file mode 100644 index 0000000..893f85c --- /dev/null +++ b/templates/00-main.yaml @@ -0,0 +1,1158 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: Stack to deploy a highly available, elastic, scalable Moodle environment. This master stack launches multiple nested stacks for different tiers. !! This can only be run in certain AWS Regions - 'us-east-1', 'us-east-2', 'us-west-2', 'eu-west-1'. + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: General AWS + Parameters: + - DeploymentLocation + - EC2KeyName + - SshAccessCidr + - HostedZoneName + - DomainName + - NotifyEmailAddress + - Label: + default: Network + Parameters: + - NumberOfAZs + - AvailabilityZones + - VpcCidr + - PublicSubnet0Cidr + - PublicSubnet1Cidr + - PublicSubnet2Cidr + - AppSubnet0Cidr + - AppSubnet1Cidr + - AppSubnet2Cidr + - DataSubnet0Cidr + - DataSubnet1Cidr + - DataSubnet2Cidr + - Label: + default: Shared File Storage + Parameters: + - SharedStorageEncryptionBoolean + - SharedStorageEncryptionCmk + - Label: + default: Database Tier + Parameters: + - DatabaseType + - DatabaseUseServerless + - DatabaseMinCapacity + - DatabaseMaxCapacity + - DatabaseInstanceType + - DatabaseEncryptedBoolean + - DatabaseCmk + - DatabaseMasterUsername + # - DatabaseMasterPassword + - DatabaseName + - Label: + default: Caching Tier + Parameters: + - CacheEngineType + - UseSessionCacheBoolean + - SessionCacheNodeType + - UseApplicationCacheBoolean + - ApplicationCacheNodeType + - UseCloudFrontBoolean + - CloudFrontAcmCertificate + - Label: + default: Bastion Tier + Parameters: + - NeedBastionHost + - BastionInstanceType + - Label: + default: Web Tier + Parameters: + - PublicAlbAcmCertificate + - WebInstanceType + - WebAsgMax + - WebAsgMin + - Label: + default: Moodle + Parameters: + - MoodleLocale + ParameterLabels: + DeploymentLocation: + default: "Location to deploy from (S3 URL without trailing slash, e.g.: https://.s3..amazonaws.com)" + BastionInstanceType: + default: Bastion Instance Type + NeedBastionHost: + default: Need Bastion Host? + CloudFrontAcmCertificate: + default: CloudFront Certificate ARN + DatabaseCmk: + default: AWS KMS CMK for RDS + DatabaseEncryptedBoolean: + default: Encrypted DB Cluster + DatabaseUseServerless: + default: Should the database use Serverless engines? + DatabaseMinCapacity: + default: (If serverless) Minimum capacity for database + DatabaseMaxCapacity: + default: (If serverless) Maximum capacity for database + DatabaseInstanceType: + default: (If NOT serverless) DB Instance Type + DatabaseMasterUsername: + default: DB Master Username + DatabaseName: + default: DB Name + SharedStorageEncryptionBoolean: + default: Shared File Storage Encrypted? + SharedStorageEncryptionCmk: + default: (Optional) Shared File Storage Encryption Key ARN (AWS KMS CMK) + EC2KeyName: + default: EC2 Key Pair + PublicAlbAcmCertificate: + default: ALB Certificate ARN + SshAccessCidr: + default: SSH Access From + UseApplicationCacheBoolean: + default: Use Application Cache + ApplicationCacheNodeType: + default: Application Cache Node Type + UseSessionCacheBoolean: + default: Use Session Cache + SessionCacheNodeType: + default: Session Cache Node Type + WebAsgMax: + default: Web ASG Max + WebAsgMin: + default: Web ASG Min + WebInstanceType: + default: Web Tier Instance Type + HostedZoneName: + default: Hosted Zone + DomainName: + default: Domain Name for Moodle site + MoodleLocale: + default: Language Code + AvailabilityZones: + default: Availability Zones + NumberOfAZs: + default: Number of Availability Zones + VpcCidr: + default: VpcCidr + PublicSubnet0Cidr: + default: Public Subnet 0 + PublicSubnet1Cidr: + default: Public Subnet 1 + PublicSubnet2Cidr: + default: Public Subnet 2 + AppSubnet0Cidr: + default: App Subnet 0 + AppSubnet1Cidr: + default: App Subnet 1 + AppSubnet2Cidr: + default: App Subnet 2 + DataSubnet0Cidr: + default: Data Subnet 0 + DataSubnet1Cidr: + default: Data Subnet 1 + DataSubnet2Cidr: + default: Data Subnet 2 + NotifyEmailAddress: + default: Email address for any notification + UseCloudFrontBoolean: + default: Use CloudFront + + +Parameters: + DeploymentLocation: + Description: Location to deploy from (S3 URL) + Type: String + BastionInstanceType: + AllowedValues: + - t3.nano + - t3.micro + - t3.small + - t3.medium + - t3.large + - t3.xlarge + - t3.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - t3a.nano + - t3a.micro + - t3a.small + - t3a.medium + - t3a.large + - t3a.xlarge + - t3a.2xlarge + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.9xlarge + - c5a.12xlarge + - c5a.18xlarge + - c5a.24xlarge + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - t4g.nano + - t4g.micro + - t4g.small + - t4g.medium + - t4g.large + - t4g.xlarge + - t4g.2xlarge + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6g.24xlarge + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.9xlarge + - c6g.12xlarge + - c6g.18xlarge + - c6g.24xlarge + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.24xlarge + ConstraintDescription: Must be a valid Amazon EC2 instance type. + Default: t4g.nano + Description: Bastion EC2 instance type. + Type: String + NeedBastionHost: + AllowedValues: + - false + - true + Default: false + Description: Whether you need Bastion host to access machines, else you can use SSM agent by default. + Type: String + PublicAlbAcmCertificate: + AllowedPattern: ^$|(arn:aws:acm:)([a-z0-9/:-])*([a-z0-9])$ + Description: '[ Optional ] The AWS Certification Manager certificate ARN for the ALB certificate - this certificate should be created in the region you wish to run the ALB and must reference the domain name you use below.' + Type: String + CloudFrontAcmCertificate: + AllowedPattern: ^$|(arn:aws:acm:)([a-z0-9/:-])*([a-z0-9])$ + Description: '[ Optional ] The AWS Certification Manager certificate ARN for the CloudFront distribution certificate - this certificate should be created in the us-east-1 (N. Virginia) region and must reference the Moodle domain name you use below.' + Type: String + DatabaseCmk: + Description: AWS KMS Customer Master Key (CMK) to encrypt database cluster + Type: String + DatabaseType: + AllowedValues: + - MySQL + - PostgreSQL + Default: PostgreSQL + Description: Indicates whether to use Aurora MySQL or PostgreSQL. + Type: String + DatabaseEncryptedBoolean: + AllowedValues: + - true + - false + Default: true + Description: Indicates whether the DB instances in the cluster are encrypted. + Type: String + DatabaseUseServerless: + AllowedValues: + - true + - false + Default: true + Description: Indicates whether the database should use the serverless engines. + Type: String + DatabaseMinCapacity: + AllowedValues: + - 0.5 + - 1 + - 2 + - 4 + - 5 + - 16 + - 32 + - 64 + - 128 + Default: 0.5 + Description: The minimum capacity for an Aurora DB cluster, starts with 0.5 and maximum up to 1 + Type: String + DatabaseMaxCapacity: + AllowedValues: + - 0.5 + - 1 + - 2 + - 4 + - 5 + - 16 + - 32 + - 64 + - 128 + Default: 64 + Description: The maximum capacity for an Aurora DB cluster, starts with 0.5 and maximum up to 1 + Type: String + DatabaseInstanceType: + AllowedValues: + - db.t3.medium + - db.t3.large + - db.r5.large + - db.r5.xlarge + - db.r5.2xlarge + - db.r5.4xlarge + - db.r5.8xlarge + - db.r5.12xlarge + - db.r5.16xlarge + - db.r6g.large + - db.r6g.xlarge + - db.r6g.2xlarge + - db.r6g.4xlarge + - db.r6g.8xlarge + - db.r6g.12xlarge + - db.r6g.16xlarge + ConstraintDescription: Must be a valid Aurora RDS instance type. + Default: db.r6g.large + Description: Amazon RDS database instance type (only used if non-serverless) + Type: String + DatabaseMasterUsername: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Description: Aurora RDS master username + ConstraintDescription: Must contain only alphanumeric characters and be at least 8 characters. + MaxLength: 16 + MinLength: 1 + Type: String + Default: moodle + DatabaseName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Description: Aurora RDS database name + Type: String + Default: moodle + SharedStorageEncryptionBoolean: + AllowedValues: + - true + - false + Default: true + Description: Do you want encrypted Shared file storage? + Type: String + SharedStorageEncryptionCmk: + AllowedPattern: ^$|(arn:aws:kms:)([a-z0-9/:-])*([a-z0-9])$ + ConstraintDescription: Must be an existing ARN for an AWS KMS CMK. + Description: '[ Optional ] The AWS KMS customer-managed CMK ARN to encrypt & decrypt the Shared file storage.' + Type: String + + CacheEngineType: + AllowedValues: + - Redis + - Memcached + Default: Memcached + Description: Indicates whether to use ElastiCache Memcached or Redis. + Type: String + UseSessionCacheBoolean: + AllowedValues: + - true + - false + Default: false + Description: Specifies whether an ElastiCache Cache Cluster should be created for sessions. This will not be used at first and will be integrated only after IsMoodleSetupComplete parameter changes to 'Yes'. + Type: String + SessionCacheNodeType: + 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.m6g.large + - cache.m6g.xlarge + - cache.m6g.2xlarge + - cache.m6g.4xlarge + - cache.m6g.8xlarge + - cache.m6g.12xlarge + - cache.m6g.16xlarge + ConstraintDescription: Must be a valid Amazon ElastiCache node type. + Default: cache.m6g.large + Description: The Amazon ElastiCache cluster node type. + Type: String + UseApplicationCacheBoolean: + AllowedValues: + - true + - false + Default: false + Description: Specifies whether an ElastiCache Cache Cluster should be created to cache application content. + Type: String + ApplicationCacheNodeType: + 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.m6g.large + - cache.m6g.xlarge + - cache.m6g.2xlarge + - cache.m6g.4xlarge + - cache.m6g.8xlarge + - cache.m6g.12xlarge + - cache.m6g.16xlarge + ConstraintDescription: Must be a valid Amazon ElastiCache node type. + Default: cache.m6g.large + Description: The Amazon ElastiCache cluster node type. + Type: String + EC2KeyName: + ConstraintDescription: Must be letters (upper or lower), numbers, and special characters. + Description: Name of an EC2 KeyPair. Your bastion & Web instances will launch with this KeyPair. + Type: AWS::EC2::KeyPair::KeyName + SshAccessCidr: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$ + Description: The CIDR IP range that is permitted to SSH to bastion instance. Note - a value of 0.0.0.0/0 will allow access from ANY IP address. You could also use /32 to restrict to your own public IP address. + Type: String + Default: 0.0.0.0/0 + WebAsgMax: + AllowedPattern: ^((?!0$)[1-2]?[0-9]|30)$ + ConstraintDescription: Must be a number between 1 and 30. + Default: 1 + Description: Specifies the maximum number of EC2 instances in the Web Autoscaling Group. + Type: String + WebAsgMin: + AllowedPattern: ^([0-0]?[0-9]|10)$ + ConstraintDescription: Must be a number between 0 and 10. + Default: 1 + Description: Specifies the minimum number of EC2 instances in the Web Autoscaling Group. + Type: String + WebInstanceType: + AllowedValues: + - t3.nano + - t3.micro + - t3.small + - t3.medium + - t3.large + - t3.xlarge + - t3.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - t3a.nano + - t3a.micro + - t3a.small + - t3a.medium + - t3a.large + - t3a.xlarge + - t3a.2xlarge + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.9xlarge + - c5a.12xlarge + - c5a.18xlarge + - c5a.24xlarge + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - t4g.nano + - t4g.micro + - t4g.small + - t4g.medium + - t4g.large + - t4g.xlarge + - t4g.2xlarge + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6g.24xlarge + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.9xlarge + - c6g.12xlarge + - c6g.18xlarge + - c6g.24xlarge + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.24xlarge + ConstraintDescription: Must be a valid Amazon EC2 instance type. + Default: m6g.large + Description: The Amazon EC2 instance type for your web instances. + Type: String + HostedZoneName: + AllowedPattern: ^$|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$ + Description: '[ Optional ] The Route 53 hosted zone to create the domain in (e.g. example.edu).' + Type: String + DomainName: + AllowedPattern: ^$|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$ + Description: '[ Optional ] The main domain name of the Moodle site (e.g. moodle.example.edu).' + Type: String + MoodleLocale: + Description: "The main language of the Moodle site, during initial configuration." + Type: String + Default: en + AvailabilityZones: + Description: 'List of Availability Zones to use for the subnets in the VPC. Note: + The logical order is preserved.' + Type: List + NumberOfAZs: + AllowedValues: + - 1 + - 2 + - 3 + Default: 2 + Description: Number of Availability Zones to use in the VPC. This must match your + selections in the list of Availability Zones parameter. + Type: Number + VpcCidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.0.0/16 + Description: CIDR block for the VPC + Type: String + DataSubnet0Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.100.0/24 + Description: CIDR block for data subnet 0 located in Availability Zone 0 + Type: String + DataSubnet1Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.101.0/24 + Description: CIDR block for data subnet 1 located in Availability Zone 1 + Type: String + DataSubnet2Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.102.0/24 + Description: CIDR block for data subnet 2 located in Availability Zone 2 + Type: String + PublicSubnet0Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.200.0/24 + Description: CIDR block for Public subnet 0 located in Availability Zone 0 + Type: String + PublicSubnet1Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.201.0/24 + Description: CIDR block for Public subnet 1 located in Availability Zone 1 + Type: String + PublicSubnet2Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.202.0/24 + Description: CIDR block for Public subnet 2 located in Availability Zone 2 + Type: String + AppSubnet0Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.0.0/22 + Description: CIDR block for App Subnet 0 located in Availability Zone 0 + Type: String + AppSubnet1Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.4.0/22 + Description: CIDR block for App Subnet 1 located in Availability Zone 1 + Type: String + AppSubnet2Cidr: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.8.0/22 + Description: CIDR block for App Subnet 2 located in Availability Zone 2 + Type: String + UseCloudFrontBoolean: + AllowedValues: + - true + - false + Default: false + Description: Specifies whether a CloudFront Distribution should be created to serve the Moodle website content. + Type: String + NotifyEmailAddress: + AllowedPattern: ^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$ + Description: Email address for notification + Type: String + Default: hello@yourdomain.com + +Conditions: + DeployWithoutSessionCache: + !Equals [ false, !Ref UseSessionCacheBoolean ] + DeployWithSessionCache: + !Equals [ true, !Ref UseSessionCacheBoolean ] + DeployApplicationCache: + !Equals [ true, !Ref UseApplicationCacheBoolean ] + DeployRoute53: + !And [ + !Not [!Equals ['', !Ref DomainName ] ], + !Not [ !Equals ['', !Ref HostedZoneName ] ] + ] + ErrorChoices: + !Or [ + !And [ + !Not [!Equals ['', !Ref DomainName ] ], #with domain name + !Equals [ true, !Ref UseCloudFrontBoolean ], #with cloudfront + !Equals ['', !Ref CloudFrontAcmCertificate ] #no SSL certificate + ], + !And [ + !Not [!Equals ['', !Ref HostedZoneName ] ], #with domain name internal + !Equals [ true, !Ref UseCloudFrontBoolean ], #with cloudfront + !Equals ['', !Ref CloudFrontAcmCertificate ] #no SSL certificate + ] + ] + DeployCloudFront: + !Or [ + !And [ #Domain Name with CloudFront but no SSL certificate will treat as CloudFront domain name only + !Not [!Equals ['', !Ref DomainName ] ], + !Equals [ true, !Ref UseCloudFrontBoolean ], + !Not [ !Equals ['', !Ref CloudFrontAcmCertificate ] ] + ], + !And [ + !Equals ['', !Ref DomainName ], + !Equals [ true, !Ref UseCloudFrontBoolean ] + ] + ] + DeployWithBastionHost: + !Equals [true, !Ref NeedBastionHost ] + SharedStorageEFS: + !Equals ['EFS', 'EFS' ] + DeployUsingRDSServerless: + !Equals [ true, !Ref DatabaseUseServerless ] + DeployUsingRDSInstances: + !Equals [ false, !Ref DatabaseUseServerless ] + NeedSSL: + !Or [ + !Equals [ true, !Ref UseCloudFrontBoolean ], #Either CloudFront + !Not [ !Equals ['', !Ref PublicAlbAcmCertificate ] ] #Either ACM certificate at ALB + ] + CustomDomainName: + !Or [ + !And [ #Domain Name with CloudFront but no SSL certificate will treat as CloudFront domain name only + !Not [!Equals ['', !Ref DomainName ] ], + !Equals [ true, !Ref UseCloudFrontBoolean ], + !Not [ !Equals ['', !Ref CloudFrontAcmCertificate ] ] + ], + !And [ #Domain Name with CloudFront but no SSL certificate will treat as CloudFront domain name only + !Not [!Equals ['', !Ref DomainName ] ], + !Equals [ false, !Ref UseCloudFrontBoolean ] + ] + ] + CloudFrontDnsName: + !And [ + !Equals ['', !Ref DomainName ], + !Equals [ true, !Ref UseCloudFrontBoolean ] ] + AlbDnsName: + !And [ + !Equals ['', !Ref DomainName ], + !Equals [ false, !Ref UseCloudFrontBoolean ] ] + + +Resources: + #Made it to fail the CloudformationDeployment due to Error choices. + SomeErrorInCloudFormationParamCombinationCheckErrorChoices: + Condition: ErrorChoices + Type: AWS::SSM::Parameter + Properties: + Name: '@#$' + Type: String + Value: '' + + rdsInstanceSecret: + Type: AWS::SecretsManager::Secret + Properties: + Description: 'Credentials for Moodle RDS instance' + GenerateSecretString: + SecretStringTemplate: !Sub '{"username": "${DatabaseMasterUsername}"}' + GenerateStringKey: 'password' + PasswordLength: 16 + ExcludeCharacters: '"@/\' + + IsMoodleSetupCompletedParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Sub '${AWS::StackName}', '/IsMoodleSetupCompleted' ] ] + Type: String + Value: 'No' + Description: SSM Parameter for Moodle Setup completed or not. Default 'No' any other value will be considered as 'Yes' + vpc: + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + NumberOfAZs: + !Ref NumberOfAZs + AvailabilityZones: + !Join + - ',' + - !Ref AvailabilityZones + VpcCidr: + !Ref VpcCidr + PublicSubnet0Cidr: + !Ref PublicSubnet0Cidr + PublicSubnet1Cidr: + !Ref PublicSubnet1Cidr + PublicSubnet2Cidr: + !Ref PublicSubnet2Cidr + AppSubnet0Cidr: + !Ref AppSubnet0Cidr + AppSubnet1Cidr: + !Ref AppSubnet1Cidr + AppSubnet2Cidr: + !Ref AppSubnet2Cidr + DataSubnet0Cidr: + !Ref DataSubnet0Cidr + DataSubnet1Cidr: + !Ref DataSubnet1Cidr + DataSubnet2Cidr: + !Ref DataSubnet2Cidr + TemplateURL: !Sub '${DeploymentLocation}/01-newvpc.yaml' + + securitygroups: + DependsOn: vpc + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + SshAccessCidr: + !Ref SshAccessCidr + Vpc: + !GetAtt [ vpc, Outputs.Vpc ] + DatabaseType: + !Ref DatabaseType + TemplateURL: !Sub '${DeploymentLocation}/02-securitygroups.yaml' + + bastion: + Condition: DeployWithBastionHost + DependsOn: securitygroups + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + BastionInstanceType: + !Ref BastionInstanceType + BastionSecurityGroup: + !GetAtt [ securitygroups, Outputs.BastionSecurityGroup ] + EC2KeyName: + !Ref EC2KeyName + NumberOfSubnets: + !Ref NumberOfAZs + Subnet: + !GetAtt [ vpc, Outputs.PublicSubnet ] + TemplateURL: !Sub '${DeploymentLocation}/03-bastion.yaml' + + publicalb: + DependsOn: securitygroups + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + NumberOfSubnets: + !Ref NumberOfAZs + Subnet: + !GetAtt [ vpc, Outputs.PublicSubnet ] + PublicAlbAcmCertificate: + !Ref PublicAlbAcmCertificate + PublicAlbSecurityGroup: + !GetAtt [ securitygroups, Outputs.PublicAlbSecurityGroup ] + Vpc: + !GetAtt [ vpc, Outputs.Vpc ] + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/03-publicalb.yaml' + + rds: + DependsOn: [ securitygroups ] + Type: AWS::CloudFormation::Stack + Condition: DeployUsingRDSInstances + Properties: + Parameters: + DatabaseType: + !Ref DatabaseType + DatabaseInstanceType: + !Ref DatabaseInstanceType + # DatabaseMasterUsername: + # !Ref DatabaseMasterUsername + RDSInstanceSecretArn: + !Ref rdsInstanceSecret + DatabaseName: + !Ref DatabaseName + DatabaseEncryptedBoolean: + !Ref DatabaseEncryptedBoolean + DatabaseCmk: + !Ref DatabaseCmk + DatabaseSecurityGroup: + !GetAtt [ securitygroups, Outputs.DatabaseSecurityGroup ] + Subnet: + !GetAtt [ vpc, Outputs.DataSubnet ] + NumberOfSubnets: + !Ref NumberOfAZs + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/03-rds.yaml' + + rdsserverless: + DependsOn: [ securitygroups ] + Type: AWS::CloudFormation::Stack + Condition: DeployUsingRDSServerless + Properties: + Parameters: + DatabaseType: + !Ref DatabaseType + RDSInstanceSecretArn: + !Ref rdsInstanceSecret + DatabaseName: + !Ref DatabaseName + DatabaseMinCapacity: + !Ref DatabaseMinCapacity + DatabaseMaxCapacity: + !Ref DatabaseMaxCapacity + DatabaseEncryptedBoolean: + !Ref DatabaseEncryptedBoolean + DatabaseCmk: + !Ref DatabaseCmk + DatabaseSecurityGroup: + !GetAtt [ securitygroups, Outputs.DatabaseSecurityGroup ] + Subnet: + !GetAtt [ vpc, Outputs.DataSubnet ] + NumberOfSubnets: + !Ref NumberOfAZs + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/03-rdsserverless.yaml' + + sharedEFS: + Condition: SharedStorageEFS + DependsOn: securitygroups + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + EncryptedBoolean: + !Ref SharedStorageEncryptionBoolean + Cmk: + !Ref SharedStorageEncryptionCmk + SecurityGroup: + !GetAtt [ securitygroups, Outputs.EfsSecurityGroup ] + NumberOfSubnets: + !Ref NumberOfAZs + Subnet: + !GetAtt [ vpc, Outputs.DataSubnet ] + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/03-efsfilesystem.yaml' + + pipelineHelper: + DependsOn: securitygroups + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + EC2KeyName: + !Ref EC2KeyName + PipelineSecurityGroup: + !GetAtt [ securitygroups, Outputs.EfsSecurityGroup ] + NumberOfSubnets: + !Ref NumberOfAZs + PipelineSubnet: + !GetAtt [ vpc, Outputs.AppSubnet ] + RDSInstanceSecretArn: + !Ref rdsInstanceSecret + DomainName: + !Ref DomainName + MoodleLocale: + !Ref MoodleLocale + ProjectName: + !Sub '${AWS::StackName}' + WebAsgMax: + !Ref WebAsgMax + WebAsgMin: + !Ref WebAsgMin + TemplateURL: !Sub '${DeploymentLocation}/03-pipelinehelper.yaml' + + sessioncache: + Condition: DeployWithSessionCache + DependsOn: securitygroups + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + CacheEngineType: !Ref CacheEngineType + CacheUsageType: 'session' + Subnet: + !GetAtt [ vpc, Outputs.DataSubnet ] + ElastiCacheClusterName: + !Sub '${AWS::StackName}session' + ElastiCacheNodeType: + !Ref SessionCacheNodeType + ElastiCacheSecurityGroup: + !GetAtt [ securitygroups, Outputs.ElastiCacheSecurityGroup ] + NumberOfSubnets: + !Ref NumberOfAZs + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/03-elasticache.yaml' + applicationcache: + Condition: DeployApplicationCache + DependsOn: securitygroups + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + CacheEngineType: !Ref CacheEngineType + CacheUsageType: 'application' + Subnet: + !GetAtt [ vpc, Outputs.DataSubnet ] + ElastiCacheClusterName: + !Sub '${AWS::StackName}application' + ElastiCacheNodeType: + !Ref ApplicationCacheNodeType + ElastiCacheSecurityGroup: + !GetAtt [ securitygroups, Outputs.ElastiCacheSecurityGroup ] + NumberOfSubnets: + !Ref NumberOfAZs + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/03-elasticache.yaml' + webapp: + DependsOn: [ publicalb, pipelineHelper ] + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + RDSInstanceSecretArn: + !Ref rdsInstanceSecret + EC2KeyName: + !Ref EC2KeyName + NumberOfSubnets: + !Ref NumberOfAZs + Subnet: + !GetAtt [ vpc, Outputs.AppSubnet ] + PublicAlbTargetGroupArn: + !GetAtt [ publicalb, Outputs.PublicAlbTargetGroupArn ] + WebAsgMax: 1 + WebAsgMin: 1 + WebInstanceType: + !Ref WebInstanceType + WebSecurityGroup: + !GetAtt [ securitygroups, Outputs.WebSecurityGroup ] + CodeArtifactS3BucketArn: !GetAtt [ pipelineHelper, Outputs.CodeArtifactS3BucketArn] + TemplateURL: !Sub '${DeploymentLocation}/04-web.yaml' + + codePipeline: + DependsOn: [webapp, pipelineHelper, rds] + Condition: DeployUsingRDSInstances + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + CodeCommitRepoName: + !GetAtt [ pipelineHelper, Outputs.MoodleRepoName ] + CodeCommitRepoArn: + !GetAtt [ pipelineHelper, Outputs.MoodleRepoArn ] + BranchName: 'main' + AppAutoScalingGroupName: + !GetAtt [ webapp, Outputs.WebAutoScalingGroupName ] + MoodleAppTargetGroupName: + !GetAtt [ publicalb, Outputs.PublicAlbTargetGroupName ] + CodeArtifactS3BucketName: + !GetAtt [ pipelineHelper, Outputs.CodeArtifactS3BucketName ] + CodeArtifactS3BucketArn: + !GetAtt [ pipelineHelper, Outputs.CodeArtifactS3BucketArn ] + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/05-codepipeline.yaml' + + codePipelineServerless: + DependsOn: [webapp, pipelineHelper, rdsserverless] + Condition: DeployUsingRDSServerless + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + CodeCommitRepoName: + !GetAtt [ pipelineHelper, Outputs.MoodleRepoName ] + CodeCommitRepoArn: + !GetAtt [ pipelineHelper, Outputs.MoodleRepoArn ] + BranchName: 'main' + AppAutoScalingGroupName: + !GetAtt [ webapp, Outputs.WebAutoScalingGroupName ] + MoodleAppTargetGroupName: + !GetAtt [ publicalb, Outputs.PublicAlbTargetGroupName ] + CodeArtifactS3BucketName: + !GetAtt [ pipelineHelper, Outputs.CodeArtifactS3BucketName ] + CodeArtifactS3BucketArn: + !GetAtt [ pipelineHelper, Outputs.CodeArtifactS3BucketArn ] + ProjectName: + !Sub '${AWS::StackName}' + TemplateURL: !Sub '${DeploymentLocation}/05-codepipeline.yaml' + + cloudfront: + Condition: DeployCloudFront + DependsOn: publicalb + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + CloudFrontAcmCertificate: + !Ref CloudFrontAcmCertificate + PublicAlbDnsName: + !GetAtt [ publicalb, Outputs.PublicAlbDnsName ] + DomainName: + !Ref DomainName + TemplateURL: !Sub '${DeploymentLocation}/04-cloudfront.yaml' + + CustomDnsParam: + Condition: CustomDomainName + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Sub '${AWS::StackName}', '/Network/DomainName' ] ] + Type: String + Value: !Join [ '', [ !If [ NeedSSL, 'https://','http://'] ,!Ref DomainName ] ] + Description: SSM Parameter for Moodle Public DNS URL + CloudFrontDnsParam: + DependsOn: cloudfront + Condition: CloudFrontDnsName + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Sub '${AWS::StackName}', '/Network/DomainName' ] ] + Type: String + Value: !Join [ '', ['https://',!GetAtt [ cloudfront, Outputs.DnsName ] ] ] + Description: SSM Parameter for Moodle Public DNS URL + AlbDnsParam: + DependsOn: publicalb + Condition: AlbDnsName + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Sub '${AWS::StackName}', '/Network/DomainName' ] ] + Type: String + Value: !Join [ '', [ !If [ NeedSSL, 'https://','http://'] ,!GetAtt [ publicalb, Outputs.PublicAlbDnsName ] ] ] + Description: SSM Parameter for Moodle Public DNS URL + + route53: + Condition: DeployRoute53 + DependsOn: publicalb + Type: AWS::CloudFormation::Stack + Properties: + Parameters: + DnsEndpoint: + !If [ DeployCloudFront, !GetAtt [ cloudfront, Outputs.DnsName ], !GetAtt [ publicalb, Outputs.PublicAlbDnsName ] ] + DnsHostId: + !If [ DeployCloudFront, 'Z2FDTNDATAQYW2', !GetAtt [ publicalb, Outputs.PublicAlbCanonicalHostedZoneId ] ] + HostedZoneName: + !Ref HostedZoneName + DomainName: + !Ref DomainName + TemplateURL: !Sub '${DeploymentLocation}/05-route53.yaml' + +Outputs: + MoodleDomainName: + Description: Moodle URL + Value: + !If [ CustomDomainName, + !Join [ '', [ !If [ NeedSSL, 'https://','http://'] , !Ref DomainName ] ], + !If [ DeployCloudFront, + !Join [ '', [ !If [ NeedSSL, 'https://','http://'] , !GetAtt [ cloudfront, Outputs.DnsName ] ] ], + !Join [ '', [ !If [ NeedSSL, 'https://','http://'] , !GetAtt [ publicalb, Outputs.PublicAlbDnsName ] ] ] + ] + ] + + DnsName: + Description: AWS-generated Moodle DNS Name (use this value to configure your custom DNS CNAME, if needed) + Value: + !If [ DeployCloudFront, + !GetAtt [ cloudfront, Outputs.DnsName ], + !GetAtt [ publicalb, Outputs.PublicAlbDnsName ] ] + + IsMoodleSetupCompleted: + Description: Once Moodle installation is completed, change this parameter value to 'Yes' and run pipeline + Value: !Join [ '', [ 'https://',!Ref "AWS::Region", '.console.aws.amazon.com/systems-manager/parameters', + !Ref IsMoodleSetupCompletedParam, '/description?region=',!Ref "AWS::Region" ] ] + + MoodleGitRepo: + Description: CodeCommit git repository having Moodle source code + Value: + !Join [ '', [ 'https://',!Ref "AWS::Region", '.console.aws.amazon.com/codesuite/codecommit/repositories/', + !Ref AWS::StackName, '-Repo/browse?region=',!Ref "AWS::Region" ] ] + + MoodleCodePipeline: + Description: Moodle Code pipeline allows you to deploy your changes + Value: + !Join [ '', [ 'https://',!Ref "AWS::Region", '.console.aws.amazon.com/codesuite/codepipeline/pipelines/', + !Ref AWS::StackName, '-Pipeline/view?region=',!Ref "AWS::Region" ] ] + + ApplicationCacheServerEndpoint: + Description: Application Cache server endpoint, use this to configure application cache endpoint in Moodle + Value: !If [DeployApplicationCache, !GetAtt applicationcache.Outputs.ElastiCacheClusterEndpointAddress, 'Not enabled'] diff --git a/templates/00-master.yaml b/templates/00-master.yaml deleted file mode 100644 index 6352da9..0000000 --- a/templates/00-master.yaml +++ /dev/null @@ -1,1112 +0,0 @@ ---- -AWSTemplateFormatVersion: 2010-09-09 - -Description: Stack to deploy a highly available, elastic, scalable Moodle environment. This master stack launches multiple nested stacks for different tiers. !! This can only be run in certain AWS Regions - 'us-east-1', 'us-east-2', 'us-west-2', 'eu-west-1'. - - - -Metadata: - AWS::CloudFormation::Interface: - ParameterGroups: - - Label: - default: General AWS - Parameters: - - EC2KeyName - - SshAccessCidr - - HostedZoneName - - DomainName - - UseRoute53Boolean - - Label: - default: Network - Parameters: - - NumberOfAZs - - AvailabilityZones - - VpcCidr - - VpcTenancy - - PublicSubnet0Cidr - - PublicSubnet1Cidr - - PublicSubnet2Cidr - - PublicSubnet3Cidr - - PublicSubnet4Cidr - - PublicSubnet5Cidr - - WebSubnet0Cidr - - WebSubnet1Cidr - - WebSubnet2Cidr - - WebSubnet3Cidr - - WebSubnet4Cidr - - WebSubnet5Cidr - - DataSubnet0Cidr - - DataSubnet1Cidr - - DataSubnet2Cidr - - DataSubnet3Cidr - - DataSubnet4Cidr - - DataSubnet5Cidr - - Label: - default: File System Tier - Parameters: - - EfsPerformanceMode - - EfsEncrpytedBoolean - - EfsCmk - - EfsGrowth - - EfsGrowthInstanceType - - Label: - default: Database Tier - Parameters: - - DatabaseInstanceType - - DatabaseEncrpytedBoolean - - DatabaseCmk - - DatabaseMasterUsername - # - DatabaseMasterPassword - - DatabaseName - - Label: - default: Caching Tier - Parameters: - - UseSessionCacheBoolean - - SessionCacheNodeType - - UseApplicationCacheBoolean - - ApplicationCacheNodeType - - UseCloudFrontBoolean - - CloudFrontAcmCertificate - - Label: - default: Bastion Tier - Parameters: - - BastionInstanceType - - Label: - default: Web Tier - Parameters: - - PublicAlbAcmCertificate - - WebInstanceType - - WebAsgMax - - WebAsgMin - - Label: - default: Moodle - Parameters: - - MoodleLocale - ParameterLabels: - BastionInstanceType: - default: Bastion Instance Type - CloudFrontAcmCertificate: - default: CloudFront Certificate ARN - DatabaseCmk: - default: AWS KMS CMK for RDS - DatabaseEncrpytedBoolean: - default: Encrypted DB Cluster - DatabaseInstanceType: - default: DB Instance Class - DatabaseMasterUsername: - default: DB Master Username - # DatabaseMasterPassword: - # default: DB Master Password - DatabaseName: - default: DB Name - EfsCmk: - default: AWS KMS CMK for EFS - EfsEncrpytedBoolean: - default: Encrpyted EFS? - EfsPerformanceMode: - default: EFS Performance Mode - EfsGrowth: - default: Add dummy data (GiB) - EfsGrowthInstanceType: - default: Instance Type - EC2KeyName: - default: EC2 Key Pair - PublicAlbAcmCertificate: - default: ALB Certificate ARN - SshAccessCidr: - default: SSH Access From - UseApplicationCacheBoolean: - default: Use Application Cache - ApplicationCacheNodeType: - default: Application Cache Node Type - UseSessionCacheBoolean: - default: Use Session Cache - SessionCacheNodeType: - default: Session Cache Node Type - WebAsgMax: - default: Web ASG Max - WebAsgMin: - default: Web ASG Min - WebInstanceType: - default: Web Tier Instance Type - HostedZoneName: - default: Hosted Zone - DomainName: - default: Site Domain - MoodleLocale: - default: Language Code - AvailabilityZones: - default: Availability Zones - NumberOfAZs: - default: Number of Availability Zones - VpcCidr: - default: VpcCidr - VpcTenancy: - default: VpcTenancy - PublicSubnet0Cidr: - default: Public Subnet 0 - PublicSubnet1Cidr: - default: Public Subnet 1 - PublicSubnet2Cidr: - default: Public Subnet 2 - PublicSubnet3Cidr: - default: Public Subnet 3 - PublicSubnet4Cidr: - default: Public Subnet 4 - PublicSubnet5Cidr: - default: Public Subnet 5 - WebSubnet0Cidr: - default: Web Subnet 0 - WebSubnet1Cidr: - default: Web Subnet 1 - WebSubnet2Cidr: - default: Web Subnet 2 - WebSubnet3Cidr: - default: Web Subnet 3 - WebSubnet4Cidr: - default: Web Subnet 4 - WebSubnet5Cidr: - default: Web Subnet 5 - DataSubnet0Cidr: - default: Data Subnet 0 - DataSubnet1Cidr: - default: Data Subnet 1 - DataSubnet2Cidr: - default: Data Subnet 2 - DataSubnet3Cidr: - default: Data Subnet 3 - DataSubnet4Cidr: - default: Data Subnet 4 - DataSubnet5Cidr: - default: Data Subnet 5 - UseRoute53Boolean: - default: Use Route 53 - UseCloudFrontBoolean: - default: Use Cloud Front - - -Parameters: - BastionInstanceType: - AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - t2.xlarge - - t2.2xlarge - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - m4.16xlarge - - m5.large - - m5.xlarge - - m5.2xlarge - - m5.4xlarge - - m5.12xlarge - - m5.24xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - c5.large - - c5.xlarge - - c5.2xlarge - - c5.4xlarge - - c5.9xlarge - - c5.18xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - r4.large - - r4.xlarge - - r4.2xlarge - - r4.4xlarge - - r4.8xlarge - - r4.16xlarge - - x1.16xlarge - - x1.32xlarge - - x1e.xlarge - - x1e.2xlarge - - x1e.4xlarge - - x1e.8xlarge - - x1e.16xlarge - - x1e.32xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - - h1.2xlarge - - h1.4xlarge - - h1.8xlarge - - h1.16xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - i3.large - - i3.xlarge - - i3.2xlarge - - i3.4xlarge - - i3.8xlarge - - i3.16xlarge - - f1.2xlarge - - f1.16xlarge - - g2.2xlarge - - g2.8xlarge - - g3.4xlarge - - g3.8xlarge - - g3.16xlarge - - p2.xlarge - - p2.8xlarge - - p2.16xlarge - - p3.2xlarge - - p3.8xlarge - - p3.16xlarge - ConstraintDescription: Must be a valid Amazon EC2 instance type. - Default: t2.nano - Description: Bastion EC2 instance type. - Type: String - PublicAlbAcmCertificate: - AllowedPattern: ^$|(arn:aws:acm:)([a-z0-9/:-])*([a-z0-9])$ - Description: '[ Optional ] The AWS Certification Manager certificate ARN for the ALB certificate - this certificate should be created in the region you wish to run the ALB and must reference the domain name you use below.' - Type: String - CloudFrontAcmCertificate: - AllowedPattern: ^$|(arn:aws:acm:)([a-z0-9/:-])*([a-z0-9])$ - Description: '[ Optional ] The AWS Certification Manager certificate ARN for the CloudFront distribution certificate - this certificate should be created in the us-east-1 (N. Virginia) region and must reference the Moodle domain name you use below.' - Type: String - DatabaseCmk: - Description: AWS KMS Customer Master Key (CMK) to encrypt database cluster - Type: String - DatabaseEncrpytedBoolean: - AllowedValues: - - true - - false - Default: true - Description: Indicates whether the DB instances in the cluster are encrypted. - Type: String - DatabaseInstanceType: - AllowedValues: - - db.r4.large - - db.r4.xlarge - - db.r4.2xlarge - - db.r4.4xlarge - - db.r4.8xlarge - - db.r4.16xlarge - ConstraintDescription: Must be a valid RDS instance class. - Default: db.r4.large - Description: The Amazon RDS database instance class. - Type: String - DatabaseMasterUsername: - AllowedPattern: ^([a-zA-Z0-9]*)$ - Description: The Amazon RDS master username. - ConstraintDescription: Must contain only alphanumeric characters and be at least 8 characters. - MaxLength: 16 - MinLength: 1 - Type: String - Default: moodle - # DatabaseMasterPassword: - # AllowedPattern: ^([a-zA-Z0-9`~!#$%^&*()_+,\\-])*$ - # ConstraintDescription: Must be letters (upper or lower), numbers, spaces, and these special characters `~!#$%^&*()_+,- - # Description: The Amazon RDS master password. Letters, numbers, spaces, and these special characters `~!#$%^&*()_+,- - # MaxLength: 41 - # MinLength: 8 - # NoEcho: true - # Type: String - DatabaseName: - AllowedPattern: ^([a-zA-Z0-9]*)$ - Description: The Amazon RDS master database name. - Type: String - Default: moodle - EfsEncrpytedBoolean: - AllowedValues: - - true - - false - Default: true - Description: Create an encrypted Amazon EFS file system. - Type: String - EfsCmk: - AllowedPattern: ^$|(arn:aws:kms:)([a-z0-9/:-])*([a-z0-9])$ - ConstraintDescription: Must be an existing ARN for an AWS KMS CMK. - Description: '[ Optional ] The AWS KMS customer-managed CMK ARN to encrypt & decrypt the EFS file system.' - Type: String - EfsPerformanceMode: - AllowedValues: - - generalPurpose - - maxIO - Default: generalPurpose - Description: Select the performance mode of the file system. - Type: String - EfsGrowth: - Default: 0 - ConstraintDescription: Must be an integer. - Description: Amount of dummy data (GiB) to add to the file system (max 6144 GiB). Amazon EFS storage charges apply. - MaxValue: 6144 - MinValue: 0 - Type: Number - EfsGrowthInstanceType: - AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - t2.xlarge - - t2.2xlarge - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - m4.16xlarge - - m5.large - - m5.xlarge - - m5.2xlarge - - m5.4xlarge - - m5.12xlarge - - m5.24xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - c5.large - - c5.xlarge - - c5.2xlarge - - c5.4xlarge - - c5.9xlarge - - c5.18xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - r4.large - - r4.xlarge - - r4.2xlarge - - r4.4xlarge - - r4.8xlarge - - r4.16xlarge - - x1.16xlarge - - x1.32xlarge - - x1e.xlarge - - x1e.2xlarge - - x1e.4xlarge - - x1e.8xlarge - - x1e.16xlarge - - x1e.32xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - - h1.2xlarge - - h1.4xlarge - - h1.8xlarge - - h1.16xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - i3.large - - i3.xlarge - - i3.2xlarge - - i3.4xlarge - - i3.8xlarge - - i3.16xlarge - - f1.2xlarge - - f1.16xlarge - - g2.2xlarge - - g2.8xlarge - - g3.4xlarge - - g3.8xlarge - - g3.16xlarge - - p2.xlarge - - p2.8xlarge - - p2.16xlarge - - p3.2xlarge - - p3.8xlarge - - p3.16xlarge - ConstraintDescription: Must be a valid Amazon EC2 instance type. - Default: r4.large - Description: The Amazon EC2 instance type that adds data to the file system. - Type: String - UseSessionCacheBoolean: - AllowedValues: - - true - - false - Default: false - Description: Specifies whether an ElastiCache Cache Cluster should be created for sessions. Do not create a session cache during he inital deploy. You should update the stack to use session cache after completing the installation wizard. - Type: String - SessionCacheNodeType: - AllowedValues: - - cache.t2.micro - - cache.t2.small - - cache.t2.medium - - cache.m4.large - - cache.m4.xlarge - - cache.m4.2xlarge - - cache.m4.4xlarge - - cache.m4.10xlarge - - cache.m3.medium - - cache.m3.large - - cache.m3.xlarge - - cache.m3.2xlarge - - cache.r3.large - - cache.r3.xlarge - - cache.r3.2xlarge - - cache.r3.4xlarge - - cache.r3.8xlarge - ConstraintDescription: Must be a valid Amazon ElastiCache node type. - Default: cache.t2.micro - Description: The Amazon ElastiCache cluster node type. - Type: String - UseApplicationCacheBoolean: - AllowedValues: - - true - - false - Default: true - Description: Specifies whether an ElastiCache Cache Cluster should be created to cache application content. - Type: String - ApplicationCacheNodeType: - AllowedValues: - - cache.t2.micro - - cache.t2.small - - cache.t2.medium - - cache.m4.large - - cache.m4.xlarge - - cache.m4.2xlarge - - cache.m4.4xlarge - - cache.m4.10xlarge - - cache.m3.medium - - cache.m3.large - - cache.m3.xlarge - - cache.m3.2xlarge - - cache.r3.large - - cache.r3.xlarge - - cache.r3.2xlarge - - cache.r3.4xlarge - - cache.r3.8xlarge - ConstraintDescription: Must be a valid Amazon ElastiCache node type. - Default: cache.t2.micro - Description: The Amazon ElastiCache cluster node type. - Type: String - EC2KeyName: - ConstraintDescription: Must be letters (upper or lower), numbers, and special characters. - Description: Name of an EC2 KeyPair. Your bastion & Web instances will launch with this KeyPair. - Type: AWS::EC2::KeyPair::KeyName - SshAccessCidr: - AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$ - Description: The CIDR IP range that is permitted to SSH to bastion instance. Note - a value of 0.0.0.0/0 will allow access from ANY IP address. - Type: String - Default: 0.0.0.0/0 - WebAsgMax: - AllowedPattern: ^((?!0$)[1-2]?[0-9]|30)$ - ConstraintDescription: Must be a number between 1 and 30. - Default: 1 - Description: Specifies the maximum number of EC2 instances in the Web Autoscaling Group. - Type: String - WebAsgMin: - AllowedPattern: ^([0-0]?[0-9]|10)$ - ConstraintDescription: Must be a number between 0 and 10. - Default: 1 - Description: Specifies the minimum number of EC2 instances in the Web Autoscaling Group. - Type: String - WebInstanceType: - AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - t2.xlarge - - t2.2xlarge - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - m4.16xlarge - - m5.large - - m5.xlarge - - m5.2xlarge - - m5.4xlarge - - m5.12xlarge - - m5.24xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - c5.large - - c5.xlarge - - c5.2xlarge - - c5.4xlarge - - c5.9xlarge - - c5.18xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - r4.large - - r4.xlarge - - r4.2xlarge - - r4.4xlarge - - r4.8xlarge - - r4.16xlarge - - x1.16xlarge - - x1.32xlarge - - x1e.xlarge - - x1e.2xlarge - - x1e.4xlarge - - x1e.8xlarge - - x1e.16xlarge - - x1e.32xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - - h1.2xlarge - - h1.4xlarge - - h1.8xlarge - - h1.16xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - i3.large - - i3.xlarge - - i3.2xlarge - - i3.4xlarge - - i3.8xlarge - - i3.16xlarge - - f1.2xlarge - - f1.16xlarge - - g2.2xlarge - - g2.8xlarge - - g3.4xlarge - - g3.8xlarge - - g3.16xlarge - - p2.xlarge - - p2.8xlarge - - p2.16xlarge - - p3.2xlarge - - p3.8xlarge - - p3.16xlarge - ConstraintDescription: Must be a valid Amazon EC2 instance type. - Default: t2.large - Description: The Amazon EC2 instance type for your web instances. - Type: String - HostedZoneName: - AllowedPattern: ^$|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$ - Description: '[ Optional ] The Route 53 hosted zone to create the domain in (e.g. example.edu).' - Type: String - DomainName: - AllowedPattern: ^$|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$ - Description: '[ Optional ] The main domain name of the Moodle site (e.g. moodle.example.edu).' - Type: String - MoodleLocale: - Description: "The main language of the Moodle site, during initial configuration." - Type: String - Default: en - AvailabilityZones: - Description: 'List of Availability Zones to use for the subnets in the VPC. Note: - The logical order is preserved.' - Type: List - NumberOfAZs: - AllowedValues: - - 2 - - 3 - - 4 - - 5 - - 6 - Default: 2 - Description: Number of Availability Zones to use in the VPC. This must match your - selections in the list of Availability Zones parameter. - Type: Number - VpcCidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.0.0/16 - Description: CIDR block for the VPC - Type: String - VpcTenancy: - AllowedValues: - - default - - dedicated - Default: default - Description: The allowed tenancy of instances launched into the VPC - Type: String - DataSubnet0Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.100.0/24 - Description: CIDR block for data subnet 0 located in Availability Zone 0 - Type: String - DataSubnet1Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.101.0/24 - Description: CIDR block for data subnet 1 located in Availability Zone 1 - Type: String - DataSubnet2Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.102.0/24 - Description: CIDR block for data subnet 2 located in Availability Zone 2 - Type: String - DataSubnet3Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.103.0/24 - Description: CIDR block for data subnet 3 located in Availability Zone 3 - Type: String - DataSubnet4Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.104.0/24 - Description: CIDR block for data subnet 4 located in Availability Zone 4 - Type: String - DataSubnet5Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.105.0/24 - Description: CIDR block for data subnet 5 located in Availability Zone 5 - Type: String - PublicSubnet0Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.200.0/24 - Description: CIDR block for Public subnet 0 located in Availability Zone 0 - Type: String - PublicSubnet1Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.201.0/24 - Description: CIDR block for Public subnet 1 located in Availability Zone 1 - Type: String - PublicSubnet2Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.202.0/24 - Description: CIDR block for Public subnet 2 located in Availability Zone 2 - Type: String - PublicSubnet3Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.203.0/24 - Description: CIDR block for Public subnet 3 located in Availability Zone 3 - Type: String - PublicSubnet4Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.204.0/24 - Description: CIDR block for Public subnet 4 located in Availability Zone 4 - Type: String - PublicSubnet5Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.205.0/24 - Description: CIDR block for Public subnet 5 located in Availability Zone 5 - Type: String - WebSubnet0Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.0.0/22 - Description: CIDR block for Web subnet 0 located in Availability Zone 0 - Type: String - WebSubnet1Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.4.0/22 - Description: CIDR block for Web subnet 1 located in Availability Zone 1 - Type: String - WebSubnet2Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.8.0/22 - Description: CIDR block for Web subnet 2 located in Availability Zone 2 - Type: String - WebSubnet3Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.12.0/22 - Description: CIDR block for Web subnet 3 located in Availability Zone 3 - Type: String - WebSubnet4Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.16.0/22 - Description: CIDR block for Web subnet 4 located in Availability Zone 4 - Type: String - WebSubnet5Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.20.0/22 - Description: CIDR block for Web subnet 5 located in Availability Zone 5 - Type: String - UseCloudFrontBoolean: - AllowedValues: - - true - - false - Default: true - Description: Specifies whether a CloudFront Distribution should be created to serve the Moodle website content. - Type: String - UseRoute53Boolean: - AllowedValues: - - true - - false - Default: true - Description: Specifies whether a record set should be created in Route 53 for your Moodle domain name. - Type: String - - -Conditions: - DeployWithoutSessionCache: - !Equals [ false, !Ref UseSessionCacheBoolean ] - DeployWithSessionCache: - !Equals [ true, !Ref UseSessionCacheBoolean ] - DeployApplicationCache: - !Equals [ true, !Ref UseApplicationCacheBoolean ] - DeployRoute53: - !Equals [ true, !Ref UseRoute53Boolean ] - DeployCloudFront: - !Equals [ true, !Ref UseCloudFrontBoolean ] - -Resources: - MyRDSInstanceSecret: - Type: AWS::SecretsManager::Secret - Properties: - Description: 'This is the secret for my RDS instance' - GenerateSecretString: - SecretStringTemplate: !Sub '{"username": "${DatabaseMasterUsername}"}' - GenerateStringKey: 'password' - PasswordLength: 16 - ExcludeCharacters: '"@/\' - vpc: - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - NumberOfAZs: - !Ref NumberOfAZs - AvailabilityZones: - !Join - - ',' - - !Ref AvailabilityZones - VpcCidr: - !Ref VpcCidr - VpcTenancy: - !Ref VpcTenancy - PublicSubnet0Cidr: - !Ref PublicSubnet0Cidr - PublicSubnet1Cidr: - !Ref PublicSubnet1Cidr - PublicSubnet2Cidr: - !Ref PublicSubnet2Cidr - PublicSubnet3Cidr: - !Ref PublicSubnet3Cidr - PublicSubnet4Cidr: - !Ref PublicSubnet4Cidr - PublicSubnet5Cidr: - !Ref PublicSubnet5Cidr - WebSubnet0Cidr: - !Ref WebSubnet0Cidr - WebSubnet1Cidr: - !Ref WebSubnet1Cidr - WebSubnet2Cidr: - !Ref WebSubnet2Cidr - WebSubnet3Cidr: - !Ref WebSubnet3Cidr - WebSubnet4Cidr: - !Ref WebSubnet4Cidr - WebSubnet5Cidr: - !Ref WebSubnet5Cidr - DataSubnet0Cidr: - !Ref DataSubnet0Cidr - DataSubnet1Cidr: - !Ref DataSubnet1Cidr - DataSubnet2Cidr: - !Ref DataSubnet2Cidr - DataSubnet3Cidr: - !Ref DataSubnet3Cidr - DataSubnet4Cidr: - !Ref DataSubnet4Cidr - DataSubnet5Cidr: - !Ref DataSubnet5Cidr - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/01-newvpc.yaml - securitygroups: - DependsOn: vpc - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - SshAccessCidr: - !Ref SshAccessCidr - Vpc: - !GetAtt [ vpc, Outputs.Vpc ] - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/02-securitygroups.yaml - bastion: - DependsOn: securitygroups - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - BastionInstanceType: - !Ref BastionInstanceType - BastionSecurityGroup: - !GetAtt [ securitygroups, Outputs.BastionSecurityGroup ] - EC2KeyName: - !Ref EC2KeyName - NumberOfSubnets: - !Ref NumberOfAZs - Subnet: - !GetAtt [ vpc, Outputs.PublicSubnet ] - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/03-bastion.yaml - publicalb: - DependsOn: securitygroups - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - NumberOfSubnets: - !Ref NumberOfAZs - Subnet: - !GetAtt [ vpc, Outputs.PublicSubnet ] - PublicAlbAcmCertificate: - !Ref PublicAlbAcmCertificate - PublicAlbSecurityGroup: - !GetAtt [ securitygroups, Outputs.PublicAlbSecurityGroup ] - Vpc: - !GetAtt [ vpc, Outputs.Vpc ] - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/03-publicalb.yaml - rds: - DependsOn: [ securitygroups ] - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - DatabaseInstanceType: - !Ref DatabaseInstanceType - # DatabaseMasterUsername: - # !Ref DatabaseMasterUsername - MyRDSInstanceSecretArn: - !Ref MyRDSInstanceSecret - DatabaseName: - !Ref DatabaseName - DatabaseEncrpytedBoolean: - !Ref DatabaseEncrpytedBoolean - DatabaseCmk: - !Ref DatabaseCmk - DatabaseSecurityGroup: - !GetAtt [ securitygroups, Outputs.DatabaseSecurityGroup ] - Subnet: - !GetAtt [ vpc, Outputs.DataSubnet ] - NumberOfSubnets: - !Ref NumberOfAZs - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/03-rds.yaml - efsfilesystem: - DependsOn: securitygroups - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - EncrpytedBoolean: - !Ref EfsEncrpytedBoolean - Cmk: - !Ref EfsCmk - Growth: - !Ref EfsGrowth - InstanceType: - !Ref EfsGrowthInstanceType - EC2KeyName: - !Ref EC2KeyName - SecurityGroup: - !GetAtt [ securitygroups, Outputs.EfsSecurityGroup ] - NumberOfSubnets: - !Ref NumberOfAZs - PerformanceMode: - !Ref EfsPerformanceMode - Subnet: - !GetAtt [ vpc, Outputs.DataSubnet ] - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/03-efsfilesystem.yaml - sessioncache: - Condition: DeployWithSessionCache - DependsOn: securitygroups - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - Subnet: - !GetAtt [ vpc, Outputs.DataSubnet ] - ElastiCacheClusterName: - !Sub '${AWS::StackName}session' - ElastiCacheNodeType: - !Ref SessionCacheNodeType - ElastiCacheSecurityGroup: - !GetAtt [ securitygroups, Outputs.ElastiCacheSecurityGroup ] - NumberOfSubnets: - !Ref NumberOfAZs - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/03-elasticache.yaml - applicationcache: - Condition: DeployApplicationCache - DependsOn: securitygroups - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - Subnet: - !GetAtt [ vpc, Outputs.DataSubnet ] - ElastiCacheClusterName: - !Sub '${AWS::StackName}application' - ElastiCacheNodeType: - !Ref ApplicationCacheNodeType - ElastiCacheSecurityGroup: - !GetAtt [ securitygroups, Outputs.ElastiCacheSecurityGroup ] - NumberOfSubnets: - !Ref NumberOfAZs - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/03-elasticache.yaml - webnocache: - DependsOn: [ publicalb, rds, efsfilesystem ] - Condition: DeployWithoutSessionCache - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - DatabaseClusterEndpointAddress: - !GetAtt [ rds, Outputs.DatabaseClusterEndpointAddress ] - # DatabaseMasterUsername: - # !Ref DatabaseMasterUsername - MyRDSInstanceSecretArn: - !Ref MyRDSInstanceSecret - DatabaseName: - !Ref DatabaseName - ElasticFileSystem: - !GetAtt [ efsfilesystem, Outputs.ElasticFileSystem ] - EC2KeyName: - !Ref EC2KeyName - NumberOfSubnets: - !Ref NumberOfAZs - PublicAlbTargetGroupArn: - !GetAtt [ publicalb, Outputs.PublicAlbTargetGroupArn ] - PublicAlbHostname: - !GetAtt [ publicalb, Outputs.PublicAlbHostname ] - SslCertificate: - !GetAtt [ publicalb, Outputs.SslCertificate ] - WebAsgMax: - !Ref WebAsgMax - WebAsgMin: - !Ref WebAsgMin - WebInstanceType: - !Ref WebInstanceType - WebSecurityGroup: - !GetAtt [ securitygroups, Outputs.WebSecurityGroup ] - Subnet: - !GetAtt [ vpc, Outputs.WebSubnet ] - DomainName: - !Ref DomainName - MoodleLocale: - !Ref MoodleLocale - ElastiCacheClusterEndpointAddress: - '' - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/04-web.yaml - webcached: - DependsOn: [ publicalb, rds, efsfilesystem, sessioncache ] - Condition: DeployWithSessionCache - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - DatabaseClusterEndpointAddress: - !GetAtt [ rds, Outputs.DatabaseClusterEndpointAddress ] - # DatabaseMasterUsername: - # !Ref DatabaseMasterUsername - MyRDSInstanceSecretArn: - !Ref MyRDSInstanceSecret - DatabaseName: - !Ref DatabaseName - ElasticFileSystem: - !GetAtt [ efsfilesystem, Outputs.ElasticFileSystem ] - EC2KeyName: - !Ref EC2KeyName - NumberOfSubnets: - !Ref NumberOfAZs - PublicAlbTargetGroupArn: - !GetAtt [ publicalb, Outputs.PublicAlbTargetGroupArn ] - PublicAlbHostname: - !GetAtt [ publicalb, Outputs.PublicAlbHostname ] - SslCertificate: - !GetAtt [ publicalb, Outputs.SslCertificate ] - WebAsgMax: - !Ref WebAsgMax - WebAsgMin: - !Ref WebAsgMin - WebInstanceType: - !Ref WebInstanceType - WebSecurityGroup: - !GetAtt [ securitygroups, Outputs.WebSecurityGroup ] - Subnet: - !GetAtt [ vpc, Outputs.WebSubnet ] - DomainName: - !Ref DomainName - MoodleLocale: - !Ref MoodleLocale - ElastiCacheClusterEndpointAddress: - !GetAtt sessioncache.Outputs.ElastiCacheClusterEndpointAddress - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/04-web.yaml - cloudfront: - Condition: DeployCloudFront - DependsOn: publicalb - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - CloudFrontAcmCertificate: - !Ref CloudFrontAcmCertificate - PublicAlbDnsName: - !GetAtt [ publicalb, Outputs.PublicAlbDnsName ] - DomainName: - !Ref DomainName - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/04-cloudfront.yaml - route53: - Condition: DeployRoute53 - DependsOn: publicalb - Type: AWS::CloudFormation::Stack - Properties: - Parameters: - DnsEndpoint: - !If [ DeployCloudFront, !GetAtt [ cloudfront, Outputs.DnsEndpoint ], !GetAtt [ publicalb, Outputs.PublicAlbDnsName ] ] - DnsHostId: - !If [ DeployCloudFront, 'Z2FDTNDATAQYW2', !GetAtt [ publicalb, Outputs.PublicAlbCanonicalHostedZoneId ] ] - HostedZoneName: - !Ref HostedZoneName - DomainName: - !Ref DomainName - TemplateURL: https://s3.amazonaws.com/aws-refarch/moodle/latest/templates/05-route53.yaml - -Outputs: - DnsName: - Description: DNS name of the new load balancer for Moodle. - Value: - !GetAtt publicalb.Outputs.PublicAlbDnsName \ No newline at end of file diff --git a/templates/01-newvpc.yaml b/templates/01-newvpc.yaml index e5ca91d..91da615 100644 --- a/templates/01-newvpc.yaml +++ b/templates/01-newvpc.yaml @@ -14,25 +14,16 @@ Metadata: - NumberOfAZs - AvailabilityZones - VpcCidr - - VpcTenancy - PublicSubnet0Cidr - PublicSubnet1Cidr - PublicSubnet2Cidr - - PublicSubnet3Cidr - - PublicSubnet4Cidr - - PublicSubnet5Cidr - - WebSubnet0Cidr - - WebSubnet1Cidr - - WebSubnet2Cidr - - WebSubnet3Cidr - - WebSubnet4Cidr - - WebSubnet5Cidr + - AppSubnet0Cidr + - AppSubnet1Cidr + - AppSubnet2Cidr - DataSubnet0Cidr - DataSubnet1Cidr - DataSubnet2Cidr - - DataSubnet3Cidr - - DataSubnet4Cidr - - DataSubnet5Cidr + ParameterLabels: AvailabilityZones: default: Availability Zones @@ -40,45 +31,25 @@ Metadata: default: Number of Availability Zones VpcCidr: default: VpcCidr - VpcTenancy: - default: VpcTenancy PublicSubnet0Cidr: default: Public Subnet 0 PublicSubnet1Cidr: default: Public Subnet 1 PublicSubnet2Cidr: default: Public Subnet 2 - PublicSubnet3Cidr: - default: Public Subnet 3 - PublicSubnet4Cidr: - default: Public Subnet 4 - PublicSubnet5Cidr: - default: Public Subnet 5 - WebSubnet0Cidr: - default: Web Subnet 0 - WebSubnet1Cidr: - default: Web Subnet 1 - WebSubnet2Cidr: - default: Web Subnet 2 - WebSubnet3Cidr: - default: Web Subnet 3 - WebSubnet4Cidr: - default: Web Subnet 4 - WebSubnet5Cidr: - default: Web Subnet 5 + AppSubnet0Cidr: + default: App Subnet 0 + AppSubnet1Cidr: + default: App Subnet 1 + AppSubnet2Cidr: + default: App Subnet 2 DataSubnet0Cidr: default: Data Subnet 0 DataSubnet1Cidr: default: Data Subnet 1 DataSubnet2Cidr: default: Data Subnet 2 - DataSubnet3Cidr: - default: Data Subnet 3 - DataSubnet4Cidr: - default: Data Subnet 4 - DataSubnet5Cidr: - default: Data Subnet 5 - + Parameters: AvailabilityZones: @@ -87,12 +58,10 @@ Parameters: Type: List NumberOfAZs: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 - Default: 3 + Default: 2 Description: Number of Availability Zones to use in the VPC. This must match your selections in the list of Availability Zones parameter. Type: Number @@ -102,13 +71,6 @@ Parameters: Default: 10.0.0.0/16 Description: CIDR block for the VPC Type: String - VpcTenancy: - AllowedValues: - - default - - dedicated - Default: default - Description: The allowed tenancy of instances launched into the VPC - Type: String DataSubnet0Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 @@ -127,24 +89,6 @@ Parameters: Default: 10.0.102.0/24 Description: CIDR block for data subnet 2 located in Availability Zone 2 Type: String - DataSubnet3Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.103.0/24 - Description: CIDR block for data subnet 3 located in Availability Zone 3 - Type: String - DataSubnet4Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.104.0/24 - Description: CIDR block for data subnet 4 located in Availability Zone 4 - Type: String - DataSubnet5Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.105.0/24 - Description: CIDR block for data subnet 5 located in Availability Zone 5 - Type: String PublicSubnet0Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 @@ -163,61 +107,25 @@ Parameters: Default: 10.0.202.0/24 Description: CIDR block for Public subnet 2 located in Availability Zone 2 Type: String - PublicSubnet3Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.203.0/24 - Description: CIDR block for Public subnet 3 located in Availability Zone 3 - Type: String - PublicSubnet4Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.204.0/24 - Description: CIDR block for Public subnet 4 located in Availability Zone 4 - Type: String - PublicSubnet5Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.205.0/24 - Description: CIDR block for Public subnet 5 located in Availability Zone 5 - Type: String - WebSubnet0Cidr: + AppSubnet0Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Default: 10.0.0.0/22 - Description: CIDR block for Web subnet 0 located in Availability Zone 0 + Description: CIDR block for App Subnet 0 located in Availability Zone 0 Type: String - WebSubnet1Cidr: + AppSubnet1Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Default: 10.0.4.0/22 - Description: CIDR block for Web subnet 1 located in Availability Zone 1 + Description: CIDR block for App Subnet 1 located in Availability Zone 1 Type: String - WebSubnet2Cidr: + AppSubnet2Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Default: 10.0.8.0/22 - Description: CIDR block for Web subnet 2 located in Availability Zone 2 - Type: String - WebSubnet3Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.12.0/22 - Description: CIDR block for Web subnet 3 located in Availability Zone 3 + Description: CIDR block for App Subnet 2 located in Availability Zone 2 Type: String - WebSubnet4Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.16.0/22 - Description: CIDR block for Web subnet 4 located in Availability Zone 4 - Type: String - WebSubnet5Cidr: - AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" - ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 - Default: 10.0.20.0/22 - Description: CIDR block for Web subnet 5 located in Availability Zone 5 - Type: String - + Conditions: NumberOfAZs1: @@ -226,157 +134,76 @@ Conditions: !Equals [ '2', !Ref NumberOfAZs ] NumberOfAZs3: !Equals [ '3', !Ref NumberOfAZs ] - NumberOfAZs4: - !Equals [ '4', !Ref NumberOfAZs ] - NumberOfAZs5: - !Equals [ '5', !Ref NumberOfAZs ] - NumberOfAZs6: - !Equals [ '6', !Ref NumberOfAZs ] AZ0: !Or - !Condition NumberOfAZs1 - !Condition NumberOfAZs2 - !Condition NumberOfAZs3 - - !Condition NumberOfAZs4 - - !Condition NumberOfAZs5 - - !Condition NumberOfAZs6 AZ1: !Or - !Condition NumberOfAZs2 - !Condition NumberOfAZs3 - - !Condition NumberOfAZs4 - - !Condition NumberOfAZs5 - - !Condition NumberOfAZs6 - AZ2: !Or - - !Condition NumberOfAZs3 - - !Condition NumberOfAZs4 - - !Condition NumberOfAZs5 - - !Condition NumberOfAZs6 - AZ3: !Or - - !Condition NumberOfAZs4 - - !Condition NumberOfAZs5 - - !Condition NumberOfAZs6 - AZ4: !Or - - !Condition NumberOfAZs5 - - !Condition NumberOfAZs6 - AZ5: !Condition NumberOfAZs6 - + AZ2: !Condition NumberOfAZs3 + Resources: - WebSubnet0: + AppSubnet0: Condition: AZ0 Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 0, !Ref AvailabilityZones ] - CidrBlock: !Ref WebSubnet0Cidr + CidrBlock: !Ref AppSubnet0Cidr MapPublicIpOnLaunch: false Tags: - Key: Name - Value: !Join [ '', [ 'WebSubnet0 / ', !Ref 'AWS::StackName' ] ] + Value: !Join [ '', [ 'AppSubnet0 / ', !Ref 'AWS::StackName' ] ] - Key: SubnetType Value: Private VpcId: !Ref Vpc - WebSubnet1: + AppSubnet1: Condition: AZ1 Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 1, !Ref AvailabilityZones ] - CidrBlock: !Ref WebSubnet1Cidr + CidrBlock: !Ref AppSubnet1Cidr MapPublicIpOnLaunch: false Tags: - Key: Name - Value: !Join [ '', [ 'WebSubnet1 / ', !Ref 'AWS::StackName' ] ] + Value: !Join [ '', [ 'AppSubnet1 / ', !Ref 'AWS::StackName' ] ] - Key: SubnetType Value: Private VpcId: !Ref Vpc - WebSubnet2: + AppSubnet2: Condition: AZ2 Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select [ 2, !Ref AvailabilityZones ] - CidrBlock: !Ref WebSubnet2Cidr - MapPublicIpOnLaunch: false - Tags: - - Key: Name - Value: !Join [ '', [ 'WebSubnet2 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Private - VpcId: !Ref Vpc - WebSubnet3: - Condition: AZ3 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 3, !Ref AvailabilityZones ] - CidrBlock: !Ref WebSubnet3Cidr + CidrBlock: !Ref AppSubnet2Cidr MapPublicIpOnLaunch: false Tags: - Key: Name - Value: !Join [ '', [ 'WebSubnet3 / ', !Ref 'AWS::StackName' ] ] + Value: !Join [ '', [ 'AppSubnet2 / ', !Ref 'AWS::StackName' ] ] - Key: SubnetType Value: Private VpcId: !Ref Vpc - WebSubnet4: - Condition: AZ4 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 4, !Ref AvailabilityZones ] - CidrBlock: !Ref WebSubnet4Cidr - MapPublicIpOnLaunch: false - Tags: - - Key: Name - Value: !Join [ '', [ 'WebSubnet4 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Private - VpcId: !Ref Vpc - WebSubnet5: - Condition: AZ5 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 5, !Ref AvailabilityZones ] - CidrBlock: !Ref WebSubnet5Cidr - MapPublicIpOnLaunch: false - Tags: - - Key: Name - Value: !Join [ '', [ 'WebSubnet5 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Private - VpcId: !Ref Vpc - - WebSubnetRouteTableAssociation0: + + AppSubnetRouteTableAssociation0: Condition: AZ0 Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref NatRouteTable0 - SubnetId: !Ref WebSubnet0 - WebSubnetRouteTableAssociation1: + SubnetId: !Ref AppSubnet0 + AppSubnetRouteTableAssociation1: Condition: AZ1 Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref NatRouteTable1 - SubnetId: !Ref WebSubnet1 - WebSubnetRouteTableAssociation2: + SubnetId: !Ref AppSubnet1 + AppSubnetRouteTableAssociation2: Condition: AZ2 Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref NatRouteTable2 - SubnetId: !Ref WebSubnet2 - WebSubnetRouteTableAssociation3: - Condition: AZ3 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref NatRouteTable3 - SubnetId: !Ref WebSubnet3 - WebSubnetRouteTableAssociation4: - Condition: AZ4 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref NatRouteTable4 - SubnetId: !Ref WebSubnet4 - WebSubnetRouteTableAssociation5: - Condition: AZ5 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref NatRouteTable5 - SubnetId: !Ref WebSubnet5 - + SubnetId: !Ref AppSubnet2 + DataSubnet0: Condition: AZ0 Type: AWS::EC2::Subnet @@ -416,46 +243,7 @@ Resources: - Key: SubnetType Value: Private VpcId: !Ref Vpc - DataSubnet3: - Condition: AZ3 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 3, !Ref AvailabilityZones ] - CidrBlock: !Ref DataSubnet3Cidr - MapPublicIpOnLaunch: false - Tags: - - Key: Name - Value: !Join [ '', [ 'DataSubnet3 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Private - VpcId: !Ref Vpc - DataSubnet4: - Condition: AZ4 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 4, !Ref AvailabilityZones ] - CidrBlock: !Ref DataSubnet4Cidr - MapPublicIpOnLaunch: false - Tags: - - Key: Name - Value: !Join [ '', [ 'DataSubnet4 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Private - VpcId: !Ref Vpc - DataSubnet5: - Condition: AZ5 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 5, !Ref AvailabilityZones ] - CidrBlock: !Ref DataSubnet5Cidr - MapPublicIpOnLaunch: false - Tags: - - Key: Name - Value: !Join [ '', [ 'DataSubnet5 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Private - VpcId: !Ref Vpc - + DataSubnetRouteTableAssociation0: Condition: AZ0 Type: AWS::EC2::SubnetRouteTableAssociation @@ -474,25 +262,7 @@ Resources: Properties: RouteTableId: !Ref NatRouteTable2 SubnetId: !Ref DataSubnet2 - DataSubnetRouteTableAssociation3: - Condition: AZ3 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref NatRouteTable3 - SubnetId: !Ref DataSubnet3 - DataSubnetRouteTableAssociation4: - Condition: AZ4 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref NatRouteTable4 - SubnetId: !Ref DataSubnet4 - DataSubnetRouteTableAssociation5: - Condition: AZ5 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - RouteTableId: !Ref NatRouteTable5 - SubnetId: !Ref DataSubnet5 - + InternetGateway: Type: AWS::EC2::InternetGateway Properties: @@ -595,96 +365,6 @@ Resources: Value: Public VpcId: !Ref Vpc - NatEIP3: - Condition: AZ3 - Type: AWS::EC2::EIP - Properties: - Domain: vpc - NatGateway3: - Condition: AZ3 - Type: AWS::EC2::NatGateway - DependsOn: AttachInternetGateway - Properties: - AllocationId: !GetAtt NatEIP3.AllocationId - SubnetId: !Ref PublicSubnet3 - NatRoute3: - Condition: AZ3 - Type: AWS::EC2::Route - Properties: - RouteTableId: !Ref NatRouteTable3 - DestinationCidrBlock: 0.0.0.0/0 - NatGatewayId: !Ref NatGateway3 - NatRouteTable3: - Condition: AZ3 - Type: AWS::EC2::RouteTable - Properties: - Tags: - - Key: Name - Value: !Join [ '', [ 'NatRouteTable3 / ', !Ref 'AWS::StackName' ] ] - - Key: Network - Value: Public - VpcId: !Ref Vpc - - NatEIP4: - Condition: AZ4 - Type: AWS::EC2::EIP - Properties: - Domain: vpc - NatGateway4: - Condition: AZ4 - Type: AWS::EC2::NatGateway - DependsOn: AttachInternetGateway - Properties: - AllocationId: !GetAtt NatEIP4.AllocationId - SubnetId: !Ref PublicSubnet4 - NatRoute4: - Condition: AZ4 - Type: AWS::EC2::Route - Properties: - RouteTableId: !Ref NatRouteTable4 - DestinationCidrBlock: 0.0.0.0/0 - NatGatewayId: !Ref NatGateway4 - NatRouteTable4: - Condition: AZ4 - Type: AWS::EC2::RouteTable - Properties: - Tags: - - Key: Name - Value: !Join [ '', [ 'NatRouteTable4 / ', !Ref 'AWS::StackName' ] ] - - Key: Network - Value: Public - VpcId: !Ref Vpc - - NatEIP5: - Condition: AZ5 - Type: AWS::EC2::EIP - Properties: - Domain: vpc - NatGateway5: - Condition: AZ5 - Type: AWS::EC2::NatGateway - DependsOn: AttachInternetGateway - Properties: - AllocationId: !GetAtt NatEIP5.AllocationId - SubnetId: !Ref PublicSubnet5 - NatRoute5: - Condition: AZ5 - Type: AWS::EC2::Route - Properties: - RouteTableId: !Ref NatRouteTable5 - DestinationCidrBlock: 0.0.0.0/0 - NatGatewayId: !Ref NatGateway5 - NatRouteTable5: - Condition: AZ5 - Type: AWS::EC2::RouteTable - Properties: - Tags: - - Key: Name - Value: !Join [ '', [ 'NatRouteTable5 / ', !Ref 'AWS::StackName' ] ] - - Key: Network - Value: Public - VpcId: !Ref Vpc - PublicRoute: Type: AWS::EC2::Route DependsOn: AttachInternetGateway @@ -719,25 +399,7 @@ Resources: Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable - PublicRouteTableAssociation3: - Condition: AZ3 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnet3 - RouteTableId: !Ref PublicRouteTable - PublicRouteTableAssociation4: - Condition: AZ4 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnet4 - RouteTableId: !Ref PublicRouteTable - PublicRouteTableAssociation5: - Condition: AZ5 - Type: AWS::EC2::SubnetRouteTableAssociation - Properties: - SubnetId: !Ref PublicSubnet5 - RouteTableId: !Ref PublicRouteTable - + PublicSubnet0: Condition: AZ0 Type: AWS::EC2::Subnet @@ -777,46 +439,7 @@ Resources: - Key: SubnetType Value: Public VpcId: !Ref Vpc - PublicSubnet3: - Condition: AZ3 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 3, !Ref AvailabilityZones ] - CidrBlock: !Ref PublicSubnet3Cidr - MapPublicIpOnLaunch: true - Tags: - - Key: Name - Value: !Join [ '', [ 'PublicSubnet3 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Public - VpcId: !Ref Vpc - PublicSubnet4: - Condition: AZ4 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 4, !Ref AvailabilityZones ] - CidrBlock: !Ref PublicSubnet4Cidr - MapPublicIpOnLaunch: true - Tags: - - Key: Name - Value: !Join [ '', [ 'PublicSubnet4 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Public - VpcId: !Ref Vpc - PublicSubnet5: - Condition: AZ5 - Type: AWS::EC2::Subnet - Properties: - AvailabilityZone: !Select [ 5, !Ref AvailabilityZones ] - CidrBlock: !Ref PublicSubnet5Cidr - MapPublicIpOnLaunch: true - Tags: - - Key: Name - Value: !Join [ '', [ 'PublicSubnet5 / ', !Ref 'AWS::StackName' ] ] - - Key: SubnetType - Value: Public - VpcId: !Ref Vpc - + Vpc: Type: AWS::EC2::VPC Properties: @@ -876,33 +499,15 @@ Outputs: PublicSubnet2: Condition: AZ2 Value: !Ref PublicSubnet2 - PublicSubnet3: - Condition: AZ3 - Value: !Ref PublicSubnet3 - PublicSubnet4: - Condition: AZ4 - Value: !Ref PublicSubnet4 - PublicSubnet5: - Condition: AZ5 - Value: !Ref PublicSubnet5 - WebSubnet0: + AppSubnet0: Condition: AZ0 - Value: !Ref WebSubnet0 - WebSubnet1: + Value: !Ref AppSubnet0 + AppSubnet1: Condition: AZ1 - Value: !Ref WebSubnet1 - WebSubnet2: + Value: !Ref AppSubnet1 + AppSubnet2: Condition: AZ2 - Value: !Ref WebSubnet2 - WebSubnet3: - Condition: AZ3 - Value: !Ref WebSubnet3 - WebSubnet4: - Condition: AZ4 - Value: !Ref WebSubnet4 - WebSubnet5: - Condition: AZ5 - Value: !Ref WebSubnet5 + Value: !Ref AppSubnet2 DataSubnet0: Condition: AZ0 Value: !Ref DataSubnet0 @@ -912,15 +517,6 @@ Outputs: DataSubnet2: Condition: AZ2 Value: !Ref DataSubnet2 - DataSubnet3: - Condition: AZ3 - Value: !Ref DataSubnet3 - DataSubnet4: - Condition: AZ4 - Value: !Ref DataSubnet4 - DataSubnet5: - Condition: AZ5 - Value: !Ref DataSubnet5 DataSubnet: Value: !If @@ -929,42 +525,18 @@ Outputs: !If [ NumberOfAZs2, !Join [ ',', [ !Ref DataSubnet0, !Ref DataSubnet1 ] ], - !If - [ NumberOfAZs3, - !Join [ ',', [ !Ref DataSubnet0, !Ref DataSubnet1, !Ref DataSubnet2 ] ], - !If - [ NumberOfAZs4, - !Join [ ',', [ !Ref DataSubnet0, !Ref DataSubnet1, !Ref DataSubnet2, !Ref DataSubnet3 ] ], - !If - [ NumberOfAZs5, - !Join [ ',', [ !Ref DataSubnet0, !Ref DataSubnet1, !Ref DataSubnet2, !Ref DataSubnet3, !Ref DataSubnet4 ] ], - !Join [ ',', [ !Ref DataSubnet0, !Ref DataSubnet1, !Ref DataSubnet2, !Ref DataSubnet3, !Ref DataSubnet4, !Ref DataSubnet5 ] ] - ] - ] - ] + !Join [ ',', [ !Ref DataSubnet0, !Ref DataSubnet1, !Ref DataSubnet2 ] ] ] ] - WebSubnet: + AppSubnet: Value: !If [ NumberOfAZs1, - !Ref WebSubnet0, + !Ref AppSubnet0, !If [ NumberOfAZs2, - !Join [ ',', [ !Ref WebSubnet0, !Ref WebSubnet1 ] ], - !If - [ NumberOfAZs3, - !Join [ ',', [ !Ref WebSubnet0, !Ref WebSubnet1, !Ref WebSubnet2 ] ], - !If - [ NumberOfAZs4, - !Join [ ',', [ !Ref WebSubnet0, !Ref WebSubnet1, !Ref WebSubnet2, !Ref WebSubnet3 ] ], - !If - [ NumberOfAZs5, - !Join [ ',', [ !Ref WebSubnet0, !Ref WebSubnet1, !Ref WebSubnet2, !Ref WebSubnet3, !Ref WebSubnet4 ] ], - !Join [ ',', [ !Ref WebSubnet0, !Ref WebSubnet1, !Ref WebSubnet2, !Ref WebSubnet3, !Ref WebSubnet4, !Ref WebSubnet5 ] ] - ] - ] - ] + !Join [ ',', [ !Ref AppSubnet0, !Ref AppSubnet1 ] ], + !Join [ ',', [ !Ref AppSubnet0, !Ref AppSubnet1, !Ref AppSubnet2 ] ] ] ] PublicSubnet: @@ -975,19 +547,7 @@ Outputs: !If [ NumberOfAZs2, !Join [ ',', [ !Ref PublicSubnet0, !Ref PublicSubnet1 ] ], - !If - [ NumberOfAZs3, - !Join [ ',', [ !Ref PublicSubnet0, !Ref PublicSubnet1, !Ref PublicSubnet2 ] ], - !If - [ NumberOfAZs4, - !Join [ ',', [ !Ref PublicSubnet0, !Ref PublicSubnet1, !Ref PublicSubnet2, !Ref PublicSubnet3 ] ], - !If - [ NumberOfAZs5, - !Join [ ',', [ !Ref PublicSubnet0, !Ref PublicSubnet1, !Ref PublicSubnet2, !Ref PublicSubnet3, !Ref PublicSubnet4 ] ], - !Join [ ',', [ !Ref PublicSubnet0, !Ref PublicSubnet1, !Ref PublicSubnet2, !Ref PublicSubnet3, !Ref PublicSubnet4, !Ref PublicSubnet5 ] ] - ] - ] - ] - ] + !Join [ ',', [ !Ref PublicSubnet0, !Ref PublicSubnet1, !Ref PublicSubnet2 ] ] + ] ] diff --git a/templates/02-securitygroups.yaml b/templates/02-securitygroups.yaml index 7d939a9..0704e69 100644 --- a/templates/02-securitygroups.yaml +++ b/templates/02-securitygroups.yaml @@ -27,7 +27,16 @@ Parameters: AllowedPattern: ^(vpc-)([a-z0-9]{8}|[a-z0-9]{17})$ Description: The Vpc Id of an existing Vpc. Type: AWS::EC2::VPC::Id - + DatabaseType: + AllowedValues: + - MySQL + - PostgreSQL + Default: PostgreSQL + Description: Indicates whether to use Aurora MySQL or PostgreSQL. + Type: String +Conditions: + UsePostgreSQL: + !Equals [!Ref DatabaseType, PostgreSQL] Resources: BastionSecurityGroup: @@ -48,8 +57,8 @@ Resources: GroupDescription: Security group for Amazon RDS cluster SecurityGroupIngress: - IpProtocol: tcp - FromPort: 5432 - ToPort: 5432 + FromPort: !If [ UsePostgreSQL, 5432, 3306 ] + ToPort: !If [ UsePostgreSQL, 5432, 3306 ] SourceSecurityGroupId: !Ref WebSecurityGroup VpcId: !Ref Vpc @@ -63,6 +72,10 @@ Resources: FromPort: 11211 ToPort: 11211 SourceSecurityGroupId: !Ref WebSecurityGroup + - IpProtocol: tcp + FromPort: 6379 + ToPort: 6379 + SourceSecurityGroupId: !Ref WebSecurityGroup VpcId: !Ref Vpc EfsSecurityGroup: diff --git a/templates/03-bastion.yaml b/templates/03-bastion.yaml index 2a1878d..e076a23 100644 --- a/templates/03-bastion.yaml +++ b/templates/03-bastion.yaml @@ -34,97 +34,101 @@ Parameters: Type: AWS::EC2::SecurityGroup::Id BastionInstanceType: AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - t2.xlarge - - t2.2xlarge - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - m4.16xlarge - - m5.large - - m5.xlarge - - m5.2xlarge - - m5.4xlarge - - m5.12xlarge - - m5.24xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - c5.large - - c5.xlarge - - c5.2xlarge - - c5.4xlarge - - c5.9xlarge - - c5.18xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - r4.large - - r4.xlarge - - r4.2xlarge - - r4.4xlarge - - r4.8xlarge - - r4.16xlarge - - x1.16xlarge - - x1.32xlarge - - x1e.xlarge - - x1e.2xlarge - - x1e.4xlarge - - x1e.8xlarge - - x1e.16xlarge - - x1e.32xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - - h1.2xlarge - - h1.4xlarge - - h1.8xlarge - - h1.16xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - i3.large - - i3.xlarge - - i3.2xlarge - - i3.4xlarge - - i3.8xlarge - - i3.16xlarge - - f1.2xlarge - - f1.16xlarge - - g2.2xlarge - - g2.8xlarge - - g3.4xlarge - - g3.8xlarge - - g3.16xlarge - - p2.xlarge - - p2.8xlarge - - p2.16xlarge - - p3.2xlarge - - p3.8xlarge - - p3.16xlarge + - t3.nano + - t3.micro + - t3.small + - t3.medium + - t3.large + - t3.xlarge + - t3.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - t3a.nano + - t3a.micro + - t3a.small + - t3a.medium + - t3a.large + - t3a.xlarge + - t3a.2xlarge + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.9xlarge + - c5a.12xlarge + - c5a.18xlarge + - c5a.24xlarge + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - t4g.nano + - t4g.micro + - t4g.small + - t4g.medium + - t4g.large + - t4g.xlarge + - t4g.2xlarge + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6g.24xlarge + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.9xlarge + - c6g.12xlarge + - c6g.18xlarge + - c6g.24xlarge + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.24xlarge ConstraintDescription: Must be a valid Amazon EC2 instance type. - Default: t2.nano + Default: t4g.nano Description: Bastion EC2 instance type. Type: String EC2KeyName: @@ -132,12 +136,10 @@ Parameters: Type: AWS::EC2::KeyPair::KeyName NumberOfSubnets: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 - Default: 3 + Default: 2 Description: Number of subnets. This must match your selections in the list of subnets below. Type: String Subnet: @@ -146,6 +148,9 @@ Parameters: LatestAmiId : Type : AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 + LatestArmAmiId : + Type : AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 Conditions: @@ -156,38 +161,19 @@ Conditions: !Equals [ 2, !Ref NumberOfSubnets ] NumberOfSubnets3: !Equals [ 3, !Ref NumberOfSubnets ] - NumberOfSubnets4: - !Equals [ 4, !Ref NumberOfSubnets ] - NumberOfSubnets5: - !Equals [ 5, !Ref NumberOfSubnets ] - NumberOfSubnets6: - !Equals [ 6, !Ref NumberOfSubnets ] Subnet0: !Or - !Condition NumberOfSubnets1 - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 Subnet1: !Or - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet2: !Or - - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet3: !Or - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet4: !Or - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet5: !Condition NumberOfSubnets6 + Subnet2: !Condition NumberOfSubnets3 + UsingGraviton2Ami: !Or + - !Equals ["t4",!Select [0, !Split [ "g.", !Ref BastionInstanceType]]] + - !Equals ["c6",!Select [0, !Split [ "g.", !Ref BastionInstanceType]]] + - !Equals ["m6",!Select [0, !Split [ "g.", !Ref BastionInstanceType]]] + - !Equals ["r6",!Select [0, !Split [ "g.", !Ref BastionInstanceType]]] Resources: @@ -198,7 +184,9 @@ Resources: Cooldown: 60 HealthCheckGracePeriod: 120 HealthCheckType: EC2 - LaunchConfigurationName: !Ref BastionLaunchConfiguration + LaunchTemplate: + LaunchTemplateId: !Ref BastionLaunchTemplate + Version: !GetAtt BastionLaunchTemplate.LatestVersionNumber MaxSize: 1 MinSize: 0 Tags: @@ -212,31 +200,30 @@ Resources: !If [ NumberOfSubnets2, [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], - !If - [ NumberOfSubnets3, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ], - !If - [ NumberOfSubnets4, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ], - !If - [ NumberOfSubnets5, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ], - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ] - ] - ] - ] + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] ] ] - BastionLaunchConfiguration: - Type: AWS::AutoScaling::LaunchConfiguration + BastionLaunchTemplate: + Type: AWS::EC2::LaunchTemplate Properties: - IamInstanceProfile: !Ref BastionInstanceProfile - ImageId: !Ref LatestAmiId - InstanceMonitoring: true - InstanceType: !Ref BastionInstanceType - KeyName: !Ref EC2KeyName - SecurityGroups: - - !Ref BastionSecurityGroup + LaunchTemplateData: + IamInstanceProfile: + Arn: !GetAtt BastionInstanceProfile.Arn + ImageId: !If [UsingGraviton2Ami, !Ref LatestArmAmiId, !Ref LatestAmiId] + Monitoring: + Enabled: true + InstanceType: !Ref BastionInstanceType + KeyName: !Ref EC2KeyName + SecurityGroupIds: + - !Ref BastionSecurityGroup + UserData: + Fn::Base64: + !Sub | + #!/bin/bash -xe + sudo systemctl enable amazon-ssm-agent + sudo systemctl start amazon-ssm-agent + sudo systemctl status amazon-ssm-agent + BastionInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: @@ -255,6 +242,8 @@ Resources: - ec2.amazonaws.com Action: - sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' Path: '/' Policies: - PolicyName: logs diff --git a/templates/03-codecommit.yaml b/templates/03-codecommit.yaml new file mode 100644 index 0000000..1ecae81 --- /dev/null +++ b/templates/03-codecommit.yaml @@ -0,0 +1,35 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: Reference Architecture to host Moodle on AWS - Creates AWS CodeCommit repository + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: CodeCommit Parameters + Parameters: + - RepoName + ParameterLabels: + RepoName: + default: CodeCommit Repo Name + +Parameters: + RepoName: + Description: The CodeCommit repo name. + Type: String + +Resources: + MoodleRepo: + Type: AWS::CodeCommit::Repository + Properties: + RepositoryName: !Ref RepoName + RepositoryDescription: This is a repository for my project with code from MySourceCodeBucket. + +Outputs: + RepoArn: + Value: !GetAtt MoodleRepo.Arn + RepoHTTP: + Value: !GetAtt MoodleRepo.CloneUrlHttp + RepoName: + Value: !GetAtt MoodleRepo.Name \ No newline at end of file diff --git a/templates/03-codedeploy.yaml b/templates/03-codedeploy.yaml new file mode 100644 index 0000000..a30a062 --- /dev/null +++ b/templates/03-codedeploy.yaml @@ -0,0 +1,28 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: Reference Architecture to host Moodle on AWS - Creates CodePiple and CodeDeploy + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: S3 Parameters + Parameters: + - S3BucketName + ParameterLabels: + S3BucketName: + default: S3 Bucket Name + +Parameters: + S3BucketName: + Description: The S3 Bucket name. + Type: String + +Resources: + S3Bucket: + Type: AWS::S3::Bucket + +Outputs: + BucketName: + Value: !Ref S3Bucket \ No newline at end of file diff --git a/templates/03-efsfilesystem.yaml b/templates/03-efsfilesystem.yaml index 3c365b1..b70945d 100644 --- a/templates/03-efsfilesystem.yaml +++ b/templates/03-efsfilesystem.yaml @@ -1,48 +1,10 @@ --- AWSTemplateFormatVersion: 2010-09-09 -Description: Reference Architecture to host Moodle on AWS - Creates EFS file system - -Metadata: - - AWS::CloudFormation::Interface: - - ParameterGroups: - - Label: - default: Amazon EFS Parameters - Parameters: - - Growth - - InstanceType - - EC2KeyName - - PerformanceMode - - EncrpytedBoolean - - Cmk - - SecurityGroup - - NumberOfSubnets - - Subnet - ParameterLabels: - EncrpytedBoolean: - default: Encryption state - Cmk: - default: KMS Key - Growth: - default: Add data (GiB) - InstanceType: - default: Instance Type - EC2KeyName: - default: Existing Key Pair - NumberOfSubnets: - default: Number of subnets - PerformanceMode: - default: Performance Mode - SecurityGroup: - default: EFS Security Group - Subnet: - default: Subnets +Description: Setting up EFS File System and Target mount points for each subnet Parameters: - - EncrpytedBoolean: + EncryptedBoolean: AllowedValues: - True - False @@ -52,169 +14,56 @@ Parameters: Cmk: Description: An existing AWS KMS Customer Master Key (CMK) to encrypt file system Type: String - Growth: - ConstraintDescription: Must be an integer. - Default: 0 - Description: Amount of dummy data (GiB) to add to the file system (max 6144 GiB). Amazon EFS storage charges apply. - MaxValue: 6144 - MinValue: 0 - Type: Number - InstanceType: - AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - t2.xlarge - - t2.2xlarge - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - m4.16xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - c5.large - - c5.xlarge - - c5.2xlarge - - c5.4xlarge - - c5.8xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - r4.large - - r4.xlarge - - r4.2xlarge - - r4.4xlarge - - r4.8xlarge - - r4.16xlarge - - i3.large - - i3.xlarge - - i3.2xlarge - - i3.4xlarge - - i3.8xlarge - - i3.16xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - - p2.xlarge - - p2.8xlarage - - p2.16xlarge - - g3.4xlarge - - g3.8xlarge - - g3.16xlarge - - f1.2xlarge - - f1.16xlarge - - x1.16xlarge - - x1.32xlarge - ConstraintDescription: Must be a valid Amazon EC2 instance type. - Default: r4.large - Description: The Amazon EC2 instance type that adds data to the file system. - Type: String - EC2KeyName: - Description: Name of an existing EC2 key pair - Type: AWS::EC2::KeyPair::KeyName NumberOfSubnets: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 - Default: 3 + Default: 2 Description: Number of subnets. This must match your selections in the list of Subnets below. Type: String - PerformanceMode: - AllowedValues: - - generalPurpose - - maxIO - Default: generalPurpose - Description: Select the performance mode of the file system. - Type: String SecurityGroup: Description: Select the Amazon EFS security group. Type: AWS::EC2::SecurityGroup::Id Subnet: Description: Select existing subnets. - Type: List - LatestAmiId : - Type : AWS::SSM::Parameter::Value - Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 - + Type: List + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: The Moodle Project Name + Type: String Conditions: - NumberOfSubnets1: !Equals [ 1, !Ref NumberOfSubnets ] NumberOfSubnets2: !Equals [ 2, !Ref NumberOfSubnets ] NumberOfSubnets3: !Equals [ 3, !Ref NumberOfSubnets ] - NumberOfSubnets4: - !Equals [ 4, !Ref NumberOfSubnets ] - NumberOfSubnets5: - !Equals [ 5, !Ref NumberOfSubnets ] - NumberOfSubnets6: - !Equals [ 6, !Ref NumberOfSubnets ] Subnet0: !Or - !Condition NumberOfSubnets1 - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 Subnet1: !Or - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet2: !Or - - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet3: !Or - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet4: !Or - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet5: !Condition NumberOfSubnets6 + Subnet2: !Condition NumberOfSubnets3 UseAWS-ManagedCMK: !Equals ['', !Ref Cmk] - - + Resources: - ElasticFileSystem: Type: AWS::EFS::FileSystem Properties: - Encrypted: !Ref EncrpytedBoolean + Encrypted: !Ref EncryptedBoolean KmsKeyId: !If [ UseAWS-ManagedCMK, !Ref 'AWS::NoValue', !Ref Cmk ] FileSystemTags: - Key: Name - Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] - PerformanceMode: !Ref PerformanceMode + Value: !Join [ '-', [ !Ref ProjectName, 'FileSystem'] ] + PerformanceMode: generalPurpose + ThroughputMode: elastic ElasticFileSystemMountTarget0: Condition: Subnet0 Type: AWS::EFS::MountTarget @@ -239,334 +88,17 @@ Resources: SecurityGroups: - !Ref SecurityGroup SubnetId: !Select [ 2, !Ref Subnet ] - ElasticFileSystemMountTarget3: - Condition: Subnet3 - Type: AWS::EFS::MountTarget - Properties: - FileSystemId: !Ref ElasticFileSystem - SecurityGroups: - - !Ref SecurityGroup - SubnetId: !Select [ 3, !Ref Subnet ] - ElasticFileSystemMountTarget4: - Condition: Subnet4 - Type: AWS::EFS::MountTarget - Properties: - FileSystemId: !Ref ElasticFileSystem - SecurityGroups: - - !Ref SecurityGroup - SubnetId: !Select [ 4, !Ref Subnet ] - ElasticFileSystemMountTarget5: - Condition: Subnet5 - Type: AWS::EFS::MountTarget - Properties: - FileSystemId: !Ref ElasticFileSystem - SecurityGroups: - - !Ref SecurityGroup - SubnetId: !Select [ 5, !Ref Subnet ] - InstanceProfile: - Type: AWS::IAM::InstanceProfile - Properties: - Path: / - Roles: - - !Ref InstanceRole - InstanceRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - ec2.amazonaws.com - Action: - - sts:AssumeRole - Path: / - Policies: - - PolicyName: efs-create-file-system-with-storage - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: - - autoscaling:DescribeAutoScalingGroups - - autoscaling:DescribeAutoScalingInstances - - autoscaling:DescribePolicies - - autoscaling:UpdateAutoScalingGroup - Resource: '*' - AutoScalingGroup: - Type: AWS::AutoScaling::AutoScalingGroup - Properties: - Cooldown: 60 - HealthCheckGracePeriod: 120 - HealthCheckType: EC2 - LaunchConfigurationName: !Ref LaunchConfiguration - MaxSize: 1 - MinSize: 0 - DesiredCapacity: 1 - Tags: - - Key: Name - Value: !Join [ '', [ 'EFS ', !Ref ElasticFileSystem, ' data load... will auto terminate.' ] ] - PropagateAtLaunch: true - VPCZoneIdentifier: - !If - [ NumberOfSubnets1, - [ !Select [ 0, !Ref Subnet ] ], - !If - [ NumberOfSubnets2, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], - !If - [ NumberOfSubnets3, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ], - !If - [ NumberOfSubnets4, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ], - !If - [ NumberOfSubnets5, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ], - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ] - ] - ] - ] - ] - ] - CreationPolicy: - ResourceSignal: - Count: 0 - Timeout: PT12H - UpdatePolicy: - AutoScalingReplacingUpdate: - WillReplace: true - AutoScalingPolicy: - Type: AWS::AutoScaling::ScalingPolicy - Properties: - AdjustmentType: ChangeInCapacity - AutoScalingGroupName: !Ref AutoScalingGroup - Cooldown: 60 - PolicyType: SimpleScaling - ScalingAdjustment: 1 - LaunchConfiguration: - Type: AWS::AutoScaling::LaunchConfiguration - Metadata: - AWS::CloudFormation::Init: - configSets: - efs_add_storage: - - efs-add-storage - efs-add-storage: - packages: - yum: - #Only needed to set permissions on the moodle folder - httpd: [] - files: - /tmp/efs-add-storage.sh: - content: !Sub | - #!/bin/bash -x - - FILE_SYSTEM_ID=$1 - DATA_DIRECTORY=$2 - GROWTH=$3 - - if [ $# -lt 3 ]; then - echo "Invalid # of arguments. Require: file system id, data directory, file system growth (GiB) " - exit 0 - fi - - # get region from instance meta-data - availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) - region=${!availabilityzone:0:-1} - - # get instance id - instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) - - # get autoscaling group name - asg_name=$(aws autoscaling describe-auto-scaling-instances --instance-ids $instance_id --region $region --output text --query 'AutoScalingInstances[0].AutoScalingGroupName') - - # set the number of threads to the number of vcpus - threads=$(( $(nproc --all) * 8 )) - - # wait for file system DNS name to be propagated - results=1 - while [[ $results != 0 ]]; do - nslookup $FILE_SYSTEM_ID.efs.$region.amazonaws.com - results=$? - if [[ results = 1 ]]; then - sleep 30 - fi - done - - # mount file system - sudo mkdir -p /$FILE_SYSTEM_ID - sudo chown ec2-user:ec2-user /$FILE_SYSTEM_ID - sudo mountpoint -q /$FILE_SYSTEM_ID || sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $FILE_SYSTEM_ID.efs.$region.amazonaws.com:/ /$FILE_SYSTEM_ID - - #Create directories for Moodle - sudo mkdir -p /$FILE_SYSTEM_ID/data - sudo mkdir -p /$FILE_SYSTEM_ID/cache - sudo mkdir -p /$FILE_SYSTEM_ID/temp - chown apache:apache /$FILE_SYSTEM_ID/data/ - chown apache:apache /$FILE_SYSTEM_ID/cache/ - chown apache:apache /$FILE_SYSTEM_ID/temp/ - - # create data directory if not exists - sudo mkdir -p /$FILE_SYSTEM_ID/$DATA_DIRECTORY - sudo chown ec2-user:ec2-user /$FILE_SYSTEM_ID/$DATA_DIRECTORY - - # dd 1GiB files to file system to match DATA_SIZE - files=$GROWTH - if [ $(( $files / $threads )) == 0 ]; - then - runs=0 - parallel_threads=$(( $files % $threads )) - else - runs=$(( $files / $threads )) - parallel_threads=$threads - fi - while [ $runs -ge 0 ]; do - if [ $runs == 0 ]; - then - parallel_threads=$(( $files % $threads )) - seq 0 $(( $parallel_threads - 1 )) | parallel --will-cite -j $parallel_threads --compress dd if=/dev/zero of=/$FILE_SYSTEM_ID/$DATA_DIRECTORY/1G-dd-$(date +%Y%m%d%H%M%S.%3N)-{} bs=1M count=1024 oflag=sync - runs=$(($runs-1)) - else - seq 0 $(( $parallel_threads - 1 )) | parallel --will-cite -j $parallel_threads --compress dd if=/dev/zero of=/$FILE_SYSTEM_ID/$DATA_DIRECTORY/1G-dd-$(date +%Y%m%d%H%M%S.%3N)-{} bs=1M count=1024 oflag=sync - runs=$(($runs-1)) - fi - done - - # set ASG to zero which terminates instance - aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --desired-capacity 0 --region $region - - mode: 000777 - owner: root - group: root - Properties: - BlockDeviceMappings: - - DeviceName: /dev/xvda - Ebs: - DeleteOnTermination: true - VolumeSize: 10 - VolumeType: gp2 - IamInstanceProfile: !Ref InstanceProfile - ImageId: !Ref LatestAmiId - InstanceMonitoring: true - InstanceType: !Ref InstanceType - KeyName: !Ref EC2KeyName - SecurityGroups: - - !Ref SecurityGroup - UserData: - "Fn::Base64": - !Join [ - "",[ - "#cloud-config\n", - "repo_update: true\n", - "repo_upgrade: all\n", - "\n", - "runcmd:\n", - "- yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm\n", - "- yum-config-manager --enable epel\n", - "- yum install -y parallel", - "- ntpstat\n", - "- /opt/aws/bin/cfn-init --configsets efs_add_storage --verbose --stack ", !Ref 'AWS::StackName', " --resource LaunchConfiguration --region ", !Ref 'AWS::Region',"\n", - "- /tmp/efs-add-storage.sh ", !Ref ElasticFileSystem, " throughput_data ", !Ref Growth,"\n", - "- /opt/aws/bin/cfn-signal -e $? --stack ", !Ref 'AWS::StackName', " --resource AutoScalingGroup --region ", !Ref 'AWS::Region',"\n" - ] - ] - EfsSizeMonitorFunction: - Type: AWS::Lambda::Function - Properties: - Code: - ZipFile: !Sub | - import boto3 - import os - import sys - - def handler(event, context): - if not os.environ.get('filesystemid'): - print "Unable to get the environment variable filesystemid" - sys.exit(1) - else: - filesystemid = os.environ.get('filesystemid') - - if not os.environ.get('region'): - print "Unable to get the environment variable region" - sys.exit(1) - else: - region = os.environ.get('region') - - def efs_get_size(): - client = boto3.client('efs') - response = client.describe_file_systems(FileSystemId=filesystemid) - k = response['FileSystems'][0]['SizeInBytes']['Value'] - return k - - def cloudwatch_put_metric(): - client = boto3.client('cloudwatch') - client.put_metric_data( - MetricData=[ - { - 'MetricName': 'SizeInBytes', - 'Dimensions': [ - { - 'Name': 'FileSystemId', - 'Value': filesystemid - }, - ], - 'Unit': 'None', - 'Value': efs_get_size() - }, - ], - Namespace='Custom/EFS' - ) - print('CloudWatch metric SizeInBytes sucessfully updated.') - - cloudwatch_put_metric() - Description: Lambda function to update the SizeInBytes EFS CloudWatch metric - Environment: - Variables: - filesystemid: !Ref ElasticFileSystem - region: !Ref 'AWS::Region' - FunctionName: !Join [ '', [ 'efs-', !Ref ElasticFileSystem, '-size-monitor' ] ] - Handler: index.handler - Role: !GetAtt LambdaRole.Arn - Runtime: python3.9 - Timeout: 60 - LambdaRole: - Type: AWS::IAM::Role + ElasticFileSystemParam: + Type: AWS::SSM::Parameter Properties: - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Action: - - sts:AssumeRole - Path: / - ManagedPolicyArns: - - arn:aws:iam::aws:policy/CloudWatchFullAccess - - arn:aws:iam::aws:policy/AmazonElasticFileSystemReadOnlyAccess - EfsLambdaPermission: - Type: AWS::Lambda::Permission - Properties: - FunctionName: !Ref EfsSizeMonitorFunction - Action: lambda:InvokeFunction - Principal: events.amazonaws.com - SourceArn: !GetAtt EfsSizeMonitorEvent.Arn - EfsSizeMonitorEvent: - Type: AWS::Events::Rule - Properties: - Description: Scheduled event to update SizeInBytes EFS CloudWatch metric - Name: !Join [ '', [ 'efs-', !Ref ElasticFileSystem, '-size-monitor-scheduled-event' ] ] - ScheduleExpression: rate(1 minute) - State: ENABLED - Targets: - - Arn: !GetAtt EfsSizeMonitorFunction.Arn - Id: 1 + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/SharedFile/ElasticFileSystem' ] ] + Type: String + Value: !Ref ElasticFileSystem + Description: SSM Parameter for Moodle EFS File System Id Outputs: ElasticFileSystem: Value: !Ref ElasticFileSystem ElasticFileSystemDnsName: Description: DNS name for the Amazon EFS file system. - Value: !Join [ '.', [ !Ref ElasticFileSystem, 'efs', !Ref 'AWS::Region', 'amazonaws', 'com' ] ] + Value: !Join [ '.', [ !Ref ElasticFileSystem, 'efs', !Ref 'AWS::Region', 'amazonaws', 'com' ] ] \ No newline at end of file diff --git a/templates/03-elasticache.yaml b/templates/03-elasticache.yaml index 284f193..4302d77 100644 --- a/templates/03-elasticache.yaml +++ b/templates/03-elasticache.yaml @@ -4,18 +4,23 @@ AWSTemplateFormatVersion: 2010-09-09 Description: Reference Architecture to host Moodle on AWS - Creates ElastiCache cache cluster Metadata: - AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Cache Parameters Parameters: + - CacheUsageType + - CacheEngineType - ElastiCacheNodeType - ElastiCacheClusterName - ElastiCacheSecurityGroup - NumberOfSubnets - Subnet ParameterLabels: + CacheEngineType: + default: Cache Engine Type + CacheUsageType: + default: Cache Usage Type ElastiCacheClusterName: default: Cache Cluster Name ElastiCacheNodeType: @@ -28,101 +33,107 @@ Metadata: default: Subnets Parameters: - + CacheEngineType: + AllowedValues: + - Redis + - Memcached + Default: Memcached + Description: Indicates whether to use ElastiCache Memcached or Redis. + Type: String + CacheUsageType: + AllowedValues: + - application + - session + Default: session + Description: Indicates whether to use ElastiCache for session or application caching. + Type: String ElastiCacheClusterName: AllowedPattern: ^([a-zA-Z0-9]*)$ - Description: The ElastiCache cluster name. Defaults to the Amazon RDS database name. + Description: ElastiCache cluster name. Type: String ElastiCacheNodeType: AllowedValues: - - cache.t2.micro - - cache.t2.small - - cache.t2.medium - - cache.m3.medium - - cache.m3.large - - cache.m3.xlarge - - cache.m3.2xlarge - - cache.m4.large - - cache.m4.xlarge - - cache.m4.2xlarge - - cache.m4.4xlarge - - cache.m4.10xlarge - - cache.r3.large - - cache.r3.xlarge - - cache.r3.2xlarge - - cache.r3.4xlarge - - cache.r3.8xlarge - - cache.r4.large - - cache.r4.xlarge - - cache.r4.2xlarge - - cache.r4.4xlarge - - cache.r4.8xlarge - - cache.r4.16xlarge + - 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.m6g.large + - cache.m6g.xlarge + - cache.m6g.2xlarge + - cache.m6g.4xlarge + - cache.m6g.8xlarge + - cache.m6g.12xlarge + - cache.m6g.16xlarge ConstraintDescription: Must be a valid Amazon ElastiCache node type. - Default: cache.t2.medium - Description: The Amazon ElastiCache cluster node type. + Default: cache.m6g.large + Description: ElastiCache cluster node type. Type: String ElastiCacheSecurityGroup: Description: Select the ElastiCache security group. Type: AWS::EC2::SecurityGroup::Id NumberOfSubnets: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 - Default: 3 + Default: 2 Description: Number of subnets. This must match your selections in the list of subnets below. Type: String Subnet: Description: Select existing subnets. The number selected must match the number of subnets above. Subnets selected must be in separate AZs. Type: List + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: Moodle Project Name + Type: String Conditions: - + UseRedis: + !Equals [!Ref CacheEngineType, Redis] + UseMemcached: + !Equals [!Ref CacheEngineType, Memcached] NumberOfSubnets1: !Equals [ 1, !Ref NumberOfSubnets ] NumberOfSubnets2: !Equals [ 2, !Ref NumberOfSubnets ] NumberOfSubnets3: !Equals [ 3, !Ref NumberOfSubnets ] - NumberOfSubnets4: - !Equals [ 4, !Ref NumberOfSubnets ] - NumberOfSubnets5: - !Equals [ 5, !Ref NumberOfSubnets ] - NumberOfSubnets6: - !Equals [ 6, !Ref NumberOfSubnets ] Subnet0: !Or - !Condition NumberOfSubnets1 - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 Subnet1: !Or - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet2: !Or - - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet3: !Or - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet4: !Or - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet5: !Condition NumberOfSubnets6 + Subnet2: !Condition NumberOfSubnets3 Resources: + ElastiCacheClusterRedis: + Condition: UseRedis + Type: AWS::ElastiCache::ReplicationGroup + Properties: + ReplicationGroupId: !Ref ElastiCacheClusterName + ReplicationGroupDescription: !Ref ElastiCacheClusterName + AtRestEncryptionEnabled: true + TransitEncryptionEnabled: false + AutoMinorVersionUpgrade: true + MultiAZEnabled: true + CacheNodeType: !Ref ElastiCacheNodeType + CacheSubnetGroupName: !Ref ElastiCacheSubnetGroup + SecurityGroupIds: + - !Ref ElastiCacheSecurityGroup + Engine: redis + NumCacheClusters: + !Ref NumberOfSubnets - ElastiCacheCluster: + ElastiCacheClusterMemcached: + Condition: UseMemcached Type: AWS::ElastiCache::CacheCluster Properties: AZMode: cross-az @@ -136,7 +147,8 @@ Resources: - Key: Name Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] VpcSecurityGroupIds: - - !Ref ElastiCacheSecurityGroup + - !Ref ElastiCacheSecurityGroup + ElastiCacheSubnetGroup: Type: AWS::ElastiCache::SubnetGroup Properties: @@ -149,21 +161,33 @@ Resources: !If [ NumberOfSubnets2, [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], - !If - [ NumberOfSubnets3, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ], - !If - [ NumberOfSubnets4, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ], - !If - [ NumberOfSubnets5, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ], - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ] - ] - ] - ] + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] ] ] + + ElastiCacheClusterEndpoint: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/Cache/', !Ref CacheUsageType, '/ElastiCacheClusterEndpoint' ] ] + Type: String + Value: !If [UseRedis, + !Join [ ':', [!GetAtt ElastiCacheClusterRedis.PrimaryEndPoint.Address, !GetAtt ElastiCacheClusterRedis.PrimaryEndPoint.Port]], + !Join [ ':', [!GetAtt ElastiCacheClusterMemcached.ConfigurationEndpoint.Address, !GetAtt ElastiCacheClusterMemcached.ConfigurationEndpoint.Port]] + ] + Description: SSM Parameter for ElastiCache Cluster Endpoint + + ElastiCacheEngine: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/Cache/', !Ref CacheUsageType, '/Engine' ] ] + Type: String + Value: !Ref CacheEngineType + Description: SSM Parameter for ElastiCache Engine Type + Outputs: ElastiCacheClusterEndpointAddress: - Value: !GetAtt ElastiCacheCluster.ConfigurationEndpoint.Address + Value: !If [UseRedis, + !Join [ ':', [!GetAtt ElastiCacheClusterRedis.PrimaryEndPoint.Address, !GetAtt ElastiCacheClusterRedis.PrimaryEndPoint.Port]], + !Join [ ':', [!GetAtt ElastiCacheClusterMemcached.ConfigurationEndpoint.Address, !GetAtt ElastiCacheClusterMemcached.ConfigurationEndpoint.Port]] + ] + \ No newline at end of file diff --git a/templates/03-pipelinehelper.yaml b/templates/03-pipelinehelper.yaml new file mode 100644 index 0000000..1aeb046 --- /dev/null +++ b/templates/03-pipelinehelper.yaml @@ -0,0 +1,806 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: This templates helps creating CodeCommit repo, S3 Bucket and update repo with Moodle code & configurations. + +Parameters: + + EC2KeyName: + Description: Name of an existing EC2 key pair + Type: AWS::EC2::KeyPair::KeyName + + PipelineSecurityGroup: + Description: Select the Pipeline security group Id + Type: AWS::EC2::SecurityGroup::Id + + NumberOfSubnets: + AllowedValues: + - 1 + - 2 + - 3 + Default: 2 + Description: Number of subnets. This must match your selections in the list of Subnets below. + Type: String + + PipelineSubnet: + Description: Select existing subnets. + Type: List + + InstanceType: + AllowedValues: + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.9xlarge + - c5a.12xlarge + - c5a.18xlarge + - c5a.24xlarge + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6g.24xlarge + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.9xlarge + - c6g.12xlarge + - c6g.18xlarge + - c6g.24xlarge + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.24xlarge + ConstraintDescription: Must be a valid Amazon EC2 instance type. + Default: m6g.large + Description: The Amazon EC2 instance type that dynamically adjusts thresholds based on permitted throughput changes. + Type: String + + LatestAmiId: + Type : AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 + LatestArmAmiId : + Type : AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 + + MoodleLocale: + Description: "The main language of the Moodle site, during initial configuration." + Type: String + Default: en + + DomainName: + Description: '[ Optional ] The main domain name of the Moodle site (e.g. moodle.example.edu).' + Type: String + + RDSInstanceSecretArn: + Description: 'Credentials for Moodle RDS instance' + Type: String + Default: "" + + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: The Moodle Project Name + Type: String + + WebAsgMax: + AllowedPattern: ^((?!0$)[1-2]?[0-9]|30)$ + ConstraintDescription: Must be a number between 1 and 30. + Default: 1 + Description: Specifies the maximum number of EC2 instances in the Web Autoscaling Group. + Type: String + WebAsgMin: + AllowedPattern: ^([0-0]?[0-9]|10)$ + ConstraintDescription: Must be a number between 0 and 10. + Default: 2 + Description: Specifies the minimum number of EC2 instances in the Web Autoscaling Group. + Type: String + +Conditions: + NumberOfSubnets1: + !Equals [ 1, !Ref NumberOfSubnets ] + NumberOfSubnets2: + !Equals [ 2, !Ref NumberOfSubnets ] + NumberOfSubnets3: + !Equals [ 3, !Ref NumberOfSubnets ] + Subnet0: !Or + - !Condition NumberOfSubnets1 + - !Condition NumberOfSubnets2 + - !Condition NumberOfSubnets3 + Subnet1: !Or + - !Condition NumberOfSubnets2 + - !Condition NumberOfSubnets3 + Subnet2: !Condition NumberOfSubnets3 + UsingGraviton2Ami: !Or + - !Equals ["t4",!Select [0, !Split [ "g.", !Ref InstanceType]]] + - !Equals ["c6",!Select [0, !Split [ "g.", !Ref InstanceType]]] + - !Equals ["m6",!Select [0, !Split [ "g.", !Ref InstanceType]]] + - !Equals ["r6",!Select [0, !Split [ "g.", !Ref InstanceType]]] + +Resources: + MoodleRepo: + Type: AWS::CodeCommit::Repository + Properties: + RepositoryName: !Sub '${ProjectName}-Repo' + RepositoryDescription: This is Moodle CodeCommit repository. + + #This bucket is being used for storing Code artifacts for deployment. + CodeArtifactS3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Join ['-', [!Sub 'moodle-code', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]]] + + InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: / + Roles: + - !Ref InstanceRole + + InstanceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' + Path: / + Policies: + - PolicyName: MoodlePipelineHelperPolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - autoscaling:DescribeAutoScalingGroups + - autoscaling:DescribeAutoScalingInstances + - autoscaling:DescribePolicies + - autoscaling:UpdateAutoScalingGroup + Resource: '*' + - Effect: Allow + Action: + - codepipeline:StartPipelineExecution + Resource: + !Sub 'arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${ProjectName}-Pipeline' + - Effect: Allow + Action: + - codecommit:GitPush + Resource: !GetAtt MoodleRepo.Arn + + PipelineHelperASGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + Cooldown: 60 + HealthCheckGracePeriod: 120 + HealthCheckType: EC2 + LaunchTemplate: + LaunchTemplateId: !Ref PipelineHelperLaunchTemplate + Version: !GetAtt PipelineHelperLaunchTemplate.LatestVersionNumber + MaxSize: 1 + MinSize: 0 + DesiredCapacity: 1 + Tags: + - Key: Name + Value: !Join [ '', [ 'Moodle Pipeline Helper ', !GetAtt MoodleRepo.Name, ' ...will auto terminate' ] ] + PropagateAtLaunch: true + VPCZoneIdentifier: + !If + [ NumberOfSubnets1, + [ !Select [ 0, !Ref PipelineSubnet ] ], + !If + [ NumberOfSubnets2, + [ !Select [ 0, !Ref PipelineSubnet ], !Select [ 1, !Ref PipelineSubnet ] ], + [ !Select [ 0, !Ref PipelineSubnet ], !Select [ 1, !Ref PipelineSubnet ], !Select [ 2, !Ref PipelineSubnet ] ] + ] + ] + CreationPolicy: + ResourceSignal: + Count: 0 + Timeout: PT15M + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: true + + PipelineHelperLaunchTemplate: + Type: AWS::EC2::LaunchTemplate + Metadata: + AWS::CloudFormation::Init: + configSets: + moodle_git_config: + - moodle-git-config + moodle-git-config: + packages: + yum: + git: [] + files: + /tmp/appspec.yml: + content: !Sub | + version: 0.0 + os: linux + files: + - source: / + destination: /var/www/moodle/html/ + hooks: + ApplicationStop: + - location: .pipeline/stop_application.sh + timeout: 300 + BeforeInstall: + - location: .pipeline/before_install.sh + timeout: 600 + AfterInstall: + - location: .pipeline/after_install.sh + timeout: 300 + ApplicationStart: + - location: .pipeline/start_application.sh + timeout: 300 + ValidateService: + - location: .pipeline/basic_health_check.sh + timeout: 300 + mode: '000755' + owner: root + group: root + /tmp/start_application.sh: + content: !Sub | + #!/bin/bash + sudo systemctl start php-fpm + sudo systemctl start httpd + + mode: '000755' + owner: root + group: root + /tmp/stop_application.sh: + content: !Sub | + #!/bin/bash + sudo systemctl stop httpd + sudo systemctl stop php-fpm + mode: '000755' + owner: root + group: root + /tmp/basic_health_check.sh: + content: !Sub | + #!/bin/bash + for i in `seq 1 10`; + do + HTTP_CODE=`curl --write-out '%{http_code}' -o /dev/null -m 10 -q -s http://localhost:80/status.txt` + if [ "$HTTP_CODE" == "200" ]; then + echo "Successfully pulled root page." + exit 0; + fi + echo "Attempt to curl endpoint returned HTTP Code $HTTP_CODE. Backing off and retrying." + sleep 10 + done + echo "Server did not come up after expected time. Failing." + exit 1 + mode: '000755' + owner: root + group: root + /tmp/config.php: + content: !Sub | + '2017-10-17', + 'region' => '${AWS::Region}', + ]); + + $secretName = '${RDSInstanceSecretArn}'; + + try { + $result = $client->getSecretValue([ + 'SecretId' => $secretName, + ]); + + } catch (AwsException $e) { + $error = $e->getAwsErrorCode(); + } + // Decrypts secret using the associated KMS CMK. + // Depending on whether the secret is a string or binary, one of these fields will be populated. + if (isset($result['SecretString'])) { + $secret = $result['SecretString']; + } + $CFG = new stdClass; + $CFG->getremoteaddrconf = 0; + $CFG->dbtype = 'pgsql'; + $CFG->dblibrary = 'native'; + $CFG->dbhost = getenv('EnvDatabaseClusterEndpointAddress'); + $CFG->dbname = getenv('EnvDatabaseName'); + $CFG->dbuser = json_decode($secret)->{'username'}; + $CFG->dbpass = json_decode($secret)->{'password'}; + $CFG->prefix = 'mdl_'; + $CFG->lang = '${MoodleLocale}'; + $CFG->dboptions = array( + 'dbpersist' => false, + 'dbsocket' => false, + 'dbport' => '', + 'dbhandlesoptions' => false, + 'dbcollation' => 'utf8mb4_unicode_ci', + 'connecttimeout' => 300, + 'readonly' => [ + 'instance' => 'db-cluster-readonly-endpoint', + 'connecttimeout' => 300, + 'latency' => 2, + 'exclude_tables' => [ + 'config', + ], + ] + ); + + // Hostname definition // + $hostname = '${DomainName}'; + $hostwithprotocol = strtolower($hostname); + + if(substr($hostwithprotocol, 0, 4) === 'http'){} else { + $hostwithprotocol = 'http://'.strtolower($hostwithprotocol); + } + + $CFG->wwwroot = strtolower($hostwithprotocol); + $CFG->sslproxy = (substr($hostwithprotocol,0,5)=='https' ? true : false); + // Moodledata location // + $CFG->dataroot = '/var/www/moodle/data'; + $CFG->tempdir = '/var/www/moodle/temp'; + $CFG->cachedir = '/var/www/moodle/cache'; + $CFG->localcachedir = '/var/www/moodle/local'; + $CFG->directorypermissions = 02777; + $CFG->admin = 'admin'; + // Configure Session Cache + $SessionsCacheType = 'Memcached'; + $SessionEndpoint = ''; + if ($SessionEndpoint != '') { + + $CFG->dbsessions = false; + + if($SessionsCacheType == 'Redis') { + + $CFG->session_handler_class = '\core\session\redis'; + $CFG->session_redis_host = $SessionEndpoint; + $CFG->session_redis_port = 6379; // Optional. + $CFG->session_redis_database = 0; // Optional, default is db 0. + //$CFG->session_redis_auth = ''; // Optional, default is don't set one. + //$CFG->session_redis_prefix = ''; // Optional, default is don't set one. + $CFG->session_redis_acquire_lock_timeout = 120; // Default is 2 minutes. + $CFG->session_redis_acquire_lock_warn = 0; // If set logs early warning if a lock has not been acquried. + $CFG->session_redis_lock_expire = 7200; // Optional, defaults to session timeout. + $CFG->session_redis_lock_retry = 100; // Optional wait between lock attempts in ms, default is 100. + + $CFG->session_redis_serializer_use_igbinary = false; // Optional, default is PHP builtin serializer. + $CFG->session_redis_compressor = 'none'; + } else { + + $CFG->session_handler_class = '\core\session\memcached'; + $CFG->session_memcached_save_path = $SessionEndpoint; + $CFG->session_memcached_prefix = 'memc.sess.key.'; + $CFG->session_memcached_acquire_lock_timeout = 120; + $CFG->session_memcached_lock_expire = 7100; + $CFG->session_memcached_lock_retry_sleep = 150; + } + } + //@error_reporting(E_ALL | E_STRICT); // NOT FOR PRODUCTION SERVERS! + //@ini_set('display_errors', '1'); // NOT FOR PRODUCTION SERVERS! + //$CFG->debug = (E_ALL | E_STRICT); // === DEBUG_DEVELOPER - NOT FOR PRODUCTION SERVERS! + //$CFG->debugdisplay = 1; + require_once(__DIR__ . '/lib/setup.php'); + // END OF CONFIG // + ?> + mode: '000755' + owner: root + group: root + + /tmp/after_install.sh: + content: + !Sub | + #!/bin/bash -xe + + # Setting up access ownership to apache:apache + chown -R apache:apache /var/www/moodle/html + chown -R apache:apache /var/www/moodle/data + chown -R apache:apache /var/www/moodle/cache + chown -R apache:apache /var/www/moodle/temp + chown -R apache:apache /var/www/moodle/local + + availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) + region=${!availabilityzone:0:-1} + + export EnvDatabaseType=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/DB/Type --query Parameters[0].Value) + export EnvDatabaseType=`echo $EnvDatabaseType | sed -e 's/^"//' -e 's/"$//'` + + if [ "$EnvDatabaseType" == "MySQL" ]; then + sed -i "s/\$CFG->dbtype = .*/\$CFG->dbtype = 'auroramysql';/" /var/www/moodle/html/config.php + else + sed -i "s/\$CFG->dbtype = .*/\$CFG->dbtype = 'pgsql';/" /var/www/moodle/html/config.php + fi + + export EnvDatabaseName=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/DB/Name --query Parameters[0].Value) + export EnvDatabaseName=`echo $EnvDatabaseName | sed -e 's/^"//' -e 's/"$//'` + sed -i "s/\$CFG->dbname.*/\$CFG->dbname = '"$EnvDatabaseName"';/" /var/www/moodle/html/config.php + + export EnvDatabaseClusterEndpointAddress=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/DB/ClusterEndpoint --query Parameters[0].Value) + export EnvDatabaseClusterEndpointAddress=`echo $EnvDatabaseClusterEndpointAddress | sed -e 's/^"//' -e 's/"$//'` + sed -i "s/\$CFG->dbhost.*/\$CFG->dbhost = '"$EnvDatabaseClusterEndpointAddress"';/" /var/www/moodle/html/config.php + + export EnvDatabaseClusterReadOnlyEndpointAddress=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/DB/ClusterReadOnlyEndpoint --query Parameters[0].Value) + export EnvDatabaseClusterReadOnlyEndpointAddress=`echo $EnvDatabaseClusterReadOnlyEndpointAddress | sed -e 's/^"//' -e 's/"$//'` + sed -i "s/'instance' => '.*/'instance' => '"$EnvDatabaseClusterReadOnlyEndpointAddress"',/" /var/www/moodle/html/config.php + + export EnvDnsName=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/Network/DomainName --query Parameters[0].Value) + export EnvDnsName=`echo $EnvDnsName | sed -e 's/^"//' -e 's/"$//'` + sed -i'' -e "s,\$hostname = .*,\$hostname = '"$EnvDnsName"';," /var/www/moodle/html/config.php + + export EnvIsMoodleSetupCompleted=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/IsMoodleSetupCompleted --query Parameters[0].Value) + export EnvIsMoodleSetupCompleted=`echo $EnvIsMoodleSetupCompleted | sed -e 's/^"//' -e 's/"$//'` + + export EnvElastiCacheClusterEndpointAddress=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/Cache/session/ElastiCacheClusterEndpoint --query Parameters[0].Value) + export EnvElastiCacheClusterEndpointAddress=`echo $EnvElastiCacheClusterEndpointAddress | sed -e 's/^"//' -e 's/"$//'` + + export EnvElastiCacheEngine=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/Cache/session/Engine --query Parameters[0].Value) + export EnvElastiCacheEngine=`echo $EnvElastiCacheEngine | sed -e 's/^"//' -e 's/"$//'` + + #setting up elasticache dependencies for cache + if [ "$EnvIsMoodleSetupCompleted" != "No" ] && [ "$EnvElastiCacheClusterEndpointAddress" != "null" -a "$EnvElastiCacheClusterEndpointAddress" != "" ]; then + sed -i "s/\$SessionEndpoint = .*/\$SessionEndpoint = '"$EnvElastiCacheClusterEndpointAddress"';/" /var/www/moodle/html/config.php + + if [ "$EnvElastiCacheEngine" == "Redis"]; then + sed -i "s/\$SessionsCacheType = .*/\$SessionsCacheType = '"$EnvElastiCacheEngine"';/" /var/www/moodle/html/config.php + else + #update Moodle source to use DYNAMIC_CLIENT_MODE so Moodle can detect changes to the elasticache cluster membership + sed -i '/\$this->options\[Memcached::OPT_BUFFER_WRITES\] = \$bufferwrites;/a \ \ \ \ \ \ \ \ $this->options[Memcached::OPT_CLIENT_MODE] = Memcached::DYNAMIC_CLIENT_MODE;' /var/www/moodle/html/cache/stores/memcached/lib.php + fi + + + else + sed -i "s/\$SessionEndpoint = .*/\$SessionEndpoint = '';/" /var/www/moodle/html/config.php + fi + + sudo systemctl restart php-fpm + mode: 000500 + owner: root + group: root + + /tmp/before_install.sh: + content: + !Sub | + #!/bin/bash -xe + + cd /opt/codedeploy-agent/deployment-root/$DEPLOYMENT_GROUP_ID/$DEPLOYMENT_ID/deployment-archive/.pipeline/ + + availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) + region=${!availabilityzone:0:-1} + + export EnvDatabaseType=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/DB/Type --query Parameters[0].Value) + export EnvDatabaseType=`echo $EnvDatabaseType | sed -e 's/^"//' -e 's/"$//'` + + if [ "$EnvDatabaseType" == "MySQL" ]; then + #Installing and configuring MYSQL libs. + sh install_mysql_dependencies.sh + else + #Installing and configuring PGSQL libs. + sh install_pgsql_dependencies.sh + fi + + #increasing PHP max_input_vars to 5000 + sed -i 's/; max_input_vars.*/max_input_vars = 5000/' /etc/php.ini + + #Configuring OPCache + sh configure_opcache.sh + + #configuring Cache clients + sh install_cacheclient.sh + + # Setting up EFS shared file storage + sh setup_efs.sh + mode: 000500 + owner: root + group: root + /tmp/setup_efs.sh: + content: + !Sub | + #!/bin/bash -xe + + availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) + region=${!availabilityzone:0:-1} + + #Mount shared storage + if grep -qs '/var/www/moodle/data ' /proc/mounts; then + echo "/var/www/moodle/data is mounted." + else + export EnvElasticFileSystem=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/SharedFile/ElasticFileSystem --query Parameters[0].Value) + export EnvElasticFileSystem=`echo $EnvElasticFileSystem | sed -e 's/^"//' -e 's/"$//'` + + sudo mkdir -p /$EnvElasticFileSystem + sudo mountpoint -q /$EnvElasticFileSystem || sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EnvElasticFileSystem.efs.${AWS::Region}.amazonaws.com:/ /$EnvElasticFileSystem + + #Create directories for Moodle + sudo mkdir -p /$EnvElasticFileSystem/data + sudo mkdir -p /$EnvElasticFileSystem/cache + sudo mkdir -p /$EnvElasticFileSystem/temp + + chown apache:apache /$EnvElasticFileSystem/data/ + chown apache:apache /$EnvElasticFileSystem/cache/ + chown apache:apache /$EnvElasticFileSystem/temp/ + + sudo umount -f /$EnvElasticFileSystem + + mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EnvElasticFileSystem.efs.${AWS::Region}.amazonaws.com:/data /var/www/moodle/data + #mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EnvElasticFileSystem.efs.${AWS::Region}.amazonaws.com:/cache /var/www/moodle/cache + #mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $EnvElasticFileSystem.efs.${AWS::Region}.amazonaws.com:/temp /var/www/moodle/temp + fi + mode: 000500 + owner: root + group: root + /tmp/install_pgsql_dependencies.sh: + content: + !Sub | + #!/bin/bash -xe + amazon-linux-extras install -y postgresql13 + yum install -y php-pgsql + mode: 000500 + owner: root + group: root + /tmp/install_mysql_dependencies.sh: + content: + !Sub | + #!/bin/bash -xe + + amazon-linux-extras install -y mariadb10.5 + yum install -y php-mysqlnd + mode: 000500 + owner: root + group: root + /tmp/configure_opcache.sh: + content: + !Sub | + #!/bin/bash -xe + # create hidden opcache directory locally & change owner to apache + if [ ! -d /var/www/.opcache ]; then + mkdir -p /var/www/.opcache + fi + #Ensure opcache is enabled and add settings recomended by moodle at https://docs.moodle.org/34/en/OPcache + sed -i 's/;opcache.file_cache=.*/opcache.file_cache=\/var\/www\/.opcache/' /etc/php.d/10-opcache.ini + sed -i 's/opcache.memory_consumption=.*/opcache.memory_consumption=512/' /etc/php.d/10-opcache.ini + sed -i 's/opcache.max_accelerated_files=.*/opcache.max_accelerated_files=8000/' /etc/php.d/10-opcache.ini + sed -i 's/;opcache.revalidate_freq=.*/opcache.revalidate_freq=300/' /etc/php.d/10-opcache.ini + sed -i 's/;opcache.use_cwd=.*/opcache.use_cwd=1/' /etc/php.d/10-opcache.ini + sed -i 's/;opcache.validate_timestamps=.*/opcache.validate_timestamps=1/' /etc/php.d/10-opcache.ini + sed -i 's/;opcache.save_comments=.*/opcache.save_comments=1/' /etc/php.d/10-opcache.ini + sed -i 's/;opcache.enable_file_override=.*/opcache.enable_file_override=60/' /etc/php.d/10-opcache.ini + mode: 000500 + owner: root + group: root + /tmp/install_cacheclient.sh: + content: + !Sub | + #!/bin/bash -xe + + #Install memcached and then remove it. Memcached is not actually needed. We install amazon-elasticache-cluster-client.so instead. However Moodle does not detect memcached is installed. Therefore, this tricks Moodle into thinking it is installed. + sudo yum install -y php-pecl-memcached + sudo yum remove -y php-pecl-memcached + sudo yum install -y php-redis + + + if [ $(uname -a | grep -c x86_64) == "1" ]; then + echo "downloading x86 client for ElastiCache" + wget -P /tmp/ https://elasticache-downloads.s3.amazonaws.com/ClusterClient/PHP-8.0/latest-64bit-X86-openssl3 + tar -xf '/tmp/latest-64bit-X86' + else + echo "downloading ARM-64 client for ElastiCache" + wget -P /tmp/ https://elasticache-downloads.s3.amazonaws.com/ClusterClient/PHP-8.0/latest-64bit-arm-openssl3 + tar -xf '/tmp/latest-64bit-arm' + fi + + cp 'amazon-elasticache-cluster-client.so' /usr/lib64/php/modules/ + if [ ! -f /etc/php.d/50-memcached.ini ]; then + touch /etc/php.d/50-memcached.ini + fi + echo 'extension=/usr/lib64/php/modules/amazon-elasticache-cluster-client.so;' >> /etc/php.d/50-memcached.ini + echo 'extension=igbinary.so;' >> /etc/php.d/50-memcached.ini + + if [ ! -f /etc/php.d/20-redis.ini ]; then + touch /etc/php.d/20-redis.ini + fi + echo 'extension=/usr/lib64/php/modules/amazon-elasticache-cluster-client.so;' >> /etc/php.d/20-redis.ini + echo 'extension=igbinary.so;' >> /etc/php.d/20-redis.ini + + + mode: 000500 + owner: root + group: root + + /tmp/moodle-git-config.sh: + content: !Sub | + #!/bin/bash -x + + git config --system user.name 'AWS User' + git config --system user.email noreply@amazon.com + + git config --system credential.helper '!aws codecommit credential-helper $@' + git config --system credential.UseHttpPath true + + DIR="/tmp/moodle" + if [ -d "$DIR" ]; then + cd /tmp/moodle + else + mkdir /tmp/moodle + mkdir /tmp/moodle/.pipeline + + # Get Latest Moodle stable version + wget -O /tmp/moodle.tgz https://download.moodle.org/download.php/direct/stable401/moodle-latest-401.tgz + tar -xvzf /tmp/moodle.tgz --strip-components=1 -C /tmp/moodle/ + wget -O /tmp/moodle/lib/aws.phar https://docs.aws.amazon.com/aws-sdk-php/v3/download/aws.phar + cd /tmp/moodle + git init + git checkout -b main + git add . + git commit -m "Moodle original code commit." + git remote add origin ${MoodleRepo.CloneUrlHttp} + fi + + #Added code scripts for CodePipeline + + cp -f /tmp/appspec.yml /tmp/moodle/ + + cp -f /tmp/start_application.sh /tmp/moodle/.pipeline/ + cp -f /tmp/stop_application.sh /tmp/moodle/.pipeline/ + cp -f /tmp/basic_health_check.sh /tmp/moodle/.pipeline/ + + cp -f /tmp/config.php /tmp/moodle/ + + cp -f /tmp/before_install.sh /tmp/moodle/.pipeline/ + cp -f /tmp/setup_efs.sh /tmp/moodle/.pipeline/ + cp -f /tmp/configure_opcache.sh /tmp/moodle/.pipeline/ + + cp -f /tmp/install_mysql_dependencies.sh /tmp/moodle/.pipeline/ + cp -f /tmp/install_pgsql_dependencies.sh /tmp/moodle/.pipeline/ + cp -f /tmp/install_cacheclient.sh /tmp/moodle/.pipeline/ + + cp -f /tmp/after_install.sh /tmp/moodle/.pipeline/ + + git add --all + git commit -m "Moodle Code pipeline commits" + + git push -u origin main + + # get instance id + instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) + + # get region from instance meta-data + availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) + region=${!availabilityzone:0:-1} + + # wait for Moodle setup to be completed + echo "Start checking whether Moodle setup completed or not" + counter=0 + while true + do + sleep 60 + export EnvIsMoodleSetupCompleted=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/IsMoodleSetupCompleted --query Parameters[0].Value) + export EnvIsMoodleSetupCompleted=`echo $EnvIsMoodleSetupCompleted | sed -e 's/^"//' -e 's/"$//'` + + if [ "$EnvIsMoodleSetupCompleted" == "No" ]; then + ((counter++)) + echo "Waiting for 1 more minute, running for the $counter time." + else + break + fi + + done + + # script to update Moodle pipeline release + + aws codepipeline start-pipeline-execution --name ${ProjectName}-Pipeline --region $region + + # set ASG to zero which terminates instance + + export EnvWebAppASGName=$(aws ssm get-parameters --region $region --names /Moodle/${ProjectName}/WebAppASGName --query Parameters[0].Value) + export EnvWebAppASGName=`echo $EnvWebAppASGName | sed -e 's/^"//' -e 's/"$//'` + + aws autoscaling update-auto-scaling-group --auto-scaling-group-name $EnvWebAppASGName --desired-capacity ${WebAsgMin} --min-size ${WebAsgMin} --max-size ${WebAsgMax} --region $region + + # Shutting down pipeline-helper instance + # get autoscaling group name + asg_name=$(aws autoscaling describe-auto-scaling-instances --instance-ids $instance_id --region $region --output text --query 'AutoScalingInstances[0].AutoScalingGroupName') + + # set pipeline-helper ASG to zero which terminates instance + aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --min-size 0 --desired-capacity 0 --region $region + mode: '000755' + owner: root + group: root + commands: + update-moodle-repo: + command: ./moodle-git-config.sh + cwd: /tmp + ignoreErrors: false + + Properties: + LaunchTemplateData: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + DeleteOnTermination: true + VolumeSize: 10 + VolumeType: gp3 + IamInstanceProfile: + Arn: !GetAtt InstanceProfile.Arn + ImageId: !If [UsingGraviton2Ami, !Ref LatestArmAmiId, !Ref LatestAmiId] + InstanceType: !Ref InstanceType + KeyName: !Ref EC2KeyName + SecurityGroupIds: + - !Ref PipelineSecurityGroup + UserData: + "Fn::Base64": + !Sub | + #!/bin/bash -xe + sudo systemctl enable amazon-ssm-agent + sudo systemctl start amazon-ssm-agent + sudo systemctl status amazon-ssm-agent + + /opt/aws/bin/cfn-init --configsets moodle_git_config --verbose --stack ${AWS::StackName} --resource PipelineHelperLaunchTemplate --region ${AWS::Region} + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource PipelineHelperASGroup --region ${AWS::Region} + +Outputs: + CodeArtifactS3BucketArn: + Value: !GetAtt CodeArtifactS3Bucket.Arn + CodeArtifactS3BucketName: + Value: !Ref CodeArtifactS3Bucket + MoodleRepoName: + Value: !GetAtt MoodleRepo.Name + MoodleRepoArn: + Value: !GetAtt MoodleRepo.Arn diff --git a/templates/03-publicalb.yaml b/templates/03-publicalb.yaml index f7b6ec8..eedb302 100644 --- a/templates/03-publicalb.yaml +++ b/templates/03-publicalb.yaml @@ -4,7 +4,6 @@ AWSTemplateFormatVersion: 2010-09-09 Description: Reference Architecture to host Moodle on AWS - Creates Application Load Balancer Metadata: - AWS::CloudFormation::Interface: ParameterGroups: - Label: @@ -28,15 +27,12 @@ Metadata: default: Subnets Parameters: - NumberOfSubnets: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 - Default: 3 + Default: 2 Description: Number of subnets. This must match your selections in the list of subnets below. Type: String PublicAlbAcmCertificate: @@ -52,6 +48,11 @@ Parameters: Vpc: Description: Select an existing Vpc Type: AWS::EC2::VPC::Id + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: The Moodle Project Name + Type: String Conditions: @@ -65,41 +66,16 @@ Conditions: !Equals [ 2, !Ref NumberOfSubnets ] NumberOfSubnets3: !Equals [ 3, !Ref NumberOfSubnets ] - NumberOfSubnets4: - !Equals [ 4, !Ref NumberOfSubnets ] - NumberOfSubnets5: - !Equals [ 5, !Ref NumberOfSubnets ] - NumberOfSubnets6: - !Equals [ 6, !Ref NumberOfSubnets ] Subnet0: !Or - !Condition NumberOfSubnets1 - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 Subnet1: !Or - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet2: !Or - - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet3: !Or - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet4: !Or - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet5: !Condition NumberOfSubnets6 + Subnet2: !Condition NumberOfSubnets3 Resources: - PublicAlbListenerNoSslCertificate: Type : AWS::ElasticLoadBalancingV2::Listener Properties: @@ -109,6 +85,7 @@ Resources: LoadBalancerArn: !Ref PublicApplicationLoadBalancer Port: 80 Protocol: HTTP + PublicAlbListenerSslCertificate: Condition: SslCertificate Type : AWS::ElasticLoadBalancingV2::Listener @@ -121,6 +98,7 @@ Resources: LoadBalancerArn: !Ref PublicApplicationLoadBalancer Port: 443 Protocol: HTTPS + PublicApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: @@ -132,49 +110,39 @@ Resources: !If [ NumberOfSubnets2, [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], - !If - [ NumberOfSubnets3, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ], - !If - [ NumberOfSubnets4, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ], - !If - [ NumberOfSubnets5, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ], - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ] - ] - ] - ] + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] ] ] LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds - Value: 60 + Value: '60' SecurityGroups: - !Ref PublicAlbSecurityGroup Tags: - Key: Name - Value: !Join [ '', [ 'Public ALB / ', !Ref 'AWS::StackName' ] ] + Value: !Join [ '-', [ 'Moodle',!Ref ProjectName,'alb' ] ] PublicAlbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 - HealthCheckPath: /README.txt + HealthCheckPath: /status.txt HealthCheckTimeoutSeconds: 5 Name: !Join [ '', [ 'PublicALB-', !Ref Vpc ] ] Port: 80 Protocol: HTTP Tags: - Key: Name - Value: !Join [ '', [ 'Public ALB / ', !Ref 'AWS::StackName' ] ] + Value: !Join [ '-', [ 'Moodle',!Ref ProjectName,'alb-TG' ] ] UnhealthyThresholdCount: 5 VpcId: !Ref Vpc Outputs: - PublicAlbTargetGroupArn: Value: !Ref PublicAlbTargetGroup + PublicAlbTargetGroupName: + Value: + !GetAtt PublicAlbTargetGroup.TargetGroupName PublicAlbCanonicalHostedZoneId: Value: !GetAtt PublicApplicationLoadBalancer.CanonicalHostedZoneID @@ -184,6 +152,9 @@ Outputs: PublicAlbFullName: Value: !GetAtt PublicApplicationLoadBalancer.LoadBalancerFullName + PublicAlbName: + Value: + !GetAtt PublicApplicationLoadBalancer.LoadBalancerName PublicAlbHostname: Value: !If [ NoSslCertificate, !Join [ '', [ 'http://', !GetAtt PublicApplicationLoadBalancer.DNSName ] ], !Join [ '', [ 'https://', !GetAtt PublicApplicationLoadBalancer.DNSName ] ] ] diff --git a/templates/03-rds.yaml b/templates/03-rds.yaml index 200debb..541aae2 100644 --- a/templates/03-rds.yaml +++ b/templates/03-rds.yaml @@ -1,7 +1,7 @@ --- AWSTemplateFormatVersion: 2010-09-09 -Description: Reference Architecture to host Moodle on AWS - Creates RDS Aurora PostgreSQL database cluster +Description: Reference Architecture to host Moodle on AWS - Creates RDS Aurora MySQL or PostgreSQL database cluster Metadata: AWS::CloudFormation::Interface: @@ -9,17 +9,20 @@ Metadata: - Label: default: Database Parameters Parameters: + - DatabaseType - DatabaseInstanceType # - DatabaseMasterUsername # - DatabaseMasterPassword - DatabaseName - - DatabaseEncrpytedBoolean + - DatabaseEncryptedBoolean - DatabaseCmk - DatabaseSecurityGroup - NumberOfSubnets - Subnet ParameterLabels: - DatabaseEncrpytedBoolean: + DatabaseType: + default: Aurora database type + DatabaseEncryptedBoolean: default: Encrypted DB Cluster DatabaseCmk: default: AWS KMS Customer Master Key (CMK) to encrypt DB @@ -39,8 +42,14 @@ Metadata: default: Subnets Parameters: - - DatabaseEncrpytedBoolean: + DatabaseType: + AllowedValues: + - MySQL + - PostgreSQL + Default: PostgreSQL + Description: Indicates whether to use Aurora MySQL or PostgreSQL. + Type: String + DatabaseEncryptedBoolean: AllowedValues: - true - false @@ -52,94 +61,71 @@ Parameters: Type: String DatabaseInstanceType: AllowedValues: + - db.t3.medium + - db.t3.large - db.r5.large - db.r5.xlarge - db.r5.2xlarge - db.r5.4xlarge - db.r5.8xlarge + - db.r5.12xlarge - db.r5.16xlarge - ConstraintDescription: Must be a valid RDS instance class. - Default: db.r5.large - Description: The Amazon RDS database instance class. + - db.r6g.large + - db.r6g.xlarge + - db.r6g.2xlarge + - db.r6g.4xlarge + - db.r6g.8xlarge + - db.r6g.12xlarge + - db.r6g.16xlarge + ConstraintDescription: Must be a valid Aurora RDS instance type. + Default: db.r6g.large + Description: Amazon Aurora RDS database instance type Type: String - # DatabaseMasterUsername: - # AllowedPattern: ^([a-zA-Z0-9]*)$ - # Description: The Amazon RDS master username. - # ConstraintDescription: Must contain only alphanumeric characters and be at least 8 characters. - # MaxLength: 16 - # MinLength: 1 - # Type: String - # DatabaseMasterPassword: - # AllowedPattern: ^([a-z0-9A-Z`~!#$%^&*()_+,\\-])*$ - # ConstraintDescription: Must be letters (upper or lower), numbers, and these special characters '_'`~!#$%^&*()_+,- - # Description: The Amazon RDS master password. - # MaxLength: 41 - # MinLength: 8 - # NoEcho: true - # Type: String - MyRDSInstanceSecretArn: + RDSInstanceSecretArn: Type: String DatabaseName: AllowedPattern: ^([a-zA-Z0-9]*)$ - Description: The Amazon RDS master database name. + Description: Amazon Aurora RDS master database name Type: String DatabaseSecurityGroup: - Description: Select the database security group. + Description: Database security group Type: AWS::EC2::SecurityGroup::Id NumberOfSubnets: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 - Default: 3 + Default: 2 Description: Number of subnets. This must match your selections in the list of subnets below. - Type: String + Type: String Subnet: Description: Select existing subnets. The number selected must match the number of subnets above. Subnets selected must be in separate AZs. Type: List + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: Moodle Project Name + Type: String Conditions: - + UseMySQL: + !Equals [!Ref DatabaseType, MySQL] + UsePostgreSQL: + !Equals [!Ref DatabaseType, PostgreSQL] NumberOfSubnets1: !Equals [ 1, !Ref NumberOfSubnets ] NumberOfSubnets2: !Equals [ 2, !Ref NumberOfSubnets ] NumberOfSubnets3: !Equals [ 3, !Ref NumberOfSubnets ] - NumberOfSubnets4: - !Equals [ 4, !Ref NumberOfSubnets ] - NumberOfSubnets5: - !Equals [ 5, !Ref NumberOfSubnets ] - NumberOfSubnets6: - !Equals [ 6, !Ref NumberOfSubnets ] Subnet0: !Or - !Condition NumberOfSubnets1 - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 Subnet1: !Or - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet2: !Or - - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet3: !Or - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet4: !Or - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet5: !Condition NumberOfSubnets6 + Subnet2: !Condition NumberOfSubnets3 UseAWS-ManagedCMK: !Equals ['', !Ref DatabaseCmk] @@ -150,14 +136,19 @@ Resources: BackupRetentionPeriod: 30 DatabaseName: !Ref DatabaseName DBSubnetGroupName: !Ref DataSubnetGroup - Engine: aurora-postgresql - DBClusterParameterGroupName: default.aurora-postgresql13 + Engine: + !If [ UsePostgreSQL, aurora-postgresql, aurora-mysql ] + DBClusterParameterGroupName: + !If [ UsePostgreSQL, default.aurora-postgresql13, default.aurora-mysql8.0 ] + EngineVersion: + !If [ UsePostgreSQL, '13.8', '8.0.mysql_aurora.3.02.1' ] KmsKeyId: !If [ UseAWS-ManagedCMK, !Ref 'AWS::NoValue', !Ref DatabaseCmk ] - MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref MyRDSInstanceSecretArn, ':SecretString:username}}' ]] - MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref MyRDSInstanceSecretArn, ':SecretString:password}}' ]] - Port: 5432 - StorageEncrypted: !Ref DatabaseEncrpytedBoolean + MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecretArn, ':SecretString:username}}' ]] + MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecretArn, ':SecretString:password}}' ]] + Port: + !If [ UsePostgreSQL, 5432, 3306 ] + StorageEncrypted: !Ref DatabaseEncryptedBoolean Tags: - Key: Name Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] @@ -172,7 +163,8 @@ Resources: DBClusterIdentifier: !Ref DatabaseCluster DBInstanceClass: !Ref DatabaseInstanceType DBSubnetGroupName: !Ref DataSubnetGroup - Engine: aurora-postgresql + Engine: + !If [ UsePostgreSQL, aurora-postgresql, aurora-mysql ] Tags: - Key: Name Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] @@ -185,7 +177,8 @@ Resources: DBClusterIdentifier: !Ref DatabaseCluster DBInstanceClass: !Ref DatabaseInstanceType DBSubnetGroupName: !Ref DataSubnetGroup - Engine: aurora-postgresql + Engine: + !If [ UsePostgreSQL, aurora-postgresql, aurora-mysql ] Tags: - Key: Name Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] @@ -200,27 +193,46 @@ Resources: !If [ NumberOfSubnets2, [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], - !If - [ NumberOfSubnets3, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ], - !If - [ NumberOfSubnets4, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ], - !If - [ NumberOfSubnets5, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ], - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ] - ] - ] - ] + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] ] ] Tags: - Key: Name Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] + DatabaseClusterParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/ClusterEndpoint' ] ] + Type: String + Value: !GetAtt DatabaseCluster.Endpoint.Address + Description: SSM Parameter for Moodle DB Cluster + + DatabaseClusterReadOnlyParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/ClusterReadOnlyEndpoint' ] ] + Type: String + Value: !GetAtt DatabaseCluster.ReadEndpoint.Address + Description: SSM Parameter for Moodle DB Cluster Read only + + DatabaseNameParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/Name' ] ] + Type: String + Value: !Ref DatabaseName + Description: SSM Parameter for Moodle DB Name + + DatabaseTypeParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/Type' ] ] + Type: String + Value: !Ref DatabaseType + Description: SSM Parameter for Moodle DB Type Outputs: - DatabaseCluster: + Database: Value: !Ref DatabaseCluster DatabaseName: Value: !Ref DatabaseName @@ -232,5 +244,8 @@ Outputs: Value: !Ref DataSubnetGroup DatabaseClusterEndpointAddress: Value: !GetAtt DatabaseCluster.Endpoint.Address - + DatabaseClusterEndpointPort: + Value: !GetAtt DatabaseCluster.Endpoint.Port + DatabaseClusterReadEndpointAddress: + Value: !GetAtt DatabaseCluster.ReadEndpoint.Address diff --git a/templates/03-rdsserverless.yaml b/templates/03-rdsserverless.yaml new file mode 100644 index 0000000..3dfe779 --- /dev/null +++ b/templates/03-rdsserverless.yaml @@ -0,0 +1,244 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: Reference Architecture to host Moodle on AWS - Creates RDS Aurora Serverless v2 MySQL or PostgreSQL database + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Database Parameters + Parameters: + - DatabaseType + - DatabaseMinCapacity + - DatabaseMaxCapacity + - DatabaseName + - DatabaseEncryptedBoolean + - DatabaseCmk + - DatabaseSecurityGroup + - NumberOfSubnets + - Subnet + ParameterLabels: + DatabaseType: + default: Aurora database type + DatabaseMinCapacity: + default: Minimum capacity for database + DatabaseMaxCapacity: + default: Maximum capacity for database + DatabaseEncryptedBoolean: + default: Encrypted DB Cluster + DatabaseCmk: + default: AWS KMS Customer Master Key (CMK) to encrypt DB + DatabaseName: + default: DB Name + DatabaseSecurityGroup: + default: DB Security Group + NumberOfSubnets: + default: Number of subnets + Subnet: + default: Subnets + +Parameters: + DatabaseType: + AllowedValues: + - MySQL + - PostgreSQL + Default: PostgreSQL + Description: Indicates whether to use Aurora MySQL or PostgreSQL. + Type: String + DatabaseEncryptedBoolean: + AllowedValues: + - true + - false + Default: True + Description: Indicates whether the DB instances in the cluster are encrypted. + Type: String + DatabaseCmk: + Description: AWS KMS Customer Master Key (CMK) to encrypt database cluster + Type: String + RDSInstanceSecretArn: + Type: String + DatabaseName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Description: Amazon Aurora RDS master database name + Type: String + DatabaseSecurityGroup: + Description: Database security group + Type: AWS::EC2::SecurityGroup::Id + NumberOfSubnets: + AllowedValues: + - 1 + - 2 + - 3 + Default: 2 + Description: Number of subnets. This must match your selections in the list of subnets below. + Type: String + Subnet: + Description: Select existing subnets. The number selected must match the number of subnets above. Subnets selected must be in separate AZs. + Type: List + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: Moodle Project Name + Type: String + DatabaseMinCapacity: + AllowedValues: + - 0.5 + - 1 + - 2 + - 4 + - 5 + - 16 + - 32 + - 64 + - 128 + Default: 0.5 + Description: The minimum capacity for an Aurora DB cluster, starts with 0.5 and maximum up to 1 + Type: String + DatabaseMaxCapacity: + AllowedValues: + - 0.5 + - 1 + - 2 + - 4 + - 5 + - 16 + - 32 + - 64 + - 128 + Default: 64 + Description: The maximum capacity for an Aurora DB cluster, starts with 0.5 and maximum up to 1 + Type: String + +Conditions: + UseMySQL: + !Equals [!Ref DatabaseType, MySQL] + UsePostgreSQL: + !Equals [!Ref DatabaseType, PostgreSQL] + NumberOfSubnets1: + !Equals [ 1, !Ref NumberOfSubnets ] + NumberOfSubnets2: + !Equals [ 2, !Ref NumberOfSubnets ] + NumberOfSubnets3: + !Equals [ 3, !Ref NumberOfSubnets ] + Subnet0: !Or + - !Condition NumberOfSubnets1 + - !Condition NumberOfSubnets2 + - !Condition NumberOfSubnets3 + Subnet1: !Or + - !Condition NumberOfSubnets2 + - !Condition NumberOfSubnets3 + Subnet2: !Condition NumberOfSubnets3 + UseAWS-ManagedCMK: + !Equals ['', !Ref DatabaseCmk] + +Resources: + DatabaseCluster: + Type: AWS::RDS::DBCluster + Properties: + BackupRetentionPeriod: 30 + DatabaseName: !Ref DatabaseName + DBSubnetGroupName: !Ref DataSubnetGroup + Engine: + !If [ UsePostgreSQL, aurora-postgresql, aurora-mysql ] + DBClusterParameterGroupName: + !If [ UsePostgreSQL, default.aurora-postgresql13, default.aurora-mysql8.0 ] + EngineVersion: + !If [ UsePostgreSQL, '13.8', '8.0.mysql_aurora.3.02.1' ] + KmsKeyId: + !If [ UseAWS-ManagedCMK, !Ref 'AWS::NoValue', !Ref DatabaseCmk ] + MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecretArn, ':SecretString:username}}' ]] + MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref RDSInstanceSecretArn, ':SecretString:password}}' ]] + Port: + !If [ UsePostgreSQL, 5432, 3306 ] + StorageEncrypted: !Ref DatabaseEncryptedBoolean + Tags: + - Key: Name + Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] + VpcSecurityGroupIds: + - !Ref DatabaseSecurityGroup + ServerlessV2ScalingConfiguration: + MinCapacity: !Ref DatabaseMinCapacity + MaxCapacity: !Ref DatabaseMaxCapacity + + DatabaseInstance0: + Type: AWS::RDS::DBInstance + DeletionPolicy: Delete + Properties: + AllowMajorVersionUpgrade: false + AutoMinorVersionUpgrade: true + DBClusterIdentifier: !Ref DatabaseCluster + DBInstanceClass: 'db.serverless' + DBSubnetGroupName: !Ref DataSubnetGroup + Engine: + !If [ UsePostgreSQL, aurora-postgresql, aurora-mysql ] + Tags: + - Key: Name + Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] + + DataSubnetGroup: + Type: AWS::RDS::DBSubnetGroup + Properties: + DBSubnetGroupDescription: RDS Database Subnet Group for Moodle + SubnetIds: + !If + [ NumberOfSubnets1, + [ !Select [ 0, !Ref Subnet ] ], + !If + [ NumberOfSubnets2, + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] + ] + ] + Tags: + - Key: Name + Value: !Join [ '', [ 'Moodle / ', !Ref 'AWS::StackName' ] ] + + DatabaseClusterParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/ClusterEndpoint' ] ] + Type: String + Value: !GetAtt DatabaseCluster.Endpoint.Address + Description: SSM Parameter for Moodle DB Cluster + + DatabaseClusterReadOnlyParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/ClusterReadOnlyEndpoint' ] ] + Type: String + # Aurora Serverless v1 does not support Read-only replicas + Value: !GetAtt DatabaseCluster.Endpoint.Address + Description: SSM Parameter for Moodle DB Cluster Read only + + DatabaseNameParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/Name' ] ] + Type: String + Value: !Ref DatabaseName + Description: SSM Parameter for Moodle DB Name + + DatabaseTypeParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Join [ '', [ '/Moodle/',!Ref ProjectName, '/DB/Type' ] ] + Type: String + Value: !Ref DatabaseType + Description: SSM Parameter for Moodle DB Type + +Outputs: + Database: + Value: !Ref DatabaseCluster + DatabaseName: + Value: !Ref DatabaseName + DataSubnetGroup: + Value: !Ref DataSubnetGroup + DatabaseClusterEndpointAddress: + Value: !GetAtt DatabaseCluster.Endpoint.Address + DatabaseClusterEndpointPort: + Value: !GetAtt DatabaseCluster.Endpoint.Port + DatabaseClusterReadEndpointAddress: + Value: !GetAtt DatabaseCluster.ReadEndpoint.Address + + diff --git a/templates/03-s3.yaml b/templates/03-s3.yaml new file mode 100644 index 0000000..0f787ec --- /dev/null +++ b/templates/03-s3.yaml @@ -0,0 +1,28 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: Reference Architecture to host Moodle on AWS - Creates S3 artifact bucket + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: S3 Parameters + Parameters: + - S3BucketName + ParameterLabels: + S3BucketName: + default: S3 Bucket Name + +Parameters: + S3BucketName: + Description: The S3 Bucket name. + Type: String + +Resources: + S3Bucket: + Type: AWS::S3::Bucket + +Outputs: + BucketName: + Value: !Ref S3Bucket \ No newline at end of file diff --git a/templates/04-cloudfront.yaml b/templates/04-cloudfront.yaml index aed9e95..a80c99f 100644 --- a/templates/04-cloudfront.yaml +++ b/templates/04-cloudfront.yaml @@ -53,8 +53,6 @@ Resources: Condition: NoSslCertificate Properties: DistributionConfig: - Aliases: - - !If [ DomainName, !Ref DomainName, !Ref 'AWS::NoValue' ] Comment: !Ref 'AWS::StackName' DefaultCacheBehavior: AllowedMethods: @@ -74,16 +72,20 @@ Resources: - '*' Cookies: Forward: all - TargetOriginId: elb - ViewerProtocolPolicy: allow-all + TargetOriginId: alb + ViewerProtocolPolicy: redirect-to-https Compress: true Enabled: true Origins: - DomainName: !Ref PublicAlbDnsName - Id: elb + Id: alb CustomOriginConfig: OriginProtocolPolicy: http-only - PriceClass: PriceClass_100 + PriceClass: PriceClass_All + HttpVersion: http2and3 + ViewerCertificate: + CloudFrontDefaultCertificate: true + CloudFrontDistributionSslCertificate: Type: AWS::CloudFront::Distribution Condition: SslCertificate @@ -110,24 +112,23 @@ Resources: - '*' Cookies: Forward: all - TargetOriginId: elb + TargetOriginId: alb ViewerProtocolPolicy: redirect-to-https Compress: true Enabled: true Origins: - DomainName: !Ref PublicAlbDnsName - Id: elb + Id: alb CustomOriginConfig: - OriginProtocolPolicy: https-only - PriceClass: PriceClass_100 + OriginProtocolPolicy: http-only + PriceClass: PriceClass_All + HttpVersion: http2and3 ViewerCertificate: AcmCertificateArn: !Ref CloudFrontAcmCertificate SslSupportMethod: sni-only - MinimumProtocolVersion: TLSv1 + MinimumProtocolVersion: TLSv1.2_2019 Outputs: - DnsEndpoint: - Value: !If [ NoSslCertificate, !GetAtt CloudFrontDistributionNoSslCertificate.DomainName, !GetAtt CloudFrontDistributionSslCertificate.DomainName ] - DnsHostname: - Value: !If [ NoSslCertificate, !Join [ '', [ 'http://', !GetAtt CloudFrontDistributionNoSslCertificate.DomainName ] ], !Join [ '', [ 'https://', !GetAtt CloudFrontDistributionSslCertificate.DomainName ] ] ] \ No newline at end of file + DnsName: + Value: !If [ NoSslCertificate, !GetAtt CloudFrontDistributionNoSslCertificate.DomainName, !GetAtt CloudFrontDistributionSslCertificate.DomainName ] \ No newline at end of file diff --git a/templates/04-efsfilegrowhelper.yaml b/templates/04-efsfilegrowhelper.yaml new file mode 100644 index 0000000..9d4dc78 --- /dev/null +++ b/templates/04-efsfilegrowhelper.yaml @@ -0,0 +1,660 @@ +--- +AWSTemplateFormatVersion: 2010-09-09 + +Description: Reference Architecture to host Moodle on AWS - Creates EFS alarms + +Parameters: + Growth: + ConstraintDescription: Must be an integer. + Default: 0 + Description: Amount of dummy data (GiB) to add to the file system (max 6144 GiB). Amazon EFS storage charges apply. + MaxValue: 6144 + MinValue: 0 + Type: Number + CriticalThreshold: + AllowedPattern: ^[0-9]+$ + ConstraintDescription: Must be an integer. + Default: 60 + Description: Send critical alarm this minutes before burst credit balance is zero. + Type: String + SecurityGroup: + Description: Select the Amazon EFS security group. + Type: AWS::EC2::SecurityGroup::Id + ElasticFileSystem: + Description: The Amazon EFS file system id. + Type: String + EmailAddress: + Description: The email address for SNS notifications. + Type: String + InstanceType: + AllowedValues: + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.9xlarge + - c5a.12xlarge + - c5a.18xlarge + - c5a.24xlarge + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6g.24xlarge + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.9xlarge + - c6g.12xlarge + - c6g.18xlarge + - c6g.24xlarge + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.24xlarge + ConstraintDescription: Must be a valid Amazon EC2 instance type. + Default: m6g.large + Description: The Amazon EC2 instance type that dynamically adjusts thresholds based on permitted throughput changes. + Type: String + EC2KeyName: + Description: Name of an existing EC2 key pair + Type: AWS::EC2::KeyPair::KeyName + NumberOfSubnets: + AllowedValues: + - 1 + - 2 + - 3 + Default: 2 + Description: Number of subnets. This must match your selections in the list of Subnets below. + Type: String + Subnet: + Description: Select existing subnets. + Type: List + WarningThreshold: + AllowedPattern: ^[0-9]+$ + ConstraintDescription: Must be an integer. + Default: 180 + Description: Send warning alarm this many minutes before burst credit balance is zero. + Type: String + LatestAmiId : + Type : AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 + LatestArmAmiId : + Type : AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 + +Conditions: + NumberOfSubnets1: + !Equals [ 1, !Ref NumberOfSubnets ] + NumberOfSubnets2: + !Equals [ 2, !Ref NumberOfSubnets ] + NumberOfSubnets3: + !Equals [ 3, !Ref NumberOfSubnets ] + Subnet0: !Or + - !Condition NumberOfSubnets1 + - !Condition NumberOfSubnets2 + - !Condition NumberOfSubnets3 + Subnet1: !Or + - !Condition NumberOfSubnets2 + - !Condition NumberOfSubnets3 + Subnet2: !Condition NumberOfSubnets3 + UsingGraviton2Ami: !Or + - !Equals ["t4",!Select [0, !Split [ "g.", !Ref InstanceType]]] + - !Equals ["c6",!Select [0, !Split [ "g.", !Ref InstanceType]]] + - !Equals ["m6",!Select [0, !Split [ "g.", !Ref InstanceType]]] + - !Equals ["r6",!Select [0, !Split [ "g.", !Ref InstanceType]]] + +Resources: + InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: / + Roles: + - !Ref InstanceRole + + InstanceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' + Path: / + Policies: + - PolicyName: MoodleEFSFileHelperPolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - autoscaling:DescribeAutoScalingGroups + - autoscaling:DescribeAutoScalingInstances + - autoscaling:DescribePolicies + - autoscaling:UpdateAutoScalingGroup + Resource: '*' + - Effect: Allow + Action: + - cloudwatch:GetMetricStatistics + - cloudwatch:PutMetricAlarm + - autoscaling:DescribeAutoScalingGroups + - autoscaling:DescribeAutoScalingInstances + - autoscaling:DescribePolicies + - autoscaling:UpdateAutoScalingGroup + - elasticfilesystem:DescribeFileSystems + Resource: '*' + - Effect: Allow + Action: + - sns:Publish + Resource: !Ref SNSTopic + + AutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + Cooldown: 60 + HealthCheckGracePeriod: 120 + HealthCheckType: EC2 + LaunchTemplate: + LaunchTemplateId: !Ref LaunchTemplate + Version: !GetAtt LaunchTemplate.LatestVersionNumber + MaxSize: 1 + MinSize: 0 + DesiredCapacity: 1 + Tags: + - Key: Name + Value: !Join [ '', [ 'Moodle EFS Helper ', !Ref 'ElasticFileSystem', ' ...will auto terminate' ] ] + PropagateAtLaunch: true + VPCZoneIdentifier: + !If + [ NumberOfSubnets1, + [ !Select [ 0, !Ref Subnet ] ], + !If + [ NumberOfSubnets2, + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] + ] + ] + CreationPolicy: + ResourceSignal: + Count: 0 + Timeout: PT12H + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: true + + AutoScalingPolicy: + Type: AWS::AutoScaling::ScalingPolicy + Properties: + AdjustmentType: ChangeInCapacity + AutoScalingGroupName: !Ref AutoScalingGroup + Cooldown: 60 + PolicyType: SimpleScaling + ScalingAdjustment: 1 + + BurstCreditBalanceDecreaseAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmActions: + - !Ref SNSTopic + - !Ref AutoScalingPolicy + AlarmDescription: !Join [ '', [ 'Set ', !Ref ElasticFileSystem, ' burst credit balance decrease threshold - ', !Ref 'AWS::StackName' ] ] + AlarmName: !Join [ '', [ 'Set ', !Ref ElasticFileSystem, ' burst credit balance decrease threshold - ', !Ref 'AWS::StackName' ] ] + ComparisonOperator: LessThanThreshold + Dimensions: + - Name: FileSystemId + Value: !Ref ElasticFileSystem + EvaluationPeriods: 10 + MetricName: PermittedThroughput + Namespace: AWS/EFS + Period: 60 + Statistic: Sum + Threshold: 0 + TreatMissingData: missing + + BurstCreditBalanceIncreaseAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmActions: + - !Ref SNSTopic + - !Ref AutoScalingPolicy + AlarmDescription: !Join [ '', [ 'Set ', !Ref ElasticFileSystem, ' burst credit balance increase threshold - ', !Ref 'AWS::StackName' ] ] + AlarmName: !Join [ '', [ 'Set ', !Ref ElasticFileSystem, ' burst credit balance increase threshold - ', !Ref 'AWS::StackName' ] ] + ComparisonOperator: LessThanThreshold + Dimensions: + - Name: FileSystemId + Value: !Ref ElasticFileSystem + EvaluationPeriods: 10 + MetricName: PermittedThroughput + Namespace: AWS/EFS + Period: 60 + Statistic: Sum + Threshold: 0 + TreatMissingData: missing + + CriticalAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmActions: + - !Ref SNSTopic + AlarmDescription: !Join [ '', [ !Ref ElasticFileSystem, ' burst credit balance - Critical - ', !Ref 'AWS::StackName' ] ] + AlarmName: !Join [ '', [ !Ref ElasticFileSystem, ' burst credit balance - Critical - ', !Ref 'AWS::StackName' ] ] + ComparisonOperator: LessThanThreshold + Dimensions: + - Name: FileSystemId + Value: !Ref ElasticFileSystem + EvaluationPeriods: 10 + MetricName: BurstCreditBalance + Namespace: AWS/EFS + Period: 60 + Statistic: Sum + Threshold: 0 + TreatMissingData: missing + + LaunchTemplate: + Type: AWS::EC2::LaunchTemplate + Metadata: + AWS::CloudFormation::Init: + configSets: + efs_add_storage: + - efs-add-storage + set_cloudwatch_alarms: + - set-cloudwatch-alarms + efs-add-storage: + packages: + yum: + #Only needed to set permissions on the moodle folder + httpd: [] + files: + /tmp/efs-add-storage.sh: + content: !Sub | + #!/bin/bash -x + + FILE_SYSTEM_ID=$1 + DATA_DIRECTORY=$2 + GROWTH=$3 + + if [ $# -lt 3 ]; then + echo "Invalid # of arguments. Require: file system id, data directory, file system growth (GiB) " + exit 0 + fi + + # get region from instance meta-data + availabilityzone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) + region=${!availabilityzone:0:-1} + + # get instance id + instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) + + # get autoscaling group name + asg_name=$(aws autoscaling describe-auto-scaling-instances --instance-ids $instance_id --region $region --output text --query 'AutoScalingInstances[0].AutoScalingGroupName') + + # set the number of threads to the number of vcpus + threads=$(( $(nproc --all) * 8 )) + + # wait for file system DNS name to be propagated + results=1 + while [[ $results != 0 ]]; do + nslookup $FILE_SYSTEM_ID.efs.$region.amazonaws.com + results=$? + if [[ results = 1 ]]; then + sleep 30 + fi + done + + # mount file system + sudo mkdir -p /$FILE_SYSTEM_ID + sudo chown ec2-user:ec2-user /$FILE_SYSTEM_ID + sudo mountpoint -q /$FILE_SYSTEM_ID || sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 $FILE_SYSTEM_ID.efs.$region.amazonaws.com:/ /$FILE_SYSTEM_ID + + #Create directories for Moodle + sudo mkdir -p /$FILE_SYSTEM_ID/data + sudo mkdir -p /$FILE_SYSTEM_ID/cache + sudo mkdir -p /$FILE_SYSTEM_ID/temp + chown apache:apache /$FILE_SYSTEM_ID/data/ + chown apache:apache /$FILE_SYSTEM_ID/cache/ + chown apache:apache /$FILE_SYSTEM_ID/temp/ + + # create data directory if not exists + sudo mkdir -p /$FILE_SYSTEM_ID/$DATA_DIRECTORY + sudo chown ec2-user:ec2-user /$FILE_SYSTEM_ID/$DATA_DIRECTORY + + # dd 1GiB files to file system to match DATA_SIZE + files=$GROWTH + if [ $(( $files / $threads )) == 0 ]; + then + runs=0 + parallel_threads=$(( $files % $threads )) + else + runs=$(( $files / $threads )) + parallel_threads=$threads + fi + while [ $runs -ge 0 ]; do + if [ $runs == 0 ]; + then + parallel_threads=$(( $files % $threads )) + seq 0 $(( $parallel_threads - 1 )) | parallel --will-cite -j $parallel_threads --compress dd if=/dev/zero of=/$FILE_SYSTEM_ID/$DATA_DIRECTORY/1G-dd-$(date +%Y%m%d%H%M%S.%3N)-{} bs=1M count=1024 oflag=sync + runs=$(($runs-1)) + else + seq 0 $(( $parallel_threads - 1 )) | parallel --will-cite -j $parallel_threads --compress dd if=/dev/zero of=/$FILE_SYSTEM_ID/$DATA_DIRECTORY/1G-dd-$(date +%Y%m%d%H%M%S.%3N)-{} bs=1M count=1024 oflag=sync + runs=$(($runs-1)) + fi + done + + # set ASG to zero which terminates instance + # aws autoscaling update-auto-scaling-group --auto-scaling-group-name $asg_name --desired-capacity 0 --region $region + mode: 000777 + owner: root + group: root + set-cloudwatch-alarms: + files: + /tmp/set-cloudwatch-alarms.sh: + content: + !Join [ + "",[ + "#!/bin/bash -x\n", + "\n", + "FILE_SYSTEM_ID=$1\n", + "WARNING_THRESHOLD_MINUTES=$2\n", + "CRITICAL_THRESHOLD_MINUTES=$3\n", + "SNS_ARN=$4\n", + "\n", + "error=0\n", + "\n", + "# get region\n", + "availability_zone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)\n", + "region=${availability_zone:0:-1}\n", + "\n", + "# get instance id\n", + "instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)\n", + "\n", + "# get autoscaling group name\n", + "asg_name=$(aws autoscaling describe-auto-scaling-instances --instance-ids ${instance_id} --region ${region} --output text --query 'AutoScalingInstances[0].AutoScalingGroupName')\n", + "\n", + "# get autoscaling policy arn\n", + "asg_policy_arn=$(aws autoscaling describe-policies --auto-scaling-group-name ${asg_name} --region ${region} --output text --query 'ScalingPolicies[0].PolicyARN')\n", + "\n", + "# validate FILE_SYSTEM_ID send notification and exit if doesn't exist\n", + "aws efs describe-file-systems --file-system-id ${FILE_SYSTEM_ID} --region ${region} --output text --query 'FileSystems[0].[FileSystemId]'\n", + "result=$?\n", + "if [ $result -ne 0 ]; then\n", + " aws sns publish --topic-arn ${SNS_ARN} --region ${region} --message 'Amazon EFS burst credit balance CloudWatch alarm error. File system '${FILE_SYSTEM_ID}' does not exist.'\n", + " exit\n", + "fi\n", + "\n", + "# get current permitted throughput\n", + "count=1\n", + "while [ -z ${permitted_throughput} ] || [ ${permitted_throughput} == null ] && [ ${count} -lt 60 ]; do\n", + " permitted_throughput=$(aws cloudwatch get-metric-statistics --namespace AWS/EFS --metric-name PermittedThroughput --dimensions Name=FileSystemId,Value=${FILE_SYSTEM_ID} --start-time $(date --utc +%FT%TZ -d '-120 seconds') --end-time $(date --utc +%FT%TZ -d '-60 seconds') --period 60 --statistics Sum --region ${region} --output json --query 'Datapoints[0].Sum')\n", + " sleep 2\n", + " count=$(expr ${count} + 1)\n", + "done\n", + "\n", + "# get current burst credit balance\n", + "count=1\n", + "while [ -z ${burst_credit_balance} ] || [ ${burst_credit_balance} == null ] && [ ${count} -lt 60 ]; do\n", + " burst_credit_balance=$(aws cloudwatch get-metric-statistics --namespace AWS/EFS --metric-name BurstCreditBalance --dimensions Name=FileSystemId,Value=${FILE_SYSTEM_ID} --start-time $(date --utc +%FT%TZ -d '-120 seconds') --end-time $(date --utc +%FT%TZ -d '-60 seconds') --period 60 --statistics Sum --region ${region} --output json --query 'Datapoints[0].Sum')\n", + " sleep 2\n", + " count=$(expr ${count} + 1)\n", + "done\n", + "\n", + "# calculate new burst credit balance warning threshold\n", + "burst_credit_balance_threshold_warning=$(( ${burst_credit_balance:0:-2} - ( ( ( ${burst_credit_balance:0:-2} / ( ${permitted_throughput:0:-2} * 60 ) ) - $WARNING_THRESHOLD_MINUTES ) * ( ${permitted_throughput:0:-2} * 60 ) ) ))\n", + "\n", + "# calculate new burst credit balance critical threshold\n", + "burst_credit_balance_threshold_critical=$(( ${burst_credit_balance:0:-2} - ( ( ( ${burst_credit_balance:0:-2} / ( ${permitted_throughput:0:-2} * 60 ) ) - $CRITICAL_THRESHOLD_MINUTES ) * ( ${permitted_throughput:0:-2} * 60 ) ) ))\n", + "\n", + "# update warning alarm with new burst credit balance warning threshold\n", + "aws cloudwatch put-metric-alarm --alarm-name ''${FILE_SYSTEM_ID}' burst credit balance - Warning - '", !Ref 'AWS::StackName', " --alarm-description ''${FILE_SYSTEM_ID}' burst credit balance - Warning - '", !Ref 'AWS::StackName', " --actions-enabled --alarm-actions ${SNS_ARN} --metric-name BurstCreditBalance --namespace AWS/EFS --statistic Sum --dimensions Name=FileSystemId,Value=${FILE_SYSTEM_ID} --period 60 --evaluation-periods 5 --threshold ${burst_credit_balance_threshold_warning} --comparison-operator LessThanThreshold --treat-missing-data missing --region ${region}\n", + "result=$?\n", + "if [ $result -ne 0 ]; then\n", + " aws sns publish --topic-arn ${SNS_ARN} --region ${region} --message 'Amazon EFS burst credit balance CloudWatch alarm error. Check CloudWatch alarms for file system '${FILE_SYSTEM_ID}'.'\n", + " error=$(expr ${error} + 1)\n", + "fi\n", + "\n", + "# update critical alarm with new burst credit balance critical threshold\n", + "aws cloudwatch put-metric-alarm --alarm-name ''${FILE_SYSTEM_ID}' burst credit balance - Critical - '", !Ref 'AWS::StackName', " --alarm-description ''${FILE_SYSTEM_ID}' burst credit balance - Critical - '", !Ref 'AWS::StackName', " --actions-enabled --alarm-actions ${SNS_ARN} --metric-name BurstCreditBalance --namespace AWS/EFS --statistic Sum --dimensions Name=FileSystemId,Value=${FILE_SYSTEM_ID} --period 60 --evaluation-periods 5 --threshold ${burst_credit_balance_threshold_critical} --comparison-operator LessThanThreshold --treat-missing-data missing --region ${region}\n", + "result=$?\n", + "if [ $result -ne 0 ]; then\n", + " aws sns publish --topic-arn ${SNS_ARN} --region ${region} --message 'Amazon EFS burst credit balance CloudWatch alarm error. Check CloudWatch alarms for file system '${FILE_SYSTEM_ID}'.'\n", + " error=$(expr ${error} + 1)\n", + "fi\n", + "\n", + "# update burst credit balance increase threshold based\n", + "aws cloudwatch put-metric-alarm --alarm-name 'Set '${FILE_SYSTEM_ID}' burst credit balance increase threshold - '", !Ref 'AWS::StackName', " --alarm-description 'Set '${FILE_SYSTEM_ID}' burst credit balance increase threshold - '", !Ref 'AWS::StackName', " --actions-enabled --alarm-actions ${SNS_ARN} ${asg_policy_arn} --metric-name PermittedThroughput --namespace AWS/EFS --statistic Sum --dimensions Name=FileSystemId,Value=${FILE_SYSTEM_ID} --period 60 --evaluation-periods 5 --threshold ${permitted_throughput:0:-2} --comparison-operator GreaterThanThreshold --treat-missing-data missing --region ${region}\n", + "result=$?\n", + "if [ $result -ne 0 ]; then\n", + " aws sns publish --topic-arn ${SNS_ARN} --region ${region} --message 'Amazon EFS burst credit balance CloudWatch alarm error. Check CloudWatch alarms for file system '${FILE_SYSTEM_ID}'.'\n", + " error=$(expr ${error} + 1)\n", + "fi\n", + "\n", + "# update burst credit balance decrease threshold based\n", + "aws cloudwatch put-metric-alarm --alarm-name 'Set '${FILE_SYSTEM_ID}' burst credit balance decrease threshold - '", !Ref 'AWS::StackName', " --alarm-description 'Set '${FILE_SYSTEM_ID}' burst credit balance decrease threshold - '", !Ref 'AWS::StackName', " --actions-enabled --alarm-actions ${SNS_ARN} ${asg_policy_arn} --metric-name PermittedThroughput --namespace AWS/EFS --statistic Sum --dimensions Name=FileSystemId,Value=${FILE_SYSTEM_ID} --period 60 --evaluation-periods 5 --threshold ${permitted_throughput:0:-2} --comparison-operator LessThanThreshold --treat-missing-data missing --region ${region}\n", + "result=$?\n", + "if [ $result -ne 0 ]; then\n", + " aws sns publish --topic-arn ${SNS_ARN} --region ${region} --message 'Amazon EFS burst credit balance CloudWatch alarm error. Check CloudWatch alarms for file system '${FILE_SYSTEM_ID}'.'\n", + " error=$(expr ${error} + 1)\n", + "fi\n", + "\n", + "# auto terminate instance - setting auto scaling group desired capacity 0\n", + "if [ $error -eq 0 ]; then\n", + " aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${asg_name} --desired-capacity 0 --region ${region}\n", + " else\n", + " aws sns publish --topic-arn ${SNS_ARN} --region ${region} --message 'Amazon EFS burst credit balance CloudWatch alarm error. Check CloudWatch alarms for file system '${FILE_SYSTEM_ID}'.'\n", + "fi\n", + "\n" + ] + ] + mode: 000777 + owner: root + group: root + Properties: + LaunchTemplateData: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + DeleteOnTermination: true + VolumeSize: 10 + VolumeType: gp3 + IamInstanceProfile: + Arn: !GetAtt InstanceProfile.Arn + ImageId: !If [UsingGraviton2Ami, !Ref LatestArmAmiId, !Ref LatestAmiId] + Monitoring: + Enabled: true + InstanceType: !Ref InstanceType + KeyName: !Ref EC2KeyName + SecurityGroupIds: + - !Ref SecurityGroup + UserData: + "Fn::Base64": + !Sub | + #!/bin/bash -xe + sudo systemctl enable amazon-ssm-agent + sudo systemctl start amazon-ssm-agent + sudo systemctl status amazon-ssm-agent + + yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + yum-config-manager --enable epel + yum install -y parallel + /opt/aws/bin/cfn-init --configsets set_cloudwatch_alarms --verbose --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region} + /opt/aws/bin/cfn-init --configsets efs_add_storage --verbose --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region} + /tmp/efs-add-storage.sh ${ElasticFileSystem} throughput_data ${Growth} + /tmp/set-cloudwatch-alarms.sh ${ElasticFileSystem} ${WarningThreshold} ${CriticalThreshold} ${SNSTopic} + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region} + + EfsSizeMonitorFunction: + DependsOn: LambdaRole + Type: AWS::Lambda::Function + Properties: + Code: + ZipFile: !Sub | + import boto3 + import os + import sys + + def handler(event, context): + if not os.environ.get('filesystemid'): + print "Unable to get the environment variable filesystemid" + sys.exit(1) + else: + filesystemid = os.environ.get('filesystemid') + + if not os.environ.get('region'): + print "Unable to get the environment variable region" + sys.exit(1) + else: + region = os.environ.get('region') + + def efs_get_size(): + client = boto3.client('efs') + response = client.describe_file_systems(FileSystemId=filesystemid) + k = response['FileSystems'][0]['SizeInBytes']['Value'] + return k + + def cloudwatch_put_metric(): + client = boto3.client('cloudwatch') + client.put_metric_data( + MetricData=[ + { + 'MetricName': 'SizeInBytes', + 'Dimensions': [ + { + 'Name': 'FileSystemId', + 'Value': filesystemid + }, + ], + 'Unit': 'None', + 'Value': efs_get_size() + }, + ], + Namespace='Custom/EFS' + ) + print('CloudWatch metric SizeInBytes sucessfully updated.') + + cloudwatch_put_metric() + Description: Lambda function to update the SizeInBytes EFS CloudWatch metric + Environment: + Variables: + filesystemid: !Ref ElasticFileSystem + region: !Ref 'AWS::Region' + FunctionName: !Join [ '', [ 'efs-', !Ref ElasticFileSystem, '-size-monitor' ] ] + Handler: index.handler + Role: !GetAtt LambdaRole.Arn + Runtime: python3.9 + Timeout: 60 + + LambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Path: / + ManagedPolicyArns: + - arn:aws:iam::aws:policy/CloudWatchFullAccess + - arn:aws:iam::aws:policy/AmazonElasticFileSystemReadOnlyAccess + # EfsLambdaPermission: + # Type: AWS::Lambda::Permission + # Properties: + # FunctionName: !Ref EfsSizeMonitorFunction + # Action: lambda:InvokeFunction + # Principal: events.amazonaws.com + # SourceArn: !GetAtt EfsSizeMonitorEvent.Arn + # EfsSizeMonitorEvent: + # Type: AWS::Events::Rule + # Properties: + # Description: Scheduled event to update SizeInBytes EFS CloudWatch metric + # Name: !Join [ '', [ 'efs-', !Ref ElasticFileSystem, '-size-monitor-scheduled-event' ] ] + # ScheduleExpression: rate(1 minute) + # State: ENABLED + # Targets: + # - Arn: !GetAtt EfsSizeMonitorFunction.Arn + # Id: '1' + + SNSTopic: + Type: AWS::SNS::Topic + Properties: + DisplayName: !Join [ '', [ !Ref ElasticFileSystem, '-alarm-notification' ] ] + Subscription: + - Endpoint: !Ref EmailAddress + Protocol: "email" + TopicName: !Join [ '', [ !Ref ElasticFileSystem, '-alarm-notification' ] ] + + WarningAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmActions: + - !Ref SNSTopic + AlarmDescription: !Join [ '', [ !Ref ElasticFileSystem, ' burst credit balance - Warning - ', !Ref 'AWS::StackName' ] ] + AlarmName: !Join [ '', [ !Ref ElasticFileSystem, ' burst credit balance - Warning - ', !Ref 'AWS::StackName' ] ] + ComparisonOperator: LessThanThreshold + Dimensions: + - Name: FileSystemId + Value: !Ref ElasticFileSystem + EvaluationPeriods: 10 + MetricName: BurstCreditBalance + Namespace: AWS/EFS + Period: 60 + Statistic: Sum + Threshold: 0 + TreatMissingData: missing + diff --git a/templates/04-web.yaml b/templates/04-web.yaml index 2370951..a0ebed9 100644 --- a/templates/04-web.yaml +++ b/templates/04-web.yaml @@ -2,113 +2,10 @@ AWSTemplateFormatVersion: 2010-09-09 Description: Reference Architecture to host Moodle on AWS - Creates Moodle web Auto Scaling group - -Metadata: - - AWS::CloudFormation::Interface: - ParameterGroups: - - Label: - default: Web Parameters - Parameters: - - EC2KeyName - - WebInstanceType - - WebAsgMax - - WebAsgMin - - WebSecurityGroup - - NumberOfSubnets - - Subnet - - PublicAlbTargetGroupArn - - PublicAlbHostname - - SslCertificate - - Label: - default: Moodle Parameters - Parameters: - - DomainName - - MoodleLocale - - Label: - default: Database Parameters - Parameters: - - DatabaseClusterEndpointAddress - - DatabaseName - # - DatabaseMasterUsername - # - DatabaseMasterPassword - - DatabaseClusterEndpointAddress - - ElastiCacheClusterEndpointAddress - - Label: - default: File System Parameters - Parameters: - - ElasticFileSystem - ParameterLabels: - DatabaseClusterEndpointAddress: - default: DB Cluster Endpoint Address - # DatabaseMasterUsername: - # default: DB Master Username - # DatabaseMasterPassword: - # default: DB Master Password - DatabaseName: - default: DB Name - ElastiCacheClusterEndpointAddress: - default: ElastiCache Endpoint Address - ElasticFileSystem: - default: EFS File System - EC2KeyName: - default: Existing Key Pair - NumberOfSubnets: - default: Number of subnets - PublicAlbTargetGroupArn: - default: Public ALB Target Group Arn - PublicAlbHostname: - default: Public ALB Hostname - SslCertificate: - default: ACM Cert attached to Public ALB - Subnet: - default: Subnets - WebAsgMax: - default: Web ASG Max - WebAsgMin: - default: Web ASG Min - WebInstanceType: - default: Web Instance Type - WebSecurityGroup: - default: Web Security Group - DomainName: - default: Site Domain - MoodleLocale: - default: Language Code - + Parameters: - ElastiCacheClusterEndpointAddress: - Description: The ElastiCacheCluster cluster endpoint address. - Type: String - DatabaseClusterEndpointAddress: - Description: The RDS cluster endpoint address. - Type: String - # DatabaseMasterUsername: - # AllowedPattern: ^([a-zA-Z0-9]*)$ - # Description: The Amazon RDS master username. - # ConstraintDescription: Must contain only alphanumeric characters and be at least 8 characters. - # MaxLength: 16 - # MinLength: 1 - # Type: String - # DatabaseMasterPassword: - # AllowedPattern: ^([a-z0-9A-Z`~!#$%^&*()_+,\\-])*$ - # ConstraintDescription: Must be letters (upper or lower), numbers, and these special characters '_'`~!#$%^&*()_+,- - # Description: The Amazon RDS master password. - # MaxLength: 41 - # MinLength: 8 - # NoEcho: true - # Type: String - MyRDSInstanceSecretArn: - Type: String - DatabaseName: - AllowedPattern: ^([a-zA-Z0-9]*)$ - Description: The Amazon RDS master database name. - Type: String - Default: moodle - ElasticFileSystem: - AllowedPattern: ^(fs-)([a-z0-9]{8}|[a-z0-9]{17})$ - Description: The Amazon EFS file system id. + RDSInstanceSecretArn: Type: String EC2KeyName: AllowedPattern: ^([a-zA-Z0-9 @.`~!#$%^&*()_+,\\-])*$ @@ -117,35 +14,22 @@ Parameters: Type: AWS::EC2::KeyPair::KeyName NumberOfSubnets: AllowedValues: + - 1 - 2 - 3 - - 4 - - 5 - - 6 Default: 2 Description: Number of subnets. This must match your selections in the list of subnets below. Type: String PublicAlbTargetGroupArn: Description: The public application load balancer target group arn. Type: String - PublicAlbHostname: - Description: The hostname of the public ALB http form (e.g. http://abdc-12345-xyz..elb.amazonaws.com) - Type: String - SslCertificate: - AllowedValues: - - True - - False - Default: True - Description: Is there an ACM SSL Certificate attached to the Public ALB? - Type: - String Subnet: Description: Select existing subnets. The number selected must match the number of subnets above. Subnets selected must be in separate AZs. Type: List WebAsgMax: AllowedPattern: ^((?!0$)[1-2]?[0-9]|30)$ ConstraintDescription: Must be a number between 1 and 30. - Default: 4 + Default: 1 Description: Specifies the maximum number of EC2 instances in the Web Autoscaling Group. Type: String WebAsgMin: @@ -156,161 +40,138 @@ Parameters: Type: String WebInstanceType: AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - t2.xlarge - - t2.2xlarge - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - m4.16xlarge - - m5.large - - m5.xlarge - - m5.2xlarge - - m5.4xlarge - - m5.12xlarge - - m5.24xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - c5.large - - c5.xlarge - - c5.2xlarge - - c5.4xlarge - - c5.9xlarge - - c5.18xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - r4.large - - r4.xlarge - - r4.2xlarge - - r4.4xlarge - - r4.8xlarge - - r4.16xlarge - - x1.16xlarge - - x1.32xlarge - - x1e.xlarge - - x1e.2xlarge - - x1e.4xlarge - - x1e.8xlarge - - x1e.16xlarge - - x1e.32xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - - h1.2xlarge - - h1.4xlarge - - h1.8xlarge - - h1.16xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - i3.large - - i3.xlarge - - i3.2xlarge - - i3.4xlarge - - i3.8xlarge - - i3.16xlarge - - f1.2xlarge - - f1.16xlarge - - g2.2xlarge - - g2.8xlarge - - g3.4xlarge - - g3.8xlarge - - g3.16xlarge - - p2.xlarge - - p2.8xlarge - - p2.16xlarge - - p3.2xlarge - - p3.8xlarge - - p3.16xlarge + - t3.nano + - t3.micro + - t3.small + - t3.medium + - t3.large + - t3.xlarge + - t3.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.8xlarge + - m5.12xlarge + - m5.16xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.12xlarge + - c5.18xlarge + - c5.24xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.8xlarge + - r5.12xlarge + - r5.16xlarge + - r5.24xlarge + - t3a.nano + - t3a.micro + - t3a.small + - t3a.medium + - t3a.large + - t3a.xlarge + - t3a.2xlarge + - m5a.large + - m5a.xlarge + - m5a.2xlarge + - m5a.4xlarge + - m5a.8xlarge + - m5a.12xlarge + - m5a.16xlarge + - m5a.24xlarge + - c5a.large + - c5a.xlarge + - c5a.2xlarge + - c5a.4xlarge + - c5a.9xlarge + - c5a.12xlarge + - c5a.18xlarge + - c5a.24xlarge + - r5a.large + - r5a.xlarge + - r5a.2xlarge + - r5a.4xlarge + - r5a.8xlarge + - r5a.12xlarge + - r5a.16xlarge + - r5a.24xlarge + - t4g.nano + - t4g.micro + - t4g.small + - t4g.medium + - t4g.large + - t4g.xlarge + - t4g.2xlarge + - m6g.large + - m6g.xlarge + - m6g.2xlarge + - m6g.4xlarge + - m6g.8xlarge + - m6g.12xlarge + - m6g.16xlarge + - m6g.24xlarge + - c6g.large + - c6g.xlarge + - c6g.2xlarge + - c6g.4xlarge + - c6g.9xlarge + - c6g.12xlarge + - c6g.18xlarge + - c6g.24xlarge + - r6g.large + - r6g.xlarge + - r6g.2xlarge + - r6g.4xlarge + - r6g.8xlarge + - r6g.12xlarge + - r6g.16xlarge + - r6g.24xlarge ConstraintDescription: Must be a valid Amazon EC2 instance type. - Default: t2.large + Default: m6g.large Description: The Amazon EC2 instance type for your web instances. Type: String WebSecurityGroup: Description: Select the web security group. Type: AWS::EC2::SecurityGroup::Id - DomainName: - AllowedPattern: ^$|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$ - Description: '[ Optional ] The main domain name of the Moodle site (e.g. moodle.example.edu). Leave empty to use the ALB DNS name for the Moodle site.' - Type: String - MoodleLocale: - Description: The main language used in the Moodle configuration wizard. - Type: String - Default: en LatestAmiId : Type : AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 + LatestArmAmiId : + Type : AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 + CodeArtifactS3BucketArn: + Type: "String" + Description: Code Artifact S3 Bucket Arn Conditions: - - NoSslCertificate: - !Equals [ False, !Ref SslCertificate ] NumberOfSubnets1: !Equals [ 1, !Ref NumberOfSubnets ] NumberOfSubnets2: !Equals [ 2, !Ref NumberOfSubnets ] NumberOfSubnets3: !Equals [ 3, !Ref NumberOfSubnets ] - NumberOfSubnets4: - !Equals [ 4, !Ref NumberOfSubnets ] - NumberOfSubnets5: - !Equals [ 5, !Ref NumberOfSubnets ] - NumberOfSubnets6: - !Equals [ 6, !Ref NumberOfSubnets ] Subnet0: !Or - !Condition NumberOfSubnets1 - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 Subnet1: !Or - !Condition NumberOfSubnets2 - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet2: !Or - - !Condition NumberOfSubnets3 - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet3: !Or - - !Condition NumberOfSubnets4 - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet4: !Or - - !Condition NumberOfSubnets5 - - !Condition NumberOfSubnets6 - Subnet5: !Condition NumberOfSubnets6 - NoDomainName: - !Equals [ '', !Ref DomainName ] + Subnet2: !Condition NumberOfSubnets3 + UsingGraviton2Ami: !Or + - !Equals ["t4",!Select [0, !Split [ "g.", !Ref WebInstanceType]]] + - !Equals ["c6",!Select [0, !Split [ "g.", !Ref WebInstanceType]]] + - !Equals ["m6",!Select [0, !Split [ "g.", !Ref WebInstanceType]]] + - !Equals ["r6",!Select [0, !Split [ "g.", !Ref WebInstanceType]]] - Resources: WebInstanceProfile: Type: AWS::IAM::InstanceProfile @@ -330,9 +191,12 @@ Resources: - ec2.amazonaws.com Action: - sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' + - 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeployLimited' Path: / Policies: - - PolicyName: logs + - PolicyName: MoodleCustomWebInstancePolicy PolicyDocument: Version: 2012-10-17 Statement: @@ -344,10 +208,6 @@ Resources: - logs:DescribeLogStreams Resource: - arn:aws:logs:*:*:* - - PolicyName: CompleteLifecycleAction - PolicyDocument: - Version: 2012-10-17 - Statement: - Effect: Allow Action: - autoscaling:CompleteLifecycleAction @@ -355,27 +215,39 @@ Resources: - autoscaling:DescribeLifecycleHooks Resource: - arn:aws:autoscaling:*:*:* - - PolicyName: SecretManager - PolicyDocument: - Version: 2012-10-17 - Statement: - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: - - !Ref MyRDSInstanceSecretArn + - !Ref RDSInstanceSecretArn + - Effect: Allow + Action: + - s3:* + Resource: + - !Ref CodeArtifactS3BucketArn + - !Join [ '', [ !Ref CodeArtifactS3BucketArn,'/*' ] ] + - Effect: Allow + Action: + - cloudformation:DescribeStackResources + - cloudformation:DescribeStackResource + Resource: + - '*' + Logs: Type: AWS::Logs::LogGroup DeletionPolicy: Retain Properties: RetentionInDays: 7 + WebAutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: - Cooldown: 60 + Cooldown: '60' HealthCheckGracePeriod: 120 HealthCheckType: ELB - LaunchConfigurationName: !Ref WebLaunchConfiguration + LaunchTemplate: + LaunchTemplateId: !Ref WebLaunchTemplate + Version: !GetAtt WebLaunchTemplate.LatestVersionNumber MaxSize: !Ref WebAsgMax MinSize: !Ref WebAsgMin Tags: @@ -391,19 +263,7 @@ Resources: !If [ NumberOfSubnets2, [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ] ], - !If - [ NumberOfSubnets3, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ], - !If - [ NumberOfSubnets4, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ] ], - !If - [ NumberOfSubnets5, - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ] ], - [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ], !Select [ 3, !Ref Subnet ], !Select [ 4, !Ref Subnet ], !Select [ 5, !Ref Subnet ] ] - ] - ] - ] + [ !Select [ 0, !Ref Subnet ], !Select [ 1, !Ref Subnet ], !Select [ 2, !Ref Subnet ] ] ] ] CreationPolicy: @@ -417,8 +277,9 @@ Resources: AdjustmentType: ChangeInCapacity AutoScalingGroupName: Ref: WebAutoScalingGroup - Cooldown: 300 + Cooldown: '300' ScalingAdjustment: 1 + CPUAlarmHigh: Type: AWS::CloudWatch::Alarm Properties: @@ -435,14 +296,16 @@ Resources: Value: !Ref WebAutoScalingGroup ComparisonOperator: GreaterThanThreshold MetricName: CPUUtilization + ScaleDownPolicy: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: Ref: WebAutoScalingGroup - Cooldown: 300 + Cooldown: '300' ScalingAdjustment: -1 + CPUAlarmLow: Type: AWS::CloudWatch::Alarm Properties: @@ -460,44 +323,16 @@ Resources: ComparisonOperator: LessThanThreshold MetricName: CPUUtilization - WebLaunchConfiguration: - Type: AWS::AutoScaling::LaunchConfiguration + WebLaunchTemplate: + Type: AWS::EC2::LaunchTemplate Metadata: AWS::CloudFormation::Init: configSets: deploy_webserver: - install_logs + - install_codedeploy - install_webserver - - install_moodle - - install_cacheclient - - install_opcache - start_webserver - install_webserver: - files: - /tmp/create_site_conf.sh: - content: !Sub | - #!/bin/bash -xe - amazon-linux-extras install -y php7.3 postgresql9.6 - yum install -y awslogs httpd php-pgsql php-gd php-soap php-intl php-mbstring php-xmlrpc php-zip - sed -i 's/memory_limit =.*/memory_limit = 2048M/' /etc/php.ini - if [ ! -f /etc/httpd/conf.d/moodle.conf ]; then - touch /etc/httpd/conf.d/moodle.conf - echo 'ServerName 127.0.0.1:80' >> /etc/httpd/conf.d/moodle.conf - echo 'DocumentRoot /var/www/moodle/html' >> /etc/httpd/conf.d/moodle.conf - echo '' >> /etc/httpd/conf.d/moodle.conf - echo ' Options Indexes FollowSymLinks' >> /etc/httpd/conf.d/moodle.conf - echo ' AllowOverride All' >> /etc/httpd/conf.d/moodle.conf - echo ' Require all granted' >> /etc/httpd/conf.d/moodle.conf - echo '' >> /etc/httpd/conf.d/moodle.conf - fi - mode: 000500 - owner: root - group: root - commands: - create_site_conf: - command: ./create_site_conf.sh - cwd: /tmp - ignoreErrors: false install_logs: packages: yum: @@ -543,7 +378,6 @@ Resources: log_group_name = ${Logs} log_stream_name = {instance_id}/httpd datetime_format = %d/%b/%Y:%H:%M:%S - mode: '000444' owner: root group: root @@ -565,178 +399,69 @@ Resources: enabled: 'true' ensureRunning: 'true' files: /etc/awslogs/awslogs.conf - install_aws_ini: commands: install_aws_ini: command: ./download_aws_ini.sh cwd: /tmp ignoreErrors: true - install_moodle: - files: - /tmp/config.php: - content: !Sub | - '2017-10-17', - 'region' => '${AWS::Region}', - ]); - - $secretName = '${MyRDSInstanceSecretArn}'; - - try { - $result = $client->getSecretValue([ - 'SecretId' => $secretName, - ]); - - } catch (AwsException $e) { - $error = $e->getAwsErrorCode(); - } - // Decrypts secret using the associated KMS CMK. - // Depending on whether the secret is a string or binary, one of these fields will be populated. - if (isset($result['SecretString'])) { - $secret = $result['SecretString']; - } - $CFG = new stdClass; - $CFG->dbtype = 'pgsql'; - $CFG->dblibrary = 'native'; - $CFG->dbhost = '${DatabaseClusterEndpointAddress}'; - $CFG->dbname = '${DatabaseName}'; - $CFG->dbuser = json_decode($secret)->{'username'}; - $CFG->dbpass = json_decode($secret)->{'password'}; - $CFG->prefix = 'mdl_'; - $CFG->lang = '${MoodleLocale}'; - $CFG->dboptions = array( - 'dbpersist' => false, - 'dbsocket' => false, - 'dbport' => '', - 'dbhandlesoptions' => false, - 'dbcollation' => 'utf8mb4_unicode_ci', - ); - // Hostname definition // - $hostname = '${DomainName}'; - if ($hostname == '') { - $hostwithprotocol = '${PublicAlbHostname}'; - } - else { - $hostwithprotocol = 'https://' . strtolower($hostname); - } - $CFG->wwwroot = strtolower($hostwithprotocol); - $CFG->sslproxy = (substr($hostwithprotocol,0,5)=='https' ? true : false); - // Moodledata location // - $CFG->dataroot = '/var/www/moodle/data'; - $CFG->tempdir = '/var/www/moodle/temp'; - $CFG->cachedir = '/var/www/moodle/cache'; - $CFG->localcachedir = '/var/www/moodle/local'; - $CFG->directorypermissions = 02777; - $CFG->admin = 'admin'; - // Configure Session Cache - $SessionEndpoint = '${ElastiCacheClusterEndpointAddress}'; - if ($SessionEndpoint != '') { - $CFG->dbsessions = false; - $CFG->session_handler_class = '\core\session\memcached'; - $CFG->session_memcached_save_path = $SessionEndpoint; - $CFG->session_memcached_prefix = 'memc.sess.key.'; - $CFG->session_memcached_acquire_lock_timeout = 120; - $CFG->session_memcached_lock_expire = 7100; - $CFG->session_memcached_lock_retry_sleep = 150; - } - //@error_reporting(E_ALL | E_STRICT); // NOT FOR PRODUCTION SERVERS! - //@ini_set('display_errors', '1'); // NOT FOR PRODUCTION SERVERS! - //$CFG->debug = (E_ALL | E_STRICT); // === DEBUG_DEVELOPER - NOT FOR PRODUCTION SERVERS! - //$CFG->debugdisplay = 1; - require_once(__DIR__ . '/lib/setup.php'); - // END OF CONFIG // - ?> - mode: '000755' - owner: root - group: root - - /tmp/install_moodle.sh: - content: !Sub | - #!/bin/bash -xe - wget -O /tmp/moodle.tgz https://download.moodle.org/download.php/direct/stable400/moodle-4.0.2.tgz - tar -xvzf /tmp/moodle.tgz --strip-components=1 -C /var/www/moodle/html/ - wget -O /var/www/moodle/html/lib/aws.phar https://docs.aws.amazon.com/aws-sdk-php/v3/download/aws.phar - cp /tmp/config.php /var/www/moodle/html/config.php - chown -R root:apache /var/www/moodle/html - chown -R apache:apache /var/www/moodle/local - - mode: 000500 - owner: root - group: root - commands: - install_moodle: - command: ./install_moodle.sh - cwd: /tmp - ignoreErrors: false - install_cacheclient: + install_codedeploy: packages: yum: - gcc-c++: [] + ruby: [] files: - /tmp/install_cacheclient.sh: + /tmp/install_codedeploy.sh: content: !Sub | #!/bin/bash -xe - - #Install memcached and then remove it. Memcached is not actually needed. We install amazon-elasticache-cluster-client.so instead. However Moodle does not detect memcached is installed. Therefore, this tricks Moodle into thinking it is installed. - sudo yum install -y php-pecl-memcached - sudo yum remove -y php-pecl-memcached - - wget -P /tmp/ https://elasticache-downloads.s3.amazonaws.com/ClusterClient/PHP-7.3/latest-64bit - tar -xf '/tmp/latest-64bit' - cp '/tmp/amazon-elasticache-cluster-client.so' /usr/lib64/php/modules/ - if [ ! -f /etc/php.d/50-memcached.ini ]; then - touch /etc/php.d/50-memcached.ini - fi - echo 'extension=/usr/lib64/php/modules/amazon-elasticache-cluster-client.so;' >> /etc/php.d/50-memcached.ini - echo 'extension=igbinary.so;' >> /etc/php.d/50-memcached.ini - - #update Moodle source to use DYNAMIC_CLIENT_MODE so Moodle can detect changes to the elasticache cluster membership - sed -i '/\$this->options\[Memcached::OPT_BUFFER_WRITES\] = \$bufferwrites;/a \ \ \ \ \ \ \ \ $this->options[Memcached::OPT_CLIENT_MODE] = Memcached::DYNAMIC_CLIENT_MODE;' /var/www/moodle/html/cache/stores/memcached/lib.php - + cd /home/ec2-user + wget https://aws-codedeploy-${AWS::Region}.s3.${AWS::Region}.amazonaws.com/latest/install + chmod +x ./install + ./install auto mode: 000500 owner: root group: root commands: - install_cacheclient: - command: ./install_cacheclient.sh + install_codedeploy: + command: ./install_codedeploy.sh cwd: /tmp - ignoreErrors: false - install_opcache: - packages: - yum: - php-opcache: [] + ignoreErrors: false + install_webserver: files: - /tmp/install_opcache.sh: - content: - !Sub | - #!/bin/bash -xe - # create hidden opcache directory locally & change owner to apache - if [ ! -d /var/www/.opcache ]; then - mkdir -p /var/www/.opcache - fi - #Ensure opcache is enabled and add settings recomended by moodle at https://docs.moodle.org/34/en/OPcache - sed -i 's/;opcache.file_cache=.*/opcache.file_cache=\/var\/www\/.opcache/' /etc/php.d/10-opcache.ini - sed -i 's/opcache.memory_consumption=.*/opcache.memory_consumption=512/' /etc/php.d/10-opcache.ini - sed -i 's/opcache.max_accelerated_files=.*/opcache.max_accelerated_files=8000/' /etc/php.d/10-opcache.ini - sed -i 's/;opcache.revalidate_freq=.*/opcache.revalidate_freq=300/' /etc/php.d/10-opcache.ini - sed -i 's/;opcache.use_cwd=.*/opcache.use_cwd=1/' /etc/php.d/10-opcache.ini - sed -i 's/;opcache.validate_timestamps=.*/opcache.validate_timestamps=1/' /etc/php.d/10-opcache.ini - sed -i 's/;opcache.save_comments=.*/opcache.save_comments=1/' /etc/php.d/10-opcache.ini - sed -i 's/;opcache.enable_file_override=.*/opcache.enable_file_override=60/' /etc/php.d/10-opcache.ini + /tmp/status.txt: + content: !Sub | + Health Check + mode: '000644' + owner: root + group: root + /tmp/create_site_conf.sh: + content: !Sub | + #!/bin/bash -xe + amazon-linux-extras install -y php8.0 + amazon-linux-extras enable php8.0 + + yum install -y httpd gcc-c++ + yum install -y php-gd php-soap php-intl php-mbstring php-xml php-zip php-opcache php-sodium php-fpm + + sed -i 's/memory_limit =.*/memory_limit = 4096M/' /etc/php.ini + if [ ! -f /etc/httpd/conf.d/moodle.conf ]; then + touch /etc/httpd/conf.d/moodle.conf + echo 'ServerName 127.0.0.1:80' >> /etc/httpd/conf.d/moodle.conf + echo 'DocumentRoot /var/www/moodle/html' >> /etc/httpd/conf.d/moodle.conf + echo '' >> /etc/httpd/conf.d/moodle.conf + echo ' Options Indexes FollowSymLinks' >> /etc/httpd/conf.d/moodle.conf + echo ' AllowOverride All' >> /etc/httpd/conf.d/moodle.conf + echo ' Require all granted' >> /etc/httpd/conf.d/moodle.conf + echo '' >> /etc/httpd/conf.d/moodle.conf + fi + + cp /tmp/status.txt /var/www/moodle/html/status.txt mode: 000500 owner: root group: root commands: - install_opcache: - command: ./install_opcache.sh + create_site_conf: + command: ./create_site_conf.sh cwd: /tmp ignoreErrors: false start_webserver: @@ -746,38 +471,43 @@ Resources: enabled: true ensureRunning: true Properties: - IamInstanceProfile: !Ref WebInstanceProfile - ImageId: !Ref LatestAmiId - InstanceMonitoring: true - InstanceType: !Ref WebInstanceType - KeyName: !Ref EC2KeyName - SecurityGroups: - - !Ref WebSecurityGroup - UserData: - "Fn::Base64": - !Sub | - #!/bin/bash -xe - yum update -y - - #Create directory structure - mkdir -p /var/www/moodle/html - mkdir -p /var/www/moodle/data - mkdir -p /var/www/moodle/cache - mkdir -p /var/www/moodle/temp - mkdir -p /var/www/moodle/local - - #Mount shared storage - echo "${ElasticFileSystem}.efs.${AWS::Region}.amazonaws.com:/data /var/www/moodle/data nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 0 0" >> /etc/fstab - echo "${ElasticFileSystem}.efs.${AWS::Region}.amazonaws.com:/cache /var/www/moodle/cache nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 0 0" >> /etc/fstab - echo "${ElasticFileSystem}.efs.${AWS::Region}.amazonaws.com:/temp /var/www/moodle/temp nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 0 0" >> /etc/fstab - mount -a - - #Run CloudFormation Init Scripts - /opt/aws/bin/cfn-init --configsets deploy_webserver --verbose --stack ${AWS::StackName} --resource WebLaunchConfiguration --region ${AWS::Region} - /opt/aws/bin/cfn-signal --exit-code $? --stack ${AWS::StackName} --resource WebAutoScalingGroup --region ${AWS::Region} + LaunchTemplateData: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + DeleteOnTermination: true + VolumeSize: 10 + VolumeType: gp3 + IamInstanceProfile: + Arn: !GetAtt WebInstanceProfile.Arn + ImageId: !If [UsingGraviton2Ami, !Ref LatestArmAmiId, !Ref LatestAmiId] + InstanceType: !Ref WebInstanceType + Monitoring: + Enabled: true + KeyName: !Ref EC2KeyName + SecurityGroupIds: + - !Ref WebSecurityGroup + UserData: + Fn::Base64: + !Sub | + #!/bin/bash -xe + sudo systemctl enable amazon-ssm-agent + sudo systemctl start amazon-ssm-agent + sudo systemctl status amazon-ssm-agent + + yum update -y + + #Create directory structure + mkdir -p /var/www/moodle/html + mkdir -p /var/www/moodle/data + mkdir -p /var/www/moodle/cache + mkdir -p /var/www/moodle/temp + mkdir -p /var/www/moodle/local + #Run CloudFormation Init Scripts + /opt/aws/bin/cfn-init --configsets deploy_webserver --verbose --stack ${AWS::StackName} --resource WebLaunchTemplate --region ${AWS::Region} + /opt/aws/bin/cfn-signal --exit-code $? --stack ${AWS::StackName} --resource WebAutoScalingGroup --region ${AWS::Region} Outputs: - - Opcachestatus: - Value: !Join [ '', [ !Ref PublicAlbHostname, '/opcache-instanceid.php' ] ] + WebAutoScalingGroupName: + Value: !Ref WebAutoScalingGroup diff --git a/templates/05-codepipeline.yaml b/templates/05-codepipeline.yaml new file mode 100644 index 0000000..22f3af4 --- /dev/null +++ b/templates/05-codepipeline.yaml @@ -0,0 +1,176 @@ +Parameters: + CodeCommitRepoName: + Type: "String" + Default: "Moodle Repo" + Description: Moodle CodeCommit repo name + CodeCommitRepoArn: + Type: "String" + Description: Moodle CodeCommit repo Arn + BranchName: + Type: "String" + Default: "main" + Description: repository's branch name + AppAutoScalingGroupName: + Type: "String" + Description: Name of Autoscaling group for Moodle Web app. + MoodleAppTargetGroupName: + Type: "String" + Description: Name of Target group attached to load balancer for web application servers. + CodeArtifactS3BucketName: + Type: "String" + Description: Code Artifact S3 Bucket Name + CodeArtifactS3BucketArn: + Type: "String" + Description: Code Artifact S3 Bucket ARN + ProjectName: + AllowedPattern: ^([a-zA-Z0-9]*)$ + Default: App + Description: The Moodle Project Name + Type: String + +Resources: + # AWS CodePipeline to deploy source code from CodeCommit repo to autoscaling groups for Moodle Web apps. + + # This role is assumed by the CodePipeline service itself. + MoodleCodeDeployServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - codedeploy.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole' + Path: / + Policies: + - PolicyName: MoodleCodeDeployCustomPolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - iam:PassRole + - ec2:CreateTags + - ec2:RunInstances + Resource: + - '*' + + MoodleDeployApp: + Type: 'AWS::CodeDeploy::Application' + Properties: + ApplicationName: !Sub '${ProjectName}-DeployApp' + + MoodleDeploymentGroup: + Type: 'AWS::CodeDeploy::DeploymentGroup' + DependsOn: MoodleDeployApp + Properties: + ApplicationName: !Ref MoodleDeployApp + DeploymentGroupName: !Sub '${ProjectName}-AppDG' + ServiceRoleArn: !GetAtt MoodleCodeDeployServiceRole.Arn + DeploymentStyle: + DeploymentOption: BLUE_GREEN #IN_PLACE + DeploymentOption: WITH_TRAFFIC_CONTROL #WITHOUT_TRAFFIC_CONTROL + AutoScalingGroups: + - !Ref AppAutoScalingGroupName + LoadBalancerInfo: + TargetGroupInfoList: + - Name: !Ref MoodleAppTargetGroupName + + MoodleWebAppASGNameParam: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub '/Moodle/${ProjectName}/WebAppASGName' + Type: String + Value: !Ref AppAutoScalingGroupName + Description: SSM Parameter for Moodle WebApp Auto scaling group + + #This role is for Moodle pipeline to perform ci-cd tasks. + # !TODO to limit this role from full access. + MoodlePipelineRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - codepipeline.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess' + Path: / + Policies: + - PolicyName: MoodlePipelineCustomPolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - codedeploy:* + Resource: + - arn:aws:codedeploy:*:*:application:* + - arn:aws:codedeploy:*:*:deploymentgroup:*/* + - arn:aws:codedeploy:*:*:instance:* + - arn:aws:codedeploy:*:*:deploymentconfig:* + - Effect: Allow + Action: + - s3:* + Resource: + - !Ref CodeArtifactS3BucketArn + - !Join [ '', [ !Ref CodeArtifactS3BucketArn,'/*' ] ] + - Effect: Allow + Action: + - codecommit:* + Resource: + - !Ref CodeCommitRepoArn + + # The CI/CD pipeline stitching the full mechanism together + MoodleAppPipeline: + Type: AWS::CodePipeline::Pipeline + Properties: + Name: !Sub '${ProjectName}-Pipeline' + RoleArn: !GetAtt MoodlePipelineRole.Arn + Stages: + - Actions: + # Initiate Pipeline from CodeCommit + - ActionTypeId: + Version: '1' + Provider: CodeCommit + Category: Source + Owner: AWS + OutputArtifacts: + - Name: source + InputArtifacts: [] + Name: source + Configuration: + RepositoryName: !Ref CodeCommitRepoName + BranchName: !Ref BranchName + PollForSourceChanges: 'false' + RunOrder: 1 + Namespace: SourceVars + Name: Initiate + - Actions: + # Creating CodeDeploy for Deploying Moodle codebase from CodeCommit repo + - ActionTypeId: + Category: Deploy + Owner: AWS + Provider: CodeDeploy + Version: "1" + InputArtifacts: + - Name: source + Name: DeployOnASG + Configuration: + ApplicationName: !Ref MoodleDeployApp + DeploymentGroupName: !Ref MoodleDeploymentGroup + RunOrder: 1 + Name: Deploy + ArtifactStore: + Location: !Ref CodeArtifactS3BucketName + Type: S3 + \ No newline at end of file diff --git a/templates/05-route53.yaml b/templates/05-route53.yaml index bad6837..ff5db23 100644 --- a/templates/05-route53.yaml +++ b/templates/05-route53.yaml @@ -20,7 +20,7 @@ Metadata: DnsHostId: default: DNS Host Id DomainName: - default: Site Domain + default: Domain Name of the Moodle site HostedZoneName: default: Hosted Zone Name @@ -28,11 +28,11 @@ Parameters: DnsEndpoint: AllowedPattern: ^(?!http)(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$ - Description: The DNS endpoint - CloudFront DNS if using CloudFront else Public ELB DNS name. + Description: The DNS endpoint - CloudFront DNS if using CloudFront else Public ALB DNS name. Type: String DnsHostId: - AllowedPattern: ^[A-Z0-9]{14}$ - Description: The DNS host zone id - 'Z2FDTNDATAQYW2' if using CloudFront else Public ELB host zone id. + AllowedPattern: ^[A-Z0-9]+$ + Description: The DNS host zone id - 'Z2FDTNDATAQYW2' if using CloudFront else Public ALB host zone id. Type: String HostedZoneName: AllowedPattern: ^(?!http)(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$