Chạy Script Tự Động Khi EC2 Khởi Động: Hướng Dẫn User Data Thực Tế

Bạn vừa launch một EC2 instance mới và muốn Nginx được cài đặt sẵn ngay khi máy chủ khởi động lần đầu — không cần SSH vào tay, không cần chạy lệnh thủ công. Đây là bài toán cực kỳ phổ biến trong production, và EC2 User Data chính là cơ chế giải quyết nó một cách sạch sẽ nhất.

TL;DR — Tóm Tắt Nhanh

BướcHành độngLưu ý quan trọng
1Soạn shell script cài NginxDòng đầu phải là #!/bin/bash
2Dán vào ô 'User Data' khi launch instanceEC2 Console → Advanced Details → User Data
3Script chạy một lần duy nhất lúc first bootChạy với quyền root, không cần sudo
4Kiểm tra log tại /var/log/cloud-init-output.logĐây là nơi debug khi script không chạy đúng

EC2 User Data Hoạt Động Như Thế Nào

Trước khi paste script vào console, cần hiểu đúng cơ chế để tránh những lỗi ngớ ngẩn mà ai cũng từng gặp ít nhất một lần.

Khi một EC2 instance khởi động, dịch vụ cloud-init chạy rất sớm trong quá trình boot — trước khi bạn có thể SSH vào. Cloud-init đọc User Data từ Instance Metadata Service (IMDS) tại địa chỉ http://169.254.169.254/latest/user-data, sau đó thực thi nội dung đó.

Mặc định, User Data script chỉ chạy một lần duy nhất trong lần boot đầu tiên sau khi instance được tạo. Nếu bạn stop rồi start lại instance, script sẽ không chạy lại — trừ khi bạn cấu hình cloud-init để làm vậy.

graph TD A["EC2 Launch"] --> B["cloud-init khởi động"] B --> C["Truy vấn IMDS 169.254.169.254"] C --> D["Đọc User Data Script"] D --> E["Thực thi script với quyền root"] E --> F["Ghi log vào /var/log/cloud-init-output.log"] F --> G["Instance sẵn sàng"] G --> H{"Stop & Start lại?"} H -->|"Mặc định"| I["Script KHÔNG chạy lại"] H -->|"Cấu hình đặc biệt"| J["Script chạy lại"]
  1. EC2 Launch: AWS tạo instance, gắn User Data vào metadata của instance.
  2. cloud-init khởi động: Dịch vụ này chạy trong quá trình boot, truy vấn IMDS để lấy User Data.
  3. Script thực thi: Shell script chạy với quyền root. Output được ghi vào /var/log/cloud-init-output.log.
  4. Instance sẵn sàng: Sau khi script hoàn thành, instance chuyển sang trạng thái running bình thường.
  5. Stop/Start lại: Script không tự chạy lại — đây là hành vi mặc định của cloud-init.

Viết User Data Script Cài Nginx

Script dưới đây cài Nginx trên Amazon Linux 2 hoặc Amazon Linux 2023. Lưu ý rằng package manager và tên service có thể khác nhau tùy AMI — ví dụ Ubuntu dùng apt-get thay vì dnf hay yum.

Cho Amazon Linux 2023 / Amazon Linux 2:

#!/bin/bash
# Cập nhật package list
dnf update -y

# Cài đặt Nginx
dnf install -y nginx

# Khởi động Nginx ngay lập tức
systemctl start nginx

# Bật Nginx tự khởi động cùng hệ thống
systemctl enable nginx

Cho Ubuntu 22.04 / 24.04:

#!/bin/bash
# Cập nhật package list
apt-get update -y

# Cài đặt Nginx
apt-get install -y nginx

# Khởi động Nginx ngay lập tức
systemctl start nginx

# Bật Nginx tự khởi động cùng hệ thống
systemctl enable nginx

Dòng #!/bin/bash (shebang) là bắt buộc — nếu thiếu dòng này, cloud-init sẽ không biết dùng interpreter nào để chạy script và có thể bỏ qua hoàn toàn.

Dán User Data Vào EC2 Console — Từng Bước

