一文圖解Kubernetes的持久化存儲解決方案
概述
Kubernetes(下稱k8s)作為目前行業(yè)內(nèi)使用最廣泛的容器編排工具,已經(jīng)深入到各個技術領域,正在徹底改變應用程序的開發(fā)和部署方式;但從另一個方面講,k8s的架構是不斷變化的。容器的創(chuàng)建和銷毀,從本質(zhì)上講,它們的生命周期是短暫的。因而,K8s的發(fā)展歷程勢必無法繞開持久化的問題,本文就將從這一點出發(fā),為大家講解k8s在持久化存儲方面所提供的解決方案,幫助大家更好的理解k8s的整體技術生態(tài)。
本文的章節(jié)內(nèi)容分配如下:
- 概述
- K8s有哪些持久化方案
- Docker存儲
- K8s原生存儲
- 開源存儲項目Ceph&Rook
- 總結
K8s有哪些持久化方案
外部存儲方案:
先拋一張CNCF(云原生計算基金會)公布的云原生存儲解決方案一覽圖,這里只截取了存儲的部分。

圖中列舉的存儲方案,目前都可以集成到Kubernetes平臺,
Docker存儲卷
當使用Docker作為K8s的容器方案時,Docker自身所支持的存儲卷也就成為了可選方案之一。Docker存儲卷是容器服務在單節(jié)點的存儲組織形式,作為解決數(shù)據(jù)存儲、容器運行時的技術方案;
K8s存儲卷
K8s自己的持久化存儲方案更關注于應用和集群層面,主要用于容器集群的存儲編排,從應用使用存儲的角度提供存儲服務。另一方面,K8s的持久化存儲方案能夠完全兼容自身的對象,如Pod對象等,即插即用,無需二次開發(fā)。
下面,我們就對這幾種存儲方案一一進行解釋。
Docker存儲
容器的讀寫層
為了提高節(jié)點存儲的使用效率,容器不光在不同運行的容器之間共享鏡像資源,而且還實現(xiàn)了在不同鏡像之間共享數(shù)據(jù)。共享鏡像數(shù)據(jù)的實現(xiàn)原理:鏡像是分層組合而成的,即一個完整的鏡像會包含多個數(shù)據(jù)層,每層數(shù)據(jù)相互疊加、覆蓋組成了最終的完整鏡像。

