Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 27 — Containerization — Docker & Kubernetes cho BA

Technical BA Masterclass Bài 27/60

Mở đầu — vì sao bài này quan trọng

Bạn đang ngồi trong buổi họp refinement. Đội dev tranh luận: "Cái này phải đóng thêm một container riêng", "Pod đang bị OOMKilled lúc cao tải", "Mình scale lên 5 replica là chịu được Black Friday". Là Technical BA, bạn không cần viết Dockerfile hay vận hành cluster Kubernetes. Nhưng nếu bạn ngồi im như tượng vì không hiểu những từ này, bạn mất quyền tham gia vào những quyết định ảnh hưởng trực tiếp tới yêu cầu phi chức năng — khả năng mở rộng, chi phí hạ tầng, thời gian triển khai, và cả trải nghiệm người dùng khi hệ thống quá tải.

Containerization (đóng gói ứng dụng vào container) đã trở thành mặc định ở gần như mọi công ty công nghệ tại Việt Nam — từ các ngân hàng như Techcombank, VPBank đang hiện đại hóa core, tới các sàn như Tiki, Shopee, hay startup fintech như MoMo, ZaloPay. Khi bạn viết spec, ước lượng năng lực hệ thống, hay thiết kế kế hoạch triển khai, những khái niệm container và Kubernetes len lỏi vào từng góc.

Bài này không biến bạn thành DevOps. Mục tiêu là: bạn đọc hiểu một Dockerfile, hiểu Kubernetes giải quyết bài toán gì, biết những từ khóa nào ảnh hưởng đến NFR và chi phí, và quan trọng nhất — biết đặt câu hỏi đúng cho đội kỹ thuật.

Khái niệm cốt lõi

Container là gì — và vì sao nó ra đời

Hãy hình dung một câu chuyện kinh điển. Lập trình viên chạy ứng dụng ngon lành trên máy của mình, đẩy lên server thì lỗi. Lý do: máy dev có Java 17, server có Java 11; máy dev có thư viện libssl phiên bản này, server có phiên bản khác. Cụm từ huyền thoại "trên máy em chạy được mà" ra đời từ đó.

Container giải quyết đúng vấn đề này. Container đóng gói ứng dụng cùng toàn bộ phần phụ thuộc của nó — mã nguồn, runtime, thư viện hệ thống, biến môi trường — vào một gói chạy độc lập (isolated), giống hệt nhau ở mọi nơi. Dù chạy trên laptop của dev, trên server staging ở văn phòng, hay trên cloud AWS, container vẫn hành xử như nhau.

Nhiều người nhầm container với máy ảo (Virtual Machine — VM). Khác biệt then chốt: VM ảo hóa cả phần cứng và mang theo nguyên một hệ điều hành đầy đủ (vài GB), khởi động mất hàng phút. Container thì chia sẻ nhân (kernel) của hệ điều hành máy chủ, chỉ đóng gói phần cần thiết của ứng dụng (vài chục đến vài trăm MB), khởi động trong vài giây. Vì nhẹ, một server có thể chạy hàng chục, hàng trăm container — tận dụng phần cứng tốt hơn nhiều so với VM. Đây chính là lý do chi phí hạ tầng được tối ưu khi dùng container.

Image và Container — phân biệt hai khái niệm

Hai từ này hay bị dùng lẫn lộn nhưng khác nhau rõ ràng.

Image là một bản đóng gói tĩnh, bất biến (immutable) — giống như file cài đặt phần mềm, hay một "khuôn đúc". Image chứa mọi thứ cần để chạy: code, thư viện, cấu hình. Image có phiên bản (tag), ví dụ myapp:1.2.0.

Container là một thực thể đang chạy được tạo ra từ image — giống như chương trình đang chạy được tạo từ file cài đặt. Từ một image, bạn có thể tạo ra 1 hay 100 container giống hệt nhau.

Tính bất biến của image rất quan trọng với BA: nó nghĩa là cái bạn test ở môi trường staging chính xác là cái sẽ lên production, không có chuyện "cấu hình bị lệch". Điều này giảm rủi ro triển khai đáng kể.

