Mở đầu — vì sao bài này quan trọng
Nếu bạn theo dõi các cuộc tranh luận kỹ thuật trên các nhóm DevOps Việt Nam, hẳn bạn đã thấy một câu hỏi lặp đi lặp lại: "Team mình nên tách microservices không, hay cứ giữ monolith?". Và thường thì câu trả lời của nhiều người là "microservices xịn hơn, hiện đại hơn". Đó là một cái bẫy tư duy nguy hiểm.
Là một DevOps Engineer, bạn không phải là người quyết định kiến trúc phần mềm — đó là việc của kiến trúc sư và đội phát triển. Nhưng bạn là người phải vận hành kiến trúc đó. Một quyết định "tách microservices" trên giấy tờ trông rất đẹp, nhưng nó đổ lên vai bạn một núi công việc: bạn phải xây pipeline CI/CD cho hàng chục service thay vì một, phải lo networking giữa các service, phải giám sát phân tán, phải xử lý việc một service chết kéo theo cả hệ thống lung lay.
Bài học này giúp bạn hiểu bản chất kỹ thuật của hai kiểu kiến trúc dưới góc nhìn vận hành, để bạn có thể ngồi vào bàn với đội dev và nói: "Nếu team chọn microservices, đây là cái giá về hạ tầng và quy trình mà chúng ta phải trả — và đây là cách chúng ta sẵn sàng cho nó". Đó mới là giá trị thật của một DevOps Engineer trưởng thành: không chạy theo trào lưu, mà giúp doanh nghiệp chọn đúng công cụ cho đúng bài toán.
Khái niệm cốt lõi
Monolith là gì dưới góc nhìn DevOps
Monolith (kiến trúc khối) là ứng dụng được đóng gói và triển khai như một đơn vị duy nhất. Một codebase, một artifact (ví dụ một file JAR, một Docker image, một process Django/Laravel), một database. Toàn bộ logic nghiệp vụ — từ xác thực người dùng, xử lý đơn hàng, tính toán khuyến mãi, đến gửi email — nằm chung trong một tiến trình.
Từ góc độ vận hành, monolith có những đặc điểm rất dễ chịu:
- Một pipeline, một deploy unit. Bạn build một image, push lên registry, deploy. Rollback cũng chỉ là quay về image cũ.
- Một database, một transaction. Khi cần đảm bảo tính nhất quán (ví dụ trừ tiền và tạo đơn hàng phải cùng thành công hoặc cùng thất bại), bạn chỉ cần một
BEGIN...COMMITtrong cùng một database. Đây là điểm mạnh cực lớn mà nhiều người quên. - Debug dễ. Một request đi từ đầu đến cuối trong một process. Log nằm một chỗ. Stack trace đầy đủ.
- Gọi hàm nội bộ nhanh và tin cậy. Module A gọi module B là một function call trong bộ nhớ — nanosecond, không thể "timeout", không "network partition".
Microservices là gì dưới góc nhìn DevOps
Microservices chia ứng dụng thành nhiều service nhỏ, mỗi service:
- Sở hữu một miền nghiệp vụ riêng (ví dụ: service đơn hàng, service thanh toán, service kho, service thông báo).
- Có codebase riêng, deploy riêng, thường có database riêng.
- Giao tiếp với nhau qua mạng — REST/gRPC (đồng bộ) hoặc message queue/event (bất đồng bộ).
- Deploy độc lập. Sửa service thanh toán thì chỉ deploy service thanh toán, không đụng đến service đơn hàng.
- Scale độc lập. Service tìm kiếm bị tải cao thì nhân bản riêng nó lên 10 replica, các service khác giữ nguyên. Tiết kiệm tài nguyên khi tải không đồng đều.
- Cô lập lỗi (fault isolation) — về lý thuyết. Service thông báo chết không nhất thiết làm sập service đặt hàng, nếu bạn thiết kế đúng.
- Tự do công nghệ. Team A dùng Go, team B dùng Python — mỗi service chọn stack phù hợp.
- N pipeline thay vì 1. 20 service là 20 pipeline CI/CD, 20 image, 20 chart Helm để bảo trì.
- Networking trở thành vấn đề sống còn. Function call biến thành network call — có độ trễ, có thể timeout, có thể mất gói. Bạn cần service discovery, load balancing, retry, circuit breaker.
- Giám sát phân tán. Một request khách hàng đi qua 6 service. Khi lỗi, log nằm rải rác 6 nơi. Bạn cần distributed tracing để lần theo.
- Nhất quán dữ liệu khó. Không còn transaction xuyên suốt. "Trừ kho và tạo đơn" giờ nằm ở hai database khác nhau — bạn phải xử lý bằng saga, eventual consistency, compensating transaction. Đây là nơi bug khó chịu nhất sinh ra.
Nguyên tắc vàng: microservices là đánh đổi, không phải nâng cấp
Điểm cốt lõi bạn phải khắc cốt ghi tâm: microservices không giải quyết vấn đề kỹ thuật, nó đổi một loại vấn đề (codebase lớn, deploy chậm) lấy một loại vấn đề khác (độ phức tạp phân tán). Việc đánh đổi này chỉ đáng khi tổ chức của bạn đủ lớn về mặt con người — nhiều team cần làm việc song song mà không dẫm chân nhau. Martin Fowler gọi đây là "microservice premium" (phí bảo hiểm microservices): bạn trả trước một khoản phức tạp về hạ tầng, và chỉ những hệ thống đủ lớn mới thu hồi được khoản đầu tư đó.
Tình huống thực tế
Tình huống 1 — Startup fintech ở TP.HCM tách microservices quá sớm
Một startup ví điện tử tại TP.HCM (gọi là "PayViet") với đội kỹ thuật 8 người. Ngay từ MVP, CTO quyết định làm microservices "cho đúng chuẩn công ty lớn": tách thành 12 service — user, wallet, transaction, notification, KYC, ledger, và nhiều nữa. Mỗi service một repo, một database Postgres riêng, deploy trên Kubernetes.
Kết quả sau 6 tháng: đội DevOps (chỉ có 1 người kiêm nhiệm) phải bảo trì 12 pipeline. Một tính năng nhỏ "hiển thị lịch sử giao dịch kèm tên người nhận" phải sửa 3 service và điều phối deploy đúng thứ tự. Tệ hơn, khi nạp tiền, "cộng số dư ví" ở wallet-service thành công nhưng "ghi sổ cái" ở ledger-service timeout — dẫn đến lệch sổ, một cơn ác mộng với fintech. Họ mất 3 tuần chỉ để xây cơ chế saga và reconciliation để vá lỗ hổng nhất quán này.
Bài học: Với đội 8 người và sản phẩm chưa rõ hình hài, monolith module hóa tốt (modular monolith) sẽ nhanh hơn, an toàn hơn nhiều. Microservices bắt họ trả "microservice premium" trong khi chưa hề có bài toán quy mô con người để thu hồi. Số service không nên vượt quá khả năng vận hành của đội.
Tình huống 2 — Sàn TMĐT bắt buộc phải tách để scale
Ngược lại, hãy nhìn một sàn thương mại điện tử lớn ở Đông Nam Á (mô hình như Tiki hoặc Shopee thời kỳ tăng trưởng nóng). Ban đầu họ chạy monolith Rails/PHP. Trong dịp sale 12/12, lưu lượng vào trang "tìm kiếm sản phẩm" và "giỏ hàng" tăng gấp 40 lần, trong khi "quản lý nhà bán hàng" gần như đứng yên.
Với monolith, để chịu tải tìm kiếm, họ phải scale toàn bộ ứng dụng lên hàng trăm instance — mỗi instance ngốn RAM cho cả những module không hề bị tải. Chi phí máy chủ đội lên khủng khiếp và vẫn chậm. Họ tách "search" và "cart" thành service riêng, scale chúng lên 200 replica trong giờ cao điểm, còn các module khác giữ ở mức thấp. Chi phí hạ tầng giờ cao điểm giảm đáng kể vì họ chỉ nhân bản đúng phần cần thiết, và mỗi team (search, cart, seller) deploy độc lập nhiều lần mỗi ngày.
Bài học: Đây là trường hợp microservices đáng đồng tiền. Tải không đồng đều + nhiều team + quy mô đủ lớn = ba tín hiệu cho thấy việc trả "phí bảo hiểm" microservices là xứng đáng. Điểm mấu chốt: họ tách theo áp lực thật (tải và tổ chức), không tách vì "cho hiện đại".
Tình huống 3 — Chiến lược "monolith trước, tách sau" của một scale-up
Một công ty SaaS logistics ở Hà Nội đi con đường trung dung và rất khôn ngoan. Họ khởi đầu bằng modular monolith: một codebase duy nhất, nhưng chia thành các module rạch ròi (order, routing, billing) với ranh giới rõ ràng — module này không được truy cập trực tiếp bảng dữ liệu của module kia, chỉ gọi qua interface nội bộ. Deploy vẫn là một unit.
Khi công ty lớn lên 40 kỹ sư và module routing (tối ưu lộ trình giao hàng) trở nên nặng về tính toán, họ chỉ tách riêng module này ra thành service để scale trên cụm máy nhiều CPU. Vì ranh giới đã rõ từ đầu, việc tách chỉ mất 2 tuần thay vì viết lại từ đầu.
Bài học: Modular monolith là "điểm khởi đầu mặc định" tuyệt vời. Nó cho bạn sự đơn giản vận hành của monolith hôm nay, và giữ tùy chọn tách microservices cho ngày mai khi có lý do thật. Bạn tách từng service khi nó thực sự "kêu cứu", không tách hàng loạt.
Hướng dẫn từng bước
Khi đội dev đặt lên bàn câu hỏi "monolith hay microservices?", đây là quy trình bạn — với vai trò DevOps — nên dẫn dắt:
- Đếm số team, đừng đếm số tính năng. Hỏi: "Có bao nhiêu team cần deploy độc lập mà không chờ nhau?". Nếu câu trả lời là 1-2 team, monolith gần như luôn thắng. Microservices trước hết giải bài toán tổ chức con người.
- Xác định điểm nghẽn scale thật. Dùng dữ liệu giám sát: có phần nào của hệ thống chịu tải lệch hẳn so với phần còn lại không? Nếu tải đồng đều, việc scale độc lập không mang lại lợi ích, và microservices mất một lý do quan trọng.
- Mặc định chọn modular monolith. Nếu không có lý do rõ ràng để tách ngay, khuyến nghị monolith có module hóa tốt. Yêu cầu đội thiết lập ranh giới module rõ ràng (mỗi module sở hữu bảng dữ liệu riêng, giao tiếp qua interface). Đây là "bảo hiểm" để tách dễ về sau.
- Nếu tách, chuẩn bị nền tảng vận hành trước. Trước khi service đầu tiên lên production, đảm bảo bạn đã có: registry ảnh, template pipeline CI/CD tái sử dụng được, service discovery (thường Kubernetes lo phần này qua Service/DNS), một chuẩn giám sát chung (metrics + logs + distributed tracing), và quy ước về API contract giữa các team.
- Tách theo miền nghiệp vụ (bounded context), không theo tầng kỹ thuật. Service phải là "đơn hàng", "thanh toán" — không phải "tầng database", "tầng API". Ranh giới sai sẽ khiến hai service luôn phải deploy cùng nhau, mất hết lợi ích.
- Tách dần (strangler fig pattern). Đừng viết lại toàn bộ. Bóc từng miền ra khỏi monolith một cách tuần tự, chuyển lưu lượng dần qua service mới, giữ monolith chạy song song cho đến khi ổn định.
- Đo lại và điều chỉnh. Sau mỗi lần tách, đo tần suất deploy, thời gian khôi phục sự cố, chi phí hạ tầng. Nếu các chỉ số xấu đi, đó là dấu hiệu bạn đã tách quá đà.
Lỗi thường gặp & mẹo
Lỗi 1 — Tách microservices để "cho hiện đại". Đây là lỗi phổ biến nhất ở Việt Nam. Microservices không phải huy hiệu công nghệ, nó là công cụ giải bài toán quy mô. Không có bài toán quy mô mà tách thì bạn chỉ tự tạo khổ.
Lỗi 2 — Distributed monolith. Cạm bẫy tệ nhất: bạn tách thành nhiều service nhưng chúng dính chặt vào nhau — deploy service A buộc phải deploy service B, chúng dùng chung một database. Bạn gánh mọi nhược điểm của cả hai kiến trúc: vừa phức tạp phân tán, vừa không deploy độc lập được. Mẹo phát hiện: nếu bạn không thể deploy một service mà không đụng service khác, bạn đang có distributed monolith.
Lỗi 3 — Chia sẻ database giữa các service. Khi hai service ghi chung một bảng, ranh giới sở hữu dữ liệu bị phá vỡ, và bạn không thể thay đổi schema độc lập. Quy tắc cứng: mỗi service sở hữu dữ liệu của riêng nó, service khác muốn dùng thì gọi qua API.
Lỗi 4 — Quên chi phí networking. Nhiều đội tách xong mới phát hiện độ trễ tăng vì một request phải nhảy qua 8 service tuần tự. Mẹo: hạn chế chuỗi gọi đồng bộ sâu; dùng giao tiếp bất đồng bộ (message queue) khi có thể để cô lập lỗi tốt hơn.
Lỗi 5 — Không có giám sát phân tán từ đầu. Với monolith bạn có thể "grep log là ra". Với microservices, không có distributed tracing thì việc debug một lỗi xuyên 6 service gần như bất khả. Chuẩn bị tracing trước, không phải sau khi cháy nhà.
Mẹo tiết kiệm: Với đội nhỏ, một monolith trên vài VM đơn giản thường rẻ và ổn định hơn nhiều so với một cụm Kubernetes chạy 15 microservices mà bạn phải trả tiền cho cả control plane lẫn overhead vận hành.
Bài tập thực hành
- Phân tích quyết định. Chọn một ứng dụng bạn từng làm hoặc biết rõ. Viết một trang trả lời: (a) hiện tại là monolith hay microservices? (b) có bao nhiêu team deploy độc lập? (c) tải có lệch giữa các phần không? (d) theo bạn kiến trúc hiện tại có phù hợp không, và nếu được chọn lại bạn sẽ làm gì?
- Nhận diện distributed monolith. Cho tình huống: hệ thống có 5 service nhưng tất cả dùng chung một database MySQL, và mỗi lần release phải deploy cả 5 cùng lúc. Liệt kê 3 dấu hiệu cho thấy đây là distributed monolith và đề xuất bước đầu tiên để "chữa".
- Thiết kế đường tách. Cho một monolith TMĐT gồm các module: user, catalog, cart, order, payment, notification. Giả sử dữ liệu giám sát cho thấy
catalog(tìm kiếm sản phẩm) chịu tải gấp 30 lần các module khác vào giờ cao điểm. Hãy chọn module nào nên tách ra trước tiên, giải thích lý do, và liệt kê 3 thứ về hạ tầng bạn cần chuẩn bị trước khi tách.
- So sánh chi phí vận hành. Lập một bảng hai cột (Monolith / Microservices) so sánh theo các tiêu chí: số pipeline CI/CD, độ khó rollback, độ khó đảm bảo nhất quán dữ liệu, khả năng scale từng phần, độ khó debug. Điền nhận định của bạn vào từng ô.
Tóm tắt
- Monolith = một codebase, một deploy unit, một database. Đơn giản để build, deploy, debug và đảm bảo nhất quán dữ liệu; nhưng khó scale từng phần và deploy chậm dần khi lớn lên.
- Microservices = nhiều service độc lập, deploy và scale riêng, cô lập lỗi tốt; đổi lại bạn gánh độ phức tạp phân tán: N pipeline, networking, giám sát phân tán, nhất quán dữ liệu khó.
- Microservices là đánh đổi, không phải nâng cấp. Nó giải bài toán quy mô tổ chức (nhiều team) và tải lệch, không phải bài toán "cho hiện đại".
- Đếm số team, không đếm số tính năng. Ít team thì monolith gần như luôn thắng.
- Modular monolith là điểm khởi đầu mặc định thông minh: đơn giản như monolith hôm nay, dễ tách như microservices ngày mai.
- Khi tách: theo bounded context, tách dần bằng strangler pattern, mỗi service sở hữu database riêng, và chuẩn bị nền tảng vận hành (registry, pipeline, tracing) trước.
- Cẩn thận cạm bẫy distributed monolith — mọi nhược điểm của cả hai kiến trúc gộp lại.