為了實現(xiàn)多個容器間共享鏡像數(shù)據(jù),容器鏡像每一層都是只讀的。而容器使用鏡像時,在多個鏡像分層的最上面還添加了一個讀寫層。每一個容器在運行時,都會基于當前鏡像在其最上層掛載一個讀寫層,用戶針對容器的所有操作都在讀寫層中完成。一旦容器銷毀,這個讀寫層也隨之銷毀。
容器的數(shù)據(jù)卷
容器中的應用讀寫數(shù)據(jù)都是發(fā)生在容器的讀寫層,鏡像層+讀寫層映射為容器內(nèi)部文件系統(tǒng)、負責容器內(nèi)部存儲的底層架構。當我們需要容器內(nèi)部應用和外部存儲進行交互時,還需要一個外置存儲,容器數(shù)據(jù)卷即提供了這樣的功能。
另一方面,容器本身的存儲數(shù)據(jù)都是臨時存儲,在容器銷毀的時候數(shù)據(jù)會一起刪除。而通過數(shù)據(jù)卷將外部存儲掛載到容器文件系統(tǒng),應用可以引用外部數(shù)據(jù),也可以將自己產(chǎn)出的數(shù)據(jù)持久化到數(shù)據(jù)卷中,因此容器數(shù)據(jù)卷是容器實現(xiàn)數(shù)據(jù)持久化的主要實現(xiàn)方式。
容器存儲組成:只讀層(容器鏡像) + 讀寫層 + 外置存儲(數(shù)據(jù)卷)
容器數(shù)據(jù)卷從作用范圍可以分為:單機數(shù)據(jù)卷 和 集群數(shù)據(jù)卷。其中單機數(shù)據(jù)卷即為容器服務在一個節(jié)點上的數(shù)據(jù)卷掛載能力,docker volume 是單機數(shù)據(jù)卷的代表實現(xiàn);
Docker Volume 是一個可供多個容器使用的目錄,它繞過 UFS,包含以下特性:
- 數(shù)據(jù)卷可以在容器之間共享和重用;
- 相比通過存儲驅(qū)動實現(xiàn)的可寫層,數(shù)據(jù)卷讀寫是直接對外置存儲進行讀寫,效率更高;
- 對數(shù)據(jù)卷的更新是對外置存儲讀寫,不會影響鏡像和容器讀寫層;
- 數(shù)據(jù)卷可以一直存在,直到?jīng)]有容器使用。
1)Docker 數(shù)據(jù)卷類型
Bind:將主機目錄/文件直接掛載到容器內(nèi)部。
需要使用主機的上的絕對路徑,且可以自動創(chuàng)建主機目錄;
容器可以修改掛載目錄下的任何文件,是應用更具有便捷性,但也帶來了安全隱患。
Volume:使用第三方數(shù)據(jù)卷的時候使用這種方式。
Volume命令行指令:docker volume (create/rm);
是Docker提供的功能,所以在非 docker 環(huán)境下無法使用;
分為命名數(shù)據(jù)卷和匿名數(shù)據(jù)卷,其實現(xiàn)是一致的,區(qū)別是匿名數(shù)據(jù)卷的名字為隨機碼;
支持數(shù)據(jù)卷驅(qū)動擴展,實現(xiàn)更多外部存儲類型的接入。
Tmpfs:非持久化的卷類型,存儲在內(nèi)存中。
數(shù)據(jù)易丟失。
2)數(shù)據(jù)卷使用語法
Bind掛載語法
-v: src:dst:opts 只支持單機版。
Src:表示卷映射源,主機目錄或文件,需要是絕對地址;
Dst:容器內(nèi)目標掛載地址;
Opts:可選,掛載屬性:ro, consistent, delegated, cached, z, Z;
Consistent, delegated, cached:為mac系統(tǒng)配置共享傳播屬性;
Z、z:配置主機目錄的selinux label。
示例:
- $ docker run -d --name devtest -v /home:/data:ro,rslave nginx
- $ docker run -d --name devtest --mount type=bind,source=/home,target=/data,readonly,bind-propagation=rslave nginx
- $ docker run -d --name devtest -v /home:/data:z nginx
Volume 掛載方式語法
-v: src:dst:opts 只支持單機版。
Src:表示卷映射源,數(shù)據(jù)卷名、空;
Dst:容器內(nèi)目標目錄;
Opts:可選,掛載屬性:ro(只讀)。
示例:
- $ docker run -d --name devtest -v myvol:/app:ro nginx
- $ docker run -d --name devtest --mount source=myvol2,target=/app,readonly nginx
3)Docker數(shù)據(jù)卷插件
Docker 數(shù)據(jù)卷實現(xiàn)了將容器外部存儲掛載到容器文件系統(tǒng)的方式。為了擴展容器對外部存儲類型的需求,docker 提出了通過存儲插件的方式掛載不同類型的存儲服務。擴展插件統(tǒng)稱為 Volume Driver,可以為每種存儲類型開發(fā)一種存儲插件。
可以查看官方文檔鏈接:
https://docs.docker.com/engine/extend/legacy_plugins/#volume-plugins
其特性簡單來說可以總結為2點:
單個節(jié)點上可以部署多個存儲插件;
一個存儲插件負責一種存儲類型的掛載服務。

關于Volume plugin的代碼實現(xiàn),可以參考這篇小文章:Docker 17.03-CE create plugin源碼解析
Docker Plugin 是以Web Service的服務運行在每一臺Docker Host上的,通過HTTP協(xié)議傳輸RPC風格的JSON數(shù)據(jù)完成通信。Plugin的啟動和停止,并不歸Docker管理,Docker Daemon依靠在缺省路徑下查找Unix Socket文件,自動發(fā)現(xiàn)可用的插件。
當客戶端與Daemon交互,使用插件創(chuàng)建數(shù)據(jù)卷時,Daemon會在后端找到插件對應的 socket 文件,建立連接并發(fā)起相應的API請求,最終結合Daemon自身的處理完成客戶端的請求。
Docker Daemon 與 Volume driver 通信方式有:
- Sock文件:linux 下放在/run/docker/plugins 目錄下
- Spec文件:/etc/docker/plugins/convoy.spec 定義
- Json文件:/usr/lib/docker/plugins/infinit.json 定義
實現(xiàn)接口:
Create, Remove, Mount, Path, Umount, Get, List, Capabilities;
使用示例:
- $ docker volume create --driver nas -o diskid="" -o host="10.46.225.247" -o path=”/nas1" -o mode="" --name nas1
Docker Volume Driver 適用在單機容器環(huán)境或者 swarm 平臺進行數(shù)據(jù)卷管理,隨著 K8s 的流行其使用場景已經(jīng)越來越少,這里不做贅述。
K8s原生存儲
如果說Docker注重的是單節(jié)點的存儲能力,那K8s 數(shù)據(jù)卷關注的則是集群級別的數(shù)據(jù)卷編排能力。
卷
Kubernetes 提供是卷存儲類型,從存在的生命周期可分為臨時和持久卷。 從卷的類型分,又可以分為本地存儲、網(wǎng)絡存儲、Secret/ConfigMap、CSI/Flexvolume、PVC;有興趣的小伙伴可以參考一下官方文檔:
【Kubernetes文檔-卷】
https://kubernetes.io/zh/docs/concepts/storage/volumes/
這里就以一幅圖來展示各個存儲的存在形式。

