Ingress trong Kubernetes: đưa ứng dụng ra ngoài thế giới

1. Tổng quan

Khi bạn đã có Service chạy trong cluster nhưng muốn người dùng bên ngoài truy cập qua domain đẹp như api.nonladev.com thay vì IP:port lộn xộn, đó là lúc cần Ingress. Bài này giải thích Ingress là gì, khác gì với Service type LoadBalancer, cách cài Ingress Controller và viết Ingress resource cho các tình huống thường gặp: path-based routing, host-based routing, TLS. Đọc xong, bạn tự expose được một ứng dụng Spring Boot ra ngoài với domain và HTTPS.

2. Vì sao cần Ingress

2.1. Vấn đề với Service

Service type NodePort hoặc LoadBalancer hoạt động tốt, nhưng có hạn chế:

  • LoadBalancer: mỗi Service cần một external IP riêng → tốn tiền nếu có nhiều service (mỗi cloud LB tính phí riêng).
  • NodePort: expose port lạ (30000-32767), không có TLS termination, không routing theo domain/path.

Giả sử bạn có 5 microservice cần expose ra ngoài, dùng LoadBalancer nghĩa là 5 cloud load balancer, 5 external IP. Ingress giải quyết bằng cách dùng một entry point để route traffic tới nhiều Service bên trong, dựa trên hostname hoặc path.

2.2. Ingress là gì

Ingress là một API object định nghĩa rule routing HTTP/HTTPS từ ngoài vào Service trong cluster. Ingress tự nó không làm gì cả — nó cần Ingress Controller (một Pod chạy reverse proxy như NGINX, Traefik, HAProxy) để đọc rule và thực thi.

Client → Ingress Controller (LoadBalancer/NodePort) → Ingress rules → Service → Pod

Lưu ý: Nếu cluster chưa cài Ingress Controller, tạo Ingress resource cũng không có tác dụng gì — nó chỉ là config “nằm chờ” controller đọc.

3. Cài Ingress Controller

Phổ biến nhất là ingress-nginx. Cài bằng Helm:

<!-- không áp dụng — dùng Helm/kubectl, không phải Maven dependency -->
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace

Lệnh này tạo namespace ingress-nginx, deploy Ingress Controller cùng một Service type LoadBalancer để nhận traffic từ ngoài. Kiểm tra:

kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
NAME                                       TYPE           EXTERNAL-IP     PORT(S)
ingress-nginx-controller                   LoadBalancer   34.123.45.67    80:31234/TCP,443:31235/TCP

External IP này chính là điểm vào duy nhất cho mọi Ingress trong cluster.

4. Deployment và Service mẫu

Trước khi viết Ingress, cần có Service để route tới. Giả sử ứng dụng order-service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080

Service order-service expose port 80, forward vào container port 8080. Lưu ý Service này không cần type LoadBalancer, dùng ClusterIP (mặc định) là đủ, vì Ingress Controller sẽ gọi vào Service qua internal network.

5. Ingress resource cơ bản — host-based routing

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

Giải thích từng phần:

  • ingressClassName: nginx — chỉ định controller nào xử lý rule này (quan trọng khi cluster có nhiều controller).
  • rules.host — chỉ traffic có header Host: api.nonladev.com mới match rule này.
  • path + pathType: Prefix — mọi request bắt đầu bằng / (tức toàn bộ) sẽ forward tới order-service:80.
  • annotation rewrite-target — riêng của ingress-nginx, dùng để rewrite URL trước khi forward (tùy controller mà annotation khác nhau).

Áp dụng và test:

kubectl apply -f order-ingress.yaml
curl -H "Host: api.nonladev.com" http://34.123.45.67/
{"service":"order-service","status":"UP"}

Trong môi trường thật, bạn trỏ DNS record api.nonladev.com về external IP của Ingress Controller, không cần header Host giả như trên.

6. Path-based routing — nhiều service dùng chung domain

Tình huống thường gặp: một domain, nhiều service theo path khác nhau.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-gateway-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80
          - path: /customers
            pathType: Prefix
            backend:
              service:
                name: customer-service
                port:
                  number: 80

