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.labels gắn nhãn app: 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.requests là tài nguyên tối thiểu scheduler cần có để đặt Pod lên node; limits là mức trần, vượt quá sẽ bị throttle (CPU) hoặc bị kill (memory - OOMKilled).
  • containerPort: 8080 chỉ 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: 3 yê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.matchLabels phải khớp với template.metadata.labels. Đây là liên kết bắt buộc giữa Deployment và Pod nó quản lý.
  • readinessProbe quyết định Pod có nhận traffic hay không (dùng bởi Service); livenessProbe quyế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-service khớp với label trên Pod (do Deployment gắn vào template.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: 80 là port mà Service lắng nghe nội bộ cluster; targetPort: 8080 là 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 NodePort cho 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ên LoadBalancer hoặ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 selector và labels viết sai hoặc lệch nhau. Luôn kubectl 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.

kubernetesdevopscontainerorchestration