Loading
Back to blog
AWS MigrationApril 202612 min read

Thực Hành AWS: Migration Portfolio Từ Vercel Lên AWS

Một bài thực hành khá trọn vẹn về hạ tầng, DNS, TLS, CI/CD và cách xử lý sự cố khi migration portfolio từ Vercel lên AWS.

Le Trung TrucPersonal engineering notesPortfolio infrastructure case study
1

Bối Cảnh Và Mục Tiêu

Trước đây mình deploy portfolio trên Vercel vì đây gần như là lựa chọn mặc định cho một dự án Next.js: setup nhanh, build đơn giản và gần như không phải bận tâm nhiều đến hạ tầng. Với một portfolio cá nhân, cách đó hoàn toàn hợp lý.

Tuy nhiên, sau một thời gian, mình muốn đi xa hơn câu chuyện deploy cho chạy được. Mình muốn hiểu rõ hơn cách một website tĩnh có thể được đưa lên production trên AWS, cách DNS được cấu hình, certificate được cấp như thế nào, CI/CD vận hành ra sao, và quan trọng hơn là phải xử lý thế nào khi kiến trúc ban đầu không thể triển khai đúng kế hoạch.

Bài viết này ghi lại toàn bộ quá trình migrate portfolio từ Vercel sang AWS, bắt đầu từ phương án S3 + CloudFront + Route 53 + ACM, sau đó chuyển sang EC2 + Nginx + Route 53 + Let's Encrypt khi gặp giới hạn ở cấp account AWS.

Đây không chỉ là một lần đổi nơi host website. Nó là một bài thực hành khá đầy đủ về hạ tầng, DNS, TLS, CI/CD, Terraform và cách ra quyết định khi môi trường thực tế không đi đúng như thiết kế ban đầu.

2

AWS vs Vercel

Nếu chỉ nhìn từ góc độ ship nhanh một portfolio, Vercel gần như luôn dễ chịu hơn. Nhưng khi mình coi đây là một bài thực hành hạ tầng thật, AWS lại có lợi thế rõ hơn ở khả năng kiểm soát, mở rộng và giá trị học được sau mỗi bước triển khai.

Nhìn ngắn gọn, khác biệt giữa hai bên có thể tóm lại như sau.

Tiêu chíVercelAWS
DeployRất nhanhPhải setup
Chi phí nhỏRẻ hoặc freeCó thể cao hơn
Chi phí khi scaleĐắt hơnRẻ hơn
Developer experienceCực tốtThấp hơn
ControlThấpFull
ScalingAutoPhải tự làm
InfraKhông cần hiểu sâuPhải hiểu và tự quản
Production lớnKhông tối ưu bằngTốt hơn
3

Kiến Trúc Ban Đầu Được Chọn

Phương án đầu tiên mình chọn là kiến trúc static khá chuẩn trên AWS: S3 để lưu static files, CloudFront để làm CDN, Route 53 để quản lý DNS, ACM để cấp SSL miễn phí, Terraform để quản lý hạ tầng bằng code, và GitHub Actions để build và deploy.

Trước khi triển khai, mình kiểm tra lại project và xác nhận rằng app có thể static export được. Đây là điều kiện quan trọng, vì nếu site có thể build hoàn toàn ra static output thì S3 + CloudFront gần như là phương án phù hợp nhất trên lý thuyết: gọn, rẻ, dễ scale và ít phải vận hành server.

Nếu mọi thứ đi đúng kế hoạch, kiến trúc mong muốn sẽ là: User -> Route 53 -> CloudFront -> S3.

Initial AWS static architecture with Route 53, CloudFront, S3, Terraform and GitHub Actions
Sơ đồ kiến trúc ban đầu cho phương án static hosting với Route 53, CloudFront, S3, Terraform và GitHub Actions.
4

Sự Cố Khi Triển Khai CloudFront

Vấn đề xuất hiện ở đúng bước quan trọng nhất: tạo CloudFront distribution.

