Ingress: Đưa HTTP Từ Ngoài Vào (Bằng Cilium)

K
Kai··7 min read

Bài 47 kiểm soát lưu lượng pod với pod trong cluster. Nhưng người dùng thật ở ngoài cluster, gõ một URL HTTP. Service kiểu ClusterIP chỉ sống trong cluster; NodePort thì lộ cổng số khó nhớ và một cổng một Service. Cái ta cần là một cửa HTTP duy nhất, nhận mọi request rồi nhìn vào hostpath để định tuyến tới đúng Service. Đó là việc của Ingress.

Chọn controller nào trong 2026

Có một chuyện ảnh hưởng trực tiếp đến lựa chọn controller. Tháng 11/2025, SIG Network thông báo khai tử Ingress NGINX — controller phổ biến nhất, chạy ở khoảng một nửa số cluster trên thế giới. Từ tháng 3/2026, dự án dừng bảo trì: không release, không bugfix, không vá lỗ hổng bảo mật. Bài này viết tháng 5/2026, tức là Ingress NGINX đã hết vòng đời, nên cài nó cho một hệ thống mới bây giờ là chọn sai ngay từ đầu.

Bên cạnh đó, tài liệu kubernetes.io ghi rằng Ingress API đã bị đóng băng (frozen): vẫn GA, vẫn được hỗ trợ, nhưng không thêm tính năng nữa, và Gateway API (Bài 49) là kế nhiệm. Đóng băng không có nghĩa là biến mất — Ingress vẫn còn rất nhiều trong cluster sản xuất 2026, nên vẫn cần biết nó chạy ra sao. Với cụm này, cách gọn nhất là dùng Ingress controller có sẵn trong Cilium, CNI đã chạy từ Bài 46: không phải cài controller thứ ba, không dính phần mềm hết vòng đời, và Cilium dịch Ingress xuống Envoy + eBPF nên nối tiếp được Bài 45.

Ingress cần một controller mới chạy

Doc nhấn mạnh một điểm hay bị bỏ qua: tạo một object Ingress không tự làm gì cả. Phải có một Ingress controller đang chạy để đọc các object Ingress và cấu hình proxy thật. Kubernetes không bật controller nào mặc định, ta phải tự chọn. Sơ đồ đường đi:

   client (ngoài)
       │  HTTP, Host: foo.kkloud.local
       ▼
   NodePort 32080 trên worker  ──(eBPF, Bài 46)──►  Envoy (cilium-envoy)
                                                        │  đọc rule định tuyến
                                                        ▼
   ┌─────────────── Ingress controller (trong cilium-operator) ───────────────┐
   │  watch object Ingress  →  DỊCH thành CiliumEnvoyConfig  →  agent nạp Envoy │
   └───────────────────────────────────────────────────────────────────────────┘
                                                        │  host/path khớp
                                          ┌─────────────┴─────────────┐
                                          ▼                           ▼
                                   Service foo (ClusterIP)     Service bar (ClusterIP)
                                          ▼                           ▼
                                      pod foo                      pod bar

Ingress là object namespaced, chọn controller qua trường ingressClassName (annotation kubernetes.io/ingress.class cũ đã deprecated). Mỗi controller đăng ký một IngressClass; nên đặt một class làm mặc định.

Bước 1 — bật Ingress controller của Cilium

Cilium đã cài bằng Helm ở Bài 46. Bật ingress controller = helm upgrade giữ nguyên value cũ (--reuse-values) và thêm vài cờ. Vì cụm không có cloud load balancer, ta phơi cửa qua NodePort (shared mode — một Service cilium-ingress chung cho mọi Ingress):

helm upgrade cilium cilium/cilium --version 1.19.4 --namespace kube-system --reuse-values \
  --set ingressController.enabled=true \
  --set ingressController.default=true \
  --set ingressController.loadbalancerMode=shared \
  --set ingressController.service.type=NodePort \
  --set ingressController.service.insecureNodePort=32080 \
  --set ingressController.service.secureNodePort=32443

Cilium ingress yêu cầu kubeProxyReplacement=true (đã có từ Bài 46) và L7 proxy Envoy (cilium-envoy DaemonSet đã chạy). Một chỗ dễ vấp khi nâng cấp: helm upgrade chỉ sửa ConfigMap, không tự khởi động lại pod Cilium. Agent và operator vẫn chạy config cũ — cilium-dbg báo enable-ingress-controller actual=false dù ConfigMap đã true. Phải restart cả hai để chúng nạp lại:

kubectl -n kube-system rollout restart deploy/cilium-operator   # tạo CRD CiliumEnvoyConfig + watch Ingress
kubectl -n kube-system rollout restart ds/cilium                # agent nạp listener Envoy

Sau đó IngressClass và cửa NodePort xuất hiện:

kubectl get ingressclass
kubectl -n kube-system get svc cilium-ingress
NAME               CONTROLLER                     PARAMETERS   AGE
cilium (default)   cilium.io/ingress-controller   <none>       6s

NAME             TYPE       CLUSTER-IP    PORT(S)                      AGE
cilium-ingress   NodePort   10.32.0.191   80:32080/TCP,443:32443/TCP   8s

Bước 2 — hai Service backend

Dựng hai app trả về chuỗi nhận dạng được (image http-echo trả cùng một câu cho mọi path — gọn để thấy Service nào đã đáp):

# foo: "Xin chao tu FOO service" ; bar: "Xin chao tu BAR service" — mỗi app 1 Deployment + 1 Service :80
kubectl create namespace ing-demo
# (manifest đầy đủ trong repo: foo/bar Deployment image hashicorp/http-echo + Service ClusterIP 80→8080)
kubectl -n ing-demo get svc
NAME   TYPE        CLUSTER-IP    PORT(S)   AGE
foo    ClusterIP   10.32.0.54    80/TCP    5m
bar    ClusterIP   10.32.0.63    80/TCP    5m

