Bài này nói về DevOps frontend ở góc nhìn senior: artifact bất biến, môi trường preview, feature flag khác với deploy theo env, rollback, và CI phía FE nên chặn gì. Bạn không phải platform engineer. Bạn vẫn phải nói được app Vue/Nuxt tới tay user thế nào.
Đọc 14.1–14.2 trước (GitOps và pipeline bạn sẽ vẽ). Rồi 14.3 cho preview và env (sâu hơn Q&A preview bên NET-OPS). 14.4 là flag: đối chiếu Q&A rollout bằng lời. 14.5 là phần bạn chịu trách nhiệm trên Kubernetes: image, probe, cache header, không phải CNI. GitOps + Argo CD là bối cảnh enterprise hay gặp; hãy quy nó về digest bạn promote.
Tóm tắt: GitOps nghĩa là Git là desired state của cluster. CI build một image bất biến gắn tag git SHA; repo GitOps (Helm/Kustomize) chuyển sang digest đó; Argo CD reconcile. Rollback là revert Git, không phải kubectl edit. Bạn không bao giờ ship :latest.
Vì sao quan trọng: “Frontend lên production thế nào?” Họ dùng Argo CD. Họ muốn phần của bạn: digest, vì sao :latest không ổn, và cách rollback một release Vue hỏng mà không cần ssh. Mitigate ngay thì gạt flag; rollback nhanh thì revert commit GitOps.
- Cách hoạt động: CI tạo image hoặc artifact tĩnh bất biến (
ghcr.io/acme/web@sha256:…). Prod nên trỏ digest, không phải tag trôi. Repo GitOps ghi digest đó. Argo CD apply. Drift (ai đó kubectl-edit) là thứ Argo sẽ kéo về. Đó chính là mục đích. - Build một lần, promote cùng digest từ staging → prod bằng cách đổi env, không rebuild. Image staging khác image prod là failure mode: bạn không test đúng thứ đã ship.
- Mitigate ngay: tắt feature flag (14.4). Rollback nhanh: revert commit GitOps; Argo sync digest trước.
kubectl rollout undorồi cũng bị apply lại. Preview là namespace từ SHA của PR, không phải snowflake trên laptop. - Phía frontend: cache
index.htmlngắn, asset đã hash thìimmutable. Argo healthy mà JS vẫn cũ, thường là CDN còn giữ HTML. Secret trong GitOps để ở Sealed / External Secrets. Biết chúng tồn tại; đừng dán.
git push SHA
CI: lint | vue-tsc | vitest | nuxt build | budget
CI: docker build → ghcr.io/acme/web@sha256:abc…
CI: commit image: sha256:abc vào gitops/apps/web.yaml
Argo CD: sync cluster theo Git (đừng kubectl apply từ laptop)
rollback: git revert commit gitops đó; Argo apply lại sha256:old
mitigate trước: flag checkout_v2 = off (14.4)
# không bao giờ: image: acme/web:latestĐiều gì xảy ra: Cluster chỉ đổi khi Git đổi. Rollback là một commit Git khác. :latest làm Argo và thực tế lệch nhau mỗi lần pull. Flag nhanh hơn revert khi checkout đang cháy.
| Cổng | Vì sao | Trên PR? |
|---|---|---|
| eslint + vue-tsc --noEmit | Bắt bug production rẻ nhất | Có |
| Vitest + production build + budget | Tree-shake gãy; 12.3 dạng CI | Có |
| Playwright smoke + CWV budget | Login/mua; lab không phải RUM | Nhỏ trên PR; nhiều hơn trên main |
| Quét image + map ẩn | Org + Sentry gắn SHA | Release, không phải CDN public |
Góc nhìn senior
- Bạn chịu trách nhiệm digest và đường GitOps, không phải CNI. Tự nhận rành Kubernetes chỉ vì đã sửa image tag, đó là tín hiệu lộ.
- Blue/green SPA tĩnh bằng hai prefix kèm alias ingress hoặc CDN. Cache HTML là miss thường gặp.
- Đừng chặn merge bằng kịch coverage. Chặn bằng budget và smoke.
- Kiểm chứng: Argo hiện SHA bạn định ship; curl
/sau rollback phải ra asset hash cũ.
Lỗi hay gặp
- Ship
:latest. Triệu chứng: không nói được prod đang chạy gì. Nguyên nhân: tag trôi. Sửa: digest trong GitOps.kubectl rollout undokhi Argo đang sync. Triệu chứng: pod cũ trở lại. Nguyên nhân: Git vẫn trỏ digest hỏng. Sửa: revert Git.- Rebuild cho prod. Triệu chứng: staging chưa từng chạy JS đó. Nguyên nhân: “đổi env rồi rebuild”. Sửa: env runtime, một digest.
- Cache HTML dài. Triệu chứng: Argo healthy, user còn Vue tuần trước. Nguyên nhân:
index.htmlimmutable. Sửa: HTML cache ngắn, asset hash thì immutable.
Nhà tuyển dụng hay hỏi gì
Frontend lên production thế nào? Họ dùng Argo CD. Họ muốn phần pipeline của bạn, vì sao :latest không ổn, và cách rollback một release Vue hỏng mà không ssh.
Trả lời khi phỏng vấn
Mình build một lần rồi promote digest. CI test, build ghcr.io/acme/web ở một sha256, rồi commit digest đó vào GitOps. Argo sync, mình không kubectl apply. Rollback là revert Git; kubectl rollout undo rồi cũng bị apply lại. Mình không bao giờ ship latest. Checkout đang cháy thì gạt flag trước, rồi mới revert digest. Mình chặn PR bằng typecheck, unit, production build, và bundle budget. Cache HTML ngắn, asset hash thì immutable. Không thì Argo trông healthy trong khi user còn chạy JavaScript tuần trước.
Nhớ nhanh
- Git là desired state. Promote một digest. Không bao giờ :latest.
- Rollback là revert GitOps. kubectl undo thua Argo.
- Gạt flag nhanh hơn revert khi checkout đang cháy.
- Cache HTML ngắn, asset hash immutable.
Câu hỏi nối tiếp thường gặp
- Blue/green SPA tĩnh thế nào? Hai prefix, rồi chuyển ingress hoặc alias CDN. Cache HTML phải hết hạn.
- Drift là gì? Ai đó kubectl-edit. Argo sẽ kéo về. Đó là điều tốt.
- Vì sao image staging khác prod là failure mode? Bạn không test thứ đã ship.
- Liveness khác readiness cho process Node của Nuxt? Đó là 14.5. /healthz rẻ, khác với Nitro đang listen.
Tóm tắt: Hai track. PR là feedback nhanh: install, rồi lint / vue-tsc / Vitest song song, rồi production build kèm budget, rồi Playwright smoke tùy chọn trên URL preview. Main publish một artifact, bump GitOps, upload map ẩn, không bao giờ rebuild trong cluster.
Vì sao quan trọng: “Vẽ pipeline cho app Nuxt trong monorepo”. Họ muốn cái gì chạy song song, artifact duy nhất publish ở đâu, và vì sao Playwright nhắm nuxt dev không phải bằng chứng. Phút để pipeline xanh và tỷ lệ flake quan trọng không kém coverage.
- Cách hoạt động: PR: cache pnpm → lint | vue-tsc | vitest song song → nuxt build + bundle budget → Playwright smoke tùy chọn trên URL preview (14.3). PR fork không nhận secret production.
- Main: cùng gate, kèm e2e đầy hơn, docker từ
disthoặc Nitro standalone, push digest, commit GitOps, sourcemap ẩn lên Sentryrelease=SHA, Argo sync, smoke synthetic trên prod. - Monorepo: build package affected (Turbo/Nx). Playwright chạy production build hoặc URL preview, không bao giờ
nuxt dev. Để ý OOM khinuxt buildtrên CI. - Quality gate xếp theo blast radius: lint/type rẻ và chặn; budget chặn merge; e2e smoke chặn deploy; phần trăm coverage thì không. Bỏ test “chỉ lần này” là cách pipeline xanh vẫn ship incident.
PR → install (pnpm cache)
→ song song: lint | vue-tsc | vitest
→ nuxt build + bundle budget
→ tùy chọn: Playwright smoke trên URL preview
→ tùy chọn: preview deploy (14.3)
main → cùng gate
→ docker FROM dist hoặc Nitro standalone
→ push ghcr.io/app@sha256:…
→ commit digest vào gitops/; map Sentry release=SHA
→ Argo sync; smoke synthetic trên prodĐiều gì xảy ra: Track PR không bao giờ publish. Main là nơi duy nhất một digest ra đời. Playwright nhắm nuxt dev sẽ cho xanh một bug tree-shake chỉ tồn tại trên graph production của Rollup/Rolldown. Map Sentry gắn với SHA mà Argo vừa sync.
| Giai đoạn | Chặn | Không được |
|---|---|---|
| PR song song | lint, vue-tsc, vitest | Chạy serial không vì lý do gì |
| PR build | production nuxt build + budget | Bỏ vì “CI chậm” |
| Main publish | một digest + GitOps + map | Rebuild trong cluster |
| E2E | prod build hoặc URL preview | nuxt dev làm bằng chứng |
Góc nhìn senior
- Ai được approve GitOps prod là kiểm soát của org. Frontend vẫn review image digest trong PR đó.
npm publishdesign system private không có provenance là mùi supply-chain (bảo mật 10.7).- App Vue sản phẩm thì merge-to-main. Lịch nightly dành cho thứ không được làm gãy checkout hôm nay.
- Kiểm chứng: một SHA trên GitOps, Sentry, và pod đang chạy. Ba SHA khác nhau là bug.
Lỗi hay gặp
- Playwright nhắm
nuxt dev. Triệu chứng: CI xanh, prod gãy. Nguyên nhân: graph bundler khác. Sửa: URL preview hoặcnuxt preview.- Rebuild trên cluster. Triệu chứng: JS prod chưa từng test. Nguyên nhân: “Helm sẽ build”. Sửa: image là artifact.
- PR fork mang secret prod. Triệu chứng: token bị lộ. Nguyên nhân: cùng một workflow. Sửa: secret chỉ trên main tin cậy.
- Source map hidden upload lên CDN. Triệu chứng: source nằm public. Nguyên nhân: trộn với asset đã hash. Sửa: chỉ Sentry,
sourcemap: "hidden".
Nhà tuyển dụng hay hỏi gì
Vẽ pipeline cho app Nuxt trong monorepo. Họ muốn hai track, chỗ nào chạy song song, artifact duy nhất publish ở đâu, và vì sao Playwright nhắm nuxt dev không phải bằng chứng.
Trả lời khi phỏng vấn
Hai track. PR là lint, type, unit, production build, ngân sách. Main publish một digest rồi cập nhật GitOps. Mình không rebuild trong cluster. Playwright đụng prod build hoặc URL preview, không bao giờ nuxt dev. Trong monorepo mình build package affected. Mình để ý OOM lúc nuxt build trên CI; laptop nói dối về RAM. Source map hidden upload đúng SHA mà Argo vừa sync. Bỏ test chỉ lần này là cách pipeline xanh vẫn ship incident. PR fork không nhận secret production.
Nhớ nhanh
- PR là phản hồi. Main publish một digest. Không bao giờ rebuild trong cluster.
- Playwright đụng prod build hoặc URL preview, không bao giờ nuxt dev.
- Build affected trong monorepo. Fork không nhận secret prod.
- Source map lên Sentry đúng SHA. Bỏ test là ship incident.
Câu hỏi nối tiếp thường gặp
- Quét secret trên bundle client thế nào? Fail CI nếu VITE_ trông giống key. Chuỗi trong dist là public.
- Ai được approve GitOps prod? Kiểm soát của org; FE vẫn review digest trong PR đó.
- Vì sao npm publish design system private không có provenance là mùi? Supply chain. Gắn nó với SHA.
- Merge-to-main khác lịch nightly, cái nào cho app Vue sản phẩm? Merge-to-main. Nightly dành cho thứ không được làm gãy checkout hôm nay.
Tóm tắt: Mỗi PR có một URL riêng của đúng SHA, nói với API staging, data giả, và OAuth client tách. Hủy khi merge. Ưu tiên env runtime trên image Node để cùng digest làm preview, staging, và prod. VITE_* / NUXT_PUBLIC_* luôn là client.
Vì sao quan trọng: “Designer xem PR thế nào mà không lộ PII prod?” CORS, cookie, OAuth redirect_uri, và VITE_* bị copy là phần họ hỏi tiếp. Khác Q&A preview NET-OPS (process của team): ở đây cơ chế là env lúc build khác env lúc runtime.
- Cách hoạt động: Host riêng
pr-123.preview.example.com. Env: API staging/test, data ẩn danh, OAuth client và cookie tách.X-Robots-Tag: noindex. Hủy lúc merge/đóng. Preview sống lâu thành staging chui là incident. - Env lúc build (
VITE_*,NUXT_PUBLIC_*bị Vite inline) được nướng vào JS. Nếu nướng URL API lúc build thì bạn không promote được digest. Env runtime (runtimeConfig/ Docker-e) để một digest chạy được ba URL. - Origin preview làm nổ CORS và cookie
Domain=.example.com. Bọc bằng BFF same-site, hoặc allowlist chỉ trên staging. Cookie host-only. Preview SSR + WebSocket cần host Node thật, không chỉ Pages tĩnh. baseURLe2e lấy từ CI. Playwright nhắm preview, không localhost. PR fork không thừa kếVITE_prod.
// nuxt.config.ts: public luôn là client; private ở lại Nitro
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: "", // NUXT_API_SECRET: chỉ server
public: { apiBase: "" }, // NUXT_PUBLIC_API_BASE: bị inline nếu nướng
},
})
// Docker runtime (cùng digest, ba URL):
// docker run -e NUXT_PUBLIC_API_BASE=https://api.staging.example.com web@sha256:abc
// không bao giờ: VITE_STRIPE_SECRET=... (luôn nằm trong JS)Điều gì xảy ra: apiSecret ở lại server. public.apiBase có thể inject lúc process start nếu bạn không nướng. Cùng sha256:abc phục vụ preview và prod với -e khác. Secret Stripe trong VITE_ đã nằm trong bundle kẻ tấn công tải về.
| Loại | Khi nào bind | Promote digest? |
|---|---|---|
VITE_* / NUXT_PUBLIC_* đã nướng | Lúc compile, trong JS | Không: URL đóng cứng trong file |
runtimeConfig Nitro qua -e | Lúc process start | Có: cùng image, URL mới |
| Cookie / OAuth client | Theo môi trường | Phải tách trên preview |
| Host Pages tĩnh | Chỉ output build | SSR/WS cần host Node |
Góc nhìn senior
- Nếu nướng URL API public lúc build, bạn không promote được digest. Đó là cả ý của topic này so với 14.1.
- Cookie
Domain=.example.comrò preview ↔ prod là chuyện lẫn session kinh điển. Cookie host-only. - CORS wildcard chỉ trên staging.
*không bao giờ trên prod kèm credentials (networking 13.4). - Kiểm chứng: noindex, API staging trên Network, OAuth redirect_uri khớp host preview, e2e baseURL là preview.
Lỗi hay gặp
- Preview trỏ DB prod. Triệu chứng: PII trên URL public. Nguyên nhân: copy
.env. Sửa: API staging, data giả.- Nướng
NUXT_PUBLIC_API_BASErồi “promote”. Triệu chứng: prod vẫn nói với staging, hoặc bạn rebuild. Nguyên nhân: bind lúc compile. Sửa:-eruntime.- Cookie domain cha. Triệu chứng: PM login prod từ preview. Sửa: host-only; OAuth client tách.
- Host tĩnh cho SSR + WS. Triệu chứng: socket 404. Nguyên nhân: Pages là file. Sửa: preview Node.
Nhà tuyển dụng hay hỏi gì
Designer và PM xem PR thế nào, và làm sao không rò data prod vào preview? CORS, cookie, OAuth redirect_uri, và VITE_ copy từ prod là phần họ hỏi tiếp.
Trả lời khi phỏng vấn
Mỗi PR là pr-123.preview.example.com trên đúng SHA, nói với staging, với OAuth client riêng. Mình không copy VITE_ prod và không trỏ preview vào DB prod. Nếu nướng URL API public lúc build thì mình không promote được artifact, nên mình ưu tiên env runtime trên image Node. Preview để noindex và hủy lúc merge. Cookie host-only, không thì Domain cha rò preview sang prod. SSR kèm WebSocket cần host Node thật. e2e baseURL là URL preview. Khác mục NET-OPS: ở đây mình nói env lúc build khác lúc runtime, không phải chuyện rollback của team.
Nhớ nhanh
- URL riêng mỗi SHA. API staging. Hủy lúc merge. Noindex.
- Env runtime để một digest promote được. VITE_ đã nướng thì không.
- Cookie host-only. OAuth tách. Không bao giờ DB prod.
- Preview SSR + WS cần Node. e2e baseURL là preview.
Câu hỏi nối tiếp thường gặp
- e2e nhắm preview thế nào? Playwright baseURL từ CI. Không localhost.
- Preview SSR kèm WebSocket được không? Được, trên host Node. Pages tĩnh không đủ.
- Vì sao cookie Domain=.example.com nguy hiểm với preview? Lẫn session preview ↔ prod.
- CORS wildcard staging khác prod, dấu sao không bao giờ được phép ở đâu? Prod kèm credentials. Networking 13.4.
Tóm tắt: Feature flag là kiểm soát rủi ro lúc runtime: kill switch, rollout theo phần trăm, entitlement, experiment. Không thay thế version control, cũng không phải nghĩa địa if/else vĩnh viễn. Bảo mật và giá đánh giá ở BFF. Xem Q&A feature-flags-rollout cho chuyện cleanup nói thành lời.
Vì sao quan trọng: “Ship dark? Kill switch? Sao không chỉ deploy?” Họ muốn bạn đánh giá flag ở đâu, chuyện gì khi dịch vụ flag sập, và cách xóa chúng. Deploy theo env không cắt được user prod. Khác 14.3 (SHA preview): flag ship bên trong prod.
- Cách hoạt động: Nêu rõ loại. Kill switch: tắt checkout mới, fail-safe. Gradual: hash ổn định trên user id, không
Math.random()mỗi request. Entitlement: gói có export, server quyết, FE chỉ phản ánh. Experiment: copy/layout; để ý CLS. - Đánh giá bảo mật và giá ở BFF để client không tự gạt được. Flag phía client chỉ là chrome UX. Ghim bucket SSR trong payload để hydrate khớp. Server
truecòn clientfalselà mismatch. - Nếu dịch vụ flag trả 500: fail-closed cho thanh toán, fail-open cho widget chat. Cleanup: một ticket và một ngày lúc 100% hoặc khi bỏ. Flag 1% suốt sáu tháng là bug process. ADR nếu nó đổi kiến trúc.
- Giấu nút phía client không phải authz. API vẫn phải từ chối. Đừng gắn flag cho nội dung Google phải thấy. Flag
localStoragelệch giữa các tab là bug đa tab.
type FlagKind = "kill" | "gradual" | "entitlement" | "experiment"
function checkoutEnabled(userId: string, flags: Flags): boolean {
if (flags.serviceDown) return false // fail-closed cho tiền
if (!flags.checkout_v2) return false // kill switch
return hashToBucket(userId, "checkout_v2") < flags.checkout_v2.percent
}
// SSR: ghim boolean vào payload để hydrate khớp
// BFF đánh giá entitlement; FE chỉ giấu nút (API vẫn 403)
// ticket: xóa checkout_v2 hai sprint sau 100%Điều gì xảy ra: Dịch vụ flag chết thì từ chối checkout, không charge mù. Phần trăm dùng hash ổn định để cùng user không bị gạt mỗi request. Boolean được ghim cho hydrate. Ticket là một phần của thiết kế, không phải hy vọng để sau. Q&A rollout là STAR nói thành lời; đây là cơ chế.
| Loại | Ví dụ | Đánh giá ở đâu |
|---|---|---|
| Kill switch | Tắt checkout mới | BFF + FE, fail-closed |
| Gradual | 10% session | Hash ổn định trên user id |
| Entitlement | Gói có export | Server; FE chỉ phản ánh |
| Experiment | Copy / layout | Client ổn; để ý CLS/CWV |
Góc nhìn senior
- Flag lồng trong một SFC là nổ tổ hợp. Làm phẳng. Test cả hai state chỗ chặn tiền hoặc auth.
- Target theo tenant hơn hẳn một boolean trong localStorage mà PM không gạt được.
- Deploy theo env không cắt được user prod. Vì vậy flag nằm cạnh GitOps.
- Kiểm chứng: cả hai state trong component test; hydrate khớp; 500 từ dịch vụ flag không charge.
Lỗi hay gặp
Math.random()mỗi request cho 10%. Triệu chứng: nháy, bucket không charge. Nguyên nhân: không hash ổn định. Sửa: hash(userId + flagKey).- Chỉ giấu nút pay phía client. Triệu chứng: fetch tự chế vẫn charge. Nguyên nhân: flag như authz. Sửa: BFF + API 403.
- Flag 1% suốt sáu tháng. Triệu chứng: hai code path mãi. Nguyên nhân: không expiry. Sửa: ticket hẹn ngày lên 100%.
- SSR true, client false. Triệu chứng: hydration mismatch. Nguyên nhân: đọc flag hai lần. Sửa: ghim trong payload.
Nhà tuyển dụng hay hỏi gì
Ship dark? Kill switch? Sao không deploy luôn? Họ muốn kiểm soát rủi ro lúc runtime, chỗ bạn đánh giá flag bảo mật/giá, và cách dọn flag, chứ không phải demo LaunchDarkly.
Trả lời khi phỏng vấn
Mình dùng flag để đổi rủi ro lúc runtime: kill switch, hash 10%, entitlement, chứ không giấu việc dở mãi. Payment được đánh giá ở BFF và fail closed. Mình ghim bucket SSR trong payload để hydrate khớp. Cleanup là một ticket; flag 1% suốt sáu tháng là bug process. Deploy theo env không cắt được user prod. Giấu nút phía client không phải authz. Câu chuyện rollout nói thành lời, kèm owner và hạn, nằm ở câu feature-flags-rollout; ở đây mình nói các loại và fail-safe.
Nhớ nhanh
- Flag là rủi ro runtime. Nêu rõ loại. Fail-closed cho tiền.
- BFF đánh giá bảo mật và giá. FE chỉ phản ánh.
- Hash ổn định, không Math.random. Ghim SSR để hydrate khớp.
- Ticket gắn expiry. Flag lồng là bug process.
Câu hỏi nối tiếp thường gặp
- Test cả hai phía trên CI thế nào? Component test với flag on và off chỗ chặn tiền hoặc auth.
- Flag khác branch khác bỏ feature? Flag cho rủi ro runtime. Branch cho việc còn dở. Bỏ nếu không phải sprint này.
- Chuyện gì nếu LaunchDarkly chết lúc checkout? Fail-closed. Đừng charge.
- Vì sao Math.random mỗi request sai cho rollout 10%? Cùng user bị nháy. Hash id.
Tóm tắt: Bạn phụ trách image, ingress path, probe, và header cache / CSP. Bạn không phụ trách cluster, CNI, hay cert-manager. Sửa tag trong values.yaml không phải “làm Kubernetes”.
Vì sao quan trọng: “Bạn có viết Helm chart không?” Họ đang thử xem bạn có nhận quá không. SPA tĩnh là nginx distroless phục vụ file đã hash. Nuxt SSR là process Node với HPA, readiness, và không sticky session, trừ khi bạn giữ session trong memory của server (điều bạn không nên làm).
- Cách hoạt động: Dockerfile hoặc bucket tĩnh + CDN. Port, health endpoint, resource request cho SSR, path (
/,/app), header bạn cần (CSP, cache). SPA: nginx hoặc distroless phục vụdist. Nuxt SSR:node .output/server/index.mjstrênHOST=0.0.0.0. - Liveness là
/healthzrẻ. Đừng SSR/làm liveness, kẻo trang chậm giết pod. Readiness là Nitro đang listen (và có thể cache đã warm). Không readiness = traffic đến trước khi listen. SSR scale ngang không cache chung là thundering herd, không phải bí ẩn jank frontend. - Bạn phụ trách CSP vì bạn biết origin FE. Bạn phụ trách header cache vì bạn biết HTML khác asset đã hash (14.1). Inject config là env runtime (14.3), không phải rebuild. Image bất biến; config được mount hoặc
-e. - Canary 10%: hai ReplicaSet hoặc weight ingress, kèm một flag (14.4) nếu cần bucket ổn định theo user. Log từ pod là request id và lỗi SSR; log browser vào Sentry (19.x). SPA fallback không được nuốt
/api.
# SPA: distroless/nginx phục vụ file đã hash, HTML Cache-Control: no-store
# Nuxt SSR:
CMD ["node", ".output/server/index.mjs"]
ENV HOST=0.0.0.0 PORT=3000
GET /healthz → 200 {"ok": true} # liveness: rẻ, không SSR
GET /ready → 200 sau khi Nitro listen
# probe: liveness /healthz; readiness /ready; đừng dùng GET / làm liveness
# nginx: try_files $uri /index.html, nhưng location /api/ thì proxy, không fallbackĐiều gì xảy ra: Process bind 0.0.0.0 để probe với tới. /healthz không render Vue. /ready chờ đến khi Nitro nhận connection. /api là location proxy để try_files không trả index.html khi miss JSON. HTML không immutable; hash JS thì có.
| Bạn phụ trách | Bạn không | Vì sao quan trọng |
|---|---|---|
| Image, path, probe, CSP, cache | CNI, cert-manager, node pool | Nhận quá là fail phỏng vấn |
| SPA: nginx + asset đã hash | Sticky session cho SPA | SPA không có session phía server |
| SSR: HOST, /healthz, /ready, HPA | SSR / làm liveness | Trang chậm không được giết pod |
| Config qua -e / file mount | Rebuild image mỗi env | Một digest (14.1, 14.3) |
Góc nhìn senior
- Đọc diff Argo khi “deploy không”. Cluster không phải bí ẩn; Git không đổi, hoặc HTML đang bị cache.
- Bạn phụ trách nội dung CSP vì bạn biết connect-src cho API và host preview.
- SSR scale ngang không cache chung thì mỗi pod đều đánh origin. Đó là ops kèm cache key, không phải Vue.
- Kiểm chứng: endpoint probe bằng curl từ pod khác;
/apilà JSON không phải HTML; Cache-Control của HTML ngắn.
Lỗi hay gặp
- Không readiness. Triệu chứng: 502 lúc roll. Nguyên nhân: traffic trước khi Nitro listen. Sửa:
/ready.- Liveness =
GET /. Triệu chứng: dashboard chậm restart pod. Nguyên nhân: SSR trên probe. Sửa:/healthzrẻ.- SPA fallback nuốt
/api. Triệu chứng: HTML bị parse như JSON. Nguyên nhân:try_filesmọi location. Sửa: proxy/apitrước.- Nhận đã thiết kế cluster vì đổi image tag. Triệu chứng: trượt vòng. Nguyên nhân: nhận quá. Sửa: nêu rõ phần bạn phụ trách.
Nhà tuyển dụng hay hỏi gì
Bạn có viết Helm chart không? Họ đang thử xem bạn có nhận quá không. Phần nào bạn phụ trách (image, ingress, probe, header cache), phần nào để team platform?
Trả lời khi phỏng vấn
Mình phụ trách image, path, probe, và header cache. Mình không thiết kế CNI hay cert-manager trong vòng frontend. SPA tĩnh là nginx distroless; Nuxt SSR là node .output/server/index.mjs trên HOST=0.0.0.0 với /healthz rẻ. Readiness là Nitro đã lên. Mình không bao giờ dùng GET / làm liveness. try_files không được nuốt /api. Mình đọc diff Argo khi deploy không lên. Nhận Kubernetes vì đã sửa image tag là tín hiệu họ chờ. Config là env runtime để digest promote.
Nhớ nhanh
- Phụ trách image, path, probe, CSP, cache. Không phụ trách cluster.
- Liveness là /healthz rẻ. Readiness là Nitro đang listen.
- SPA fallback không được nuốt /api. Cache HTML ngắn.
- Image bất biến, config runtime. Nhận quá thì trượt vòng.
Câu hỏi nối tiếp thường gặp
- Canary 10% chạy thế nào? Weight ingress hoặc hai ReplicaSet, kèm một flag nếu bucket phải ổn định theo từng user.
- Log từ pod khác browser thứ gì? Pod: request id, lỗi SSR. Browser: Sentry (19.x).
- Vì sao SSR full trang / là liveness kém? Trang chậm sẽ giết pod. Dùng /healthz.
- SPA fallback nuốt /api, ngăn thế nào? Proxy /api trước; try_files chỉ dành cho SPA.