如上圖所示:
- 最上層的pod和PVC由用戶管理,pod創(chuàng)建volume卷,并指定存儲方式。
- 中間部分由集群管理者創(chuàng)建StorageClass對象,StorageClass只需確定PV屬性(存儲類型,大小等)及創(chuàng)建PV所需要用的的存儲插件;K8s會自動根據(jù)用戶提交的PVC來找到對應的StorageClass,之后根據(jù)其定義的存儲插件,創(chuàng)建出PV。
- 最下層指代各個實際的存儲資源。
PV和PVC
這里單獨來聊聊PV和PVC,也是實際應用場景中最常用的一組概念,其中:
PV 是 PersistentVolume 的縮寫,譯為持久化存儲卷;PV 在 K8s 中代表一個具體存儲類型的卷,其對象中定義了具體存儲類型和卷參數(shù)。即目標存儲服務所有相關的信息都保存在 PV 中,K8s 引用 PV 中的存儲信息執(zhí)行掛載操作。
PVC的存在,是從應用角度對存儲卷進行二次抽象;由于 PV 描述的是對具體存儲類型,需要定義詳細的存儲信息,而應用層用戶在消費存儲服務的時候往往不希望對底層細節(jié)知道的太多,讓應用編排層面來定義具體的存儲服務不夠友好。這時對存儲服務再次進行抽象,只把用戶關系的參數(shù)提煉出來,用 PVC 來抽象更底層的 PV。所以 PVC、PV 關注的對象不一樣,PVC 關注用戶對存儲需求,給用戶提供統(tǒng)一的存儲定義方式;而 PV 關注的是存儲細節(jié),可以定義具體存儲類型、存儲掛載使用的詳細參數(shù)等。其具體的對應關系如下圖所示:

使用方法
PVC 只有綁定了 PV 之后才能被 Pod 使用,而 PVC 綁定 PV 的過程即是消費 PV 的過程,這個過程是有一定規(guī)則的,下面規(guī)則都滿足的 PV 才能被 PVC 綁定:
- VolumeMode:被消費 PV 的 VolumeMode 需要和 PVC 一致;
- AccessMode:被消費 PV 的 AccessMode 需要和 PVC 一致;
- StorageClassName:如果 PVC 定義了此參數(shù),PV 必須有相關的參數(shù)定義才能進行綁定;
- LabelSelector:通過 label 匹配的方式從 PV 列表中選擇合適的 PV 綁定;
- storage:被消費 PV 的 capacity 必須大于或者等于 PVC 的存儲容量需求才能被綁定。
PVC模板:
- apiVersion: v1
- kind: PersistentVolumeClaim
- metadata:
- name: disk-1
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 20Gi
- storageClassName: test-disk
- volumeMode: Filesystem
PV模板:
- apiVersion: v1
- kind: PersistentVolume
- metadata:
- labels:
- failure-domain.beta.kubernetes.io/region: cn-zn
- failure-domain.beta.kubernetes.io/zone: cn-zn
- name: d-wz9g2j5qbo37r2lamkg4
- spec:
- accessModes:
- - ReadWriteOnce
- capacity:
- storage: 30Gi
- flexVolume:
- driver: alicloud/disk
- fsType: ext4
- options:
- VolumeId: d-wz9g2j5qbo37r2lamkg4
- persistentVolumeReclaimPolicy: Delete
- storageClassName: test-disk
- volumeMode: Filesystem
開源存儲項目Ceph&Rook
圍繞云原生技術的工具和項目正在大量涌現(xiàn)。作為生產(chǎn)中最突出的問題之一,有相當一部分開源項目致力于解決“在云原生架構上處理存儲”這個問題。
目前最受歡迎的存儲項目是Ceph和Rook。
Ceph是一個動態(tài)管理的、水平可伸縮的分布式存儲集群。Ceph提供了對存儲資源的邏輯抽象。它被設計成不存在單點故障、可自我管理和基于軟件的。Ceph同時為相同的存儲集群提供塊、對象或文件系統(tǒng)接口。它能夠提供非常穩(wěn)定的塊存儲系統(tǒng),并且K8S對Ceph放出了完整的生態(tài),幾乎可以說是全面兼容。
Ceph的架構非常復雜,有許多底層技術,如RADOS、librados、RADOSGW、RDB,它的CRUSH 算法和監(jiān)視器、OSD和MDS等組件。這里不深入解讀其架構,關鍵在于,Ceph是一個分布式存儲集群,它可提供更高的可伸縮性,在不犧牲性能的情況下消除了單點故障,并提供了對對象、塊和文件的訪問的統(tǒng)一存儲。

