Phần 1: Storage
1. Storage Class là gì?
Storage Class là một khái niệm cho phép quản trị viên định nghĩa và cung cấp nhiều loại lưu trữ khác nhau (như SSD tốc độ cao, ổ đĩa từ, hoặc dịch vụ đám mây) mà không cần tạo ổ đĩa thủ công trước cho từng yêu cầu.
Khi người dùng tạo một PersistentVolumeClaim (PVC), Kubernetes sẽ tự động sử dụng StorageClass tương ứng để cung cấp một PersistentVolume (PV) phù hợp, giúp tự động hóa việc cấp phát lưu trữ linh hoạt cho các ứng dụng.
Các thống số cần thiết của StorageClass:
- Loại storage nào (SSD, HDD, NFS, Ceph, …)
- Các tạo volume (dynamic provisioning)
- Thông số cấu hình (IOPS, dung lượng, chế độ access)
2. Cách hoạt động
- Admin định nghĩa 1 StorageClass (ví dụ: SSD, trên GCP, EBS trên AWS)
- Developer chỉ cần tạo PVC và ghi tên Storage Class
- Kubernetes sẽ tự động tạo Volume đúng loại theo nhu cầu
3. Ví dụ
Storage trên On Premise có dạng NFS, GFS, Ceph, …
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
provisioner: kubernetes.io/no-provisioner-> nghĩa là không có dynamic provisioning- Thường rtong cloud (AWS, GCP, Azue), provisioner sẽ là driver để tự động tạo ổ đĩa (EBS, GCE-PD,..)
- Nhưng ở đây là no-provisioner –> admin phải tự tạo PersistentVolume (PV) thủ công và gắn với StorageClass.
- Phù hợp khi dùng NFS, Ceph, GlusterFS,… vì k8s không tự tạo ổ được mà chỉ gắn ổ đã có sẵn.
volumeBindingMode: WaitingForFirstConsumer- Volume(PV) chỉ được bind khi Pod thực sự được schedule.
- Tránh tình trạng volume gắn nhầm node và khi Pod chưa chạy.
- Rất hữu ích trong môi trường multi-node.
StorageClass (SSD trên GCP)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
PVC sử dụng StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd
=> k8s sẽ tự tạo ra PersistentVolume 10Gi SSD cho Pod
Phần 2: PV và PVC trong K8S
1. Khái niệm PV và PVC
PV (PersistentVolume):ổ cứng thật (admin chuẩn bị trước hoặc cloud driver tạo ra)PVC (PersistentVolumeClaim):phiếu đặt ổ cứng (developer viết trong manifest của Pod)- K8s Scheduler thấy PVC -> đi tìm PV phù hợp để bind –> Pod mới mount được volume
PV: Tài nguyên thực (cung)PVC: Yêu cầu sử dụng (cầu)AccessMode: Quy định cách Pod truy cập (1 node, nhiều node, read-only,..)
2. Cơ chế AccessMode trong Kubernetes
Trong kubernetes, PersistentVolume(PV), PersistentVolumeClaim(PVC) hỗ trợ các chế độ đọc/ghi khác nhau:
| AccessMode | Viết tắt | Ý nghĩa | Ví dụ sử dụng |
|---|---|---|---|
| ReadWriteOnce | RWO | Volume chỉ được gắn (mount) để đọc/ghi bởi 1 node duy nhất cùng lúc | Dùng cho hầu hết database (MySQL, PostgreSQL, MariaDB, MongoDB, …) |
| ReadOnlyMany | ROX | Volume có thể gắn đọc-only từ nhiều node | Dùng cho chia sẻ file tĩnh, cấu hình, dữ liệu không thay đổi |
| ReadWriteMany | RWX | Volume có thể gắn đọc/ghi đồng thời từ nhiều node | Dùng cho NFS, GlusterFS, CephFS → nhiều Pod truy cập và ghi dữ liệu chung |
| ReadWriteOncePod | RWOP | (K8s 1.22+) Chỉ cho phép 1 Pod duy nhất gắn volume để đọc/ghi, kể cả trên cùng một Node | Hữu ích khi muốn đảm bảo dữ liệu tuyệt đối không bị nhiều Pod truy cập ghi cùng lúc |
3. So sánh PersistentVolume(PV) và PersistentVolumeClaim(PVC)
| Thuộc tính | PV (PersistentVolume) | PVC (PersistentVolumeClaim) |
|---|---|---|
| Vai trò | Là tài nguyên lưu trữ vật lý (storage real): ổ đĩa, NFS, Ceph, EBS… | Là “yêu cầu” tài nguyên từ Pod đến PV |
| Ai tạo | Thường do Admin tạo (hoặc Dynamic Provisioner qua StorageClass) | Do Developer/Pod khai báo để xin storage |
| Quản lý bởi | Cluster-level object (giống tài nguyên chung trong cluster) | Namespaced object (nằm trong namespace của Pod) |
| Binding | Một PV có thể gắn với nhiều PVC? ❌ Không. Mỗi PV chỉ bind được với 1 PVC tại một thời điểm | Một PVC sẽ bind đúng với 1 PV thỏa điều kiện |
| Dung lượng | Xác định sẵn dung lượng (vd: 10Gi, 100Gi) | Yêu cầu dung lượng (vd: cần 10Gi) |
| AccessMode | Định nghĩa volume hỗ trợ RWO, ROX, RWX… | Phải khớp với AccessMode của PV để bind được |
| Tuổi thọ | Tồn tại độc lập với Pod. Pod xoá đi thì PV vẫn còn (tuỳ ReclaimPolicy) | PVC gắn liền với Pod, Pod cần PVC để mount volume |
| ReclaimPolicy | Retain, Recycle, Delete → quyết định PV khi PVC bị xoá | Không có ReclaimPolicy, vì PVC chỉ là request, không giữ tài nguyên thực sự |
4. Triển khai PV và PVC
4.1 Cài đặt và cấu hình NFS server (Thực hiện trên server nfs độc lập)
sudo apt install nfs-server -y
sudo mkdir /data
sudo chown -R nobody:nogroup /data
sudo chmod -R 777 /data
sudo vi /etc/exports /data *(rw,sync,no_subtree_check)
sudo exportfs -rav
sudo systemctl restart nfs-server
4.2 Cài đặt và cấu hình NFS client (trên tất cả các node của cụm k8s)
sudo apt install nfs-common -y
4.3 Cấu hình PV
File pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
nfs:
path: /data
server: 10.100.1.21 #chú ý thay đổi địa chỉ IP tương ứng của bạn
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-storage
Triển khai qua rancher
Vảo rancher -> Import chọn namespace -> Paste nội dụng file


