Mở đầu — vì sao bài này quan trọng
Nếu bạn đã đi qua các bài về deploy container và Kubernetes, hẳn bạn đã quen với hình dung: đóng gói image, đẩy lên registry, rồi cho orchestrator kéo image về chạy trong pod. Nhưng thế giới serverless — với AWS Lambda, Google Cloud Functions, Azure Functions — vận hành theo một logic hoàn toàn khác. Ở đây không có server để bạn SSH vào, không có pod đang chạy để bạn kubectl exec, thậm chí không có gì "đang chạy" cho tới khi có request gọi tới. Function của bạn chỉ tồn tại dưới dạng một gói code cộng với cấu hình runtime, ngủ yên trong hạ tầng của nhà cung cấp, và được đánh thức theo sự kiện.
Sự khác biệt này thay đổi hoàn toàn cách bạn thiết kế CI/CD pipeline. Nhiều đội ngũ mắc sai lầm khi bê nguyên tư duy deploy container sang serverless: họ lo về việc "khởi động lại service", "health check pod", "rolling update từng replica" — trong khi những khái niệm đó gần như không tồn tại. Ngược lại, họ lại bỏ qua những mối bận tâm rất riêng của serverless: cold start, versioning, alias, packaging đúng cách để không vượt giới hạn dung lượng.
Ở Việt Nam, ngày càng nhiều startup và đội ngũ sản phẩm chọn serverless cho backend của API, cho các job xử lý ảnh, gửi thông báo, xử lý webhook thanh toán từ MoMo hay VNPay. Lý do rất thực tế: không phải nuôi server, trả tiền theo số lần gọi, tự động scale khi có traffic đột biến. Nhưng để đưa serverless vào production một cách chuyên nghiệp, bạn cần một pipeline được thiết kế đúng với đặc thù của nó. Đó chính là nội dung của bài này.
Khái niệm cốt lõi
Function = gói code + cấu hình runtime, không phải một server
Điểm mấu chốt đầu tiên: đơn vị deploy của serverless không phải một image chạy liên tục, mà là một deployment package — thường là một file .zip chứa code và dependencies, cộng với cấu hình runtime (runtime nào: Node.js 20, Python 3.12; memory bao nhiêu MB; timeout bao nhiêu giây; biến môi trường gì; IAM role nào).
Với AWS Lambda, bạn có hai cách đóng gói:
- Zip package: nén code +
node_modules/site-packages thành file zip, upload lên Lambda (trực tiếp hoặc qua S3). Giới hạn 50 MB khi upload trực tiếp, 250 MB sau khi giải nén. - Container image: từ 2020, Lambda hỗ trợ đóng gói function thành image (tối đa 10 GB) đẩy lên ECR. Cách này tiện khi dependencies quá lớn (ví dụ thư viện ML), nhưng bản chất vẫn là "gói code + cấu hình", không phải chạy container như trong K8s.
kubectl apply, không có Helm chart, không có readiness probe. Thay vào đó, bạn dùng công cụ đóng gói và đẩy code lên: Serverless Framework, AWS SAM, Terraform, hoặc AWS CLI trực tiếp.Cold start — mối bận tâm đặc trưng của serverless
Đây là khái niệm bạn bắt buộc phải hiểu vì nó ảnh hưởng tới cả cách viết code lẫn cách thiết kế pipeline. Khi một function lâu không được gọi, nhà cung cấp sẽ thu hồi tài nguyên. Lần gọi tiếp theo, họ phải khởi tạo lại môi trường: tải code, khởi động runtime, chạy phần code ngoài handler. Độ trễ thêm này gọi là cold start, thường từ vài trăm mili-giây tới vài giây tùy runtime và kích thước package.
Cold start liên quan trực tiếp tới CI/CD ở mấy điểm:
- Package càng nhẹ, cold start càng nhanh. Vì thế bước build trong pipeline nên loại bỏ dev-dependencies, tree-shake, chỉ đóng gói những gì cần thiết.
- Sau mỗi lần deploy, mọi phiên bản đang "ấm" đều bị thay thế, nghĩa là ngay sau deploy sẽ có một loạt cold start. Nếu function phục vụ traffic nhạy cảm về độ trễ, bạn cân nhắc provisioned concurrency (giữ sẵn một số instance ấm) và cần warm-up chúng như một bước trong pipeline.
Version và alias — nền tảng của rollback và triển khai an toàn
Đây là cơ chế mà bạn sẽ dùng để làm rollback và blue-green cho serverless.
- Version: mỗi lần bạn "publish" một function, Lambda tạo ra một version bất biến (immutable), đánh số 1, 2, 3... Code và cấu hình của một version đã publish không bao giờ thay đổi.
$LATESTlà bản đang chỉnh sửa, còn version có số là bản đóng băng. - Alias: là một con trỏ có tên (ví dụ
prod,staging) trỏ tới một version cụ thể. Client gọi qua alias, không gọi thẳng version.
prod trỏ 90% traffic vào version 7 và 10% vào version 8 — đó chính là canary. Và khi cần rollback, bạn chỉ cần trỏ alias prod về version cũ. Không rebuild, không redeploy, chỉ đổi con trỏ — thao tác diễn ra gần như tức thì.Google Cloud Functions (thế hệ 2, chạy trên Cloud Run) và Azure Functions (deployment slots) có cơ chế tương đương, dù tên gọi khác nhau.
Tình huống thực tế
Tình huống 1: Startup fintech tại TP.HCM xử lý webhook thanh toán
Một startup ví điện tử tại TP.HCM dùng Lambda để nhận webhook IPN (Instant Payment Notification) từ VNPay và MoMo. Mỗi giao dịch thành công, cổng thanh toán gọi tới function của họ để cập nhật đơn hàng. Traffic dao động mạnh: bình thường vài chục request/phút, nhưng đợt sale 12/12 vọt lên vài nghìn request/phút.
Ban đầu đội dev deploy bằng cách... vào AWS Console, upload zip thủ công. Hậu quả: một lần deploy trực tiếp lên $LATEST khi đang có traffic, code mới có bug parse sai chữ ký, khiến 40 phút webhook bị trả lỗi 500 — cổng thanh toán retry nhưng nhiều đơn vẫn bị treo trạng thái. Không có cách rollback nhanh vì bản cũ đã bị đè.
Sau sự cố, họ xây pipeline với Serverless Framework trên GitHub Actions: mỗi merge vào main sẽ build, publish một version mới, deploy lên alias staging, chạy smoke test, rồi mới dịch chuyển alias prod theo canary 10% trong 5 phút trước khi lên 100%. Version cũ luôn được giữ lại. Lần sau khi có lỗi tương tự, họ chỉ cần một lệnh trỏ alias prod về version trước — khôi phục trong chưa đầy 30 giây.
Bài học: với serverless, đừng bao giờ deploy thẳng lên $LATEST đang phục vụ production. Publish version + dùng alias là điều kiện cần để có rollback tức thời.
Tình huống 2: Đội sản phẩm thương mại điện tử và bài toán package phình to
Một sàn thương mại điện tử tầm trung ở Hà Nội dùng Google Cloud Functions cho service tạo thumbnail ảnh sản phẩm bằng thư viện sharp. Pipeline của họ ban đầu đóng gói cả devDependencies, khiến package nặng ~180 MB. Cold start lên tới 4-5 giây, và trong khung giờ thấp điểm ban đêm, khi function bị thu hồi, những request thumbnail đầu tiên buổi sáng chậm đến mức trang danh mục load lỗi ảnh.
Đội DevOps sửa pipeline: thêm bước npm ci --omit=dev, tách phần khởi tạo client Cloud Storage ra ngoài handler để tái sử dụng qua các lần gọi, và bật minimum instances = 1 cho function này. Package giảm còn ~45 MB, cold start còn ~900 ms, và nhờ giữ 1 instance ấm, request đầu ngày không còn cold start. Chi phí giữ 1 instance thấp hơn nhiều so với thiệt hại UX.
Bài học: trong pipeline serverless, tối ưu package và tách khởi tạo tài nguyên ra ngoài handler không phải "tối ưu cho vui" — nó ảnh hưởng trực tiếp tới cold start và trải nghiệm người dùng.
Tình huống 3: Đội SaaS Đông Nam Á quản lý hàng chục function
Một công ty SaaS phục vụ khách hàng khắp Đông Nam Á có khoảng 60 Lambda function cho các job nền: gửi email, đồng bộ dữ liệu, tạo báo cáo. Vấn đề là mỗi function có một cách deploy khác nhau, dev nào tiện gì làm nấy. Kết quả: không ai biết chắc phiên bản nào đang chạy ở production.
Họ chuẩn hóa bằng AWS SAM và một template pipeline dùng chung. Mọi function đều đi qua đúng các bước: lint, test, build zip, deploy lên môi trường dev khi push nhánh feature, và deploy lên prod qua SAM với AutoPublishAlias cộng DeploymentPreference kiểu Canary10Percent5Minutes. SAM tự publish version, tự dịch alias theo canary, và tự gắn CloudWatch Alarm để auto-rollback nếu tỉ lệ lỗi tăng. Từ chỗ hỗn loạn, họ có một quy trình nhất quán cho cả 60 function.
Bài học: serverless dễ deploy tới mức mỗi người làm một kiểu — và đó lại là rủi ro. Chuẩn hóa bằng framework (SAM/Serverless) và tận dụng cơ chế deployment preference tích hợp sẵn sẽ tiết kiệm rất nhiều đau đầu.
Hướng dẫn từng bước
Dưới đây là một pipeline mẫu cho Lambda dùng Serverless Framework trên GitHub Actions. Bạn hoàn toàn có thể áp dụng logic tương tự cho SAM hay Terraform.
Bước 1 — Cấu hình đóng gói. Trong serverless.yml, khai báo runtime, memory, timeout và bật AutoPublishAlias để mỗi lần deploy tự publish version mới và trỏ alias.
service: payment-webhook
provider:
name: aws
runtime: nodejs20.x
memorySize: 256
timeout: 15
functions:
ipnHandler:
handler: src/handler.ipn
events:
- httpApi:
path: /ipn
method: post
package:
patterns:
- '!node_modules/**/aws-sdk/**' # bỏ SDK đã có sẵn trong runtime
- '!**/*.test.js'
Bước 2 — Job build và test. Cài dependencies production, chạy lint và unit test, rồi đóng gói. Đây là nơi bạn siết dung lượng package để giảm cold start.
- run: npm ci
- run: npm run lint && npm test
- run: npm ci --omit=dev # loại dev-deps trước khi đóng gói
Bước 3 — Deploy lên môi trường staging. Dùng OIDC để GitHub Actions lấy quyền tạm thời vào AWS (không lưu access key tĩnh trong secret — an toàn hơn nhiều).
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
aws-region: ap-southeast-1
- run: npx serverless deploy --stage staging
Bước 4 — Smoke test trên staging. Gọi thẳng endpoint vừa deploy với một payload mẫu, kiểm tra trả về đúng. Nếu fail, dừng pipeline.
- run: curl -f -X POST "$STAGING_URL/ipn" -d @test/fixtures/ipn.json
Bước 5 — Deploy production theo canary. Với Serverless Framework, dùng plugin serverless-plugin-canary-deployments; với SAM, dùng DeploymentPreference. Cấu hình dịch chuyển alias prod dần dần và gắn alarm để tự rollback.
# trong serverless.yml
functions:
ipnHandler:
deploymentSettings:
type: Canary10Percent5Minutes
alias: prod
alarms:
- IpnErrorAlarm # CloudWatch alarm theo dõi tỉ lệ lỗi
Bước 6 — Rollback khi cần. Nếu alarm không tự kích hoạt rollback nhưng bạn phát hiện vấn đề, chỉ cần trỏ alias về version trước:
aws lambda update-alias \
--function-name payment-webhook \
--name prod \
--function-version 7
Bước 7 — Warm-up (tùy chọn). Nếu function nhạy cảm về độ trễ, sau khi deploy hãy bật provisioned concurrency và gọi vài request để làm ấm trước khi dịch full traffic.
Lỗi thường gặp & mẹo
- Deploy thẳng lên
$LATESTproduction. Đây là lỗi phổ biến nhất và nguy hiểm nhất. Luôn publish version + dùng alias để có đường lui. - Đóng gói cả dev-dependencies. Package phình to làm cold start chậm và có khi vượt giới hạn dung lượng. Luôn
--omit=dev(Node) hoặc dùng layer/requirements gọn (Python) trước khi đóng gói. - Khởi tạo tài nguyên bên trong handler. Mở kết nối database, khởi tạo SDK client bên trong handler khiến mỗi lần gọi phải làm lại. Đặt chúng ở phạm vi ngoài handler (module scope) để tái sử dụng qua các lần gọi ấm.
- Quên rằng deploy = một loạt cold start. Ngay sau deploy, mọi instance đều nguội. Với traffic quan trọng, hãy warm-up hoặc dùng provisioned concurrency, đừng deploy vào giờ cao điểm mà không chuẩn bị.
- Lưu access key AWS tĩnh trong GitHub Secrets. Dùng OIDC để lấy credential tạm thời — không có key dài hạn để rò rỉ. (Chi tiết về quản lý secret bạn sẽ gặp lại ở bài chuyên sâu về secret management.)
- Không gắn alarm cho canary. Canary mà không có metric giám sát thì chỉ là "chờ may rủi". Gắn CloudWatch Alarm theo error rate/latency để rollback tự động.
- Mẹo timeout và concurrency. Đặt timeout sát với thực tế (webhook nên ngắn), và cấu hình reserved concurrency để một function lỗi không "ăn" hết quota concurrency của cả tài khoản, làm sập các function khác.
- Mẹo test local. Dùng
serverless invoke localhoặcsam local invokeđể chạy thử function ngay trong pipeline hoặc trên máy dev, bắt lỗi sớm trước khi lên cloud.
Bài tập thực hành
- Dựng pipeline cơ bản. Tạo một Lambda function đơn giản (ví dụ nhận JSON, trả về đã xử lý) và viết pipeline GitHub Actions gồm các bước: lint, test, đóng gói với
--omit=dev, deploy lên stagedev. Kiểm tra kích thước package trước và sau khi loại dev-deps, ghi lại con số chênh lệch.
- Thực hành version + alias. Deploy hai phiên bản khác nhau của function (ví dụ v1 trả "hello", v2 trả "xin chào"). Tạo alias
prodtrỏ v1, sau đó dùngupdate-aliasđể chuyển sang v2, rồi rollback về v1. Xác nhận thao tác diễn ra tức thời mà không cần redeploy.
- Đo cold start. Cấu hình một function với package nặng (cố tình thêm dependencies thừa) và một bản gọn. Gọi mỗi bản sau khi để nguội vài phút, đo thời gian phản hồi lần đầu qua CloudWatch Logs (trường
Init Duration). So sánh và rút ra kết luận.
- Nâng cao — canary với auto-rollback. Dùng SAM với
DeploymentPreference: Canary10Percent5Minutesvà một CloudWatch Alarm theo error rate. Cố tình deploy một version lỗi và quan sát xem SAM có tự rollback không.
Tóm tắt
Serverless CI/CD khác biệt căn bản so với deploy container: không pod, không Kubernetes, không orchestrator. Đơn vị deploy của bạn là gói code cộng cấu hình runtime, được đóng gói thành zip hoặc image rồi đẩy lên nhà cung cấp. Vì đặc thù này, pipeline serverless đơn giản hơn ở phần điều phối nhưng lại phải bận tâm tới những vấn đề rất riêng.
Ba trụ cột bạn cần nắm chắc: thứ nhất, cold start — tối ưu package và cách khởi tạo tài nguyên để giảm độ trễ, và nhớ rằng mỗi lần deploy sẽ kéo theo một loạt cold start. Thứ hai, version và alias — nền tảng để làm canary và rollback tức thời chỉ bằng cách dịch con trỏ, không cần rebuild. Thứ ba, chuẩn hóa bằng framework như Serverless Framework hoặc AWS SAM để cả đội có một quy trình nhất quán, với deployment preference và alarm giám sát tích hợp sẵn.
Ba tình huống thực tế — từ webhook thanh toán fintech, thumbnail thương mại điện tử, tới đội SaaS quản lý 60 function — cho thấy cùng một bài học: serverless dễ deploy đến mức dễ deploy ẩu, và cái giá của việc thiếu version/alias, thiếu tối ưu package, thiếu chuẩn hóa luôn xuất hiện đúng vào lúc traffic cao nhất. Một pipeline được thiết kế đúng với đặc thù serverless sẽ giúp bạn tận dụng trọn vẹn sức mạnh của mô hình này mà vẫn ngủ ngon.