Dockerfile — bản thiết kế của image (BA cần đọc hiểu)

Docker là công cụ phổ biến nhất để tạo và chạy container. Dockerfile là một file text mô tả từng bước để xây dựng image. Bạn không cần viết nó, nhưng đọc hiểu được sẽ giúp bạn rất nhiều khi review hay đánh giá rủi ro. Đây là một ví dụ điển hình:

FROM node:18-alpine          # Khởi đầu từ image nền có sẵn Node.js 18, bản nhẹ alpine
WORKDIR /app                 # Đặt thư mục làm việc bên trong container
COPY package*.json ./        # Sao chép file khai báo thư viện vào
RUN npm install --production # Chạy lệnh cài thư viện
COPY . .                     # Sao chép toàn bộ mã nguồn vào
EXPOSE 3000                  # Khai báo ứng dụng lắng nghe ở cổng 3000
ENV NODE_ENV=production      # Đặt biến môi trường
CMD ["node", "server.js"]    # Lệnh chạy khi container khởi động

Đọc từ trên xuống, bạn hiểu được: ứng dụng này dùng Node.js, mở cổng 3000, chạy ở chế độ production. Với BA, ba dòng đáng chú ý là FROM (image nền — liên quan đến lỗ hổng bảo mật nếu image cũ), EXPOSE (cổng — liên quan đến thiết kế tích hợp), và ENV (biến môi trường — liên quan đến cấu hình và bí mật, secrets).

Kubernetes — khi container nhiều lên thì cần "nhạc trưởng"

Một container thì Docker quản lý ổn. Nhưng production thực tế có hàng trăm container chạy trên nhiều server. Ai sẽ khởi động lại container khi nó chết? Ai phân bổ container lên server nào? Ai tự động tăng số lượng container khi lượng truy cập tăng đột biến? Ai chia tải đều giữa các container?

Đó là việc của Kubernetes (viết tắt K8s) — một nền tảng điều phối container (container orchestration). Hãy hình dung Kubernetes như một người nhạc trưởng điều khiển dàn nhạc gồm hàng trăm container.

Vài khái niệm Kubernetes mà BA nên thuộc nằm lòng:

  • Pod: đơn vị nhỏ nhất Kubernetes quản lý, thường chứa một container (đôi khi vài container gắn bó). Khi nói "pod bị chết", nghĩa là một bản chạy của ứng dụng gặp sự cố.
  • Replica: số bản sao của một pod chạy song song. replicas: 5 nghĩa là có 5 bản ứng dụng chạy cùng lúc để chia tải và chống lỗi.
  • Service: cổng vào ổn định để truy cập các pod. Vì pod có thể chết và tạo lại liên tục (địa chỉ thay đổi), Service đóng vai trò "địa chỉ cố định" và chia tải (load balancing).
  • Deployment: bản mô tả "tôi muốn chạy ứng dụng X, 5 bản, phiên bản 1.2.0". Kubernetes tự đảm bảo trạng thái thực tế khớp với mong muốn này.
  • Auto-scaling: tự động tăng/giảm số replica theo tải. Đây là điểm cực kỳ liên quan đến NFR về khả năng mở rộng.
  • Namespace: phân vùng logic để tách biệt môi trường hay nhóm (ví dụ dev, staging, production).
Triết lý cốt lõi của Kubernetes là declarative (khai báo): bạn mô tả trạng thái mong muốn ("tôi cần 5 bản, mỗi bản tối đa 512MB RAM"), Kubernetes tự lo phần còn lại — tự khởi động lại pod chết, tự phân bổ, tự self-healing. BA viết NFR theo tư duy này rất tự nhiên: bạn mô tả "cái cần đạt", không phải "cách làm".

Tình huống thực tế

Tình huống 1 — Sàn TMĐT và cơn sốt 12/12

