Kubernetes存儲-Ceph存儲
原文鏈接:https://www.qikqiak.com/k8strain/storage/ceph/#_11
簡介
Ceph 是一個統一的分布式存儲系統,提供較好的性能、可靠性和可擴展性。
架構
支持的接口
1、Object:有原生 API,而且也兼容 Swift 和 S3 的 API
2、Block:支持精簡配置、快照、克隆
3、File:Posix 接口,支持快照

組件
Monitor:一個 Ceph 集群需要多個 Monitor 組成的小集群,它們通過 Paxos 同步數據,用來保存 OSD 的元數據。
OSD:全稱 Object Storage Device,也就是負責響應客戶端請求返回具體數據的進程,一個 Ceph 集群一般都有很多個 OSD。主要功能用於數據的存儲,當直接使用硬盤作為存儲目標時,一塊硬盤稱之為 OSD,當使用一個目錄作為存儲目標的時候,這個目錄也被稱為 OSD。
MDS:全稱 Ceph Metadata Server,是 CephFS 服務依賴的元數據服務,對象存儲和塊設備存儲不需要該服務。
Object:Ceph 最底層的存儲單元是 Object 對象,一條數據、一個配置都是一個對象,每個 Object 包含 ID、元數據和原始數據。
Pool:Pool 是一個存儲對象的邏輯分區,它通常規定了數據冗余的類型與副本數,默認為3副本。對於不同類型的存儲,需要單獨的 Pool,如 RBD。
PG:全稱 Placement Grouops,是一個邏輯概念,一個 OSD 包含多個 PG。引入 PG 這一層其實是為了更好的分配數據和定位數據。每個 Pool 內包含很多個 PG,它是一個對象的集合,服務端數據均衡和恢復的最小單位就是 PG。

pool 是 ceph 存儲數據時的邏輯分區,它起到 namespace 的作用
每個 pool 包含一定數量(可配置)的 PG
PG 里的對象被映射到不同的 Object 上
pool 是分布到整個集群的
FileStore與BlueStore:FileStore 是老版本默認使用的后端存儲引擎,如果使用 FileStore,建議使用 xfs 文件系統。BlueStore 是一個新的后端存儲引擎,可以直接管理裸硬盤,拋棄了 ext4 與 xfs 等本地文件系統。可以直接對物理硬盤進行操作,同時效率也高出很多。
RADOS:全稱 Reliable Autonomic Distributed Object Store,是 Ceph 集群的精華,用於實現數據分配、Failover 等集群操作。
Librados:Librados 是 Rados 提供庫,因為 RADOS 是協議很難直接訪問,因此上層的 RBD、RGW 和 CephFS 都是通過 librados 訪問的,目前提供 PHP、Ruby、Java、Python、C 和 C++ 支持。
CRUSH:CRUSH 是 Ceph 使用的數據分布算法,類似一致性哈希,讓數據分配到預期的地方。
RBD:全稱 RADOS Block Device,是 Ceph 對外提供的塊設備服務,如虛擬機硬盤,支持快照功能。
RGW:全稱是 RADOS Gateway,是 Ceph 對外提供的對象存儲服務,接口與 S3 和 Swift 兼容。
CephFS:全稱 Ceph File System,是 Ceph 對外提供的文件系統服務。
存儲
塊存儲
典型設備:
磁盤陣列,硬盤,主要是將裸磁盤空間映射給主機使用的。
優點:
1、通過 Raid 與 LVM 等手段,對數據提供了保護。
2、多塊廉價的硬盤組合起來,提高容量。
3、多塊磁盤組合出來的邏輯盤,提升讀寫效率。
缺點:
1、采用 SAN 架構組網時,光纖交換機,造價成本高。
2、主機之間無法共享數據。
使用場景
1、Docker 容器、虛擬機磁盤存儲分配。
2、日志存儲
3、文件存儲
文件存儲
典型設備
FTP、NFS 服務器,為了克服塊存儲文件無法共享的問題,所以有了文件存儲,在服務器上架設 FTP 與 NFS 服務器,就是文件存儲。
優點:
1、造價低,隨便一台機器就可以了
2、方便文件可以共享
缺點:
1、讀寫速率低
2、傳輸速率慢
使用場景
1、日志存儲
2、有目錄結構的文件存儲
對象存儲
典型設備
內置大容量硬盤的分布式服務器(swift, s3);多台服務器內置大容量硬盤,安裝上對象存儲管理軟件,對外提供讀寫訪問功能。
優點:
1、具備塊存儲的讀寫高速。
2、具備文件存儲的共享等特性
使用場景:(適合更新變動較少的數據)
1、圖片存儲
2、視頻存儲
部署
由於我們這里在 Kubernetes 集群中使用,也為了方便管理,我們這里使用 Rook 來部署 Ceph 集群,Rook 是一個開源的雲原生存儲編排工具,提供平台、框架和對各種存儲解決方案的支持,以和雲原生環境進行本地集成。
Rook 將存儲軟件轉變成自我管理、自我擴展和自我修復的存儲服務,通過自動化部署、啟動、配置、供應、擴展、升級、遷移、災難恢復、監控和資源管理來實現。Rook 底層使用雲原生容器管理、調度和編排平台提供的能力來提供這些功能,其實就是我們平常說的 Operator。Rook 利用擴展功能將其深度集成到雲原生環境中,並為調度、生命周期管理、資源管理、安全性、監控等提供了無縫的體驗。有關 Rook 當前支持的存儲解決方案的狀態的更多詳細信息,可以參考 Rook 倉庫 的項目介紹。

