Service, DNS và Ingress

4 câu hỏi phỏng vấn~30 phútTừ khóa: ClusterIP, NodePort, LoadBalancer, headless, port, targetPort, nodePort, EndpointSlice, kube-proxy, CoreDNS, Ingress, Ingress Controller, pathType, TLS, cert-manager

Tiếp đi: Service và Ingress.

Bài này hoàn thiện đường đi của một request: Internet → Ingress → Service → Pod → Tomcat → Controller.

1. Bài toán

payment-service Pods:
Pod A → 10.1.0.11
Pod B → 10.1.0.12
Pod C → 10.1.0.13

order-service cần gọi payment. Người dùng ngoài Internet cần gọi POST /orders.

2. Vấn đề

3. Giải pháp

Service = endpoint ổn định (IP ảo + tên DNS) đứng trước một nhóm Pod chọn bằng label. Pod nào Ready mới được đưa vào danh sách.

order-service Pod
      │ http://payment-service
      ▼
┌───────────────────┐
│ payment-service   │  ← ClusterIP 10.96.182.15, ổn định
└─────────┬─────────┘
     ┌────┼────┐   selector app=payment-service
     ▼    ▼    ▼
   Pod A Pod B Pod C
TypeTruy cập từDùng cho
ClusterIP (mặc định)Trong clusterservice-to-service: order → payment, payment → postgres
NodePortNodeIP:30000-32767Test, môi trường on-prem đơn giản
LoadBalancerIP public do cloud cấpMột cửa vào, thường là cho Ingress Controller
Headless (clusterIP: None)DNS trả thẳng IP từng PodStatefulSet: postgres-0.postgres
ExternalNameCNAME ra ngoàiTrỏ tên nội bộ tới DB managed

Ingress = luật routing HTTP/HTTPS từ ngoài vào Service theo host và path. Ingress Controller (ingress-nginx, Traefik, AWS Load Balancer Controller...) là phần mềm thật sự chạy các luật đó. Chỉ tạo kind: Ingress mà cluster không có controller thì không có gì xảy ra.

                   Internet
                       │
               Cloud Load Balancer   (1 cái, Service type LoadBalancer)
                       │
               Ingress Controller (nginx Pods)
                       │  đọc luật từ các Ingress resource
         ┌─────────────┼──────────────┐
      /orders      /payments       (không expose)
         ▼             ▼
   order-service  payment-service ──► postgres (ClusterIP)
     (ClusterIP)    (ClusterIP)

4. Code từng bước

Bước 1: Service ClusterIP cho payment và order

apiVersion: v1
kind: Service
metadata:
  name: payment-service
spec:
  type: ClusterIP
  selector:
    app: payment-service       # khớp label Pod
  ports:
    - name: http
      port: 80                 # port của Service
      targetPort: 8080         # port Spring Boot trong Pod
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 80
      targetPort: 8080

Dấu --- tách nhiều resource trong một file.

Bước 2: order-service gọi payment qua DNS

# application.yml của order-service
clients:
  payment:
    url: http://payment-service              # cùng namespace
    # khác namespace: http://payment-service.payments.svc.cluster.local
spring:
  datasource:
    url: jdbc:postgresql://postgres:5432/shop
@Configuration
class PaymentClientConfig {
    @Bean
    RestClient paymentRestClient(@Value("${clients.payment.url}") String baseUrl) {
        return RestClient.builder().baseUrl(baseUrl).build();
    }
}

Giống Docker Compose gọi db:5432, chỉ khác là DNS do CoreDNS trong cluster cung cấp.

Bước 3: Ingress có TLS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt   # nếu cluster có cert-manager
spec:
  ingressClassName: nginx
  tls:
    - hosts: [ "api.shop.example.com" ]
      secretName: api-shop-tls                   # Secret type kubernetes.io/tls
  rules:
    - host: api.shop.example.com
      http:
        paths:
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: order-service
                port: { number: 80 }
          - path: /payments
            pathType: Prefix
            backend:
              service:
                name: payment-service
                port: { number: 80 }

pathType: Prefix khớp /orders, /orders/123, /orders/123/items (theo từng đoạn path, nên /ordersx không khớp). Exact chỉ khớp đúng chuỗi.

Bước 4: Test

kubectl get svc,ing
kubectl get endpointslices -l kubernetes.io/service-name=payment-service
# ENDPOINTS: 10.1.0.11:8080,10.1.0.12:8080,10.1.0.13:8080

