🚪 Ingress và Ingress controller

Mục tiêu bài học

Sau bài này, bạn phải làm được những việc sau, và làm được trong khoảng 8 đến 10 phút nếu gặp trong đề thi:

  • Viết Ingress rule route theo host và theo path tới hai Service khác nhau.
  • Kiểm tra Ingress đã được controller nhận, và test đúng bằng curl với header Host.
  • Phân biệt và xử lý hai lỗi test hay gặp nhất: 404 (không match rule) và 502/503 (backend có vấn đề).

Ôn nhanh 30 giây

Bài trước bạn đã chốt: Service có ClusterIP và DNS name dạng <svc>.<ns>.svc.cluster.local, và nếu Service không có endpoint thì kết nối sẽ fail. Ingress đứng trên Service: nó không thay thế Service, nó chỉ quyết định request HTTP nào được đẩy vào Service nào.

[!NOTE] Ingress là object khai báo rule ở layer 7 (HTTP/HTTPS). Bản thân object Ingress không làm gì cả. Phải có Ingress controller (thường là ingress-nginx) đọc object đó rồi cấu hình reverse proxy. Không có controller thì kubectl get ingress vẫn ra resource, nhưng không có traffic nào được route.

Luồng traffic đầy đủ trông như sau:

flowchart LR
  Client["👤<br/>Client gửi Host header"] --> IC["🚪<br/>Ingress controller<br/>NodePort 30080"]
  IC --> Rule["📜<br/>Match rule<br/>host + path"]
  Rule --> Svc["🔗<br/>Service ClusterIP"]
  Svc --> EP["📦<br/>Pod endpoints"]
  Rule -.->|không khớp rule| E404["❌ 404 Not Found"]
  Svc -.->|không có endpoint| E503["⚠️ 502 / 503"]

Ví dụ có hướng dẫn

Bối cảnh: namespace shop có hai app, web (nginx, port 80) và api (http-echo, port 5678). Yêu cầu: shop.example.com/ vào web, shop.example.com/api vào api.

Bước 1: Kiểm tra cluster đã có Ingress controller chưa. Đây là bước nhiều người bỏ qua rồi ngồi sửa YAML 15 phút.

kubectl get ingressclass
kubectl get pods -A | grep -i ingress

Có nginx trong danh sách ingressclass là dùng được. Không có thì phải cài controller trước, nhưng trong đề CKA controller gần như luôn được cài sẵn.

Bước 2: Tạo workload và Service.

kubectl create ns shop
kubectl -n shop create deploy web --image=nginx:1.25 --port=80
kubectl -n shop expose deploy web --port=80 --name=web

kubectl -n shop create deploy api --image=hashicorp/http-echo:1.0
kubectl -n shop expose deploy api --port=80 --target-port=5678 --name=api

Chú ý: --port=80 là port của Service, --target-port=5678 là port container đang listen. Nhầm chỗ này chính là nguồn gốc lỗi 502/503 ở phần sau.

Bước 3: Sinh YAML bằng kubectl create ingress thay vì gõ tay. Cú pháp --rule="host/path=service:port", path kết thúc bằng * thì thành pathType: Prefix.

kubectl -n shop create ingress shop-ing \
  --class=nginx \
  --rule="shop.example.com/*=web:80" \
  --rule="shop.example.com/api*=api:80" \
  --dry-run=client -o yaml > shop-ing.yaml

Bước 4: Đọc lại và chỉnh YAML (thêm TLS, đổi port number sang port name, sửa path nếu cần):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-ing
  namespace: shop
spec:
  ingressClassName: nginx
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api
            port:
              number: 80
kubectl apply -f shop-ing.yaml
kubectl -n shop get ingress
kubectl -n shop describe ingress shop-ing

Trong describe, phần Rules: phải hiện đúng Host, Path và Backends. Đó là bằng chứng rule đã vào đúng chỗ.

Bước 5: Test bằng curl với header Host.