AWS trả về lỗi cho biết account của mình phải được verify trước khi có thể tạo CloudFront resource mới. Điều này có nghĩa là ứng dụng không sai, Terraform không sai, DNS và certificate flow cũng không sai, nhưng account AWS chưa đủ điều kiện để đi tiếp với CloudFront.

Đây là một tình huống rất thực tế production. Nhiều khi kiến trúc đúng và code đúng vẫn chưa đủ. Hệ thống vẫn có thể bị chặn bởi giới hạn ở cấp account hoặc dịch vụ.

AWS CloudFront create distribution screen showing account verification error
Thông báo AWS trả về khi account chưa được verify để tạo CloudFront distribution.
5

Cách Mình Xử Lý Vấn Đề

Mình xử lý sự cố CloudFront theo hai hướng.

Hướng thứ nhất là mở support case với AWS để xác nhận đây là giới hạn ở cấp account, không phải do mình cấu hình sai. Việc này quan trọng vì nó giúp phân biệt rõ đâu là lỗi hạ tầng thật sự, đâu là lỗi do implementation.

Hướng thứ hai là thực dụng hơn: thay vì chờ CloudFront được mở, mình chuyển sang một phương án khác để đưa site lên production ngay.

Đây là quyết định quan trọng nhất trong cả bài thực hành. Nếu cứ cố giữ kiến trúc ban đầu bằng mọi giá, project sẽ bị đứng lại ở đúng điểm cần ship. Thay vào đó, mình chọn một kiến trúc đơn giản hơn nhưng vận hành được ngay.

  1. 1Vào AWS Support Center và tạo một support case mới.
  2. 2Chọn nhóm Account and billing, vì đây là nhóm hỗ trợ miễn phí và phù hợp với các vấn đề liên quan đến trạng thái account.
  3. 3Mô tả rõ lỗi khi tạo CloudFront distribution, bao gồm nguyên văn error message AWS trả về.
  4. 4Nếu màn hình hoặc log có request ID thì đính kèm luôn trong case để support có thêm dữ liệu tra cứu.
  5. 5Ghi rõ mục tiêu triển khai: một portfolio cá nhân trên AWS đang bị chặn ở bước tạo CloudFront resource.
  6. 6Sau khi gửi case, dùng phản hồi đó để xác nhận đây là giới hạn ở cấp account chứ không phải do Terraform hay cấu hình ứng dụng.
AWS Support Center case created for CloudFront account verification issue
Support case được mở trong AWS Support Center để xác nhận vấn đề nằm ở cấp account.
6

Phương Án Thay Thế: EC2 + Nginx

Kế hoạch mới của mình là: EC2, Nginx, Route 53, Let's Encrypt và GitHub Actions.

Ý tưởng rất đơn giản: build Next.js ra static files, đưa static files lên EC2, dùng Nginx phục vụ nội dung, dùng Let's Encrypt để cấp HTTPS, dùng Route 53 để quản lý domain, và dùng GitHub Actions để deploy tự động.

So với phương án S3 + CloudFront, đây không phải kiến trúc đẹp bằng trên giấy. Nhưng đổi lại, nó thực tế, dễ hiểu và quan trọng nhất là chạy được ngay trong điều kiện account hiện tại.

7

Tổng Quan Kiến Trúc Production Hiện Tại

Kiến trúc production mà portfolio đang dùng hiện nay là: User -> Route 53 -> EC2 -> Nginx -> static files.

Phần CI/CD hoạt động theo luồng: GitHub -> GitHub Actions -> SSH/rsync -> EC2.

Còn HTTPS được xử lý theo hướng: Let's Encrypt / Certbot -> Nginx on EC2.

Đây là một hệ thống nhỏ, đủ rõ ràng để kiểm soát, nhưng vẫn chạm vào đầy đủ những phần cốt lõi của một môi trường production thật.

Current AWS production architecture with Route 53, EC2, Nginx, Let's Encrypt and GitHub Actions
Sơ đồ kiến trúc production hiện tại với Route 53, EC2, Nginx, Let's Encrypt và GitHub Actions.
8

Phân Tích Chi Tiết Từng Thành Phần

