DevOps 5 — Kubernetes & Điều phối Container

13 thg 7, 2026 3 lượt xem
#orchestration
#kubernetes
#devops
#k8s

Mở đầu: từ một container đến hàng trăm container

Ở các bài trước, bạn đã đóng gói ứng dụng vào container (Docker). Một container chạy ngon lành trên máy bạn, thậm chí trên một server. Nhưng khi hệ thống lớn lên, bạn sẽ gặp những câu hỏi mà một mình Docker không trả lời được:

  • Container bị crash lúc 2 giờ sáng thì ai khởi động lại nó?
  • Lượng truy cập tăng gấp 10 lần, làm sao tự động thêm bản sao của ứng dụng?
  • Có 50 container chạy trên 5 máy chủ, làm sao phân phối chúng cho hợp lý, và nếu một máy chết thì chuyện gì xảy ra?
  • Làm sao cập nhật phiên bản mới mà không làm gián đoạn dịch vụ?
  • Các container cần tìm thấy nhau qua mạng như thế nào khi địa chỉ IP của chúng thay đổi liên tục?

Đây chính là bài toán điều phối container (container orchestration), và Kubernetes (viết tắt K8s — số 8 thay cho 8 chữ cái giữa "K" và "s") là công cụ phổ biến nhất để giải quyết. Bài này sẽ đi từ con số 0: vì sao cần orchestration, kiến trúc K8s, các đối tượng cốt lõi, cách scaling và tự phục hồi, cho đến khi nào bạn không nên dùng K8s.

Vì sao cần điều phối khi có nhiều container

Hãy tưởng tượng bạn quản lý một nhà hàng. Một đầu bếp (container) thì dễ. Nhưng khi có 30 đầu bếp ở 5 bếp khác nhau, bạn cần một quản lý ca (orchestrator): ai làm món gì, bếp nào quá tải thì chuyển bớt, đầu bếp ốm thì gọi người thay, giờ cao điểm thì tăng người. Bạn không thể tự tay điều phối từng người.

Orchestration tự động hóa những việc này cho container:

  • Lập lịch (scheduling): quyết định container nào chạy trên máy nào, dựa trên tài nguyên còn trống (CPU, RAM).
  • Tự phục hồi (self-healing): phát hiện container chết hoặc máy hỏng và tự khởi động lại / dời sang máy khác.
  • Mở rộng (scaling): tăng/giảm số bản sao container theo tải.
  • Khám phá dịch vụ & cân bằng tải (service discovery & load balancing): cho phép container tìm nhau qua tên cố định và chia đều lưu lượng.
  • Triển khai có kiểm soát (rolling update): cập nhật phiên bản từ từ, có thể quay lui (rollback) nếu lỗi.
  • Quản lý cấu hình & bí mật: tách config và mật khẩu ra khỏi image.

Bạn có thể làm thủ công những việc này bằng script, nhưng khi quy mô lớn lên, mọi thứ trở nên cực kỳ phức tạp và dễ sai. Kubernetes biến tất cả thành khai báo (declarative): bạn mô tả "trạng thái mong muốn" (tôi muốn 3 bản sao của app này chạy), còn K8s liên tục so sánh và đưa hệ thống về đúng trạng thái đó.

Tư duy cốt lõi của K8s: Bạn không ra lệnh "hãy khởi động container này". Bạn khai báo "tôi muốn luôn có 3 container như thế này tồn tại". K8s tự lo phần còn lại — đây gọi là mô hình reconciliation loop (vòng lặp hòa giải).

Kiến trúc Kubernetes

Một cluster Kubernetes gồm hai phần: Control Plane (bộ não, ra quyết định) và các Worker Node (cơ bắp, chạy ứng dụng thật).

