AWSTemplateFormatVersion: 2010-09-09 Description: >- Creates a Wordpress container using ECS, ELB Application load balancer and other resources. **WARNING** You will be billed for the AWS resources used if you create a stack from this template. Metadata: LICENSE: Apache License Version 2.0 cfn-lint: config: ignore_checks: # Check for QSID - E9008 Parameters: DomainName: Description: >- (Optional) Domain name for the web site. It must be an existing, publicly resolvable domain. Type: String Default: '' ConstraintDescription: Must be a valid domain name. CertificateArn: Description: (Optional) The ARN of the SSL certificate to use for the load balancer. Type: String Default: '' HostedZoneID: Description: >- (Optional) Route 53 Hosted Zone ID of the domain name. If left blank route 53 will not be configured and DNS must be setup manually. If you specify this, you must also specify a Domain name Type: String Default: '' MaxLength: '32' VPCID: Type: 'AWS::EC2::VPC::Id' Description: 'ID of the VPC (e.g., vpc-0343606e).' PublicSubnet1ID: Type: 'AWS::EC2::Subnet::Id' Description: Subnet Id for public subnet 1 located in Availability Zone 1 AllowedPattern: '^subnet-[a-zA-Z0-9]*$' ConstraintDescription: Invalid subnet id PublicSubnet2ID: Type: 'AWS::EC2::Subnet::Id' Description: Subnet Id for public subnet 2 located in Availability Zone 2 AllowedPattern: '^subnet-[a-zA-Z0-9]*$' ConstraintDescription: Invalid subnet id PublicSubnet3ID: Type: String Description: Subnet Id for public subnet 3 located in Availability Zone 3 Default: '' PrivateSubnet1AID: Type: 'AWS::EC2::Subnet::Id' Description: Subnet Id for private subnet 1 located in Availability Zone 1 AllowedPattern: '^subnet-[a-zA-Z0-9]*$' ConstraintDescription: Invalid subnet id PrivateSubnet2AID: Type: 'AWS::EC2::Subnet::Id' Description: Subnet Id for private subnet 2 located in Availability Zone 2 AllowedPattern: '^subnet-[a-zA-Z0-9]*$' ConstraintDescription: Invalid subnet id PrivateSubnet3AID: Type: String Description: Subnet Id for private subnet 3 located in Availability Zone 3 Default: '' FileSystemId: Type: String Description: EFS file system Id ContainerSecurityGroupId: Type: 'AWS::EC2::SecurityGroup::Id' Description: Container security group Id AllowedPattern: '^sg-[a-zA-Z0-9]*$' ConstraintDescription: Please select ContainerSecurityGroupId security group. LoadBalancerSecurityGroupId: Type: 'AWS::EC2::SecurityGroup::Id' Description: Application load balancer security group Id AllowedPattern: '^sg-[a-zA-Z0-9]*$' ConstraintDescription: Please select LoadBalancerSecurityGroupId security group. DBEndpoint: Type: String Description: The connection endpoint of the database. DBName: Type: String Default: myDatabase Description: The name of your database. If you don't provide a name, then Amazon RDS won't create a database in this DB cluster. AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*' MaxLength: '64' MinLength: '5' DBUsername: NoEcho: 'true' Description: Username for MySQL database access Type: String MinLength: '1' MaxLength: '16' AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*' ConstraintDescription: must begin with a letter and contain only alphanumeric characters. DBPassword: NoEcho: 'true' Description: Password MySQL database access Type: String MinLength: '8' MaxLength: '41' AllowedPattern: '[a-zA-Z0-9]*' ConstraintDescription: must contain only alphanumeric characters. ContainerCpu: Type: String Default: '256' Description: The number of cpu units used by the task. AllowedValues: - '256' - '512' - '1024' - '2048' - '4096' ContainerMemory: Type: String Default: 0.5GB Description: The amount (in GiB) of memory used by the task. AllowedValues: - '0.5GB' - '1GB' - '2GB' - '3GB' - '4GB' - '5GB' - '6GB' - '7GB' - '8GB' - '12GB' - '16GB' - '30GB' ContainerDesiredCount: Type: Number Default: '2' Description: Desired number of WordPress containers. ContainerImage: Type: String Default: 'docker.io/wordpress:latest' Description: The image used to start a container. By default uses Wordpress official docker image. ContainerMaxCount: Type: Number Default: '2' Description: Maximum number of WordPress containers in the Auto Scaling group. ContainerMinCount: Type: Number Default: '1' Description: Minimum number of WordPress containers in the Auto Scaling group. Conditions: 3AZDeployment: !And - !Not - !Equals - !Ref PrivateSubnet3AID - '' - !Not - !Equals - !Ref PublicSubnet3ID - '' UseSSL: !Not - !Equals - !Ref CertificateArn - '' AddDNSRecord: !And - !Not - !Equals - !Ref DomainName - '' - !Not - !Equals - !Ref HostedZoneID - '' Rules: CorrectCpuMemConfigRule: Assertions: - Assert: !Or - !And - !Equals - '256' - !Ref ContainerCpu - !Contains - ['0.5GB', '1GB', '2GB'] - !Ref ContainerMemory - !And - !Equals - '512' - !Ref ContainerCpu - !Contains - ['1GB', '2GB', '3GB', '4GB'] - !Ref ContainerMemory - !And - !Equals - '1024' - !Ref ContainerCpu - !Contains - ['2GB','3GB', '4GB', '5GB', '6GB', '7GB', '8GB'] - !Ref ContainerMemory - !And - !Equals - '2048' - !Ref ContainerCpu - !Contains - ['4GB', '5GB', '6GB', '7GB', '8GB', '12GB', '16GB'] - !Ref ContainerMemory - !And - !Equals - '4096' - !Ref ContainerCpu - !Contains - ['8GB', '12GB', '16GB', '30GB'] - !Ref ContainerMemory Resources: DBPasswordSecret: Type: 'AWS::SecretsManager::Secret' Properties: SecretString: !Ref DBPassword Tags: - Key: Name Value: WORDPRESS_DB_PASSWORD WordpressAccessPoint: Type: 'AWS::EFS::AccessPoint' Properties: FileSystemId: !Ref FileSystemId RootDirectory: CreationInfo: OwnerGid: '708798' OwnerUid: '7987987' Permissions: '0755' Path: /wp-content Cluster: Type: 'AWS::ECS::Cluster' ContainerExecutionRole: Type: 'AWS::IAM::Role' Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - !Sub >- arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy Policies: - PolicyName: AccessDBAdminUserPasswordSecret # inline policy name PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'secretsmanager:GetSecretValue' Resource: !Ref DBPasswordSecret TaskRole: Type: 'AWS::IAM::Role' Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: 'sts:AssumeRole' Policies: - PolicyName: AccessDBAdminUserPasswordSecret PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'secretsmanager:GetSecretValue' Resource: !Ref DBPasswordSecret TaskDefinition: Type: 'AWS::ECS::TaskDefinition' Properties: NetworkMode: awsvpc RequiresCompatibilities: - FARGATE Cpu: !Ref ContainerCpu Memory: !Ref ContainerMemory ExecutionRoleArn: !GetAtt ContainerExecutionRole.Arn TaskRoleArn: !Ref TaskRole Volumes: - Name: efs-wp-content EFSVolumeConfiguration: AuthorizationConfig: AccessPointId: !Ref WordpressAccessPoint IAM: DISABLED FilesystemId: !Ref FileSystemId TransitEncryption: ENABLED ContainerDefinitions: - Name: !Join - '-' - - !Ref 'AWS::StackName' - Container Image: !Ref ContainerImage PortMappings: - ContainerPort: 80 LogConfiguration: LogDriver: awslogs Options: awslogs-region: !Ref 'AWS::Region' awslogs-group: !Ref TaskLogGroup awslogs-stream-prefix: !Ref 'AWS::StackName' MountPoints: - SourceVolume: 'efs-wp-content' ContainerPath: '/var/www/html/wp-content' ReadOnly: false Secrets: - Name: WORDPRESS_DB_PASSWORD ValueFrom: !Ref DBPasswordSecret Environment: - Name: WORDPRESS_DB_HOST Value: !Ref DBEndpoint - Name: WORDPRESS_DB_NAME Value: !Ref DBName - Name: WORDPRESS_DB_USER Value: !Ref DBUsername - Name: WORDPRESS_CONFIG_EXTRA Value: !Sub - >- define('WP_HOME','${SCHEME}${DOMAIN}'); define('WP_SITEURL','${SCHEME}${DOMAIN}'); define('CONCATENATE_SCRIPTS',false); define('WPINV_USE_PHP_SESSIONS',false); define('FS_METHOD','direct'); - SCHEME: !If - UseSSL - 'https://' - 'http://' DOMAIN: !If - AddDNSRecord - !Ref DomainName - !GetAtt ApplicationLoadBalancer.DNSName Service: Type: 'AWS::ECS::Service' DependsOn: - ALBListenerHTTP Properties: ServiceName: !Join - '-' - - !Ref 'AWS::StackName' - Service Cluster: !Ref Cluster TaskDefinition: !Ref TaskDefinition DeploymentConfiguration: MinimumHealthyPercent: 100 MaximumPercent: 200 DesiredCount: !Ref ContainerDesiredCount HealthCheckGracePeriodSeconds: 200 LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED Subnets: !If - 3AZDeployment - - !Ref PrivateSubnet1AID - !Ref PrivateSubnet2AID - !Ref PrivateSubnet3AID - - !Ref PrivateSubnet1AID - !Ref PrivateSubnet2AID SecurityGroups: - !Ref ContainerSecurityGroupId LoadBalancers: - ContainerName: !Join - '-' - - !Ref 'AWS::StackName' - Container ContainerPort: 80 TargetGroupArn: !Ref ALBTargetGroup ApplicationLoadBalancer: Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' Properties: Subnets: !If - 3AZDeployment - - !Ref PublicSubnet1ID - !Ref PublicSubnet2ID - !Ref PublicSubnet3ID - - !Ref PublicSubnet1ID - !Ref PublicSubnet2ID SecurityGroups: - !Ref LoadBalancerSecurityGroupId LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds Value: '60' Scheme: internet-facing ALBTargetGroup: Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' Properties: VpcId: !Ref VPCID HealthCheckIntervalSeconds: 30 HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 2 HealthCheckPort: '80' HealthCheckProtocol: HTTP Port: 80 Matcher: # Adding 301, 302 to ignore 'Moved Permanently' and 'Found' http errors; caused by wordpress setup page. HttpCode: '200,301,302' Protocol: HTTP UnhealthyThresholdCount: 5 TargetType: ip TargetGroupAttributes: - Key: stickiness.enabled Value: 'true' - Key: stickiness.type Value: lb_cookie - Key: stickiness.lb_cookie.duration_seconds Value: '30' - Key: deregistration_delay.timeout_seconds Value: '60' ALBListenerHTTP: Type: 'AWS::ElasticLoadBalancingV2::Listener' Properties: DefaultActions: - !If - UseSSL - Type: redirect RedirectConfig: Host: '#{host}' Path: '/#{path}' Port: '443' Protocol: HTTPS StatusCode: HTTP_301 - Type: forward TargetGroupArn: !Ref ALBTargetGroup LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 80 Protocol: HTTP ALBListenerHTTPS: Type: 'AWS::ElasticLoadBalancingV2::Listener' Condition: UseSSL Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref ALBTargetGroup LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 443 Protocol: HTTPS Certificates: - CertificateArn: !Ref CertificateArn Route53RecordSet: Condition: AddDNSRecord Type: 'AWS::Route53::RecordSet' Properties: Type: A Name: !Ref DomainName AliasTarget: HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt ApplicationLoadBalancer.DNSName HostedZoneId: !Ref HostedZoneID AutoScalingRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - !Sub >- arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole TaskLogGroup: Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Join - '' - - /ecs/ - !Ref 'AWS::StackName' AutoScalingTarget: Type: 'AWS::ApplicationAutoScaling::ScalableTarget' Properties: MaxCapacity: !Ref ContainerMaxCount MinCapacity: !Ref ContainerMinCount ResourceId: !Join - / - - service - !Ref Cluster - !GetAtt Service.Name ScalableDimension: 'ecs:service:DesiredCount' ServiceNamespace: ecs RoleARN: !GetAtt AutoScalingRole.Arn AutoScalingPolicy: Type: 'AWS::ApplicationAutoScaling::ScalingPolicy' Properties: PolicyName: !Join - '' - - !Ref 'AWS::StackName' - AutoScalingPolicy PolicyType: TargetTrackingScaling ScalingTargetId: !Ref AutoScalingTarget TargetTrackingScalingPolicyConfiguration: PredefinedMetricSpecification: PredefinedMetricType: ECSServiceAverageCPUUtilization ScaleInCooldown: 10 ScaleOutCooldown: 10 TargetValue: 75 Outputs: ApplicationURL: Condition: AddDNSRecord Description: The URL of the Application. Value: !Join - '' - - !If - UseSSL - 'https://' - 'http://' - !Ref DomainName ApplicationLoadBalancerDNSName: Description: The URL of the ELB. Point your domain to it by using a CNAME/ALIAS record. Value: !Join - '' - - !If - UseSSL - 'https://' - 'http://' - !GetAtt ApplicationLoadBalancer.DNSName ApplicationLoadBalancerArn: Description: The Amazon Resource Name (ARN) of the application load balancer. Value: !Ref ApplicationLoadBalancer DBPasswordSecret: Description: The Amazon Resource Name (ARN) of the DBPassword SSM secret. Value: !Ref DBPasswordSecret Cluster: Description: The name of your ECS cluster. Value: !Ref Cluster TaskDefinition: Description: The Amazon Resource Name (ARN) of the ECS task definition. Value: !Ref TaskDefinition