Request tới api.nonladev.com/orders/123 route vào order-service, còn /customers/55 route vào customer-service. pathType: Prefix nghĩa là match theo tiền tố — khác với Exact (phải khớp y hệt) và ImplementationSpecific (tùy controller tự diễn giải, thường dùng cho regex).

Lưu ý: Thứ tự path không ảnh hưởng matching trong Ingress v1 — controller tự chọn path dài nhất khớp trước. Nhưng viết path rõ ràng, không chồng lấn, để tránh bug khó debug.

7. TLS — HTTPS cho Ingress

Để có HTTPS, cần Secret chứa cert và key, rồi tham chiếu trong Ingress.

kubectl create secret tls nonladev-tls \
  --cert=tls.crt --key=tls.key
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress-tls
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.nonladev.com
      secretName: nonladev-tls
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

spec.tls khai báo host nào dùng cert nào. Controller tự động làm TLS termination — tức giải mã HTTPS tại Ingress, rồi forward HTTP thuần vào Service bên trong. Pod của bạn không cần biết gì về TLS.

Trong thực tế, bạn thường dùng cert-manager để tự động xin cert Let’s Encrypt thay vì tạo Secret tay:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress-tls
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.nonladev.com
      secretName: nonladev-tls
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

Annotation cluster-issuer yêu cầu cert-manager tự tạo và renew Secret nonladev-tls — không cần tay động gì thêm.

8. Viết test kiểm tra endpoint sau khi expose

Sau khi Ingress hoạt động, bạn có thể viết integration test đơn giản kiểm tra controller class xử lý path đúng (test ở tầng ứng dụng Spring Boot, không test trực tiếp YAML):

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderControllerIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturnOrderWhenCalledWithOrdersPrefix() {
        ResponseEntity<String> response =
                restTemplate.getForEntity("/orders/123", String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).contains("orderId");
    }
}

Test này đảm bảo ứng dụng Spring Boot đáp ứng đúng path /orders/123 mà Ingress rewrite tới — tách biệt lỗi do code backend với lỗi do cấu hình Ingress.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

9. Lỗi thường gặp

  • Quên ingressClassName: nếu cluster có nhiều Ingress Controller (ví dụ cả nginx và traefik), Ingress không rõ controller nào xử lý → không hoạt động hoặc bị cả hai xử lý nhầm.
  • Service type sai: Service backend để LoadBalancer không cần thiết, gây lãng phí — chỉ cần ClusterIP.
  • pathType không khớp mong đợi: dùng Exact khi cần Prefix khiến request con (/orders/123) bị 404 vì chỉ /orders khớp chính xác.
  • DNS chưa trỏ: test bằng curl -H "Host: ..." trước khi cấu hình DNS thật, tránh mất thời gian chờ DNS propagate.
  • Rewrite annotation sai controller: annotation của ingress-nginx không áp dụng được cho Traefik hay AWS ALB Ingress Controller — mỗi controller có bộ annotation riêng, đọc kỹ doc tương ứng.

Khi nào KHÔNG nên dùng Ingress: nếu ứng dụng dùng protocol không phải HTTP/HTTPS (ví dụ raw TCP, gRPC streaming phức tạp, hay UDP), Ingress chuẩn không hỗ trợ tốt — cần xem Gateway API hoặc Service type LoadBalancer trực tiếp cho TCP/UDP.

10. Kết luận

Ingress là lớp routing HTTP/HTTPS tập trung, giúp bạn expose nhiều Service ra ngoài qua một entry point duy nhất, tiết kiệm chi phí LoadBalancer và dễ quản lý domain/path/TLS. Điểm mấu chốt cần nhớ: Ingress resource chỉ là rule, cần Ingress Controller để thực thi; chọn đúng ingressClassName và pathType; dùng cert-manager để tự động hóa TLS. Với ứng dụng không phải HTTP thuần, cân nhắc Gateway API thay vì ép dùng Ingress.

kubernetesingressnetworkingdevopsnginx