Chương 23: Hands-On Labs
Tổng Quan
10 bài thực hành với AWS CLI commands step-by-step. Mỗi lab có sơ đồ kiến trúc, hướng dẫn chi tiết, và phần cleanup (xoá resources tránh phát sinh chi phí).
⚠️ Prerequisites: AWS Account (Free Tier), AWS CLI configured, region:
us-east-1
🔧 Setup AWS CLI
# Install (macOS)
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /
# Configure
aws configure
# Access Key: YOUR_KEY
# Secret Key: YOUR_SECRET
# Region: us-east-1
# Output: json
# Verify
aws sts get-caller-identity
🌐 Lab 1: VPC & Networking
Mục tiêu: Tạo VPC production-ready với public + private subnets, IGW, NAT Gateway, Route Tables.
Kiến trúc
VPC (10.0.0.0/16)
├─ AZ-A
│ ├─ Public Subnet (10.0.1.0/24) ← NAT Gateway
│ └─ Private Subnet (10.0.10.0/24) ← EC2
├─ AZ-B
│ ├─ Public Subnet (10.0.2.0/24) ← NAT Gateway
│ └─ Private Subnet (10.0.11.0/24) ← EC2
└─ Internet Gateway
Commands
# 1. Create VPC
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=lab-vpc}]' \
--query 'Vpc.VpcId' --output text)
# 2. Create Subnets
PUB_1=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24 \
--availability-zone us-east-1a --query 'Subnet.SubnetId' --output text)
PUB_2=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.2.0/24 \
--availability-zone us-east-1b --query 'Subnet.SubnetId' --output text)
PRIV_1=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.10.0/24 \
--availability-zone us-east-1a --query 'Subnet.SubnetId' --output text)
PRIV_2=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.11.0/24 \
--availability-zone us-east-1b --query 'Subnet.SubnetId' --output text)
# 3. Create + Attach Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID
# 4. Create NAT Gateway (AZ-A)
EIP=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
NAT=$(aws ec2 create-nat-gateway --subnet-id $PUB_1 --allocation-id $EIP \
--query 'NatGateway.NatGatewayId' --output text)
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT
# 5. Route Tables
PUB_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PUB_RT --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
aws ec2 associate-route-table --subnet-id $PUB_1 --route-table-id $PUB_RT
aws ec2 associate-route-table --subnet-id $PUB_2 --route-table-id $PUB_RT
PRIV_RT=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $PRIV_RT --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT
aws ec2 associate-route-table --subnet-id $PRIV_1 --route-table-id $PRIV_RT
aws ec2 associate-route-table --subnet-id $PRIV_2 --route-table-id $PRIV_RT
# 6. Verify
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
--query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone]' --output table
🧹 Cleanup
aws ec2 delete-nat-gateway --nat-gateway-id $NAT
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $NAT
aws ec2 release-address --allocation-id $EIP
aws ec2 detach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID
aws ec2 delete-subnet --subnet-id $PUB_1; aws ec2 delete-subnet --subnet-id $PUB_2
aws ec2 delete-subnet --subnet-id $PRIV_1; aws ec2 delete-subnet --subnet-id $PRIV_2
aws ec2 delete-route-table --route-table-id $PUB_RT
aws ec2 delete-route-table --route-table-id $PRIV_RT
aws ec2 delete-vpc --vpc-id $VPC_ID
📝 Học được gì?
- VPC architecture: public vs private subnets
- Internet Gateway cho public access, NAT Gateway cho outbound-only
- Route Tables điều hướng traffic
💻 Lab 2: EC2 & Auto Scaling
Mục tiêu: Deploy web server với Auto Scaling Group + ALB.
Commands
# 1. Security Group
WEB_SG=$(aws ec2 create-security-group --group-name lab-web-sg \
--description "Web SG" --vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $WEB_SG --protocol tcp --port 80 --cidr 0.0.0.0/0
# 2. Launch Template
cat > user-data.sh << 'EOF'
#!/bin/bash
yum update -y && yum install -y httpd
systemctl start httpd && systemctl enable httpd
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
AZ=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
echo "<h1>Instance: $INSTANCE_ID | AZ: $AZ</h1>" > /var/www/html/index.html
EOF
aws ec2 create-launch-template --launch-template-name lab-template \
--launch-template-data "{
\"ImageId\":\"ami-0c55b159cbfafe1f0\",
\"InstanceType\":\"t3.micro\",
\"SecurityGroupIds\":[\"$WEB_SG\"],
\"UserData\":\"$(base64 -i user-data.sh)\"
}"
# 3. Auto Scaling Group (Min 2, Max 4)
aws autoscaling create-auto-scaling-group --auto-scaling-group-name lab-asg \
--launch-template "LaunchTemplateName=lab-template,Version=\$Latest" \
--min-size 2 --max-size 4 --desired-capacity 2 \
--vpc-zone-identifier "$PRIV_1,$PRIV_2"
# 4. Target Tracking (scale khi CPU > 50%)
aws autoscaling put-scaling-policy --auto-scaling-group-name lab-asg \
--policy-name cpu-tracking --policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"TargetValue": 50.0,
"PredefinedMetricSpecification": {"PredefinedMetricType":"ASGAverageCPUUtilization"}
}'
# 5. Check instances
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names lab-asg \
--query 'AutoScalingGroups[0].Instances[*].[InstanceId,AvailabilityZone,HealthStatus]' \
--output table
📝 Học được gì?
- Launch Template = template cho EC2 instances
- ASG tự heal (replace unhealthy) + scale theo policy
- Target tracking = đơn giản nhất (set target, AWS lo phần còn lại)
📦 Lab 3: S3 — Versioning, Lifecycle, Encryption
Mục tiêu: Tạo S3 bucket với versioning, lifecycle policies, server-side encryption.
BUCKET="lab-s3-$(date +%s)"
# 1. Create + Enable versioning + encryption
aws s3api create-bucket --bucket $BUCKET --region us-east-1
aws s3api put-bucket-versioning --bucket $BUCKET --versioning-configuration Status=Enabled
aws s3api put-bucket-encryption --bucket $BUCKET --server-side-encryption-configuration '{
"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]
}'
# 2. Lifecycle policy (Standard → IA → Glacier)
aws s3api put-bucket-lifecycle-configuration --bucket $BUCKET --lifecycle-configuration '{
"Rules":[{
"Id":"auto-tier","Status":"Enabled",
"Transitions":[
{"Days":30,"StorageClass":"STANDARD_IA"},
{"Days":90,"StorageClass":"GLACIER"}
],
"Expiration":{"Days":365}
}]
}'
# 3. Test versioning
echo "v1" > test.txt && aws s3 cp test.txt s3://$BUCKET/
echo "v2" > test.txt && aws s3 cp test.txt s3://$BUCKET/
aws s3api list-object-versions --bucket $BUCKET --prefix test.txt \
--query 'Versions[*].[VersionId,LastModified,IsLatest]' --output table
# Cleanup
aws s3 rm s3://$BUCKET --recursive
aws s3api delete-objects --bucket $BUCKET --delete \
"$(aws s3api list-object-versions --bucket $BUCKET --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"
aws s3api delete-bucket --bucket $BUCKET
📝 Học được gì?
- Versioning protect against accidental deletion
- Lifecycle auto-tier objects → cost optimization
- SSE encryption at rest
🗄️ Lab 4: RDS Multi-AZ
Mục tiêu: Tạo RDS MySQL Multi-AZ, test failover.
# 1. DB Subnet Group
aws rds create-db-subnet-group --db-subnet-group-name lab-db-subnets \
--db-subnet-group-description "Lab DB subnets" \
--subnet-ids "$PRIV_1" "$PRIV_2"
# 2. Security Group for DB
DB_SG=$(aws ec2 create-security-group --group-name lab-db-sg \
--description "DB SG" --vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $DB_SG \
--protocol tcp --port 3306 --source-group $WEB_SG
# 3. Create RDS Multi-AZ
aws rds create-db-instance --db-instance-identifier lab-db \
--engine mysql --engine-version 8.0 --db-instance-class db.t3.micro \
--allocated-storage 20 --master-username admin --master-user-password Lab12345! \
--db-subnet-group-name lab-db-subnets --vpc-security-group-ids $DB_SG \
--multi-az --no-publicly-accessible
# 4. Wait + verify
aws rds wait db-instance-available --db-instance-identifier lab-db
aws rds describe-db-instances --db-instance-identifier lab-db \
--query 'DBInstances[0].[Endpoint.Address,MultiAZ,AvailabilityZone]' --output table
# Cleanup
aws rds delete-db-instance --db-instance-identifier lab-db \
--skip-final-snapshot --delete-automated-backups
📝 Học được gì?
- Multi-AZ = synchronous replication + auto failover
- DB phải ở private subnet
- Security Group chaining: Web SG → DB SG
🔐 Lab 5: IAM Roles & Policies
Mục tiêu: Tạo IAM role cho EC2 access S3 (best practice, không dùng access keys).
# 1. Trust policy (allow EC2 assume role)
cat > trust.json << 'EOF'
{"Version":"2012-10-17","Statement":[{
"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},
"Action":"sts:AssumeRole"
}]}
EOF
aws iam create-role --role-name Lab-EC2-S3-Role --assume-role-policy-document file://trust.json
# 2. Attach S3 read-only policy
aws iam attach-role-policy --role-name Lab-EC2-S3-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# 3. Create instance profile (connect role to EC2)
aws iam create-instance-profile --instance-profile-name Lab-EC2-S3-Profile
aws iam add-role-to-instance-profile --instance-profile-name Lab-EC2-S3-Profile \
--role-name Lab-EC2-S3-Role
# Cleanup
aws iam remove-role-from-instance-profile --instance-profile-name Lab-EC2-S3-Profile \
--role-name Lab-EC2-S3-Role
aws iam delete-instance-profile --instance-profile-name Lab-EC2-S3-Profile
aws iam detach-role-policy --role-name Lab-EC2-S3-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam delete-role --role-name Lab-EC2-S3-Role
📝 Học được gì?
- IAM Roles = best practice (KHÔNG dùng access keys trên EC2)
- Trust policy = ai được assume role
- Instance Profile = cầu nối Role → EC2
⚡ Lab 6: Lambda Function
Mục tiêu: Tạo Lambda function xử lý S3 event (trigger khi upload file).
# 1. Create Lambda execution role
cat > lambda-trust.json << 'EOF'
{"Version":"2012-10-17","Statement":[{
"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},
"Action":"sts:AssumeRole"
}]}
EOF
aws iam create-role --role-name Lab-Lambda-Role --assume-role-policy-document file://lambda-trust.json
aws iam attach-role-policy --role-name Lab-Lambda-Role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
ROLE_ARN=$(aws iam get-role --role-name Lab-Lambda-Role --query 'Role.Arn' --output text)
# 2. Create function code
cat > index.js << 'EOF'
exports.handler = async (event) => {
console.log('S3 Event:', JSON.stringify(event, null, 2));
const bucket = event.Records[0].s3.bucket.name;
const key = event.Records[0].s3.object.key;
console.log(`New file uploaded: s3://${bucket}/${key}`);
return { statusCode: 200, body: `Processed: ${key}` };
};
EOF
zip function.zip index.js
# 3. Create Lambda
sleep 10 # Wait for role propagation
aws lambda create-function --function-name lab-s3-processor \
--runtime nodejs18.x --handler index.handler \
--role $ROLE_ARN --zip-file fileb://function.zip
# 4. Test invoke
aws lambda invoke --function-name lab-s3-processor \
--payload '{"Records":[{"s3":{"bucket":{"name":"test"},"object":{"key":"test.txt"}}}]}' \
output.json && cat output.json
# Cleanup
aws lambda delete-function --function-name lab-s3-processor
aws iam detach-role-policy --role-name Lab-Lambda-Role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name Lab-Lambda-Role
📝 Học được gì?
- Lambda = event-driven, serverless
- Cần IAM role cho Lambda (execution role)
- S3 events → Lambda trigger = common pattern
📊 Lab 7: CloudWatch Alarm
Mục tiêu: Tạo CloudWatch alarm cho EC2 CPU utilization.
# Create alarm: notify khi CPU > 80% trong 5 phút
aws cloudwatch put-metric-alarm --alarm-name lab-cpu-alarm \
--metric-name CPUUtilization --namespace AWS/EC2 \
--statistic Average --period 300 --evaluation-periods 1 \
--threshold 80 --comparison-operator GreaterThanThreshold \
--alarm-description "CPU > 80% for 5 minutes" \
--dimensions "Name=InstanceId,Value=YOUR_INSTANCE_ID"
# List alarms
aws cloudwatch describe-alarms --alarm-names lab-cpu-alarm \
--query 'MetricAlarms[0].[AlarmName,StateValue,Threshold]' --output table
# Cleanup
aws cloudwatch delete-alarms --alarm-names lab-cpu-alarm
📝 Học được gì?
- CloudWatch Alarms monitor metrics → trigger actions
- Period = khoảng thời gian check, Evaluation Periods = bao nhiêu lần vượt threshold
⚠️ Lưu Ý Quan Trọng
- Luôn cleanup sau mỗi lab → tránh phát sinh chi phí AWS
- NAT Gateway có phí (~$0.045/giờ) → xoá ngay khi xong lab
- Free Tier: t3.micro (750h/tháng), S3 (5GB), Lambda (1M requests), RDS (750h)
- Kiểm tra billing sau mỗi lab: AWS Console → Billing Dashboard
Exam Tips 💡
- Labs giúp hiểu cách components kết nối → dễ trả lời câu hỏi architecture.
- Security Group chaining (Web SG → DB SG) là pattern phổ biến trong thi.
- IAM Role for EC2 (KHÔNG access keys) = đáp án cho mọi câu "secure access".
- VPC architecture (public + private + NAT) xuất hiện trong ~30% câu hỏi.