Capstone: Deploy Ứng Dụng Hoàn Chỉnh và Tổng Kết Series

K
Kai··6 min read

Mười bốn bài qua, ta học từng mảnh ghép riêng lẻ. Bài cuối ghép chúng thành một bức tranh hoàn chỉnh: deploy một ứng dụng nhiều thành phần thật — một frontend chạy nhiều bản sao đứng sau Ingress, và một database có lưu trữ bền vững — dùng gần như mọi khái niệm đã học. Sau đó ta dọn cluster và nhìn lại cả hành trình.

Kiến trúc dự án

Một app web hai tầng kinh điển, mọi thành phần là một mảnh ghép của series:

   Internet
      │
      ▼
   Ingress (capstone.local)            ← Bài 9
      │
      ▼
   Service "frontend" (ClusterIP)      ← Bài 5
      │  cân bằng tải
      ├──► Pod frontend (nginx)        ┐ Deployment 2 bản sao  ← Bài 4
      └──► Pod frontend (nginx)        ┘ + probes + resources  ← Bài 10, 11
            └─ nội dung từ ConfigMap   ← Bài 7

   Service "db" (ClusterIP) ──► Pod postgres   ← Bài 4, 5
                                  ├─ mật khẩu từ Secret   ← Bài 7
                                  ├─ dữ liệu trên PVC      ← Bài 8
                                  └─ readiness probe       ← Bài 10

Tất cả gọn trong một namespace riêng capstone (Bài 6), để dọn dẹp dễ và tách khỏi những gì đang chạy.

Tầng database: Secret + PVC + probe

Database cần ba thứ ta đã học: mật khẩu không hard-code (Secret), dữ liệu không bay hơi (PVC), và Kubernetes biết nó sẵn sàng chưa (readiness probe). Trích manifest:

apiVersion: apps/v1
kind: Deployment
metadata: { name: db, namespace: capstone }
spec:
  replicas: 1
  selector: { matchLabels: { app: db } }
  template:
    metadata: { labels: { app: db } }
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          env:
            - name: POSTGRES_PASSWORD
              valueFrom: { secretKeyRef: { name: db-secret, key: POSTGRES_PASSWORD } }  # Bài 7
          resources:                                                                     # Bài 11
            requests: { cpu: 100m, memory: 128Mi }
            limits:   { cpu: 500m, memory: 256Mi }
          readinessProbe:                                                                # Bài 10
            exec: { command: ["sh","-c","pg_isready -U postgres"] }
          volumeMounts:
            - { name: data, mountPath: /var/lib/postgresql/data }
      volumes:
        - name: data
          persistentVolumeClaim: { claimName: db-data }                                  # Bài 8

Tầng frontend: nhiều bản sao + ConfigMap + Ingress

Frontend là nginx chạy 2 bản sao (sẵn sàng cân bằng tải và chịu lỗi), phục vụ trang lấy từ ConfigMap, có probesresources, và phơi ra ngoài qua Service + Ingress:

apiVersion: apps/v1
kind: Deployment
metadata: { name: frontend, namespace: capstone }
spec:
  replicas: 2
  # ... selector, template ...
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          livenessProbe:  { httpGet: { path: /, port: 80 } }
          readinessProbe: { httpGet: { path: /, port: 80 } }
          volumeMounts:
            - { name: content, mountPath: /usr/share/nginx/html }
      volumes:
        - name: content
          configMap: { name: web-content }     # trang index.html từ ConfigMap

Triển khai toàn bộ chỉ là vài lệnh apply:

kubectl create namespace capstone
kubectl apply -f app.yaml          # configmap, secret, pvc, db deployment+service
kubectl apply -f frontend.yaml     # frontend deployment+service, ingress

Toàn cảnh: mọi mảnh ghép cùng chạy

kubectl get all,ingress,pvc -n capstone
NAME                            READY   STATUS    RESTARTS   AGE
pod/db-574d8bd786-wwbxb         1/1     Running   0          50s
pod/frontend-86658d7ff6-2dzmt   1/1     Running   0          15s
pod/frontend-86658d7ff6-gtftl   1/1     Running   0          15s

NAME               TYPE        CLUSTER-IP     PORT(S)    AGE
service/db         ClusterIP   10.96.135.57   5432/TCP   50s
service/frontend   ClusterIP   10.104.95.30   80/TCP     15s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/db         1/1     1            1           50s
deployment.apps/frontend   2/2     2            2           15s

NAME                                 CLASS   HOSTS            PORTS   AGE
ingress.../capstone                  nginx   capstone.local   80      15s

NAME                            STATUS   CAPACITY   ACCESS MODES   STORAGECLASS
persistentvolumeclaim/db-data   Bound    200Mi      RWO            standard

Một ứng dụng hoàn chỉnh: 2 pod frontend, 1 pod db, hai Service, một Ingress, một PVC Bound. Mọi khái niệm của series hiện diện trong vài chục dòng YAML.