組件
1、Rook Operator:Rook 的核心組件,Rook Operator 是一個簡單的容器,自動啟動存儲集群,並監控存儲守護進程,來確保存儲集群的健康。
2、Rook Agent:在每個存儲節點上運行,並配置一個 FlexVolume 或者 CSI 插件,和 Kubernetes 的存儲卷控制框架進行集成。Agent 處理所有的存儲操作,例如掛接網絡存儲設備、在主機上加載存儲卷以及格式化文件系統等。
3、Rook Discovers:檢測掛接到存儲節點上的存儲設備。
Rook 還會用 Kubernetes Pod 的形式,部署 Ceph 的 MON、OSD 以及 MGR 守護進程。Rook Operator 讓用戶可以通過 CRD 來創建和管理存儲集群。每種資源都定義了自己的 CRD:
1、RookCluster:提供了對存儲機群的配置能力,用來提供塊存儲、對象存儲以及共享文件系統。每個集群都有多個 Pool。
2、Pool:為塊存儲提供支持,Pool 也是給文件和對象存儲提供內部支持。
3、Object Store:用 S3 兼容接口開放存儲服務。
4、File System:為多個 Kubernetes Pod 提供共享存儲。
安裝
首先在節點上安裝lvm2軟件包:
# Centos
sudo yum install -y lvm2
# Ubuntu
sudo apt-get install -y lvm2
這里部署用的是 release-1.2 版本的 Rook,部署清單文件地址:https://github.com/rook/rook/tree/release-1.2/cluster/examples/kubernetes/ceph。
從上面鏈接中獲取 common.yaml 與 operator.yaml 兩個資源清單文件:
# 會安裝crd、rbac相關資源對象
$ kubectl apply -f common.yaml
# 安裝 rook operator
$ kubectl apply -f operator.yaml
在繼續操作之前,驗證 rook-ceph-operator 是否處於“Running”狀態:
$ kubectl get pod -n rook-ceph
NAME READY STATUS RESTARTS AGE
rook-ceph-operator-658dfb6cc4-qx9bg 1/1 Running 0 69s
rook-discover-t8xqd 1/1 Running 0 13s
rook-discover-xxwf6 1/1 Running 0 13s
可以看到 Operator 運行成功后,還會有一個 DaemonSet 控制器運行得 rook-discover 應用,當 Rook Operator 處於 Running 狀態,我們就可以創建 Ceph 集群了。為了使集群在重啟后不受影響,請確保設置的 dataDirHostPath 屬性值為有效得主機路徑。更多相關設置,可以查看集群配置相關文檔。
創建如下資源清單:
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
# 最新得 ceph 鏡像, 可以查看 https://hub.docker.com/r/ceph/ceph/tags
image: ceph/ceph:v14.2.5
dataDirHostPath: /var/lib/rook # 主機有效目錄
mon:
count: 2
dashboard:
enabled: true
storage:
useAllNodes: true
useAllDevices: false
# 重要: Directories 應該只在預生產環境中使用
directories:
- path: /data/rook
其中有幾個比較重要的字段:
dataDirHostPath:宿主機上的目錄,用於每個服務存儲配置和數據。如果目錄不存在,會自動創建該目錄。由於此目錄在主機上保留,因此在刪除 Pod 后將保留該目錄,另外不得使用以下路徑及其任何子路徑:/etc/ceph、/rook或/var/log/ceph。useAllNodes:用於表示是否使用集群中的所有節點進行存儲,如果在nodes字段下指定了各個節點,則必須將useAllNodes設置為 false。useAllDevices:表示 OSD 是否自動使用節點上的所有設備,一般設置為 false,這樣可控性較高directories:一般來說應該使用一塊裸盤來做存儲,有時為了測試方便,使用一個目錄也是可以的,當然生成環境不推薦使用目錄。
除了上面這些字段屬性之外還有很多其他可以細粒度控制得參數,可以查看集群配置相關文檔。
現在直接創建上面的 CephCluster 對象即可,創建完成后,Rook Operator就會根據我們的描述信息去自動創建Ceph集群了。
驗證
要驗證集群是否處於正常狀態,我們可以使用 Rook 工具箱 來運行 ceph status 命令查看。
Rook 工具箱是一個用於調試和測試 Rook 的常用工具容器,該工具基於 CentOS 鏡像,所以可以使用 yum 來輕松安裝更多的工具包。我們這里用 Deployment 控制器來部署 Rook 工具箱,部署的資源清單文件如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rook-ceph-tools
namespace: rook-ceph
labels:
app: rook-ceph-tools
spec:
replicas: 1
selector:
matchLabels:
app: rook-ceph-tools
template:
metadata:
labels:
app: rook-ceph-tools
spec:
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: rook-ceph-tools
image: rook/ceph:v1.2.1
command: ["/tini"]
args: ["-g", "--", "/usr/local/bin/toolbox.sh"]
imagePullPolicy: IfNotPresent
env:
- name: ROOK_ADMIN_SECRET
valueFrom:
secretKeyRef:
name: rook-ceph-mon
key: admin-secret
securityContext:
privileged: true
volumeMounts:
- mountPath: /dev
name: dev
- mountPath: /sys/bus
name: sysbus
- mountPath: /lib/modules
name: libmodules
- name: mon-endpoint-volume
mountPath: /etc/rook
# 如果設置 hostNetwork: false, "rbd map" 命令會被 hang 住, 參考 https://github.com/rook/rook/issues/2021
hostNetwork: true
volumes:
- name: dev
hostPath:
path: /dev
- name: sysbus
hostPath:
path: /sys/bus
- name: libmodules
hostPath:
path: /lib/modules
- name: mon-endpoint-volume
configMap:
name: rook-ceph-mon-endpoints
items:
- key: data
path: mon-endpoints
一旦 toolbox 的 Pod 運行成功后,我們就可以使用下面的命令進入到工具箱內部進行操作:
$ kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash
工具箱中的所有可用工具命令均已准備就緒,可滿足您的故障排除需求。例如:
ceph status
ceph osd status
ceph df
rados df
比如現在我們要查看集群的狀態,需要滿足下面的條件才認為是健康的:
1、所有 mons 應該達到法定數量
2、mgr 應該是激活狀態
3、至少有一個 OSD 處於激活狀態
4、如果不是 HEALTH_OK 狀態,則應該查看告警或者錯誤信息
# ceph status
cluster:
id: 01afabec-6f13-4336-a2cb-c91bbbca7798
health: HEALTH_WARN
OSD count 2 < osd_pool_default_size 3
services:
mon: 2 daemons, quorum a,b (age 3m)
mgr: a(active, since 2m)
osd: 2 osds: 2 up (since 2m), 2 in (since 2m)
data:
pools: 0 pools, 0 pgs
objects: 0 objects, 0 B
usage: 45 GiB used, 25 GiB / 70 GiB avail
pgs:
如果群集運行不正常,可以查看 Ceph 常見問題以了解更多詳細信息和可能的解決方案。
Dashboard
Ceph 有一個 Dashboard 工具,我們可以在上面查看集群的狀態,包括總體運行狀態,mgr、osd 和其他 Ceph 進程的狀態,查看池和 PG 狀態,以及顯示守護進程的日志等等。
我們可以在上面的 cluster CRD 對象中開啟 dashboard,設置dashboard.enable=true即可,這樣 Rook Operator 就會啟用 ceph-mgr dashboard 模塊,並將創建一個 Kubernetes Service 來暴露該服務,將啟用端口 7000 進行 https 訪問,如果 Ceph 集群部署成功了,我們可以使用下面的命令來查看 Dashboard 的 Service:
$ kubectl get svc -n rook-ceph
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
csi-cephfsplugin-metrics ClusterIP 10.99.162.98 <none> 8080/TCP,8081/TCP 5m47s
csi-rbdplugin-metrics ClusterIP 10.103.124.243 <none> 8080/TCP,8081/TCP 5m47s
rook-ceph-mgr ClusterIP 10.102.96.161 <none> 9283/TCP 4m33s
rook-ceph-mgr-dashboard ClusterIP 10.98.21.237 <none> 7000/TCP 4m58s
rook-ceph-mon-a ClusterIP 10.99.73.73 <none> 6789/TCP,3300/TCP 5m20s
rook-ceph-mon-b ClusterIP 10.108.132.47 <none> 6789/TCP,3300/TCP 5m15s
這里的 rook-ceph-mgr 服務用於報告 Prometheus metrics 指標數據的,而后面的的 rook-ceph-mgr-dashboard 服務就是我們的 Dashboard 服務,如果在集群內部我們可以通過 DNS 名稱 http://rook-ceph-mgr-dashboard.rook-ceph:7000或者 CluterIP http://10.111.195.180:7000 來進行訪問,但是如果要在集群外部進行訪問的話,我們就需要通過 Ingress 或者 NodePort 類型的 Service 來暴露了,為了方便測試我們這里創建一個新的 NodePort 類型的服務來訪問 Dashboard,資源清單如下所示:
apiVersion: v1
kind: Service
metadata:
name: rook-ceph-mgr-dashboard-external
namespace: rook-ceph
labels:
app: rook-ceph-mgr
rook_cluster: rook-ceph
spec:
ports:
- name: dashboard
port: 7000
protocol: TCP
targetPort: 7000
selector:
app: rook-ceph-mgr
rook_cluster: rook-ceph
type: NodePort
創建完成后我們可以查看到新創建的 rook-ceph-mgr-dashboard-external 這個 Service 服務:
$ kubectl get svc -n rook-ceph
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
csi-cephfsplugin-metrics ClusterIP 10.99.162.98 <none> 8080/TCP,8081/TCP 9m17s
csi-rbdplugin-metrics ClusterIP 10.103.124.243 <none> 8080/TCP,8081/TCP 9m17s
rook-ceph-mgr ClusterIP 10.102.96.161 <none> 9283/TCP 8m3s
rook-ceph-mgr-dashboard ClusterIP 10.98.21.237 <none> 7000/TCP 8m28s
rook-ceph-mgr-dashboard-external NodePort 10.110.93.143 <none> 7000:32745/TCP 7s
rook-ceph-mon-a ClusterIP 10.99.73.73 <none> 6789/TCP,3300/TCP 8m50s
rook-ceph-mon-b ClusterIP 10.108.132.47 <none> 6789/TCP,3300/TCP 8m45s
現在我們需要通過 http://<NodeIp>:32745 就可以訪問到 Dashboard 了。