kubectl -n ingress-nginx get svc ingress-nginx-controller
# ví dụ trả về NodePort 30080

curl -s -o /dev/null -w "%{http_code}\n" -H "Host: shop.example.com" http://10.0.0.10:30080/
curl -s -H "Host: shop.example.com" http://10.0.0.10:30080/api

Không tiện dùng NodePort thì dùng port-forward, miễn giữ nguyên header Host:

kubectl -n ingress-nginx port-forward svc/ingress-nginx-controller 8080:80
curl -s -H "Host: shop.example.com" http://localhost:8080/api

Bước 6: Xử lý lỗi khi test.

  • Ra 404: controller nhận request nhưng không rule nào khớp. Kiểm tra lại Host bạn gửi có đúng bằng host trong YAML, và path có khớp pathType không.
  • Ra 503: rule đã khớp, nhưng Service backend không có endpoint. Chạy kubectl -n shop get endpoints web api. Nếu ENDPOINTS là <none> thì sai targetPort, hoặc pod chưa Ready.

[!TIP] Trong đề thi, thứ tự path trong YAML không quan trọng. Nginx Ingress luôn chọn prefix dài nhất trước, nên /api thắng / mà bạn không cần sắp xếp gì.

Thực hành có hướng dẫn

Phần này là phần chính. Bạn hãy tự làm trước, chỉ mở phần giải sau khi đã thử ít nhất một lần.

Bài 1: Hai host, một cluster (4 phút)

Tạo Ingress tên multi-host trong namespace shop với:

  • web.example.com route mọi path vào Service web port 80.
  • api.example.com route mọi path vào Service api port 80.
kubectl -n shop create ingress multi-host \
  --class=nginx \
  --rule="web.example.com/*=web:80" \
  --rule="api.example.com/*=api:80" \
  --dry-run=client -o yaml | kubectl apply -f -
Xem cách kiểm tra kết quả
kubectl -n shop describe ingress multi-host
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: web.example.com" http://<NODE_IP>:30080/
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: api.example.com" http://<NODE_IP>:30080/

Cả hai phải trả 200. Nếu một cái trả 404, gần như chắc chắn bạn gõ sai host trong lệnh curl hoặc trong --rule.

Bài 2: Sửa lỗi 404 do pathType quá chặt (3 phút)

Apply manifest dưới đây, sau đó test curl -H "Host: shop.example.com" .../api/v1/users và giải thích tại sao ra 404.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: exact-demo
  namespace: shop
spec:
  ingressClassName: nginx
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /api
        pathType: Exact
        backend:
          service:
            name: api
            port:
              number: 80
Xem lời giải

pathType: Exact chỉ khớp chính xác chuỗi /api, không khớp /api/v1/users. Sửa nhanh nhất:

kubectl -n shop patch ingress exact-demo --type=json \
  -p '[{"op":"replace","path":"/spec/rules/0/http/paths/0/pathType","value":"Prefix"}]'

Test lại, phải ra 200. Đây là bẫy số một của dạng bài Ingress: đề cho path kiểu /api rồi bảo "mọi request dưới /api", bạn phải dùng Prefix.

Bài 3: Sửa lỗi 502 do sai targetPort (3 phút)

kubectl -n shop expose deploy api --port=80 --target-port=8080 --name=api-broken

Tạo Ingress trỏ vào api-broken rồi test. Bạn sẽ thấy 502 hoặc 503. Chẩn đoán theo đúng thứ tự sau:

kubectl -n shop get endpoints api-broken
Xem lời giải

ENDPOINTS sẽ là <none> vì targetPort: 8080 không khớp port container đang listen (5678). Không có endpoint thì controller không có gì để proxy tới.

kubectl -n shop patch svc api-broken -p '{"spec":{"ports":[{"port":80,"targetPort":5678}]}}'
kubectl -n shop get endpoints api-broken

Đợi vài giây, endpoint xuất hiện, test lại ra 200.

Bài học: 404 là lỗi ở tầng rule, 502/503 là lỗi ở tầng backend. Nhìn mã lỗi là biết phải đi kiểm tra phía nào.