Một sàn thương mại điện tử tại TP.HCM (gọi là "ShopFast") chạy chiến dịch 12/12. Ngày thường họ có khoảng 8.000 người dùng đồng thời, hệ thống chạy 6 replica thoải mái. Đến khung giờ flash sale 12h trưa ngày 12/12, lượng truy cập vọt lên 60.000 người đồng thời trong 15 phút.

Trước đây, khi còn dùng server cố định, mỗi mùa sale họ phải thuê thêm server cả tháng (lãng phí vì chỉ dùng vài giờ) hoặc chấp nhận sập trang. Sau khi chuyển sang Kubernetes với auto-scaling, họ cấu hình: khi CPU trung bình vượt 70%, tự tăng replica, tối đa 40 bản. Trong flash sale, hệ thống tự nhảy từ 6 lên 38 replica trong 90 giây, rồi tự co lại sau khi đợt cao điểm qua đi. Hóa đơn cloud chỉ tăng trong đúng vài giờ cao tải.

Vai trò của BA ở đây không phải cấu hình auto-scaling, mà là cung cấp dữ liệu để cấu hình đúng: lượng truy cập đỉnh dự kiến, thời gian cao điểm, ngưỡng phản hồi chấp nhận được. BA viết NFR: "Hệ thống phải phục vụ 60.000 người dùng đồng thời với thời gian phản hồi dưới 2 giây ở phân vị 95". Từ con số đó, đội kỹ thuật mới đặt được ngưỡng scale.

Bài học: Container và Kubernetes biến khả năng mở rộng thành một tham số có thể cấu hình. Nhưng tham số đó chỉ đúng khi BA cung cấp được số liệu kinh doanh chuẩn. NFR mơ hồ ("phải chịu được tải cao") thì auto-scaling cũng vô dụng.

Tình huống 2 — Ngân hàng và bài toán môi trường nhất quán

Một ngân hàng cỡ vừa ở Hà Nội phát triển ứng dụng cho vay tiêu dùng. Đội QA liên tục phàn nàn: lỗi tìm thấy ở staging nhưng dev không tái hiện được ở máy họ, còn tính năng test xong lại lỗi khác khi lên production. Mỗi lần như vậy tốn 2–3 ngày điều tra, dự án chậm tiến độ liên miên.

Nguyên nhân: ba môi trường (dev, staging, production) có cấu hình hệ điều hành, phiên bản thư viện, biến môi trường khác nhau. Sau khi đóng gói ứng dụng vào container, cùng một image bất biến loan-app:2.3.1 được dùng xuyên suốt cả ba môi trường — chỉ thay đổi cấu hình bên ngoài (như chuỗi kết nối database). Số lỗi "không tái hiện được" giảm rõ rệt, thời gian điều tra lỗi môi trường gần như biến mất.

Với BA, điều này thay đổi cách viết kế hoạch kiểm thử và tiêu chí nghiệm thu (acceptance criteria). Bạn có thể tự tin viết: "Tính năng được nghiệm thu trên staging với image build X, và chính image X đó sẽ lên production" — vì tính bất biến của image đảm bảo điều đó.

Bài học: Tính bất biến của container loại bỏ cả một lớp rủi ro "lệch môi trường". BA tận dụng điều này để siết chặt tiêu chí nghiệm thu và giảm rủi ro triển khai.

Tình huống 3 — Startup fintech và cái bẫy "tách quá nhiều container"

Một startup fintech ở Singapore mở rộng sang thị trường Việt Nam. Đội kỹ thuật mới, hào hứng với Kubernetes, quyết định tách ứng dụng thành 18 microservice, mỗi service một loạt container, chạy trên cluster Kubernetes. Vấn đề: đội chỉ có 9 kỹ sư, chưa ai vận hành Kubernetes ở quy mô lớn. Kết quả là chi phí cloud tăng gấp ba, sự cố vận hành liên tục, và mỗi tính năng đơn giản phải sửa ở 4–5 service mới xong.

