Buổi 27 – Storage & Ingress
Tuần 14 · Giai đoạn 4 – Kubernetes Engineering · Slide: Slide buổi 27
Tóm tắt: Dùng PV, PVC, StorageClass để PostgreSQL giữ dữ liệu khi Pod bị xóa, và dùng ingress-nginx trên kind để truy cập ứng dụng qua host club.localhost.
Mục tiêu học tập
- Giải thích được vòng đời PV, PVC, StorageClass và vì sao Pod bị xóa nhưng dữ liệu vẫn còn.
- Chạy được PostgreSQL dạng StatefulSet có PVC trên cluster kind và chứng minh dữ liệu còn sau khi xóa Pod.
- Phân biệt được reclaim policy Delete và Retain, dự đoán điều gì xảy ra khi xóa PVC.
- Cài được ingress-nginx cho kind và định tuyến host club.localhost tới frontend và backend.
- Chẩn đoán được lỗi Ingress (404, 502, không truy cập được) và PVC bị Pending bằng kubectl describe.
Điều kiện cần
- Đã hoàn thành buổi 23-26: Pod, Deployment, Service, ConfigMap, Secret, resource và probe.
- Có Docker Desktop (WSL2 integration bật), kind 0.23+, kubectl, làm việc trong filesystem WSL (ví dụ
~/devops-labs). - Có thư mục
starter-project/đã sao chép vào repo cá nhân; imageclub-api:1.0.0vàclub-web:1.0.0đã build. - Port 80 và 443 của máy chưa bị chương trình khác chiếm (kind sẽ map hai port này).
Kiểm tra nhanh:
docker version --format '{{.Server.Version}}'
kind version
kubectl version --client
docker image ls | grep -E 'club-(api|web)'Kết quả mong đợi: Docker Server 27 trở lên, kind v0.23 trở lên, hai image có tag 1.0.0. Nếu thiếu image, build lại:
cd ~/devops-labs/starter-project
docker build -t club-api:1.0.0 ./backend
docker build -t club-web:1.0.0 ./frontendVấn đề thực tế
CLB chạy Club Management trên Kubernetes. Sau buổi 25, Postgres chạy như một Pod, nhưng khi một bạn lỡ chạy kubectl delete pod hoặc node khởi động lại, danh sách thành viên biến mất vì dữ liệu nằm trong filesystem tạm của container. Một cơ sở dữ liệu mà mất dữ liệu mỗi khi Pod chết thì không dùng được.
Vấn đề thứ hai: Service kiểu ClusterIP chỉ truy cập được từ trong cluster. Để xem ứng dụng, bạn đang dùng kubectl port-forward cho từng Service, mỗi lần một cổng khác nhau. Người dùng thật không làm vậy; họ gõ một tên miền, và một cổng vào duy nhất quyết định request đi tới frontend hay API. Buổi này giải quyết cả hai: lưu trữ bền vững (PVC) và cổng vào HTTP (Ingress).
Khái niệm cốt lõi
Vì sao cần storage riêng
Filesystem của container gắn với vòng đời container. Container bị thay thế thì lớp ghi (writable layer) bị bỏ. Kubernetes tách "chỗ chứa dữ liệu" khỏi Pod bằng ba đối tượng.
| Đối tượng | Ý nghĩa | Ai tạo | Ví dụ |
|---|---|---|---|
| PersistentVolume (PV) | Một phần dung lượng lưu trữ thật trong cluster | Admin hoặc provisioner tự động | Thư mục trên node kind |
| PersistentVolumeClaim (PVC) | Yêu cầu dung lượng và kiểu truy cập của một workload | Developer | postgres-pvc, 1Gi |
| StorageClass | Mô tả "loại" storage và cách tạo PV tự động | Admin | standard trên kind |
Chuỗi hoạt động: Pod tham chiếu PVC, PVC được gắn (bind) với một PV, PV trỏ tới storage thật. Với dynamic provisioning, bạn chỉ tạo PVC; StorageClass tự tạo PV phù hợp.
Access mode, reclaim policy, binding mode
| Thuộc tính | Giá trị | Ý nghĩa |
|---|---|---|
| accessModes | ReadWriteOnce (RWO) | Một node đọc/ghi tại một thời điểm; phù hợp database |
| accessModes | ReadOnlyMany, ReadWriteMany | Cần storage hỗ trợ; kind mặc định không có RWX |
| reclaimPolicy | Delete | Xóa PVC thì PV và dữ liệu bị xóa theo |
| reclaimPolicy | Retain | Xóa PVC thì PV còn, ở trạng thái Released, dữ liệu giữ lại để admin xử lý |
| volumeBindingMode | Immediate | Tạo PV ngay khi có PVC |
| volumeBindingMode | WaitForFirstConsumer | Chờ tới khi có Pod dùng PVC mới tạo PV, để chọn đúng node |
Trên kind, StorageClass mặc định tên standard (local-path provisioner), reclaimPolicy Delete, binding mode WaitForFirstConsumer. Hệ quả quan trọng: PVC vừa tạo ở trạng thái Pending là bình thường cho tới khi có Pod dùng nó.
StatefulSet và PVC
Deployment dùng cho workload không trạng thái. Database cần danh tính ổn định và storage riêng, nên dùng StatefulSet. Trong lab này, starter-project/k8s/postgres.yaml tạo PVC postgres-pvc riêng rồi để StatefulSet mount vào. Với nhiều replica, bạn sẽ dùng volumeClaimTemplates để mỗi Pod có PVC riêng; với một replica, cách PVC tường minh giúp bạn thấy rõ từng đối tượng. Lưu ý: xóa StatefulSet hoặc Pod không xóa PVC; chỉ xóa PVC mới (có thể) làm mất dữ liệu.
Ingress và Ingress controller
| Thành phần | Vai trò |
|---|---|
| Ingress (đối tượng API) | Khai báo luật: host nào, path nào, chuyển tới Service nào |
| Ingress controller (ví dụ ingress-nginx) | Pod chạy proxy thật, đọc các Ingress và áp dụng luật |
| IngressClass | Chọn controller nào xử lý Ingress (ingressClassName: nginx) |
Chỉ tạo Ingress mà không cài controller thì không có gì xảy ra: Ingress chỉ là khai báo. Ingress hoạt động ở tầng HTTP (host, path), khác với Service LoadBalancer hoạt động ở tầng TCP/UDP. Trên kind không có cloud load balancer, nên ta map port 80 và 443 của container node ra máy host bằng extraPortMappings, và gắn nhãn ingress-ready=true để controller được xếp lịch lên đúng node đó.
Path matching
| pathType | Hành vi |
|---|---|
| Prefix | Khớp theo tiền tố từng đoạn path: /api khớp /api và /api/members |
| Exact | Chỉ khớp đúng path |
| ImplementationSpecific | Tùy controller |
Nhiều rule cùng khớp thì rule có path dài hơn được ưu tiên, nên /api thắng /.
Kiến trúc và cách hoạt động
Từng bước của một request GET http://club.localhost/api/members:
- Trình duyệt phân giải
club.localhostvề 127.0.0.1 (tên miền.localhostluôn trỏ về máy local). - Request tới port 80 của máy; Docker map vào port 80 của container node kind (do
extraPortMappings). - ingress-nginx nhận request, tìm Ingress có host
club.localhostvà path/api, chuyển tới Servicebackendport 3000. - Service chọn một trong hai Pod
club-api; Pod này truy vấn Servicepostgres. - Pod
postgres-0đọc dữ liệu từ/var/lib/postgresql/data, thực chất là PV gắn qua PVCpostgres-pvc.
Demo của mentor
Mục đích: cho cả lớp thấy dữ liệu sống sót qua việc xóa Pod, và Ingress là "bảng chỉ đường". Dùng cluster đã tạo sẵn (hoặc tạo ở phần Chuẩn bị của lab). Thời lượng khoảng 15 phút trong 30 phút lý thuyết.
- Quan sát StorageClass:
kubectl get storageclassKết quả mong đợi (rút gọn):
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
standard (default) rancher.io/local-path Delete WaitForFirstConsumerHỏi lớp: "Vì sao binding mode là WaitForFirstConsumer? Nếu cluster có 3 node thì điều gì có thể sai nếu PV tạo trước khi biết Pod chạy ở node nào?"
- Tạo riêng PVC và quan sát trạng thái Pending:
kubectl apply -f starter-project/k8s/namespace.yaml
kubectl apply -n club -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-pvc
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 100Mi
EOF
kubectl get pvc -n club demo-pvcKết quả mong đợi: STATUS là Pending. Chạy kubectl describe pvc demo-pvc -n club, mục Events nói "waiting for first consumer". Hỏi lớp: "Pending ở đây là lỗi không?" (Không.)
- Tạo Pod dùng PVC, ghi file, xóa Pod, tạo lại:
kubectl apply -n club -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: demo-writer
spec:
containers:
- name: busybox
image: busybox:1.36
command: ["sh", "-c", "date >> /data/log.txt; sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: demo-pvc
EOF
kubectl wait -n club --for=condition=Ready pod/demo-writer --timeout=60s
kubectl get pvc,pv -n club
kubectl delete pod demo-writer -n clubKết quả mong đợi: PVC chuyển Bound, có một PV tên dạng pvc-xxxxxxxx. Sau đó apply lại Pod rồi kubectl exec -n club demo-writer -- cat /data/log.txt thấy hai dòng ngày giờ: dữ liệu còn nguyên. Hỏi lớp: "Nếu giờ xóa PVC thì file còn không?"
Dọn dẹp demo:
kubectl delete pod demo-writer -n club --ignore-not-foundrồikubectl delete pvc demo-pvc -n club. Cho lớp thấykubectl get pvkhông còn PV tương ứng (reclaimPolicy Delete).Ingress: giải thích file
starter-project/k8s/ingress.yamltừng dòng (ingressClassName,host, haipath). Hỏi lớp: "Nếu đổi thứ tự hai path,/api/membersđi đâu?" (Vẫn tới backend vì path dài hơn thắng.)
Hands-on lab
Thời gian dự kiến: 60 phút. Mục tiêu: chạy toàn bộ Club Management trên kind, Postgres có PVC, truy cập qua http://club.localhost.
Chuẩn bị
Làm việc trong WSL:
cd ~/devops-labs
mkdir -p lab-27/source lab-27/evidence lab-27/screenshots
cp -r ~/devops-labs/starter-project lab-27/source/ 2>/dev/null || true
cd lab-27/source/starter-projectNếu bạn dùng thư mục starter-project ở nơi khác, hãy cd vào đó. Từ đây các đường dẫn k8s/... tính từ thư mục starter-project.
Xóa cluster cũ (nếu có) để dùng cấu hình có port mapping:
kind get clusters
kind delete cluster --name clubLệnh này xóa toàn bộ workload trong cluster club cũ; nếu chưa có cluster thì kind chỉ báo không tìm thấy, bỏ qua.
Ghi toàn bộ lệnh và kết quả vào file bằng cách chạy script hoặc sao chép tay vào ../../evidence/command-output.txt.
Phần 1. Tạo cluster kind có port mapping
- Xem cấu hình cluster:
cat k8s/kind-config.yamlHai điểm quan trọng: nhãn ingress-ready=true và extraPortMappings cho port 80, 443. Thiếu hai điểm này, ingress-nginx không nhận được traffic từ máy host.
- Tạo cluster:
kind create cluster --name club --config k8s/kind-config.yaml
kubectl cluster-info --context kind-clubKết quả mong đợi:
Creating cluster "club" ...
✓ Ensuring node image
✓ Preparing nodes
Set kubectl context to "kind-club"Vì sao: cluster kind là một container Docker; port mapping chỉ có thể đặt lúc tạo cluster, không sửa sau được.
- Nạp image vào kind (kind không tự thấy image trong Docker local):
kind load docker-image club-api:1.0.0 --name club
kind load docker-image club-web:1.0.0 --name clubNếu lệnh báo không tìm thấy image, quay lại phần Điều kiện cần để build.
Phần 2. Postgres với PVC
- Tạo namespace, ConfigMap và Secret (Secret tạo bằng lệnh, không commit giá trị thật):
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl create secret generic club-secrets -n club --from-literal=db-password='lab-local-pass-27'Kết quả mong đợi: secret/club-secrets created. Giá trị mật khẩu chỉ dùng cho lab local.
- Triển khai Postgres:
kubectl apply -f k8s/postgres.yaml
kubectl get pvc -n club
kubectl get pods -n club -wKết quả mong đợi: ban đầu PVC Pending, vài giây sau Bound; Pod postgres-0 chuyển Running và READY 1/1. Nhấn Ctrl+C để thoát chế độ -w.
Vì sao: PVC Pending cho tới khi Pod postgres-0 được xếp lịch (WaitForFirstConsumer), sau đó local-path provisioner tạo PV.
- Xem PV và PVC liên kết với nhau:
kubectl get pv
kubectl describe pvc postgres-pvc -n clubKết quả mong đợi: một PV Bound với CLAIM là club/postgres-pvc, RECLAIM POLICY là Delete, StorageClass standard. Ghi lại tên PV, bạn sẽ cần ở L2.
- Tạo bảng và dữ liệu mẫu (Kubernetes không tự chạy
init.sqlnhư Compose):
kubectl exec -i -n club postgres-0 -- psql -U club -d clubdb < database/init.sql
kubectl exec -n club postgres-0 -- psql -U club -d clubdb -c "SELECT id, name, email FROM members;"Kết quả mong đợi:
id | name | email
----+-------+-------------------
1 | Alice | alice@example.com
2 | Bob | bob@example.comNếu không thấy thư mục database/, tìm đúng đường dẫn file init.sql trong starter-project.
- Thêm một dòng "của riêng bạn" để dễ nhận ra dữ liệu:
kubectl exec -n club postgres-0 -- psql -U club -d clubdb -c "INSERT INTO members (name, email) VALUES ('Ban', 'ban@example.com');"Phần 3. Chứng minh dữ liệu còn sau khi xóa Pod
- Xóa Pod:
kubectl delete pod postgres-0 -n club
kubectl get pods -n club -wKết quả mong đợi: StatefulSet tạo lại Pod cùng tên postgres-0, trở lại Running.
- Kiểm tra dữ liệu:
kubectl exec -n club postgres-0 -- psql -U club -d clubdb -c "SELECT count(*) FROM members;"Kết quả mong đợi: count là 3, bao gồm dòng Ban.
Vì sao: Pod mới mount lại cùng PVC, nên thấy cùng dữ liệu. Biến PGDATA=/var/lib/postgresql/data/pgdata (đặt trong postgres.yaml) tránh lỗi Postgres từ chối khởi tạo khi thư mục mount có thư mục lost+found hoặc không rỗng.
Phần 4. Triển khai backend và frontend
kubectl apply -f k8s/backend.yaml
kubectl apply -f k8s/frontend.yaml
kubectl rollout status deployment/club-api -n club
kubectl rollout status deployment/club-web -n club
kubectl get pods -n clubKết quả mong đợi: hai Pod club-api, một Pod club-web, một Pod postgres-0, tất cả 1/1 Running.
Kiểm tra backend trước khi có Ingress, để biết lỗi (nếu có) không thuộc về Ingress:
kubectl port-forward -n club svc/backend 3001:3000 &
sleep 2
curl -s http://localhost:3001/ready
curl -s http://localhost:3001/api/members
kill %1Kết quả mong đợi: {"status":"ready"} và danh sách ba thành viên dạng JSON. Dùng port 3001 để tránh xung đột port 3000 trên máy.
Phần 5. Cài ingress-nginx và tạo Ingress
- Cài ingress-nginx bản manifest dành cho kind (do dự án kind duy trì, đã kèm toleration và nhãn node cần thiết):
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.1/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120sKết quả mong đợi: pod/ingress-nginx-controller-... condition met. Lần đầu có thể mất 1-2 phút để kéo image.
Vì sao: manifest này yêu cầu node có nhãn ingress-ready=true, và bạn đã đặt nhãn đó trong kind-config.yaml. URL ghim vào tag controller-v1.12.1 để mọi học viên cài cùng một phiên bản. Lưu ý: dự án ingress-nginx đã thông báo ngừng bảo trì từ tháng 3/2026; bản đã ghim vẫn chạy tốt cho lab local, nhưng với hệ thống thật hãy xem xét Gateway API hoặc controller khác (xem trang kind - Ingress để biết hướng dẫn mới nhất).
- Áp dụng Ingress của dự án:
cat k8s/ingress.yaml
kubectl apply -f k8s/ingress.yaml
kubectl get ingress -n clubKết quả mong đợi: Ingress club, HOSTS là club.localhost, CLASS là nginx.
- Truy cập qua Ingress. Dùng header Host để không phụ thuộc DNS của WSL:
curl -s -H "Host: club.localhost" http://127.0.0.1/api/members
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: club.localhost" http://127.0.0.1/Kết quả mong đợi: JSON thành viên và mã 200. Trên Windows, mở trình duyệt vào http://club.localhost sẽ thấy giao diện frontend.
Vì sao: Ingress chọn luật theo header Host; không có header đúng, controller trả 404.
- Xem log controller để thấy request đi qua:
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=5L1 – Foundation: checklist hoàn thành
- Cluster
clubtạo bằngk8s/kind-config.yaml,kubectl get nodesbáoReady. - PVC
postgres-pvcở trạng tháiBound, có chụp/ghi lại kết quảkubectl get pvc,pv. - Bảng
memberscó dữ liệu, kể cả dòng do bạn thêm. - Sau khi xóa
postgres-0, truy vấn vẫn thấy đủ dữ liệu. -
curl -H "Host: club.localhost" http://127.0.0.1/api/memberstrả JSON. - Lưu lệnh và output vào
evidence/command-output.txt.
L2 – Engineering: thử thách tự làm
Thử thách 1. Chứng minh vòng đời PV/PVC. Không xem gợi ý trước.
- Ghi tên PV hiện tại và reclaim policy.
- Dự đoán rồi kiểm chứng: xóa StatefulSet
postgres(không xóa PVC) thì dữ liệu còn không? Tạo lại StatefulSet, truy vấn lại. - Dự đoán rồi kiểm chứng: xóa PVC thì chuyện gì xảy ra với PV và dữ liệu?
- Tiêu chí hoàn thành: bảng 3 dòng (hành động, dự đoán, kết quả thực tế) trong
report.md, và giải thích vì sao khi xóa PVC mà Pod vẫn đang dùng, PVC ở trạng tháiTerminating.
Gợi ý
kubectl delete statefulset postgres -n clubrồikubectl apply -f k8s/postgres.yaml: PVC không bị đụng tới.kubectl delete pvc postgres-pvc -n clubkhi Pod còn chạy sẽ treo ởTerminatingdo finalizerkubernetes.io/pvc-protection; xóa Pod/StatefulSet trước.- Theo dõi PV bằng
kubectl get pv -wở terminal thứ hai.
Lời giải
kubectl get pv
kubectl delete statefulset postgres -n club
kubectl get pvc -n club # vẫn Bound
kubectl apply -f k8s/postgres.yaml
kubectl exec -n club postgres-0 -- psql -U club -d clubdb -c "SELECT count(*) FROM members;" # còn dữ liệu
kubectl delete statefulset postgres -n club
kubectl delete pvc postgres-pvc -n club
kubectl get pv # PV biến mất vì reclaimPolicy Delete
kubectl apply -f k8s/postgres.yaml # tạo lại PVC mới, database rỗng
kubectl exec -n club postgres-0 -- psql -U club -d clubdb -c "\dt" # không có bảng membersKết luận: xóa PVC với reclaimPolicy Delete làm mất dữ liệu vĩnh viễn; phải chạy lại init.sql.
Thử thách 2. Chuyển sang Retain. Đặt PV hiện tại về Retain bằng kubectl patch pv, xóa StatefulSet và PVC, quan sát PV chuyển Released. Giải thích vì sao PV Released chưa thể bind lại cho PVC mới và nêu cách admin phục hồi (gợi ý: claimRef).
- Tiêu chí hoàn thành: output
kubectl get pvtrước và sau, cùng lời giải thích 3-5 câu.
Gợi ý
Lệnh patch có dạng kubectl patch pv TEN_PV -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'. Thay TEN_PV bằng tên PV thật từ kubectl get pv. PV Released còn claimRef trỏ tới PVC cũ, nên không bind cho PVC khác.
Lời giải
PV=$(kubectl get pvc postgres-pvc -n club -o jsonpath='{.spec.volumeName}')
kubectl patch pv "$PV" -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
kubectl delete statefulset postgres -n club
kubectl delete pvc postgres-pvc -n club
kubectl get pv # STATUS: ReleasedPV giữ dữ liệu nhưng còn claimRef cũ. Để dùng lại: xóa spec.claimRef (kubectl patch pv "$PV" --type=json -p='[{"op":"remove","path":"/spec/claimRef"}]') để PV về Available, rồi tạo PVC khớp (storageClassName, dung lượng) hoặc đặt volumeName trong PVC. Với local-path, dữ liệu nằm trên node nên phải dọn thủ công khi không cần nữa.
Thử thách 3 (nếu còn thời gian). Thêm host thứ hai. Tạo Ingress mới (file lab-27/source/ingress-api.yaml, tức ../ingress-api.yaml tính từ thư mục starter-project) để api.club.localhost chuyển toàn bộ tới Service backend, trong khi club.localhost vẫn hoạt động. Kiểm tra bằng curl với header Host.
- Tiêu chí hoàn thành: hai lệnh curl khác host trả đúng nội dung,
kubectl describe ingresshiển thị cả hai rule.
Lời giải
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: club-api-host
namespace: club
spec:
ingressClassName: nginx
rules:
- host: api.club.localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend
port:
number: 3000kubectl apply -f ../ingress-api.yaml
curl -s -H "Host: api.club.localhost" http://127.0.0.1/api/membersPhá lỗi và debug
Tình huống A. PVC bị Pending mãi (liên quan Tình huống 09 – Pod Pending)
Phá: tạo PVC tham chiếu StorageClass không tồn tại.
kubectl apply -n club -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: broken-pvc
spec:
storageClassName: fast-ssd
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
EOF
kubectl get pvc -n club broken-pvcTriệu chứng: STATUS là Pending không đổi; nếu Pod dùng PVC này, Pod cũng Pending.
Điều tra theo bước:
kubectl describe pvc broken-pvc -n club: đọc Events, thấystorageclass.storage.k8s.io "fast-ssd" not found.kubectl get storageclass: chỉ cóstandard.- Phân biệt với Pending bình thường của WaitForFirstConsumer (Events nói "waiting for first consumer").
Nguyên nhân gốc: tên StorageClass sai nên không có provisioner nào xử lý PVC. Cách sửa: xóa PVC, tạo lại với standard hoặc bỏ trường storageClassName để dùng mặc định. Phòng tránh: kiểm tra kubectl get storageclass trước khi viết PVC; PVC không đổi được storageClassName sau khi tạo.
Dọn dẹp: kubectl delete pvc broken-pvc -n club. Xem thêm examples/troubleshooting/scenario-09.
Tình huống B. Ingress trả 404 hoặc 503
Phá 1: gọi không có header Host đúng.
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/api/membersTriệu chứng: mã 404 từ nginx vì không có rule cho host 127.0.0.1.
Phá 2: tạo bản lỗi từ file gốc bằng sed 's/name: backend$/name: backendd/' k8s/ingress.yaml | kubectl apply -f -, tức là sửa tên Service trong Ingress thành backendd rồi apply lại, gọi curl -i -H "Host: club.localhost" http://127.0.0.1/api/members.
Triệu chứng: 503 Service Temporarily Unavailable (không có endpoint hợp lệ).
Điều tra:
kubectl describe ingress club -n club: cột Backends hiển thịbackendd:3000 (<error: endpoints "backendd" not found>).kubectl get svc,endpoints -n club: đối chiếu tên Service và có endpoint hay không.kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=20.
Nguyên nhân gốc: Ingress tham chiếu Service không tồn tại, hoặc Service không có Pod Ready. Phòng tránh: dùng kubectl describe ingress ngay sau khi apply; kiểm tra readiness của backend. Khôi phục bằng kubectl apply -f k8s/ingress.yaml.
Tình huống C. Cluster thiếu port mapping
Nếu cluster tạo bằng kind create cluster mặc định (không có --config), curl http://127.0.0.1/ báo Connection refused dù controller chạy tốt. Điều tra bằng docker ps và xem cột PORTS của container club-control-plane: không có 0.0.0.0:80->80/tcp. Cách sửa: xóa cluster và tạo lại với k8s/kind-config.yaml.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
PVC Pending ngay sau khi tạo | WaitForFirstConsumer, chưa có Pod dùng PVC | Bình thường; tạo Pod dùng PVC |
PVC Pending mãi dù có Pod | StorageClass sai tên hoặc không có provisioner | kubectl describe pvc, sửa storageClassName |
postgres-0 CrashLoopBackOff, log báo thư mục data không rỗng | Thiếu PGDATA trỏ thư mục con | Dùng PGDATA=/var/lib/postgresql/data/pgdata, xóa PVC cũ hỏng và tạo lại |
| Xóa PVC xong database rỗng | reclaimPolicy Delete | Sao lưu trước; dùng Retain cho dữ liệu quan trọng |
curl tới localhost:80 bị refused | Cluster tạo thiếu extraPortMappings, hoặc port 80 bị chiếm | Tạo lại cluster với kind-config.yaml; kiểm tra port 80 |
| Ingress có nhưng trả 404 | Sai host/header, hoặc thiếu ingressClassName: nginx | Gọi đúng Host; kiểm tra kubectl get ingressclass |
Controller Pending | Node thiếu nhãn ingress-ready=true | Tạo lại cluster đúng cấu hình |
ErrImageNeverPull hoặc ImagePullBackOff ở backend | Quên kind load docker-image | Nạp lại image vào cluster club |
/api/members trả 500, bảng không tồn tại | Chưa chạy init.sql trên Postgres mới | Chạy lại lệnh psql < init.sql |
Tự kiểm tra
- PV, PVC và StorageClass khác nhau thế nào? Developer thường tạo đối tượng nào?
- Vì sao PVC trên kind ở trạng thái Pending trước khi có Pod, và đó có phải lỗi không?
- reclaimPolicy Delete và Retain khác nhau thế nào khi xóa PVC?
- Chỉ cài Ingress mà không có Ingress controller thì điều gì xảy ra?
- Vì sao kind cần
extraPortMappingsvà nhãningress-ready=true? - Tình huống: bạn xóa
postgres-0, Pod tạo lại, dữ liệu còn. Hôm sau bạn xóa PVC rồi apply lạipostgres.yaml, database rỗng. Giải thích cả hai kết quả. - Tình huống:
curl http://127.0.0.1/api/memberstrả 404 còn port-forward trực tiếp vào Service thì chạy. Bạn kiểm tra gì theo thứ tự?
Đáp án
- PV là dung lượng thật; PVC là yêu cầu của workload; StorageClass mô tả loại storage và cách tạo PV tự động. Developer tạo PVC.
- Binding mode WaitForFirstConsumer chờ Pod được xếp lịch để chọn node; không phải lỗi. Dùng
describe pvcđể thấy Events. - Delete: xóa PV và dữ liệu cùng PVC. Retain: PV còn ở trạng thái Released, dữ liệu giữ lại, admin phải xử lý thủ công.
- Không có gì: Ingress chỉ là khai báo, cần controller (ví dụ ingress-nginx) đọc và thực thi.
- Cluster kind chạy trong container Docker; port mapping đưa port 80/443 của host vào node, nhãn giúp controller chạy đúng node có mapping.
- Xóa Pod: PVC và PV vẫn còn, Pod mới mount lại nên còn dữ liệu. Xóa PVC với policy Delete: PV và dữ liệu bị xóa, PVC mới cấp PV mới trống.
- Có gửi đúng header Host
club.localhostkhông;kubectl describe ingress;ingressClassName; Service và endpoints có tồn tại không; log controller.
Nộp bài
Nộp vào thư mục lab-27/ trong repository cá nhân theo templates/lab-template.md.
-
README.mdtrả lời đủ 7 câu theo template, ghi rõ đã làm L1 hay L2. -
source/chứa manifest bạn dùng hoặc sửa (postgres.yaml,ingress.yaml,ingress-api.yamlnếu làm L2). -
evidence/command-output.txtcó output củakubectl get pvc,pv,kubectl get ingress -n club, truy vấn dữ liệu trước và sau khi xóa Pod, và hai lệnh curl. -
report.mdcó bảng dự đoán/kết quả của thử thách 1, phân tích một lỗi bạn đã gặp (hoặc Tình huống A/B) kèm nguyên nhân gốc. -
screenshots/có ảnh trình duyệt mởhttp://club.localhost(bổ sung, không thay cho evidence). - Không commit mật khẩu thật; Secret chỉ tạo bằng lệnh, không có file chứa giá trị thật trong repo.
Recap
- PVC tách dữ liệu khỏi vòng đời Pod; Pod bị xóa không làm mất dữ liệu, xóa PVC thì có thể.
- StorageClass cấp PV tự động; trên kind là
standardvới policy Delete và WaitForFirstConsumer. - Database trên Kubernetes dùng StatefulSet, RWO,
PGDATAtrỏ thư mục con, và init dữ liệu là việc của bạn. - Ingress chỉ là luật; cần Ingress controller để chạy, và kind cần port mapping cùng nhãn node.
- Path dài hơn thắng:
/apitới backend,/tới frontend. - Debug theo lớp: PVC (describe) rồi Pod, Service và endpoints, Ingress, cuối cùng log controller.