Bài 4: Đề mini bấm giờ 6 phút

Trong namespace shop, tạo Ingress tên timebox route shop.example.com/api (và mọi path con của nó) tới Service api port 80, đồng thời route tất cả host khác vào web. Kiểm tra bằng curl và chứng minh cả hai rule đều hoạt động.

Xem gợi ý và lời giải

Gợi ý: rule không có host sẽ đóng vai trò catch-all.

kubectl -n shop create ingress timebox --class=nginx \
  --rule="shop.example.com/api*=api:80" \
  --rule="*=web:80" \
  --dry-run=client -o yaml | kubectl apply -f -

Kiểm tra:

kubectl -n shop describe ingress timebox
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: shop.example.com" http://<NODE_IP>:30080/api
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: batky.example.com" http://<NODE_IP>:30080/

Lỗi thường gặp và troubleshoot

Triệu chứng Nguyên nhân hay gặp Lệnh kiểm tra
404 từ controller Host gửi lên không khớp host trong rule, hoặc pathType: Exact quá chặt, hoặc thiếu ingressClassName kubectl -n shop describe ingress <name>
404 dù YAML đúng Rule nằm ở namespace khác với Service, hoặc controller không phải class bạn khai kubectl get ingressclass, kubectl -n shop get ingress
503 Service backend không có endpoint (sai targetPort, pod chưa Ready, sai tên Service) kubectl -n shop get endpoints <svc>
502 Controller nối được tới IP nhưng backend đóng kết nối, sai targetPort, hoặc app không nói HTTP kubectl -n shop exec deploy/web -- curl -s <cluster-ip>:80
Connection refused khi curl Sai NodePort, controller Service không tồn tại, hoặc firewall node kubectl -n ingress-nginx get svc ingress-nginx-controller
Ingress có mà describe không có Rules Rule sai cấu trúc, backend thiếu port kubectl -n shop get ingress -o yaml

Thêm một mẹo chẩn đoán: log của controller thường ghi rõ request nào bị 404.

kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=50

==Không có Ingress controller thì Ingress chỉ là file YAML vô hồn.== Luôn kiểm tra controller và ingressclass trước khi sửa manifest.

Góc chuyên gia

  • Trong đề CKA, controller thường đã cài sẵn. Việc của bạn là viết rule đúng, không phải cài lại controller. Đừng mất thời gian debug controller.
  • Dùng kubectl create ingress ... --dry-run=client -o yaml để có skeleton, sau đó edit hoặc apply. Gõ tay toàn bộ manifest Ingress rất dễ sai indent ở tầng paths.
  • Nhớ quy tắc dấu *: có * thì Prefix (và dấu * bị cắt), không có * thì Exact. ==Đây là bẫy số một của dạng bài này.==
  • Nếu đề yêu cầu backend dùng port name thay vì số, sửa thành port: { name: http } và đảm bảo Service có name: http trong spec.ports.
  • Đặt namespace mặc định cho context để khỏi gõ -n mỗi lệnh: kubectl config set-context --current --namespace=shop.
  • Test nhanh bằng một dòng, đỡ đọc output dài: curl -s -o /dev/null -w "%{http_code}\n" -H "Host: ..." http://...
  • Chỉ thêm annotation nginx.ingress.kubernetes.io/rewrite-target khi đề nói rõ yêu cầu rewrite. Thêm khi không được yêu cầu là tự tạo bug.
  • Quy tắc thời gian: task Ingress đơn giản nên xong trong 6 đến 8 phút. Quá 8 phút thì ghi chú lại, chuyển task khác, quay lại sau. Một task 4% không đáng để mất 15 phút.

Tóm tắt và bài tiếp theo

