Kubernetes cơ bản: Pod, Deployment và Service
1. Tổng quan
Kubernetes có hàng chục loại resource, nhưng 80% công việc hàng ngày xoay quanh ba khái niệm: Pod, Deployment, Service. Bài này giúp bạn hiểu bản chất của từng resource, cách chúng liên kết với nhau, và viết được một bộ manifest YAML triển khai ứng dụng thật sự chạy được trên cluster.
Đọc xong, bạn sẽ tự deploy được một ứng dụng Spring Boot lên Kubernetes, scale nó lên nhiều replica, và expose ra ngoài qua Service - tất cả chỉ bằng kubectl và vài file YAML.
Lưu ý: Bài giả định bạn đã có cluster (Minikube, Kind, hoặc cloud) và
kubectlđã trỏ đúng context.
2. Pod - Đơn vị triển khai nhỏ nhất
2.1. Pod là gì
Pod là nhóm một hoặc nhiều container chia sẻ network namespace (cùng IP, cùng port space) và volume. Trong thực tế, phần lớn Pod chỉ chứa một container ứng dụng.
apiVersion: v1
kind: Pod
metadata:
name: order-service-pod
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
Giải thích:
metadata.labelsgắn nhãnapp: order-service- đây là cách Kubernetes “nhận diện” Pod, Service và Deployment dùng label này để chọn đối tượng liên kết.resources.requestslà tài nguyên tối thiểu scheduler cần có để đặt Pod lên node;limitslà mức trần, vượt quá sẽ bị throttle (CPU) hoặc bị kill (memory - OOMKilled).containerPort: 8080chỉ là khai báo thông tin, không tự mở port ra ngoài.
Áp dụng và kiểm tra:
kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME READY STATUS RESTARTS AGE
order-service-pod 1/1 Running 0 5s
2.2. Vì sao không nên tạo Pod trực tiếp
Pod tạo “trần” như trên có một vấn đề lớn: nếu Node chứa Pod bị chết, hoặc Pod crash, không ai tự tạo lại nó. Pod không có cơ chế self-healing hay scaling.
Lưu ý: Trong môi trường production, gần như không bao giờ tạo Pod trực tiếp. Luôn dùng Deployment (hoặc StatefulSet, Job) để quản lý Pod.
3. Deployment - Quản lý vòng đời Pod
3.1. Khai báo Deployment
Deployment tạo và quản lý một tập Pod thông qua ReplicaSet, đảm bảo luôn có đúng số lượng replica đang chạy, và hỗ trợ rolling update không downtime.
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
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
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
Giải thích:
replicas: 3yêu cầu Deployment luôn duy trì 3 Pod chạy đồng thời - nếu một Pod chết, Deployment tự tạo Pod mới thay thế.selector.matchLabelsphải khớp vớitemplate.metadata.labels. Đây là liên kết bắt buộc giữa Deployment và Pod nó quản lý.readinessProbequyết định Pod có nhận traffic hay không (dùng bởi Service);livenessProbequyết định Pod có cần restart hay không. Nhầm hai probe này là lỗi rất phổ biến khi mới học Kubernetes.
Áp dụng và quan sát:
kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME READY UP-TO-DATE AVAILABLE AGE
order-service 3/3 3 3 12s
NAME READY STATUS RESTARTS AGE
order-service-7d4f9c8b6d-2kx9p 1/1 Running 0 12s
order-service-7d4f9c8b6d-7h2mn 1/1 Running 0 12s
order-service-7d4f9c8b6d-xq1lw 1/1 Running 0 12s
3.2. Scale và rolling update
Scale tăng/giảm replica không cần sửa YAML:
kubectl scale deployment order-service --replicas=5
Rolling update khi đổi image - Deployment tự thay Pod cũ bằng Pod mới theo chiến lược cuốn chiếu, không downtime:
kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out
Nếu version mới có bug, rollback về bản trước ngay lập tức:
kubectl rollout undo deployment/order-service
3.3. Cấu hình chiến lược rolling update
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
maxUnavailable: 1 cho phép tối đa 1 Pod “mất đi” trong lúc update; maxSurge: 1 cho phép tạo thêm tối đa 1 Pod mới vượt số replicas khai báo. Với replicas: 3, cluster sẽ có lúc chạy 2-4 Pod trong quá trình chuyển đổi, nhưng không bao giờ dưới 2.
4. Service - Expose Pod ổn định
4.1. Vì sao cần Service
Pod có IP riêng, nhưng IP này thay đổi mỗi khi Pod bị tạo lại (crash, rolling update, scale). Service cung cấp một địa chỉ ổn định (ClusterIP + DNS name) đứng trước nhóm Pod, tự động load-balance traffic dựa trên label selector - không quan tâm Pod bên dưới đổi bao nhiêu lần.
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Giải thích:
selector.app: order-servicekhớp với label trên Pod (do Deployment gắn vàotemplate.metadata.labels) - Service tự động tìm và theo dõi các Pod này qua object trung gian gọi là Endpoints.port: 80là port mà Service lắng nghe nội bộ cluster;targetPort: 8080là port container thực sự expose. Hai số này không nhất thiết phải giống nhau.type: ClusterIP(mặc định) chỉ truy cập được từ trong cluster - phù hợp cho service nội bộ giữa các microservice.
Kiểm tra Service và Endpoints:
kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
order-service ClusterIP 10.96.142.201 <none> 80/TCP 4s
NAME ENDPOINTS AGE
order-service 10.244.1.5:8080,10.244.1.6:8080,+1 more 4s
4.2. Các loại Service type
| Type | Phạm vi truy cập | Khi nào dùng |
|---|---|---|
ClusterIP |
Chỉ trong cluster | Giao tiếp giữa microservice nội bộ |
NodePort |
Mở port cố định (30000-32767) trên mọi Node | Test nhanh, môi trường dev/on-prem |
LoadBalancer |
Cấp IP public qua cloud provider | Expose service ra Internet trên cloud |
Ví dụ expose ra ngoài để test nhanh trên Minikube:
apiVersion: v1
kind: Service
metadata:
name: order-service-external
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
nodePort: 30080
type: NodePort
minikube service order-service-external --url
Lưu ý: Dùng
NodePortcho production là anti-pattern - port range giới hạn, không có TLS termination hay health-based routing như Ingress. Với production trên cloud, ưu tiênLoadBalancerhoặc Ingress Controller.
5. Kết hợp cả ba trong một ứng dụng thực tế
Toàn bộ pipeline: client gọi vào Service → Service load-balance tới các Pod do Deployment quản lý.
Client
│
▼
Service (order-service, ClusterIP 10.96.142.201:80)
│ (label selector: app=order-service)
▼
┌─────────────┬─────────────┬─────────────┐
│ Pod #1 │ Pod #2 │ Pod #3 │
│ :8080 │ :8080 │ :8080 │
└─────────────┴─────────────┴─────────────┘
▲ quản lý bởi
Deployment (order-service, replicas: 3)
Để tiện triển khai một lần, bạn có thể gộp cả Deployment và Service vào một file, phân tách bằng ---:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
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
kubectl apply -f order-service-all.yaml
6. Viết test cho readiness endpoint trước khi deploy
Trước khi probe của Kubernetes dựa vào /actuator/health/readiness, bạn nên đảm bảo endpoint này trả đúng trạng thái trong code Spring Boot.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void readinessProbe_shouldReturnUp() {
ResponseEntity<String> response =
restTemplate.getForEntity("/actuator/health/readiness", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("\"status\":\"UP\"");
}
}
Test này gọi TestRestTemplate lên endpoint readiness thật, xác nhận HTTP 200 và body chứa UP. Đây chính là endpoint mà readinessProbe trong Deployment sẽ gọi định kỳ - nếu test fail ở đây, Pod cũng sẽ không bao giờ “Ready” trên cluster.
7. Lỗi thường gặp
- Selector không khớp label: Service hoặc Deployment không tìm thấy Pod nào (Endpoints rỗng) do
selectorvàlabelsviết sai hoặc lệch nhau. Luônkubectl get endpoints <svc-name>để xác nhận. - Thiếu readinessProbe: Service vẫn route traffic vào Pod chưa khởi động xong ứng dụng, gây lỗi 502/503 ngay sau khi deploy.
- Resource requests/limits thiếu: Pod có thể bị scheduler đặt sai node, hoặc bị OOMKilled bất ngờ khi traffic tăng.
- Dùng Pod trần cho production: không có self-healing, mất Pod là mất luôn, phải tạo lại tay.
- Image tag
latest: rolling update không nhận biết thay đổi, Kubernetes nghĩ image không đổi và không pull lại. Luôn dùng tag version cụ thể.
8. Kết luận
Pod là đơn vị chạy container nhỏ nhất, Deployment quản lý vòng đời và scaling của tập Pod, còn Service cung cấp địa chỉ ổn định để truy cập chúng dù Pod bên dưới thay đổi liên tục. Nắm chắc ba khối này là nền tảng để bạn tiến tiếp lên Ingress, ConfigMap/Secret, hay Helm chart sau này. Bước tiếp theo nên thử: deploy một ứng dụng Spring Boot thật, quan sát rolling update, và thử giết một Pod bằng tay để xem Deployment tự phục hồi ra sao.