4.4 Cấu hình PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: beobeo-ecommerce
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: nfs-storage


4.5 Pod openlitespeed mount dữ liệu từ Pod vào thư mục /data của NFS
File Deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-blog
labels:
app: wordpress-blog-selector
namespace: beobeo-ecommerce
spec:
selector:
matchLabels:
app: wordpress-blog-selector
template:
metadata:
labels:
app: wordpress-blog-selector
namespace: beobeo-ecommerce
spec:
containers:
- image: litespeedtech/openlitespeed:latest
imagePullPolicy: Always
name: blog
ports:
- containerPort: 7080
name: tcp-mgmt
protocol: TCP
- containerPort: 80
name: tcp-http
protocol: TCP
volumeMounts:
- mountPath: /var/www
name: wwwdata
volumes:
- name: wwwdata
persistentVolumeClaim:
claimName: nfs-pvc
replicas: 1

Sau khi tạo Deployment trạng thái PVC đã chuyển trạng thái từ pending qua Bound

Trên nfs-server ta đã thấy đã mount thành công


Phần 3: StateFulSet
1. StateFulSet là gì?
StateFulSet và Stateless
Các loại ứng dụng có thể chia làm 2 loại là stateful application và stateless application. Điểm khác nhau cơ bản là các ứng dụng stateless không lưu trạng thái xử lý trước đó, mọi request tới đều được xử lý như một yêu cầu mới hoàn toàn mới, không liên quan gì tới các xử lý trước đó. Triển khai ứng dụng stateless dưới dạng Deployment, do các yêu cầu xử lý độc lập nhau, có thể dễ dàng scale ứng dụng lên (tăng/giảm số lượng Pod) để xử lý request.
Các ứng dụng stateful thường thấy như các loại database (MySQL, Elasticsearch,…) là các ứng dụng cần duy trì trạng thái và dữ liệu qua nhiều phiên làm việc. Triển khai ứng dụng stateful dưới dạng StateFulSet, ví dụ như mysql, mongodb, elasticsearch đều được triển khai dưới dạng statefulset trên kubernetes
Deployment
- Các Pod của Deployment là hoàn toàn giống nhau (Identical) và có thể thay thể lẫn nhau và chức năng. Nghĩa là một Pod bị lỗi thì hoàn toàn có thể thay thế bằng một Pod mới để tiếp tục xử lý.
- Deployment trước tiên sẽ sinh ra các Replicaset, sau ReplicaSet sẽ tạo ra các Pod theo thứ tự ngẫu nhiên. Tên Pod cũng theo format tên của Replicset + mã hash random gán vào đuôi mỗi Pod