但是在訪問的時候需要我們登錄才能夠訪問,Rook 創建了一個默認的用戶 admin,並在運行 Rook 的命名空間中生成了一個名為 rook-ceph-dashboard-admin-password 的 Secret,要獲取密碼,可以運行以下命令:
$ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}" | base64 --decode && echo
xxxx(登錄密碼)

使用
現在我們的 Ceph 集群搭建成功了,我們就可以來使用存儲了。首先我們需要創建存儲池,可以用 CRD 來定義 Pool。Rook 提供了兩種機制來維持 OSD:
1、副本:缺省選項,每個對象都會根據 spec.replicated.size 在多個磁盤上進行復制。建議非生產環境至少 2 個副本,生產環境至少 3 個。
2、Erasure Code:是一種較為節約的方式。EC 把數據拆分 n 段(spec.erasureCoded.dataChunks),再加入 k 個代碼段(spec.erasureCoded.codingChunks),用分布的方式把 n+k 段數據保存在磁盤上。這種情況下 Ceph 能夠隔離 k 個 OSD 的損失。
我們這里使用副本的方式,創建如下所示的 RBD 類型的存儲池:
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: k8s-test-pool # operator會監聽並創建一個pool,執行完后界面上也能看到對應的pool
namespace: rook-ceph
spec:
failureDomain: host # 數據塊的故障域: 值為host時,每個數據塊將放置在不同的主機上;值為osd時,每個數據塊將放置在不同的osd上
replicated:
size: 3 # 池中數據的副本數,1就是不保存任何副本
存儲池創建完成后我們在 Dashboard 上面的確可以看到新增了一個 pool,而且集群健康狀態下多了一條日志:
Health check update: too few PGs per OSD (6 < min 30) (TOO_FEW_PGS)
這是因為每個 osd 上的 pg 數量小於最小的數目30個。pgs 為8,因為是3副本的配置,所以當有4個 osd 的時候,每個 osd 上均分了8/4 *3=6個pgs,也就是出現了如上的錯誤小於最小配置30個,集群這種狀態如果進行數據的存儲和操作,集群會卡死,無法響應io,同時會導致大面積的 osd down。
我們可以進入 toolbox 的容器中查看上面存儲的 pg 數量,並可以通過增加pg_num來解決這個問題:
$ ceph osd pool get k8s-test-pool pg_num
pg_num: 8
$ ceph osd pool set k8s-test-pool pg_num 64
set pool 1 pg_num to 64
$ ceph -s
cluster:
id: 01afabec-6f13-4336-a2cb-c91bbbca7798
health: HEALTH_WARN
OSD count 2 < osd_pool_default_size 3
services:
mon: 2 daemons, quorum a,b (age 18m)
mgr: a(active, since 17m)
osd: 2 osds: 2 up (since 16m), 2 in (since 16m)
data:
pools: 1 pools, 64 pgs
objects: 0 objects, 0 B
usage: 45 GiB used, 25 GiB / 70 GiB avail
pgs: 64 active+undersized
不過需要注意的是我們這里的 pool 上沒有數據,所以修改 pg 影響並不大,但是如果是生產環境重新修改 pg 數,會對生產環境產生較大影響。因為 pg 數變了,就會導致整個集群的數據重新均衡和遷移,數據越大響應 io 的時間會越長。所以,最好在一開始就設置好 pg 數。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
# clusterID 是 rook 集群運行的命名空間
clusterID: rook-ceph
# 指定存儲池
pool: k8s-test-pool
# RBD image (實際的存儲介質) 格式. 默認為 "2".
imageFormat: "2"
# RBD image 特性. CSI RBD 現在只支持 `layering` .
imageFeatures: layering
# Ceph 管理員認證信息,這些都是在 clusterID 命名空間下面自動生成的
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
# 指定 volume 的文件系統格式,如果不指定, csi-provisioner 會默認設置為 `ext4`
csi.storage.k8s.io/fstype: ext4
# uncomment the following to use rbd-nbd as mounter on supported nodes
# **IMPORTANT**: If you are using rbd-nbd as the mounter, during upgrade you will be hit a ceph-csi
# issue that causes the mount to be disconnected. You will need to follow special upgrade steps
# to restart your application pods. Therefore, this option is not recommended.
#mounter: rbd-nbd
reclaimPolicy: Delete
直接創建上面的StorageClass資源對象,接着創建一個PVC來使用這個StorageClass對象:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
storageClassName: rook-ceph-block
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
創建完成后我們可以看到我們的 PVC 對象已經是 Bound 狀態了,自動創建了對應的 PV,然后我們就可以直接使用這個 PVC 對象來做數據持久化操作了。
$ kubectl get pvc -l app=wordpress
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pv-claim Bound pvc-865bff43-d921-491b-9452-aeae64e80109 20Gi RWO rook-ceph-block 16s
在官方倉庫 cluster/examples/kubernetes 目錄下,官方給了個 wordpress 的例子,可以直接運行測試即可:
$ kubectl apply -f mysql.yaml
$ kubectl apply -f wordpress.yaml
官方的這個示例里面的 wordpress 用的 Loadbalancer 類型,我們可以改成 NodePort:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 54m
wordpress NodePort 10.104.129.67 <none> 80:32383/TCP 51s
wordpress-mysql ClusterIP None <none> 3306/TCP 63s
當應用都處於 Running 狀態后,我們可以通過 `http://<任意節點IP>:32383 去訪問 wordpress 應用:

現在我在WordPress里創建了一個Ceph測試文章。
現在將應用的Pod全部刪掉重建:
$ kubectl delete pod -l app=wordpress
pod "wordpress-7bfc545758-pgzth" deleted
pod "wordpress-mysql-764fc64f97-vm6ml" deleted
$ kubectl get pod -l app=wordpress
NAME READY STATUS RESTARTS AGE
wordpress-7bfc545758-chffm 1/1 Running 0 41s
wordpress-mysql-764fc64f97-k5phc 1/1 Running 0 41s
當 Pod 重建完成后再次訪問 wordpress 應用的主頁我們可以發現之前我們添加的數據仍然存在,這就證明我們的數據持久化是正確的。