Route 53 được dùng để quản lý DNS cho letrungtruc.dev và www.letrungtruc.dev. Sau khi chuyển nameserver sang Route 53, toàn bộ phần DNS được quản lý ngay trong AWS. Điều này giúp hạ tầng đồng nhất hơn và thuận tiện khi làm việc với Terraform.

Website hiện chạy trên một instance t3.micro trong region ap-southeast-1. Mình chọn t3.micro vì đủ cho một static portfolio, chi phí thấp và phù hợp hơn với điều kiện account tại thời điểm triển khai. EC2 được gắn Elastic IP để địa chỉ public không thay đổi.

Mình không tạo custom VPC mới. Hệ thống dùng default VPC và public subnet có sẵn trong account. Với một portfolio cá nhân, đây là lựa chọn hợp lý hơn việc dựng một network topology quá phức tạp.

Nginx là lớp web server phục vụ static files. Sau khi build, nội dung site được deploy vào /var/www/portfolio. Nginx trỏ root vào thư mục này và serve toàn bộ nội dung cho người dùng.

Vì production path hiện tại không dùng CloudFront hay ALB, mình không dùng ACM cho runtime hiện tại. Thay vào đó, mình dùng Let's Encrypt và certbot. Certbot xin certificate trực tiếp cho cả letrungtruc.dev và www.letrungtruc.dev.

GitHub Actions hiện phụ trách checkout source, setup Node.js, npm ci, npm run build, copy thư mục out/ lên EC2 và reload Nginx. Flow này rất đơn giản, nhưng với một portfolio cá nhân thì đơn giản thường là lợi thế.

Security group hiện mở 80 cho HTTP, 443 cho HTTPS và 22 cho SSH. Trong quá trình triển khai, mình đã phải cân bằng giữa bảo mật và tính thực dụng. Khi cần GitHub Actions SSH vào EC2 để deploy, việc mở lại SSH cho workflow là điều cần thiết để pipeline hoạt động ổn định.

9

Toàn Bộ Quy Trình Thực Hành

Nếu nhìn lại toàn bộ quá trình, flow thực hành của mình diễn ra theo các bước sau.

1. Kiểm tra xem project có thể static export hay không.

2. Chuyển cấu hình Next.js sang output static.

3. Dựng hạ tầng plan đầu bằng Terraform.

4. Gặp lỗi khi tạo CloudFront distribution.

5. Mở support case để xác nhận vấn đề ở cấp account.

6. Thiết kế lại plan theo hướng EC2 + Nginx.

7. Tạo EC2, Elastic IP, security group và Route 53 records.

8. Cài Nginx và cấu hình thư mục phục vụ static site.

9. Bật HTTPS bằng certbot.

10. Thay workflow deploy từ flow cũ sang GitHub Actions -> SSH/rsync -> EC2.

11. Test production và cleanup phần hạ tầng cũ không còn dùng.

Điều mình thấy hay nhất ở quá trình này là mọi thứ không đi theo đúng một đường thẳng. Kiến trúc ban đầu hợp lý, nhưng điều kiện thực tế buộc mình phải thay đổi. Và chính chỗ đó mới phản ánh đúng tinh thần của một bài thực hành production.

10

Kết Luận Và Bài Học Rút Ra

Sau cùng, mình không coi bài này đơn thuần là một lần move hosting từ Vercel sang AWS. Nó giống một case study nhỏ về cách một decision kỹ thuật phải thay đổi khi đi vào môi trường thực tế.

Trên lý thuyết, S3 + CloudFront + Route 53 + ACM vẫn là kiến trúc static mình đánh giá cao hơn cho use case này. Nhưng trong bối cảnh account chưa được mở CloudFront, lựa chọn đúng không phải là tiếp tục cố chấp với thiết kế ban đầu, mà là chuyển sang một phương án triển khai được, vận hành được và ship được.

Với mình, đó là bài học đáng giá nhất của lần migrate này: trong production, kiến trúc tốt nhất không phải lúc nào cũng là kiến trúc đẹp nhất, mà là kiến trúc giải quyết được bài toán trong điều kiện thực tế.