Sau khi rời ingress-nginx, bài toán lớn nhất của Gateway API trên một cluster nhiều website là chứng chỉ: nhiều site cùng ghi vào một Gateway thì ghi đè nhau. NGINX Gateway Fabric cùng ListenerSet giải quyết đúng chỗ đó. Bài này mô tả thiết kế một Gateway, một load balancer cho mọi website trên LKE, mỗi site tự khai hostname và chứng chỉ bằng ListenerSet, cert-manager 1.21 cấp chứng chỉ qua listener HTTP dùng chung, cùng các lỗi hay gặp khi triển khai. Đây là bài kết Series 2, nên phần cuối nhìn lại những gì series đã đi qua.
1. Vì sao NGINX Gateway Fabric và ListenerSet
Bài trước, ingress-nginx nghỉ hưu: pilot Traefik, Gateway API và rollback, dừng ở vấn đề certificateRefs: danh sách chứng chỉ của một listener là trường atomic, nhiều site cùng ghi vào một Gateway thì ghi đè nhau.
ListenerSet (GEP-1713 trong Gateway API) cho phép một namespace khai thêm listener cho một Gateway có sẵn mà không sửa Gateway đó. Mỗi site có một ListenerSet trong namespace của nó, chứa hostname và Secret TLS cùng namespace. Không còn ai cùng ghi một danh sách, cũng không cần ReferenceGrant để Gateway đọc Secret ở namespace khác.
NGINX Gateway Fabric có lõi là nginx, gần với kinh nghiệm của đội đã vận hành Nginx trên VPS nhiều năm. Các phiên bản cần khớp nhau:
| Thành phần | Phiên bản | Lý do |
|---|---|---|
| NGINX Gateway Fabric | 2.8 trở lên | Có ListenerSet, yêu cầu Kubernetes từ 1.32 |
| CRD Gateway API | 1.6.3, kênh standard | Cần CRD listenersets.gateway.networking.k8s.io |
| cert-manager | 1.21 trở lên | Hỗ trợ ListenerSet từ 1.20, annotation http01-parentreffallback từ 1.21 |
Trong cert-manager, ListenerSet vẫn nằm sau feature gate ListenerSets và mặc định tắt. Phải bật cả config.gatewayAPI.enableListenerSet lẫn config.featureGates.ListenerSets khi cài, theo hướng dẫn cert-manager: Annotated Gateway resource.
2. Kiến trúc NGINX Gateway Fabric: một Gateway, một load balancer
Internet
→ NodeBalancer (TCP 80 + TCP 443, không terminate TLS)
→ Gateway public-gateway (namespace nginx-gateway)
:80 HTTP dùng chung: redirect và challenge ACME HTTP-01
:443 HTTPS placeholder (placeholder.invalid)
→ ListenerSet của từng site (namespace site): HTTPS :443, hostname + Secret TLS
→ HTTPRoute → Service → PodTLS được terminate tại NGINX Gateway Fabric. Đội hạ tầng giữ Gateway, ClusterIssuer và Secret placeholder, tạo một lần cho cả cluster. Chart web tạo ListenerSet, HTTPRoute, Deployment, Service trong namespace của site. Thêm hay gỡ một site không đụng vào Gateway.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public-gateway
namespace: nginx-gateway
spec:
gatewayClassName: nginx
infrastructure:
annotations:
service.beta.kubernetes.io/linode-loadbalancer-default-protocol: tcp
allowedListeners:
namespaces: {from: All} # ListenerSet ở namespace site gắn được
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes: {namespaces: {from: All}}
- name: https
protocol: HTTPS
port: 443
hostname: placeholder.invalid # chỉ để load balancer mở cổng 443
tls:
mode: Terminate
certificateRefs: [{kind: Secret, name: placeholder-tls}]
allowedRoutes: {namespaces: {from: Same}}Listener :443 với hostname .invalid là mẹo cần thiết trên LKE: NodeBalancer lấy danh sách cổng từ listener khai trên chính Gateway. Nếu Gateway chỉ có :80, Service và NodeBalancer chỉ mở cổng 80, HTTPS không vào được dù ListenerSet của site có listener 443. Hostname .invalid bảo đảm placeholder không bao giờ trùng domain thật khi chọn chứng chỉ theo SNI.
3. Mỗi site trên NGINX Gateway Fabric: ListenerSet, ba HTTPRoute và hai policy
apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
name: site-a-example
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
acme.cert-manager.io/http01-parentreffallback: "true"
spec:
parentRef: {name: public-gateway, namespace: nginx-gateway, kind: Gateway}
listeners:
- name: https
hostname: "site-a.example"
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs: [{kind: Secret, name: site-a-example-tls}]
- name: https-www # chỉ có khi bật wwwRedirect
hostname: "www.site-a.example"
...cert-manager đọc annotation trên ListenerSet và tự tạo Certificate trong cùng namespace, NGINX Gateway Fabric nạp Secret khi nó sẵn sàng. ListenerSet chỉ có listener HTTPS, nên annotation http01-parentreffallback cho phép challenge HTTP-01 đi qua listener :80 của Gateway. Thiếu annotation này, challenge báo NoReadyListeners.
Route tách thành ba HTTPRoute: route chính đưa HTTPS của domain tới Service, route https-redirect chuyển HTTP sang HTTPS (chỉ khi bật TLS), route www-redirect chuyển www về domain chính. Site không bật TLS gắn route thẳng vào listener HTTP của Gateway, không có ListenerSet, nên không gặp lỗi 404 do redirect toàn cục. Route challenge /.well-known/acme-challenge/ loại Exact được ưu tiên hơn redirect PathPrefix /, nên gia hạn chứng chỉ vẫn chạy khi đã bật redirect.
Các annotation ingress-nginx còn lại được thay bằng policy riêng của NGINX Gateway Fabric gắn vào route chính: ClientSettingsPolicy giới hạn body (ví dụ 128m), ProxySettingsPolicy đặt timeout connect, read, send. Lưu ý thực tế: route chính nên chỉ có một rule, vì nhiều rule có thể làm NGINX Gateway Fabric sinh trùng chỉ thị client_max_body_size. So với thiết kế dùng script gom chứng chỉ, chart bỏ hẳn template Certificate, Middleware, ReferenceGrant và CronJob đồng bộ.
4. Lỗi hay gặp khi triển khai NGINX Gateway Fabric
- Helm chỉ cài control plane. Data plane của NGINX Gateway Fabric (pod nginx và Service
LoadBalancer) chỉ xuất hiện khi có object Gateway. Mỗi Gateway sinh một load balancer riêng. Muốn nhiều site chung một IP thì chỉ được có đúng một Gateway. - Listener mặc định chỉ nhận route cùng namespace. HTTPRoute ở namespace khác báo
The Route is not allowed by any listenercho tới khi listener cóallowedRoutes.namespaces.from: All. - Giữ IP bằng cách patch Service cũ vẫn chạy, với điều kiện Gateway nằm cùng namespace với Service ingress-nginx cũ, vì Service chỉ chọn được pod cùng namespace. Khác namespace thì có 0 endpoint.
- Dùng annotation của Linode để giữ IP.
linode-loadbalancer-preserve: "true"giữ lại NodeBalancer khi Service bị xoá.linode-loadbalancer-nodebalancer-idgắn một Service mới vào NodeBalancer có sẵn và nhận lại đúng IP cũ. - Đổi kiểu Service không phải cách gỡ load balancer. Patch Service của Gateway từ
LoadBalancersangClusterIPthì NGINX Gateway Fabric lập tức đặt lạiLoadBalancer, Cloud Controller Manager cấp một NodeBalancer mới, còn cái cũ bị bỏ lại và vẫn tính phí. Sau mỗi buổi thử, đối chiếu danh sách load balancer trên tài khoản với Service trong cluster. - Gỡ và cài lại cert-manager an toàn cho dữ liệu nếu CRD được giữ bằng resource policy
keep: Certificate và Secret còn nguyên, controller mới tiếp tục quản lý mà không cấp lại.
5. Checklist trước khi đưa NGINX Gateway Fabric lên production
- Test luồng cấp chứng chỉ thật cho một domain thật qua ListenerSet và cert-manager, ghi lại kết quả để chạy lại được.
- Lên kế hoạch chạy song song. NGINX Gateway Fabric không xử lý object Ingress, nên giai đoạn chuyển tiếp phải chạy ingress-nginx và NGINX Gateway Fabric như hai controller riêng, chuyển hostname theo từng đợt.
- Có template cho mọi loại site, kể cả site chỉ redirect nguyên domain.
- Không để secret trong values mặc định của chart. Giá trị nào đã từng nằm trong values thì coi như đã lộ, rotate rồi chuyển sang Secret.
- Đồng bộ phiên bản CRD Gateway API với yêu cầu của controller, gỡ CRD của controller đã thử trước đó.
- Dọn hạ tầng thử nghiệm: cluster test, load balancer bị bỏ lại.
6. Nhìn lại Series 2
Series đi theo đúng thứ tự một đội PHP chuyển hàng trăm website Laravel lên Kubernetes:
| Giai đoạn | Nội dung | Bài đại diện |
|---|---|---|
| Nền tảng | Cluster đầu tiên, ingress, cert-manager, biến môi trường trong container | Dựng cluster LKE đầu tiên: node pool, kubeconfig và lựa chọn ban đầu |
| CI/CD | Giám sát, requests và limits, pipeline deploy đầu tiên | CI/CD đầu tiên với Bitbucket Pipelines, Docker Hub và kubectl |
| Đóng gói | Helm chart, cầu nối sang GitLab CI, chart dùng chung | Helm chart là gì: đóng gói website Laravel để deploy lặp lại |
| Chuyển nhà | Database ngoài cluster, NFS, migrate hàng loạt, backup | Migrate website lên Kubernetes: quy trình chuyển hàng loạt an toàn |
| Mở rộng | Pod giữ chỗ, cluster thứ hai, stack giám sát mới | kube-prometheus-stack: thay monitoring cũ và cấu hình sai âm thầm |
| Vận hành và đổi controller | Jenkins làm form cho GitLab CI, Gateway API | Jenkins và GitLab CI: vận hành Website AI trên Kubernetes |
Ba điều đúng suốt series: thứ chạy được thì phải lặp lại được, nhờ chart dùng chung và pipeline; dữ liệu đặt ngoài cluster giúp mọi cuộc chuyển nhà nhẹ đi; còn những gì "cài một lần rồi để đó", như ingress controller hay cert-manager không nâng cấp, sớm muộn cũng thành món nợ phải trả cùng lúc. Lên lịch nâng cấp các thành phần hạ tầng của cluster như một việc định kỳ, đừng chờ tới khi dự án ngừng vá.
Phía Cloud VPS cũng có bài nhìn lại riêng: Nhìn lại mô hình Cloud VPS: điểm yếu và phương án gộp VPS. Mọi bài của các series nằm trong chuyên mục blog kỹ thuật của Sudo.
Kết series
Cảm ơn bạn đã theo dõi 30 bài về Kubernetes và CI/CD trên Linode. Con đường không thẳng: có quyết định đúng từ sớm, có thiết kế sai mà mãi sau mới thấy, có pilot phải gỡ. Nếu bạn đang chuẩn bị rời ingress-nginx sang NGINX Gateway Fabric hay đưa nhiều website Laravel lên Kubernetes, hy vọng các lệnh, cấu hình mẫu và sai lầm trong series giúp bạn đi bớt đường vòng.
Phần còn lại là vận hành: sự cố, cách điều tra và những thứ phải bảo trì định kỳ. Mời bạn đọc tiếp series Vận hành thực chiến, bắt đầu từ bài Điều tra sự cố nhiều tầng: từ balancer tới database và Kubernetes.