Bước 3 — Ingress: định tuyến theo host và path

Một object Ingress, gộp cả hai kiểu định tuyến doc mô tả. apps.kkloud.local fanout theo path (/foo→foo, /bar→bar); còn foo.kkloud.local/bar.kkloud.localvirtual host theo host (cùng path /, khác host → khác Service):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-ingress
  namespace: ing-demo
spec:
  ingressClassName: cilium
  rules:
  - host: apps.kkloud.local          # fanout theo path
    http:
      paths:
      - {path: /foo, pathType: Prefix, backend: {service: {name: foo, port: {number: 80}}}}
      - {path: /bar, pathType: Prefix, backend: {service: {name: bar, port: {number: 80}}}}
  - host: foo.kkloud.local            # virtual host theo host
    http:
      paths:
      - {path: /, pathType: Prefix, backend: {service: {name: foo, port: {number: 80}}}}
  - host: bar.kkloud.local
    http:
      paths:
      - {path: /, pathType: Prefix, backend: {service: {name: bar, port: {number: 80}}}}

pathType có ba kiểu: Prefix (khớp tiền tố — /foo khớp /foo, /foo/, /foo/x), Exact (khớp đúng chuỗi), và ImplementationSpecific (tùy controller). Áp xong, Cilium dịch Ingress này thành một CiliumEnvoyConfig — đây là mắt xích nối với Bài 45: Ingress là khai báo mức Kubernetes, còn thứ thật sự chạy là listener Envoy do eBPF dẫn lưu lượng vào:

kubectl -n kube-system get cec cilium-ingress          # CiliumEnvoyConfig Cilium tự sinh
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg envoy admin listeners
NAME             AGE
cilium-ingress   2m28s
...
kube-system/cilium-ingress/listener::127.0.0.1:14423   # listener Envoy cho ingress

Bước 4 — thử định tuyến

NodePort của Cilium không nghe trên loopback (eBPF gắn vào card mạng ens5, không phải lo), nên curl bằng IP của node chứ đừng localhost. Từ một worker, gửi request với Host header khác nhau:

IP=10.0.1.20                          # IP nội bộ worker-0
curl -s -H "Host: apps.kkloud.local" $IP:32080/foo     # path /foo
curl -s -H "Host: apps.kkloud.local" $IP:32080/bar     # path /bar
curl -s -H "Host: foo.kkloud.local"  $IP:32080/        # host foo
curl -s -H "Host: bar.kkloud.local"  $IP:32080/        # host bar
curl -s -o /dev/null -w "HTTP %{http_code}\n" -H "Host: nope.kkloud.local" $IP:32080/   # host lạ
curl -s -o /dev/null -w "HTTP %{http_code}\n" -H "Host: apps.kkloud.local" $IP:32080/baz # path lạ
Xin chao tu FOO service        # /foo  -> Service foo
Xin chao tu BAR service        # /bar  -> Service bar
Xin chao tu FOO service        # host foo.kkloud.local -> foo
Xin chao tu BAR service        # host bar.kkloud.local -> bar
HTTP 404                        # host không khớp rule nào
HTTP 404                        # path không khớp rule nào

Một cửa duy nhất (cổng 32080), bốn đường đi khác nhau, phân giải hoàn toàn ở tầng HTTP theo host + path. Hai request cuối trả 404 vì không khớp rule nào và Ingress này không khai defaultBackend (nếu có, mọi request lạc sẽ rơi vào đó thay vì 404). Ta dùng Host: header giả vì chưa trỏ DNS thật — ngoài đời, các host này là bản ghi A trỏ về IP node/load balancer.

🧹 Dọn dẹp

kubectl delete namespace ing-demo        # xóa foo, bar, demo-ingress

Xóa workload demo. Giữ Ingress controller của Cilium (ingressController.enabled=true) và Service cilium-ingress — đó là một cờ cấu hình trên CNI sẵn có, không phải workload riêng, và Bài 49 đi tiếp mạch service mesh của Cilium. Manifest ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 48-ingress.

Tổng kết

Ingress đưa HTTP từ ngoài vào, định tuyến theo host và path tới Service — nhưng chỉ khi có một Ingress controller chạy (tạo object Ingress suông không có tác dụng). Ta đã bật controller có sẵn của Cilium qua helm upgrade --reuse-values (nhớ restart operator + agent vì upgrade chỉ sửa ConfigMap), phơi qua NodePort cilium-ingress 32080 vì cụm baremetal không có cloud LB, rồi test thật cả hai kiểu định tuyến: fanout theo path (/foo→foo, /bar→bar dưới cùng host) và virtual host theo host (foo./bar.kkloud.local→Service tương ứng), với host/path lạc trả 404. Điểm nối quan trọng với Bài 45–46: Cilium dịch một Ingress thành CiliumEnvoyConfig rồi nạp thành listener Envoy, lưu lượng vào qua NodePort do eBPF dẫn — không có một controller proxy tách rời. Và quyết định nền: vì Ingress NGINX đã khai tử (3/2026) và Ingress API đã đóng băng, ta tránh phần mềm hết vòng đời, dùng controller đang được bảo trì.

Ingress đóng băng nghĩa là những thứ phức tạp hơn — định tuyến theo header, traffic splitting theo phần trăm, tách vai trò hạ tầng/ứng dụng — không bao giờ vào được API này. Bài 49 chuyển sang kẻ kế nhiệm chính thức: Gateway API, cũng chạy trên Cilium, được thiết kế lại từ đầu cho đúng những nhu cầu đó.

Related Posts