--- AWSTemplateFormatVersion: 2010-09-09 Description: Reference Architecture to host Moodle on AWS - Creates Moodle web Auto Scaling group Parameters: RDSInstanceSecretArn: Type: String EC2KeyName: AllowedPattern: ^([a-zA-Z0-9 @.`~!#$%^&*()_+,\\-])*$ 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 NumberOfSubnets: AllowedValues: - 1 - 2 - 3 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 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: 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 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 WebSecurityGroup: Description: Select the web security group. Type: AWS::EC2::SecurityGroup::Id 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: 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 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 Properties: Path: / Roles: - !Ref WebInstanceRole WebInstanceRole: 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' - 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeployLimited' Path: / Policies: - PolicyName: MoodleCustomWebInstancePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - logs:DescribeLogStreams Resource: - arn:aws:logs:*:*:* - Effect: Allow Action: - autoscaling:CompleteLifecycleAction - autoscaling:DescribeAutoScalingInstances - autoscaling:DescribeLifecycleHooks Resource: - arn:aws:autoscaling:*:*:* - Effect: Allow Action: - secretsmanager:GetSecretValue Resource: - !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' HealthCheckGracePeriod: 120 HealthCheckType: ELB LaunchTemplate: LaunchTemplateId: !Ref WebLaunchTemplate Version: !GetAtt WebLaunchTemplate.LatestVersionNumber MaxSize: !Ref WebAsgMax MinSize: !Ref WebAsgMin Tags: - Key: Name Value: !Join [ '', [ 'Web ASG / ', !Ref 'AWS::StackName' ] ] PropagateAtLaunch: true TargetGroupARNs: - !Ref PublicAlbTargetGroupArn 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: !Ref WebAsgMin Timeout: PT15M ScaleUpPolicy: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: Ref: WebAutoScalingGroup Cooldown: '300' ScalingAdjustment: 1 CPUAlarmHigh: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 3 Statistic: Average Threshold: 75 AlarmDescription: Alarm if CPU too high Period: 60 AlarmActions: - Ref: ScaleUpPolicy Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref WebAutoScalingGroup ComparisonOperator: GreaterThanThreshold MetricName: CPUUtilization ScaleDownPolicy: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: Ref: WebAutoScalingGroup Cooldown: '300' ScalingAdjustment: -1 CPUAlarmLow: Type: AWS::CloudWatch::Alarm Properties: EvaluationPeriods: 3 Statistic: Average Threshold: 25 AlarmDescription: Alarm if CPU too low Period: 60 AlarmActions: - Ref: ScaleDownPolicy Namespace: AWS/EC2 Dimensions: - Name: AutoScalingGroupName Value: !Ref WebAutoScalingGroup ComparisonOperator: LessThanThreshold MetricName: CPUUtilization WebLaunchTemplate: Type: AWS::EC2::LaunchTemplate Metadata: AWS::CloudFormation::Init: configSets: deploy_webserver: - install_logs - install_codedeploy - install_webserver - start_webserver install_logs: packages: yum: awslogs: [] files: /etc/awslogs/awslogs.conf: content: !Sub | [general] state_file= /var/awslogs/state/agent-state [/var/log/cloud-init.log] file = /var/log/cloud-init.log log_group_name = ${Logs} log_stream_name = {instance_id}/cloud-init.log datetime_format = [/var/log/cloud-init-output.log] file = /var/log/cloud-init-output.log log_group_name = ${Logs} log_stream_name = {instance_id}/cloud-init-output.log datetime_format = [/var/log/cfn-init.log] file = /var/log/cfn-init.log log_group_name = ${Logs} log_stream_name = {instance_id}/cfn-init.log datetime_format = [/var/log/cfn-hup.log] file = /var/log/cfn-hup.log log_group_name = ${Logs} log_stream_name = {instance_id}/cfn-hup.log datetime_format = [/var/log/cfn-wire.log] file = /var/log/cfn-wire.log log_group_name = ${Logs} log_stream_name = {instance_id}/cfn-wire.log datetime_format = [/var/log/httpd] file = /var/log/httpd/* log_group_name = ${Logs} log_stream_name = {instance_id}/httpd datetime_format = %d/%b/%Y:%H:%M:%S mode: '000444' owner: root group: root /etc/awslogs/awscli.conf: content: !Sub | [plugins] cwlogs = cwlogs [default] region = ${AWS::Region} mode: '000444' owner: root group: root commands: 01_create_state_directory: command: mkdir -p /var/awslogs/state services: sysvinit: awslogsd: 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_codedeploy: packages: yum: ruby: [] files: /tmp/install_codedeploy.sh: content: !Sub | #!/bin/bash -xe 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_codedeploy: command: ./install_codedeploy.sh cwd: /tmp ignoreErrors: false install_webserver: files: /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: create_site_conf: command: ./create_site_conf.sh cwd: /tmp ignoreErrors: false start_webserver: services: sysvinit: httpd: enabled: true ensureRunning: true Properties: 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: WebAutoScalingGroupName: Value: !Ref WebAutoScalingGroup