Ở các bài trước, pod đã có IP, Service đã gom nhiều pod về một địa chỉ ổn định. Nhưng địa chỉ đó chỉ sống bên trong cluster. Trình duyệt của khách truy cập website thì đứng ở Internet. Bài này trả lời câu hỏi còn lại trước khi dựng cluster cho Website AI: request từ Internet đi vào pod bằng đường nào, NodeBalancer của Linode đóng vai trò gì, và vì sao Ingress là cách hợp lý để nhiều domain dùng chung một cổng vào. Phần cuối nói về externalTrafficPolicy, một thiết lập nhỏ nhưng quyết định ứng dụng có thấy được IP thật của người dùng hay không.

Trên Cloud VPS, việc này do tầng balancer đảm nhận, như trong bài tầng Balancer với Nginx reverse proxy. Kubernetes chia việc đó thành nhiều mảnh nhỏ hơn, và hiểu từng mảnh thì mới tránh được hoá đơn bất ngờ.

1. Ba kiểu Service và đường đi từ Internet

Bài Pod, Deployment, Service và Namespace đã giới thiệu Service kiểu ClusterIP. Kubernetes còn hai kiểu nữa liên quan trực tiếp tới traffic từ bên ngoài:

Kiểu ServiceAi truy cập đượcCách hoạt động
ClusterIP (mặc định)Chỉ pod trong clusterMột IP ảo nội bộ, kube-proxy chuyển tiếp tới các pod khớp selector
NodePortAi gọi được IP của nodeMở cùng một cổng (dải mặc định 30000–32767) trên mọi node, rồi chuyển vào Service
LoadBalancerInternetNhư NodePort, thêm một bộ cân bằng tải bên ngoài do nhà cung cấp cloud tạo, trỏ vào cổng NodePort của các node

Ba kiểu này lồng vào nhau: LoadBalancer có sẵn một NodePort, NodePort có sẵn một ClusterIP. Dùng NodePort trực tiếp cho website thì không ổn: phải gõ cổng 3xxxx, IP node đổi khi node bị thay, không ai kiểm tra node nào còn sống. Vì vậy với traffic thật, câu trả lời gần như luôn là LoadBalancer, và trên Linode, đó là NodeBalancer.

2. NodeBalancer: Service LoadBalancer trên Linode

NodeBalancer là dịch vụ cân bằng tải có sẵn của Linode: một IP public, nhận kết nối và chia về nhiều máy phía sau, có health check. Trên LKE, bạn không phải tự tạo NodeBalancer. Thành phần Linode Cloud Controller Manager (CCM) chạy sẵn trong control plane, theo dõi các Service kiểu LoadBalancer và tự làm phần việc với Linode:

  1. Thấy một Service type: LoadBalancer mới, CCM gọi Linode API tạo một NodeBalancer.
  2. Mỗi cổng của Service thành một cấu hình trên NodeBalancer. Backend là IP private của từng node kèm cổng NodePort.
  3. IP public của NodeBalancer được ghi vào status.loadBalancer của Service, hiện ở cột EXTERNAL-IP.
  4. Khi node được thêm, bớt hoặc thay mới, CCM cập nhật danh sách backend. Khi Service bị xoá, CCM xoá luôn NodeBalancer.
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: site-a-example-com
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
  - name: http
    port: 80
    targetPort: 80

Ngay sau khi tạo, cột EXTERNAL-IP của kubectl get svc hiện <pending>, rồi chuyển thành IP của NodeBalancer khi CCM tạo xong.

Hành vi của NodeBalancer (TCP hay HTTP, health check, Proxy Protocol…) được chỉnh bằng annotation tiền tố service.beta.kubernetes.io/linode-loadbalancer- trên Service. Danh sách đầy đủ nằm trong README của CCM và trong tài liệu NodeBalancer của Linode.

Có hai hệ quả cần nhớ ngay từ đầu:

  • Mỗi Service LoadBalancer là một NodeBalancer, và mỗi NodeBalancer tính phí riêng hằng tháng. Mười website, mỗi site một Service LoadBalancer, là mười NodeBalancer và mười IP public.
  • IP đi theo đối tượng Service. Xoá Service, hoặc gỡ một chart có tạo Service đó, thì NodeBalancer bị xoá và IP trả lại cho Linode. Mọi bản ghi DNS trỏ vào IP cũ sẽ chết.

