- vừa được xem lúc

Tạo và sử dụng PersistentVolume và PersistentVolumeClaim trong Kubernetes

0 0 14

Người đăng: Mai Trung Đức

Theo Viblo Asia

Hello các bạn lại là mình đây 👋👋

Mấy tháng nay nhìn vào series K8S này lưng chửng chưa hoàn thiện lúc cũng thấy cân cấn, mà chưa có thời gian tập trung vào viết bài. Đợt này lại quyết tâm lên dây cót tinh thần, quyết tâm đưa ra 1 cái routine đều đặn viết bài, vừa là để giữ nhịp ra bài cho series này, vừa là để giữ cảm hứng sáng tác, 🤣🤣

Hôm nay chúng ta cùng nhau tìm hiểu về PersistentVolume và PersistentVolumeClaim trong K8S để lưu trữ lại data nhé.

Triển thôi 🚀🚀

Lấy K8S Session

Vẫn là bước quen thuộc, trước khi vào bài này các bạn lấy cho mình K8S session để lát nữa ta làm thực hành nhé. Xem lại bài cũ giúp mình nhé

Âu cây bắt đầu hoy

Review lại series Docker chút

series học Docker của mình, hầu như khi phải deploy một app hoàn chỉnh giống như thật, thì ta luôn phải mount 1 hoặc 1 vài volume nào đó vào container để lưu trữ lại data.

Các bạn tưởng tượng image giống như 1 Class trong Java, container giống như 1 object được khởi tạo từ class đó, mỗi container là một môi trường độc lập, ở mỗi lần mà ta khởi tạo nó ở bất kì đâu. Do vậy nếu container của ta có sinh ra data nào trong lúc chạy, kiểu logs/files.... thì sau khi khởi động lại thì mọi thứ sẽ bị xoá sạch bong yêu lại từ đầu 😄

Vậy nên ta cần phải tạo ra volume để lưu lại data giữa các lần khởi tạo container, ở đó volume có thể là Docker volume (volume được quản lý bởi Docker) hoặc local volume (volume do ta quản lý, ta định nghĩa folder nào là volume và mount nó vào container).

K8S Volume

Điều tương tự xảy ra khi ta làm việc với K8S, mỗi khi mà container của chúng ta crash,stop hoặc có lỗi gì đó với Pod, thì kubelet sẽ restart lại container hoặc reschedule lại Pod mới và mọi thứ lại bị reset sạch bong. Do vậy ta cần phải dùng tới Volume để có thể lưu lại data.

Có 2 loại mà ta hay sử dụng nhất:

  • PersistentVolume: volume tồn tại kể cả khi pod bị destroy, có thể được mount sang pod khác
  • Ephemeral Volume: volume ăn theo 1 Pod, khi Pod restart thì vẫn còn đó, nhưng nếu Pod destroy thì volume này cũng bay màu luôn

Ở bài này ta sẽ tập trung vào PersistentVolume nhé

Lý thuyết vỡ lòng

Muốn vô thực hành lắm rồi nhưng vẫn phải rượt qua lý thuyết tí đã nhé các bạn 🤣

  • PersistentVolume: là 1 phần storage mà được cấp bởi admin, hoặc là được cấp động (dynamic) bằng StorageClass. PersistentVolume độc lập với vòng đời của Pod, ý là Pod có bị destroy thì PV vẫn còn đó (hoặc có thể bị delete theo tuỳ ta cấu hình).
  • PersistentVolumeClaim: là 1 request để "xin" được sử dụng storage. Pod muốn sử dụng volume thì cần phải có Claim.

Từ giờ trở đi xuyên suốt series ta sẽ gọi tắt PersistentVolume là PV và PersistentVolumeClaim là PVC, cái này cộng đồng cũng gọi vậy cho tiện chứ không phải mình tự quy ước đâu nhé 🤣 (K8S các command họ cũng viết tắt là thế luôn kubectl get pvc, get pv).

1 PVC thì có thể được sử dụng bởi 1 hoặc nhiều Node tuỳ chúng ta cấu hình, và nó có thể được dùng để read hoặc write hoặc cả read and write.

Chú ý quan trọng là 1 PV sẽ chỉ được bound bởi duy nhất 1 PVC nhé các bạn, do vậy ta sẽ không tạo được 2 PersistentVolumeClaim mà Bound vào 1 PersistentVolume đâu

Ví dụ:

# PV
apiVersion: v1
kind: PersistentVolume
metadata: name: block-pv
spec: capacity: storage: 10Gi # tối đa 10GB accessModes: - ReadWriteOnce --- apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: block-pvc
spec: accessModes: - ReadWriteOnce # read + write nhưng chỉ dành cho 1 Node trên cluster resources: requests: storage: 10Gi # "xin" hết 10 GB

Như ở trên ta có 1 PV và PVC có accessModesReadWriteOnce ý bảo là khi sử dụng thì chỉ có 1 node được dùng PV/PVC này. Và ở trên các bạn thấy là PVC "xin" hết cả 10GB của PV, chú ý rằng PVC không được xin quá lượng storage của PV mà ta định nghĩa.

Ở trên mình có nói tới StorageClass, thì Volume của chúng ta có thể được cấp động thông qua StorageClass, hầu như mình thấy thì chúng ta ít khi phải dùng tới StorageClass, nhưng có một số trường hợp ta muốn dùng các loại storage khác nhau cho các PV khác nhau, ví dụ: PV 1 cần dùng storage xịn (premium) để tốc độ read/write nhanh hơn, PV 2 thì storage lởm lởm cũng được, các cloud provider (GCP, AWS, Azure... họ cũng có những loại storage định sẵn cho chúng ta nữa). Mình mới phải đụng đến cái này 1 lần ở 1 project khi mà mình phải tự chạy MongoDB trên K8S, và muốn tốc độ đọc ghi data nhanh hơn thì mình dùng loại premium. Còn lại thì hầu như mình không định nghĩa gì về StorageClass thì K8S dùng loại mặc định của Cloud provider người ta định nghĩa sẵn, ở đây ta đang dùng Digital Ocean thì nó là dạng disk thông thường 😃

