Build một lần, chỉ làm việc thật sự cần. Sau đó khai báo trạng thái mong muốn và để cluster tự giữ nó.Build once, do only the work that matters. Then declare the state you want and let the cluster hold it.
Bản đồ dưới đây là đường đi của một commit: qua pipeline CI, thành một image, rồi thành Pod đang phục vụ request. Mỗi ga là một khái niệm; bấm để tới phần giải thích.The map below is the route a commit takes: through the CI pipeline, into an image, and into a Pod serving requests. Each station is a concept; tap one to jump to it.
nét liền: luôn xảy rasolid: always happensnét đứt: quyết định lúc chạydashed: decided at runtimenhánh xanh phía trên: các job chạy song songupper blue branch: jobs running in parallel
Pipeline là chuỗi job chạy mỗi khi có commit. Mỗi job chạy trên một runner, thường là máy ảo hoặc container dùng một lần rồi bỏ.
A pipeline is the set of jobs that runs on every commit. Each job runs on a runner, usually a throwaway VM or container.
Thời gian bạn phải chờ là wall-clock từ lúc push đến lúc có kết quả, và nó bằng độ dài critical path: chuỗi job phụ thuộc nhau dài nhất. Tổng thời gian các job không quan trọng bằng.
The time you wait is wall-clock from push to result, and it equals the critical path: the longest chain of dependent jobs. The sum of all job times matters far less.
Runner mất sạch trạng thái sau mỗi lần chạy. Muốn tái sử dụng thứ gì (dependency, layer, output build) thì phải chủ động lưu nó ra ngoài runner.
Runners lose all state after each run. Anything you want to reuse (dependencies, layers, build outputs) must be stored outside the runner explicitly.
Kubernetes
Kubernetes vận hành theo kiểu khai báo: bạn gửi manifest (YAML) mô tả trạng thái mong muốn lên API server, etcd lưu trạng thái đó, còn các controller chạy vòng lặp reconcile để kéo thực tế về khớp với mô tả.
Kubernetes is declarative: you submit manifests (YAML) describing the desired state to the API server, it is stored in etcd, and controllers run reconcile loops that pull reality toward it.
Control plane gồm API server, etcd, scheduler và controller manager. Mỗi node chạy kubelet, container runtime (thường là containerd) và kube-proxy.
The control plane is the API server, etcd, the scheduler and the controller manager. Every node runs the kubelet, a container runtime (usually containerd) and kube-proxy.
Các object nối với nhau bằng label và selector, không bằng tên. Requests là lượng CPU/RAM bạn đặt trước cho container; scheduler và autoscaler đều dựa vào con số này.
Objects are wired together by labels and selectors, not by name. Requests are the CPU/memory reserved for a container; the scheduler and autoscaler both rely on that number.
Điểm nốiWhere they meet
Điểm nối hai chủ đề: CI build ra một container image, đẩy lên registry, rồi Kubernetes kéo đúng image đó về chạy. Mọi tối ưu CI kết thúc ở image digest; mọi khái niệm Kubernetes bắt đầu từ đó.
Where the two topics meet: CI builds a container image, pushes it to a registry, and Kubernetes pulls that exact image to run it. Every CI optimisation ends at an image digest; every Kubernetes concept starts there.
Chọn cái nàoWhich one do I need
Bắt đầu bằng câu hỏi có cần gì không. Phần lớn đội nhỏ dừng ở đây.Start by asking whether you need anything at all. Most small teams can stop here.
CI
Khi nào không cầnWhen you don't need it.Không cần áp dụng kỹ thuật nào nếu pipeline dưới khoảng 10 phút, ít khi phải xếp hàng chờ runner và không ai ngồi đợi nó để làm tiếp. Mốc 10 phút là kinh nghiệm, không phải chuẩn; con số đúng là lúc đội bắt đầu chuyển việc khác trong khi chờ.You need none of these if the pipeline takes under about 10 minutes, rarely queues for runners, and nobody sits waiting on it. Ten minutes is a rule of thumb; the real threshold is when people start context-switching while they wait.
Phương án rẻ nhấtCheapest alternative.Bật cache có sẵn của action setup (ví dụ setup-node với cache: npm), bật concurrency để commit mới huỷ lần chạy cũ, rồi nhìn thời gian từng step. Chỉ đi tiếp khi biết chính xác step nào ăn thời gian.Turn on the built-in cache of your setup action (e.g. setup-node with cache: npm), enable concurrency so new commits cancel stale runs, then look at per-step timings. Go further only once you know which step eats the time.
Kubernetes
Khi nào không cầnWhen you don't need it.Không cần Kubernetes nếu bạn có vài service, một đội nhỏ và không ai có thời gian trực vận hành cluster. Kể cả bản managed (EKS, GKE, AKS) vẫn đòi người hiểu networking, RBAC, nâng cấp phiên bản và gỡ lỗi scheduler.You don't need Kubernetes with a handful of services, a small team, and nobody with time to operate a cluster. Even managed offerings (EKS, GKE, AKS) still need someone who understands networking, RBAC, version upgrades and scheduler debugging.
Phương án rẻ nhấtCheapest alternative.Nền tảng chạy container managed (Google Cloud Run, AWS ECS Fargate, Fly.io, Render) hoặc một VM với Docker Compose, systemd và Caddy làm reverse proxy. Chuyển sang Kubernetes khi số service, số đội hoặc nhu cầu autoscale vượt quá những thứ đó.A managed container platform (Google Cloud Run, AWS ECS Fargate, Fly.io, Render) or a single VM with Docker Compose, systemd and Caddy as the reverse proxy. Move to Kubernetes when the number of services, teams or autoscaling needs outgrows that.
CI: pipeline chậm ở đâu?CI: where is the time going?
Pipeline chậm?
│
├─ Chưa có thời gian từng step? ──────────▶ Đo trước. Không tối ưu mù.
├─ Job chờ runner lâu / boot chậm? ───────▶ C10 Warm runner
├─ Mỗi lần đều cài lại dependency? ──────▶ C2 Dependency cache
├─ Build/biên dịch chiếm nhiều nhất? ─────▶ C3 Incremental build
├─ docker build chạy lại từ đầu? ─────────▶ C4 Layer caching
├─ Test chiếm nhiều nhất?
│ ├─ Phần lớn thời gian ở tạo DB? ──────▶ C7 Schema một lần
│ ├─ PR nhỏ vẫn chạy cả suite? ─────────▶ C5 Affected-only
│ └─ Đã tối ưu mà vẫn dài? ─────────────▶ C6 Sharding
├─ Job không liên quan chờ nhau? ─────────▶ C1 Song song → C8 DAG
└─ Deploy lại build image? ───────────────▶ C9 Build oncePipeline slow?
│
├─ No per-step timings yet? ──────────────▶ Measure first. Don't guess.
├─ Long queue for runners / slow boot? ───▶ C10 Warm runners
├─ Dependencies reinstalled every run? ───▶ C2 Dependency cache
├─ Compile/build is the biggest chunk? ───▶ C3 Incremental build
├─ docker build starts from scratch? ─────▶ C4 Layer caching
├─ Tests are the biggest chunk?
│ ├─ Most time spent creating DBs? ─────▶ C7 Schema once
│ ├─ Small PR runs the whole suite? ────▶ C5 Affected-only
│ └─ Still long after that? ────────────▶ C6 Sharding
├─ Unrelated jobs wait on each other? ────▶ C1 Parallel → C8 DAG
└─ Deploy step rebuilds the image? ───────▶ C9 Build once
Kubernetes: tôi cần gì?Kubernetes: what do I need?
Tôi cần…
│
├─ Chạy app stateless, cập nhật không downtime ─▶ K2 Deployment (tạo K1 Pod)
├─ Service gọi nhau bằng địa chỉ ổn định ───────▶ K3 Service
├─ Nhận HTTP từ ngoài theo host/path ──────────▶ K4 Ingress → Gateway API
├─ Đổi cấu hình mà không build lại image ──────▶ K5 ConfigMap / Secret
├─ Chịu tải lên xuống theo giờ ────────────────▶ K6 HPA (bắt buộc có requests)
├─ Không nhận traffic khi chưa sẵn sàng ───────▶ K9 readiness
├─ Tự khởi động lại khi tiến trình treo ───────▶ K9 liveness
├─ Tách đội/môi trường, giới hạn tài nguyên ───▶ K10 Namespace + ResourceQuota
└─ Pod nằm Pending mãi / node chết ────────────▶ K7 Scheduler, K8 NodeI need to…
│
├─ Run a stateless app, update with no downtime ─▶ K2 Deployment (creates K1 Pods)
├─ Let services call each other at a stable address ▶ K3 Service
├─ Accept outside HTTP routed by host/path ──────▶ K4 Ingress → Gateway API
├─ Change config without rebuilding the image ───▶ K5 ConfigMap / Secret
├─ Absorb load that rises and falls ─────────────▶ K6 HPA (requests required)
├─ Keep traffic off pods that aren't ready ──────▶ K9 readiness
├─ Restart a hung process automatically ─────────▶ K9 liveness
├─ Separate teams/envs, cap resources ───────────▶ K10 Namespace + ResourceQuota
└─ Pod stuck Pending / a node died ──────────────▶ K7 Scheduler, K8 Node
Tra cứu từng khái niệmConcept reference
Mỗi thẻ có câu đánh đổi ở ngay đầu. Phần “Đính chính” ghi lại chỗ ảnh gốc sai hoặc thiếu.Every card leads with its trade-off. The “Correction” notes mark where the source image is wrong or incomplete.
CI: 10 cách tăng tốc buildCI: 10 ways to speed up builds
C1
Chạy job song song và matrixParallel jobs and matrix
Song song hoáParallelize
Ảnh gốcSource image: Serial Jobs → Parallel Matrix
Job không phụ thuộc nhau thì đừng bắt chúng xếp hàng.
Jobs that don't depend on each other shouldn't wait in line.
Đánh đổiTrade-offChọn chạy song song là đổi thêm phút runner (mỗi job setup lại từ đầu) lấy thời gian chờ ngắn hơn.Choosing parallel jobs trades extra runner minutes (each job sets up from scratch) for a shorter wait.
Lint, typecheck và unit test không cần output của nhau.
Lint, typecheck and unit tests that don't need each other's output.
Thư viện phải chạy trên nhiều phiên bản runtime hoặc nhiều hệ điều hành.
A library that must pass on several runtime versions or operating systems.
Monorepo có nhiều package build độc lập.
A monorepo with packages that build independently.
ĐượcYou get
Thời gian chờ bằng job dài nhất thay vì tổng các job.
Wait time drops to the longest job instead of the sum.
Lỗi rẻ báo sớm: lint thất bại sau 1 phút thay vì sau khi test chạy xong.
Cheap failures surface early: lint fails in a minute instead of after the tests.
Matrix phủ nhiều môi trường chỉ với vài dòng cấu hình.
A matrix covers many environments with a few lines of config.
MấtYou lose
Mỗi job trả lại chi phí setup, nên tổng phút runner tăng.
Each job pays setup again, so total runner minutes go up.
Bị chặn bởi giới hạn số job đồng thời của gói dịch vụ hoặc số runner.
Capped by your plan's concurrent-job limit or runner count.
Log và kết quả rải ra nhiều job, khó đọc hơn khi gỡ lỗi.
Logs and results spread across jobs, harder to read when debugging.
Dấu hiệu nhận biếtHow to spot itMột job có nhiều step mà không step nào dùng output của step trước.One job has many steps and none of them uses the previous step's output.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Tách các việc độc lập thành job riêng để chúng chạy cùng lúc. Matrix là một dạng đặc biệt: cùng một job nhân ra nhiều biến thể, ví dụ Node 20 và 22, Linux và macOS.Split independent work into separate jobs so they run at the same time. A matrix is a special case: one job fanned out into variants, such as Node 20 and 22, or Linux and macOS.
Hoạt động thế nàoHow it works.Hầu hết nền tảng CI mặc định chạy các job song song, trừ khi bạn khai báo phụ thuộc. Thứ thật sự gây tuần tự thường là việc dồn mọi thứ vào một job nhiều step. Khi tách, mỗi job tự checkout và tự cài môi trường, nên phần setup bị lặp lại.Most CI platforms run jobs in parallel by default unless you declare dependencies. What actually serialises things is usually cramming everything into one job with many steps. Once split, every job checks out and sets up on its own, so setup is repeated.
Mẹo triển khaiImplementation tips
Đo setup trước khi tách: setup 2 phút cho một job 30 giây thì tách ra là lỗ.
Measure setup before splitting: two minutes of setup for a 30-second job is a net loss.
Tắt fail-fast của matrix khi cần thấy hết các biến thể lỗi; bật khi chỉ cần biết có lỗi.
Turn matrix fail-fast off when you need to see every failing variant, on when you only need to know something broke.
Đặt concurrency với cancel-in-progress để commit mới huỷ lần chạy cũ trên cùng nhánh.
Set concurrency with cancel-in-progress so a new commit cancels the stale run on the same branch.
C2
Cache dependencyDependency cache
Không làm lạiDon't redo
Ảnh gốcSource image: Reinstall Every Run → Dependency Cache
Lockfile không đổi thì không có lý do tải lại package.
If the lockfile didn't change, there's no reason to download packages again.
Đánh đổiTrade-offChọn cache là đổi rủi ro dùng nhầm bản cũ lấy bước cài gần như tức thì.Choosing a cache trades the risk of restoring something stale for a near-instant install.
key = os + runtime + hash(package-lock.json)
hit → khôi phục ~/.npm → npm ci chạy offline, vài giây
miss → tải từ registry → lưu cache với key mớikey = os + runtime + hash(package-lock.json)
hit → restore ~/.npm → npm ci runs offline, seconds
miss → download from registry → save cache under new key
Dùng choUse for
npm, pnpm, yarn; pip, uv, poetry.
npm, pnpm, yarn; pip, uv, poetry.
Maven, Gradle với hàng trăm MB artifact.
Maven and Gradle with hundreds of MB of artifacts.
Go modules, Cargo registry.
Go modules, the Cargo registry.
ĐượcYou get
Bước cài giảm từ vài phút xuống vài giây khi cache hit.
Install drops from minutes to seconds on a cache hit.
Ít phụ thuộc registry bên ngoài, ít lỗi mạng ngẫu nhiên.
Less dependence on external registries, fewer random network failures.
Gần như không tốn công: nhiều action setup có sẵn tuỳ chọn cache.
Nearly free to adopt: many setup actions have a built-in cache option.
MấtYou lose
Key thiết kế sai thì dùng nhầm dependency cũ, lỗi không tái hiện được trên máy dev.
A badly designed key restores stale dependencies, causing failures nobody can reproduce locally.
Cache quá lớn thì tải và giải nén cũng tốn thời gian, có khi chậm hơn cài mới.
A huge cache takes time to download and extract, sometimes longer than a fresh install.
Có giới hạn dung lượng và bị xoá khi lâu không dùng (GitHub Actions: mặc định 10 GB mỗi repo, xoá sau 7 ngày không truy cập).
Size limits and eviction apply (GitHub Actions: 10 GB per repo by default, evicted after 7 days without access).
Dấu hiệu nhận biếtHow to spot itLog mỗi lần chạy đều thấy tải lại đúng những package giống hệt lần trước.Every run's log downloads exactly the same packages as last time.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Lưu thư mục cache của package manager giữa các lần chạy, dùng hash của lockfile làm khoá.Persist the package manager's cache directory between runs, keyed by a hash of the lockfile.
Hoạt động thế nàoHow it works.Trước step cài đặt, CI tìm cache theo key và giải nén vào runner. Sau khi job xong, nếu key chưa tồn tại thì CI nén và lưu lại. Lockfile đổi thì key đổi, cache cũ không bị dùng nhầm.Before the install step, CI looks up the cache by key and extracts it onto the runner. After the job, if the key didn't exist, CI compresses and saves it. A changed lockfile means a new key, so the stale cache isn't used.
Mẹo triển khaiImplementation tips
Cache thư mục cache của package manager (~/.npm), không cache node_modules, rồi vẫn chạy npm ci.
Cache the package manager's store (~/.npm), not node_modules, and still run npm ci.
Đưa phiên bản runtime vào key; đổi Node 20 sang 22 mà dùng lại native module cũ là lỗi khó hiểu.
Put the runtime version in the key; reusing native modules across Node 20 and 22 breaks in confusing ways.
Dùng restore-keys để khi miss vẫn khôi phục được bản gần nhất, chỉ tải phần chênh lệch.
Use restore-keys so a miss still restores the closest match and only fetches the difference.
C3
Incremental build với remote cacheIncremental build with remote cache
Một file đổi thì chỉ build lại phần phụ thuộc vào nó.
One changed file rebuilds only what depends on it.
Đánh đổiTrade-offChọn incremental build là đổi công khai báo dependency chặt chẽ lấy thời gian build tỷ lệ với thay đổi.Choosing incremental builds trades the effort of declaring dependencies strictly for build time that tracks the change.
libs/a ─┐
libs/b ─┼─▶ app a đổi → build a, app
libs/c ─┘ b, c → lấy từ remote cachelibs/a ─┐
libs/b ─┼─▶ app a changed → build a, app
libs/c ─┘ b, c → pulled from remote cache
Dùng choUse for
Monorepo nhiều package và app dùng chung thư viện.
Monorepos where many apps share libraries.
Dự án Java, Kotlin, C++ biên dịch lâu.
Java, Kotlin or C++ projects with long compiles.
Đội đông, nhiều PR chạm vào những phần khác nhau của repo.
Large teams whose PRs touch different parts of the repo.
ĐượcYou get
Thời gian build tỷ lệ với độ lớn thay đổi, không với độ lớn repo.
Build time scales with the size of the change, not the size of the repo.
Cache dùng chung giữa CI và máy dev, dev cũng nhanh hơn.
The cache is shared between CI and laptops, so local builds speed up too.
Hiệu quả rõ nhất ở monorepo, nơi phần lớn code không đổi trong một PR.
Biggest payoff in monorepos, where most code is untouched by any given PR.
MấtYou lose
Input ẩn (biến môi trường, file ngoài khai báo) làm cache trả sai kết quả.
Hidden inputs (env vars, undeclared files) make the cache return wrong results.
Remote cache là hạ tầng cần vận hành và bảo vệ; ai ghi được cache là ghi được output build.
The remote cache is infrastructure to run and secure; whoever can write to it can write build outputs.
Chuyển sang tool hỗ trợ tốt (Bazel, Gradle, Nx) là khoản đầu tư lớn.
Migrating to a tool that does this well (Bazel, Gradle, Nx) is a big investment.
Dấu hiệu nhận biếtHow to spot itSửa một file README mà CI vẫn biên dịch lại toàn bộ.Editing a README still recompiles everything.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Build tool băm input của từng target (source, cấu hình, dependency). Hash trùng với lần trước thì lấy output cũ thay vì biên dịch lại.The build tool hashes each target's inputs (source, config, dependencies). If the hash matches a previous run, it reuses the output instead of recompiling.
Hoạt động thế nàoHow it works.Trên máy dev, incremental build dựa vào output nằm sẵn trên đĩa. Runner CI thì trống trơn mỗi lần, nên cần remote cache dùng chung: Gradle build cache, Bazel remote cache, Nx hoặc Turborepo remote cache.On a laptop, incremental builds rely on outputs already on disk. A CI runner starts empty every time, so you need a shared remote cache: Gradle build cache, Bazel remote cache, Nx or Turborepo remote caching.
Mẹo triển khaiImplementation tips
Chỉ pipeline trên main được ghi remote cache; PR chỉ đọc, để chặn cache poisoning.
Let only main write to the remote cache; PRs read only, to prevent cache poisoning.
Theo dõi cache hit rate; tỷ lệ thấp bất thường gần như luôn là do input khai báo lỏng.
Track cache hit rate; an unusually low rate almost always means loosely declared inputs.
Chạy clean build định kỳ để phát hiện cache đã sai mà không ai biết.
Run a periodic clean build to catch a cache that has silently gone wrong.
C4
Docker layer caching và image gọnDocker layer caching and lean images
Layer nào không đổi thì dùng lại; thứ đổi thường xuyên đặt xuống cuối.
Reuse every unchanged layer; put what changes often at the bottom.
Đánh đổiTrade-offChọn layer caching là đổi kỷ luật sắp xếp Dockerfile lấy việc chỉ build lại phần thật sự đổi.Choosing layer caching trades Dockerfile ordering discipline for rebuilding only what changed.
FROM node:22-slim AS build
COPY package*.json ./ ← chỉ đổi khi dependency đổi
RUN npm ci ← được cache
COPY . . ← đổi mỗi commit
RUN npm run build
FROM node:22-slim ← stage cuối: không toolchain
COPY --from=build /app/dist ./distFROM node:22-slim AS build
COPY package*.json ./ ← changes only with dependencies
RUN npm ci ← cached
COPY . . ← changes every commit
RUN npm run build
FROM node:22-slim ← final stage: no toolchain
COPY --from=build /app/dist ./dist
Dùng choUse for
Service Node, Python, Go đóng image mỗi PR.
Node, Python or Go services that build an image on every PR.
Image nặng vì kéo theo compiler và công cụ build.
Images bloated by compilers and build tooling.
Nhiều service dùng chung một base image.
Many services built on the same base image.
ĐượcYou get
Build lại chỉ tốn thời gian của vài layer cuối.
Rebuilds only pay for the last few layers.
Multi-stage bỏ toolchain khỏi image cuối, image nhỏ đi nhiều lần nên pull nhanh hơn khi triển khai và khi mở rộng.
Multi-stage drops the toolchain from the final image, often shrinking it several times, so pulls are faster on deploy and scale-out.
Image nhỏ ít package hơn, ít lỗ hổng phải vá hơn.
Smaller images carry fewer packages and fewer vulnerabilities to patch.
MấtYou lose
COPY . . đặt quá sớm hoặc ARG đổi mỗi lần (như commit SHA) phá toàn bộ cache phía sau.
An early COPY . . or an ARG that changes every run (like the commit SHA) invalidates everything below it.
Xuất cache lên registry tốn thêm thời gian upload.
Exporting cache to a registry adds upload time.
Base image nằm trong cache lâu làm chậm việc nhận bản vá bảo mật.
A long-cached base image delays picking up security patches.
Dấu hiệu nhận biếtHow to spot itĐổi một dòng code mà log build vẫn chạy lại npm ci từ đầu.A one-line code change still re-runs npm ci from scratch.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Docker build theo từng layer. Nếu instruction và input của một layer không đổi, Docker dùng lại layer cũ. Chỉ cần một layer đổi là mọi layer phía sau phải build lại.Docker builds layer by layer. If a layer's instruction and inputs are unchanged, the old layer is reused. As soon as one layer changes, every layer after it rebuilds.
Hoạt động thế nàoHow it works.Sắp thứ ít đổi lên trên, thứ đổi thường xuống dưới. Runner CI không giữ cache local, nên phải xuất cache ra ngoài bằng BuildKit: --cache-to và --cache-from với type=registry hoặc type=gha. Multi-stage build tách môi trường build khỏi image chạy.Put rarely changing steps first and frequently changing ones last. CI runners don't keep a local cache, so export it with BuildKit: --cache-to and --cache-from with type=registry or type=gha. Multi-stage builds separate the build environment from the runtime image.
Mẹo triển khaiImplementation tips
Luôn có .dockerignore loại .git và node_modules; thiếu nó thì COPY . . gần như lúc nào cũng khác.
Always have a .dockerignore excluding .git and node_modules; without it COPY . . is almost always different.
Dùng RUN --mount=type=cache cho thư mục cache của package manager.
Use RUN --mount=type=cache for the package manager's cache directory.
Ghim base image theo digest và để Renovate hoặc Dependabot cập nhật.
Pin base images by digest and let Renovate or Dependabot bump them.
C5
Chỉ chạy test bị ảnh hưởngAffected-only tests
Làm ít hơnDo less
Ảnh gốcSource image: Run Full Suite → Affected-Only Tests
PR sửa một chỗ thì chạy test của chỗ đó và những gì phụ thuộc vào nó.
A PR that touches one area runs that area's tests and its dependents.
Đánh đổiTrade-offChọn affected-only là đổi một phần độ chắc chắn lấy tốc độ phản hồi trên PR.Choosing affected-only trades some certainty for faster feedback on PRs.
diff: libs/payment
graph: payment ◀── checkout ◀── web
chạy: payment, checkout, web
bỏ qua: search, admin, docsdiff: libs/payment
graph: payment ◀── checkout ◀── web
run: payment, checkout, web
skip: search, admin, docs
Dùng choUse for
Monorepo có ranh giới package rõ ràng.
Monorepos with clear package boundaries.
Suite chạy hàng chục phút trong khi PR thường chỉ vài file.
Suites that take tens of minutes while most PRs touch a few files.
Đội merge nhiều PR nhỏ mỗi ngày.
Teams merging many small PRs a day.
ĐượcYou get
Thời gian test trên PR tỷ lệ với phạm vi thay đổi.
PR test time scales with the scope of the change.
Giảm mạnh phút runner.
Large cut in runner minutes.
Reviewer nhận kết quả sớm, vòng review ngắn lại.
Reviewers get results sooner, so review loops shorten.
MấtYou lose
Đồ thị thiếu cạnh (file cấu hình, fixture, reflection, gọi qua mạng) thì bỏ sót test đáng lẽ thất bại.
Missing edges (config files, fixtures, reflection, network calls) skip tests that should have failed.
Khó áp dụng cho monolith không có ranh giới module.
Hard to apply to a monolith with no module boundaries.
Lỗi tích hợp chéo module có thể lọt đến main.
Cross-module integration bugs can slip into main.
Dấu hiệu nhận biếtHow to spot itPR sửa một module nhỏ vẫn phải chờ toàn bộ suite.A PR touching one small module still waits for the entire suite.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Dùng đồ thị phụ thuộc để suy ra test nào có thể bị ảnh hưởng bởi diff, và bỏ qua phần còn lại.Use the dependency graph to work out which tests the diff can affect, and skip the rest.
Hoạt động thế nàoHow it works.Tool so diff với merge-base của nhánh đích rồi đi ngược đồ thị: Nx affected, Turborepo --filter, Bazel, Jest --changedSince, pytest-testmon. Chất lượng kết quả phụ thuộc hoàn toàn vào độ đầy đủ của đồ thị.The tool diffs against the target branch's merge-base and walks the graph backwards: Nx affected, Turborepo --filter, Bazel, Jest --changedSince, pytest-testmon. The result is only as good as the graph is complete.
Mẹo triển khaiImplementation tips
Luôn chạy full suite trên main hoặc theo lịch hằng đêm làm lưới an toàn.
Always run the full suite on main or nightly as a safety net.
Diff chạm lockfile, cấu hình CI hoặc file dùng chung thì ép chạy toàn bộ.
Force a full run when the diff touches the lockfile, CI config or shared files.
So với merge-base, không so với commit trước, nếu không PR nhiều commit sẽ bỏ sót thay đổi.
Compare with the merge-base, not the previous commit, or multi-commit PRs miss changes.
C6
Chia shard test ra nhiều runnerTest sharding across runners
Song song hoáParallelize
Ảnh gốcSource image: Single Runner → Sharded Runners
Suite dài chia thành N phần chạy song song; shard chậm nhất quyết định tổng.
Split a long suite into N parallel parts; the slowest shard sets the total.
Đánh đổiTrade-offChọn sharding là đổi N lần chi phí setup lấy thời gian chờ gần như chia N.Choosing sharding trades paying setup N times for a wait time close to divided by N.
40' trên 1 runner
├─ lý tưởng: 5 shard × 8' = 8'
└─ thực tế: setup 2' + shard chậm nhất 9' ≈ 11'40' on one runner
├─ ideal: 5 shards × 8' = 8'
└─ real: 2' setup + slowest 9' ≈ 11'
Dùng choUse for
Suite E2E dài.
Long end-to-end suites.
Test tích hợp nặng, mỗi test đã hợp lý nhưng tổng vẫn dài.
Heavy integration tests where each test is reasonable but the total is long.
Sau khi đã cache và bỏ bớt test thừa mà vẫn chậm.
After caching and trimming tests, when it is still slow.
ĐượcYou get
Thời gian giảm gần tuyến tính khi setup nhỏ so với thời gian test.
Near-linear speedup when setup is small relative to test time.
Dễ mở rộng: tăng N khi suite lớn lên.
Scales simply: raise N as the suite grows.
Kết hợp tốt với cache, vì mỗi shard đều hưởng setup nhanh.
Pairs well with caching, since every shard benefits from fast setup.
MấtYou lose
Tổng phút runner tăng vì setup lặp lại N lần.
Total runner minutes rise because setup repeats N times.
Chia theo số file dễ lệch: một shard ôm hết test chậm.
Splitting by file count skews easily: one shard gets all the slow tests.
Test phụ thuộc thứ tự chạy hoặc dữ liệu chung sẽ vỡ khi bị tách.
Tests that depend on run order or shared data break when separated.
Dấu hiệu nhận biếtHow to spot itTest là phần dài nhất của pipeline, và không có test đơn lẻ nào chậm bất thường.Tests are the longest part of the pipeline, and no single test is unusually slow.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Chia một suite test thành N phần và chạy trên N runner cùng lúc.Divide a test suite into N parts and run them on N runners at once.
Hoạt động thế nàoHow it works.Test runner nhận chỉ số shard và chỉ chạy phần của mình: Jest --shard=1/5, Playwright --shard, pytest-split, Knapsack. Chia theo thời gian chạy lịch sử cho kết quả đều hơn nhiều so với chia theo số file.The test runner takes a shard index and runs only its slice: Jest --shard=1/5, Playwright --shard, pytest-split, Knapsack. Splitting by historical timing is far more even than splitting by file count.
Mẹo triển khaiImplementation tips
Lưu timing report từ lần chạy trước và chia theo nó.
Save timing reports from previous runs and split by them.
Chọn N sao cho setup chỉ chiếm phần nhỏ thời gian của mỗi shard; quá nhiều shard là trả tiền cho setup.
Pick N so setup stays a small share of each shard; too many shards means paying for setup.
Gộp report JUnit và coverage ở một job cuối để có một kết quả duy nhất.
Merge JUnit and coverage reports in a final job so there is one result.
C7
Tạo schema một lần, cô lập từng testCreate the schema once, isolate each test
Làm ít hơnDo less
Ảnh gốcSource image: Fresh DB Per Test → Shared Test DB
Trả chi phí đắt một lần, mỗi test chỉ trả chi phí rẻ để có trạng thái sạch.
Pay the expensive part once; each test pays only a cheap reset.
Đánh đổiTrade-offChọn rollback theo từng test là đổi khả năng kiểm tra hành vi lúc commit lấy tốc độ mà vẫn giữ cô lập.Choosing per-test rollback trades the ability to test commit-time behaviour for speed with isolation intact.
Chậm: mỗi test → CREATE + migrate → chạy → DROP × 1000
Nhanh: migrate 1 lần
mỗi test → BEGIN → chạy → ROLLBACK × 1000
worker song song → DB riêng clone từ templateSlow: each test → CREATE + migrate → run → DROP × 1000
Fast: migrate once
each test → BEGIN → run → ROLLBACK × 1000
parallel worker → own DB cloned from a template
Dùng choUse for
Test tích hợp tầng repository hoặc ORM.
Integration tests for the repository or ORM layer.
Suite có hàng nghìn test chạm database.
Suites with thousands of database-touching tests.
Chạy test song song nhiều worker (kết hợp C6).
Parallel test workers (combine with C6).
ĐượcYou get
Mỗi test chỉ tốn một transaction thay vì dựng schema.
Each test costs a transaction instead of building a schema.
Vẫn cô lập hoàn toàn, không flaky vì dữ liệu của test khác.
Full isolation remains, so no flakiness from another test's data.
Chạy song song an toàn khi mỗi worker có database riêng.
Safe to parallelise when each worker has its own database.
MấtYou lose
Code tự commit hoặc mở connection thứ hai sẽ thoát khỏi rollback.
Code that commits on its own or opens a second connection escapes the rollback.
Không kiểm tra được hành vi chỉ xảy ra lúc commit (deferred constraint, LISTEN/NOTIFY).
Can't test behaviour that only happens at commit (deferred constraints, LISTEN/NOTIFY).
Thay Postgres bằng SQLite in-memory cho nhanh sẽ lệch hành vi so với production.
Swapping Postgres for in-memory SQLite for speed diverges from production behaviour.
Dấu hiệu nhận biếtHow to spot itProfile cho thấy phần lớn thời gian test nằm ở migrate hoặc truncate chứ không ở logic.Profiling shows most test time in migrating or truncating, not in the logic under test.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Tách phần tốn kém (tạo schema, chạy migration, seed) ra khỏi từng test. Mỗi test vẫn thấy một database sạch, nhưng bằng cơ chế rẻ như rollback transaction.Move the expensive part (creating the schema, running migrations, seeding) out of each test. Every test still sees a clean database, via a cheap mechanism such as a rolled-back transaction.
Hoạt động thế nàoHow it works.Migration chạy một lần mỗi phiên test. Mỗi test mở transaction và rollback khi xong; pytest-django và Rails làm việc này mặc định. Khi chạy song song, mỗi worker có database riêng; với Postgres, CREATE DATABASE test_w1 TEMPLATE test_base clone nhanh hơn chạy lại migration.Migrations run once per test session. Each test opens a transaction and rolls it back; pytest-django and Rails do this by default. For parallel runs each worker gets its own database; in Postgres, CREATE DATABASE test_w1 TEMPLATE test_base clones faster than re-running migrations.
Mẹo triển khaiImplementation tips
Dùng Testcontainers với đúng phiên bản database của production.
Use Testcontainers with the same database version as production.
Chỉ cho database test: tắt fsync và chạy trên tmpfs.
For test databases only: disable fsync and run on tmpfs.
Test nào cần commit thật thì đánh dấu riêng và dọn bằng truncate.
Mark tests that need a real commit separately and clean up with truncate.
C8
Pipeline dạng DAGDAG pipeline
Song song hoáParallelize
Ảnh gốcSource image: Linear Pipeline → DAG Pipeline
Job chạy ngay khi thứ nó cần đã xong, không chờ cả stage.
A job starts as soon as what it needs is done, not when a whole stage finishes.
Đánh đổiTrade-offChọn DAG là đổi sự đơn giản của pipeline tuyến tính lấy thời gian bằng đúng critical path.Choosing a DAG trades the simplicity of a linear pipeline for wall time equal to the critical path.
Pipeline GitLab nhiều stage mà job nhanh phải chờ job chậm cùng stage.
Multi-stage GitLab pipelines where fast jobs wait for slow ones in the same stage.
Build một lần rồi rẽ nhánh ra nhiều loại test.
Build once, then fan out into several kinds of tests.
Triển khai lên nhiều môi trường hoặc nhiều service độc lập.
Deploying to several environments or independent services.
ĐượcYou get
Thời gian đúng bằng critical path.
Wall time equals the critical path exactly.
Nhìn đồ thị là biết nên tối ưu job nào.
The graph shows which job is worth optimising.
Job thất bại chỉ chặn các nhánh phụ thuộc vào nó.
A failing job blocks only the branches that depend on it.
MấtYou lose
Đồ thị lớn khó đọc và khó bảo trì hơn pipeline tuyến tính.
Large graphs are harder to read and maintain than a linear pipeline.
Khai báo thiếu phụ thuộc thì job chạy khi artifact chưa sẵn sàng.
A missing dependency lets a job run before its artifact exists.
Nhiều nhánh song song cần nhiều runner cùng lúc.
Many parallel branches need many runners at once.
Dấu hiệu nhận biếtHow to spot itJob nhanh phải chờ một job chậm không liên quan chỉ vì chung stage.A fast job waits on an unrelated slow job just because they share a stage.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Mô tả pipeline như đồ thị phụ thuộc có hướng. Khác C1 ở chỗ: C1 bỏ xếp hàng cho job độc lập, còn DAG cho cả những job có phụ thuộc chạy sớm nhất có thể.Describe the pipeline as a directed dependency graph. The difference from C1: C1 removes queuing for independent jobs, while a DAG lets even dependent jobs start as early as possible.
Hoạt động thế nàoHow it works.GitHub Actions dùng needs; GitLab dùng needs để thoát khỏi ràng buộc stage. Tổng thời gian bằng critical path, nên tối ưu một job không nằm trên critical path không làm pipeline nhanh hơn.GitHub Actions uses needs; GitLab uses needs to break out of stage ordering. Total time equals the critical path, so speeding up a job off the critical path doesn't make the pipeline faster.
Mẹo triển khaiImplementation tips
Tìm critical path trước; tối ưu nhánh khác là công vô ích.
Find the critical path first; optimising other branches is wasted effort.
Truyền dữ liệu giữa job bằng artifact đặt tên rõ ràng, không dựa vào trạng thái runner.
Pass data between jobs as clearly named artifacts, never through runner state.
Xem lại đồ thị mỗi khi thêm job; phụ thuộc thừa tích tụ rất nhanh.
Review the graph whenever you add a job; unnecessary edges pile up fast.
C9
Build một lần, deploy nhiều nơiBuild once, deploy many
Không làm lạiDon't redo
Ảnh gốcSource image: Rebuild Per Stage → Build Once, Deploy Many
Thứ đã qua test phải chính là thứ lên production.
The artifact that passed the tests must be the one that ships.
Đánh đổiTrade-offChọn build once là đổi công tách config khỏi artifact lấy sự chắc chắn thứ lên production là thứ đã test.Choosing build-once trades the work of separating config from the artifact for certainty that what ships is what was tested.
commit a1b2 ─▶ build ─▶ image@sha256:9f3c…
├─ test
├─ staging cùng digest
└─ production cùng digest
config từng môi trường ─▶ K5 ConfigMap / Secretcommit a1b2 ─▶ build ─▶ image@sha256:9f3c…
├─ test
├─ staging same digest
└─ production same digest
per-env config ─▶ K5 ConfigMap / Secret
Dùng choUse for
Mọi pipeline có từ hai môi trường trở lên.
Any pipeline with two or more environments.
Cần rollback nhanh về đúng bản trước.
Fast rollback to the exact previous build.
Audit hoặc compliance cần biết chính xác cái gì đang chạy.
Audit or compliance needing to know exactly what is running.
ĐượcYou get
Loại hẳn lỗi “build lại ra kết quả khác” giữa test và production.
Eliminates “the rebuild came out different” between test and production.
Triển khai nhanh vì không phải build.
Deploys are fast because nothing is built.
Rollback chỉ là trỏ lại digest cũ.
Rollback is pointing back at the old digest.
MấtYou lose
Bắt buộc tách config khỏi image; code đọc config lúc build phải sửa.
Config must be separated from the image; code that reads config at build time has to change.
Cần registry và chính sách giữ, dọn image cũ.
You need a registry and a retention policy for old images.
Không còn dùng được kiểu bật tắt tính năng lúc build theo từng môi trường.
Build-time, per-environment feature toggles no longer work.
Dấu hiệu nhận biếtHow to spot itJob deploy chạy lại docker build cho từng môi trường.The deploy job re-runs docker build for each environment.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Build artifact (image, binary) đúng một lần, test chính artifact đó, rồi promote nguyên vẹn qua staging và production. Khác biệt giữa môi trường nằm ở cấu hình nạp lúc chạy.Build the artifact (image, binary) exactly once, test that artifact, and promote it unchanged through staging and production. Environment differences live in configuration loaded at runtime.
Hoạt động thế nàoHow it works.Push image một lần với tag là commit SHA. Các bước sau tham chiếu image theo digest, thứ bất biến, thay vì theo tag có thể bị ghi đè. Triển khai chỉ là cập nhật digest trong manifest Kubernetes.Push the image once, tagged with the commit SHA. Later steps reference it by digest, which is immutable, rather than by a tag that can be overwritten. A deploy is just updating the digest in the Kubernetes manifest.
Mẹo triển khaiImplementation tips
Triển khai theo digest, không bao giờ theo latest.
Deploy by digest, never by latest.
Ký image bằng cosign và kiểm tra chữ ký khi admit vào cluster.
Sign images with cosign and verify the signature at admission.
Frontend: đừng gắn cứng API URL lúc build; nạp từ file cấu hình lúc chạy.
Frontends: don't bake API URLs in at build time; load them from a runtime config file.
C10
Warm runner poolWarm runner pool
Không làm lạiDon't redo
Ảnh gốcSource image: Boot From Scratch → Warm Runner Pool
Job nhận ngay một runner đã khởi động và có sẵn công cụ.
Jobs get a runner that is already booted and tooled.
Đánh đổiTrade-offChọn warm pool là đổi tiền cho máy chạy không tải lấy việc job bắt đầu ngay lập tức.Choosing a warm pool trades paying for idle machines for jobs that start immediately.
Lạnh: job ─▶ tạo VM ─▶ cài toolchain ─▶ chạy
Ấm: job ─▶ runner rảnh trong pool ───▶ chạy
└─ xong một job thì huỷ, pool tạo cái mớiCold: job ─▶ create VM ─▶ install toolchain ─▶ run
Warm: job ─▶ idle runner from the pool ─────▶ run
└─ destroyed after one job, pool replaces it
Dùng choUse for
Đội đông với nhiều job ngắn, nơi thời gian chờ lớn hơn thời gian chạy.
Large teams with many short jobs, where waiting exceeds running.
Hàng đợi runner dài vào giờ cao điểm.
Long runner queues at peak hours.
Job cần toolchain nặng như Android SDK, CUDA, Xcode.
Jobs needing heavy toolchains like the Android SDK, CUDA or Xcode.
ĐượcYou get
Loại bỏ thời gian xếp hàng và khởi động máy.
Removes queue and boot time.
Image dựng sẵn bỏ bước cài toolchain mỗi lần.
Pre-baked images skip toolchain installs every run.
Ít phụ thuộc mạng ngoài hơn, ổn định hơn.
Less dependence on outside networks, more stable.
MấtYou lose
Trả tiền cho máy ngồi không tải.
You pay for machines sitting idle.
Runner dùng lại giữa các job có thể rò trạng thái hoặc secret, nguy hiểm với repo public và PR từ fork.
Runners reused across jobs can leak state or secrets, dangerous for public repos and fork PRs.
Tự vận hành fleet runner là một hệ thống thật cần người chăm.
Operating your own runner fleet is a real system that needs an owner.
Dấu hiệu nhận biếtHow to spot itThời gian chờ nhận runner hoặc cài công cụ dài hơn cả thời gian chạy job.Time waiting for a runner or installing tools exceeds the job itself.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Giữ sẵn một nhóm runner rảnh đã khởi động, hoặc dựng sẵn image runner chứa toolchain, để job không phải chờ tạo máy và cài công cụ.Keep a pool of idle, booted runners, or pre-bake a runner image with the toolchain, so jobs don't wait for a machine to be created and tools installed.
Hoạt động thế nàoHow it works.Autoscaler cho runner tự host giữ một số runner rảnh tối thiểu: Actions Runner Controller với minRunners, GitLab Runner autoscaler với idle count. Cách thứ hai là đóng sẵn toolchain nặng vào image runner để bỏ bước cài.Self-hosted runner autoscalers keep a minimum number idle: Actions Runner Controller with minRunners, the GitLab Runner autoscaler with an idle count. The other lever is baking heavy toolchains into the runner image to skip installation.
Mẹo triển khaiImplementation tips
Ấm nhưng vẫn dùng một lần: mỗi runner chạy một job rồi huỷ, pool tạo cái mới sẵn.
Warm but still ephemeral: each runner runs one job and is destroyed, the pool pre-creates the next.
Tăng giảm pool theo lịch giờ làm việc thay vì giữ cố định cả ngày.
Scale the pool on a working-hours schedule instead of keeping it fixed all day.
Không bao giờ cho PR từ fork chạy trên runner tự host.
Never let fork PRs run on self-hosted runners.
Kubernetes: 10 khái niệmKubernetes: 10 concepts
K1
PodPod
WorkloadWorkload
Ảnh gốcSource image: Pod
Một hoặc vài container dùng chung một IP và vòng đời.
One or a few containers sharing one IP and one lifecycle.
Đánh đổiTrade-offGộp container vào một Pod là đổi khả năng mở rộng riêng từng container lấy việc chúng luôn đi cùng nhau.Putting containers in one Pod trades scaling them independently for guaranteeing they always run together.
Init container chạy migration hoặc chờ dependency trước khi app lên.
An init container that runs migrations or waits for a dependency before the app starts.
Hai tiến trình buộc phải cùng máy và cùng sống chết.
Two processes that must share a machine and live and die together.
ĐượcYou get
Gom các container cùng vòng đời thành một đơn vị quản lý.
Groups containers with a shared lifecycle into one unit.
Mạng đơn giản: mỗi Pod một IP, không phải map port.
Simple networking: one IP per Pod, no port mapping.
Sidecar tách logic phụ trợ khỏi code ứng dụng.
Sidecars keep supporting logic out of application code.
MấtYou lose
Pod là tạm thời: IP và dữ liệu local mất khi Pod bị thay.
Pods are ephemeral: IP and local data are lost when replaced.
Mở rộng theo đơn vị Pod, nên mọi container bên trong mở rộng cùng nhau.
Scaling is per Pod, so every container inside scales together.
Pod tạo trực tiếp không có controller thì chết là không ai tạo lại.
A Pod created without a controller is never recreated when it dies.
Dấu hiệu nhận biếtHow to spot itHai container luôn phải đi cùng nhau và nói chuyện qua localhost.Two containers must always travel together and talk over localhost.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Đơn vị nhỏ nhất Kubernetes triển khai. Các container trong một Pod dùng chung network namespace (một IP, gọi nhau qua localhost) và có thể dùng chung volume.The smallest unit Kubernetes deploys. Containers in a Pod share a network namespace (one IP, reach each other on localhost) and can share volumes.
Hoạt động thế nàoHow it works.Scheduler gán Pod cho một node; kubelet trên node đó gọi container runtime để tạo container. Pod không tự hồi sinh: chết là mất, và controller như Deployment sẽ tạo Pod mới với IP mới.The scheduler assigns the Pod to a node; that node's kubelet asks the container runtime to create the containers. Pods don't resurrect: a dead Pod is gone, and a controller such as a Deployment creates a new one with a new IP.
Mẹo triển khaiImplementation tips
Gần như không bao giờ tạo Pod trực tiếp; dùng Deployment, Job hoặc StatefulSet.
Almost never create Pods directly; use a Deployment, Job or StatefulSet.
Sidecar kiểu mới (init container với restartPolicy: Always, GA từ Kubernetes 1.33) khởi động trước và tắt sau app.
Native sidecars (init containers with restartPolicy: Always, GA since Kubernetes 1.33) start before and stop after the app.
Gỡ lỗi Pod vừa crash: kubectl logs <pod> --previous.
Debug a Pod that just crashed with kubectl logs <pod> --previous.
K2
DeploymentDeployment
WorkloadWorkload
Ảnh gốcSource image: Deployment
Giữ đúng N Pod và thay phiên bản dần dần.
Keeps N Pods running and swaps versions gradually.
Đánh đổiTrade-offChọn rolling update là đổi việc hai phiên bản chạy chung một lúc lấy cập nhật không downtime.Choosing rolling updates trades having two versions live at once for zero-downtime releases.
Rolling update và rollback (kubectl rollout undo) có sẵn.
Rolling updates and rollback (kubectl rollout undo) built in.
Tự tạo lại Pod chết.
Recreates Pods that die.
Khai báo: nói muốn 5 replica, controller lo phần còn lại.
Declarative: say you want 5 replicas, the controller does the rest.
MấtYou lose
Không hợp với ứng dụng stateful cần danh tính ổn định; dùng StatefulSet.
Wrong fit for stateful apps that need stable identity; use a StatefulSet.
Trong lúc rollout, v1 và v2 chạy cùng lúc, nên API và schema phải tương thích ngược.
During a rollout v1 and v2 run side by side, so APIs and schemas must be backward compatible.
Không có readiness probe thì “Ready” chỉ có nghĩa là tiến trình đã khởi động.
Without a readiness probe, “Ready” only means the process started.
Dấu hiệu nhận biếtHow to spot itService stateless, các replica thay thế được cho nhau.A stateless service whose replicas are interchangeable.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Controller quản lý ReplicaSet để luôn có đúng số Pod từ một template, và rolling update khi template thay đổi.A controller that manages ReplicaSets to keep a set number of Pods from one template, and rolls out changes when the template changes.
Hoạt động thế nàoHow it works.Đổi template (image, env) thì Deployment tạo ReplicaSet mới, tăng nó và giảm ReplicaSet cũ theo maxSurge và maxUnavailable. Nó chỉ đi tiếp khi Pod mới qua readiness (K9). Deployment giữ lại ReplicaSet cũ để rollback.When the template (image, env) changes, the Deployment creates a new ReplicaSet, scales it up and the old one down according to maxSurge and maxUnavailable. It advances only once new Pods pass readiness (K9). Old ReplicaSets are kept for rollback.
Mẹo triển khaiImplementation tips
Service ít replica: đặt maxUnavailable: 0 để không bao giờ hụt công suất.
For low-replica services, set maxUnavailable: 0 so capacity never dips.
Migration database theo kiểu expand rồi contract để hai phiên bản cùng sống được.
Run database migrations as expand-then-contract so both versions can coexist.
Chạy kubectl rollout status trong CI và đặt progressDeadlineSeconds để rollout kẹt thì pipeline thất bại.
Run kubectl rollout status in CI and set progressDeadlineSeconds so a stuck rollout fails the pipeline.
K3
ServiceService
MạngNetworking
Ảnh gốcSource image: Service
Một địa chỉ ổn định đứng trước nhóm Pod liên tục thay đổi.
One stable address in front of Pods that keep changing.
Đánh đổiTrade-offChọn Service là đổi quyền chọn Pod cụ thể lấy một địa chỉ không bao giờ phải cập nhật.Choosing a Service trades control over which Pod you hit for an address you never have to update.
api.shop.svc.cluster.local → 10.96.0.7 selector app=api
├─▶ 10.1.4.7 ✓
├─▶ 10.1.6.2 ✓
└─▶ 10.1.5.1 ✗ chưa Ready → bị loại khỏi endpointapi.shop.svc.cluster.local → 10.96.0.7 selector app=api
├─▶ 10.1.4.7 ✓
├─▶ 10.1.6.2 ✓
└─▶ 10.1.5.1 ✗ not Ready → removed from endpoints
Dùng choUse for
Service gọi nhau trong cluster.
Services calling each other inside the cluster.
Gom nhiều replica sau một địa chỉ.
Putting many replicas behind one address.
Kiểu LoadBalancer hoặc NodePort để lộ ra ngoài khi không cần định tuyến HTTP.
Type LoadBalancer or NodePort to expose outside when HTTP routing isn't needed.
ĐượcYou get
Client không cần biết Pod nào đang sống.
Clients don't need to know which Pods are alive.
DNS có sẵn dạng <service>.<namespace>.svc.cluster.local.
Built-in DNS as <service>.<namespace>.svc.cluster.local.
Cân bằng tải cơ bản không tốn thêm gì.
Basic load balancing at no extra cost.
MấtYou lose
Cân bằng ở tầng L4 theo kết nối: gRPC và HTTP/2 giữ kết nối lâu sẽ dính vào một Pod.
L4, per-connection balancing: gRPC and HTTP/2 hold long connections and stick to one Pod.
Selector gõ sai thì Service không có endpoint nào và không báo lỗi.
A typo in the selector leaves the Service with no endpoints and no error.
ClusterIP chỉ ổn định trong vòng đời Service; xoá và tạo lại là IP mới.
The ClusterIP is stable only for the Service's lifetime; delete and recreate gives a new IP.
Dấu hiệu nhận biếtHow to spot itClient đang hardcode IP của Pod.A client has a Pod IP hard-coded.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.IP ảo ổn định (ClusterIP) kèm tên DNS, đứng trước một nhóm Pod được chọn bằng label. Traffic chỉ chia cho các Pod đang Ready.A stable virtual IP (ClusterIP) plus a DNS name in front of a set of Pods chosen by label. Traffic is spread only across Pods that are Ready.
Hoạt động thế nàoHow it works.Controller duy trì EndpointSlice chứa IP các Pod khớp selector và đang Ready. kube-proxy (iptables, IPVS hoặc nftables) hoặc plugin CNI dịch IP ảo thành IP Pod thật.A controller maintains EndpointSlices listing the IPs of Ready Pods that match the selector. kube-proxy (iptables, IPVS or nftables) or the CNI plugin translates the virtual IP into real Pod IPs.
Mẹo triển khaiImplementation tips
Lệnh gỡ lỗi đầu tiên: kubectl get endpointslices -l kubernetes.io/service-name=<svc>.
First debugging command: kubectl get endpointslices -l kubernetes.io/service-name=<svc>.
gRPC cần cân bằng phía client hoặc service mesh.
gRPC needs client-side balancing or a service mesh.
Luôn gọi bằng tên DNS, không bằng ClusterIP.
Always call by DNS name, not by ClusterIP.
K4
Ingress và Gateway APIIngress and the Gateway API
MạngNetworking
Ảnh gốcSource image: Ingress
Một cổng vào HTTP, định tuyến theo host và path.
One HTTP entry point, routed by host and path.
Đánh đổiTrade-offChọn Ingress là đổi một điểm lỗi chung lấy một cổng vào duy nhất dễ quản.Choosing Ingress trades a shared point of failure for a single, easy-to-manage front door.
Một load balancer của cloud thay vì mỗi service một cái.
One cloud load balancer instead of one per service.
ĐượcYou get
Một điểm vào, một IP, một chỗ quản TLS.
One entry point, one IP, one place for TLS.
Giảm chi phí load balancer.
Lower load balancer cost.
Định tuyến theo host và path không cần sửa ứng dụng.
Host and path routing without touching the app.
MấtYou lose
Tính năng nâng cao (rewrite, rate limit, canary) nằm trong annotation riêng của từng controller, khó chuyển đổi.
Advanced features (rewrites, rate limits, canaries) live in controller-specific annotations, hard to port.
Controller là điểm lỗi chung cho toàn bộ traffic vào.
The controller is a shared failure point for all inbound traffic.
Chỉ cho HTTP(S); TCP và UDP phải đi đường khác.
HTTP(S) only; TCP and UDP need another path.
Dấu hiệu nhận biếtHow to spot itMỗi service đang có một LoadBalancer riêng.Every service has its own LoadBalancer.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Luật định tuyến HTTP(S) từ bên ngoài vào cluster: theo host và path, chuyển request đến Service tương ứng, thường kèm kết thúc TLS.Rules for routing outside HTTP(S) traffic into the cluster: by host and path to the matching Service, usually with TLS termination.
Hoạt động thế nàoHow it works.Object Ingress chỉ là cấu hình. Phải có một Ingress controller (Traefik, HAProxy, controller của nhà cung cấp cloud…) đọc nó và tự cấu hình proxy. Gateway API, thế hệ kế tiếp, tách vai: Gateway do đội hạ tầng quản, HTTPRoute do đội ứng dụng quản.An Ingress object is only configuration. An Ingress controller (Traefik, HAProxy, a cloud provider's controller…) must read it and configure its proxy. The Gateway API, its successor, splits roles: the infrastructure team owns the Gateway, application teams own HTTPRoutes.
Mẹo triển khaiImplementation tips
Dự án mới: bắt đầu thẳng với Gateway API.
New projects: start directly with the Gateway API.
Kiểm tra cluster có dùng ingress-nginx không: kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx; nếu có, lên kế hoạch chuyển (công cụ ingress2gateway hỗ trợ chuyển đổi).
Check for ingress-nginx with kubectl get pods -A -l app.kubernetes.io/name=ingress-nginx; if present, plan a migration (the ingress2gateway tool helps convert).
Chạy controller ít nhất hai replica, rải trên nhiều node.
Run the controller with at least two replicas across nodes.
K5
ConfigMap và SecretConfigMap and Secret
Cấu hình & ranh giớiConfig & boundaries
Ảnh gốcSource image: ConfigMap & Secret
Cấu hình nằm ngoài image; Secret mặc định chỉ là base64.
Config lives outside the image; a Secret is only base64 by default.
Đánh đổiTrade-offTách cấu hình khỏi image là đổi thêm một nơi phải quản phiên bản lấy khả năng dùng một image cho mọi môi trường.Separating config from the image trades one more thing to version for a single image across all environments.
Cùng một image chạy staging và production với cấu hình khác nhau (liên kết C9).
The same image running staging and production with different config (see C9).
Chuỗi kết nối database, API key.
Database connection strings, API keys.
File cấu hình như nginx.conf mount vào container.
Config files such as nginx.conf mounted into a container.
ĐượcYou get
Một image dùng cho mọi môi trường.
One image for every environment.
Đổi cấu hình không cần build.
Config changes need no build.
Secret có RBAC riêng và có thể bật mã hoá khi lưu trong etcd.
Secrets get their own RBAC and can be encrypted at rest in etcd.
MấtYou lose
Ai có quyền đọc object Secret là đọc được giá trị thật.
Anyone who can read the Secret object can read the real value.
Đổi ConfigMap không tự khởi động lại Pod đang dùng nó qua env.
Changing a ConfigMap doesn't restart Pods that consume it via env.
Thêm một thứ phải quản phiên bản song song với code.
One more thing to version alongside the code.
Dấu hiệu nhận biếtHow to spot itPhải build lại image chỉ để đổi một biến môi trường.An image gets rebuilt just to change one environment variable.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Object lưu cấu hình tách khỏi image. ConfigMap cho dữ liệu thường; Secret cho dữ liệu nhạy cảm, được xử lý riêng về quyền nhưng mặc định chỉ mã hoá base64, không phải mã hoá bảo mật.Objects that hold configuration outside the image. ConfigMaps hold ordinary data; Secrets hold sensitive data with separate access handling, but by default are only base64-encoded, not encrypted.
Hoạt động thế nàoHow it works.Pod tham chiếu qua env/envFrom (đọc một lần khi container khởi động), hoặc mount thành volume (kubelet cập nhật sau một khoảng trễ, trừ khi dùng subPath). Mỗi object tối đa 1 MiB.Pods reference them via env/envFrom, read once when the container starts, or mount them as volumes, which the kubelet refreshes after a delay (except with subPath). Each object is capped at 1 MiB.
Mẹo triển khaiImplementation tips
Đặt tên ConfigMap kèm hash nội dung (Kustomize configMapGenerator) để đổi cấu hình tự kích hoạt rollout.
Suffix ConfigMap names with a content hash (Kustomize configMapGenerator) so config changes trigger a rollout.
Không commit Secret dạng YAML lên git; dùng Sealed Secrets, SOPS hoặc External Secrets Operator.
Never commit Secret YAML to git; use Sealed Secrets, SOPS or the External Secrets Operator.
Cần nạp lại env thì kubectl rollout restart deployment/<tên>.
To reload env, run kubectl rollout restart deployment/<name>.
K6
Replicas và HorizontalPodAutoscalerReplicas and the HorizontalPodAutoscaler
WorkloadWorkload
Ảnh gốcSource image: Replicas & Autoscaling
CPU vượt mục tiêu thì thêm Pod, theo một công thức cụ thể.
CPU above target adds Pods, by a specific formula.
Đánh đổiTrade-offChọn autoscaling là đổi độ trễ phản ứng và số Pod khó đoán trước lấy việc không phải canh tải bằng tay.Choosing autoscaling trades reaction lag and an unpredictable Pod count for not watching load by hand.
desired = ceil( hiện tại × thực tế / mục tiêu )
2 × 140% / 70% = 4.0 → scale lên 4
2 × 76% / 70% = 2.17 → lệch 8,6% < dung sai 10% → giữ 2
% tính trên CPU requests, không phải CPU của nodedesired = ceil( current × actual / target )
2 × 140% / 70% = 4.0 → scale to 4
2 × 76% / 70% = 2.17 → 8.6% off < 10% tolerance → stays 2
% is of CPU requests, not of the node's CPU
Dùng choUse for
API có traffic lên xuống theo giờ trong ngày.
APIs whose traffic follows the time of day.
Worker mở rộng theo độ dài hàng đợi (dùng KEDA).
Workers scaling on queue length (with KEDA).
Sự kiện, khuyến mãi có tải tăng dự đoán được.
Events and sales with predictable load spikes.
ĐượcYou get
Tự thích ứng với tải.
Adapts to load automatically.
Tiết kiệm tài nguyên lúc thấp điểm.
Saves resources off-peak.
Kết hợp Cluster Autoscaler hoặc Karpenter để thêm node khi cần.
Pairs with Cluster Autoscaler or Karpenter to add nodes when needed.
MấtYou lose
Container không có CPU requests thì HPA theo CPU không tính được.
Without CPU requests on containers, CPU-based HPA can't compute anything.
Phản ứng chậm (chu kỳ metric cộng thời gian khởi động Pod), không cứu được spike vài giây.
Slow to react (metric interval plus Pod startup), so it can't absorb second-scale spikes.
Nút thắt nằm ở database thì thêm Pod chỉ làm database quá tải nhanh hơn.
If the bottleneck is the database, more Pods just overload it faster.
Dấu hiệu nhận biếtHow to spot itCó người phải tăng replicas bằng tay vào giờ cao điểm.Someone bumps replicas by hand at peak hours.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Replicas là số Pod mong muốn của Deployment. HPA tự điều chỉnh con số đó theo metric như CPU, memory hoặc metric tuỳ chỉnh.Replicas is a Deployment's desired Pod count. The HPA adjusts that number automatically from metrics such as CPU, memory or custom metrics.
Hoạt động thế nàoHow it works.Theo chu kỳ mặc định 15 giây, HPA lấy metric từ metrics-server, so với mục tiêu và đặt lại replicas. Chiều thu hẹp (scale down) có cửa sổ ổn định mặc định 5 phút để tránh dao động lên xuống.Every 15 seconds by default, the HPA reads metrics from metrics-server, compares them with the target and resets replicas. Scale-down has a default 5-minute stabilisation window to avoid flapping.
Mẹo triển khaiImplementation tips
Đặt minReplicas từ 2 trở lên để còn dự phòng khi một Pod chết.
Set minReplicas to 2 or more so one dead Pod doesn't mean an outage.
App khởi động chậm thì đặt mục tiêu thấp hơn để có khoảng đệm.
For slow-starting apps, set a lower target to leave headroom.
Deployment đã có HPA thì bỏ trường replicas khỏi manifest, để GitOps không kéo ngược số Pod.
When a Deployment has an HPA, remove replicas from the manifest so GitOps doesn't fight it.
K7
SchedulerScheduler
Điều phốiScheduling & nodes
Ảnh gốcSource image: The Scheduler
Lọc node không hợp, chấm điểm node còn lại, rồi gán Pod.
Filter out unfit nodes, score the rest, then bind the Pod.
Đánh đổiTrade-offKhai báo ràng buộc xếp lịch là đổi sự linh hoạt khi xếp lấy quyền kiểm soát Pod chạy ở đâu.Declaring scheduling constraints trades placement flexibility for control over where Pods run.
pod (requests 500m CPU, 1Gi) Pending
filter: node-a ✗ thiếu CPU node-b ✗ taint node-c ✓
score: node-c 87
bind: pod → node-cpod (requests 500m CPU, 1Gi) Pending
filter: node-a ✗ no CPU node-b ✗ taint node-c ✓
score: node-c 87
bind: pod → node-c
Dùng choUse for
Rải replica qua nhiều zone với topologySpreadConstraints.
Spreading replicas across zones with topologySpreadConstraints.
Đẩy workload GPU lên node GPU bằng taint và toleration.
Steering GPU workloads onto GPU nodes with taints and tolerations.
Tách workload ồn ào khỏi workload nhạy độ trễ.
Keeping noisy workloads away from latency-sensitive ones.
ĐượcYou get
Đặt workload tự động.
Places workloads automatically.
Ràng buộc khai báo được, đọc được trong manifest.
Constraints are declared and readable in manifests.
Event nói rõ vì sao không xếp được.
Events say exactly why a Pod couldn't be placed.
MấtYou lose
Xếp theo requests: đặt requests thấp là node bị nhồi quá tải thật.
Places by requests: low requests mean nodes get truly overloaded.
Chỉ quyết định lúc tạo Pod, không tự dời Pod khi tải lệch (cần descheduler).
Decides only at Pod creation; it won't rebalance later (you need the descheduler).
Ràng buộc chồng chéo có thể khiến Pod không bao giờ xếp được.
Overlapping constraints can make a Pod unschedulable forever.
Dấu hiệu nhận biếtHow to spot itPod nằm Pending mãi.A Pod sits in Pending forever.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Thành phần control plane chọn node cho những Pod chưa được gán.The control-plane component that picks a node for Pods that don't have one yet.
Hoạt động thế nàoHow it works.Bước filter xét requests (không phải mức dùng thực tế), nodeSelector và affinity, taint và toleration, cổng và volume. Bước score ưu tiên cân tải, affinity mềm, image có sẵn trên node. Không node nào qua filter thì Pod nằm Pending, kèm event giải thích lý do.Filtering checks requests (not actual usage), nodeSelector and affinity, taints and tolerations, ports and volumes. Scoring favours balanced load, soft affinity and images already present. If no node passes, the Pod stays Pending with an event explaining why.
Mẹo triển khaiImplementation tips
Pod Pending: đọc phần Events của kubectl describe pod trước tiên.
For a Pending Pod, read the Events section of kubectl describe pod first.
Đặt requests theo mức dùng thực tế ở phân vị 90, không đoán.
Set requests from observed p90 usage, not guesses.
Ưu tiên topologySpreadConstraints thay cho podAntiAffinity cứng.
Prefer topologySpreadConstraints over hard podAntiAffinity.
K8
NodeNode
Điều phốiScheduling & nodes
Ảnh gốcSource image: Node
Node im lặng thì bị đánh dấu NotReady; Pod chỉ bị dời sau thêm vài phút.
A silent node is marked NotReady; its Pods move only minutes later.
Đánh đổiTrade-offĐể timeout eviction dài là đổi thời gian hồi phục chậm lấy việc không dời Pod oan khi mạng chập chờn.Keeping a long eviction timeout trades slower recovery for not evicting Pods over a network blip.
kubelet@node-1 ── heartbeat (Lease) ──▶ control plane
im lặng quá 50s → node NotReady, gắn taint NoExecute
thêm 300s → Pod bị evict, controller tạo lại nơi khác
tổng ≈ 6 phút từ lúc node chết đến khi Pod chạy chỗ kháckubelet@node-1 ── heartbeat (Lease) ──▶ control plane
silent over 50s → node NotReady, NoExecute taint added
another 300s → Pods evicted, controllers recreate them
≈ 6 minutes from node death to Pods running elsewhere
Dùng choUse for
Chia node pool theo loại workload (CPU, memory, GPU).
Node pools split by workload type (CPU, memory, GPU).
Nâng cấp node an toàn bằng cordon rồi drain.
Safe node upgrades via cordon then drain.
Node spot giá rẻ cho workload chịu được gián đoạn.
Cheap spot nodes for interruption-tolerant workloads.
ĐượcYou get
Thêm bớt node tự do, workload tự dời.
Add and remove nodes freely; workloads move on their own.
Phát hiện node chết mà không cần người theo dõi.
Detects dead nodes without a human watching.
Drain kèm PodDisruptionBudget cho phép bảo trì không downtime.
Drain plus a PodDisruptionBudget allows zero-downtime maintenance.
MấtYou lose
Với cấu hình mặc định, phát hiện và dời Pod mất khoảng 6 phút.
With defaults, detecting and rescheduling takes about six minutes.
Pod của StatefulSet trên node mất liên lạc không tự tạo lại cho đến khi xác nhận node đã chết, để tránh hai bản cùng ghi dữ liệu.
StatefulSet Pods on an unreachable node aren't recreated until the node is confirmed dead, to avoid two writers.
Một node chết kéo theo mọi Pod trên đó; replica không được rải thì mất cả service.
A dead node takes all its Pods down; unspread replicas mean the whole service goes.
Dấu hiệu nhận biếtHow to spot itPod ở trạng thái Unknown hoặc Terminating trên một node NotReady.Pods show Unknown or Terminating on a NotReady node.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Máy ảo hoặc máy vật lý chạy Pod. Mỗi node có kubelet, container runtime và kube-proxy; kubelet báo nhịp tim về control plane.A virtual or physical machine that runs Pods. Each node runs the kubelet, a container runtime and kube-proxy; the kubelet sends heartbeats to the control plane.
Hoạt động thế nàoHow it works.kubelet cập nhật object Lease định kỳ. Node lifecycle controller đánh dấu NotReady khi quá node-monitor-grace-period. Pod mặc định có toleration 300 giây cho taint not-ready và unreachable, hết thời gian đó mới bị evict.The kubelet renews a Lease object periodically. The node lifecycle controller marks the node NotReady after node-monitor-grace-period. Pods get a default 300-second toleration for the not-ready and unreachable taints, and are evicted only after that.
Mẹo triển khaiImplementation tips
Luôn có PodDisruptionBudget trước khi drain node.
Always have a PodDisruptionBudget before draining a node.
Service cần hồi phục nhanh: giảm tolerationSeconds cho taint not-ready và unreachable ngay trên Pod.
For faster recovery, lower tolerationSeconds for the not-ready and unreachable taints on the Pod itself.
Theo dõi các condition MemoryPressure và DiskPressure; chúng báo trước việc kubelet sắp evict Pod.
Watch the MemoryPressure and DiskPressure conditions; they warn that the kubelet is about to evict Pods.
K9
Liveness, readiness và startup probeLiveness, readiness and startup probes
WorkloadWorkload
Ảnh gốcSource image: Liveness & Readiness
Readiness rút traffic; liveness khởi động lại container.
Readiness pulls traffic; liveness restarts the container.
Đánh đổiTrade-offLàm liveness chặt là đổi rủi ro khởi động lại oan lấy khả năng tự thoát khỏi trạng thái treo.A strict liveness probe trades the risk of needless restarts for automatic escape from hangs.
startup … … ✓ trước đó liveness chưa chạy
readiness ✓ ✓ ✓ ✗ ✗ ✓ rút khỏi Service lúc ✗, tự vào lại
liveness ✓ ✓ ✓ ✗ ✗ ✗ failureThreshold 3 → SIGTERM → chờ → SIGKILLstartup … … ✓ liveness doesn't run before this
readiness ✓ ✓ ✓ ✗ ✗ ✓ out of the Service on ✗, back in on ✓
liveness ✓ ✓ ✓ ✗ ✗ ✗ failureThreshold 3 → SIGTERM → wait → SIGKILL
Dùng choUse for
App cần làm nóng cache hoặc nạp model trước khi nhận traffic.
Apps that warm caches or load a model before taking traffic.
Phát hiện deadlock: tiến trình còn chạy nhưng không phản hồi.
Catching deadlocks: the process runs but no longer responds.
Rolling update chỉ chuyển traffic khi bản mới thật sự sẵn sàng.
Rolling updates that shift traffic only when the new version is truly ready.
ĐượcYou get
Traffic chỉ đến Pod sẵn sàng.
Traffic reaches only ready Pods.
Tự thoát khỏi trạng thái treo cứng.
Recovers from hard hangs automatically.
Rollout an toàn hơn vì Deployment chờ readiness.
Safer rollouts because the Deployment waits on readiness.
MấtYou lose
Liveness kiểm tra dependency (database) thì database chậm là cả đội Pod khởi động lại cùng lúc, tự gây sập.
A liveness probe that checks dependencies (the database) restarts every Pod when the database slows, causing an outage.
Probe quá nhạy gây vòng lặp khởi động lại (CrashLoopBackOff).
Over-sensitive probes cause restart loops (CrashLoopBackOff).
Readiness thất bại đồng loạt thì Service không còn endpoint nào.
If readiness fails everywhere at once, the Service has no endpoints left.
Dấu hiệu nhận biếtHow to spot itMỗi lần triển khai đều có vài giây trả về 502 hoặc 503.Every deploy has a few seconds of 502s or 503s.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Ba loại probe do kubelet chạy định kỳ. Readiness: Pod có nên nhận traffic không (thất bại thì bị rút khỏi endpoint, không khởi động lại). Liveness: container còn sống không (thất bại thì khởi động lại). Startup: chặn hai probe kia cho đến khi app khởi động xong.Three probes the kubelet runs periodically. Readiness: should this Pod get traffic (failure removes it from endpoints, no restart). Liveness: is the container alive (failure restarts it). Startup: holds off the other two until the app has booted.
Hoạt động thế nàoHow it works.Probe có thể là HTTP GET, TCP, exec hoặc gRPC, chạy mỗi periodSeconds, và chỉ đổi trạng thái sau failureThreshold lần thất bại liên tiếp. Liveness thất bại thì kubelet tắt container như bình thường: gửi SIGTERM, chờ terminationGracePeriodSeconds, rồi mới SIGKILL.A probe can be HTTP GET, TCP, exec or gRPC, runs every periodSeconds, and changes state only after failureThreshold consecutive failures. On liveness failure the kubelet stops the container normally: SIGTERM, wait terminationGracePeriodSeconds, then SIGKILL.
Mẹo triển khaiImplementation tips
Liveness chỉ kiểm tra chính tiến trình, qua endpoint rẻ, không gọi ra ngoài.
Liveness checks only the process itself, via a cheap endpoint with no outbound calls.
App phải xử lý SIGTERM: ngừng nhận request mới, hoàn tất request đang chạy rồi thoát.
Handle SIGTERM: stop accepting new requests, finish in-flight ones, then exit.
Dùng startup probe thay cho initialDelaySeconds lớn.
Use a startup probe instead of a large initialDelaySeconds.
K10
NamespaceNamespace
Cấu hình & ranh giớiConfig & boundaries
Ảnh gốcSource image: Namespace
Cùng tên dùng được hai lần, mỗi bên một quota; nhưng không tự cô lập mạng.
The same name twice with a quota each; but no network isolation by itself.
Đánh đổiTrade-offChọn namespace thay vì cluster riêng là đổi mức cô lập lấy chi phí và công vận hành thấp.Choosing namespaces over separate clusters trades isolation for lower cost and operational effort.
ns: dev ns: prod
deploy/api deploy/api
svc/api svc/api
quota 8 CPU quota 32 CPU
api.dev.svc ≠ api.prod.svc
mặc định: Pod ở dev vẫn gọi được Pod ở prodns: dev ns: prod
deploy/api deploy/api
svc/api svc/api
quota 8 CPU quota 32 CPU
api.dev.svc ≠ api.prod.svc
by default: Pods in dev can still reach Pods in prod
Dùng choUse for
Tách đội hoặc ứng dụng trong một cluster dùng chung.
Separating teams or apps in a shared cluster.
Giới hạn tài nguyên mỗi đội.
Capping resources per team.
Môi trường preview tạm thời cho từng PR.
Temporary preview environments per PR.
ĐượcYou get
Cùng một bộ manifest triển khai được ở nhiều nơi.
The same manifests deploy in many places.
Quota chặn một đội dùng hết tài nguyên cluster.
Quotas stop one team from consuming the whole cluster.
Xoá namespace là dọn sạch mọi thứ bên trong.
Deleting a namespace cleans up everything inside.
MấtYou lose
Không cô lập mạng mặc định; cần NetworkPolicy và CNI hỗ trợ nó.
No network isolation by default; you need NetworkPolicy and a CNI that enforces it.
Không tách node: workload các namespace vẫn chạy chung máy.
No node separation: workloads from different namespaces share machines.
Dev và prod chung cluster thì vẫn chung control plane và chung rủi ro nâng cấp.
Dev and prod in one cluster still share a control plane and upgrade risk.
Dấu hiệu nhận biếtHow to spot itNhiều đội dùng chung cluster và dẫm chân nhau.Several teams share a cluster and step on each other.
Cách hoạt động và mẹo triển khaiHow it works and implementation tips
Nó là gìWhat it is.Phân vùng logic trong cluster. Tên object chỉ cần duy nhất trong một namespace. Namespace là đơn vị để gắn quyền (RBAC), quota và policy.A logical partition in the cluster. Object names need only be unique within a namespace. Namespaces are the unit for permissions (RBAC), quotas and policies.
Hoạt động thế nàoHow it works.Phần lớn object thuộc về một namespace; ResourceQuota và LimitRange áp theo namespace; tên DNS của Service chứa tên namespace. Một số resource là cluster-scoped và không thuộc namespace nào: Node, PersistentVolume, ClusterRole.Most objects belong to a namespace; ResourceQuota and LimitRange apply per namespace; a Service's DNS name includes the namespace. Some resources are cluster-scoped and belong to none: Nodes, PersistentVolumes, ClusterRoles.
Mẹo triển khaiImplementation tips
Luôn ghi -n rõ ràng hoặc đặt namespace trong context; lệnh chạy nhầm namespace là sự cố thường gặp.
Always pass -n or set the namespace in your context; commands in the wrong namespace are a common incident.
Áp NetworkPolicy default-deny cho từng namespace rồi mở dần.
Apply a default-deny NetworkPolicy per namespace, then open up selectively.
Khi đủ lớn, đưa production sang cluster riêng.
Once big enough, move production to its own cluster.
Bảng đánh đổiTrade-off tables
CI: các trục đánh đổiCI: trade-off axes
Kỹ thuậtTechnique
Thời gian lấy lạiTime recovered
Công triển khaiSetup effort
Rủi ro sai kết quảCorrectness risk
Phút runnerRunner minutes
C1Chạy job song song và matrixParallel jobs and matrix
Kubernetes: ai thực thi, hỏng thì saoKubernetes: who enforces it, what breaks
Các khái niệm Kubernetes không phải phương án để chọn một trong nhiều, mà là các lớp cùng hoạt động. Vì vậy bảng này không so trục đánh đổi, mà trả lời câu hỏi hữu ích hơn khi vận hành: ai thực thi, hỏng thì trông thế nào, và lệnh đầu tiên cần chạy.Kubernetes concepts aren't alternatives to pick between; they are layers that work together. So this table doesn't compare trade-off axes. It answers the more useful operational question: who enforces it, what failure looks like, and the first command to run.
exceeded quota khi tạo objectexceeded quota on create
kubectl describe quota -n <ns>
Anti-patterns
Sai lầmMistake
Vì sao saiWhy it's wrong
Sửa thế nàoFix
Tối ưu CI khi chưa đo từng stepOptimising CI without per-step timings
Sửa nhầm chỗ, thường là chỗ không nằm trên critical pathYou fix the wrong thing, usually off the critical path
Đo trước, tối ưu critical pathMeasure, then optimise the critical path
Cache node_modules với key cố địnhCaching node_modules under a fixed key
Dùng dependency cũ, lỗi không tái hiện đượcStale dependencies, failures nobody can reproduce
Cache ~/.npm, key theo hash lockfileCache ~/.npm, key by lockfile hash
COPY . . ở đầu DockerfileCOPY . . at the top of the Dockerfile
Mọi commit phá cache của các layer phía sauEvery commit invalidates all later layers
Copy lockfile, cài dependency, rồi mới copy sourceCopy the lockfile, install, then copy source
Build lại image cho từng môi trườngRebuilding the image per environment
Thứ lên production chưa từng được testWhat ships was never tested
Build một lần, promote theo digestBuild once, promote by digest
Test dùng chung database có dữ liệuTests sharing a stateful database
Flaky theo thứ tự chạy, nhất là khi song songOrder-dependent flakiness, worse in parallel
Rollback theo test, mỗi worker một databasePer-test rollback, one database per worker
Affected-only mà không bao giờ chạy fullAffected-only with no full run ever
Lỗi tích hợp lọt vào mainIntegration bugs reach main
Full suite trên main hoặc hằng đêmFull suite on main or nightly
Triển khai image :latestDeploying :latest
Không biết đang chạy gì, rollback không xác địnhNobody knows what's running; rollback is undefined
Tag bằng commit SHA, triển khai theo digestTag with the commit SHA, deploy by digest
Liveness probe gọi databaseLiveness probe that calls the database
Database chậm là mọi Pod khởi động lại cùng lúcA slow database restarts every Pod at once
Liveness chỉ kiểm tra tiến trìnhLiveness checks only the process
Không đặt CPU/memory requestsNo CPU/memory requests
Scheduler nhồi node, HPA theo CPU không hoạt độngThe scheduler overpacks nodes; CPU-based HPA can't work
Đặt requests theo mức dùng p90Set requests from p90 usage
Coi Secret là đã được mã hoáTreating Secrets as encrypted
Lộ qua bất kỳ ai có quyền đọc Secret hoặc bản backup etcdLeaked to anyone who can read Secrets or etcd backups
Encryption at rest, RBAC chặt, kho bí mật ngoàiEncryption at rest, tight RBAC, external secret store
Coi namespace là ranh giới bảo mậtTreating a namespace as a security boundary
Pod namespace khác gọi thẳng được vàoPods in other namespaces can call in directly
NetworkPolicy default-deny và RBACDefault-deny NetworkPolicy and RBAC
Dùng ingress-nginx cho dự án mớiStarting a new project on ingress-nginx
Đã ngừng bảo trì, không còn bản vá bảo mậtRetired, no more security fixes
Gateway API hoặc controller còn được bảo trìGateway API or a maintained controller
Chỗ ảnh gốc sai hoặc thiếuWhere the source images are wrong or incomplete
Đã đối chiếu với tài liệu chính thức. Các mục không có trong bảng dưới đây là chính xác.Checked against the official documentation. Anything not listed below is accurate.
Khái niệmConcept
Đính chính so với ảnh gốcCorrection to the source image
C4Docker layer caching và image gọnDocker layer caching and lean images
Ảnh gộp hai vấn đề khác nhau dưới tên “Fat Docker Image”. Image béo làm chậm pull và push; không tái dùng layer làm chậm build. Cách chữa cũng khác: multi-stage và base image nhỏ cho vấn đề thứ nhất, sắp thứ tự Dockerfile và xuất cache cho vấn đề thứ hai.The image merges two different problems under “Fat Docker Image”. A fat image slows pulls and pushes; missing layer reuse slows builds. The fixes differ too: multi-stage builds and slim bases for the first, Dockerfile ordering and exported cache for the second.
C6Chia shard test ra nhiều runnerTest sharding across runners
“40 phút thành 8” là trường hợp lý tưởng: 5 shard chia đều tuyệt đối và không tốn setup. Thực tế mỗi shard trả lại thời gian khởi động, và shard chậm nhất quyết định tổng.“40 minutes into 8” is the ideal case: five perfectly even shards with zero setup. In practice every shard pays startup again, and the slowest shard decides the total.
C7Tạo schema một lần, cô lập từng testCreate the schema once, isolate each test
Ảnh khuyên “Shared Test DB”. Dùng chung một database có trạng thái giữa các test là nguồn flaky kinh điển, nhất là khi chạy song song: test A để lại dữ liệu, test B thất bại tuỳ thứ tự. Cái cần chia sẻ là schema, không phải dữ liệu.The image recommends “Shared Test DB”. Sharing one stateful database across tests is a classic source of flakiness, especially in parallel: test A leaves data behind and test B fails depending on order. Share the schema, not the data.
Ảnh đúng nhưng thiếu hai ý quan trọng: container trong Pod còn dùng chung volume, và Pod không tự hồi sinh khi chết.The image is right but leaves out two key points: containers in a Pod can also share volumes, and a Pod does not come back by itself when it dies.
“Pod mới lên trước khi Pod cũ đi” chỉ đúng khi maxUnavailable: 0. Mặc định là maxSurge: 25% và maxUnavailable: 25%, tức Kubernetes được phép tắt tới 25% Pod cũ trước khi Pod mới sẵn sàng.“New pods up before old ones go” is only true with maxUnavailable: 0. The defaults are maxSurge: 25% and maxUnavailable: 25%, so Kubernetes may terminate up to a quarter of old Pods before new ones are ready.
“IP không bao giờ đổi” chỉ đúng khi Service chưa bị xoá. Ảnh cũng không nói rằng Pod chưa Ready bị loại khỏi danh sách endpoint.“The IP never changes” holds only while the Service exists. The image also doesn't mention that non-Ready Pods are dropped from the endpoint list.
K4Ingress và Gateway APIIngress and the Gateway API
Ảnh không nói rằng Ingress cần một controller để hoạt động. Quan trọng hơn: ingress-nginx, controller phổ biến nhất, đã ngừng bảo trì từ tháng 3/2026, không còn bản vá bảo mật. Ingress API vẫn còn nhưng đã đóng băng; Kubernetes khuyến nghị Gateway API.The image doesn't say Ingress needs a controller to work. More importantly, ingress-nginx, the most widely used controller, was retired in March 2026 and no longer receives security fixes. The Ingress API remains but is frozen; Kubernetes recommends the Gateway API.
Ảnh đúng khi nói Secret chỉ là base64. Thiếu phần làm gì với điều đó: bật encryption at rest cho etcd, giới hạn RBAC, hoặc dùng kho bí mật bên ngoài. Cũng thiếu điểm hay gây lỗi nhất: env không tự cập nhật khi ConfigMap đổi.The image is right that Secrets are only base64. It leaves out what to do about it: enable encryption at rest for etcd, restrict RBAC, or use an external secret store. It also misses the most common gotcha: env vars don't update when a ConfigMap changes.
K6Replicas và HorizontalPodAutoscalerReplicas and the HorizontalPodAutoscaler
Con số trong ảnh không khớp với thuật toán của HPA. Với 2 replica, CPU 76% và mục tiêu 70%, tỷ lệ là 1,086; mức lệch này nằm trong dung sai mặc định 10% nên HPA giữ nguyên 2, không thể lên 5. Nếu vượt dung sai, kết quả cũng chỉ là ceil(2,17) = 3.The numbers in the image don't match the HPA algorithm. With 2 replicas, 76% CPU and a 70% target, the ratio is 1.086, inside the default 10% tolerance, so the HPA stays at 2; it cannot jump to 5. Even outside the tolerance the result would be ceil(2.17) = 3.
Ảnh ghi “silent 40s”. 40 giây là mặc định cũ của node-monitor-grace-period; các bản Kubernetes gần đây dùng 50 giây. Quan trọng hơn, node NotReady không có nghĩa là Pod được dời ngay: mặc định còn chờ thêm 300 giây.The image says “silent 40s”. Forty seconds was the old default for node-monitor-grace-period; recent Kubernetes releases use 50 seconds. More importantly, a NotReady node doesn't mean Pods move immediately: by default they wait another 300 seconds.
K9Liveness, readiness và startup probeLiveness, readiness and startup probes
Ảnh vẽ liveness thất bại dẫn thẳng tới SIGKILL. Thực tế kubelet gửi SIGTERM trước, chờ grace period (mặc định 30 giây), chỉ SIGKILL nếu container chưa thoát. Ảnh cũng thiếu startup probe, thứ cần cho app khởi động chậm.The image shows a liveness failure going straight to SIGKILL. In fact the kubelet sends SIGTERM first, waits the grace period (30 seconds by default), and only sends SIGKILL if the container hasn't exited. The image also omits the startup probe, which slow-starting apps need.
Ảnh đúng về tên trùng và quota. Thiếu điểm dễ hiểu nhầm nhất: namespace không phải ranh giới bảo mật. Muốn cô lập phải có RBAC và NetworkPolicy.The image is right about duplicate names and quotas. It misses the most misunderstood point: a namespace is not a security boundary. Isolation needs RBAC and NetworkPolicy.
Ba câu cần nhớThree things to remember
Đo trước khi tối ưu: pipeline nhanh bằng critical path của nó, không bằng tổng các bước.
Measure before optimising: a pipeline is as fast as its critical path, not the sum of its steps.
Build một lần, test đúng thứ đó, promote đúng digest đó. Cấu hình là thứ duy nhất thay đổi giữa các môi trường.
Build once, test that artifact, promote that digest. Configuration is the only thing that changes between environments.
Kubernetes cần mẫn kéo thực tế về đúng những gì bạn khai báo. Khai báo sai requests, probe hay selector thì nó cũng cần mẫn làm sai.
Kubernetes diligently pulls reality toward whatever you declare. Declare the wrong requests, probes or selectors and it will diligently do the wrong thing.