Chốt lại 4 dòng, đúng kiểu mình hay ghi ra giấy nháp:

  • Ingress = rule L7 theo host + path, cần Ingress controller mới có tác dụng.
  • apiVersion: networking.k8s.io/v1, chọn controller bằng ingressClassName, backend trỏ tới Service cùng namespace.
  • pathType chỉ có 3 giá trị: Prefix, Exact, ImplementationSpecific. Mặc định khi tạo bằng kubectl là Exact nếu path không có *.
  • 404 = rule không khớp. 502/503 = backend không có endpoint hoặc sai port.

Điền thử trước khi xem đáp án:

1. Ingress object chỉ có hiệu lực khi cluster đang chạy một {{c1::Ingress controller}}.
2. `kubectl create ingress` sinh ra `pathType` {{c2::Prefix}} khi path kết thúc bằng `*`, và {{c3::Exact}} khi path không có `*`.
3. Mã 404 từ Ingress nghĩa là request không khớp {{c4::host hoặc path trong rules}}.
4. Mã 502 hoặc 503 nghĩa là rule đã khớp nhưng {{c5::Service backend không có endpoint hoặc sai targetPort}}.
5. Lệnh kiểm tra nhanh backend có endpoint hay không là `kubectl get {{c6::endpoints}} <service>`.

Ôn lại bằng bộ flashcard dưới đây, mỗi ngày một lượt là đủ nhớ:

[
  { "front": "Ingress hoạt động ở layer nào và cho giao thức nào?", "back": "Layer 7, chỉ HTTP/HTTPS (khác Service là layer 4)." },
  { "front": "Điều kiện bắt buộc để một Ingress object có tác dụng?", "back": "Cluster phải có Ingress controller đang chạy và nhận đúng ingressClassName." },
  { "front": "apiVersion hiện tại của Ingress?", "back": "networking.k8s.io/v1 (từ Kubernetes 1.19)." },
  { "front": "Field nào chọn Ingress controller cho một Ingress?", "back": "spec.ingressClassName." },
  { "front": "Ba giá trị hợp lệ của pathType?", "back": "Prefix, Exact, ImplementationSpecific." },
  { "front": "`--rule=\"foo.com/path*=svc:80\"` tạo ra pathType gì?", "back": "Prefix, với path là /path (dấu * bị cắt)." },
  { "front": "`--rule=\"foo.com/path=svc:80\"` (không có dấu *) tạo ra pathType gì?", "back": "Exact, chỉ khớp đúng chuỗi /path." },
  { "front": "Backend của Ingress trỏ tới đối tượng nào?", "back": "Một Service cùng namespace, chỉ định port bằng number hoặc name." },
  { "front": "Ingress trả 404 thường do nguyên nhân gì?", "back": "Host hoặc path không khớp rule, pathType quá chặt, hoặc thiếu ingressClassName." },
  { "front": "Ingress trả 503 thường do nguyên nhân gì?", "back": "Service backend không có endpoint (sai targetPort, pod chưa Ready, sai tên Service)." },
  { "front": "Lệnh kiểm tra Service có endpoint hay không?", "back": "kubectl get endpoints <svc>, hoặc kubectl get endpointslices." },
  { "front": "Test Ingress bằng curl như thế nào?", "back": "Gửi header Host đúng bằng host trong rule tới địa chỉ controller: curl -H \"Host: shop.example.com\" http://<node-ip>:<nodeport>/" },
  { "front": "spec.tls của Ingress gồm những field nào?", "back": "Danh sách hosts và secretName trỏ tới Secret type kubernetes.io/tls." },
  { "front": "Khi nhiều path cùng khớp, Ingress controller chọn path nào?", "back": "Path có prefix dài nhất (longest match), không phụ thuộc thứ tự trong YAML." }
]

Bài tiếp theo: NetworkPolicy và CNI. Bạn đã biết traffic từ ngoài vào pod đi qua những đâu. Bài sau chúng ta sẽ chặn traffic đó ở tầng mạng: viết NetworkPolicy theo label, hiểu vì sao mặc định mọi pod nói chuyện được với nhau, và vì sao policy đúng mà vẫn "không ăn" khi CNI không hỗ trợ.