❤️ AZDIGI chính thức cập nhật hệ thống blog mới hoàn chỉnh. Tuy nhiên có thể một số bài viết bị sai lệch hình ảnh, hãy ấn nút Báo cáo bài viết ở cuối bài để AZDIGI cập nhật trong thời gian nhanh nhất. Chân thành cám ơn.

bài 6, mình đã đóng gói một app Node.js đơn thành Helm chart. Nếu cần xem lại phần nền tảng về cấu trúc chart, values và template, bạn cũng nên lướt lại bài 3 về anatomy chart trước khi đi tiếp. Sang phần này, câu chuyện bắt đầu thực tế hơn: thay vì một service đơn lẻ, chúng ta có frontend và backend với vòng đời deploy khác nhau, cấu hình khác nhau, và cách route request cũng không còn đơn giản nữa.

Case lab của bài là một stack rất quen thuộc: React hoặc Next.js ở phía giao diện, Express ở phía API, và cả hai được đóng gói bằng một umbrella chart. Mục tiêu không phải chỉ để deploy được, mà là deploy theo kiến trúc đủ sạch để sau này mở rộng sang production mà không biến file values thành một nồi lẩu YAML.

Vì sao nên tách frontend và backend?

Gom tất cả vào một chart hoặc một container nghe có vẻ tiện lúc demo, nhưng lên Kubernetes thì nhược điểm lộ ra khá nhanh. Frontend thường thay đổi nhiều hơn, backend lại có pattern scale khác, runtime cũng khác, và phần cấu hình giữa hai bên gần như không cùng ngôn ngữ với nhau.

  • Chu kỳ deploy khác nhau: sửa giao diện không nên kéo theo rollout API.
  • Scale khác nhau: frontend chủ yếu serve asset, backend xử lý request động.
  • Runtime khác nhau: frontend có thể build ra static file, backend vẫn cần Node.js runtime.
  • Config khác nhau: frontend cần API base URL, backend cần CORS, secret, database URL.

Nhìn ở sơ đồ trên là thấy ngay điểm quan trọng nhất: người dùng vẫn truy cập một domain thống nhất, nhưng phía dưới cluster thì frontend và backend là hai workload riêng. Đây là kiểu tách vừa đủ: public surface gọn, kiến trúc bên trong vẫn rõ.

Kiến trúc tổng thể cho React hoặc Next.js + Express

Trong lab này mình dùng mô hình rất phổ biến:

  • Frontend build ra asset cho giao diện
  • Nginx đứng trước để phục vụ static file
  • Backend Express expose REST API
  • Ingress route / vào frontend và route /api vào backend
Browser
  -> Ingress
     -> /      -> frontend Service -> Nginx -> React / Next.js
     -> /api   -> backend Service  -> Express API

ℹ️ Với Next.js chạy SSR, frontend không còn đơn thuần là Nginx phục vụ file tĩnh. Tuy vậy, cách tổ chức umbrella chart, shared values và parent ingress vẫn giữ nguyên giá trị.

Docker strategy cho full-stack app

Ở đây có hai hướng đóng gói hoàn toàn khác nhau.

  • Frontend: dùng multi-stage build, stage đầu build bằng Node.js, stage sau chỉ còn Nginx và thư mục build.
  • Backend: giữ image Node.js riêng, cài dependency production và chạy npm start.
window.__APP_CONFIG__?.API_BASE_URL || process.env.REACT_APP_API_BASE_URL || '/api'

Đoạn trên là mảnh ghép rất đáng giá với frontend. Nó cho phép tách build artifact khỏi runtime config, nghĩa là bạn có thể đổi API base URL bằng ConfigMap mà không phải rebuild image mỗi lần đổi môi trường.

Các lựa chọn chart architecture

Khi bắt đầu đóng gói Helm cho một ứng dụng full-stack, thường có ba hướng:

  • Monolith chart: nhanh để demo, nhưng values và template phình rất nhanh.
  • Separate charts: tách biệt cao, phù hợp tổ chức lớn hoặc hệ microservices rõ ràng.
  • Umbrella chart: giữ được modularity mà vẫn có một release chung cho cả stack.

Với case React hoặc Next.js + Express, umbrella chart là điểm cân bằng đẹp nhất. Nó không dính chặt quá mức như monolith chart, nhưng cũng không rời rạc đến mức dev environment phải cài nhiều release thủ công.

Cấu trúc umbrella chart trong lab

