Service cho một IP cho một dịch vụ. Ingress cho một điểm vào cho nhiều dịch vụ, phân biệt bằng tên miền hoặc đường dẫn. Bài này dựng nó và đo cái giá.
Một địa chỉ, nhiều dịch vụ
rules:
- host: a.thu.local
http: {paths: [{path: /, pathType: Prefix,
backend: {service: {name: a, port: {number: 80}}}}]}
- host: b.thu.local
http: {paths: [{path: /, pathType: Prefix,
backend: {service: {name: b, port: {number: 80}}}}]}
Cùng một ClusterIP 10.96.2.214:
Host: a.thu.local -> dich-vu-a
Host: b.thu.local -> dich-vu-b
Khác biệt cốt lõi so với Service: Ingress hoạt động ở tầng HTTP. Nó đọc header Host và đường dẫn — thứ mà iptables (phần 56) không nhìn thấy, vì iptables chỉ làm việc với IP và cổng.
Đó là lý do một địa chỉ IP phục vụ được hàng trăm tên miền, và cũng là lý do Ingress không dùng được cho giao thức không phải HTTP.
Định tuyến theo đường dẫn: 404 nếu không viết thêm
paths:
- {path: /duong-a, pathType: Prefix, backend: {service: {name: a, ...}}}
GET /duong-a/ -> mã trả về 404
nginx nhận được: "GET /duong-a/ HTTP/1.1"
Ingress chuyển nguyên đường dẫn xuống backend. Ứng dụng phía sau không có /duong-a nên trả 404 — và điều đó hoàn toàn đúng theo đặc tả.
Đây là chỗ vấp phổ biến nhất khi mới dùng Ingress: định tuyến theo tên miền chạy ngay, còn định tuyến theo đường dẫn thì 404 và trông như cấu hình sai.
Chữa bằng annotation của ingress-nginx:
metadata:
annotations: {nginx.ingress.kubernetes.io/rewrite-target: /$2}
spec:
rules:
- http:
paths:
- path: /rw-a(/|$)(.*)
pathType: ImplementationSpecific
backend: {service: {name: a, port: {number: 80}}}
/rw-a/ -> dich-vu-a
Chú ý pathType: ImplementationSpecific — cần nó để dùng biểu thức chính quy. Và rewrite-target là annotation riêng của ingress-nginx, không phải chuẩn Kubernetes; bộ điều khiển khác có cú pháp khác.
Đây là điểm yếu lớn nhất của Ingress: phần chuẩn hoá chỉ đủ cho những trường hợp đơn giản nhất, còn mọi thứ hữu ích đều nằm trong annotation riêng của từng bộ điều khiển. Đổi từ ingress-nginx sang Traefik là viết lại toàn bộ annotation.
Đó cũng là lý do Gateway API ra đời — nó đưa những thứ này vào chính đặc tả thay vì để trong annotation.
Độ trễ thêm vào: không đo được
gọi thẳng Service 0,29 ms (200/200 OK)
qua Ingress 0,28 ms (200/200 OK)
Thêm một chặng proxy HTTP đầy đủ mà không tốn gì đo được.
Kết quả này phù hợp với việc nginx là một proxy rất nhanh và cụm chạy trên một máy. Trên cụm thật, chặng thêm có thể qua mạng giữa hai node, và khi ấy nó tốn đúng bằng độ trễ mạng đó — không phải bằng công của proxy.
Chi phí thật của Ingress nằm ở chỗ khác: một tiến trình nữa phải vận hành, cấu hình, nâng cấp và theo dõi. Và nó là điểm hỏng chung cho mọi dịch vụ đi qua nó — bộ điều khiển Ingress chết là mọi thứ ngoài cụm không vào được, dù pod vẫn khoẻ.
Cài đặt: hai phút nằm Pending vì thiếu một nhãn
0/4 nodes are available: 4 node(s) didn't match Pod's node affinity/selector
nodeSelector: {ingress-ready: "true"}
Bản kê dành cho kind đòi node phải có nhãn ingress-ready=true. Gán nhãn xong thì controller lên sau 116 giây.
Thông báo lỗi nói đúng nguyên nhân — như phần 47 đã nêu, FailedScheduling luôn liệt kê từng nhóm node trượt vì sao. Chỉ cần chịu đọc nó thay vì đoán.
IngressClass
spec:
ingressClassName: nginx
Một cụm chạy được nhiều bộ điều khiển Ingress cùng lúc, và ingressClassName quyết định cái nào nhận. Thiếu nó thì Ingress có thể không bộ điều khiển nào nhận — và nó nằm im, không lỗi, không cảnh báo.
kubectl get ingressclass
Đây là nguyên nhân số một của "tôi tạo Ingress rồi mà không có gì xảy ra".
TLS
spec:
tls:
- hosts: [a.thu.local]
secretName: a-tls
Secret phải cùng namespace với Ingress, và phải có tls.crt cùng tls.key. Với chứng chỉ tự động, cert-manager tạo Secret đó và gia hạn giúp.
Kết thúc TLS xảy ra tại bộ điều khiển Ingress, nên lưu lượng từ đó tới pod là HTTP thường. Trong mạng cụm tin cậy thì đó là đánh đổi hợp lý; nếu cần mã hoá cả chặng trong thì đó là việc của lưới dịch vụ.
Ingress hay Gateway API
Ingress đã ổn định từ lâu, được mọi bộ điều khiển hỗ trợ, và đủ cho phần lớn nhu cầu: định tuyến HTTP theo tên miền và đường dẫn, kết thúc TLS.
Gateway API là thế hệ sau, giải đúng ba vấn đề của Ingress: chuẩn hoá những thứ hiện nằm trong annotation, tách vai trò giữa người quản trị hạ tầng và người viết ứng dụng, và hỗ trợ giao thức không phải HTTP.
Với dự án mới trên cụm hỗ trợ nó, Gateway API là lựa chọn đúng. Với hệ thống đang chạy, Ingress không có gì cấp bách phải đổi.
Thử ba mươi giây
Kiểm Ingress của bạn có được bộ điều khiển nào nhận không:
kubectl get ingress -A -o custom-columns=\
NS:.metadata.namespace,TEN:.metadata.name,\
CLASS:.spec.ingressClassName,DIA_CHI:.status.loadBalancer.ingress[0].ip
Cột DIA_CHI rỗng nghĩa là chưa bộ điều khiển nào xử lý — thường vì CLASS sai hoặc rỗng. Ingress đó tồn tại trong etcd và không làm gì cả.
Phần sau đo NetworkPolicy: mặc định mọi pod gọi được mọi pod, và cách chặn lại.