19.Wordpress Ec2
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 1 (Học cách KHÔNG nên làm)¶
1. Mục tiêu & Bối cảnh¶
- Mục tiêu: Trải nghiệm quy trình cài đặt một ứng dụng (WordPress) thủ công trên EC2 để hiểu rõ các nhược điểm của phương pháp này.
- Tại sao? Hiểu được sự phức tạp và rủi ro của việc cài đặt thủ công sẽ giúp ta đánh giá đúng giá trị của các công cụ tự động hóa trên AWS sau này.
- Kiến trúc cuối cùng:
- Một trang WordPress hoạt động trên một EC2 instance duy nhất.
- Kiến trúc đơn khối (monolithic): Cả web server (Apache) và database (MariaDB) đều chạy trên cùng một instance.
- Không có tính sẵn sàng cao (High Availability).
2. Bước 1: Chuẩn bị môi trường¶
- Đăng nhập AWS: Sử dụng tài khoản Management Account của bạn.
- Chọn Region:
us-east-1(N. Virginia). - Deploy CloudFormation Stack:
- Sử dụng link "one-click deployment" được cung cấp kèm bài học.
- Stack name sẽ là
WordPress. - Kéo xuống dưới, tick vào ô "capabilities".
- Nhấn Create stack.
- Quan trọng: Đợi đến khi trạng thái stack chuyển thành CREATE_COMPLETE.
- Mở tài liệu lệnh: Mở file "Lesson Commands Document" đính kèm để copy-paste các lệnh cần thiết.
3. Bước 2: Kết nối vào EC2 Instance¶
- Truy cập EC2 Console.
- Vào mục Instances (running).
- Bạn sẽ thấy một instance có tên
A4LE public EC2. - Chuột phải vào instance → Connect.
- Chọn tab EC2 Instance Connect.
- Username:
ec2-user. - Nhấn Connect.
4. Bước 3: Cài đặt thủ công trên EC2 (qua Terminal)¶
3.1. Thiết lập biến môi trường¶
Mục đích: Lưu trữ các thông tin nhạy cảm (tên database, user, password) vào biến để tái sử dụng, tránh gõ lại và giảm lỗi.
# Đặt tên database
export DB_NAME='a4l_wordpress_db'
# Đặt tên user database
export DB_USER='a4l_wordpress_user'
# Đặt mật khẩu cho user database
export DB_PASSWORD='Password123!!'
# Đặt mật khẩu cho user root của database
export DB_ROOT_PASSWORD='Password321!!'
# Kiểm tra lại một biến để chắc chắn
echo $DB_NAME
3.2. Cài đặt các gói phần mềm cần thiết¶
Cài đặt web server (Apache), database server (MariaDB) và công cụ tải file (wget).
(Lưu ý:-y để tự động đồng ý cài đặt)
3.3. Kích hoạt và khởi động các dịch vụ¶
Đảm bảo web server và database tự động chạy khi instance khởi động lại và khởi chạy chúng ngay bây giờ.
# Kích hoạt dịch vụ httpd (Apache)
sudo systemctl enable httpd
# Kích hoạt dịch vụ mariadb
sudo systemctl enable mariadb
# Khởi động dịch vụ httpd
sudo systemctl start httpd
# Khởi động dịch vụ mariadb
sudo systemctl start mariadb
3.4. Thiết lập mật khẩu root cho Database¶
Bảo mật cơ sở dữ liệu bằng cách đặt mật khẩu cho người dùng quản trị cao nhất (root).
# Đặt mật khẩu root cho MariaDB, sử dụng biến đã tạo
sudo mysqladmin -u root password "$DB_ROOT_PASSWORD"
3.5. Tải và giải nén mã nguồn WordPress¶
-
Tải WordPress về thư mục gốc của web server (
/var/www/html): -
Di chuyển vào thư mục web root và giải nén:
-
Di chuyển file từ thư mục con ra thư mục gốc: Sau khi giải nén, các file nằm trong thư mục
/var/www/html/wordpress. Ta cần chuyển chúng ra/var/www/html. -
Dọn dẹp các file không cần thiết: Xóa thư mục
wordpressrỗng và file nénlatest.tar.gz. -
Kiểm tra lại kết quả:
Bạn sẽ thấy toàn bộ file và thư mục của WordPress đã nằm trực tiếp trong /var/www/html.