1. Giới thiệu
Sau bài Triển khai ứng dụng full-stack với k8s cho người mới. Trong đó, mình đã giới thiệu và thực hành với các resource của k8s như Deployment, Service, StatefulSet,..., giúp mọi người có thể thực hành và dựng được 1 app cơ bản, hoàn chỉnh chạy trong cluster từ minikube. Tuy nhiên ở bài viết đó thì chưa có giải thích, đào sâu vào các câu hỏi như là các thành phần giao tiếp với nhau như nào, tại sao chỗ này phải dùng service, tại sao trong service, chỗ dùng NodePort, chỗ dùng ClusterIP,... rất nhiều câu hỏi về kiến trúc, về network,.. mà trong bài trên chưa đào sâu vào để hiểu. Do vậy, trong bài này sẽ tập trung vào các ví dụ đơn giản để làm rõ và hiểu hơn về Networking trong k8s, từ khái niệm đến các thực hành, để bạn có một hình dung trực quan về cách các resource giao tiếp với nhau trong cluster.
2. Pod IP & vì sao phải cần Service
Khi chạy app trong Kubernetes, mỗi Pod được gán một IP riêng. Nhưng IP này không cố định: khi Pod chết/rollback/scale là IP của Pod đổi ngay. Vì thế, nếu FE hoặc Pod khác gọi trực tiếp vào IP cũ Pod → rất dễ “toang” sau một lần redeploy/restart.
Dưới đây là 1 ví dụ, mà bạn có thể làm theo để thấy được vấn đề và cách giải quyết vấn đề bằng Service
2.1. Thử gọi trực tiếp Pod IP (và lý do thất bại)
- Bước 1: Tạo một Pod backend đơn giản (chưa có Service)
Ở đây mình dùng image nginxdemos/hello:plain-text là một demo image do team NGINX cung cấp, dùng để test nhanh NGINX và môi trường container/k8s. Nó đơn giản là nhận 1 request và trả về response dạng text.
Các bạn nhớ bật minikube để có thể thực hành ở local nhé. Bật terminal lên chạy kubectl apply -f be-pod.yaml
Đợi 1 lúc cho pod khởi chạy xong thì chạy lệnh kubectl get pod be-pod -o wide
để thấy được IP của Pod be-pod. (ví dụ: 10.244.0.209)
- Bước 2: Tạo tạm một Pod “client” để test call pod be-pod bằng curl
kubectl run curl --image=curlimages/curl -it --rm --restart=Never -- sh
Lệnh này dùng image curlimages/curl chưa duy nhất curl để test HTTP request từ trong cluster và mở shell sau khi pod được tạo thành công
Khi vào shell của pod curl này thì gõ lệnh curl -s http://10.244.0.209
Bạn sẽ thấy 1 response trên terminal:
Vậy là từ Pod curl đã call thành công sang be-pod khi biết được IP của pod be-pod
- Bước 3: Mô phỏng Pod restart => IP đổi
Tuy nhiên chúng ta sẽ mô phỏng tình huống pod bị kill, restart, redeploy,... Chúng ta chạy các lệnh sau:
kubectl delete pod be-pod
kubectl apply -f be-pod.yaml
kubectl get pod be-pod -o wide
Sau khi chạy lại pod và check ip pod, chúng ta thấy ip của pod đã thay đổi. Khi này, thực hiện lại bước 2 với ip pod cũ thì sẽ bị lỗi. Đây là lý do không bao giờ nên setup FE gọi trực tiếp Pod IP trong K8s
2.2. Dùng Service (ClusterIP) để có định danh ổn định + load balancing
Trong bài viết trên, chúng ta thấy có sự xuất hiện của service. Service sẽ cũng cấp 1 định danh ổn định đại diện cho các Pod trong 1 nhóm nào đó (chọn qua selector)
- Bước 4: Tạo Service cho backend
Ở đây, selector.app
phải trỏ đúng vào metadata.labels.app
trong be-pod.yaml
để service biết rằng nó cần quản lý các pod có nhãn là be
Trong terminal, chúng ta chạy: kubectl apply -f be-svc.yaml
Sau khi apply thành công thì chạy kubectl get svc backend
để xem thông tin service của be-pod
Cách setup service (dùng ClusterIP) sẽ cung cấp 1 IP cho service này, và chúng ta có thể thấy None ở ExternalIP, tức là không thể gọi đến service này từ ngoài cluster.
- Bước 5: Gọi qua DNS Service (ổn định, không sợ Pod đổi IP)
Chúng ta sẽ lại dùng pod curl để call thử đến be-pod thông qua service
Lúc này chúng ta hoàn toàn ko cần quan tâm đến Pod IP, ngay cả khi restart lại be-pod thì nhờ service, chúng ta vẫn có thể thực hiện các request call đến be-pod
2.3. Thêm Deployment + scale để thấy Service load balance
Trong thực tế, có thể sẽ có nhiều tình huống crash pod, (ở local thì thường chạy với 1 pod để phù hợp với sự hạn chế tài nguyên của máy tính). Do vậy thay vì Pod đơn lẻ, ta demo với Deployment (2 replicas) để scale và test với load balancing ( 1 cơ chế cân bằng tải của service )
File deployment.yaml
kubectl apply -f be-deploy.yaml
kubectl get pods -l app=be -o wide
Tiếp tục dùng pod curl để chạy thử lệnh: for i in 1 2 3 4 5; do curl -s http://backend | grep 'Server name'; done
Bạn sẽ thấy “Server name” thay đổi qua lại giữa 2 Pod khác nhau → chứng tỏ Service đang cân bằng tải cho bạn. Còn về các cách, hay là các thuật toán cân bằng tải sẽ được trình bày trong 1 bài viết trong tương lai gần. Đến đây, chúng ta đã có thể hiểu phần nào tại sao không dùng pod IP trong thực tế, cách giao tiếp trong cluster, cách từ 1 FE sẽ call đến BE trong môi trường k8s (bằng cách set biến env trong FE, ví dụ API_BASE_URL=http://backend:80)
3. Các loại Service trong Kubernetes
Trong phần trước, bạn đã thấy ClusterIP giải quyết bài toán Pod IP “ngắn hạn”. Tuy nhiên, đôi khi chúng ta cần mở Service cho người dùng bên ngoài cluster truy cập (ví dụ: frontend app, public API). Đây là lúc cần tìm hiểu kỹ hơn các loại Service khác
3.1. ClusterIP (mặc định)
- Là gì?
- Đây là Service mặc định trong Kubernetes.
- Chỉ cho phép truy cập bên trong cluster.
- DNS: <service-name>.<namespace>.svc.cluster.local
- Use case:
- BE ↔ DB (nội bộ)
- FE ↔ BE (nếu cả hai đều nằm trong cluster)
3.2. NodePort
- Là gì?
- Mở một port cố định (30000–32767) trên mỗi Node
- Bạn có thể truy cập Service từ ngoài cluster thông qua: http://<NodeIP>:<NodePort> (NodeIP lấy bằng minikube ip)
- Use case:
- Demo local với Minikube/K3s
- Khi chưa có Ingress/LoadBalancer
- Yaml mẫu:
3.3. LoadBalancer
- Là gì?
- Khi bạn dùng Kubernetes trên cloud (AWS/GCP/Azure), Service type LoadBalancer sẽ nhờ cloud cấp IP public/Load Balancer thật
- Người dùng chỉ cần truy cập IP public đó
- Use case:
- Production trên cloud.
- Khi cần expose trực tiếp service ra Internet.
- Yaml mẫu:
Truy cập thông qua: http://<cloud-public-ip>. Và chỉ hoạt động nếu cluster chạy trên cloud provider có hỗ trợ (EKS, GKE, AKS…)
4. Kinh nghiệm & Best Practice khi làm việc với Networking trong Kubernetes
Sau khi thực hành qua ClusterIP, NodePort, LoadBalancer, mình rút ra được một số kinh nghiệm nho nhỏ. Nếu bạn cũng mới bắt đầu, hy vọng sẽ giúp tiết kiệm kha khá thời gian.
- Luôn dùng Service name thay vì Pod IP
- Chọn đúng loại Service cho đúng mục đích
- NodePort – đừng hardcode nếu không cần