5. Service
Mục lục
- 1. Vì sao cần Kubernetes Service?
- 2. NodePort Service hoạt động thế nào?
- 3. Demo: Tạo NodePort Service
- 4. ClusterIP Service là gì?
- 5. LoadBalancer Service là gì?
Services
1. Vì sao cần Kubernetes Service?
Kubernetes Service giúp thiết lập giao tiếp giữa các thành phần bên trong và bên ngoài ứng dụng — kết nối ứng dụng với nhau, hoặc với user bên ngoài. Ví dụ: ứng dụng có nhiều nhóm Pod chạy các phần khác nhau — 1 nhóm phục vụ front-end cho user, 1 nhóm chạy các tiến trình back-end, và 1 nhóm kết nối tới nguồn dữ liệu bên ngoài. Chính Service là thứ cho phép kết nối giữa các nhóm Pod này: giúp ứng dụng front-end truy cập được từ end user, giúp giao tiếp giữa back-end và front-end Pod, và giúp thiết lập kết nối tới nguồn dữ liệu bên ngoài. Nhờ vậy, Service giúp các microservice trong ứng dụng loose coupling (kết nối lỏng lẻo) với nhau.
Xét 1 use case cụ thể của Service — giao tiếp từ bên ngoài (external communication). Phần trước đã nói về cách các Pod giao tiếp với nhau qua networking nội bộ; giờ xét khía cạnh khác: giả sử đã deploy 1 Pod chạy web application — làm sao 1 user bên ngoài truy cập được trang web đó?
Xét lại setup hiện tại: Kubernetes node có IP address, ví dụ 192.168.1.2. Laptop cũng nằm trên cùng mạng đó, IP 192.168.1.10. Mạng nội bộ của Pod nằm trong dải 10.244.0.0, và Pod có IP 10.244.0.2. Rõ ràng không thể ping hay truy cập trực tiếp Pod ở địa chỉ 10.244.0.2 từ laptop, vì nó nằm ở 1 mạng riêng biệt.
Vậy có cách nào để xem trang web đó? Nếu SSH vào Kubernetes node ở 192.168.1.2, từ bên trong node có thể truy cập trang web của Pod bằng curl. Hoặc nếu node có GUI, có thể mở browser và xem trang web theo địa chỉ http://10.244.0.2 — nhưng đây là truy cập từ bên trong Kubernetes node, không phải điều thực sự muốn. Mong muốn thực sự là truy cập web server từ chính laptop, mà không cần SSH vào node, chỉ đơn giản bằng cách dùng IP của Kubernetes node.
Vậy cần 1 thứ ở giữa để map request từ laptop, qua node, tới đúng Pod đang chạy web container — đây chính là lúc Kubernetes Service phát huy tác dụng. Service là 1 object, giống như Pod, ReplicaSet, hay Deployment. Một trong các use case của nó là lắng nghe (listen) trên 1 port của node, và forward request trên port đó tới 1 port trên Pod đang chạy web application — loại Service này gọi là NodePort Service, vì Service lắng nghe trên port của node và forward request tới Pod.
💡 Hình dung như thế này: Service giống tổng đài lễ tân của công ty. Không cần nhớ số điện thoại bàn của từng nhân viên (IP của từng Pod) — vốn thay đổi liên tục khi nhân viên nghỉ hoặc có người mới (Pod bị xóa/tạo lại). Chỉ cần nhớ đúng 1 số tổng đài cố định (ClusterIP hoặc tên Service); tổng đài tự động nối máy tới bất kỳ nhân viên nào đang rảnh. NodePort giống việc tổng đài đó có thêm 1 đường dây ngoài — khách ngoài công ty cũng gọi được, và số máy lẻ đó nghe được y hệt ở mọi chi nhánh (mọi node), dù nhân viên trực chỉ ngồi đúng 1 chi nhánh.
Có nhiều loại Service khác nhau:
- NodePort — như vừa nói, giúp 1 port nội bộ truy cập được qua 1 port trên node.
- ClusterIP — Service tạo ra 1 virtual IP bên trong cluster để giao tiếp giữa các service khác nhau, ví dụ 1 nhóm frontend server tới 1 nhóm backend server.
- LoadBalancer — cung cấp (provision) 1 load balancer cho ứng dụng trên các cloud provider được hỗ trợ — ví dụ dùng để phân phối tải (distribute load) giữa các web server khác nhau ở tầng front-end.
2. NodePort Service hoạt động thế nào?
Quay lại NodePort: Service giúp map 1 port trên node tới 1 port trên Pod. Nhìn kỹ hơn vào Service, có tất cả 3 port liên quan:
- Port trên Pod, nơi web server thực sự chạy — trong ví dụ này là
80— gọi là targetPort, vì đó là nơi Service forward request tới. - Port trên chính Service — gọi đơn giản là port (các thuật ngữ này đều đứng từ góc nhìn của Service — bản thân Service thực chất giống 1 virtual server bên trong cluster, có IP address riêng bên trong cluster, gọi là ClusterIP của Service).
- Port trên chính node, dùng để truy cập web server từ bên ngoài — gọi là NodePort, ví dụ set là
30008. NodePort chỉ được nằm trong 1 dải hợp lệ, mặc định từ30000đến32767.
Để tạo Service, cũng dùng file định nghĩa như đã làm với Deployment/ReplicaSet/Pod trước đó. Cấu trúc cấp cao vẫn giữ nguyên: apiVersion, kind, metadata, spec.
apiVersion—v1.kind—Service.metadata— cóname(tên Service); cũng có thể cólabelsnhưng chưa cần dùng ở đây.spec— phần quan trọng nhất, khác nhau tùy loại object. Với Service,speccótypevàports:type— loại Service cần tạo:ClusterIP,NodePort, hoặcLoadBalancer. Ở đây tạo NodePort nên setNodePort.ports— là 1 array, chứatargetPort(set80),port(set80), vànodePort(set30008, hoặc số bất kỳ trong dải hợp lệ).
Trong 3 field trên, chỉ có port là bắt buộc. Nếu không cung cấp targetPort, mặc định lấy giá trị bằng port; nếu không cung cấp nodePort, Kubernetes tự động cấp 1 port trống trong dải hợp lệ (30000–32767). Vì ports là array nên nhớ có dấu - đánh dấu phần tử đầu tiên — về lý thuyết có thể khai nhiều port mapping trong cùng 1 Service.
Vẫn còn thiếu 1 thứ quan trọng: chưa có gì trong file định nghĩa kết nối Service với Pod — mới chỉ khai targetPort, chứ chưa khai rõ Pod đích, trong khi có thể có hàng trăm Pod khác cũng chạy web service trên port 80. Cách giải quyết — như đã làm với ReplicaSet trước đó, và là kỹ thuật sẽ còn gặp rất nhiều trong Kubernetes — dùng label và selector để liên kết chúng lại: lấy đúng label đã gắn trên Pod, đưa vào field mới trong spec gọi là selector (giống hệt cách dùng trong ReplicaSet và Deployment) — copy đúng label từ file định nghĩa Pod, đặt vào selector. Vậy là Service đã được liên kết với Pod.
Gộp lại toàn bộ field vừa giải thích, file service-definition.yaml hoàn chỉnh trông như sau:
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
ports:
- targetPort: 80
port: 80
nodePort: 30008
selector:
app: myapp
Sau khi hoàn tất, tạo Service bằng kubectl create với file định nghĩa Service làm input:
kubectl create -f service-definition.yaml
# Output minh họa
service/myapp-service created
Xem Service vừa tạo bằng kubectl get services:
kubectl get services
# Output minh họa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-service NodePort 10.107.0.42 <none> 80:30008/TCP 8s
Type là NodePort như đã khai, cột CLUSTER-IP chính là ClusterIP của Service, và cột PORT(S) ghi 80:30008/TCP — đúng cặp port:nodePort đã khai trong file. Giờ có thể dùng port này để truy cập web service bằng curl hoặc browser: curl tới 192.168.1.2 (IP của node), dùng port 30008 để truy cập web server.
Khi Service map tới nhiều Pod. Thực tế không phải lúc nào Service cũng chỉ map tới đúng 1 Pod. Trong môi trường production, thường có nhiều instance của web application để đạt high availability và load balancing. Giả sử có nhiều Pod giống nhau cùng chạy web application, tất cả cùng mang label app: my-app. Cùng label đó được dùng làm selector khi tạo Service — khi Service được tạo, nó tìm các Pod khớp label và tìm thấy 3 Pod. Service tự động chọn cả 3 Pod này làm endpoint để forward request từ user tới — không cần cấu hình gì thêm. Nếu thắc mắc thuật toán nào được dùng để cân bằng tải giữa 3 Pod — đó là thuật toán random (ngẫu nhiên). Nhờ đó, Service đóng vai trò như 1 load balancer tích hợp sẵn (built-in) để phân phối tải giữa các Pod.
Khi Pod trải rộng trên nhiều node. Giả sử web application chạy trên các Pod nằm ở các node riêng biệt trong cluster. Khi tạo Service, không cần cấu hình gì thêm, Kubernetes tự động tạo 1 Service trải rộng (span) trên tất cả các node trong cluster, và map target port tới cùng 1 NodePort trên tất cả các node. Nhờ vậy, có thể truy cập ứng dụng bằng IP của bất kỳ node nào trong cluster, dùng cùng 1 số port (30008 trong ví dụ này) — cùng port đó khả dụng trên mọi node thuộc cluster.
Tóm lại: dù là 1 Pod trên 1 node, nhiều Pod trên 1 node, hay nhiều Pod trên nhiều node, Service được tạo hoàn toàn giống nhau, không cần thêm bước nào khác lúc tạo Service. Khi Pod bị xóa hoặc thêm mới, Service tự động cập nhật theo — rất linh hoạt và thích ứng. Sau khi tạo xong, thường không cần chỉnh sửa gì thêm.
3. Demo: Tạo NodePort Service
Demo này tiếp nối từ demo trước — đã tạo sẵn 1 Deployment tên my-app-deployment với 6 replicas (tức 6 Pod đang chạy trong cluster). Giờ cần tạo Service để end user có thể truy cập ứng dụng qua trình duyệt.
Tạo 1 thư mục mới tên service, bên trong tạo file service-definition.yaml (không bắt buộc theo đúng cấu trúc thư mục này — chỉ là cách tổ chức ví dụ, có thể để mọi file cùng 1 chỗ cũng được).
Khai báo:
apiVersion: v1.kind: Service.metadata—name: my-app-service.spec.type: NodePort— mục tiêu là truy cập ứng dụng qua 1 port trên node (ở đây là node Minikube).spec.ports—portvàtargetPortđều là80(port Nginx đang lắng nghe);nodePort: 30004(giá trị bất kỳ từ30000đến32767) — đây là port trên worker node (node Minikube) mà ứng dụng sẽ truy cập được.spec.selector—my-app-deploymentđược tạo bằng lệnhkubectl create deployment(không qua file YAML) nên Kubernetes tự gán label mặc định theo đúng tên Deployment: label của Pod làapp: my-app-deployment— copy đúng giá trị này vàoselector.
Sau khi hoàn tất file định nghĩa, tạo Service:
kubectl create -f service-definition.yaml
# Output minh họa
service/my-app-service created
Xem Service vừa tạo:
kubectl get service
# Output minh họa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app-service NodePort 10.108.0.15 <none> 80:30004/TCP 5s
Type là NodePort (đúng như đã khai), có 1 ClusterIP được tạo — địa chỉ dành riêng cho Service bên trong mạng nội bộ cluster — và port trên worker node để truy cập ứng dụng. Nếu biết IP của worker node, có thể mở browser, gõ IP đó kèm số port để truy cập ứng dụng.
Vì đang chạy trên Minikube, có thể dùng lệnh minikube service kèm tên Service, thêm option --url, lệnh sẽ in ra URL nơi Service khả dụng:
minikube service myapp-service --url
(Tên Service được gọi không nhất quán ở bước này — lúc khai báo trong file là my-app-service, lúc gọi lại ở lệnh minikube service lại là myapp-service — giữ nguyên cả 2 cách viết.)
Copy URL đó, mở trình duyệt, dán vào — sẽ thấy trang mặc định của NGINX, xác nhận ứng dụng NGINX đang chạy và truy cập được qua trình duyệt.
4. ClusterIP Service là gì?
1 ứng dụng web full-stack thường có nhiều loại Pod khác nhau, host các phần khác nhau của ứng dụng: có thể có nhiều Pod chạy front-end web server, 1 nhóm Pod khác chạy back-end server, 1 nhóm Pod chạy key-value store như Redis, và 1 nhóm Pod khác chạy persistent database như MySQL. Web front-end server cần giao tiếp với back-end server, và back-end server cần giao tiếp với database cũng như Redis. Vậy đâu là cách đúng để thiết lập kết nối giữa các service hoặc tầng (tier) này của ứng dụng?
Các Pod đều có IP address riêng, nhưng các IP này không cố định — Pod có thể chết bất kỳ lúc nào, và Pod mới liên tục được tạo ra. Vì vậy không thể dựa vào các IP address này để giao tiếp nội bộ giữa các phần của ứng dụng. Ngoài ra: giả sử Pod front-end đầu tiên ở 10.244.0.3 cần kết nối tới 1 back-end service — sẽ kết nối tới Pod nào trong 3 Pod back-end? Và ai là người quyết định việc đó?
Kubernetes Service giúp nhóm các Pod lại và cung cấp 1 interface duy nhất để truy cập các Pod trong nhóm đó. Ví dụ: 1 Service tạo cho các Pod back-end sẽ giúp nhóm toàn bộ Pod back-end lại, và cung cấp 1 interface duy nhất để các Pod khác truy cập service đó — request được forward tới 1 trong các Pod dưới service đó theo cách ngẫu nhiên (random). Tương tự, tạo thêm service cho Redis, và cho phép Pod back-end truy cập hệ thống Redis qua service đó.
Cách này giúp deploy dễ dàng và hiệu quả 1 ứng dụng theo kiến trúc microservices trên Kubernetes cluster — mỗi tầng (layer) giờ có thể scale hoặc di chuyển tùy ý mà không ảnh hưởng tới giao tiếp giữa các service. Mỗi service được gán 1 IP và 1 tên bên trong cluster — và chính cái tên đó là thứ nên được các Pod khác dùng để truy cập service. Loại service này gọi là ClusterIP.
Để tạo loại service này, cũng dùng file định nghĩa như thường lệ. Trong file định nghĩa service, dùng template mặc định gồm apiVersion, kind, metadata, spec. apiVersion là v1, kind là Service, và đặt tên cho service — ở đây gọi là back-end. Dưới spec, có type và ports, với type là ClusterIP.
Thực tế, ClusterIP chính là type mặc định — nếu không khai type, Kubernetes tự động hiểu là ClusterIP. Dưới ports, có targetPort và port. targetPort là port nơi back-end được expose, trong trường hợp này là 80, và port là port nơi service được expose, cũng là 80. Để liên kết service với 1 nhóm Pod, dùng selector — lấy label từ file định nghĩa Pod, copy vào dưới selector. Vậy là hoàn tất.
Gộp lại toàn bộ field vừa giải thích, file service-definition.yaml hoàn chỉnh trông như sau:
apiVersion: v1
kind: Service
metadata:
name: back-end
spec:
type: ClusterIP
ports:
- targetPort: 80
port: 80
selector:
app: backend
Tạo service bằng lệnh kubectl create, sau đó kiểm tra trạng thái bằng kubectl get services:
kubectl create -f service-definition.yaml
# Output minh họa
service/back-end created
kubectl get services
# Output minh họa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
back-end ClusterIP 10.104.0.23 <none> 80/TCP 6s
Cột TYPE là ClusterIP (mặc định, không cần khai type cũng ra kết quả này), cột CLUSTER-IP là địa chỉ nội bộ dùng để các Pod khác truy cập — không có EXTERNAL-IP vì ClusterIP chỉ dùng cho giao tiếp bên trong cluster, không expose ra ngoài. Service có thể được truy cập bởi các Pod khác bằng ClusterIP hoặc bằng tên của service (back-end).
5. LoadBalancer Service là gì?
Đã biết NodePort Service giúp 1 ứng dụng truy cập được từ bên ngoài qua 1 port trên các worker node. Xét tới các ứng dụng front-end — ví dụ voting app và result app. Các Pod này host trên các worker node trong cluster — giả sử có 1 cluster 4 node. Để các ứng dụng truy cập được từ bên ngoài, tạo Service loại NodePort — service loại NodePort giúp nhận traffic trên port của các node và route traffic tới đúng Pod.
Nhưng URL nào sẽ đưa cho end user để truy cập ứng dụng? Có thể truy cập 1 trong 2 ứng dụng bằng IP của bất kỳ node nào, kèm theo port cao (high port) mà service expose ra — vậy sẽ có 4 tổ hợp IP:port cho voting app, và 4 tổ hợp IP:port cho result app. Lưu ý: kể cả khi Pod chỉ host trên 2 trong số các node, chúng vẫn truy cập được qua IP của tất cả các node trong cluster — ví dụ Pod của voting app chỉ deploy trên node có IP kết thúc bằng 70 và 71, nhưng vẫn truy cập được qua port trên tất cả các node trong cluster.
Đó là cách 1 Service được cấu hình — có thể share các URL này cho user để truy cập ứng dụng. Nhưng đó không phải điều end user mong muốn — họ cần 1 URL duy nhất, ví dụ example-votingapp.com hay example-resultapp.com, để truy cập ứng dụng.
Vậy làm sao đạt được điều đó? 1 cách là tạo 1 VM mới dành riêng cho load balancer, cài đặt và cấu hình 1 load balancer phù hợp trên đó, ví dụ HAProxy hay NGINX, rồi cấu hình load balancer đó route traffic tới các node bên dưới. Nhưng tự thiết lập, rồi duy trì và quản lý toàn bộ hệ thống load balancing bên ngoài như vậy có thể là 1 việc khá tốn công.
Tuy nhiên, nếu đang chạy trên 1 nền tảng cloud được hỗ trợ như Google Cloud, AWS, hay Azure, có thể tận dụng luôn native load balancer của nền tảng cloud đó. Kubernetes hỗ trợ tích hợp với native load balancer của 1 số cloud provider nhất định, và tự cấu hình luôn giúp mình. Vậy tất cả những gì cần làm là set type của service cho frontend service thành LoadBalancer thay vì NodePort.
Lưu ý: cách này chỉ hoạt động với các nền tảng cloud được hỗ trợ — GCP, AWS, và Azure chắc chắn được hỗ trợ. Nếu set type của service thành LoadBalancer trên 1 môi trường không được hỗ trợ, như VirtualBox hay môi trường khác, nó sẽ có hiệu ứng y hệt như set type thành NodePort — service vẫn được expose trên 1 port cao (high-end port) trên các node, chỉ là sẽ không thực hiện bất kỳ cấu hình load balancer bên ngoài nào.
Nguồn tham khảo
Nguồn gốc: Khóa "Kubernetes for the Absolute Beginners - Hands-on Tutorial" — module "Services", bài "Services", "NodePort", "Demo - NodePort Service", "ClusterIP", và "LoadBalancer", nền tảng KodeKloud.
Đính chính/cập nhật (fact-check):
- kube-proxy mode mặc định (iptables) dùng thuật toán "randomized equal cost selection" để cân bằng tải giữa các Pod — khớp đúng mô tả "random" trong bài; mode IPVS (hỗ trợ round-robin, least-connection...) đã bị deprecated từ Kubernetes v1.35, mode nftables mới hơn được khuyến nghị cho cluster lớn — 2025-2026, Kubernetes blog: NFTables mode for kube-proxy, Tigera: Comparing kube-proxy modes
- Ngoài GCP/AWS/Azure, MetalLB hiện là giải pháp phổ biến nhất giúp Service
type: LoadBalancerhoạt động thật (cấp IP thật, không chỉ tương đương NodePort) trên cluster bare-metal/on-premises không thuộc cloud được hỗ trợ sẵn — 2026, MetalLB official site