對于Rook,我們可以從以下幾點來了解這個有趣的項目。它旨在聚合Kubernetes和Ceph的工具——將計算和存儲放在一個集群中。
- Rook 是一個開源的cloud-native storage編排, 提供平臺和框架;為各種存儲解決方案提供平臺、框架和支持,以便與云原生環(huán)境本地集成。
- Rook 將存儲軟件轉(zhuǎn)變?yōu)樽晕夜芾怼⒆晕覕U展和自我修復的存儲服務,它通過自動化部署、引導、配置、置備、擴展、升級、遷移、災難恢復、監(jiān)控和資源管理來實現(xiàn)此目的。
- Rook 使用底層云本機容器管理、調(diào)度和編排平臺提供的工具來實現(xiàn)它自身的功能。
- Rook 目前支持Ceph、NFS、Minio Object Store和CockroachDB。
- Rook使用Kubernetes原語使Ceph存儲系統(tǒng)能夠在Kubernetes上運行。
所以在ROOK的幫助之下我們甚至可以做到一鍵編排部署Ceph,同時部署結束之后的運維工作ROOK也會介入自動進行實現(xiàn)對存儲拓展,即便是集群出現(xiàn)了問題ROOK也能在一定程度上保證存儲的高可用性,絕大多數(shù)情況之下甚至不需要Ceph的運維知識都可以正常使用。
安裝方法
1. 獲取rook倉庫:
https://github.com/rook/rook.git
2. 獲取部署yaml文件
rook倉庫中,
cluster/examples/kubernetes/ceph/common.yaml 文件。
- #運行common.yaml文件
- kubectl create -f common.yaml
3. 安裝operator
編排文件為
/cluster/examples/kubernetes/ceph/operator.yaml
- #運行operator.yaml文件
- kubectl create -f operator.yaml
4. 安裝完成之后,需要等待所有操作器正常運行之后才能繼續(xù)還是ceph分部署集群的安裝
- #獲取命名空間下運行的pod,等待所以的pod都是running狀態(tài)之后繼續(xù)下一步
- kubectl -n rook-ceph get pod
5. 創(chuàng)建Ceph集群
編排文件為
/cluster/examples/kubernetes/ceph/cluster.yaml
這里也需要進行一定的基礎配置與修改才能繼續(xù),cluster.yaml文件內(nèi)容如下:
- apiVersion: ceph.rook.io/v1
- kind: CephCluster
- metadata:
- name: rook-ceph
- namespace: rook-ceph
- spec:
- cephVersion:
- image: ceph/ceph:v14.2.6
- allowUnsupported: false
- dataDirHostPath: /var/lib/rook
- skipUpgradeChecks: false
- continueUpgradeAfterChecksEvenIfNotHealthy: false
- mon:
- #這里是最重要的,mon是存儲集群的監(jiān)控器,我們K8S里面有多少主機這里的就必須使用多少個mon
- count: 3
- allowMultiplePerNode: false
- dashboard:
- #這里是是否啟用監(jiān)控面板,基本上都會使用
- enabled: true
- #監(jiān)控面板是否使用SSL,如果是使用8443端口,不是則使用7000端口,由于這是運維人員使用建議不啟用
- ssl: true
- monitoring:
- enabled: false
- rulesNamespace: rook-ceph
- network:
- hostNetwork: false
- rbdMirroring:
- workers: 0
- crashCollector:
- disable: false
- annotations:
- resources:
- removeOSDsIfOutAndSafeToRemove: false
- storage:
- useAllNodes: true
- useAllDevices: true
- config:
- disruptionManagement:
- managePodBudgets: false
- osdMaintenanceTimeout: 30
- manageMachineDisruptionBudgets: false
- machineDisruptionBudgetNamespace: openshift-machine-api
- #運行cluster.yaml文件
- kubectl create -f cluster.yaml
6. 創(chuàng)建ceph控制面板
如果上面部署時,啟用了SSL則需要使用
/cluster/examples/kubernetes/ceph/dashboard-external-https.yaml,否則使用同目錄下的dashboard-external-http.yaml文件:
- #dashboard沒有啟用SSL
- kubectl create -f dashboard-external-http.yaml
- #dashboard啟用SSL
- kubectl create -f dashboard-external-https.yaml
7. 創(chuàng)建Ceph工具
運維人員可以直接通過對這個容器的shell進行Ceph集群的控制(后面有實例),編排文件是toolbox.yaml
- #安裝Ceph工具
- kubectl create -f toolbox.yaml
8,創(chuàng)建存儲系統(tǒng)與存儲類
集群搭建完畢之后便是存儲的創(chuàng)建,目前Ceph支持塊存儲、文件系統(tǒng)存儲、對象存儲三種方案,K8S官方對接的存儲方案是塊存儲,他也是比較穩(wěn)定的方案,但是塊存儲目前不支持多主機讀寫;文件系統(tǒng)存儲是支持多主機存儲的性能也不錯;對象存儲系統(tǒng)IO性能太差不考慮,所以可以根據(jù)要求自行決定。
存儲系統(tǒng)創(chuàng)建完成之后對這個系統(tǒng)添加一個存儲類之后整個集群才能通過K8S的存儲類直接使用Ceph存儲。
- 塊存儲系統(tǒng)+塊存儲類yaml文件:
- apiVersion: ceph.rook.io/v1
- kind: CephBlockPool
- metadata:
- name: replicapool
- namespace: rook-ceph
- spec:
- failureDomain: host
- replicated:
- size: 3 #這里的數(shù)字分部署數(shù)量,一樣有幾臺主機便寫入對應的值
- ---
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- name: rook-ceph-block
- provisioner: rook-ceph.rbd.csi.ceph.com
- parameters:
- clusterID: rook-ceph
- pool: replicapool
- imageFormat: "2"
- imageFeatures: layering
- csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
- csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
- csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
- csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
- csi.storage.k8s.io/fstype: xfs
- reclaimPolicy: Delete
- 文件系統(tǒng)存儲yaml文件:
- apiVersion: ceph.rook.io/v1
- kind: CephFilesystem
- metadata:
- name: myfs
- namespace: rook-ceph
- spec:
- metadataPool:
- replicated:
- size: 3 #這里的數(shù)字分部署數(shù)量,一樣有幾臺主機便寫入對應的值
- dataPools:
- - replicated:
- size: 3 #這里的數(shù)字分部署數(shù)量,一樣有幾臺主機便寫入對應的值
- preservePoolsOnDelete: true
- metadataServer:
- activeCount: 1
- activeStandby: true
- 文件系統(tǒng)存儲類yaml文件:
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- name: csi-cephfs
- provisioner: rook-ceph.cephfs.csi.ceph.com
- parameters:
- clusterID: rook-ceph
- fsName: myfs
- pool: myfs-data0
- csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
- csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
- csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
- csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
- reclaimPolicy: Delete
總結
本文通過介紹并圖解K8s中各個存儲方案實現(xiàn)方式以及可選擇的開源項目,為讀者呈現(xiàn)更全面的K8s存儲方案選擇。在我們實際的使用場景中,亦需要根據(jù)特定的需求來制定符合項目要求的存儲方案,從而達到最好的實現(xiàn)效果。也希望有更多的朋友能夠加入到kubernetes的隊伍中來,讓kubernetes真正深入到眾多的用戶和企業(yè)中去。