Chuyển tới nội dung

Storage, PV, PVC và StateFullset

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:

AccessModeViết tắtÝ nghĩaVí dụ sử dụng
ReadWriteOnceRWOVolume chỉ được gắn (mount) để đọc/ghi bởi 1 node duy nhất cùng lúcDùng cho hầu hết database (MySQL, PostgreSQL, MariaDB, MongoDB, …)
ReadOnlyManyROXVolume có thể gắn đọc-only từ nhiều nodeDùng cho chia sẻ file tĩnh, cấu hình, dữ liệu không thay đổi
ReadWriteManyRWXVolume có thể gắn đọc/ghi đồng thời từ nhiều nodeDùng cho NFS, GlusterFS, CephFS → nhiều Pod truy cập và ghi dữ liệu chung
ReadWriteOncePodRWOP(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 NodeHữ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ínhPV (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ạoThường do Admin tạo (hoặc Dynamic Provisioner qua StorageClass)Do Developer/Pod khai báo để xin storage
Quản lý bởiCluster-level object (giống tài nguyên chung trong cluster)Namespaced object (nằm trong namespace của Pod)
BindingMộ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ểmMột PVC sẽ bind đúng với 1 PV thỏa điều kiện
Dung lượngXá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
ReclaimPolicyRetainRecycleDelete → 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

Liên hệ