Control Plane: bộ não của cluster

  • kube-apiserver: cổng vào duy nhất của cluster. Mọi lệnh (từ kubectl, từ CI/CD, từ các thành phần nội bộ) đều đi qua đây. Nó xác thực, phân quyền và lưu thay đổi vào etcd.
  • etcd: kho dữ liệu key-value phân tán, lưu toàn bộ trạng thái của cluster (có những Pod nào, cấu hình ra sao). Đây là "nguồn sự thật" duy nhất. Mất etcd là mất cluster, nên nó cần được sao lưu cẩn thận.
  • kube-scheduler: khi có Pod mới chưa được gán node, scheduler chọn node phù hợp dựa trên tài nguyên, ràng buộc (affinity, taint/toleration), v.v.
  • kube-controller-manager: chạy nhiều controller — mỗi controller là một vòng lặp liên tục so sánh trạng thái thực tế với trạng thái mong muốn rồi sửa cho khớp. Ví dụ: nếu bạn muốn 3 bản sao mà chỉ còn 2, controller sẽ tạo thêm 1.

Worker Node: nơi ứng dụng thật sự chạy

  • kubelet: đại diện (agent) trên mỗi node. Nó nhận lệnh từ api-server và đảm bảo các container trong Pod được chạy đúng và khỏe mạnh.
  • kube-proxy: quản lý quy tắc mạng trên node, giúp định tuyến lưu lượng đến đúng Pod (phục vụ cho Service).
  • container runtime: phần mềm thực sự chạy container, ví dụ containerd hoặc CRI-O (Docker engine không còn được dùng trực tiếp làm runtime trong K8s mới, nhưng image Docker vẫn dùng bình thường).

Các đối tượng cốt lõi

K8s quản lý mọi thứ qua các đối tượng (object) được khai báo bằng file YAML. Hãy đi từ nhỏ đến lớn.

Pod — đơn vị nhỏ nhất

Pod là đơn vị triển khai nhỏ nhất trong K8s. Một Pod bao bọc một hoặc vài container dùng chung mạng (cùng IP) và bộ nhớ tạm. Thường một Pod chỉ chứa một container chính (ứng dụng của bạn), đôi khi kèm container phụ ("sidecar", ví dụ thu log).

Điểm quan trọng: Pod là phù du (ephemeral). Nó có thể bị xóa và tạo lại bất cứ lúc nào, mỗi lần lại có IP mới. Vì vậy, bạn gần như không bao giờ tạo Pod trực tiếp mà dùng các đối tượng cấp cao hơn để quản lý chúng.

ReplicaSet — đảm bảo số lượng bản sao

ReplicaSet đảm bảo luôn có đúng N bản sao (replica) của một Pod đang chạy. Nếu một Pod chết, ReplicaSet tạo Pod mới ngay. Tuy nhiên bạn cũng hiếm khi dùng ReplicaSet trực tiếp.

Deployment — quản lý vòng đời ứng dụng

Deployment là đối tượng bạn dùng nhiều nhất cho ứng dụng không lưu trạng thái (stateless). Nó quản lý ReplicaSet bên dưới và cung cấp:

  • Rolling update: cập nhật phiên bản mới từng chút một.
  • Rollback: quay về phiên bản cũ nếu có lỗi.
  • Scaling: thay đổi số replica dễ dàng.

Nói cách khác: bạn khai báo Deployment, Deployment tạo ReplicaSet, ReplicaSet tạo Pod. (Với ứng dụng cần lưu trạng thái và danh tính ổn định như database, người ta dùng StatefulSet; chạy đúng một bản trên mỗi node thì dùng DaemonSet; tác vụ chạy một lần thì dùng Job/CronJob.)

Service — địa chỉ mạng ổn định

Vì Pod có IP thay đổi liên tục, ta cần một "địa chỉ cố định" để truy cập chúng. Service chính là điều đó: nó nhóm các Pod (qua nhãn — label) lại sau một IP và tên DNS cố định, đồng thời cân bằng tải giữa các Pod.

Có ba loại Service phổ biến:

  • ClusterIP (mặc định): chỉ truy cập được trong nội bộ cluster. Dùng cho giao tiếp giữa các dịch vụ với nhau (ví dụ backend gọi database).
  • NodePort: mở một cổng cố định (30000–32767) trên mọi node, cho phép truy cập từ bên ngoài qua IP-của-node:cổng. Đơn giản nhưng thô sơ, ít dùng cho production.
  • LoadBalancer: yêu cầu nhà cung cấp đám mây (AWS, GCP, Azure) cấp một bộ cân bằng tải bên ngoài với IP công khai. Đây là cách phổ biến để phơi dịch vụ ra Internet trên cloud.

