FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:22-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
25
Containers, CI/CD & IaC
draftedDocker cho Node (multi-stage, non-root, PID 1 & signal), Kubernetes basics, thiết kế pipeline, rolling/blue-green/canary, feature flags, migration trong pipeline, Terraform/CDK, GitOps.
0/45 rated · mastery 0%
Bài học
2 bài · ~25 phút đọc. Học theo thứ tự, rồi làm câu hỏi.- 01~13 phút· 4 câuverify
Image, layer và build cache cho Node
Image là gì, layer và cache key, thứ tự Dockerfile, .dockerignore, npm ci vs npm install, BuildKit cache mount và cache trong CI, 12-factor build/release/run.
- 02~12 phút· 4 câuverify
Multi-stage build, base image và non-root
Multi-stage cho TypeScript/Next.js standalone, chọn alpine/slim/distroless, non-root và securityContext, secret lúc build (ARG/ENV vs secret mount), review một Dockerfile production.
Tóm tắt & cheat sheet của track
TL;DR
- Interviewer muốn biết bạn ship code an toàn và lặp lại được: image nhỏ và an toàn, pipeline nhanh và đáng tin, deploy có đường lui, hạ tầng mô tả bằng code.
- Với Node, 80% câu hỏi Docker xoay quanh: multi-stage build, layer caching (
package*.jsontrước,npm ci), non-root user, PID 1 & SIGTERM (graceful shutdown),.dockerignore. - Kubernetes basics cần nói chắc: Pod/Deployment/Service/Ingress, readiness vs liveness vs startup probe, requests/limits và
OOMKilled, HPA, ConfigMap/Secret. - Deploy strategy là trade-off giữa tốc độ rollback, chi phí, và blast radius; DB migration là thứ quyết định bạn có rollback được hay không (expand/contract).
- IaC: Terraform state là trái tim (remote backend + locking + drift detection); GitOps = Git là source of truth, cluster tự pull trạng thái mong muốn.
Khái niệm cốt lõi
| Khái niệm | Một câu | Ví dụ ngắn |
|---|---|---|
| Multi-stage build | Build ở stage có devDependencies, copy chỉ artifact sang stage runtime | Image từ ~1 GB xuống ~150 MB |
| Layer caching | Mỗi instruction là một layer; layer đổi → mọi layer sau build lại | COPY package*.json + npm ci trước COPY . . |
| PID 1 | Process đầu tiên trong container nhận signal, và kernel không áp default handler cho nó | CMD npm start → SIGTERM không tới node → bị SIGKILL sau grace period |
| Readiness probe | "Có nhận traffic được không?" — fail thì bị rút khỏi Service endpoints | Đang warm cache → chưa ready |
| Liveness probe | "Có cần restart không?" — fail thì kubelet restart container | Event loop treo hẳn |
| requests / limits | requests dùng để schedule; limits là trần (memory vượt → OOMKilled, CPU vượt → throttle) | memory: 512Mi limit, Node heap vượt → exit 137 |
| Rolling update | Thay dần pod cũ bằng pod mới | maxSurge: 25%, maxUnavailable: 0 |
| Blue/green | Hai môi trường đầy đủ, đổi traffic một lần | Rollback = chuyển router về blue |
| Canary | Đẩy % traffic nhỏ sang bản mới, đo, rồi tăng dần | 5% → 25% → 100% nếu error rate ổn |
| Feature flag | Tách deploy (code lên prod) khỏi release (bật cho user) | Bật checkout mới cho 10% tenant |
| Terraform state | File ánh xạ resource trong code ↔ resource thật | Mất state = Terraform muốn tạo lại mọi thứ |
| GitOps | Agent trong cluster (Argo CD/Flux) reconcile theo Git | Merge PR = deploy; revert commit = rollback |
Cơ chế hoạt động
Pipeline chuẩn cho một Node/Next.js service:
Graceful shutdown của một pod:
Trade-offs
| Strategy | Rollback | Chi phí hạ tầng | Blast radius | Yêu cầu |
|---|---|---|---|---|
| Recreate | Chậm (deploy lại) | Thấp | 100% + downtime | Chấp nhận downtime |
| Rolling | Vừa (rollout undo) | Thấp (+surge) | Tăng dần, khó dừng giữa chừng có kiểm soát | Code cũ & mới chạy song song, schema tương thích |
| Blue/green | Tức thì (đổi router) | x2 trong lúc deploy | 100% khi switch | Stateless hoặc DB tương thích cả hai |
| Canary | Nhanh (dồn traffic về bản cũ) | Thấp | Nhỏ, có kiểm soát | Metric tốt theo version, tự động phân tích |
| Feature flag | Tức thì (tắt flag) | Không | Theo segment | Kỷ luật dọn flag, test cả hai nhánh |
| IaC | Mạnh | Yếu |
|---|---|---|
| Terraform/OpenTofu | Multi-cloud + SaaS provider, plan rõ ràng, ecosystem lớn | Tự quản state, HCL hạn chế logic |
| AWS CDK | TypeScript thật, construct tái sử dụng, sinh CloudFormation | Chỉ AWS (chủ yếu), CloudFormation chậm/giới hạn |
| CloudFormation | Native, state do AWS quản, rollback stack | YAML dài, ít trừu tượng |
| Pulumi | Ngôn ngữ thật, multi-cloud | Ecosystem nhỏ hơn Terraform |
Pitfalls
FROM node:latest→ build không tái lập; dùng tag cụ thể (hoặc digest) và cập nhật có chủ đích.COPY . .trướcnpm install→ mỗi lần sửa code đều cài lại dependency; thiếu.dockerignore→ copy cảnode_modules,.env,.gitvào image.- Chạy container bằng root; secret trong
ENV/ARG(lộ quadocker history) — dùng BuildKit secret mount. CMD npm start/ shell form → signal không tới Node; không xử lý SIGTERM → cắt ngang request mỗi lần deploy.- Liveness probe gọi DB → DB chậm làm restart toàn bộ pod (cascading failure).
- Không đặt memory limit hoặc đặt heap Node lớn hơn limit → OOMKilled.
- Build image riêng cho từng môi trường thay vì build once, promote cùng một digest.
- Migration phá vỡ tương thích (rename/drop cột) chạy cùng release code mới → không rollback được.
- Terraform state local / trong git; không có locking → hai người
applycùng lúc hỏng state. - Feature flag không bao giờ được dọn → nợ kỹ thuật, tổ hợp nhánh không ai test.
Cheat sheet
- Dockerfile Node: multi-stage ·
npm ci·--omit=dev·USER node·CMD ["node","server.js"](exec form) ·--init/tini nếu cần reap zombie ·.dockerignore. - Next.js:
output: "standalone"để image runtime chỉ chứa server tối thiểu. - Probes: startup (khởi động chậm) → readiness (dependency cần thiết cho traffic) → liveness (chỉ process tự thân, rẻ).
- Exit code 137 = SIGKILL (thường OOMKilled); 143 = SIGTERM.
- HPA scale theo % requests → không có requests thì HPA CPU không hoạt động đúng.
- K8s Secret chỉ là base64 → bật encryption at rest, hoặc External Secrets / CSI driver.
- Pipeline: fail fast (lint/typecheck trước), cache dependency, song song hoá, artifact bất biến theo SHA.
- Rollback code phải luôn khả thi → migration expand trước, contract sau.
- Terraform: remote state + lock,
plantrong PR,applytừ CI, drift detection định kỳ, module có version. - 12-factor: config trong env, stateless process, logs ra stdout, dev/prod parity, disposability (start nhanh, shutdown êm).
References
- Docker — Multi-stage builds ↗
- Docker — Build cache ↗
- Docker — Dockerfile reference (CMD / ENTRYPOINT exec form) ↗
- Kubernetes — Configure liveness, readiness and startup probes ↗
- Kubernetes — Resource management for pods and containers ↗
- Kubernetes — Pod lifecycle (termination) ↗
- Kubernetes — Horizontal Pod Autoscaling ↗
- Kubernetes — Deployments (rolling update) ↗
- Terraform — State and locking ↗
- Terraform — S3 backend ↗
- The Twelve-Factor App ↗
- OpenGitOps principles ↗
- GitHub Actions — OpenID Connect in AWS ↗
Interview Questions
Showing 45 / 45
FROM node:latest
WORKDIR /app
COPY . .
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc && npm install
ENV DATABASE_URL=postgres://admin:secret@db:5432/app
EXPOSE 3000
CMD npm run start
const server = app.listen(port);
const shutdown = () => {
server.close(async () => { await db.end(); process.exit(0); });
setTimeout(() => process.exit(1), 25_000).unref(); // safety net
};
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
containers:
- name: api
image: acme/api:1.4.2
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=1024"
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "500m", memory: "512Mi" }
on:
pull_request_target:
types: [opened, synchronize]
jobs:
deploy-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: some-org/deploy-action@main
- run: npm ci && npm run build && ./deploy.sh
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
lifecycle:
preStop:
exec: { command: ["sh", "-c", "sleep 10"] }
terminationGracePeriodSeconds: 45
livenessProbe:
httpGet: { path: /health, port: 3000 } # /health runs SELECT 1 and pings Redis
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 2
readinessProbe:
httpGet: { path: /health, port: 3000 }
moved {
from = aws_db_instance.main
to = module.database.aws_db_instance.this
}
resource "aws_db_instance" "this" {
deletion_protection = true
lifecycle { prevent_destroy = true }
}