DEV Community

Ted Dam
Ted Dam

Posted on

Part 1 — Container hoá app & chạy trong Kubernetes local

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).

Kiến trúc todo-ops end-to-end: Browser → Ingress → Service → Todo API → Postgres

Đ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+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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

💡 Trên Apple Silicon (M1/M2/M3) đổi amd64arm64 trong hai lệnh kubectlkind. Trên macOS đã dùng brew nê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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode
  • mkdir todo-ops tạo thư mục dự án.
  • cd todo-ops bước vào trong đó — từ giờ mọi lệnh chạy trong thư mục này.
  • git init biế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/
Enter fullscreen mode Exit fullscreen mode

✅ 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 .git bị tạo nhầm bằng rm -rf .git, rồi cd vào đúng todo-ops và 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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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ệnh npm start = chạy node 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.2 nghĩ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}`);
});
Enter fullscreen mode Exit fullscreen mode

Giải thích những chỗ quan trọng:

  • express.json()middleware — nó chạy trước mỗi request và biến JSON trong body thành req.body. Không có dòng này thì POST không đọc được title.
  • todos / nextIdstate 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.
  • /healthz trả 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.log giúp ta biết app đã lên.

✅ Checkpoint 2

Chạy ls — phải thấy đủ package.jsonserver.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.js báo SyntaxError → 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
Enter fullscreen mode Exit fullscreen mode
  • npm install đọc package.json, tải Express về thư mục node_modules/.
  • node server.js khở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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Output mong đợi:

[{"id":1,"title":"Đọc DevOps 101 Part 1","done":false}]
Enter fullscreen mode Exit fullscreen mode

Thêm một todo mới:

curl -X POST -H 'Content-Type: application/json' -d '{"title":"học K8s"}' localhost:3000/todos
Enter fullscreen mode Exit fullscreen mode
  • -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}
Enter fullscreen mode Exit fullscreen mode

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:

  • curl báo Connection refused → app chưa chạy hoặc đã bị Ctrl+C. Kiểm tra terminal thứ nhất có dòng listening on port 3000 không.
  • POST trả {"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ạy npm install.

💡 Hiểu thêm — Dockerfile là gì?
Dockerfilecô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.

Vì sao COPY package.json trước: khi chỉ sửa mã nguồn, các bước cài thư viện được cache hit; khi sửa package.json thì mới cài lại


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"]
Enter fullscreen mode Exit fullscreen mode

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 /app trong 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ồm server.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 copy node_modules/ local (có thể build cho HĐH khác) và .git vào image, tạo file .dockerignore:

node_modules
.git

Bây giờ build image:

docker build -t todo-api:v1 .
Enter fullscreen mode Exit fullscreen mode
  • -t todo-api:v1 — đặt tên:tag cho image. Ta dùng đúng todo-api:v1 như 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
Enter fullscreen mode Exit fullscreen mode

✅ 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ặc package.json sai cú pháp JSON. Chạy node -c không giúp cho JSON; kiểm tra dấu phẩy/ngoặc trong package.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ác curl 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
Enter fullscreen mode Exit fullscreen mode

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 qua http://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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Kiểm tra kubectl đã kết nối được cluster:

kubectl get nodes
Enter fullscreen mode Exit fullscreen mode

Output mong đợi:

NAME                      STATUS   ROLES           AGE   VERSION
todo-ops-control-plane    Ready    control-plane   1m    v1.3x.x
Enter fullscreen mode Exit fullscreen mode

Tạo namespace todo để nhóm tài nguyên của app lại:

kubectl create namespace todo
Enter fullscreen mode Exit fullscreen mode

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 todo cho gọn, thay vì trộn vào default.

✅ Checkpoint 5

kubectl get nodes → node STATUS = Ready. kubectl get ns → có dòng todo.

Nếu sai:

  • Node kẹt ở NotReady và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 allocated khi 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ối extraPortMappings (sẽ xử lý ở Part 3).
  • Tạo lỗi và muốn làm lại sạch: kind delete cluster --name todo-ops rồi tạo lại.

💡 Hiểu thêm — Vì sao phải kind load image thủ công?
Cluster kind chạy bên trong Docker riêng của nó. Image todo-api:v1 bạ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
Enter fullscreen mode Exit fullscreen mode
  • Lệnh này copy image todo-api:v1 từ Docker host vào trong node của cluster todo-ops.

Output mong đợi:

Image: "todo-api:v1" with ID "sha256:..." not yet present on node "todo-ops-control-plane", loading...
Enter fullscreen mode Exit fullscreen mode

✅ 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ạy docker build lạ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ải kind load lạ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.

Deployment tạo và giám sát Pod; Service cho địa chỉ mạng ổn định để client gọi tới Pod qua label selector

💡 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ồi kubectl 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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ãn app: todo-api. Phải khớp với template.metadata.labels bê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: IfNotPresentquan 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
Enter fullscreen mode Exit fullscreen mode

Output mong đợi: deployment.apps/todo-api created

Xem pod:

kubectl get pods -n todo
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

✅ Checkpoint 7

Pod ở trạng thái Running và cột READY1/1.

Nếu sai:

  • STATUS = ErrImagePull hoặc ImagePullBackOff → K8s không tìm thấy image. Nguyên nhân số 1: quên kind load (Bước 6), hoặc thiếu imagePullPolicy: IfNotPresent. Chạy lại kind load docker-image todo-api:v1 --name todo-ops, rồi kubectl 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 trong server.js. Xem log để biết lý do:
  kubectl logs -n todo -l app=todo-api

Log sẽ in đúng lỗi Node.js (ví dụ SyntaxError, Cannot find module). Sửa server.js, build lại image, kind load lại, rồi kubectl rollout restart deploy/todo-api -n todo.

  • error validating data khi 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ặc selector không khớp template.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-forward mở một "đường hầm" tạm: request tới localhost:3000 trê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 &
Enter fullscreen mode Exit fullscreen mode
  • deploy/todo-api — forward tới Deployment todo-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
Enter fullscreen mode Exit fullscreen mode

Gọi API (giờ là gọi vào app trong cluster):

curl localhost:3000/todos
Enter fullscreen mode Exit fullscreen mode

Output mong đợi:

[{"id":1,"title":"Đọc DevOps 101 Part 1","done":false}]
Enter fullscreen mode Exit fullscreen mode

Xong thì tắt đường hầm nền:

kill %1
Enter fullscreen mode Exit fullscreen mode
  • %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:

  • curl báo Connection refused → đường hầm chưa mở hoặc đã bị kill. Xem lại có dòng Forwarding 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ưa Running. Quay lại Checkpoint 7.
  • Nhớ kill %1 sau khi xong; nếu quên, port 3000 vẫn bị chiếm cho lần sau. Có thể tắt bằng kill %1 hoặc đóng terminal.

Bạn đã hiểu gì

Vòng đời Part 1: mã nguồn + Dockerfile → docker build → image todo-api:v1 → kind load → Deployment/Pod trong cluster → port-forward → curl từ máy bạn

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 Dockerfile thành image todo-api:v1.
  • Cluster local: dùng kind tạo cluster todo-ops chạy trong Docker, tạo namespace todo.
  • Nạp image: hiểu vì sao phải kind load (cluster không thấy Docker host).
  • Deploy declarative: viết k8s/deployment.yaml thật, hiểu apiVersion / kind / metadata / spec / selector / template / containers, và vì sao imagePullPolicy: IfNotPresent quan 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ệt ImagePullBackOff với CrashLoopBackOff.

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 ConfigMapSecret (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)