Ingress — định tuyến HTTP thông minh

Tạo một LoadBalancer cho mỗi dịch vụ thì tốn kém. Ingress giải quyết bằng cách cung cấp một điểm vào duy nhất cho HTTP/HTTPS, rồi định tuyến theo tên miền hoặc đường dẫn:

  • shop.example.com → Service frontend
  • api.example.com/orders → Service orders
  • api.example.com/users → Service users

Ingress còn xử lý TLS/HTTPS. Lưu ý: Ingress chỉ là quy tắc; bạn cần cài một Ingress Controller (như NGINX Ingress, Traefik) để thực thi các quy tắc đó.

ConfigMap & Secret — tách cấu hình khỏi code

  • ConfigMap: lưu cấu hình không nhạy cảm dạng key-value (URL, feature flag, tham số). Bạn nạp chúng vào Pod qua biến môi trường hoặc file. Nhờ đó cùng một image có thể chạy ở dev/staging/prod chỉ bằng cách đổi ConfigMap.
  • Secret: giống ConfigMap nhưng dành cho dữ liệu nhạy cảm (mật khẩu, token, chứng chỉ). Mặc định Secret chỉ được mã hóa base64 (không phải mã hóa thật!), nên trong production cần bật encryption at rest cho etcd hoặc dùng giải pháp như Sealed Secrets, Vault.

Namespace — chia ngăn cluster

Namespace chia một cluster thành nhiều "ngăn" logic, giúp tách biệt môi trường hoặc nhóm dự án (ví dụ dev, staging, prod, hoặc theo team). Tài nguyên trong các namespace khác nhau không xung đột tên, và bạn có thể đặt quota tài nguyên cho từng namespace.

Volume & PVC — lưu trữ bền vững

Khi Pod chết, dữ liệu trong nó mất hết. Để lưu dữ liệu lâu dài (database, file upload), K8s dùng:

  • Volume: vùng lưu trữ gắn vào Pod.
  • PersistentVolume (PV): một mảnh lưu trữ trong cluster (đĩa cloud, NFS...) do quản trị viên hoặc StorageClass cung cấp.
  • PersistentVolumeClaim (PVC): "yêu cầu" lưu trữ từ phía ứng dụng — Pod xin "tôi cần 10Gi", K8s khớp với một PV phù hợp. Cách tách biệt này giúp ứng dụng không cần biết chi tiết hạ tầng lưu trữ bên dưới.

Scaling & Self-healing

Self-healing: tự phục hồi

Đây là sức mạnh lớn nhất của K8s. Nhờ vòng lặp hòa giải:

  • Pod crash → kubelet khởi động lại container.
  • Cả node chết → các Pod trên node đó được lên lịch lại trên node khỏe mạnh khác.
  • Số replica giảm dưới mức mong muốn → controller tạo thêm Pod.

Để K8s biết Pod có thực sự khỏe hay không, bạn khai báo probe:

  • livenessProbe: kiểm tra container còn "sống" không. Nếu fail, K8s khởi động lại nó.
  • readinessProbe: kiểm tra container đã sẵn sàng nhận lưu lượng chưa. Nếu fail, Service tạm thời ngừng gửi request đến Pod đó (nhưng không khởi động lại).
  • startupProbe: dành cho ứng dụng khởi động chậm, tránh bị liveness giết oan trong lúc đang khởi động.

Scaling: mở rộng theo tải

  • Scaling thủ công: kubectl scale deployment my-app --replicas=5.
  • Horizontal Pod Autoscaler (HPA): tự động tăng/giảm số Pod dựa trên chỉ số như CPU, RAM hoặc metric tùy chỉnh. Ví dụ: "nếu CPU trung bình > 70%, tăng replica, tối đa 10". HPA cần Metrics Server để đọc số liệu sử dụng tài nguyên.
  • (Ngoài ra còn VPA điều chỉnh tài nguyên cho mỗi Pod, và Cluster Autoscaler thêm/bớt cả node — nhưng HPA là cái bạn dùng đầu tiên.)

Rolling Update & Rollback

