Bạn sẽ học gì
Sau bài này, bạn sẽ tự tay đưa một app từ số 0 (một thư mục trống) đến chạy được bên trong một cluster Kubernetes chạy trên máy của bạn. Cụ thể:
- Viết một app Todo API nhỏ bằng Node.js + Express.
- Đóng gói (container hoá) nó thành một Docker image.
- Tạo một cluster Kubernetes local bằng
kind. - Deploy app bằng file YAML "thật" (không dùng lệnh tắt) để hiểu Kubernetes vận hành thế nào.
- Truy cập app đang chạy trong cluster từ máy của bạn.
Đây là Part 1 trong series "DevOps 101 — Học K8s, Helm, ArgoCD từ số 0". Cả series dùng chung một app tên todo-ops, các part sau sẽ xây tiếp lên nền này (thêm database, config, ingress, Helm, GitOps với ArgoCD).
Điều kiện tiên quyết
- Docker (hoặc Docker Desktop / OrbStack) — đang chạy.
- kind — công cụ tạo cluster Kubernetes trong Docker.
- kubectl — CLI để nói chuyện với Kubernetes.
- Node.js 20+ và npm — để chạy thử app local.
- git — để quản lý mã nguồn.
Cài đặt
Chọn theo hệ điều hành của bạn.
macOS (dùng Homebrew — nếu chưa có, cài trước):
# Cài Homebrew (bỏ qua nếu đã có)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Docker Desktop (hoặc OrbStack: brew install --cask orbstack)
brew install --cask docker
# Các CLI còn lại
brew install kind kubectl node git
Sau khi cài xong, mở Docker Desktop (hoặc OrbStack) và chờ nó báo Running trước khi chạy tiếp.
Linux (Ubuntu/Debian):
# Docker Engine
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker "$USER" # cho phép chạy docker không cần sudo (đăng xuất/đăng nhập lại để có hiệu lực)
# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -m 0755 kubectl /usr/local/bin/kubectl && rm kubectl
# kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
sudo install -m 0755 kind /usr/local/bin/kind && rm kind
# Node.js 20 (qua NodeSource) + git
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs git
💡 Trên Apple Silicon (M1/M2/M3) đổi
amd64→arm64trong hai lệnhkubectlvàkind. Trên macOS đã dùngbrewnên không phải lo.
Kiểm tra nhanh (mỗi lệnh phải in ra một dòng version, không báo command not found):
docker --version
kind --version
kubectl version --client
node --version
git --version
Thời gian
Khoảng ~40 phút nếu bạn gõ theo từng bước.
Vị trí trong series
Cốt lõi (làm theo thứ tự)
[Part 1] Container & Kubernetes local ← bạn đang ở đây
Part 2 Config & State (Postgres, ConfigMap, Secret)
Part 3 Networking (Service & Ingress)
Part 4 Helm (đóng gói chart)
Part 5 ArgoCD / GitOps
Part 6 CI với GitHub Actions
Part 7 Observability & Bảo mật
Part 8 Monitoring (Prometheus, Grafana, Alertmanager)
Track nâng cao (làm khi todo-ops còn chạy — trước khi dọn dẹp)
Part 9 Autoscaling (HPA)
Part 10 StatefulSet cho DB
Part 11 Nhiều môi trường (dev/stage/prod)
Part 12 Secrets production (Sealed / External Secrets)
Part 13 Logging (Loki)
Part 14 Tracing (Tempo)
Dọn dẹp (làm cuối cùng)
Part 15 Dọn dẹp — xoá cluster + mọi tài nguyên, trả máy về sạch
⚠️ Part 15 xoá cluster todo-ops. Nếu định làm track nâng cao (Part 9-14),
làm chúng trước Part 15. Cleanup luôn để cuối cùng.
💡 Hiểu thêm — Vì sao phải "container hoá"?
Cái đau kinh điển của lập trình: "máy tôi chạy được mà!". App của bạn cần Node.js phiên bản đúng, đúng thư viện, đúng biến môi trường... Khi mang sang máy khác hay server, thiếu một thứ là hỏng.
Container giải quyết bằng cách đóng gói app + toàn bộ thứ nó cần để chạy vào một "hộp" chuẩn hoá. Hộp đó chạy giống hệt nhau ở mọi nơi có Docker. Docker image là bản đóng gói tĩnh (như file cài đặt); container là một image đang chạy (như tiến trình).
Bước 1 — Tạo project
Tạo thư mục dự án và khởi tạo git.
mkdir todo-ops && cd todo-ops && git init
-
mkdir todo-opstạo thư mục dự án. -
cd todo-opsbước vào trong đó — từ giờ mọi lệnh chạy trong thư mục này. -
git initbiến thư mục thành một kho git để theo dõi thay đổi (các part sau, đặc biệt Part 6 GitOps, sẽ cần git).
Output mong đợi:
Initialized empty Git repository in /.../todo-ops/.git/
✅ Checkpoint 1
Chạy pwd — phải thấy đường dẫn kết thúc bằng /todo-ops. Chạy ls -a — phải thấy thư mục .git.
Nếu sai:
- Lỡ
git initở nhầm thư mục cha → xoá thư mục.gitbị tạo nhầm bằngrm -rf .git, rồicdvào đúngtodo-opsvà làm lại.
Bước 2 — Viết app Node.js
App của chúng ta là Todo API: một service HTTP đơn giản cho phép xem và thêm todo. Part 1 chưa có database — dữ liệu lưu tạm trong một biến mảng trong bộ nhớ (in-memory). Nghĩa là restart app thì todo mất; đó là chủ ý, Part 2 sẽ thay bằng Postgres.
Tạo hai file: package.json (khai báo dự án & thư viện) và server.js (mã app).
2a. package.json
Tạo file package.json với nội dung:
{
"name": "todo-api",
"version": "1.0.0",
"description": "Todo API - DevOps 101",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.19.2"
}
}
Giải thích các field:
-
name,version,description— thông tin mô tả dự án. -
main— file khởi động chính. -
scripts.start— định nghĩa lệnhnpm start= chạynode server.js. Đây là cách chuẩn để "khởi động app" mà không cần nhớ tên file. -
dependencies.express— khai báo app cần thư viện Express (framework web cho Node.js).^4.19.2nghĩa là "phiên bản 4.19.2 trở lên nhưng vẫn trong dòng 4.x".
2b. server.js
Tạo file server.js với nội dung:
const express = require("express");
const app = express();
const PORT = 3000;
// Cho phép Express đọc JSON trong body của request (dùng cho POST).
app.use(express.json());
// "Database" tạm thời: một mảng trong bộ nhớ.
// Restart app là mất — Part 2 sẽ thay bằng Postgres.
let todos = [
{ id: 1, title: "Đọc DevOps 101 Part 1", done: false },
];
let nextId = 2;
// GET /todos → trả về toàn bộ danh sách todo dưới dạng JSON.
app.get("/todos", (req, res) => {
res.json(todos);
});
// POST /todos → thêm một todo mới. Body cần có { "title": "..." }.
app.post("/todos", (req, res) => {
const title = req.body && req.body.title;
if (!title) {
return res.status(400).json({ error: "title is required" });
}
const todo = { id: nextId++, title, done: false };
todos.push(todo);
res.status(201).json(todo);
});
// GET /healthz → probe kiểm tra sức khoẻ. Luôn trả 200 nếu app còn sống.
app.get("/healthz", (req, res) => {
res.status(200).send("OK");
});
// Lắng nghe trên port 3000, mọi địa chỉ (0.0.0.0) — quan trọng khi chạy trong container.
app.listen(PORT, () => {
console.log(`Todo API listening on port ${PORT}`);
});
Giải thích những chỗ quan trọng:
-
express.json()là middleware — nó chạy trước mỗi request và biến JSON trong body thànhreq.body. Không có dòng này thìPOSTkhông đọc đượctitle. -
todos/nextIdlà state in-memory. Đơn giản để học; dữ liệu không bền. - Ba route ứng với ba endpoint trong spec:
GET /todos,POST /todos,GET /healthz. -
/healthztrả200 OK— Part 4 sẽ dùng endpoint này làm health probe cho Kubernetes. Đặt sẵn từ giờ để nhất quán. -
app.listen(PORT)— mở cổng và chờ request.console.loggiúp ta biết app đã lên.
✅ Checkpoint 2
Chạy ls — phải thấy đủ package.json và server.js. Chạy node -c server.js (kiểm tra cú pháp) — không in gì là đúng.
Nếu sai:
node -c server.jsbáoSyntaxError→ có ký tự gõ thiếu (dấu ngoặc, dấu phẩy). Đọc số dòng trong lỗi và sửa.
Bước 3 — Chạy thử local
Trước khi container hoá, hãy chắc app chạy được trên máy trần đã. Đây là thói quen tốt: cô lập lỗi — nếu app hỏng ngay ở đây thì không phải lỗi Docker/K8s.
Cài thư viện rồi khởi động:
npm install
node server.js
-
npm installđọcpackage.json, tải Express về thư mụcnode_modules/. -
node server.jskhởi động app. Terminal sẽ "treo" lại (app đang chạy) và in dòng log.
Output mong đợi (ở terminal chạy app):
Todo API listening on port 3000
Mở một terminal thứ hai (cửa sổ/tab mới, vẫn ở thư mục todo-ops) và thử gọi API:
curl localhost:3000/todos
Output mong đợi:
[{"id":1,"title":"Đọc DevOps 101 Part 1","done":false}]
Thêm một todo mới:
curl -X POST -H 'Content-Type: application/json' -d '{"title":"học K8s"}' localhost:3000/todos
-
-X POST— dùng phương thức POST. -
-H 'Content-Type: application/json'— báo cho server biết body là JSON. -
-d '{...}'— dữ liệu gửi lên.
Output mong đợi:
{"id":2,"title":"học K8s","done":false}
Gọi lại curl localhost:3000/todos sẽ thấy cả hai todo. Xong thì quay lại terminal thứ nhất, nhấn Ctrl+C để dừng app.
✅ Checkpoint 3
GET /todos trả JSON mảng, POST trả về todo vừa tạo với id tăng dần.
Nếu sai:
curlbáoConnection refused→ app chưa chạy hoặc đã bịCtrl+C. Kiểm tra terminal thứ nhất có dònglistening on port 3000không.POSTtrả{"error":"title is required"}→ thiếu header-H 'Content-Type: application/json', nên Express không parse được body.Cannot find module 'express'→ quên chạynpm install.
💡 Hiểu thêm — Dockerfile là gì?
Dockerfilelà công thức để Docker xây image. Mỗi dòng là một bước, và Docker chạy chúng theo thứ tự trên xuống. Docker cache lại từng bước, nên ta cố tình sắp xếp: thứ ít đổi (cài thư viện) đặt trên, thứ hay đổi (mã nguồn) đặt dưới — để build lần sau nhanh hơn.
Bước 4 — Đóng gói thành container
Tạo file Dockerfile (không có đuôi mở rộng) trong thư mục todo-ops:
# Bắt đầu từ một image có sẵn Node.js 20 trên nền Alpine Linux (nhẹ).
FROM node:20-alpine
# Đặt thư mục làm việc bên trong container. Các lệnh sau chạy trong đây.
WORKDIR /app
# Chỉ copy package.json (và package-lock nếu có) trước.
COPY package*.json ./
# Cài thư viện. Nhờ copy trước, bước này được cache lại khi mã nguồn đổi.
RUN npm install
# Bây giờ mới copy toàn bộ mã nguồn còn lại vào /app.
COPY . .
# Ghi chú rằng container mở port 3000 (tài liệu hoá, không tự mở cổng).
EXPOSE 3000
# Lệnh chạy khi container khởi động.
CMD ["node", "server.js"]
Giải thích từng dòng:
-
FROM node:20-alpine— nền tảng. Không cần tự cài Node; image này đã có sẵn. -
WORKDIR /app— tạo & vào thư mục/apptrong container. -
COPY package*.json ./— copy trước file khai báo thư viện. Đây là mẹo tận dụng cache (xem 💡 ở trên). -
RUN npm install— cài Express bên trong image. -
COPY . .— copy mọi thứ còn lại (gồmserver.js) vào/app. -
EXPOSE 3000— khai báo cổng (mang tính tài liệu). -
CMD ["node", "server.js"]— lệnh chạy app khi container start.
💡 Nên có
.dockerignore: để tránh copynode_modules/local (có thể build cho HĐH khác) và.gitvào image, tạo file.dockerignore:node_modules .git
Bây giờ build image:
docker build -t todo-api:v1 .
-
-t todo-api:v1— đặt tên:tag cho image. Ta dùng đúngtodo-api:v1như spec của series. -
.(dấu chấm cuối) — "build context" là thư mục hiện tại; Docker gửi các file ở đây cho quá trình build.
Output mong đợi (rút gọn, kết thúc bằng dòng thành công):
=> [1/5] FROM docker.io/library/node:20-alpine ...
=> [4/5] RUN npm install ...
=> [5/5] COPY . . ...
=> => naming to docker.io/library/todo-api:v1
✅ Checkpoint 4
Chạy docker images | grep todo-api — phải thấy một dòng có todo-api và tag v1.
Nếu sai:
Cannot connect to the Docker daemon→ Docker chưa chạy. Mở Docker Desktop / OrbStack rồi build lại.- Build dừng ở
RUN npm install→ thường do mạng, hoặcpackage.jsonsai cú pháp JSON. Chạynode -ckhông giúp cho JSON; kiểm tra dấu phẩy/ngoặc trongpackage.json.- Muốn chắc image chạy được, thử luôn:
docker run --rm -p 3000:3000 todo-api:v1, rồi ở terminal kháccurl localhost:3000/todos.Ctrl+Cđể dừng.
💡 Hiểu thêm — Kubernetes, cluster, node là gì?
Kubernetes (K8s) là hệ điều phối container: bạn khai báo "tôi muốn chạy app này, 1 bản, luôn sống" và K8s lo phần còn lại (khởi động lại khi chết, phân bổ tài nguyên...).
- Cluster = cả dàn máy do K8s quản lý.
- Node = một máy (thật hoặc ảo) trong cluster, nơi container thực sự chạy.
kind("Kubernetes IN Docker") giả lập cả một cluster bên trong Docker trên máy bạn — hoàn hảo để học mà không tốn tiền cloud.
Bước 5 — Tạo cluster Kubernetes local bằng kind
Tạo file kind-config.yaml:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
Giải thích:
-
kind: Cluster/apiVersion— đây là file cấu hình cho công cụ kind (không phải manifest K8s thường). -
nodes— danh sách máy trong cluster. Ta chỉ cần một node. -
role: control-plane— node này vừa là "bộ não" (điều khiển cluster) vừa chạy app luôn (đủ cho môi trường học). -
extraPortMappings— nối port 80 của máy bạn vào port 80 của node. Part 1 chưa dùng cổng này; ta đặt sẵn để Part 3 (Ingress) có thể mở app ra ngoài quahttp://localhost. Đặt từ giờ để không phải tạo lại cluster sau.
Tạo cluster:
kind create cluster --name todo-ops --config kind-config.yaml
Output mong đợi (rút gọn):
Creating cluster "todo-ops" ...
✓ Ensuring node image ...
✓ Preparing nodes ...
✓ Starting control-plane ...
Set kubectl context to "kind-todo-ops"
Kiểm tra kubectl đã kết nối được cluster:
kubectl get nodes
Output mong đợi:
NAME STATUS ROLES AGE VERSION
todo-ops-control-plane Ready control-plane 1m v1.3x.x
Tạo namespace todo để nhóm tài nguyên của app lại:
kubectl create namespace todo
Output mong đợi: namespace/todo created
💡 Hiểu thêm — Namespace: namespace là "ngăn kéo" logic trong cluster để tách biệt tài nguyên (theo team, theo app, theo môi trường). Ta để mọi thứ của app trong namespace
todocho gọn, thay vì trộn vàodefault.
✅ Checkpoint 5
kubectl get nodes → node STATUS = Ready. kubectl get ns → có dòng todo.
Nếu sai:
- Node kẹt ở
NotReadyvài phút → chờ thêm; nếu quá lâu, Docker có thể thiếu RAM (cấp ≥4GB cho Docker Desktop).port is already allocatedkhi tạo cluster → có gì đó đang chiếm port 80 trên máy. Tắt tiến trình đó, hoặc tạm bỏ khốiextraPortMappings(sẽ xử lý ở Part 3).- Tạo lỗi và muốn làm lại sạch:
kind delete cluster --name todo-opsrồi tạo lại.
💡 Hiểu thêm — Vì sao phải
kind loadimage thủ công?
Cluster kind chạy bên trong Docker riêng của nó. Imagetodo-api:v1bạn vừa build nằm ở Docker của máy host — kind không tự nhìn thấy nó, và ta cũng chưa đẩy image lên registry nào (như Docker Hub). Vì vậy phải "nạp" image từ host vào trong cluster kind bằng tay.
Bước 6 — Nạp image vào cluster kind
kind load docker-image todo-api:v1 --name todo-ops
- Lệnh này copy image
todo-api:v1từ Docker host vào trong node của clustertodo-ops.
Output mong đợi:
Image: "todo-api:v1" with ID "sha256:..." not yet present on node "todo-ops-control-plane", loading...
✅ Checkpoint 6
Không có lỗi đỏ. (Nếu lệnh chạy xong im lặng hoặc in dòng loading... là ổn.)
Nếu sai:
image: "todo-api:v1" not present locally→ chưa build image, hoặc gõ sai tên/tag. Quay lại Bước 4 chạydocker buildlại.- Đây là lỗi hay gặp nhất về sau: nếu ở Bước 7 pod báo
ErrImagePull/ImagePullBackOff, gần như chắc chắn là quên bước load này hoặc load xong nhưng sau đó build lại image mà chưa load lần nữa. Mỗi lần build image mới, phảikind loadlại.
💡 Hiểu thêm — Pod, Deployment, Service khác nhau thế nào?
- Pod: đơn vị chạy nhỏ nhất của K8s — bọc quanh một (hoặc vài) container. Pod dễ chết/bị thay.
- Deployment: người quản lý Pod. Bạn nói "tôi muốn 1 pod luôn sống" — Deployment đảm bảo điều đó, pod chết thì nó tạo pod mới. Ta luôn tạo Deployment, không tạo Pod trần.
- Service: địa chỉ mạng ổn định để gọi tới các Pod (vì pod hay đổi). Part 1 chưa cần Service — ta dùng
port-forwardđể truy cập tạm. Part 3 sẽ thêm Service + Ingress.
💡 Hiểu thêm — Vì sao viết YAML thay vì lệnh tắt?
K8s có lệnh nhanh nhưkubectl create deployment .... Nhưng cách làm chuẩn của DevOps là declarative: mô tả trạng thái mong muốn trong file YAML, lưu vào git, rồikubectl apply. File YAML là nguồn sự thật, có thể review, tái lập, và Part 6 (GitOps) sẽ tự động apply chúng từ git. Vì thế ngay từ đầu ta học viết YAML.
Bước 7 — Deploy bằng manifest YAML
Tạo thư mục k8s/ và file k8s/deployment.yaml:
mkdir k8s
Nội dung k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-api
labels:
app: todo-api
spec:
replicas: 1
selector:
matchLabels:
app: todo-api
template:
metadata:
labels:
app: todo-api
spec:
containers:
- name: todo-api
image: todo-api:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
Giải thích các field quan trọng:
-
apiVersion: apps/v1— nhóm & phiên bản API cho Deployment. K8s dùng cái này để biết cách hiểu file. -
kind: Deployment— loại tài nguyên: một Deployment. -
metadata.name— tên tài nguyên (todo-api).labels— nhãn để phân loại/tìm kiếm. -
spec— mô tả trạng thái mong muốn:-
replicas: 1— muốn có 1 pod chạy. -
selector.matchLabels— Deployment quản lý các pod có nhãnapp: todo-api. Phải khớp vớitemplate.metadata.labelsbên dưới, nếu không K8s báo lỗi. -
template— "khuôn" để tạo pod: -
template.metadata.labels— nhãn dán lên pod (khớp với selector). -
template.spec.containers— danh sách container trong pod:-
name— tên container. -
image: todo-api:v1— dùng đúng image ta đã build & load. -
imagePullPolicy: IfNotPresent— quan trọng cho kind: chỉ kéo image nếu chưa có sẵn trên node. Vì ta đãkind load, K8s dùng image local thay vì đi tìm trên internet (và fail). -
ports.containerPort: 3000— báo container lắng nghe port 3000.
-
-
Apply manifest vào namespace todo:
kubectl apply -f k8s/deployment.yaml -n todo
Output mong đợi: deployment.apps/todo-api created
Xem pod:
kubectl get pods -n todo
Output mong đợi (chờ ~10-30 giây cho tới khi Running):
NAME READY STATUS RESTARTS AGE
todo-api-xxxxxxxxxx-yyyyy 1/1 Running 0 20s
✅ Checkpoint 7
Pod ở trạng thái Running và cột READY là 1/1.
Nếu sai:
STATUS = ErrImagePullhoặcImagePullBackOff→ K8s không tìm thấy image. Nguyên nhân số 1: quênkind load(Bước 6), hoặc thiếuimagePullPolicy: IfNotPresent. Chạy lạikind load docker-image todo-api:v1 --name todo-ops, rồikubectl delete pod -n todo -l app=todo-apiđể pod được tạo lại.STATUS = CrashLoopBackOff→ container khởi động rồi chết liên tục, thường do lỗi trongserver.js. Xem log để biết lý do:kubectl logs -n todo -l app=todo-apiLog sẽ in đúng lỗi Node.js (ví dụ
SyntaxError,Cannot find module). Sửaserver.js, build lại image,kind loadlại, rồikubectl rollout restart deploy/todo-api -n todo.
error validating datakhi apply → YAML sai thụt lề (YAML cực nhạy với dấu cách; chỉ dùng space, không dùng tab) hoặcselectorkhông khớptemplate.labels.
💡 Hiểu thêm —
port-forward: app đang chạy bên trong cluster, máy bạn chưa có đường vào.kubectl port-forwardmở một "đường hầm" tạm: request tớilocalhost:3000trên máy bạn được chuyển thẳng vào pod. Đây là cách nhanh để debug/test, không phải cách mở app cho người dùng thật (việc đó là Service + Ingress ở Part 3).
Bước 8 — Truy cập app đang chạy trong cluster
Mở đường hầm tới Deployment (chạy nền với &):
kubectl port-forward -n todo deploy/todo-api 3000:3000 &
-
deploy/todo-api— forward tới Deploymenttodo-api. -
3000:3000— port máy bạn : port trong pod. -
&— chạy nền để bạn gõ lệnh tiếp trong cùng terminal.
Output mong đợi:
Forwarding from 127.0.0.1:3000 -> 3000
Gọi API (giờ là gọi vào app trong cluster):
curl localhost:3000/todos
Output mong đợi:
[{"id":1,"title":"Đọc DevOps 101 Part 1","done":false}]
Xong thì tắt đường hầm nền:
kill %1
-
%1— job chạy nền số 1 (chính làport-forward). Lệnh này dừng nó.
✅ Checkpoint 8
curl localhost:3000/todos trả JSON mảng todo — từ app chạy trong Kubernetes. Bạn vừa hoàn thành vòng đời: code → image → cluster → truy cập.
Nếu sai:
curlbáoConnection refused→ đường hầm chưa mở hoặc đã bịkill. Xem lại có dòngForwarding from ...không; nếu terminal đã chạy nền, gõjobsđể xem job còn sống.unable to forward ... pod is not running→ pod chưaRunning. Quay lại Checkpoint 7.- Nhớ
kill %1sau khi xong; nếu quên, port 3000 vẫn bị chiếm cho lần sau. Có thể tắt bằngkill %1hoặc đóng terminal.
Bạn đã hiểu gì
Tóm lại, bạn vừa đi trọn con đường từ số 0:
-
App → image: viết Todo API Node.js, đóng gói bằng
Dockerfilethành imagetodo-api:v1. -
Cluster local: dùng
kindtạo clustertodo-opschạy trong Docker, tạo namespacetodo. -
Nạp image: hiểu vì sao phải
kind load(cluster không thấy Docker host). -
Deploy declarative: viết
k8s/deployment.yamlthật, hiểuapiVersion / kind / metadata / spec / selector / template / containers, và vì saoimagePullPolicy: IfNotPresentquan trọng với kind. - Khái niệm nền: container vs image, Pod vs Deployment vs Service, namespace, port-forward.
-
Cách tự sửa lỗi: đọc
kubectl get pods(trạng thái) vàkubectl logs(nguyên nhân), phân biệtImagePullBackOffvớiCrashLoopBackOff.
Cluster của bạn vẫn đang chạy — giữ nguyên, Part 2 sẽ dùng tiếp. (Nếu cần dọn hẳn: kind delete cluster --name todo-ops.)
Tiếp theo
Trong Part 2 → Config & State, ta sẽ:
- Thêm PostgreSQL để todo không mất khi restart (thay mảng in-memory).
- Tách cấu hình ra khỏi code bằng ConfigMap và Secret (chuỗi kết nối DB, mật khẩu).
- Cập nhật app đọc config từ biến môi trường thay vì hard-code.




Top comments (0)