1-Click Deployment
Description: Animals4Life base VPC Template + 3 Public Instances
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
Instance1:
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-EBS-INSTANCE1-AZA
Instance2:
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-EBS-INSTANCE2-AZA
Instance3:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
IamInstanceProfile: !Ref SessionManagerInstanceProfile
SubnetId: !Ref SubnetWEBB
SecurityGroupIds:
- !Ref InstanceSecurityGroup
Tags:
- Key: Name
Value: A4L-EBS-INSTANCE1-AZB
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
Commands User¶
Instance 1¶
lsblk sudo file -s /dev/xvdf sudo mkfs -t xfs /dev/xvdf sudo file -s /dev/xvdf sudo mkdir /ebstest sudo mount /dev/xvdf /ebstest cd /ebstest sudo nano amazingtestfile.txt add a message save and exit ls -la
Reboot Instance 1¶
sudo reboot
Instance 1 After Reboot¶
df -k sudo blkid sudo nano /etc/fstab ADD LINE UUID=YOURUUIDHEREREPLACEME /ebstest xfs defaults,nofail sudo mount -a cd /ebstest ls -la
Instance 2¶
lsblk sudo file -s /dev/xvdf sudo mkdir /ebstest sudo mount /dev/xvdf /ebstest cd /ebstest ls -la
Instance 3¶
lsblk sudo file -s /dev/xvdf sudo mkdir /ebstest sudo mount /dev/xvdf /ebstest cd /ebstest ls -la
InstanceStoreTest¶
lsblk sudo file -s /dev/nvme1n1 sudo mkfs -t xfs /dev/nvme1n1 sudo file -s /dev/nvme1n1 sudo mkdir /instancestore sudo mount /dev/nvme1n1 /instancestore cd /instancestore sudo touch instancestore.txt
InstancStoreTest - After Restart¶
df -k its not there but we can mount it sudo mount /dev/nvme1n1 /instancestore cd /instancestore ls -la
InstanceStoreTest - After Stop/Start¶
sudo file -s /dev/nvme1n1
🛠️ Demo thực tế EBS & Snapshot – Phần 2 (script đầy đủ, thực chiến & mẹo đi làm, đi thi)¶
1. Kiểm tra persistence & auto-mount sau reboot¶
a. Sau reboot instance¶
- Đợi EC2 khởi động lại, connect lại bằng EC2 Instance Connect.
-
Kiểm tra mount:
→ Không thấy volume mount vì mount thủ công chưa auto-mount sau reboot. -
Tìm UUID của volume:
- Tìm dòng tương ứng với
/dev/xvdf(hoặc device bạn mount, tùy instance nhận). -
Copy UUID (giá trị trong dấu “”).
-
Edit file
/etc/fstabđể auto-mount: - Thêm dòng:
- (Thay UUID và mount point cho đúng)
-
defaults,nofailgiúp instance boot ngay cả khi volume chưa attach. -
Mount lại toàn bộ từ fstab:
-
Kiểm tra lại:
→ File vẫn còn, chứng minh EBS persistent & auto-mount.
2. Detach EBS & attach sang instance khác (trong cùng AZ)¶
a. Dừng (stop) instance 1a (nơi đang attach volume)¶
- EC2 Console > Instances > instance 1a > Stop
b. Detach volume¶
- EC2 Console > Volumes > chọn volume vừa test > Actions > Detach Volume
c. Attach volume sang instance 2a (cùng AZ)¶
- Chọn volume > Actions > Attach Volume > chọn instance 2a > Device:
/dev/xvdf - EC2 Console > Instances > instance 2a > Connect (EC2 Instance Connect)
d. Kiểm tra trên instance mới¶
lsblk
sudo file -s /dev/xvdf
# Thấy đã có file system xfs
sudo mkdir /EBS-test
sudo mount /dev/xvdf /EBS-test
ls /EBS-test
cat /EBS-test/amazing-test-file.txt
3. Di chuyển volume sang AZ khác bằng snapshot¶
a. Dừng instance 2a, detach volume như trên¶
b. Tạo snapshot từ volume¶
- EC2 Console > Volumes > chọn volume > Actions > Create Snapshot > mô tả
EBS test snap
c. Tạo volume từ snapshot, chọn AZ khác (ví dụ: us-east-1b)¶
- Snapshots > chọn snapshot vừa tạo > Actions > Create Volume
- AZ: us-east-1b, size/type giữ nguyên, tag:
EBS test volume - AZB
d. Attach volume sang instance ở AZ mới (instance-1ab)¶
- Volumes > chọn volume mới > Attach Volume > chọn instance-1ab > Device:
/dev/xvdf - EC2 Console > Instances > instance-1ab > Connect
e. Kiểm tra trên instance mới ở AZ khác¶
lsblk
sudo file -s /dev/xvdf
sudo mkdir /EBS-test
sudo mount /dev/xvdf /EBS-test
ls /EBS-test
cat /EBS-test/amazing-test-file.txt
4. Copy snapshot sang region khác (DR/migration)¶
- Snapshots > chọn snapshot > Actions > Copy Snapshot
- Chọn region đích (ví dụ: ap-southeast-2), đặt tên, bấm Copy
- Ở region đích, có thể tạo volume mới từ snapshot này.
5. Clean up resources (dọn dẹp tránh tốn phí)¶
- Snapshots > chọn snapshot > Delete
- Volumes > từng volume > Delete
- EC2 Console > Instances > dừng (stop) hoặc terminate các instance không còn dùng
6. Mẹo thực tế & đi thi cần nhớ¶
- EBS persistent, detach/attach linh hoạt trong cùng AZ
- Muốn di chuyển AZ/region → dùng snapshot
- Snapshot chỉ chứa data thực dùng, incremental
- Volume tạo từ snapshot phải mount lại, file system vẫn giữ nguyên
- fstab + UUID để auto-mount khi reboot
- Không thể attach EBS volume cross-AZ, chỉ cross-AZ bằng snapshot
- Always cleanup sau thực hành tránh phát sinh phí
- snapshot thì tạo volume trực tiếp từ az này qua az khác (cùng region được) . còn muốn volume qua bên region khác thì phải tạo copy của snapshot ở az trong region này
7. Lệnh tổng hợp thực tế¶
# Mount auto sau reboot
sudo blkid # Lấy UUID
sudo nano /etc/fstab # Thêm cấu hình mount
sudo mount -a # Mount lại toàn bộ
df -h
ls /EBS-test
# Kiểm tra, mount, đọc file sau khi attach sang instance khác
lsblk
sudo file -s /dev/xvdf
sudo mkdir /EBS-test
sudo mount /dev/xvdf /EBS-test
ls /EBS-test
cat /EBS-test/amazing-test-file.txt
8. Giá (tham khảo us-east-1, 2024)¶
- EBS gp3: ~$0.08/GB/tháng
- Snapshot: ~$0.05/GB/tháng (data thực dùng)
- Instance lớn (cho demo instance store): ~$0.13$/giờ (không Free Tier)
- Instance store: Đã tính trong giá instance
Tóm tắt:
- EBS persistent, detach/attach linh hoạt trong AZ, snapshot để move AZ/region, dùng fstab để auto-mount, luôn cleanup để tránh phí, nhớ các step thực tế và lệnh Linux cơ bản.