Mở đầu — vì sao bài này quan trọng
Ở những bài trước, chúng ta đã đi qua máy ảo (VM) và container — hai cách chạy ứng dụng mà bạn vẫn phải bận tâm tới "cái máy" bên dưới: chọn cấu hình CPU/RAM, cài đặt hệ điều hành, vá lỗi bảo mật, và quan trọng nhất là bạn trả tiền cho máy đó kể cả khi nó ngồi không.
Serverless lật ngược toàn bộ tư duy đó. Bạn viết một đoạn code, đẩy lên cloud, và nhà cung cấp lo phần còn lại: cấp phát tài nguyên, mở rộng khi tải tăng, thu hồi khi không có ai gọi. Bạn không thấy server, không SSH vào đâu cả, và bạn chỉ trả tiền cho đúng những mili-giây code của bạn thực sự chạy.
Với một kỹ sư DevOps hoặc một startup Việt Nam đang đếm từng đồng chi phí hạ tầng, đây là một mô hình cực kỳ hấp dẫn: một API xử lý 10.000 request/tháng có thể tốn chưa tới vài nghìn đồng, thay vì phải thuê một VM chạy 24/7 tốn vài trăm nghìn. Nhưng serverless không phải "viên đạn bạc" — nó có những cái bẫy về độ trễ, giới hạn thời gian, và cách thiết kế code buộc bạn phải hiểu rõ. Bài này sẽ giúp bạn nắm chắc cả sức mạnh lẫn giới hạn của serverless compute, với ba "ông lớn": AWS Lambda, Google Cloud Functions và Azure Functions.
Khái niệm cốt lõi
Serverless thực chất là gì?
"Serverless" không có nghĩa là không có server. Server vẫn tồn tại — chỉ là bạn không nhìn thấy, không quản lý, và không quan tâm tới nó nữa. Nhà cung cấp cloud che giấu hoàn toàn tầng hạ tầng. Bạn chỉ cung cấp đơn vị nhỏ nhất: một hàm (function) — một đoạn code nhận đầu vào, xử lý, trả về kết quả.
Mô hình này còn được gọi là FaaS (Function as a Service). Đây là tầng trừu tượng cao nhất trong chuỗi Compute: từ VM (bạn quản lý gần như mọi thứ) → Container (bạn quản lý ứng dụng và image) → Serverless (bạn chỉ quản lý code).
Bốn đặc điểm định hình serverless
1. Không quản lý server (no server management). Bạn không vá OS, không cấu hình auto-scaling group, không lo ổ đĩa đầy. Provider tự lo toàn bộ. Điều này giải phóng đội ngũ DevOps khỏi rất nhiều việc vận hành lặt vặt để tập trung vào logic nghiệp vụ.
2. Trả tiền theo lần thực thi (pay-per-execution). Đây là điểm khác biệt lớn nhất so với VM. Bạn không trả tiền theo giờ máy chạy, mà theo hai yếu tố: số lượng request (số lần hàm được gọi) và thời lượng thực thi × bộ nhớ cấp phát (đo bằng đơn vị GB-giây). Khi không ai gọi hàm, chi phí bằng 0. Với AWS Lambda, một triệu request đầu tiên mỗi tháng thậm chí còn miễn phí (free tier).
3. Auto-scale từ 0 tới N tức thì (scale to zero). Đây là siêu năng lực của serverless. Khi không có request, không có instance nào chạy — bạn không tốn tiền. Khi 1.000 request ập đến cùng lúc, provider tự động tạo ra 1.000 bản sao hàm để xử lý song song, rồi thu hồi khi xong. Bạn không phải cấu hình gì cả. So sánh: với VM, bạn phải dự đoán trước tải, cấu hình auto-scaling group, và luôn duy trì tối thiểu một máy chạy.
4. Stateless (không lưu trạng thái). Mỗi lần hàm chạy là một môi trường độc lập, không đảm bảo chia sẻ bộ nhớ với lần chạy trước. Bạn không thể lưu dữ liệu vào biến toàn cục rồi kỳ vọng lần gọi sau vẫn còn. Mọi trạng thái cần lưu phải đẩy ra ngoài: database, cache (Redis), hoặc object storage (S3). Đây vừa là ràng buộc, vừa là điều khiến serverless scale được vô hạn — vì bất kỳ instance nào cũng xử lý được bất kỳ request nào.
Cold start — cái bẫy độ trễ
Khi một hàm lâu không được gọi, provider thu hồi hết instance. Lần gọi tiếp theo, hệ thống phải khởi tạo lại môi trường: tải code, khởi động runtime (Node.js, Python, Java...), chạy code khởi tạo. Độ trễ thêm này gọi là cold start — có thể từ vài trăm mili-giây (Node.js, Python) tới vài giây (Java, .NET nặng).
Khi instance đã "ấm" (warm), các request tiếp theo được phục vụ ngay, không có độ trễ khởi tạo. Cold start là cái giá phải trả cho khả năng scale-to-zero, và là yếu tố then chốt khi quyết định serverless có phù hợp cho ứng dụng nhạy độ trễ hay không.
Ba nền tảng: Lambda, Cloud Functions, Azure Functions
Ba nhà cung cấp lớn đều có dịch vụ FaaS tương đương nhau về ý tưởng, khác nhau ở chi tiết:
- AWS Lambda — "ông tổ" của serverless (ra mắt 2014), hệ sinh thái trigger phong phú nhất (S3, API Gateway, DynamoDB Streams, EventBridge...). Giới hạn thời gian tối đa 15 phút mỗi lần chạy, bộ nhớ tối đa 10 GB.
- Google Cloud Functions — tích hợp chặt với hệ sinh thái GCP và Firebase, rất tiện cho ứng dụng mobile/web. Bản Gen 2 chạy trên nền Cloud Run, hỗ trợ tới 60 phút cho HTTP function.
- Azure Functions — mạnh về tích hợp với hệ sinh thái Microsoft (.NET, Office 365, Logic Apps). Có mô hình "binding" giúp kết nối input/output với dịch vụ khác mà ít code hơn. Consumption plan cũng scale-to-zero.
Tình huống thực tế
Ví dụ 1: Startup thương mại điện tử Việt Nam xử lý ảnh sản phẩm
Một sàn TMĐT vừa và nhỏ ở TP.HCM cho phép người bán tự đăng ảnh sản phẩm. Vấn đề: người bán tải lên ảnh gốc 5–8 MB chụp từ điện thoại, làm trang web tải chậm. Họ cần tự động tạo ra ba phiên bản thumbnail (nhỏ, vừa, lớn) cho mỗi ảnh.
Cách cũ (VM): duy trì một server chạy 24/7 với ImageMagick, nhận ảnh và xử lý. Tải ảnh không đều — cao điểm buổi tối, gần như trống ban ngày. Nghĩa là họ trả tiền cho server ngồi không phần lớn thời gian.
Cách serverless: họ dựng một AWS Lambda với trigger là sự kiện "có object mới upload lên S3". Mỗi khi người bán đăng ảnh, ảnh vào S3 → S3 phát sự kiện → Lambda tự động chạy, resize thành ba kích thước, lưu lại vào S3. Không có ảnh mới thì không có Lambda nào chạy.
Kết quả: với khoảng 40.000 ảnh upload mỗi tháng, mỗi lần resize mất ~800ms ở mức 512 MB bộ nhớ, chi phí Lambda rơi vào khoảng dưới 2 USD/tháng — rẻ hơn nhiều so với thuê một VM t3.small chạy cả tháng (~15 USD). Và khi có đợt khuyến mãi khiến lượng upload tăng gấp 10, hệ thống tự scale mà không ai phải đụng tay.
Bài học: Serverless tỏa sáng với các tác vụ theo sự kiện, không liên tục, có tải dao động mạnh. Đây gần như là "trường hợp mẫu" của serverless.
Ví dụ 2: Fintech Đông Nam Á và bài toán cold start
Một công ty fintech ở Singapore phục vụ thị trường khu vực (gồm cả Việt Nam) dùng Azure Functions để xây API kiểm tra hạn mức tín dụng, gọi từ ứng dụng mobile. Ban đầu họ dùng Consumption plan (scale-to-zero) vì rẻ.
Vấn đề nảy sinh: vào sáng sớm khi lượng người dùng mở app còn thưa, nhiều request đầu tiên bị cold start. Runtime .NET của họ mất tới 2–3 giây để khởi động lạnh. Với một app tài chính mà người dùng kỳ vọng phản hồi tức thì, độ trễ này gây tỷ lệ bỏ cuộc cao ở màn hình kiểm tra hạn mức.
Giải pháp: họ chuyển những hàm nhạy độ trễ sang Premium plan của Azure Functions, giữ sẵn một số instance "ấm" (pre-warmed instances) luôn sẵn sàng. Cold start biến mất với các hàm quan trọng. Những hàm không nhạy độ trễ (như job tổng hợp báo cáo ban đêm) vẫn giữ ở Consumption plan để tiết kiệm.
Bài học: Serverless không tự động phù hợp cho mọi API độ trễ thấp. Bạn phải hiểu cold start, chọn runtime khởi động nhanh (Node.js/Python thay vì Java/.NET nặng khi có thể), và dùng cơ chế "provisioned/premium" cho phần nhạy cảm. Đừng mù quáng chọn Consumption plan chỉ vì nó rẻ nhất.
Ví dụ 3: Đội nội bộ dựng chatbot với Google Cloud Functions
Một doanh nghiệp bán lẻ ở Hà Nội muốn làm chatbot trả lời câu hỏi khách hàng qua webhook từ nền tảng chat. Lượng tin nhắn không đoán trước được: có ngày vài trăm, ngày khuyến mãi lên tới hàng chục nghìn.
Họ dùng Google Cloud Functions với HTTP trigger làm điểm nhận webhook. Mỗi tin nhắn kích hoạt một hàm, gọi tới một API xử lý ngôn ngữ, rồi trả lời. Vì scale-to-zero và tự động scale lên, họ không phải lo về việc "server sập vì quá tải" trong ngày cao điểm — Cloud Functions tự nhân bản instance.
Một chi tiết quan trọng họ học được: phải đặt giới hạn số instance tối đa (max instances). Ban đầu không đặt, một đợt spam tin nhắn khiến hàm scale lên hàng nghìn instance, gọi API bên thứ ba quá nhiều lần và suýt phát sinh hóa đơn ngoài dự kiến. Sau khi đặt trần max instances = 50, họ kiểm soát được cả chi phí lẫn tải xuống các dịch vụ phụ thuộc.
Bài học: Auto-scale vô hạn là con dao hai lưỡi. Luôn đặt giới hạn concurrency/max instances để tránh "hóa đơn kinh hoàng" và bảo vệ các dịch vụ downstream (database, API bên thứ ba) khỏi bị đè bẹp.
Hướng dẫn từng bước
Dưới đây là quy trình chung để triển khai một serverless function đầu tiên (minh họa với AWS Lambda, nguyên tắc tương tự cho GCP và Azure):
- Xác định trigger. Điều gì sẽ kích hoạt hàm? HTTP request (qua API Gateway), upload file (S3), bản ghi mới (DynamoDB/database), lịch định kỳ (cron/EventBridge), hay message trong hàng đợi? Chọn đúng trigger là bước quyết định kiến trúc.
- Viết hàm với đúng "chữ ký" handler. Mỗi provider quy định một hàm entry-point nhận
event(dữ liệu sự kiện) vàcontext(thông tin runtime). Ví dụ Lambda Node.js:exports.handler = async (event, context) => { ... }. Giữ hàm nhỏ, làm một việc duy nhất.
- Đẩy trạng thái ra ngoài. Vì hàm stateless, mọi dữ liệu cần lưu phải ghi vào database, cache hoặc storage. Không bao giờ dựa vào biến toàn cục để giữ dữ liệu giữa các lần gọi (dù có thể tận dụng biến toàn cục để cache kết nối database nhằm giảm cold start — mẹo ở phần sau).
- Cấu hình bộ nhớ và timeout. Chọn mức bộ nhớ (ví dụ 128 MB tới 10 GB). Lưu ý: ở Lambda, tăng bộ nhớ cũng tăng CPU tương ứng — đôi khi tăng RAM giúp hàm chạy nhanh hơn và rẻ hơn tổng thể. Đặt timeout hợp lý (mặc định thường 3 giây, nhớ nới nếu hàm gọi API chậm).
- Gán quyền tối thiểu (least privilege). Cấp cho hàm một IAM role chỉ với đúng quyền nó cần (ví dụ chỉ đọc/ghi một bucket S3 cụ thể). Đây là nguyên tắc bảo mật quan trọng — chúng ta sẽ đào sâu ở bài về IAM.
- Triển khai và kiểm thử. Deploy qua Console, CLI, hoặc tốt hơn là Infrastructure as Code (Terraform/CloudFormation — sẽ học ở bài sau). Test bằng cách kích hoạt trigger thật và kiểm tra log.
- Giám sát. Bật logging (CloudWatch Logs) và theo dõi các chỉ số: số lần gọi, thời lượng, tỷ lệ lỗi, số lần bị throttle. Đây là mắt và tai của bạn với một hệ thống mà bạn không thể SSH vào.
Lỗi thường gặp & mẹo
Lỗi: Dùng serverless cho tác vụ chạy dài. Lambda giới hạn 15 phút. Nếu bạn cần xử lý một file 5 GB hay chạy một job ETL 2 tiếng, serverless FaaS không hợp. Hãy chia nhỏ tác vụ, hoặc dùng container/VM cho phần chạy dài.
Lỗi: Coi thường cold start. Chọn Java cho một API cần phản hồi dưới 100ms trên Consumption plan sẽ khiến bạn khổ sở. Mẹo: ưu tiên runtime nhẹ (Node.js, Python, Go), giảm kích thước gói code, và dùng provisioned concurrency (Lambda) / premium plan (Azure) cho hàm nhạy độ trễ.
Mẹo: Tái sử dụng kết nối ngoài handler. Đặt code khởi tạo kết nối database ở phạm vi toàn cục (ngoài hàm handler). Khi instance còn "ấm", kết nối được tái dùng qua nhiều request, giảm đáng kể độ trễ. Đây không mâu thuẫn với stateless — bạn chỉ tái dùng tài nguyên, không lưu dữ liệu nghiệp vụ.
Lỗi: Không đặt giới hạn concurrency. Như ví dụ chatbot, auto-scale vô hạn có thể gây hóa đơn sốc và đè sập database. Luôn đặt reserved/max concurrency phù hợp.
Lỗi: Đóng gói hàm quá nặng. Gói code hàng trăm MB làm cold start chậm và deploy lâu. Chỉ đóng gói đúng dependency cần thiết; tách phần dùng chung thành Layer (Lambda) nếu cần.
Mẹo: Coi chừng chi phí ẩn. Bản thân Lambda rẻ, nhưng nó thường gọi tới các dịch vụ khác (API Gateway, NAT Gateway, ghi log CloudWatch, gọi database). Chi phí thật của một kiến trúc serverless là tổng của cả hệ, không chỉ dòng "Lambda" trên hóa đơn.
Mẹo: Idempotency (bất biến khi gọi lại). Với trigger dựa trên hàng đợi hay stream, một sự kiện có thể được gửi lại (at-least-once delivery). Hãy thiết kế hàm sao cho chạy hai lần trên cùng một sự kiện không gây hậu quả kép (ví dụ: không trừ tiền hai lần).
Bài tập thực hành
- Vẽ sơ đồ luồng. Chọn ví dụ resize ảnh ở trên. Vẽ luồng: người dùng upload → S3 → sự kiện → Lambda → S3. Đánh dấu điểm nào là trigger, điểm nào là nơi lưu trạng thái.
- Ước tính chi phí. Giả sử một hàm được gọi 2 triệu lần/tháng, mỗi lần chạy 500ms ở 512 MB. Tra bảng giá AWS Lambda và tính ra chi phí ước lượng. So sánh với chi phí thuê một VM tương đương chạy 24/7.
- Quyết định kiến trúc. Với mỗi tình huống sau, quyết định serverless có phù hợp không và giải thích: (a) API xử lý thanh toán cần phản hồi < 200ms, tải liên tục cao; (b) job gửi email tổng kết mỗi cuối tháng; (c) xử lý video dài 30 phút; (d) webhook nhận sự kiện từ cổng thanh toán với tải bất thường.
- Nếu có tài khoản cloud (free tier): Tạo một hàm "Hello World" trên Lambda hoặc Cloud Functions, gắn HTTP trigger, gọi thử bằng trình duyệt, rồi mở log xem thời lượng thực thi và tìm dấu vết cold start ở lần gọi đầu tiên.
Tóm tắt
Serverless compute (FaaS) là tầng trừu tượng cao nhất của Compute: bạn chỉ viết hàm, provider lo toàn bộ hạ tầng. Bốn đặc điểm cốt lõi là không quản lý server, trả tiền theo lần thực thi, auto-scale từ 0 tới N, và stateless. Ba nền tảng lớn — AWS Lambda, Google Cloud Functions, Azure Functions — cùng chung ý tưởng, khác ở giới hạn thời gian, runtime và cách tích hợp.
Serverless tỏa sáng với tác vụ theo sự kiện, tải dao động mạnh, không liên tục — như resize ảnh, xử lý webhook, job định kỳ. Nhưng nó không phải viên đạn bạc: cold start gây độ trễ với ứng dụng nhạy cảm, giới hạn thời gian không hợp cho tác vụ dài, và auto-scale vô hạn cần được kiểm soát bằng giới hạn concurrency. Thiết kế tốt đòi hỏi hàm nhỏ, stateless, idempotent, quyền tối thiểu, và giám sát chặt chẽ.
Nắm vững serverless, bạn có thêm một công cụ mạnh để xây hệ thống vừa tiết kiệm vừa co giãn linh hoạt — một kỹ năng rất được săn đón trong nghề DevOps ngày nay.