Deployment cập nhật phiên bản theo kiểu rolling: thay từng nhóm nhỏ Pod thay vì tắt hết một lúc, nhờ đó dịch vụ không gián đoạn.

  • maxUnavailable: tối đa bao nhiêu Pod được phép "tắt" cùng lúc trong khi cập nhật.
  • maxSurge: tối đa bao nhiêu Pod "dư" được tạo thêm so với mức mong muốn.

Quy trình: tạo Pod mới (phiên bản mới) → chờ chúng Ready (qua readinessProbe) → xóa Pod cũ → lặp lại cho đến khi xong. Nếu phát hiện phiên bản mới lỗi, bạn rollback về phiên bản trước chỉ bằng một lệnh, vì K8s lưu lịch sử các ReplicaSet.

# Cap nhat image (kich hoat rolling update)
kubectl set image deployment/web-app web=myapp:2.0

# Theo doi qua trinh trien khai
kubectl rollout status deployment/web-app

# Xem lich su cac phien ban
kubectl rollout history deployment/web-app

# Quay lui ve phien ban truoc neu co loi
kubectl rollout undo deployment/web-app

kubectl cơ bản

kubectl là công cụ dòng lệnh để giao tiếp với cluster. Một số lệnh thiết yếu:

# Xem tai nguyen
kubectl get pods                      # liet ke Pod
kubectl get pods -o wide              # kem IP va node
kubectl get deployments,services      # nhieu loai cung luc
kubectl get all -n staging            # tat ca trong namespace staging

# Chi tiet & go loi
kubectl describe pod web-app-xxxx     # mo ta chi tiet, su kien
kubectl logs web-app-xxxx             # xem log container
kubectl logs -f web-app-xxxx          # theo doi log truc tiep
kubectl exec -it web-app-xxxx -- sh   # vao shell ben trong Pod

# Trien khai tu file YAML (declarative)
kubectl apply -f deployment.yaml      # tao/cap nhat theo file
kubectl delete -f deployment.yaml     # xoa theo file

# Scaling thu cong
kubectl scale deployment web-app --replicas=5

# Chuyen tiep cong de test local
kubectl port-forward service/web-app 8080:80

Quy tắc nên nhớ: ưu tiên cách declarative (kubectl apply -f) với file YAML lưu trong Git, thay vì gõ lệnh tạo tài nguyên trực tiếp. Điều này giúp mọi thay đổi được kiểm soát phiên bản — nền tảng cho GitOps mà ta sẽ gặp ở các bài sau.

Ví dụ manifest: Deployment + Service

Dưới đây là một ví dụ hoàn chỉnh, dễ hiểu: triển khai một web app với 3 bản sao và phơi nó ra qua Service.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web-app
spec:
  replicas: 3                  # muon luon co 3 ban sao
  selector:
    matchLabels:
      app: web-app             # Deployment quan ly cac Pod co nhan nay
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:                    # khuon mau de tao Pod
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: web
          image: myapp:1.0
          ports:
            - containerPort: 8080
          env:
            - name: APP_ENV
              valueFrom:
                configMapKeyRef:
                  name: web-config
                  key: app_env
          resources:           # gioi han tai nguyen
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi"
          readinessProbe:      # khi nao san sang nhan request
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:       # khi nao can khoi dong lai
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: ClusterIP             # truy cap noi bo cluster
  selector:
    app: web-app              # chon cac Pod co nhan nay
  ports:
    - port: 80                # cong cua Service
      targetPort: 8080        # cong cua container

Áp dụng:

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get pods         # se thay 3 Pod web-app dang chay

Hãy chú ý sợi dây nối các đối tượng: Service chọn Pod qua selector: app: web-app, khớp với labels: app: web-appDeployment gắn lên từng Pod. Đây là cơ chế label & selector — chất keo gắn kết hầu hết mọi thứ trong K8s.

Khi nào KHÔNG cần Kubernetes

