Service, DNS và Ingress
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 đề
- Config
payment.url=http://10.1.0.11:8080: Pod A chết, Pod D mới có IP10.1.0.27, URL cũ chết theo. - Có 3 Pod: gửi request vào Pod nào? Pod không Ready thì phải tránh.
- Pod IP chỉ dùng được trong cluster. Người dùng ngoài không với tới.
- Expose mỗi service bằng một load balancer cloud: 10 service = 10 LB, 10 IP public, 10 chứng chỉ TLS, tốn tiề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
| Type | Truy cập từ | Dùng cho |
|---|---|---|
| ClusterIP (mặc định) | Trong cluster | service-to-service: order → payment, payment → postgres |
| NodePort | NodeIP:30000-32767 | Test, môi trường on-prem đơn giản |
| LoadBalancer | IP public do cloud cấp | Một cửa vào, thường là cho Ingress Controller |
Headless (clusterIP: None) | DNS trả thẳng IP từng Pod | StatefulSet: postgres-0.postgres |
| ExternalName | CNAME ra ngoài | Trỏ 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 LoadBalancer | Ingress | |
|---|---|---|
| Tầng | L4 (TCP/UDP) | L7 (HTTP/HTTPS) |
| Routing | Một IP → một Service | Một IP → nhiều Service theo host/path |
| TLS | Tùy LB cloud | Tập trung ở controller, cert-manager tự gia hạn |
| Chi phí | Mỗi Service một LB | Mộ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
- ClusterIP cho mọi service nội bộ. Chỉ service public mới có rule Ingress; payment và postgres không cần ra Internet.
- Một Ingress Controller + một LB cho cả cluster thay vì LB cho từng service.
- NodePort chỉ để test hoặc khi không có LB; không expose trực tiếp ra Internet.
- Service LoadBalancer khi cần giao thức không phải HTTP.
DB_URL, password postgres ở đâu? Nhét thẳng vào YAML Deployment thì password nằm trong Git. Bài sau: ConfigMap, Secret và phân quyền.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:
- Selector gõ sai: Service
app: payment, Podapp: payment-service.kubectl get pods --show-labels kubectl describe svc payment-service | grep Selector - Pod không Ready: readiness fail, Pod bị gỡ khỏi endpoint (bài 17.2).
- 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.localtrỏ tới ClusterIP. Pod được cấu hình search domain nên cùng namespace chỉ cần gọipayment-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 callpayment-service, and across namespaces I add.namespace. A headless Service returns the Pod IPs directly, and StatefulSet Pods get individual names such aspostgres-0.postgres.
Cách nhớ ngắn gọn
- Service = IP ảo + DNS + selector; chỉ Pod Ready mới vào endpoint.
- nodePort → Node, port → Service, targetPort → Pod.
- ClusterIP nội bộ; LoadBalancer cho cửa vào; Ingress route nhiều service sau một LB.
- Ingress resource = luật; Ingress Controller = thứ chạy luật.
- Gọi không tới: xem endpoints → label → readiness → targetPort.
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.