Khi BA ngồi lại phân tích cùng kiến trúc sư, họ nhận ra phần lớn 18 service đó không có nhu cầu mở rộng độc lập hay đội phát triển riêng — nghĩa là việc tách chúng ra chỉ tạo gánh nặng mà không mang lại lợi ích. Họ gộp lại còn 5 service hợp lý. (Việc chọn monolith hay microservice là chủ đề riêng — bài này chỉ nhấn mạnh: container không tự động biến hệ thống thành tốt hơn.)

Bài học: Kubernetes và container là công cụ mạnh nhưng đi kèm độ phức tạp vận hành thật. BA cần đặt câu hỏi tỉnh táo: "Việc đóng gói/tách này giải quyết nhu cầu kinh doanh nào? Đội mình có đủ năng lực vận hành không?" Đừng chạy theo công nghệ.

Hướng dẫn từng bước

Khi bạn gặp một dự án liên quan đến container hóa, đây là cách tiếp cận của một Technical BA:

Bước 1 — Đọc hiểu Dockerfile của ứng dụng. Mở Dockerfile ra, xác định ba điều: ứng dụng dùng công nghệ gì (FROM), mở cổng nào (EXPOSE), và có biến môi trường nhạy cảm nào không (ENV). Nếu thấy ENV DB_PASSWORD=... ghi cứng trong file, đó là red flag bảo mật cần nêu ngay (bí mật phải nằm ở nơi quản lý riêng, không hard-code).

Bước 2 — Xác định yêu cầu mở rộng. Hỏi và ghi lại: lượng truy cập bình thường, đỉnh, tần suất đỉnh. Quy đổi thành NFR cụ thể với con số. Đây là đầu vào để đội kỹ thuật cấu hình replica và auto-scaling.

Bước 3 — Làm rõ giới hạn tài nguyên. Mỗi container thường được giới hạn CPU và RAM (resource limits). Hỏi đội: "Một pod cần bao nhiêu RAM? Nếu vượt thì sao?" Nếu pod vượt giới hạn RAM, Kubernetes có thể tắt nó (OOMKilled) — ảnh hưởng trực tiếp đến độ ổn định mà bạn cam kết trong NFR.

Bước 4 — Hiểu chiến lược triển khai. Hỏi đội triển khai theo kiểu gì: rolling update (thay từng pod một, không gián đoạn) hay blue-green. Điều này quyết định bạn có cần khai báo "không downtime" trong yêu cầu hay không, và ảnh hưởng tới kế hoạch phát hành.

Bước 5 — Đối chiếu với chi phí. Số replica, kích thước container, auto-scaling tối đa — tất cả đều ra tiền. Phối hợp với đội để ước lượng chi phí hạ tầng theo các kịch bản tải, đưa vào phần đánh giá khả thi của tài liệu.

Bước 6 — Ghi lại bằng ngôn ngữ chung. Trong spec, đừng viết lệnh kỹ thuật. Hãy viết kết quả mong muốn: "Hệ thống tự mở rộng để duy trì thời gian phản hồi dưới 2 giây ở tải đỉnh 60.000 người dùng đồng thời, và tự co lại để tối ưu chi phí khi tải giảm." Đội kỹ thuật dịch câu này thành cấu hình Kubernetes.

Lỗi thường gặp & mẹo

Lỗi 1 — Nhầm container với máy ảo. Nhiều BA gọi container là "máy ảo nhỏ". Sai về bản chất và dễ dẫn đến ước lượng tài nguyên sai. Hãy nhớ: container chia sẻ kernel, nhẹ và nhanh hơn VM nhiều.

Lỗi 2 — Cho rằng container = bảo mật tự động. Container cô lập ứng dụng nhưng không miễn nhiễm lỗ hổng. Image nền cũ có thể chứa thư viện dính CVE (lỗ hổng đã công bố). Mẹo: trong spec, yêu cầu quét lỗ hổng image (image scanning) và dùng image nền được cập nhật.

