Troubleshoot service và kết nối mạng trong cluster
Pod của bạn đã Running và Ready, nhưng gọi Service thì timeout. Đây là tình huống tôi gặp nhiều nhất trong lúc luyện thi, và cũng là dạng bài ăn điểm nhanh nếu bạn kiểm tra đúng thứ tự. Bài này tập trung vào một câu hỏi duy nhất: luồng traffic đứt ở đâu? Giữa pod và app có 4 chặng dễ đứt: DNS, Endpoints, port mapping và NetworkPolicy. Đi đúng thứ tự thì mỗi ca troubleshoot chỉ mất 2 đến 3 phút.
Mục tiêu bài học
- Xác định được Service có endpoint hay không chỉ bằng 1 lệnh, và biết vì sao endpoint rỗng.
- Kiểm tra được chuỗi
port→targetPort→containerPortkhớp với app đang listen. - Nhận diện NetworkPolicy chặn ingress hoặc chặn egress (đặc biệt là chặn DNS).
- Viết được lệnh test kết nối từ trong cluster bằng pod tạm, không phụ thuộc công cụ ngoài.
- Có quy trình 6 bước lặp lại được để dùng trong phòng thi.
Ôn nhanh bài trước
Bài trước chốt lại cách đọc describe, events và log để xử lý CrashLoopBackOff, ImagePullBackOff, Pending. Điểm móc nối quan trọng: khi pod đã 1/1 Running với restart count bằng 0 mà app vẫn không truy cập được, lỗi gần như chắc chắn không nằm trong container nữa, mà nằm ở lớp Service, DNS hoặc policy. Đừng đọc log app thêm lần nữa, hãy chuyển sang kiểm tra endpoints.
Ví dụ mẫu: Service có endpoints rỗng
Bối cảnh: namespace shop có Deployment api chạy nginx, container listen port 80. Service api-svc phải trỏ tới nó. Thực tế đang lỗi, ta debug từng bước.
Bước 1. Nhìn tổng thể trong một lệnh.
kubectl -n shop get svc,ep,pods --show-labels
Kết quả đáng ngờ:
NAME TYPE CLUSTER-IP PORT(S)
service/api-svc ClusterIP 10.96.14.22 80/TCP
NAME ENDPOINTS
endpoints/api-svc <none>
NAME READY STATUS LABELS
pod/api-6d9f-abcde 1/1 Running app=api-server,pod-template-hash=6d9f
ENDPOINTS <none> là manh mối số một. Service không có backend thì mọi thứ phía sau đều vô nghĩa.
Bước 2. So selector của Service với label của pod.
kubectl -n shop describe svc api-svc | grep -i selector
kubectl -n shop get pods --show-labels
Service đang chọn app=api, còn pod mang label app=api-server. Lệch một chữ là đủ để endpoint rỗng.
Bước 3. Sửa selector, không sửa pod.
Trong đề thi, câu hỏi thường quy định bên nào được phép sửa (đôi khi là "sửa Service", đôi khi là "sửa pod"). Ở đây đề cho sửa Service:
kubectl -n shop patch svc api-svc -p '{"spec":{"selector":{"app":"api-server"}}}'
Bước 4. Xác nhận endpoint đã có IP.
kubectl -n shop get endpoints api-svc
kubectl -n shop get endpointslices -l kubernetes.io/service-name=api-svc
Lần này phải thấy 1 hoặc 2 địa chỉ IP pod kèm port. Nếu vẫn <none>, kiểm tra pod đã Ready chưa: pod chưa Ready thì không được đưa vào endpoints.
Bước 5. Test từ bên trong cluster, không test từ máy bạn.
kubectl -n shop run tmp --image=busybox:1.28 --rm -it --restart=Never -- \
wget -qO- --timeout=3 http://api-svc.shop.svc.cluster.local/
Kết quả vẫn timed out. Endpoint có IP rồi mà vẫn hỏng, vậy điểm đứt nằm ở port mapping.
Bước 6. So port với targetPort và containerPort.
kubectl -n shop get svc api-svc -o yaml | grep -A5 ports
kubectl -n shop get deploy api -o jsonpath='{.spec.template.spec.containers[*].ports}'
Service khai port: 80, targetPort: 8080, nhưng nginx trong pod listen port 80. Traffic bị đẩy vào cổng không ai nghe. Sửa:
kubectl -n shop patch svc api-svc -p \
'{"spec":{"ports":[{"port":80,"targetPort":80,"protocol":"TCP"}]}}'
Lưu ý: patch vào ports là thay thế toàn bộ danh sách, nên phải khai lại đầy đủ port, targetPort, protocol.
Bước 7. Test lại. Lần này wget trả về nội dung trang mặc định của nginx. Luồng traffic đã thông.
[!TIP] Thứ tự kiểm tra cố định của tôi trong phòng thi: DNS → Endpoints → port/targetPort → NetworkPolicy → kube-proxy/CNI. Đi từ trên xuống, gặp chặng nào sai thì dừng lại sửa, không nhảy cóc.
Thực hành có hướng dẫn
Chuẩn bị chung cho cả 4 bài:
kubectl create namespace shop --dry-run=client -o yaml | kubectl apply -f -
Mỗi bài tập làm theo đúng 4 bước: quan sát → khoanh vùng → sửa → kiểm chứng bằng lệnh test. Đáp án nằm trong phần gập lại, hãy thử ít nhất 3 phút trước khi mở.
Bài tập 1: Endpoint rỗng vì selector lệch
kubectl -n shop apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: shop
spec:
replicas: 2
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-svc
namespace: shop
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
EOF
Nhiệm vụ: làm cho http://web-svc trả về nội dung từ trong cluster.
Xem gợi ý và đáp án
Gợi ý: kubectl -n shop get endpoints web-svc sẽ ra <none>. Đối chiếu kubectl -n shop get pods --show-labels với selector trong kubectl -n shop describe svc web-svc.
Đáp án (sửa Service):
kubectl -n shop patch svc web-svc -p '{"spec":{"selector":{"app":"web-frontend"}}}'
kubectl -n shop get endpoints web-svc
kubectl -n shop run t1 --image=busybox:1.28 --rm -it --restart=Never -- \
wget -qO- --timeout=3 http://web-svc.shop.svc.cluster.local/
Cách khác cũng hợp lệ nếu đề cho phép sửa pod: thêm label app=web vào pod template, sau đó rollout lại.
Bài tập 2: Có endpoint nhưng connection refused
kubectl -n shop create deployment api --image=nginx:1.25 --replicas=1
kubectl -n shop expose deployment api --name=api-svc --port=80 --target-port=8080
Nhiệm vụ: api-svc phải phục vụ được trên port 80. Kiểm chứng bằng pod tạm.
Xem gợi ý và đáp án
Gợi ý: kubectl -n shop get endpoints api-svc có IP, nên vấn đề không phải selector. Test bằng busybox sẽ thấy connection refused thay vì timeout. Đọc kubectl -n shop get svc api-svc -o yaml, so targetPort: 8080 với port nginx thực tế listen (80).
Đáp án:
kubectl -n shop patch svc api-svc -p \
'{"spec":{"ports":[{"port":80,"targetPort":80,"protocol":"TCP"}]}}'
kubectl -n shop run t2 --image=busybox:1.28 --rm -it --restart=Never -- \
wget -qO- --timeout=3 http://api-svc/
Phân biệt triệu chứng: timeout thường là policy hoặc sai địa chỉ, connection refused thường là đúng IP nhưng sai port.
Bài tập 3: NetworkPolicy chặn ingress
kubectl -n shop apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
Nhiệm vụ: giữ nguyên policy default-deny, nhưng cho phép pod có label access=granted gọi được web-svc trên port 80. Test bằng pod tạm có label đó.
Xem gợi ý và đáp án
Gợi ý: kubectl -n shop get networkpolicy để thấy policy đang áp dụng. podSelector: {} nghĩa là áp lên mọi pod trong namespace. Đọc kubectl -n shop describe networkpolicy default-deny để xem chặng nào đang bị deny.
Đáp án:
kubectl -n shop apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-granted
namespace: shop
spec:
podSelector:
matchLabels:
app: web-frontend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
access: granted
ports:
- protocol: TCP
port: 80
EOF
Test:
kubectl -n shop run t3 --labels="access=granted" --image=busybox:1.28 --rm -it --restart=Never -- \
wget -qO- --timeout=3 http://web-svc/
Chạy lại đúng lệnh trên nhưng bỏ --labels, bạn sẽ thấy bị chặn. Đó là bằng chứng policy hoạt động.
Bài tập 4: DNS và egress policy
kubectl create namespace client --dry-run=client -o yaml | kubectl apply -f -
kubectl -n client apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-egress
namespace: client
spec:
podSelector: {}
policyTypes:
- Egress
EOF
Nhiệm vụ: pod trong namespace client phải gọi được web-svc.shop.svc.cluster.local, và DNS vẫn phải hoạt động. Không được xóa policy.
Xem gợi ý và đáp án
Gợi ý: chạy test trước, bạn sẽ thấy lỗi không phân giải được tên, dù CoreDNS hoàn toàn khỏe. deny-egress chặn luôn port 53 ra CoreDNS. Cần thêm 2 rule egress: một cho DNS, một cho traffic tới namespace shop.
Đáp án:
kubectl -n client apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-and-shop
namespace: client
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: shop
ports:
- protocol: TCP
port: 80
EOF
Test:
kubectl -n client run t4 --image=busybox:1.28 --rm -it --restart=Never -- \
wget -qO- --timeout=3 http://web-svc.shop.svc.cluster.local/
Bài học lớn nhất ở đây: ==DNS fail trong cluster thường là lỗi NetworkPolicy egress, không phải lỗi CoreDNS==.
Quy trình 6 bước áp dụng cho mọi đề
1. `kubectl -n <ns> get svc,ep,pods --show-labels`: tìm endpoint rỗng trước tiên.
2. `kubectl -n <ns> describe svc <name>`: đọc Selector, Port, TargetPort, Endpoints.
3. `kubectl -n <ns> get endpointslices -l kubernetes.io/service-name=<name>`: xác nhận IP và port đã resolve.
4. `kubectl -n <ns> get networkpolicy` + `describe`: loại trừ ingress/egress bị chặn.
5. Test bằng pod tạm trong đúng namespace: `wget`, `nc -zv` hoặc `nslookup`.
6. Nếu Service hỏng nhưng `kubectl port-forward pod/<pod> 8080:80` vẫn chạy: lỗi nằm ở lớp Service/policy, không phải app.
Lỗi thường gặp và cách xử lý
| Triệu chứng | Nguyên nhân hay gặp | Lệnh kiểm tra |
|---|---|---|
Endpoints <none> |
Service selector không khớp label pod, hoặc pod chưa Ready | get svc,ep --show-labels, describe svc |
| Timeout khi gọi Service | NetworkPolicy chặn, hoặc targetPort sai | get networkpolicy, get svc -o yaml |
| Connection refused | Đúng pod nhưng sai port | so targetPort với containerPort |
| Không phân giải được tên | Sai namespace, hoặc egress policy chặn port 53 | nslookup <svc>.<ns>.svc.cluster.local |
| Chỉ một số pod lỗi | Pod đó ở node mất CNI | kubectl -n kube-system get pods -o wide |
| NodePort không vào được từ ngoài | externalTrafficPolicy: Local và node không có pod backend |
describe svc, get pods -o wide |
Một số bẫy cụ thể:
- Service không có selector (dùng cho Endpoints tự tạo hoặc ExternalName) thì endpoint không bao giờ tự sinh. Phải tạo object
Endpointscùng tên bằng tay. - Named port: nếu
targetPort: httpmàcontainerPortkhông đặtname: http, endpoint sẽ không resolve được port. Kiểm tra bằngget endpointslices ... -o yaml. - Headless Service (
clusterIP: None) không có VIP, DNS trả về IP pod. App nào hard-code ClusterIP sẽ hỏng. kubectl get endpointsvẫn dùng được, nhưng trên cluster mới nên quen vớiendpointslices.- Test bằng
curltrong image không có curl:nginxkhông có curl,busyboxkhông có curl. Dùngwget -qO-hoặcnc -zv <host> <port>cho chắc.
[!WARNING] Đừng sửa cùng lúc nhiều thứ. Mỗi lần chỉ sửa một điểm rồi test lại. Trong phòng thi, sửa 3 chỗ một lúc mà vẫn lỗi thì bạn không biết chỗ nào đúng, và mất luôn khả năng quay lui.
Góc chuyên gia
- 30 giây đầu tiên luôn dành cho endpoints. Chín trên mười ca Service hỏng trong đề CKA là do selector hoặc targetPort, không phải do DNS.
- Test từ đúng namespace. Gọi tên ngắn
web-svcchỉ đúng khi client ở cùng namespace. Khác namespace phải dùngweb-svc.shop.svc.cluster.localhoặc ít nhấtweb-svc.shop. - Port-forward là máy đo chia đôi vùng lỗi.
kubectl port-forward pod/<pod> 8080:80chạy được nghĩa là app ổn, vấn đề ở Service hoặc policy. Port-forward tớisvc/mà lỗi nghĩa là Service không có endpoint. - NetworkPolicy có tính cộng dồn: nhiều policy, chỉ cần một policy cho phép là traffic đi qua. Đừng cố tìm "policy đúng duy nhất".
- Mặc định không có NetworkPolicy nào thì mọi traffic được phép. Nếu namespace có đúng một policy
deny-all, hãy nhớ nó chỉ áp lên các pod khớppodSelector. - Kiểm tra image có sẵn trước khi chạy pod tạm. Nếu
busyboxkhông pull được, hãykubectl execvào một pod đang chạy sẵn trong cluster. Đừng mất 5 phút chờImagePullBackOff. - Ghi lại bằng chứng vào đáp án khi được yêu cầu. Vài đề yêu cầu dán output lệnh test, hãy chạy đúng lệnh mà đề mô tả.
Tóm tắt và bước tiếp theo
Bốn dòng tôi tự nhắc trước giờ thi:
- Endpoint rỗng thì nhìn selector trước, đừng nhìn DNS.
- Endpoint có IP mà vẫn lỗi thì so
targetPortvớicontainerPort. - Có policy trong namespace thì đọc
describe networkpolicytrước khi nghi ngờ CNI. - DNS fail mà CoreDNS vẫn chạy: nghĩ ngay tới egress policy chặn port 53.
Bài tiếp theo mở rộng sang đọc log control plane và journalctl: khi cả service lẫn pod đều ổn mà cluster vẫn có vấn đề, bạn sẽ cần đọc log của kube-apiserver, etcd, kube-scheduler và journalctl -u kubelet để tìm nguyên nhân gốc. Phần này cũng là nền để bạn xử lý các ca node NotReady nặng hơn.
Kiểm tra nhanh trước khi chuyển bài:
1. Khi Service không có backend nào, lệnh đầu tiên nên chạy là `kubectl get {{c1::endpoints}} <tên-service>`.
2. Nếu Service khai `port: 80` mà bỏ trống `targetPort`, traffic sẽ đi tới container port {{c2::80}}.
3. Tên DNS đầy đủ của Service `api` trong namespace `shop` là `api.shop.{{c3::svc.cluster.local}}`.
4. NetworkPolicy với `podSelector: {}` và `policyTypes: [Ingress]` sẽ {{c4::chặn toàn bộ}} traffic ingress vào mọi pod trong namespace đó.
5. Nếu policy egress chặn hết, triệu chứng thường thấy là pod không {{c5::phân giải được DNS}} dù CoreDNS vẫn chạy bình thường.
6. Service có `clusterIP: None` được gọi là {{c6::headless}} Service.
[
{ "front": "Lệnh nhanh nhất để biết Service có backend hay không?", "back": "kubectl get endpoints <svc>, hoặc get endpointslices -l kubernetes.io/service-name=<svc>." },
{ "front": "Endpoints rỗng thường do nguyên nhân gì?", "back": "Selector của Service không khớp label pod, hoặc pod chưa Ready." },
{ "front": "Service tự sinh Endpoints khi nào?", "back": "Khi Service có selector khớp pod Ready; Service không có selector phải tạo Endpoints thủ công." },
{ "front": "targetPort bỏ trống thì lấy giá trị nào?", "back": "Mặc định bằng giá trị của port." },
{ "front": "Tên DNS đầy đủ của Service?", "back": "<service>.<namespace>.svc.cluster.local." },
{ "front": "Cách test kết nối từ trong cluster bằng pod tạm?", "back": "kubectl run tmp --image=busybox:1.28 --rm -it --restart=Never -- wget -qO- --timeout=3 http://<svc>:<port>." },
{ "front": "Port-forward tới pod chạy được nhưng tới Service thì không, kết luận gì?", "back": "App ổn; lỗi nằm ở Service, Endpoints hoặc NetworkPolicy." },
{ "front": "NetworkPolicy podSelector rỗng với policyTypes Ingress nghĩa là gì?", "back": "Chặn toàn bộ ingress vào mọi pod trong namespace." },
{ "front": "Vì sao policy egress chặn hết lại làm DNS fail?", "back": "Vì nó chặn luôn port 53 ra CoreDNS, dù CoreDNS vẫn khỏe." },
{ "front": "NetworkPolicy có tính cộng dồn không?", "back": "Có, chỉ cần một policy cho phép là traffic được đi qua." },
{ "front": "clusterIP: None nghĩa là gì?", "back": "Headless Service, DNS trả về IP của từng pod, không có VIP." },
{ "front": "Dấu hiệu CNI lỗi trên một node?", "back": "Pod trên node đó mất kết nối mạng và pod CNI trong kube-system không Running." },
{ "front": "externalTrafficPolicy: Local gây lỗi gì với NodePort?", "back": "Node không có pod backend sẽ drop traffic thay vì chuyển sang node khác." },
{ "front": "Phân biệt timeout và connection refused nói lên điều gì?", "back": "Timeout thường do policy hoặc sai địa chỉ; connection refused thường do đúng IP nhưng sai port." }
]