- Có thể tạo một Service để Load banlancing tới tất cả các Pod cho các request tới ứng dụng.
- Các Pod của Deployment cũng có thể bị xóa theo thứ tự bất kỳ, hoặc xóa đồng thời nhiều Pod (trong trường hợp scale down deployment)

StatefulSet
- Các Pod của Statefulset không thể được tạo hay xóa cùng lúc. Nó sẽ được tạo tuần tự.
- Các Pod của Statefulset không hoàn toàn giống nhau. Trên thực tế nó đều có các định dạng riêng. Có pod-0, pod-1,.. của statefulset.
- Các Pod được tạo với các mô tả giống nhau (specification) nhưng không thể thay thế lẫn nhau (not interchangeable)
- Khi một Pod bị lỗi nó sẽ được thay thế bằng một Pod mới cùng định danh (ví dụ pod-1 bị lỗi sẽ bị thay bằng Pod mới với tên vẫn là pod-1).

2. Triển khai Statefulset – Mariabd trên k8s
2.1 Chỉnh sửa cấu hình NFS Server ( Thực hiện trên nfs-server)
sudo vi /etc/exports
/data *(rw,sync,no_subtree_check,no_root_squash)
sudo exportfs -rav
sudo systemctl restart nfs-server

2.2 Cấu hình StatefulSet Mariadb trên rancher
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mariadb
namespace: beobeo-ecommerce
spec:
serviceName: mariadb-service
replicas: 1
selector:
matchLabels:
app: mariadb
template:
metadata:
labels:
app: mariadb
spec:
securityContext:
fsGroup: 65534
containers:
- name: mariadb
image: mariadb:latest
env:
- name: MARIADB_ROOT_PASSWORD
value: "beobeo@2025!"
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mariadb-storage
mountPath: /var/lib/mysql
volumes:
- name: mariadb-storage
persistentVolumeClaim:
claimName: mariadb-storage

fsGroup: 65534 có nghĩa là nobody:nogroup (UID/GID 65534). Làm như vậy để mọi client kể cả khi UID không khớp vẫn đọc/ghi được. Chỉ áp dụng cho môi trường test/lab. Trong môi trường Production cần phải phân quyền chi tiết và rõ ràng hơn.


2.3 Cấu hình Service NodePort MariaDB để truy cập bên ngoài
apiVersion: v1
kind: Service
metadata:
name: mariadb-service
namespace: beobeo-ecommerce
spec:
selector:
app: mariadb
type: NodePort
ports:
- port: 3306
targetPort: 3306
nodePort: 31306
2.4 Cấu hình service ClusterIP để truy cập nội bộ trong cụm k8s
apiVersion: v1
kind: Service
metadata:
name: mariadb
namespace: beobeo-ecommerce
spec:
type: ClusterIP
ports:
- name: mysql
port: 3306
targetPort: 3306
selector:
app: mariadb

Như vậy name nội bộ kết nối tới mariadb là : ..svc.cluster.local
Trong trường hợp của tôi sẽ là mariadb.beobeo-ecommerce.svc.cluster.local
Vì name clusterIP là mariadb
Và Namespace là: beobeo-ecommerce

Từ pod Openlitespeed đã tạo thử kết nối vào database theo clusterip name:

Nguồn: https://kubernetes.io/docs/concepts/storage/
https://tonytechlab.com
https://viblo.asia/p/k8s-basic-kubernetes-statefulset-r1QLxPe2LAw