charts/fullstack-stack/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
├── templates/
│   └── ingress.yaml
└── charts/
    ├── frontend/
    │   ├── Chart.yaml
    │   ├── values.yaml
    │   └── templates/
    │       ├── _helpers.tpl
    │       ├── configmap.yaml
    │       ├── deployment.yaml
    │       └── service.yaml
    └── backend/
        ├── Chart.yaml
        ├── values.yaml
        └── templates/
            ├── _helpers.tpl
            ├── deployment.yaml
            └── service.yaml

Parent chart giữ trách nhiệm orchestration ở cấp toàn stack: dependency, ingress và các giá trị dùng chung. Frontend subchart tập trung vào deployment Nginx, service và ConfigMap chứa config.js. Backend subchart quản lý deployment Express, service và các biến môi trường như PORT, APP_NAME, CORS_ORIGIN.

global:
  frontend:
    apiBaseUrl: "/api"

Đây là kiểu values khá sạch vì nhìn vào là biết config nào thuộc frontend, backend hay ingress. Không còn kiểu đặt tên như replicaCountFrontend hoặc servicePortBackend chạy lung tung khắp file.

Service communication trong cluster

Một hiểu lầm khá phổ biến là frontend trong Kubernetes phải gọi backend bằng service DNS nội bộ, ví dụ http://backend.default.svc.cluster.local:8080. Thực ra với SPA chạy trên trình duyệt, request đi từ browser chứ không đi từ pod frontend.

  • Nếu frontend và backend dùng chung domain, endpoint frontend nên gọi là /api.
  • Ingress chịu trách nhiệm map /api vào backend Service.
  • Browser không cần biết gì về DNS nội bộ của cluster.

💡 Pattern gọi /api giúp bạn tránh bớt một mớ CORS phức tạp, đồng thời giữ cho dev và prod giống nhau hơn.

values-dev.yaml và values-prod.yaml nên khác nhau ở đâu?

Helm mạnh nhất khi cùng một chart có thể mang nhiều profile môi trường. Ở lab này, file values-dev.yamlvalues-prod.yaml khác nhau ở những điểm rất thực dụng: replica, host, image tag và chính sách CORS.

Infographic so sánh values dev và prod cho full stack Helm chart
  • Development: replica 1, host local, image tag linh hoạt, log dễ đọc để debug.
  • Production: pin image tag, replica nhiều hơn, CORS giới hạn, sẵn chỗ để thêm HPA, Secret, NetworkPolicy và ingress annotation.

Quy trình build và deploy của lab

docker build -t ghcr.io/example/react-spa:latest apps/frontend
docker build -t ghcr.io/example/express-api:latest apps/backend
helm dependency build charts/fullstack-stack
helm template react-express charts/fullstack-stack \
  -f charts/fullstack-stack/values-dev.yaml
helm upgrade --install react-express charts/fullstack-stack \
  -f charts/fullstack-stack/values-dev.yaml \
  --namespace demo --create-namespace
helm lint charts/fullstack-stack -f charts/fullstack-stack/values-dev.yaml

Sau khi render, bạn sẽ thấy đầy đủ ConfigMap cho frontend runtime config, Service và Deployment cho cả hai thành phần, cùng với Ingress ở parent chart. Đây là điểm mình thích ở pattern umbrella chart: nhìn toàn stack trong cùng một release, nhưng mỗi workload vẫn có ranh giới rất rõ.

Tổng kết

Với bài toán full-stack kiểu React hoặc Next.js + Express trên Kubernetes, umbrella chart gần như là điểm rơi hợp lý nhất. Nó cho bạn một release chung, nhưng không ép frontend và backend sống chung một kiểu cấu hình. Chỗ nào cần dùng chung thì đặt ở parent chart, chỗ nào là concern riêng thì để subchart tự xử lý.

Nếu chỉ cần nhớ một ý, thì là ý này: Helm không chỉ dùng để render YAML. Helm là cách bạn đóng gói kiến trúc triển khai của cả hệ thống, và với full-stack Node.js, điều đó quan trọng hơn nhiều so với việc có thêm vài file template.

Bài tiếp theo: database integration

Ở phần 8, mình sẽ nối backend này với database thật, tách Secret ra khỏi values thường, thêm migration flow và bàn kỹ hơn về chuyện chart nên quản lý dependency database tới đâu. Đây mới là đoạn mà chart full-stack bắt đầu chạm vào production thật sự.

Chia sẻ:
Bài viết đã được kiểm duyệt bởi AZDIGI Team

Về tác giả

Trần Thắng

Trần Thắng

Chuyên gia tại AZDIGI với nhiều năm kinh nghiệm trong lĩnh vực web hosting và quản trị hệ thống.

Hơn 10 năm phục vụ 80.000+ khách hàng

Bắt đầu dự án web của bạn với AZDIGI