---
id: kubernetes-manifests
title: "Deployment và Service"
track: kubernetes
trackTitle: "Kubernetes"
order: 2
level: core
status: published
estimatedMinutes: 50
prerequisites:
  - kubernetes-architecture
next: kubernetes-kubectl
reviewedAgainst: 2026-10-15
sources:
  - title: "KodeKloud Engineer — 100 Days of DevOps"
    url: https://engineer.kodekloud.com/practice
  - title: "Deployment"
    url: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
  - title: "Service"
    url: https://kubernetes.io/docs/concepts/services-networking/service/
  - title: "Configure probes"
    url: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
  - title: "Resource management"
    url: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
---

# Deployment và Service

Hai file dưới dùng `nginx:1.27-alpine` để cluster lab pull được. Tag còn trên [Docker Hub — nginx](https://hub.docker.com/_/nginx) thì giữ; đổi tag thì sửa cả probe nếu path khác.

## Để làm gì

Viết Deployment giữ replica, Service cấp tên ổn định, và khai `requests`/`limits` để scheduler biết xếp Pod chỗ nào.

## Gồm những gì

- Deployment `apps/v1`, Service `v1` kiểu `ClusterIP`.
- Ba chỗ label phải cùng giá trị.
- `containerPort`, `port`, `targetPort`.
- `resources.requests` và `resources.limits`.
- Readiness probe.

Rolling update và `kubectl rollout` ở bài sau. Volume, Secret, init/sidecar tách bài riêng. Ingress chưa có ở đây.

## Dùng khi nào

Process stateless, cần vài bản giống nhau. App ghi dữ liệu trong container thì Pod thay là mất — gắn volume trước khi có data thật.

Image `my-app:v1` không chạy nếu registry cluster không có. Lab dùng image công khai để kiểm YAML.

## Kiến thức cốt lõi

`apiVersion` + `kind` chọn schema. Deployment: `apps/v1`. Service: `v1`.

`spec.selector.matchLabels` của Deployment phải khớp `template.metadata.labels`. Lệch một key thì API từ chối, hoặc ReplicaSet không nhận Pod. Docs: đừng sửa selector sau khi đã tạo; muốn đổi thì xóa Deployment rồi tạo lại.

`replicas` là số Pod muốn có. Đổi số này không tạo revision rollout — chỉ scale. Đổi image hoặc label trong template mới ra ReplicaSet mới.

`resources.requests` là số scheduler dùng để xếp. `resources.limits` là trần runtime cắt. Bỏ cả hai: node có thể nhận quá tải, container không có trần. Lab 100 Days hay bắt khai đúng CPU/memory trên container. Đơn vị thường gặp: `100m` = 0,1 CPU; memory `64Mi`.

`readinessProbe` quyết định Service có đưa Pod vào endpoint không. Pod `Running` mà probe fail thì chưa nhận traffic. Probe lab gọi `/` vì Nginx phục vụ path đó. App thật nên có `/healthz` và chỉ 200 khi dependency bắt buộc sẵn sàng.

Service `port` là cổng client trong cluster gọi. `targetPort` là cổng trên Pod. Nginx official nghe `80`. `targetPort: http` trỏ tên port trong `containerPort`, đổi số mà giữ tên thì mapping còn.

`metadata.labels` trên Service để `kubectl get -l`. Label đó không chọn Pod. Pod do `spec.selector`.

## Lệnh và code mẫu

`deploy/web.yaml`:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          ports:
            - name: http
              containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 3
            periodSeconds: 10
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
```

`deploy/web-service.yaml`:

```yaml
apiVersion: v1
kind: Service
metadata:
  name: web-service
  labels:
    app: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - name: http
      port: 80
      targetPort: http
```

Ba chỗ `app: web`: template, selector Deployment, selector Service.

App Node nghe `3000` thì sửa `containerPort`, probe, `targetPort` cùng lúc. Quên một chỗ thì Ready mà traffic vào sai port.

Kiểm cú pháp, chưa gửi cluster:

```bash
kubectl apply --dry-run=client -f "$HOME/devops-lab/k8s"
```

## Thực hành

1. Lưu hai file trong `$HOME/devops-lab/k8s`.
2. Đối chiếu ba chỗ `app: web`.
3. Chạy dry-run ở trên.
4. Có cluster thì bỏ `--dry-run=client`, sang bài `kubectl`.

## Kết quả mong đợi

Dry-run in `deployment.apps/web-app` và `service/web-service`. Chưa có Pod cho đến khi apply thật.

## Checklist

- [ ] Đọc đúng `apiVersion` của Deployment và Service.
- [ ] Khớp ba chỗ label.
- [ ] Phân biệt `port` và `targetPort`.
- [ ] `requests` cho scheduler, `limits` cho trần runtime.
- [ ] Readiness fail thì Service không có endpoint dù Pod đang chạy.

## Thẻ ôn

### kubernetes-manifests-label
kind: trap
cue: Ba chỗ phải cùng label: template Pod, selector Deployment, selector Service. Tên Deployment không thay được.

Q: Deployment tên `nginx-deploy`, template `app: nginx`. Service để `selector.app: nginx-deploy`. Pod lên Running. `curl` Service thì sao?

A: Không tới Pod. Service chỉ ghép label trên Pod. `nginx-deploy` là tên object, không phải label. Sửa selector thành `app: nginx`, hoặc đổi label template cho khớp — cả ba chỗ một giá trị.

### kubernetes-manifests-port
kind: distinguish
cue: `port` là cổng gọi vào Service. `targetPort` là cổng process trong Pod.

Q: Nginx nghe 80 trong container. Service khai `port: 8080`, `targetPort: 80`. App khác gọi `http://web-service:8080`. Request vào container port nào?

A: Vào 80. Client gọi 8080 trên ClusterIP. kube-proxy chuyển tới `targetPort` 80 trên Pod. Gọi `:80` trên Service thì không có listener ở đó, trừ khi bạn cũng khai `port: 80`. Đảo `targetPort` thành 8080 trong khi process vẫn nghe 80 thì endpoint chết.

### kubernetes-manifests-resources
kind: distinguish
cue: `requests` để xếp node. `limits` là trần. Lab hay bắt khai cả hai trên container.

Q: Pod không có `requests`/`limits`. Scheduler xếp thế nào, và container có bị cắt CPU/RAM không?

A: Không có request thì scheduler không giữ chỗ; node dễ nhận quá tải. Không có limit thì container không có trần, một process phình là đụng hàng xóm trên node. Khai `requests` cho xếp, `limits` cho cắt. `100m` là 0,1 CPU.

### kubernetes-manifests-readiness
kind: trap
cue: Running chưa phải Ready. Probe fail thì Service bỏ Pod khỏi endpoint.

Q: `kubectl get pod` in Running, READY 0/1. Service không có endpoint. Xem gì trước khi tăng replica?

A: `kubectl describe pod` — Events và điều kiện Ready. Thường probe trỏ path không tồn tại, hoặc app chưa listen. Tăng replica chỉ thêm bản cùng lỗi. Sửa probe hoặc app, đợi 1/1 rồi mới tính scale.

### kubernetes-manifests-replicas-vs-image
kind: distinguish
cue: Đổi `replicas` là scale, không tạo revision. Đổi image trong template mới ra ReplicaSet mới.

Q: Bạn sửa `replicas: 3` thành `5` rồi apply. `kubectl rollout history` có thêm revision không?

A: Không. Docs Deployment: chỉ đổi Pod template (image, label, env, …) mới tạo revision. Scale không. Rollback sau này không đụng số replica bạn vừa tăng.

## Lỗi thường gặp

**Selector trỏ tên Deployment.** `spec.selector` không đọc `metadata.name`.

**Probe path app không phục vụ.** Pod chạy, không Ready, Service trống.

**`image: nginx:latest`.** Lần pull sau có thể khác bản. Pin tag. Production thì pin digest.

**Chỉ khai `limits`, quên `requests`.** QoS và chỗ xếp khác với khi khai đủ cặp. Lab hay chấm đúng số đã ra đề — đọc đề, đừng đoán.

**`containerPort` 80, `targetPort` 8080.** Process vẫn nghe 80. Traffic Service không vào.

## Nguồn

1. [KodeKloud Engineer — 100 Days of DevOps](https://engineer.kodekloud.com/practice) — đề cương ngày Deploy Applications with Kubernetes Deployments, Set Resource Limits, Deploy Nginx Web Server. Trang practice cần đăng nhập. Bài này viết lại lệnh, không chép lab.
2. [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) — replica, template, selector.
3. [Service](https://kubernetes.io/docs/concepts/services-networking/service/) — port, selector, endpoint.
4. [Configure probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) — readiness.
5. [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) — request và limit.

## Tiếp theo

[kubectl](./03-kubectl.md)