# gọi thử từ trong cluster
kubectl run tmp --rm -it --image=curlimages/curl --restart=Never -- \
  curl -s http://payment-service/actuator/health

# gọi từ máy mình không cần Ingress
kubectl port-forward svc/order-service 8080:80
curl localhost:8080/orders/1

# qua Ingress
curl https://api.shop.example.com/orders/1

5. Cách hoạt động

Service không phải một process. Nó là IP ảo + rule mạng:

Pod Ready/NotReady thay đổi
      │
      ▼
EndpointSlice controller cập nhật danh sách IP:port của Pod Ready
      │
      ▼
kube-proxy trên MỌI node watch → viết rule iptables/IPVS
      "đi tới 10.96.182.15:80 → DNAT ngẫu nhiên tới 10.1.0.11:8080 | .12 | .13"

order Pod: GET http://payment-service/payments
  1. DNS: CoreDNS trả 10.96.182.15
  2. Gói tin đi ra, rule trên node DNAT tới một Pod IP
  3. Tới Pod :8080 → Tomcat → DispatcherServlet → PaymentController

Load balancing ở đây là theo kết nối (L4). Nếu client giữ HTTP keep-alive hoặc gRPC thì mọi request đi theo một kết nối sẽ dồn vào một Pod.

Ba con số port

Outside ──:30080 (nodePort)──► Node ──► Service :80 (port) ──► Pod :8080 (targetPort) ──► Spring Boot

Request từ Internet

https://api.shop.example.com/payments/123
   │
Cloud LB ──► Ingress Controller: TLS termination, host=api.shop..., path=/payments
   │
payment-service :80 ──► Ready Pod :8080 ──► Tomcat ──► PaymentController

6. So sánh

Service LoadBalancerIngress
TầngL4 (TCP/UDP)L7 (HTTP/HTTPS)
RoutingMột IP → một ServiceMột IP → nhiều Service theo host/path
TLSTùy LB cloudTập trung ở controller, cert-manager tự gia hạn
Chi phíMỗi Service một LBMột LB cho cả cluster
Giao thức khác HTTPĐược (postgres, MQTT)Chủ yếu HTTP

Ingress và API gateway có chồng chéo; khi nào cần thêm Spring Cloud Gateway đã bàn ở bài 4.6.9. Bản kế nhiệm của Ingress là Gateway API (Gateway, HTTPRoute), tách rõ vai trò hạ tầng và vai trò team app; ingress-nginx cũng đang dần được thay bằng các controller theo Gateway API.

7. Khi nào nên dùng / không nên dùng

Có Ingress rồi thì bỏ Service được không?

Thông thường không. Mỗi tầng có một việc:

Ingress     → "/payments thì gửi payment-service"     (HTTP routing từ ngoài)
Service     → "payment-service gồm những Pod Ready nào" (endpoint ổn định + chọn Pod)
Deployment  → "luôn có 3 Pod payment"                   (vòng đời Pod)

Ingress rule trỏ tới Service, không trỏ tới Pod. Ingress Controller đọc danh sách endpoint của Service để biết Pod nào cần gửi (ingress-nginx gửi thẳng tới Pod IP, bỏ qua kube-proxy, nhưng vẫn lấy danh sách từ Service). Không có Service thì Ingress không có backend.

Chuỗi ghép bằng tên và label:

Ingress backend.service.name: payment-service
      │
Service payment-service, selector app=payment-service
      │
Pod template labels app=payment-service

Service có Pod chạy đủ mà gọi vẫn bị timeout thì kiểm tra gì trước nhỉ?

Kiểm tra endpoint trước. 80% trường hợp là Service không chọn được Pod nào:

kubectl get endpointslices -l kubernetes.io/service-name=payment-service
# ENDPOINTS: <none>   ← Service rỗng

Ba nguyên nhân hay gặp:

  1. Selector gõ sai: Service app: payment, Pod app: payment-service.
    kubectl get pods --show-labels
    kubectl describe svc payment-service | grep Selector
  2. Pod không Ready: readiness fail, Pod bị gỡ khỏi endpoint (bài 17.2).
  3. Sai targetPort: app nghe 8080, Service trỏ 8081. Endpoint có IP nhưng kết nối bị từ chối.

Cách debug chi tiết hơn (debug pod, DNS, NetworkPolicy) ở bài 17.7.