3. Ingress là gì và ingress controller làm gì

Hệ quả thứ nhất là lý do Ingress tồn tại. Ingress là một đối tượng Kubernetes mô tả luật định tuyến HTTP: host nào, đường dẫn nào thì chuyển vào Service nào. Bản thân đối tượng Ingress không xử lý request nào cả. Nó chỉ là cấu hình. Phần chạy thật là ingress controller: một (hoặc vài) pod reverse proxy, đọc tất cả Ingress trong cluster rồi tự sinh cấu hình cho chính nó.

LKE không cài sẵn ingress controller. Người dùng tự chọn và tự cài. Phổ biến nhất là ingress-nginx (dự án của cộng đồng Kubernetes, dùng nginx bên trong) và Traefik. Với một team đã quen nginx trên Cloud VPS, ingress-nginx là lựa chọn tự nhiên. Tài liệu chính thức về đối tượng này nằm ở trang Ingress trên kubernetes.io.

          Internet
             │  :80 / :443
             ▼
   ┌─────────────────────────┐
   │ NodeBalancer (IP public)│   do CCM tạo cho MỘT Service LoadBalancer
   └────────────┬────────────┘
                │  IP private của node : NodePort
                ▼
   ┌─────────────────────────┐
   │ pod ingress controller  │   đọc mọi Ingress, chọn đích theo Host + path
   └──────┬───────────┬──────┘
          │           │   Service ClusterIP của từng site
          ▼           ▼
   ┌───────────┐ ┌───────────┐
   │ pod site A│ │ pod site B│   site-a.example, example.com
   └───────────┘ └───────────┘

Một Ingress cho hai domain trỏ về hai Service khác nhau trông như sau. Mỗi website vẫn chỉ cần Service ClusterIP:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: site-a-example-com
spec:
  ingressClassName: nginx        # controller nào xử lý Ingress này
  rules:
  - host: site-a.example
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web            # Service ClusterIP của site A
            port:
              number: 80

Trường ingressClassName trỏ tới một IngressClass, cho biết controller nào chịu trách nhiệm. Annotation cũ kubernetes.io/ingress.class còn gặp trong nhiều hướng dẫn, nhưng đã được thay bằng trường này từ Kubernetes 1.18.

4. Một NodeBalancer cho nhiều domain qua Ingress

Ghép hai mảnh lại: chỉ Service của ingress controller mang type: LoadBalancer, nên cả cluster chỉ có một NodeBalancer và một IP public. Mọi domain trỏ bản ghi A về IP đó, controller đọc header Host và chuyển vào đúng site. Thêm website mới chỉ là thêm một Ingress.

Về kiến trúc, đây là tầng balancer của Cloud VPS dời vào trong cluster: NodeBalancer giữ IP public, Ingress controller làm việc của nginx reverse proxy (định tuyến theo domain, kết thúc TLS, redirect HTTPS). Chỉ khác ở chỗ cấu hình không còn là file trong /etc/nginx/conf.d/ mà là đối tượng Ingress nằm cạnh Deployment của từng site.

Cách làmSố NodeBalancerƯu điểmNhược điểm
Mỗi site một Service LoadBalancerBằng số siteCác site độc lập hoàn toàn ở tầng mạngTốn phí theo số site, mỗi site một IP, TLS phải tự lo trong pod
Một ingress controller cho tất cả1Rẻ, một IP, TLS và luật chung đặt ở một chỗController thành cổng chung: lỗi cấu hình hoặc quá tải ảnh hưởng mọi site

Nhược điểm ở dòng thứ hai không mới: balancer của Cloud VPS cũng là điểm lỗi chung. Khác biệt là controller có thể chạy nhiều replica trên nhiều node.

5. externalTrafficPolicy: Cluster hay Local

Service LoadBalancer và NodePort có trường externalTrafficPolicy, quyết định node nhận traffic xử lý gói tin ra sao khi pod đích không nằm trên chính node đó.

Cluster (mặc định)Local
Node không có pod đíchVẫn nhận, chuyển tiếp sang node khác có podKhông nhận. Health check của load balancer thấy node đó "down" và bỏ qua
Địa chỉ nguồn pod nhìn thấyBị SNAT thành IP của node chuyển tiếpGiữ nguyên địa chỉ nguồn của gói tin tới node
Chia tảiĐều giữa các pod, có thể thêm một bước nhảy mạngChỉ các node có pod nhận traffic, dễ lệch tải nếu số pod mỗi node không đều