Lỗi 3 — Viết NFR mơ hồ rồi mong auto-scaling tự lo. Auto-scaling chỉ tốt khi có ngưỡng rõ ràng. "Chịu được tải cao" là vô nghĩa với máy móc. Luôn kèm con số: bao nhiêu người dùng đồng thời, thời gian phản hồi mục tiêu.

Lỗi 4 — Bỏ qua trạng thái (state). Container và pod là phù du (ephemeral) — chết là mất dữ liệu bên trong. Nếu ứng dụng cần lưu file hay phiên đăng nhập, đó là yêu cầu về lưu trữ bền (persistent storage) phải nêu rõ. Đừng giả định pod nhớ được gì sau khi khởi động lại.

Mẹo vàng: Khi nghe thuật ngữ lạ trong họp ("pod bị evicted", "image pull backoff"), đừng giả vờ hiểu. Hỏi thẳng: "Anh giải thích cái đó ảnh hưởng đến người dùng như thế nào?" Câu hỏi quy mọi thứ về tác động nghiệp vụ — đó là giá trị riêng của BA, và không ai cười bạn vì hỏi đúng trọng tâm.

Bài tập thực hành

Bài 1 — Đọc hiểu Dockerfile. Cho Dockerfile sau, hãy trả lời: ứng dụng dùng công nghệ gì, mở cổng nào, và có vấn đề bảo mật nào không?

FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
ENV API_SECRET_KEY=abc123xyz
EXPOSE 8000
CMD ["python", "app.py"]
(Gợi ý: chú ý dòng ENV — bí mật bị ghi cứng là vấn đề.)

Bài 2 — Chuyển nhu cầu kinh doanh thành NFR. Một ứng dụng đặt vé xem phim bình thường có 3.000 người dùng đồng thời, nhưng khi mở bán vé bom tấn lúc 9h sáng thì vọt lên 25.000 trong 10 phút rồi giảm dần. Hãy viết một câu NFR về khả năng mở rộng kèm con số cụ thể, theo phong cách declarative.

Bài 3 — Đặt câu hỏi rủi ro. Đội kỹ thuật báo: "Ứng dụng giỏ hàng lưu thông tin giỏ ngay trong bộ nhớ của pod." Hãy viết 2 câu hỏi bạn cần đặt cho đội, và giải thích rủi ro nghiệp vụ tiềm ẩn (gợi ý: pod chết thì sao?).

Bài 4 — Phân tích đánh đổi. Một startup 6 người muốn tách ứng dụng thành 15 microservice trên Kubernetes. Hãy liệt kê 3 câu hỏi bạn đặt ra để đánh giá quyết định này có hợp lý không.

Tóm tắt

Là Technical BA, bạn không vận hành container, nhưng bạn cần hiểu đủ để tham gia quyết định và viết spec đúng. Những điều cốt lõi cần nhớ:

  • Container đóng gói ứng dụng cùng mọi phần phụ thuộc vào một gói bất biến, chạy giống nhau ở mọi môi trường — xóa bỏ "trên máy em chạy được mà" và lớp rủi ro lệch môi trường.
  • Image là khuôn đúc bất biến; container là thực thể đang chạy sinh ra từ image. Tính bất biến giúp BA siết chặt tiêu chí nghiệm thu.
  • Dockerfile là bản thiết kế image; BA nên đọc hiểu ba dòng FROM, EXPOSE, ENV vì chúng liên quan đến bảo mật, tích hợp và cấu hình.
  • Kubernetes điều phối hàng trăm container — với Pod, Replica, Service, Deployment, Auto-scaling. Triết lý declarative khớp tự nhiên với cách BA viết NFR.
  • Container không tự động làm hệ thống tốt hơn hay an toàn hơn. BA luôn phải hỏi: nhu cầu kinh doanh là gì, đội có đủ năng lực vận hành không, và mọi NFR phải có con số cụ thể.
Giá trị lớn nhất bạn mang lại không phải là biết gõ lệnh kubectl, mà là biết quy mọi thuật ngữ kỹ thuật về tác động nghiệp vụ — và đặt đúng câu hỏi để cả đội ra quyết định sáng suốt.