Kubernetes mạnh mẽ nhưng phức tạp và tốn chi phí vận hành. Nó không phải lựa chọn mặc định cho mọi dự án. Bạn nên cân nhắc không dùng K8s khi:

  • Ứng dụng nhỏ, ít lưu lượng: một app đơn giản, vài người dùng — một VM với Docker Compose, hoặc một dịch vụ PaaS (Render, Railway, Fly.io, App Engine) là quá đủ.
  • Đội ngũ nhỏ, thiếu kinh nghiệm vận hành: K8s đòi hỏi hiểu biết về mạng, lưu trữ, bảo mật, monitoring. Chi phí học và bảo trì có thể lớn hơn lợi ích.
  • Ưu tiên ra mắt nhanh (MVP/startup giai đoạn sớm): thời gian dựng cluster có thể dùng để làm sản phẩm. Bắt đầu đơn giản, chuyển sang K8s khi thực sự cần scale.
  • Tải ổn định, không cần co giãn: nếu lưu lượng đều đặn và một vài server cố định là đủ, sự linh hoạt của K8s không đem lại nhiều giá trị.
  • Workload không phù hợp: ví dụ chỉ là một tác vụ batch định kỳ — một CronJob trên server hoặc một serverless function có thể đơn giản hơn nhiều.

Một giải pháp dung hòa: nếu cần K8s nhưng không muốn tự vận hành control plane, hãy dùng managed Kubernetes (EKS, GKE, AKS) — nhà cung cấp lo phần control plane, bạn chỉ tập trung vào ứng dụng.

Nguyên tắc: chọn công cụ vừa đủ phức tạp cho bài toán. Dùng K8s khi bạn thực sự có nhu cầu về quy mô, độ sẵn sàng cao, triển khai liên tục và nhiều dịch vụ — chứ không phải vì nó "thời thượng".

Tóm tắt

  • Orchestration tự động hóa việc lập lịch, tự phục hồi, mở rộng, khám phá dịch vụ và triển khai cho nhiều container — những việc làm thủ công sẽ không khả thi ở quy mô lớn.
  • Kiến trúc K8s gồm Control Plane (api-server, etcd, scheduler, controller-manager) ra quyết định, và các Worker Node (kubelet, kube-proxy, container runtime) chạy ứng dụng.
  • Đối tượng cốt lõi: Pod (đơn vị nhỏ nhất, phù du) → ReplicaSet → Deployment (quản lý vòng đời); Service (ClusterIP/NodePort/LoadBalancer) cho địa chỉ ổn định; Ingress định tuyến HTTP; ConfigMap/Secret cho cấu hình; Namespace chia ngăn; Volume/PVC cho lưu trữ bền vững.
  • K8s hoạt động theo mô hình khai báovòng lặp hòa giải: bạn mô tả trạng thái mong muốn, K8s tự đưa hệ thống về đúng trạng thái đó — đây là gốc rễ của self-healing và scaling (HPA).
  • Rolling update cho phép cập nhật không gián đoạn và rollback dễ dàng.
  • K8s không dành cho mọi dự án; với ứng dụng nhỏ hay đội ngũ thiếu kinh nghiệm vận hành, các lựa chọn đơn giản hơn thường tốt hơn.

Tự kiểm tra

  1. Giải thích bằng lời của bạn: vì sao Pod được coi là "phù du" và tại sao ta nên dùng Deployment thay vì tạo Pod trực tiếp?
  2. Mô tả vai trò của bốn thành phần trong Control Plane (api-server, etcd, scheduler, controller-manager). Nếu mất etcd thì điều gì xảy ra?
  3. So sánh ba loại Service ClusterIP, NodePort và LoadBalancer. Trường hợp nào bạn sẽ dùng từng loại?
  4. "Vòng lặp hòa giải" (reconciliation loop) là gì và nó liên quan thế nào đến tính tự phục hồi của K8s?
  5. Phân biệt livenessProbereadinessProbe. Điều gì xảy ra với lưu lượng khi readinessProbe thất bại?
  6. Nêu ít nhất ba tình huống mà bạn không nên dùng Kubernetes, và đề xuất giải pháp thay thế phù hợp.

Đọc tiếp

DevOps 6 — Infrastructure as Code

Bài viết liên quan

Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.

13 thg 7, 2026 3

Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.

13 thg 7, 2026 3

Ba trụ cột logging–metrics–tracing, Prometheus/Grafana, alerting, SLI/SLO/SLA và golden signals.

13 thg 7, 2026 3

Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.

13 thg 7, 2026 3