Đây là phần mà nhiều người mất thời gian tìm vì AWS Console thay đổi giao diện khá thường xuyên. Ô User Data nằm khá sâu trong wizard launch instance.

  1. Vào EC2 Console → chọn Launch Instances.
  2. Điền tên, chọn AMI và instance type như bình thường.
  3. Cuộn xuống phần Advanced Details — click để mở rộng.
  4. Tìm ô User Data ở gần cuối phần Advanced Details.
  5. Đảm bảo radio button 'As text' được chọn (không phải 'As file').
  6. Paste toàn bộ script vào ô đó, bao gồm cả dòng #!/bin/bash.
  7. Hoàn tất các cấu hình còn lại (Security Group, Key Pair, v.v.) rồi launch.
graph LR A["EC2 Console"] --> B["Launch Instances"] B --> C["Chọn AMI & Instance Type"] C --> D["Advanced Details ▼ Mở rộng"] D --> E["User Data field Chọn: As text"] E --> F["Paste script #!/bin/bash ..."] F --> G["Launch Instance"]
  1. Launch Instances: Điểm bắt đầu trong EC2 Console.
  2. Advanced Details: Section cần mở rộng để thấy ô User Data.
  3. User Data field: Paste script vào đây, chọn 'As text'.
  4. Launch: Instance được tạo với User Data đã gắn kèm.

Cài Nginx Qua AWS CLI Với User Data

Nếu bạn cần tự động hóa việc launch instance trong CI/CD pipeline hoặc Infrastructure as Code, dùng AWS CLI sẽ tiện hơn nhiều so với console.

aws ec2 run-instances \
  --image-id ami-0c02fb55956c7d316 \
  --instance-type t3.micro \
  --key-name my-key-pair \
  --security-group-ids sg-0123456789abcdef0 \
  --subnet-id subnet-0123456789abcdef0 \
  --user-data file://nginx-install.sh \
  --region us-east-1

Trong đó nginx-install.sh là file chứa shell script ở trên. Tham số --user-data file:// cho phép truyền nội dung file trực tiếp — AWS CLI sẽ tự động encode Base64 trước khi gửi lên API.

Nghĩ về User Data như một tờ giấy ghi chú bạn nhét vào tay nhân viên mới trước ca làm việc đầu tiên. Họ đọc và làm theo đúng một lần — sau đó tờ giấy không còn tác dụng nữa, dù họ có nghỉ rồi đi làm lại.

Kiểm Tra Và Debug Khi Script Không Chạy Đúng

Script chạy trong background lúc boot, không có terminal để xem output trực tiếp. File log duy nhất bạn cần quan tâm là /var/log/cloud-init-output.log.

SSH vào instance sau khi nó chuyển sang trạng thái running, rồi chạy:

# Xem toàn bộ output của User Data script
cat /var/log/cloud-init-output.log

# Xem trạng thái cloud-init
cloud-init status

# Kiểm tra Nginx đã chạy chưa
systemctl status nginx

Nếu cloud-init status trả về done nhưng Nginx không chạy, lỗi thường nằm trong script — sai tên package, thiếu quyền, hoặc network chưa sẵn sàng khi script chạy. Đọc log từ trên xuống để tìm dòng lỗi đầu tiên.

Một lỗi hay gặp khác: bạn thấy instance đã running nhưng log vẫn trống. Điều này thường xảy ra khi Security Group chặn outbound traffic, khiến dnf install không kết nối được đến repository — script bị treo hoặc fail ngay bước cài package mà không có thông báo rõ ràng.

Kinh Nghiệm Thực Tế: Sai Lầm Phổ Biến Và Cách Sửa

Triệu chứng: Instance running, SSH được, nhưng Nginx không có mặt. Log cloud-init-output.log cho thấy script bắt đầu chạy nhưng dừng đột ngột ở bước dnf install.

Chẩn đoán ban đầu (sai): Nhiều người nghĩ ngay đến AMI không tương thích hoặc script có lỗi cú pháp. Họ sửa script, launch lại instance nhiều lần — vẫn thất bại.

Nguyên nhân thực sự: Security Group của instance không có outbound rule cho HTTPS (port 443) hoặc HTTP (port 80). Lệnh dnf install nginx cần kết nối ra ngoài để tải package từ AWS repository. Khi outbound bị chặn, lệnh timeout sau vài phút và script dừng lại — không có error message rõ ràng trong log.