Phỏng vấn hỏi về Service và Ingress thì trả lời sao?

Q1. Pod đã có IP rồi, sao còn cần Service?

🇻🇳 Tiếng Việt:

Pod là tạm thời, bị tạo lại là đổi IP, và thường có nhiều Pod cho một ứng dụng. Service cho một IP ảo và tên DNS ổn định, chọn Pod bằng label và chỉ đưa Pod Ready vào danh sách endpoint, rồi kube-proxy phân phối kết nối tới các Pod đó. Client chỉ cần biết http://payment-service.

🇬🇧 English:

Pods are ephemeral; when one is recreated its IP changes, and an application usually has several Pods. A Service gives a stable virtual IP and DNS name, selects Pods by label and only keeps Ready Pods in its endpoints, and kube-proxy spreads connections across them. Clients only need to know http://payment-service.

Q2. ClusterIP, NodePort, LoadBalancer khác nhau thế nào?

🇻🇳 Tiếng Việt:

ClusterIP là mặc định, chỉ truy cập được trong cluster, dùng cho gọi nội bộ. NodePort mở thêm một port cố định 30000 đến 32767 trên mọi node để truy cập từ ngoài qua IP node. LoadBalancer xây trên NodePort và nhờ cloud tạo load balancer có IP public. Mỗi loại bao loại trước. Thực tế em để service nội bộ là ClusterIP và chỉ có Ingress Controller dùng LoadBalancer.

🇬🇧 English:

ClusterIP is the default and only reachable inside the cluster, for internal calls. NodePort additionally opens a fixed port between 30000 and 32767 on every node so it can be reached via a node IP. LoadBalancer builds on NodePort and asks the cloud provider for a load balancer with a public IP. Each type includes the previous one. In practice I keep internal services as ClusterIP and only the Ingress Controller uses a LoadBalancer.

Q3. Ingress resource khác Ingress Controller thế nào?

🇻🇳 Tiếng Việt:

Ingress resource chỉ là cấu hình: host nào, path nào đi tới Service nào, dùng chứng chỉ TLS nào. Ingress Controller là phần mềm chạy trong cluster, như ingress-nginx, watch các Ingress resource rồi cấu hình reverse proxy và thật sự nhận traffic. Không cài controller thì Ingress không có tác dụng.

🇬🇧 English:

An Ingress resource is only configuration: which host and path go to which Service, and which TLS certificate to use. An Ingress Controller is software running in the cluster, such as ingress-nginx, that watches Ingress resources, configures a reverse proxy and actually handles the traffic. Without a controller, an Ingress does nothing.

Q4. Service DNS trong Kubernetes hoạt động thế nào?

🇻🇳 Tiếng Việt:

CoreDNS chạy trong cluster tạo bản ghi cho mỗi Service theo dạng tên.namespace.svc.cluster.local trỏ tới ClusterIP. Pod được cấu hình search domain nên cùng namespace chỉ cần gọi payment-service, khác namespace thì thêm .namespace. Headless Service thì DNS trả thẳng IP các Pod, và StatefulSet có tên riêng từng Pod như postgres-0.postgres.

🇬🇧 English:

CoreDNS runs in the cluster and creates a record for each Service as name.namespace.svc.cluster.local, pointing to its ClusterIP. Pods are configured with search domains, so in the same namespace I just call payment-service, and across namespaces I add .namespace. A headless Service returns the Pod IPs directly, and StatefulSet Pods get individual names such as postgres-0.postgres.

Cách nhớ ngắn gọn


Tự kiểm tra

order-service ở namespace shop gọi payment-service ở namespace payments bằng URL nào?

http://payment-service.payments (đầy đủ payment-service.payments.svc.cluster.local).

Service có port: 80, targetPort: 8080. Spring Boot phải nghe port nào?

8080. Client gọi Service qua port 80.

Tạo Ingress xong nhưng không có IP/ADDRESS. Thiếu gì?

Thiếu Ingress Controller, hoặc sai ingressClassName.

Vì sao gRPC client dài hạn hay dồn tải vào một Pod?

Service cân bằng theo kết nối L4; một kết nối HTTP/2 giữ lâu mang mọi request tới cùng một Pod.

Chứng chỉ TLS cho Ingress lưu ở đâu?

Trong Secret type kubernetes.io/tls, tham chiếu bằng spec.tls.secretName.