Kubernetes giải thích rất rõ cơ chế này trong bài Using Source IP. Với Cluster, kube-proxy phải đổi địa chỉ nguồn để gói trả lời quay về đúng đường. Cái giá là pod không còn biết ai đã gửi request.

Vì sao ứng dụng sau Ingress chỉ thấy IP nội bộ

Trên LKE, IP thật của người dùng có thể mất ở hai lớp, và nhiều người chỉ biết một:

  1. Lớp NodeBalancer. NodeBalancer là một proxy: nó nhận kết nối từ người dùng rồi mở kết nối mới tới node. Ở chế độ TCP, node nhìn thấy địa chỉ của NodeBalancer, không phải của người dùng. Muốn chuyển IP thật qua lớp này, NodeBalancer phải bật Proxy Protocol (qua annotation của CCM), và ingress controller phải bật đọc Proxy Protocol tương ứng.
  2. Lớp kube-proxy. Với externalTrafficPolicy: Cluster, gói tin tới node không có pod controller sẽ bị SNAT khi chuyển sang node khác.

Hệ quả không chỉ là log đầy IP nội bộ: $request->ip() trong Laravel, rate limit theo IP, chặn IP xấu, thống kê theo vùng đều sai theo. Chuyển externalTrafficPolicy sang Local chỉ gỡ được lớp thứ hai. Lớp thứ nhất vẫn còn nguyên nếu không làm Proxy Protocol.

Proxy Protocol cũng có bẫy riêng: phải bật cùng lúc ở cả hai phía. Một bên gửi mà bên kia không chờ, hoặc ngược lại, thì mọi request hỏng. Đổi thiết lập này trên cluster đang chạy cần kế hoạch, không phải một dòng sửa vội.

6. Lỗi thường gặp với Ingress và NodeBalancer

  • EXTERNAL-IP đứng mãi ở <pending>: CCM chưa tạo được NodeBalancer. Xem kubectl describe svc để đọc event: annotation sai giá trị, vượt hạn mức tài khoản, hoặc lỗi gọi API.
  • Tạo Ingress mà không có gì xảy ra: cluster chưa có ingress controller, hoặc ingressClassName không khớp tên IngressClass nào. Đối tượng Ingress vẫn được tạo thành công, chỉ là không ai đọc nó.
  • Trả 404 từ default backend: header Host không khớp luật nào, thường do thiếu www hoặc DNS trỏ nhầm.
  • Mất IP public sau khi cài lại controller: gỡ chart rồi cài lại sẽ xoá Service cũ và tạo Service mới, kéo theo NodeBalancer mới với IP mới. Phải đổi DNS cho mọi domain.
  • Local mà controller chỉ có một replica: chỉ một node nhận traffic. Node đó bận hoặc bị thay thì toàn bộ website đứng chờ.

7. Những điểm cần nhớ trước khi dựng cluster

Checklist kiến thức nền trước khi tạo cluster, cùng với chuỗi bài từ Kubernetes là gì tới PersistentVolume, PVC và StorageClass:

  • Website chỉ cần Service ClusterIP. Cổng ra Internet nên gom về một ingress controller duy nhất, chỉ controller đó dùng Service LoadBalancer.
  • IP của NodeBalancer gắn với đối tượng Service. Coi Service của controller là tài sản cần giữ: không xoá, không gỡ chart tuỳ tiện.
  • Ingress là nơi đặt luật cho từng domain. TLS cũng sẽ kết thúc ở đây, nên chứng chỉ phải được cấp và gia hạn tự động ngay trong cluster, thay cho certbot trên balancer.
  • Muốn có IP thật của người dùng thì phải tính cả hai lớp, NodeBalancer và kube-proxy, chứ không chỉ đổi externalTrafficPolicy.

Bài tiếp theo

Dựng cluster LKE đầu tiên: node pool, kubeconfig và lựa chọn ban đầu. Tạo cluster cho Website AI: chọn region, kích thước node, lấy kubeconfig và giới hạn ai được cầm chìa khoá vào cluster.