Cách fix: Kiểm tra Security Group outbound rules. Đảm bảo có rule cho phép 0.0.0.0/0 trên port 443 và 80, hoặc ít nhất là đến các IP của AWS package repository. Sau đó terminate instance cũ và launch lại với script không thay đổi.

# Kiểm tra outbound rules của Security Group
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[*].IpPermissionsEgress' \
  --region us-east-1

Script không lỗi — môi trường mới là vấn đề. Đây là lý do tại sao debug User Data luôn phải bắt đầu từ network, không phải từ script.

Xem Lại User Data Của Instance Đang Chạy

Nếu bạn cần kiểm tra User Data đã được gắn vào instance nào đó, có hai cách:

Từ bên trong instance (truy vấn IMDS):

# IMDSv2 — cách được khuyến nghị hiện tại
TOKEN=$(curl -s -X PUT 'http://169.254.169.254/latest/api/token' \
  -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/user-data

Từ AWS CLI bên ngoài:

aws ec2 describe-instance-attribute \
  --instance-id i-0123456789abcdef0 \
  --attribute userData \
  --query 'UserData.Value' \
  --output text \
  --region us-east-1 | base64 --decode

User Data được lưu dưới dạng Base64 encoded — lệnh base64 --decode ở cuối giải mã về dạng text đọc được.

Cập Nhật User Data Sau Khi Instance Đã Tạo

Bạn có thể sửa User Data của một instance đã tồn tại, nhưng instance phải ở trạng thái stopped. Script mới sẽ không tự chạy lại khi start lại — đây là hành vi mặc định của cloud-init.

# Bước 1: Stop instance trước
aws ec2 stop-instances \
  --instance-ids i-0123456789abcdef0 \
  --region us-east-1

# Bước 2: Cập nhật User Data (truyền nội dung mới dưới dạng Base64)
aws ec2 modify-instance-attribute \
  --instance-id i-0123456789abcdef0 \
  --user-data file://new-script.sh \
  --region us-east-1

Nếu bạn cần script chạy lại trong mỗi lần boot (ví dụ để cập nhật config), đó là trường hợp dùng cloud-init directives hoặc cơ chế khác như AWS Systems Manager Run Command — User Data đơn thuần không phải công cụ phù hợp cho use case đó.

Chạy Script EC2 Startup Đúng Cách — Tổng Kết Và Bước Tiếp Theo

EC2 User Data là cách đơn giản và hiệu quả nhất để tự động hóa việc cài đặt phần mềm khi instance khởi động lần đầu. Với Nginx hay bất kỳ package nào khác, quy trình đều giống nhau: viết shell script với shebang, paste vào ô User Data trong Advanced Details, kiểm tra log tại /var/log/cloud-init-output.log khi cần debug.

Khi hệ thống phức tạp hơn — nhiều instance, nhiều môi trường — hãy xem xét chuyển sang AWS Systems Manager State Manager hoặc tích hợp User Data với AWS CloudFormation cfn-init để có khả năng quản lý cấu hình tốt hơn. Tài liệu tham khảo chính thức: AWS EC2 User Guide — Run commands on your Linux instance at launch.

Glossary — Các Thuật Ngữ Quan Trọng

Thuật ngữGiải thích
User DataScript hoặc dữ liệu được truyền vào EC2 instance khi launch, thực thi bởi cloud-init trong lần boot đầu tiên.
cloud-initCông cụ khởi tạo instance tiêu chuẩn trên Linux, chịu trách nhiệm đọc và thực thi User Data.
IMDS (Instance Metadata Service)Endpoint nội bộ tại 169.254.169.254 cung cấp metadata và User Data cho instance.
Shebang (#!/bin/bash)Dòng đầu tiên của shell script, chỉ định interpreter sẽ được dùng để thực thi script.
IMDSv2Phiên bản bảo mật hơn của IMDS, yêu cầu session token trước khi truy vấn metadata.

Nhận xét

Bài đăng phổ biến từ blog này

EC2 Không Có Internet Trong Custom VPC: Cách Gắn Internet Gateway và Cập Nhật Route Table

RDS Multi-AZ: Lợi Ích Thực Sự Là Gì và Khi Nào Nên Bật?

Lỗi CORS trên API Gateway: Cách bật CORS và Lambda phải trả về header gì