Kiểm chứng: nó thật sự hoạt động

Trang web qua Ingress — request với host capstone.local đi qua Ingress → Service → một trong hai pod frontend → trang từ ConfigMap:

minikube ssh "curl -s -H 'Host: capstone.local' http://localhost/ | grep h1"
<title>KKloud Capstone</title>
<h1>KKloud — Capstone trên minikube</h1>

Database dùng Secret + PVC — ghi và đọc dữ liệu thật, mật khẩu lấy từ Secret, dữ liệu nằm trên PVC bền vững:

kubectl exec -n capstone deploy/db -- psql -U postgres \
  -c "INSERT INTO notes(msg) VALUES('capstone hoạt động'); SELECT * FROM notes;"
INSERT 0 1
 id |        msg
----+--------------------
  1 | capstone hoạt động
(1 row)

Cả hai tầng hoạt động và ăn khớp. Từ một cluster trắng tới một ứng dụng web có database — khai báo hoàn toàn bằng YAML, tái lập được, lưu vào Git được. Đây là toàn bộ series cô đọng lại. Manifest đầy đủ ở repo nghiadaulau/kubernetes-minikube-series, thư mục 14-capstone.

🧹 Dọn dẹp: minikube delete

Học xong thì trả lại máy sự sạch sẽ. Vì mọi thứ nằm trong namespace capstone, xoá namespace là dọn sạch ứng dụng:

kubectl delete namespace capstone     # xoá toàn bộ app + PVC trong đó

Và khi xong cả series, một lệnh xoá luôn cluster:

minikube delete                       # xoá sạch cluster — máy về như chưa có gì

Đây cũng là vẻ đẹp của minikube: học thoải mái rồi xoá không để lại dấu vết, không tốn tiền cloud.

Tổng kết series

Mười lăm bài, từ "Kubernetes là gì" tới một dự án chạy thật:

  • Nền tảng (Bài 0–4): từ container tới orchestration và trạng thái mong muốn; kiến trúc control plane / node; cài minikube; Pod (đơn vị nhỏ nhất); Deployment/ReplicaSet (self-healing, scale, rolling update).
  • Kết nối & cấu hình (Bài 5–9): Service (địa chỉ ổn định + cân bằng tải), Namespace/Label (tổ chức & chất keo selector), ConfigMap/Secret (tách cấu hình), PV/PVC (lưu trữ bền), Ingress (định tuyến HTTP).
  • Vận hành (Bài 10–14): probes (health check), resources/HPA (tài nguyên & autoscale), các workload khác (StatefulSet/DaemonSet/Job), gỡ lỗi, và capstone.

Vài ý cốt lõi đáng mang theo:

  • Trạng thái mong muốn (declarative) — bạn mô tả "đích đến", các control loop kéo hệ thống về đó và giữ ở đó. Đây là linh hồn của Kubernetes (và cũng là tinh thần khai báo từ series Ansible).
  • Mọi thứ qua API server, mọi thứ là object — Pod, Service, ConfigMap... đều là object khai báo bằng YAML, apply rồi để controller lo.
  • Label là chất keo — Deployment, Service, selector đều tìm nhau động qua nhãn; đó là vì sao scale/self-healing/cân bằng tải ăn khớp.
  • Tách biệt mối quan tâm — cấu hình (ConfigMap/Secret) tách khỏi image, lưu trữ (PVC) tách khỏi pod, định tuyến (Service/Ingress) tách khỏi backend. Mỗi mảnh thay được độc lập.

Hướng học tiếp

Series này cố tình dừng ở nền tảng. Từ đây có thể đi sâu:

  • Kubernetes from scratch — dựng cluster bằng tay (kubeadm), hiểu nội tại etcd/scheduler/CNI/CRI. (Một series riêng sắp tới.)
  • Helm — đóng gói và quản lý ứng dụng Kubernetes như "package", cho deploy có tham số, tái lập.
  • Bảo mật & quản trị: RBAC, NetworkPolicy, Pod Security, quản lý secret đúng cách (nhớ cảnh báo base64 ở Bài 7).
  • GitOps (ArgoCD/Flux): để Git là nguồn sự thật, cluster tự đồng bộ theo — mở rộng tự nhiên của tư duy declarative.
  • Operator & CRD: mở rộng chính Kubernetes, cách vận hành database/hệ thống phức tạp ở production.
  • Kết nối các series khác: chạy Kubernetes trên EC2/EKS (series AWS), đóng gói app bằng Docker (series Docker), cấu hình node bằng Ansible (series Ansible), và dựa trên mạng/SSH bạn đã hiểu (series Mạng, Linux).

Cảm ơn bạn đã theo hết series. Bạn giờ không chỉ chạy được kubectl mà hiểu vì sao mỗi đối tượng tồn tại và chúng ghép với nhau ra sao — đủ nền vững để bước vào Kubernetes production và đi tiếp vào những tầng sâu hơn của hệ sinh thái cloud-native.