Oke hòm hòm lý thuyết vậy, ta zô thực hành để xem đầu đuôi nó thế nào nhỉ 😎

Chạy Demo app

Ở bài này ta sẽ có 1 demo app, cho phép upload file, hiển thị các file đã upload và có thể xoá file vừa upload:

Đơn giản không có gì mấy 🤣

Ta thử deploy và chạy app này lên nhé. Ở bài này các bạn tạo cho mình một folder để ta làm việc đặt tên là viblo-k8s-pv-demo nhé.

Đầu tiên các bạn tạo cho mình file deployment.yml (ko cần nói các bạn đoán đc ta sẽ có gì ở đây rồi đúng ko 😃 ):

apiVersion: apps/v1
kind: Deployment
metadata: name: demoapp labels: app.kubernetes.io/name: viblo-pv-demo
spec: selector: matchLabels: app: demoapp template: metadata: labels: app: demoapp spec: containers: - name: demoapp image: maitrungduc1410/viblo-k8s-pv-demo:latest ports: - containerPort: 3000 name: http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "750Mi" cpu: "500m"

Ở trên ta có 1 deployment, khi apply sẽ tạo 1 ra 1 pod, trong pod đó có 1 container tên là demoapp, bên cạnh đó ta có 1 số cấu hình như là containerPort hay yêu cầu resources tối thiểu và tối đa cho container của chúng ta, phần này nếu các bạn chưa rõ thì xem lại bài về Deloyment của mình nhé.

Có 1 chú ý nhỏ là ở trên, mình đặt labels cho deployment của mình là app.kubernetes.io/name: viblo-pv-demo, cái này các bạn có thể đặt là bất kì giá trị nào, nhưng mình follow theo cách khuyên dùng từ Kubernetes nhé 😉

Tiếp đó, chúng ta để luôn file k8s session chung vào folder viblo-k8s-pv-demo nhé:

Oke rồi chúng ta apply để tạo deployment này nhé:

kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Nếu thấy báo deployment.apps/demoapp created là ngon rồi.

Ta thử get deploy xem nhé:

kubectl get deploy --kubeconfig=kubernetes-config ------
NAME READY UP-TO-DATE AVAILABLE AGE
demoapp 1/1 1 1 114s

Ở trên ta thấy deployment của chúng ta READY rồi, check xem pod của nó thế nào nhé:

kubectl get pod --kubeconfig=kubernetes-config ------
NAME READY STATUS RESTARTS AGE
demoapp-75497867c-dgmkq 1/1 Running 0 4m21s

Pod của chúng ta cũng RUNNING rồi, 1/1 container trong pod đã READY.

Tiếp đó ta check log xem container của chúng ta có gì nhé (thay tên pod của các bạn vào cho đúng nhé):

kubectl logs demoapp-75497867c-dgmkq --kubeconfig=kubernetes-config ------ ▲ Next.js 13.5.2 - Local: http://demoapp-75497867c-dgmkq:3000 - Network: http://10.244.2.250:3000 ✓ Ready in 405ms

Oke ngon nghẻ rồi.

À giải thích 1 chút về 2 dòng logs ta có:

  • Local: http://demoapp-75497867c-dgmkq:3000: đây là địa chỉ local app của chúng ta, nó sẽ lấy bằng tên của pod và cổng mà app của chúng ta chạy, chú ý rằng cổng 3000 này là từ trong app của chúng ta chứ không phải cái containerPort: 3000 ở file deployment nhé. Ở ví dụ bài này thì mình định nghĩa ở đây, cái này khi làm thật nếu ta không rõ ràng port nào ra port nào là mệt đấy 🤣. containerPort là cái mà ta muốn expose ra trong K8S cluster, cái này thường sẽ phải match với port mà ta định nghĩa trong app của chúng ta
  • Network: http://10.244.2.250:3000: đây là địa chỉ app của chúng ta theo format Pod_ClusterIP:<cổng của app>, ở đây pod của chúng ta có ClusterIP=10.244.2.250

Ủa check pod IP dư lào quên rồi 😅

Để xem thông tin về Pod ta chạy command sau (thay tên pod của các bạn vào cho đúng nhé):

kubectl describe pod demoapp-75497867c-dgmkq --kubeconfig=kubernetes-config

Các bạn sẽ thấy như sau, dòng mình highlight bên dưới là IP của Pod nhé:

Ta thử "chui" vào container vào và thử curl xem có trả về nội dung webapp của chúng ta không nhé:

kubectl exec -it demoapp-75497867c-dgmkq --kubeconfig=kubernetes-config -- sh

Ở trên các bạn thấy rằng ta không cần chỉ đích danh là ta muốn "chui" vào container nào trong pod bởi vì pod của ta có mỗi 1 container, trường hợp ta có nhiều hơn 1 container trong pod thì ta có thể thêm option -c <container_name> để chỉ định là ta muốn chui vào đâu nhé 😃. kubectl exec -it demoapp-75497867c-dgmkq -c my_container.....

Sau khi đã vào trong container thì ta chạy curl thử xem nhé:

curl http://demoapp-75497867c-dgmkq:3000

