Bài viết này được note lại hoàn toàn từ bài chia sẻ của anh Nguyễn Trung trong nhóm VIET OPENINFRA (xem bài viết gốc ở đây) với mục đích để lưu trữ kiến thức cho bản thân.
Trong Kubernetes, có khá nhiều khái niệm liên quan tới DataCenter và công nghệ liên quan tới DataCenter về mặt Network, Storage. Vì nó là Container Cluster mà, phần này mình sẽ chia sẻ về Storage trên Kubernetes (K8S).
Với K8s thì ta sẽ thường chạy các loại Resource để deploy được các App dưới dạng API HTTP khá nhiều, chỉ xử lý phần Logic nghiệp vụ, sau đó đẩy dữ liệu vào Database hay đẩy vào các dịch vụ khác như MessageQueue, Logging, NoSQL, Cache khá nhiều. Nếu anh em ai dùng AWS thì có thể sẽ dùng những dịch vụ này khá dễ, chỉ cần khởi tạo dịch vụ từ AWS và kết nối tới là xong (không cần phải cài đặt hay deploy trên EC2 hay trên EKS). Nhưng chúng ta sẽ lại back về câu chuyện “Kubernetes on bare metal”.
On-prem hay Bare-metal, những gì chúng ta có sẽ chỉ là Server, Storage, một LoadBalancer (có thể có hoặc không). Và để deploy được những ứng dụng lưu trữ dạng phân tán hay thậm chí StatefulSets như RDMS Database thì ta sẽ cần đến Storage. Nhắc đến Storage trên K8S, ta nghĩ ngay tới cụm từ “Volume”, giống với Docker, Volume là vùng được mount tới Container hoặc Pod dùng để lưu trữ dữ liệu vĩnh viễn, trong Kubernetes ta sẽ quan tâm tới 2 loại Volume chính đó là:
- RWO — Loại này thường là một Disk dạng Block giống EBS của AWS. Loại Volume này thực chất sẽ là một cái Disk được gắn vào Node, sau đó Node sẽ gắn vào Pod rồi Kubernetes sẽ format disk đó thành FileSystem để Pod sử dụng. Disk Block của các Vendor từ AWS tới On-prem tới ảo hóa thì tất cả chúng đều bị tuân theo cơ chế đó là trong một thời điểm chỉ có một Node được gắn với một Disk (theo tài liệu của K8S), và chỉ một Pod ở Node đó có thể mount được tới Volume (đã biến thành từ Disk sang). Kể cả ta có 2 Pod cùng trên một Node thì 2 Pod cũng không thể cùng sử dụng tới Volume này. Loại này thường dùng phổ biến nhất là cho các Database từ SQL tới NoSQL, Sharding, Cluster Database.
- RWX — Loại này thì chính là File Sharing như mô hình truyền thống, đa phần sẽ là NFS và các Node cùng mount vào NFS Volume File System này, File Share thì nhiều Pod ở các Node có thể cùng truy cập tới và đọc ghi dữ liệu. Loại này thì khi đưa ứng dụng lên K8S, rất ít khi phải sử dụng tới loại này, trừ mô hình cũ vẫn dùng NFS không thể chuyển đổi, nhưng tần suất sử dụng trên On-Prem hoặc Bare-metal lại cực kỳ nhiều. Đó là vì chúng dễ setup, chỉ cần một NFS Server và cơ chế Mount đơn giản, tiện lợi. Nếu Volume được dùng được tạo từ NFS thì chúng cũng có thể dùng cho các loại ứng dụng StatefulSet như trên RWO, nhưng thực tế không khuyến cáo vì hiệu năng thấp hơn Disk Block (RWO) khá nhiều. Nhưng mode này lại dễ setup và sử dụng hơn rất nhiều.
Tiếp đến ta sẽ kể đến việc Kubernetes xử lý Storage như nào ? Đầu tiên là Local và HostPath, 2 mode này thì tạm thời ta không nói đến vì chúng vẫn là Volume nhưng không tính là Volume để lưu trữ mà là Volume của các Pod đặc biệt được mount ra file của Host cho các hệ thống như CNI, Controller liên quan tới Core System của Kubernetes nhiều hơn.
Với các Cloud Provider như AWS, Azure, GKE, VMware (cloud nội bộ) thì Kubernetes viết sẵn các đoạn code cho việc kết nối Volume từ Node/Pod của các hãng này. Ví dụ Kubernetes khi cài đặt trên AWS, ta không cần cài đặt thêm bất cứ cái gì cả nhưng vẫn sử dụng được EBS mỗi khi cần, chỉ việc khai báo một cái StorageClass rồi tạo PVC từ StorageClass với kiểu là EBS gp2 là có ngay Disk và Volume cho Pod luôn. Ta gọi đây là In-tree Cloud, như đã nói trên thì Kubernetes đã viết sẵn code xử lý đoạn này.
Tuy nhiên với xu hướng Cloud mở rộng và độ phức tạp cũng như sự khác nhau giữa các Storage của các hãng, và Kubernetes cũng chỉ tập trung và tuân chỉ theo sứ mệnh của mình đó là tôi chỉ quản lý về Workload, Container. Phần Network và Storage là bên thứ 3 tự code và deploy vào, vì vậy về sau Kubernetes đã dừng phát triển code kết nối Storage của các hãng khác và để hãng tự phát triển phần này. Lưu ý là Kubernetes vẫn sẽ xử lý phần từ Pod mount vào Container, còn việc các Node kết nối Storage như nào thì ta sẽ nói đến phần tiếp là Controller.
Với hướng phát triển mới, thì ta có khái niệm Out-of-Tree Cloud, tức là muốn dùng Storage bên ngoài thì phải có Controller được cài đặt để giao tiếp với hệ thống hạ tầng máy chủ bên ngoài. Ta gọi các controller là CSI. Khi Kubernetes được triển khai và chạy trên môi trường máy chủ với hạ tầng Storage tương ứng như nào thì chúng chỉ có thể sử dụng Storage và CSI của môi trường đó. Có thể kể đến như sau:
- Chạy trên VMware On-Prem, cài đặt vSphere CSI để VM tự móc nối với vCenter và tự cấp Disk cho Node khi có yêu cầu tạo Volume mới, sau đó CSI này sẽ tự format Disk đó theo khai báo PVC rồi móc PVC với Pod để mount volume. Còn với dạng disk share thì vSphere bản mới có VSAN FS có thể tự mount từ Host tới một vùng FileSystem trên vSAN.
- Chạy trên Bare-metal / Máy chủ vật lý + SAN Storage. Ta có các máy vật lý cài Kubernetes và có một con SAN của IBM, HP, DELL. Tất cả các vendor về SAN Storage đều đã viết CSI cho Kubernetes, về cơ bản thì ta sẽ tạo một cái Pool hoặc Zone ở trên SAN, sau đó cài CSI gọi tới SAN để tự generate cấu hình và kết nối xuống các Node, chỉ cần khai báo theo template là dung lượng bao nhiêu, loại Block hay Share.
- Rook Ceph Storage, Portworx Storage, Software Storage. Trong trường hợp các máy chủ không có thiết bị Storage nhưng lại có sẵn Disk Local và ý tưởng là sẽ join các Disk ở các Node lại thành một Disk Pool cho việc cấp phát Storage, vừa tiết kiệm việc dùng Storage ngoài, vừa tận dụng được Disk Local, hiệu suất cũng khá ổn. Tuy nhiên việc vận hành và troubleshot yêu cầu phải có skill về Network, Storage cao. Software Storage cũng sử dụng CSI và có tạo thêm các CRD (CustomResourceDefinition) để xử lý phần Storage, ví dụ Ceph sẽ tạo ra resource gọi là CephCluster, vì một cụm Kubernetes có nhiều Node với nhiều Disk, nhưng ta có thể tạo nhiều Ceph Cluster cho nhiều mục đích khác nhau hoặc để tách biệt ra chẳng hạn, hoặc chúng có thể link tới một cụm Ceph Cluster bên ngoài nên sẽ có thêm Resource là CephCluster. Về bản chất thì chúng định nghĩa một hạ tầng Storage giống như các Vendor khác vậy. Sau đó từ PV gọi lệnh tạo Volume từ hạ tầng Storage này với các tham số mong muốn.
- Chạy trên cloud như AWS. Các bản Kubernetes từ 1.20 về sau sẽ phải cài thêm CSI là EBS CSI hoặc EFS CSI dùng để kết nối tới dịch vụ EBS (gp2, gp3) hoặc EFS và tự attach chúng với Worker Node rồi lại mount tới Pod.
Tiếp theo ta sẽ xem các khái niệm và thiết kế resource về Storage của Kubernetes như nào. StorageClass, PV, PVC là gì ?
Controller của Storage là CSI, CSI thì sẽ hoạt động chặt chẽ với StorageClass. Vậy 2 cái này là gì và chúng hoạt động như nào và có liên quan gì tới nhau?
Đầu tiên phải kể tới CSI, khi deploy thì chúng sẽ được deploy dưới dạng DaemonSet và Deployment, với DaemonSet là các Pod nằm trên từng Node có chức năng giống với Drivers vậy, ngày xưa ai hay cắm USB vào máy thì hầu như phải cài Drivers thì CSI sẽ deploy các DS này xuống từng Node tương ứng với Drivers, còn Controller dạng Deployment kia thì sẽ điều khiển việc nhận lệnh từ Kubernetes yêu cầu tạo Volume, các Pod DS kia sẽ đồng bộ cấu hình với nhau qua Controller và Mount được đúng Storage, đúng Pod, đúng thời điểm và đúng định dạng mong muốn.
Đầu tiên ta muốn có Volume cho Pod, ta phải tạo PV (PersistentVolume). PV sẽ khai báo annotation với tham số tới Storage Provider (chi tiết ở đây), PV khi khai báo xong thành công và kết nối yêu cầu khởi tạo được Volume thì sẽ có trạng thái là Bound tức là đã tạo Volume thành công. Tiếp theo ta sẽ khai báo PVC trỏ từ PV và đặt format cho FileSystem ở PVC, rồi từ Pod ta mới khai báo Volume mount tới PVC. Vậy là hoàn thành tạo Volume, mount Volume.
StorageClass sinh ra với mục đích làm Dynamic Provisioning Disk. Với việc mỗi Pod được tạo ra thì phải khai báo PV tới Storage Provider, việc này giống như ngày xưa ta request tạo VM xong thì phải khai báo và chọn Disk gắn vào từng VM như nào vậy, nhưng thực tế thì ta chỉ cần một cái POOL và tất cả Volume tạo ra thì cho vào Pool đấy rồi tự đặt ID của từng Resource trong Pool giúp. StorageClass là một Profile có chức năng khai báo connect đến StorageProvider và ta không cần khai báo PV rồi PVC làm gì cả. Ta sẽ khai báo PVC và chỉ định trường storageClassName tới StorageClass đã khai báo, Controller sẽ xử lý việc tạo PV cho chúng ta luôn.
Vậy là StorageClass chỉ là cái Profile được khai báo sẵn thôi và ta sẽ dùng luôn cấu hình kết nối của StorageClass. Tuy nhiên cái này thì là khởi tạo ban đầu, ví dụ tạo 1 PVC từ StorageClass thì thường nó sẽ chỉ khởi tạo ban đầu, nhưng nếu tự sửa PV/PVC tới Storage Provider khác hoặc edit lại thông số không chính xác thì chúng vẫn hiển thị StorageClass trên PVC, nhưng phần kết nối từ PV/PVC có thể đã bị thay đổi mà không kiểm soát được nên StorageClass chỉ đóng vai trò là Profile thôi nhé!