20.Wordpress Ec2 2
1-Click Deployment
Description: Animals4Life base VPC Template + Public Instance
Parameters:
LatestAmiId:
Description: AMI for EC2 (default is latest AmaLinux2023)
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.16.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: a4l-vpc1
IPv6CidrBlock:
Type: AWS::EC2::VPCCidrBlock
Properties:
VpcId: !Ref VPC
AmazonProvidedIpv6CidrBlock: true
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: A4L-vpc1-igw
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
RouteTableWeb:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: A4L-vpc1-rt-web
RouteTableWebDefaultIPv4:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref RouteTableWeb
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
RouteTableWebDefaultIPv6:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref RouteTableWeb
DestinationIpv6CidrBlock: '::/0'
GatewayId: !Ref InternetGateway
RouteTableAssociationWebA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetWEBA
RouteTableId: !Ref RouteTableWeb
RouteTableAssociationWebB:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetWEBB
RouteTableId: !Ref RouteTableWeb
RouteTableAssociationWebC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SubnetWEBC
RouteTableId: !Ref RouteTableWeb
SubnetReservedA:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 0
- !GetAZs ''
CidrBlock: 10.16.0.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 00::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-reserved-A
SubnetReservedB:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 1
- !GetAZs ''
CidrBlock: 10.16.64.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 04::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-reserved-B
SubnetReservedC:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 2
- !GetAZs ''
CidrBlock: 10.16.128.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 08::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-reserved-C
SubnetDBA:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 0
- !GetAZs ''
CidrBlock: 10.16.16.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 01::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-db-A
SubnetDBB:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 1
- !GetAZs ''
CidrBlock: 10.16.80.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 05::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-db-B
SubnetDBC:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 2
- !GetAZs ''
CidrBlock: 10.16.144.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 09::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-db-C
SubnetAPPA:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 0
- !GetAZs ''
CidrBlock: 10.16.32.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 02::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-app-A
SubnetAPPB:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 1
- !GetAZs ''
CidrBlock: 10.16.96.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 06::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-app-B
SubnetAPPC:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 2
- !GetAZs ''
CidrBlock: 10.16.160.0/20
AssignIpv6AddressOnCreation: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 0A::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-app-C
SubnetWEBA:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 0
- !GetAZs ''
CidrBlock: 10.16.48.0/20
MapPublicIpOnLaunch: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 03::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-web-A
SubnetWEBB:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 1
- !GetAZs ''
CidrBlock: 10.16.112.0/20
MapPublicIpOnLaunch: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 07::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-web-B
SubnetWEBC:
Type: AWS::EC2::Subnet
DependsOn: IPv6CidrBlock
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 2
- !GetAZs ''
CidrBlock: 10.16.176.0/20
MapPublicIpOnLaunch: true
Ipv6CidrBlock: !Sub
- ${VpcPart}${SubnetPart}
- SubnetPart: 0B::/64
VpcPart: !Select
- 0
- !Split
- 00::/56
- !Select
- 0
- !GetAtt VPC.Ipv6CidrBlocks
Tags:
- Key: Name
Value: sn-web-C
IPv6WorkaroundSubnetWEBA:
Type: Custom::SubnetModify
Properties:
ServiceToken: !GetAtt IPv6WorkaroundLambda.Arn
SubnetId: !Ref SubnetWEBA
IPv6WorkaroundSubnetWEBB:
Type: Custom::SubnetModify
Properties:
ServiceToken: !GetAtt IPv6WorkaroundLambda.Arn
SubnetId: !Ref SubnetWEBB
IPv6WorkaroundSubnetWEBC:
Type: Custom::SubnetModify
Properties:
ServiceToken: !GetAtt IPv6WorkaroundLambda.Arn
SubnetId: !Ref SubnetWEBC
IPv6WorkaroundRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub ipv6-fix-logs-${AWS::StackName}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- PolicyName: !Sub ipv6-fix-modify-${AWS::StackName}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:ModifySubnetAttribute
Resource: '*'
IPv6WorkaroundLambda:
Type: AWS::Lambda::Function
Properties:
Handler: index.lambda_handler
Code:
#import cfnresponse below required to send respose back to CFN
ZipFile: !Sub |
import cfnresponse
import boto3
def lambda_handler(event, context):
if event['RequestType'] is 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS)
return
responseValue = event['ResourceProperties']['SubnetId']
ec2 = boto3.client('ec2', region_name='${AWS::Region}')
ec2.modify_subnet_attribute(AssignIpv6AddressOnCreation={
'Value': True
},
SubnetId=responseValue)
responseData = {}
responseData['SubnetId'] = responseValue
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
Runtime: python3.9
Role: !GetAtt IPv6WorkaroundRole.Arn
Timeout: 30
PublicEC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
IamInstanceProfile: !Ref SessionManagerInstanceProfile
SubnetId: !Ref SubnetWEBA
SecurityGroupIds:
- !Ref InstanceSecurityGroup
Tags:
- Key: Name
Value: A4L-PublicEC2
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: Enable SSH access via port 22 IPv4 & v6
SecurityGroupIngress:
- Description: Allow SSH IPv4 IN
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
- Description: Allow HTTP IPv4 IN
IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
- Description: Allow SSH IPv6 IN
IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIpv6: '::/0'
SessionManagerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ssm:DescribeAssociation
- ssm:GetDeployablePatchSnapshotForInstance
- ssm:GetDocument
- ssm:DescribeDocument
- ssm:GetManifest
- ssm:GetParameter
- ssm:GetParameters
- ssm:ListAssociations
- ssm:ListInstanceAssociations
- ssm:PutInventory
- ssm:PutComplianceItems
- ssm:PutConfigurePackageResult
- ssm:UpdateAssociationStatus
- ssm:UpdateInstanceAssociationStatus
- ssm:UpdateInstanceInformation
Resource: '*'
- Effect: Allow
Action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
Resource: '*'
- Effect: Allow
Action:
- ec2messages:AcknowledgeMessage
- ec2messages:DeleteMessage
- ec2messages:FailMessage
- ec2messages:GetEndpoint
- ec2messages:GetMessages
- ec2messages:SendReply
Resource: '*'
SessionManagerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref SessionManagerRole
Outputs:
a4lvpc1:
Description: Animals4Life VPC1_ID
Value: !Ref VPC
Export:
Name: a4l-vpc1
a4lvpc1subnetweba:
Description: Animals4Life VPC1 SubnetWEBA
Value: !Ref SubnetWEBA
Export:
Name: a4l-vpc1-subnet-weba
a4lvpc1subnetwebb:
Description: Animals4Life VPC1 SubnetWEBB
Value: !Ref SubnetWEBB
Export:
Name: a4l-vpc1-subnet-webb
a4lvpc1subnetwebc:
Description: Animals4Life VPC1 SubnetWEBC
Value: !Ref SubnetWEBC
Export:
Name: a4l-vpc1-subnet-webc
a4lvpc1subnetappa:
Description: Animals4Life VPC1 SubnetAPPA
Value: !Ref SubnetAPPA
Export:
Name: a4l-vpc1-subnet-appa
a4lvpc1subnetappb:
Description: Animals4Life VPC1 SubnetAPPB
Value: !Ref SubnetAPPB
Export:
Name: a4l-vpc1-subnet-appb
a4lvpc1subnetappc:
Description: Animals4Life VPC1 SubnetAPPC
Value: !Ref SubnetAPPC
Export:
Name: a4l-vpc1-subnet-appc
a4lvpc1subnetdba:
Description: Animals4Life VPC1 SubnetDBA
Value: !Ref SubnetDBA
Export:
Name: a4l-vpc1-subnet-dba
a4lvpc1subnetdbb:
Description: Animals4Life VPC1 SubnetDBB
Value: !Ref SubnetDBB
Export:
Name: a4l-vpc1-subnet-dbb
a4lvpc1subnetdbc:
Description: Animals4Life VPC1 SubnetDBC
Value: !Ref SubnetDBC
Export:
Name: a4l-vpc1-subnet-dbc
a4lvpc1subnetreserveda:
Description: Animals4Life VPC1 SubnetReservedA
Value: !Ref SubnetReservedA
Export:
Name: a4l-vpc1-subnet-reserveda
a4lvpc1subnetreservedb:
Description: Animals4Life VPC1 SubnetReservedB
Value: !Ref SubnetReservedB
Export:
Name: a4l-vpc1-subnet-reservedb
a4lvpc1subnetreservedc:
Description: Animals4Life VPC1 SubnetReservedC
Value: !Ref SubnetReservedC
Export:
Name: a4l-vpc1-subnet-reservedc
Lesson Commands
# DBName=database name for wordpress
# DBUser=mariadb user for wordpress
# DBPassword=password for the mariadb user for wordpress
# DBRootPassword = root password for mariadb
# STEP 1 - Configure Authentication Variables which are used below
DBName='a4lwordpress'
DBUser='a4lwordpress'
DBPassword='4n1m4l$4L1f3'
DBRootPassword='4n1m4l$4L1f3'
# STEP 2 - Install system software - including Web and DB
sudo dnf install wget php-mysqlnd httpd php-fpm php-mysqli mariadb105-server php-json php php-devel -y
# STEP 3 - Web and DB Servers Online - and set to startup
sudo systemctl enable httpd
sudo systemctl enable mariadb
sudo systemctl start httpd
sudo systemctl start mariadb
# STEP 4 - Set Mariadb Root Password
sudo mysqladmin -u root password $DBRootPassword
# STEP 5 - Install Wordpress
sudo wget http://wordpress.org/latest.tar.gz -P /var/www/html
cd /var/www/html
sudo tar -zxvf latest.tar.gz
sudo cp -rvf wordpress/* .
sudo rm -R wordpress
sudo rm latest.tar.gz
# STEP 6 - Configure Wordpress
sudo cp ./wp-config-sample.php ./wp-config.php
sudo sed -i "s/'database_name_here'/'$DBName'/g" wp-config.php
sudo sed -i "s/'username_here'/'$DBUser'/g" wp-config.php
sudo sed -i "s/'password_here'/'$DBPassword'/g" wp-config.php
sudo chown apache:apache * -R
# STEP 7 Create Wordpress DB
echo "CREATE DATABASE $DBName;" >> /tmp/db.setup
echo "CREATE USER '$DBUser'@'localhost' IDENTIFIED BY '$DBPassword';" >> /tmp/db.setup
echo "GRANT ALL ON $DBName.* TO '$DBUser'@'localhost';" >> /tmp/db.setup
echo "FLUSH PRIVILEGES;" >> /tmp/db.setup
mysql -u root --password=$DBRootPassword < /tmp/db.setup
sudo rm /tmp/db.setup
# STEP 8 - Browse to http://your_instance_public_ipv4_ip
💻 Cài đặt WordPress thủ công trên EC2 – Phần 2 (Hoàn thiện & test)¶
1. Cấu hình WordPress (wp-config.php)¶
-
Đổi tên file cấu hình mẫu:
-
Tự động thay thế giá trị cấu hình bằng biến đã đặt:
-
Phân quyền file/folder cho web server:
2. Tạo Database, User và Phân quyền cho WordPress¶
-
Tạo script SQL để tạo database/user:
-
Chạy script SQL qua MariaDB:
-
Xóa script chứa thông tin nhạy cảm:
3. Kiểm tra cài đặt WordPress¶
- Lấy IP public của EC2 từ console.
- Mở trình duyệt, truy cập:
-
Hoàn tất wizard cài đặt:
- Chọn ngôn ngữ: English (United States)
- Blog title: “All the cats”
- Username:
admin - Password: (copy từ wizard, dùng để login)
- Email:
test@test.com - Nhấn Install WordPress
- Đăng nhập với username/password vừa tạo
-
Kiểm tra giao diện:
- Vào “All the cats” → Visit site
- Trang WordPress đã hoạt động!
4. Dọn dẹp tài nguyên AWS (Clean up)¶
- Xóa stack CloudFormation để gỡ toàn bộ hạ tầng demo:
- AWS Console > CloudFormation > Chọn stack
WordPress> Delete
- AWS Console > CloudFormation > Chọn stack
5. Tổng kết & bài học kinh nghiệm¶
- Bạn đã cài đặt WordPress thủ công trên một EC2 duy nhất (monolithic).
- Quy trình này rất dễ lỗi, tốn thời gian, không lặp lại được nếu làm nhiều lần.
- Thực tế, nên dùng automation (CloudFormation, Ansible, AMI, ...).
- Các bài học tiếp theo sẽ hướng dẫn bạn tự động hóa cài đặt và triển khai WordPress trên AWS.