Ở đây ta dùng địa chỉ Local mà mình đã nói khi nãy nhé (vì ta đang ở trong chính container rồi, các bạn cũng có thể dùng địa chỉ Network cũng được, nếu thấy như sau là oke rồi nhé:

Oke rồi, CTRL + D để thoát khỏi container ra ngoài thôi.

Tiếp đó, để app của chúng ta có thể truy cập được công khai (public) từ trình duyệt thì ta sẽ cần phải có service, và type là LoadBalancer nhé các bạn, lí do là vì ta cần 1 public IP có thể truy cập được từ bên ngoài, phổ biến, tiện và (thường) hợp lý nhất thì sẽ là LoadBalancer, các bạn có thể xem lại bài về Service của mình nhé.

Các bạn tạo cho mình file svc.yml (bên K8S người ta hay viết tắt service=svc 😃) với nội dung như sau:

apiVersion: v1
kind: Service
metadata: name: demoapp
spec: type: LoadBalancer ports: - name: http # tên port của service protocol: TCP port: 3001 targetPort: http # tên port của container bên file deployment selector: app: demoapp
  • Ở trên ta có 1 service tên là demoapp (đặt là gì cũng được, ở đây mình đặt giống với tên deployment)
  • Service này có 1 port ta đặt tên là http, cổng của service này là 3001, sẽ target vào port tên là http mà ta định nghĩa bên file deployment.yml đoạn containerPort. Việc đặt tên cho service là http thì sau này khi ta cần dùng tới nó, ví dụ ở bài ingress thì sẽ tốt hơn thay vì dùng số
  • Dưới cùng ta có selector để chỉ định rằng ta muốn route traffic vào pod mà có labels ta mong muốn

Tiếp theo ta apply service này nhé:

kubectl apply -f svc.yml --kubeconfig=kubernetes-config ------
service/demoapp configured

Sau khi apply ta get tất cả các service trong namespace xem có gì nhé:

kubectl get svc --kubeconfig=kubernetes-config ------
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp LoadBalancer 10.245.112.177 <pending> 3001:30917/TCP 4s

Ở trên các bạn thấy rằng service của chúng ta đã lên, và EXTERNAL-IP đang Pending vì sẽ mất vài phút để cloud provider (ở đây là Digital ocean) tạo Load balancer cho service này.

Đợi một chút làm cốc cafe rồi get lại service ta sẽ thấy có EXTERNAL-IP nhé:

kubectl get svc --kubeconfig=kubernetes-config ------ NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp LoadBalancer 10.245.112.177 146.190.202.195 3001:30917/TCP 3m52s

Khi đã có EXTERNAL-IP thì ta có thể truy cập được app rồi, ta mở trình duyệt ở địa chỉ EXTERNAL-IP:3001 nhé:

Pòm pòm chíu chíu 🎆🎆🎆🎆💪💪💪💪

Tiếp đó chúng ta thử upload vài file lên xem nhé, các bạn có thể upload bất kì file nào, limit là 200MB 1 file, sau khi upload thì file sẽ hiển thị ở table bên dưới:

Ta F5 thoải mái file đã được lưu trên server.

Giờ ta thử restart lại deployment để tạo lại pod mới xem nhé:

kubectl rollout restart deploy demoapp --kubeconfig=kubernetes-config ------ deployment.apps/demoapp restarted

Chờ 1 chút để pod mới được tạo, sau đó ta quay lại trình duyệt F5:

Ui bỏ mịa, mất sạch file 🫣🫣, chắc chắn do cá mập cắn cáp quang 🦈🦈🦈

Tạo PV và PVC

Ở đây ta muốn rằng mỗi khi mà container của chúng ta khởi tạo (do lỗi container, lỗi Pod, restart deployment,....) thì data của chúng ta vẫn phải còn ở đó, chứ production mà mất data thì xong phim 😂😂

Và như đầu bài ta đã nói thì ta sẽ dùng PersistentVolume (PV) và PersistentVolumeClaim (PVC) cho việc này nhé.

Đầu tiên các bạn tạo cho mình file tên là pvc.yml cho PersistentVolumeClaim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: demo-pvc
spec: accessModes: - ReadWriteOnce resources: requests: storage: "2Gi"

Sau đó ta tạo PersistentVolumeClaim này nhé:

kubectl apply -f pvc.yml --kubeconfig=kubernetes-config ------ persistentvolumeclaim/demo-pvc created

Sau đó ta thử get PersistentVolumeClaim mà ta vừa tạo xem nhé:

kubectl get pvc --kubeconfig=kubernetes-config ------ NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
demo-pvc Bound pvc-93715847-269c-40bd-9407-7a11a61514a4 2Gi RWO do-block-storage 61s

Như ở trên các bạn thấy là PVC demo-pvc của ta đã được tạo thành công và "ăn"(bound) vào PV pvc-93715847-269c-40bd-9407-7a11a61514a4, storageClassdo-block-storage, đây là storageClass mặc định mà DigitalOcean tạo sẵn và manage (quản lý) thay chúng ta.

Nếu các bạn dùng K8S ở cloud khác (AWS, GCP, Azure,...) sẽ có thể có các storageClass khác nhé

Ôi dừngggggggggggggggggg!!!!!!!!!!!!!!!!!!!!!!!!! 😲😲😲

Ủa, đầu bài như ta đã nói, để có thể tạo được PVC thì ta cần có:

  • storage class: cái này ở đây thì Digital Ocean tạo sẵn cho ta rồi
  • PV: cái này ta đã tạo đâu????? từ đâu nó lại tự tạo sẵn cho ta cái PV tên là pvc-93715847-269c-40bd-9407-7a11a61514a4 vậy nhỉ???????

Thì đây là mặc định của cloud provider các bạn à, thường khi ta dùng các provider lớn, họ sẽ cung cấp sẵn PV được provision (được họ cung cấp và quản lý thay ta), bởi vì những phần cấu hình đó nhiều khi không dễ nên họ sẽ "abstract" phần đó luôn cho ta.

Ở đây khi ta tạo 1 PVC thì Digital Ocean sẽ tương ứng tạo cho ta 1 PV với Capacity tương ứng (2Gi), storage class là do-block-storage, đây là 1 cái Volume ở trên DigitalOcean (nó là disk như ta vẫn biết)

Khi ta xem ở trên DigitalOcean nó sẽ như sau (đương nhiên các bạn ko xem được, mình xem được thôi 😂😂):

Ở trên các bạn thấy là volume này chưa được mount vào Pod nào cả nên vẫn có nút Attach to Droplet kia, Droplet là 1 Node, Pod thì ở trong Node 😃

Giờ ta quay lại terminal và thử get PV xem nhé:

kubectl get pv --kubeconfig=kubernetes-config ------ NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-93715847-269c-40bd-9407-7a11a61514a4 2Gi RWO Delete Bound learnk8s-8174d9/demo-pvc do-block-storage 22m

Ở trên các bạn sẽ thấy PV của các bạn nhé 😃

Nhưng chú ý là, vì PV là cluster-object, tức là nó không thuộc về namespace (khác với PVC, PVC thuộc về 1 namespace), do vậy khi các bạn get pv thì nó sẽ show toàn bộ pv có trong cluster, nên có thể các bạn sẽ thấy PV của người khác nữa nhé 😃, nhưng các bạn không delete được đâu vì mình đã limit permission rồi, chỉ xem được thôi 😃

Èoooooooo, bài nói về PersistentVolume và PersistentVolumeClaim, ấy xong tạo được mỗi PVC, PV có tự tạo được đâu 😒😒

Nếu các bạn vẫn tò mò thì ta thử xem có gì trong cấu hình của 1 PV có gì mà nãy giờ mình cứ bảo khoai vậy nhỉ

kubectl get pv pvc-93715847-269c-40bd-9407-7a11a61514a4 -o yaml --kubeconfig=kubernetes-
config

nhớ thay tên PV của các bạn vào cho đúng nhé

Ở trên các bạn thấy là ngoài một số thông số mặc định mà object nào cũng có (uid, resourceVersion, creationTimestamp...) thì để cấu hình một PV nhiều thứ cách rách vãi chưởng 😄, và nó còn có các thông số phụ thuộc vào từng cloud provider nữa, đó là lí do vì sao các cloud provider họ thường "abstract" sẵn cho ta luôn.

Mount PVC vào Pod

Giờ ta sẽ tiến hành mount PVC vào Pod và file upload lên app của chúng ta sẽ được lưu vào PVC này nhé.

Các bạn mở cho mình file deployment.yml và sửa như sau nhé:

apiVersion: apps/v1
kind: Deployment
metadata: name: demoapp labels: app.kubernetes.io/name: viblo-pv-demo
spec: selector: matchLabels: app: demoapp template: metadata: labels: app: demoapp spec: containers: - name: demoapp image: maitrungduc1410/viblo-k8s-pv-demo:latest ports: - containerPort: 3000 name: http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "750Mi" cpu: "500m" volumeMounts: - name: data mountPath: /app/storage # readOnly: true # nếu như ta muốn chỉ cho phép read ko write volumes: - name: data persistentVolumeClaim: claimName: demo-pvc # readOnly: true # override readOnly ở volumeMounts

Ở trên các bạn thấy là mình đã thêm vào volumes (ở dưới cùng của file), ta đặt tên volume này là data (các bạn thích đặt là gì cũng được nhé), và volumeSource (nguồn gốc của volume này) ta lấy từ PVC với tên là demo-pvc

Sau đó ở bên trong phần cấu hình cho container demoapp ta thêm volumeMounts, chỉ định tên của volume mà ta muốn mount vào container, ở đây tên là data (mà ta vừa định nghĩa ở mục volumes), tiếp theo ta chỉ định đường dẫn nào mà ta muốn mount với mountPath

Ta cũng có thể chỉ định rằng volume này chỉ được đọc bằng cách thêm readOnly: true, ở phần cấu hình cho persistentVolumeClaim ta cũng có thể thêm readOnly: true, nó sẽ ghi đè lên readOnly ở phần volumeMounts (nếu có)

Oke rồi giờ ta apply lại deployment này nhé:

kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Chờ một lúc cho Pod mới được tạo lại, sau đó ta quay lại trình duyệt F5 nhé:

Ở trên các bạn thấy rằng app chúng ta vẫn chạy bình thường nhưng tự nhiên có 1 cái tên là lost+found, ủa gì z trời??????🤔🤔🤔

Thì đây là một folder mặc định được sinh ra khi ta mount volume vào container thôi các bạn à, cũng không có gì đặc biệt mấy đâu, nó giống như lúc ta upgrade MacOS và sau khi upgrade thì ta cũng có thể có folder lost+found đó nếu như có 1 vài file mà MacOS nó không biết nên để vào đâu.

Âu cây rồi, giờ ta thử upload file mới lên xem để kiểm tra rằng app của chúng ta vẫn chạy ổn áp không nhé:

Bùm 🧨🧨🧨, lỗi 500????? Ủa ban đầu nãy vẫn chạy ngon mà nhỉ? Mount được cái volume vào là hỏng luôn? Kiểu này khả năng liên quan tới permission các thứ rồi 🤔🤔

Ta thử xem logs container của ta có gì nhé:

kubectl logs demoapp-ff5cb7fd-7vwfl --kubeconfig=kubernetes-config

Thay tên Pod của bạn vào cho đúng nhé

[Error: EACCES: permission denied, open '/app/storage/ecea77d730bb21afb0472d001'] { errno: -13, code: 'EACCES', syscall: 'open', path: '/app/storage/ecea77d730bb21afb0472d001'
}

Ều biết ngay lỗi permission mà 😪😪😪

Giờ ta sẽ "chui" vào container xem permission của files/folders trong app của chúng ta như thế nào nhé (Thay tên Pod của bạn vào cho đúng nhé):

kubectl exec -it demoapp-ff5cb7fd-7vwfl --kubeconfig=kubernetes-config -- sh

Sau khi vào trong container các bạn chạy ls -la để xem tất cả các files/folders ta có trong app nhé:

Ta quay trở lại bên trên đoạn lỗi in ra, lỗi đó NodeJS nói là "không có quyền được mở file ecea77d730bb21afb0472d001 bên trong đường dẫn /app/storage" để ghi. Và xem ảnh mà ta vừa ls -la thì ta thấy rằng folder storage đang thuộc sở hữu của root:root (user=root, group=root)

Giờ ta thử check xem user hiện tại là gì nhé, ta đơn giản là gõ id (Enter):

id ------ uid=1001(nextjs) gid=65533(nogroup) groups=65533(nogroup)

Ở trên các bạn thấy rằng user của ta là nextjs có userid=1001, primary groupid=65533 (group chính, vì 1 user có thể thuộc về nhiều group), trong khi folder storage thuộc sở hữu của root:root (uid=0,gid=0) (ta có thể xem kĩ hơn khi chạy command cat /etc/passwd)

Qua đây ta thấy rõ ràng rằng user nextjs không có quyền ghi vào folder storage. Vậy thì giờ ta chỉ cần đổi quyền của storage về thuộc sở hữu của nextjs:nogroup là được rồi 😃

Ấy vậy mà không giống như khi ta dùng Docker hay Docker Compose, khi đó ta có thể exec lại vào container với option -u root (chui vào container với vai root), rồi đổi quyền của folder ta muốn. Bây giờ ta đang dùng Kubernetes và ta không thể làm thế được 😭😭😭😭

Thứ nữa là vì user của ta không thuộc group nào cả nên mặc định nó được gán là nogroup. Và nếu các bạn xem ở Dockerfile của mình, thì mình có tạo ra 1 group tên là nodejs nhưng không gán nó vào cho user nextjs ở Dockerfile nhé, mình chỉ dùng group nodejs để đặt permission cho các file và folder trong đó thôi.

Vậy bây giờ thứ ta muốn là gì????? Nãy giờ nói luyên thuyên hoài 😏😏😏

Đương nhiên là thứ ta muốn là nextjs có quyền ghi vào folder storage rồi😂😂

Oke vậy bằng cách nào????? lòng và lòng vòng 😡

Với Kubernetes thì ta sẽ dùng Pod securityContext để làm điều đó nhé, cụ thể như sau:

  • Ta chỉ cần set fsGroup của Pod securityContext cho Deployment của chúng ta để nó là group 1001 (chính là group nodejs, các bạn có thể xem đoạn mình tạo group này trong Dockerfile)
  • fsGroup này nó là 1 group "bổ trợ" đặc biệt, được thêm vào tất cả các containers chạy trong 1 Pod, cụ thể là trường hợp này thì K8S sẽ thêm group 1001(nodejs) vào user nextjs cho ta
  • Đồng thời K8S sẽ đổi toàn bộ files/folders có trong đường dẫn mà ta đang mount, ở đây là storage về thuộc sở hữu của group 1001(nodejs), việc này nhằm đảm bảo các containers bên trong pod có đủ quyền để truy cập (đọc/ghi) vào volume

Và sau đó kết quả ta sẽ có là gì:

  • user nextjs sẽ thuộc group nodejs(1001)
  • folder storage sẽ thuộc sở hữu của root:nodejs (volume trên k8s mount từ ngoài vào mặc định nó thuộc user root, caí này không quan trọng, miễn là nó cùng group nodejs với nextjs là đc rồi)
  • Và như vậy thì nextjs sẽ ghi được vào folder storage 😉

Oke tạm tạm hiểu là vậy, nói dài qúaaaaaa, giờ thực hành cái coi.

À trước khi thực hành thì các bạn nhờ là bên trên khi chạy id trong container thì user nextjs của ta như sau nhé:

uid=1001(nextjs) gid=65533(nogroup) groups=65533(nogroup)

Tiếp theo bạn sửa lại file deployment.yml như sau:

apiVersion: apps/v1
kind: Deployment
metadata: name: demoapp labels: app.kubernetes.io/name: viblo-pv-demo
spec: selector: matchLabels: app: demoapp template: metadata: labels: app: demoapp spec: securityContext: # --> ở đây fsGroup: 1001 # --> và đây containers: - name: demoapp image: maitrungduc1410/viblo-k8s-pv-demo:latest ports: - containerPort: 3000 name: http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "750Mi" cpu: "500m" volumeMounts: - name: data mountPath: /app/storage # readOnly: true volumes: - name: data persistentVolumeClaim: claimName: demo-pvc # readOnly: true # nếu như ta muốn chỉ cho phép read ko write

Ở trên, ta thêm vào đúng 2 dòng đoạn securityContext, mình gọi đây là Pod securityContext bởi vì nó được áp dụng cho tất cả các container trong pod, chúng ta cũng có cả container securityContext cũng xêm xêm nhưng là áp dụng cho từng container

Việc dùng securityContext này khá là hữu ích khi ta chạy app ở production để tăng thêm tính bảo mật cho pod/container của ta trên K8S Cluster, hầu như mình luôn dùng nó, cái này ta sẽ nói kĩ thêm ở các bài sau nhé

Giờ ta apply lại deployment nhé:

kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Sau khi apply các bạn chờ tẹo cho Pod mới được tạo, sau đó ta hãy khoan sử dụng app để upload file, mà "chui" vô container xem tí đã nhé:

kubectl exec -it demoapp-7b95bd5d66-f86qd --kubeconfig=kubernetes-config -- sh

Thay tên pod của các bạn vào cho đúng nhé

Sau khi vào bên trong thì các bạn chạy command ls -la để xem files/folders nhé:

Ở trên ta thấy rằng folder storage đã thuộc sở hữu của root:nodejs

Tiếp theo ta check xem user hiện ta như thế nào nhé, các bạn chạy id:

/app $ id
uid=1001(nextjs) gid=65533(nogroup) groups=1001(nodejs),65533(nogroup)

Đó, giờ ta thấy là nextjs đã thuộc về thêm 1 group là 1001(tên là nodejs

Điều đó có nghĩa là nextjs sẽ có quyền ghi vào folder storage rồi 😎😎😎

Ta thoát ra ngoài (CTRL+D) và quay trở lại trình duyệt F5 và thử upload file nhé:

Yeahhh, upload ngon nghẻ rồi. Giờ ta thử restart deployment để Pod mới được tạo ra xem files của ta còn không nhé

kubectl rollout restart deploy demoapp --kubeconfig=kubernetes-config ------ deployment.apps/demoapp restarted

Chờ 1 tẹo cho Pod mới được tạo (các bạn get pod xem trạng thái pod mới như thế nào nhé), sau đó quay lại trình duyệt và F5:

Yayy, files vẫn còn 🥳🥳🥳🥳🥳Pằng pằng chíu chíu

Kể ra nó cũng không khó và không khác lắm so với việc ta vẫn mount volume khi làm việc với Docker nhỉ ? 😃 Ta vọc thêm chút nhé

RECLAIM POLICY

Ta thử get persistent volume xem nhé:

kubectl get pv --kubeconfig=kubernetes-config ------ NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-1f23bfc0-5d62-432c-8706-84bc72b1680f 2Gi RWO Delete Bound learnk8s-bde84e/demo-pvc do-block-storage 24m

Ở bên trên có 1 cột tên là RECLAIM POLICY và nó đang show là Delete, ý ở đây là PV này sẽ bị xoá cùng với PVC khi ta thực hiện xoá PVC

RECLAIM POLICY có thể có 3 giá trị sau:

  • Delete: xoá PV cùng với PVC khi PVC bị xoá
  • Retain: xoá PVC, PV vẫn còn. Trường hợp này thường dùng khi data của ta quan trọng, ví dụ database (mysql, mongodb...)
  • Recycle: deprecated (lỗi thời), nhưng vẫn được support ở 1 vài phiên bản K8S, recycle nói với K8S thực hiện 1 vài thao tác cơ bản để dọn dẹp volume và chuẩn bị cho lầm claim sau, nhưng việc này không đảm bảo chắc chắn toàn bộ data bị dọn sạch và có thể vẫn được recover bởi những người có ý đồ ác (nghe rùng rợn ghê ha 🤣🤣). Hiện nay thì ta nên dùng Delete thay vì Recycle, Delete sẽ đảm bảo Volume bị xoá vĩnh viễn

Với volume được "provision" (như ta đang dùng volume của DigitalOcean) thì mặc định policy là Delete

Để thay đổi Reclaim policy cho PV thì ta làm như sau (thay tên PV của các bạn vào cho đúng nhé):

kubectl patch pv pvc-1f23bfc0-5d62-432c-8706-84bc72b1680f -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' --kubeconfig=kubernetes-config

Pùm, lỗi:

Error from server (Forbidden): persistentvolumes "pvc-1f23bfc0-5d62-432c-8706-84bc72b1680f" is forbidden: User "learnk8s-bde84e" cannot patch resource "persistentvolumes" in API group "" at the cluster scope

Lỗi bảo là ta không có quyền được patch sửa volume, à đúng rồi mà 😄, như ban đầu mình nói là vì PV là cluster-objects (kiểu global, chung, không thuộc về namespace nào), nên mình chỉ để quyền cho các bạn là getlist thôi 😁😁

Readonly Volume

Trong trường hợp ta mount volume vào và muốn "force" (bắt, ép buộc) chỉ cho đọc chứ không cho ghi vào volume thì ta làm như sau:

apiVersion: apps/v1
kind: Deployment
metadata: name: demoapp labels: app.kubernetes.io/name: viblo-pv-demo
spec: selector: matchLabels: app: demoapp template: metadata: labels: app: demoapp spec: securityContext: fsGroup: 1001 containers: - name: demoapp image: maitrungduc1410/viblo-k8s-pv-demo:latest ports: - containerPort: 3000 name: http resources: requests: memory: "128Mi" cpu: "64m" limits: memory: "750Mi" cpu: "500m" volumeMounts: - name: data mountPath: /app/storage readOnly: true # ---->> thêm vào đây volumes: - name: data persistentVolumeClaim: claimName: demo-pvc # readOnly: true # nếu như ta muốn chỉ cho phép read ko write

Sau đó ta apply lại deployment này nhé:

kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Chờ tẹo cho Pod mới được tạo, quay trở lại trình duyệt, F5, thử upload file mới lên ta sẽ thấy lỗi, kiểm tra logs của container ta sẽ thấy như sau:

kubectl logs demoapp-745f777bff-2g6d4 -n learnk8s-bde84e --kubeconfig=kubernetes-config ------ ▲ Next.js 13.5.2 - Local: http://demoapp-745f777bff-2g6d4:3000 - Network: http://10.244.2.252:3000 ✓ Ready in 396ms [Error: EROFS: read-only file system, open '/app/storage/a7205fe6eb0105240ed478500'] { errno: -30, code: 'EROFS', syscall: 'open', path: '/app/storage/a7205fe6eb0105240ed478500'
}

Ở trên ta thấy rằng volume hiện tại là read-only và không thể ghi vào được

Các câu hỏi thường gặp

Nếu lưu lượng data vượt quá capacity PVC?

Ở trong bài này PVC của ta có capacity là 2Gi, vậy nếu ta upload file lên và làm cho folder storage vượt quá 2Gi thì sao?

Ta cùng xem nhé 😉

Để dễ demo thì ta sẽ xoá PVC đi tạo lại 1 cái mới với capacity nhỏ hơn cho dễ demo nhé, ta lấy 1Gi nhé.

Đầu tiên ta sẽ xoá deployment và PVC hiện tại đi nhé:

kubectl delete -f deployment.yml --kubeconfig=kubernetes-config kubectl delete -f pvc.yml --kubeconfig=kubernetes-config

Sau đó ở file pvc.yml ta sửa lại nhé:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: demo-pvc
spec: accessModes: - ReadWriteOnce resources: requests: storage: "1Gi" # ---> sửa ở đây

Ở trên ta set về 1Gi vì trên Digital Ocean K8S thì 1 PVC tối thiểu phải là 1Gi.

Tiếp theo các bạn mở file deployment.yml và xoá chỗ có readOnly đi nhé, vì giờ ta cần ghi mà 😃

Sau đó ta apply PVC và deployment nhé:

kubectl apply -f pvc.yml --kubeconfig=kubernetes-config kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Okay rồi sau khi app chạy lên, các bạn tải video demo này của mình về, file video này 150Mb, tức là upload đến file thứ 7 thì dung lượng folder storage sẽ nhiều hơn capacity 1Gi của PVC.

Ở hình dưới upload 6 file lên ta thấy vẫn bình thường (mỗi lần các bạn nhớ thay tên đi không là nó ghi đè nhé):

Và đây là tới file thứ 7, kết quả rất rõ ràng nhé các bạn 😃:

App đang chạy có xoá được PVC hay không?

Ta thử xem nhé 😄:

kubectl delete pvc demo-pvc --kubeconfig=kubernetes-config ------ persistentvolumeclaim "demo-pvc" deleted

Message pvc deleted nhưng terminal bị treo luôn, app vẫn chạy bình thường, PV không sao cả (vì retain policy là Delete)

Từ đây, câu trả lời là: PVC sẽ không bị xoá nếu vẫn còn đang được sử dụng bởi bất kì Pod nào, kể cả nếu admin có cố gắng xoá PV trong lúc đó thì PVC cũng không bị xoá

Bây giờ ta sẽ xoá deployment nhé, các bạn mở thêm 1 cửa sổ terminal nữa, cứ để cửa sổ kia bị treo, ở cửa sổ mới ta chạy:

kubectl delete -f deployment.yml --kubeconfig=kubernetes-config ------ deployment.apps "demoapp" deleted

Giờ ta quay lại cửa sổ bị treo kia sẽ thấy nó hết treo, PVC và PV bị xoá thành công 👍️

Tăng capacity của PVC?

Giờ nếu ta đã tạo PVC với capacity là 2Gi, và ta muốn tăng lên 4Gi có được không?

Ta lại thử xem nhé 😉,nhưng trước tiên ta xoá deployment và pvc hiện tại đi nhé:

kubectl delete -f deployment.yml --kubeconfig=kubernetes-config kubectl delete -f pvc.yml --kubeconfig=kubernetes-config 

Sau đó ta vẫn giữ capacity là 1Gi trong file pvc.ymlapply nhé:

kubectl apply -f pvc.yml --kubeconfig=kubernetes-config ------ persistentvolumeclaim/demo-pvc created

Check thử PVC xem đã lên chưa nàooo:

kubectl get pvc --kubeconfig=kubernetes-config ------ NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
demo-pvc Bound pvc-e382cfe8-82a3-4e08-97bb-58b5c83c9846 1Gi RWO do-block-storage 3s

Oke ngon rồi, giờ ta tăng capacity của PVC lên thành 2Gi nhé:

#pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: demo-pvc
spec: accessModes: - ReadWriteOnce resources: requests: storage: "2Gi" # ---> update ở đây

Sau đó ta apply:

kubectl apply -f pvc.yml --kubeconfig=kubernetes-config ------
persistentvolumeclaim/demo-pvc configured

Ta lại tiếp tục get pvc xem nó đã được cập nhật chưa nhé:

kubectl get pvc --kubeconfig=kubernetes-config ------ NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
demo-pvc Bound pvc-e382cfe8-82a3-4e08-97bb-58b5c83c9846 1Gi RWO do-block-storage 2m21s

Ủa gì z ?????? Vẫn là 1Gi à??? 🧐🧐🧐. Oke thử xem PV thế nào nàoooo:

kubectl get pv --kubeconfig=kubernetes-config ------ NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-e382cfe8-82a3-4e08-97bb-58b5c83c9846 2Gi RWO Delete Bound learnk8s-f0dd2b/demo-pvc do-block-storage 3m10s

Ủa gì z trời???? PV lên 2Gi rồi mà???? là sao ta 🤔🤔🤔

Lạ nhỉ, giờ làm gì tiếp nhỉ???

Thôi thì cứ chạy deployment lên xem đã rồi tính:

kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Chờ tẹo cho Pod lên, app lên ngon nghẻ rồi thử get pvc lại xem:

kubectl get pvc --kubeconfig=kubernetes-config ------ NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
demo-pvc Bound pvc-e382cfe8-82a3-4e08-97bb-58b5c83c9846 2Gi RWO do-block-storage 8m13s

Ố ồ ô.... update lên 2Gi rồi này 🤔🤔🤔

Lí do ở đây là vì PVC sẽ không resize cho tới khi nó được Bound vào 1 Pod nào đó, và ngay sau khi Pod của ta lên sóng, thì PVC được bound -> resize từ 1 -> 2Gi

Ở đây ta đang test với trường hợp ta resize PVC trước cả khi ta tạo Deployment. Giờ nếu ta resize khi ta đã có Deployment và app đang chạy ngon thì kịch bản cũng tương tự, ta phải restart Deployment thì PVC mới được update. Các bạn tự thử xem sao nhé 😉

Các bạn chú ý rằng các cloud provider khác nhau thì có thể nó sẽ có sự khác nhau đôi chút, ở đây ta đang dùng DigitalOcean nhé

Giảm capacity của PVC thì data có bị mất?

Giả sử PVC của ta là 2Gi, ta đang chứa khoảng 1.5Gi dữ liệu, vậy giờ nếu ta thử resize xuống 1Gi PVC thì sao? data của ta có bị mất???

Ta thử xem nhé

(suốt ngày thử thử, nói luôn đi 😪😪😪-------gắng tí nữa sắp hết bài rồi các bạn ạ 😉)

Đầu tiên ta lại xoá pvc và deployment làm lại từ đầu cho sạch nhé (nhớ phải xoá deployment trước PVC đó):

kubectl delete -f deployment.yml --kubeconfig=kubernetes-config kubectl delete -f pvc.yml --kubeconfig=kubernetes-config

Sau đó ta để PVC là 2Gi nhé:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: demo-pvc
spec: accessModes: - ReadWriteOnce resources: requests: storage: "2Gi"

Tiếp theo ta apply PVC và Deployment:

kubectl apply -f pvc.yml --kubeconfig=kubernetes-config kubectl apply -f deployment.yml --kubeconfig=kubernetes-config

Và ta sẽ lại dùng file video (150Mi) của mình nhé.

Ở đây ta sẽ upload 8 lần file này lên server (mỗi lần các bạn nhớ thay tên đi không là nó ghi đè nhé), thì như vậy ta sẽ có cỡ 1.5Gi data, PVC 2Gi là đủ, và sau đó ta sẽ resize xuống 1Gi PVC:

Screenshot 2023-09-28 at 10.34.29 PM.png

Oke đủ 8 file rồi, giờ ta resize PVC xuống 1Gi nhé:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: name: demo-pvc
spec: accessModes: - ReadWriteOnce resources: requests: storage: "1Gi"

apply thôi nào:

kubectl apply -f pvc.yml --kubeconfig=kubernetes-config ------ The PersistentVolumeClaim "demo-pvc" is invalid: spec.resources.requests.storage: Forbidden: field can not be less than previous value

Gòi ta thấy kết quả gòi nhé 🤣🤣, không thể resize xuống nhỏ hơn lượng data mà ta đang có

Kết bài

Hi vọng qua bài này các bạn đã biết thêm về PV và PVC trên K8S là gì, thực ra cách dùng nó cũng không có gì khó và khác lắm với việc ta vẫn mount volume khi làm với Docker nhỉ? 😃

Chỉ là những thử râu ria thì hơi nhiều và ta phải để ý khi chạy app trên K8S cho được mượt thôi 😃

Các bạn tự vọc vạch thêm nhé. Hẹn gặp lại các bạn ở các bài sau 👋👋👋

Bình luận

Bài viết tương tự

- vừa được xem lúc

Deploying A Containerized Web Application On Kubernetes

1. Overview. Kubernetes is an open source project (available on kubernetes.io) which can run on many different environments, from laptops to high-availability multi-node clusters; from public clouds to on-premise deployments; from virtual machines to bare metal.

0 0 55

- vừa được xem lúc

Kubernetes - Học cách sử dụng Kubernetes Namespace cơ bản

Namespace trong Kubernetes là gì. Tại sao nên sử dụng namespace.

0 0 113

- vừa được xem lúc

[Kubernetes] Kubectl và các command cơ bản

Mở đầu. Kubectl là công cụ quản trị Kubernetes thông qua giao diện dòng lệnh, cho phép bạn thực thi các câu lệnh trong Kubernetes cluster.

0 0 59

- vừa được xem lúc

Triển khai EFK Stack trên Kubernetes

EFK stack on K8S. Giới thiệu. Một hệ thống có thể chạy nhiều dịch vụ hoặc ứng dụng khác nhau, vì vậy việc. theo dõi hệ thống là vô cùng cần thiết.

0 0 72

- vừa được xem lúc

Thực hành Kubernetes (K8S) bằng cách sử dụng lệnh Command

Bài hướng dẫn hôm nay sẽ hướng dẫn sử dụng K8S bằng cách sử dụng câu lệnh thay vì UI trên web. Có 2 lựa chọn để thực hiện:. . Sử dụng Cloud Shell.

0 0 56

- vừa được xem lúc

Kubernetes best practices - Liveness và Readiness Health checks

Mở đầu. Kubernetes cung cấp cho bạn một framework để chạy các hệ phân tán một cách mạnh mẽ.

0 0 49