Kubernetes云原生開源分布式存儲簡介

1 Kubernetes存儲介紹

1.1 為何引入PV装蓬、PVC以及StorageClass灿渴?

熟悉Kubernetes的都對PV下梢、PVC以及StorageClass不陌生全肮,我們經(jīng)常用到悬钳,因此這里不再詳細(xì)介紹PV盐捷、PVC以及StorageClass的用法,僅簡單聊聊為什么需要引入這三個概念他去。

我們看下最早期Pod使用Volume的寫法:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - image: ...
    name: test-pod
    volumeMounts:
    - mountPath: /data
      name: data
  volumes:
  - name: data
    capacity:
      storage: 10Gi
    cephfs:
      monitors:
      - 172.16.0.1:6789
      - 172.16.0.2:6789
      - 172.16.0.3:6789
      path: /opt/eshop_dir/eshop
      user: admin
      secretRef:
        name: ceph-secret

這種方式至少存在兩個問題:

  • Pod聲明與底層存儲耦合在一起毙驯,每次聲明volume都需要配置存儲類型以及該存儲插件的一堆配置,如果是第三方存儲灾测,配置會非常復(fù)雜爆价。
  • 開發(fā)人員的需求可能只是需要一個20GB的卷,這種方式卻不得不強(qiáng)制要求開發(fā)人員了解底層存儲類型和配置媳搪。

比如前面的例子中每次聲明Pod都需要配置Ceph集群的mon地址以及secret铭段,特別麻煩。

于是引入了PV(Persistent Volume)秦爆,PV其實(shí)就是把Volume的配置聲明部分從Pod中分離出來:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: cephfs
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  cephfs:
    monitors:
    - 172.16.0.1:6789
    - 172.16.0.2:6789
    - 172.16.0.3:6789
    path: /opt/eshop_dir/eshop
    user: admin
    secretRef:
      name: ceph-secret

我們發(fā)現(xiàn)PV的spec部分幾乎和前面Pod的volume定義部分是一樣的序愚。

有了PV,在Pod中就可以不用再定義volume的配置了等限,直接引用即可爸吮,volume定義和Pod松耦合了。

但是這沒有解決volume定義的第二個問題望门,存儲系統(tǒng)通常由運(yùn)維人員管理形娇,開發(fā)人員并不知道底層存儲配置,也就很難去定義好PV筹误。

為了解決這個問題桐早,引入了PVC(Persistent Volume Claim),聲明與消費(fèi)分離,開發(fā)與運(yùn)維責(zé)任分離哄酝。

運(yùn)維人員負(fù)責(zé)存儲管理友存,可以事先根據(jù)存儲配置定義好PV,而開發(fā)人員無需了解底層存儲配置陶衅,只需要通過PVC聲明需要的存儲類型屡立、大小、訪問模式等需求即可搀军,然后就可以在Pod中引用PVC侠驯,完全不用關(guān)心底層存儲細(xì)節(jié)。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cephfs
spec:
  accessModes:
      - ReadWriteMany
  resources:
      requests:
        storage: 8Gi

PVC會根據(jù)聲明的大小奕巍、存儲類型(如storageClassName)吟策、accessModes等關(guān)鍵字查找PV,如果找到了匹配的PV的止,則會與之關(guān)聯(lián)檩坚。

通過PV以及PVC,開發(fā)人員的問題是解決了诅福,但沒有解決運(yùn)維人員的問題匾委。運(yùn)維人員需要維護(hù)一堆PV列表和配置,如果PV不夠用需要手動創(chuàng)建新的PV氓润,PV空閑了還需要手動去回收赂乐,管理效率太低了。

于是又引入了StorageClass咖气,StorageClass類似聲明了一個非常大的存儲池挨措,其中一個最重要的參數(shù)是provisioner,這個provisioner聲明了誰來提供存儲源崩溪,我們熟悉的OpenStack Cinder浅役、Ceph、AWS EBS等都是provisioner伶唯。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: aws-gp2
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  fsType: ext4

有了StorageClass后觉既,Kubernetes會根據(jù)開發(fā)人員定義的PVC中聲明的StorageClassName以及大小等需求自動創(chuàng)建PV,即Dynamic Provisioning乳幸。

而運(yùn)維人員只需要聲明好StorageClass以及Quota配額瞪讼,無需維護(hù)PV。

通過PV粹断、PVC以及StorageClass符欠,開發(fā)和運(yùn)維的工作徹底解放了。

1.2 Kubernetes存儲方案發(fā)展過程概述

我們知道Kubernetes存儲最開始是通過Volume Plugin實(shí)現(xiàn)集成外部存儲系統(tǒng)姿染,即不同的存儲系統(tǒng)對應(yīng)不同的volume plugin背亥。

Volume Plugin實(shí)現(xiàn)代碼全都放在了Kubernetes主干代碼中(in-tree),也就是說這些插件與核心Kubernetes二進(jìn)制文件一起鏈接悬赏、編譯狡汉、構(gòu)建和發(fā)布。

這種方案至少存在如下幾個問題:

  • 在Kubernetes中添加新存儲系統(tǒng)支持需要在核心Kubernetes增加插件代碼闽颇,隨著存儲插件越來越多盾戴,Kubernetes代碼也會變得越來越龐大。
  • Kubernetes與具體的存儲plugin耦合在一起兵多,一旦存儲接口發(fā)生任何變化都需要重新修改plugin代碼尖啡,也就是說不得不修改Kubernetes代碼,這會導(dǎo)致Kubernetes代碼維護(hù)越來越困難剩膘。
  • 如果plugin有bug或者存儲系統(tǒng)故障導(dǎo)致crash衅斩,可能導(dǎo)致整個Kubernetes集群整體crash。
  • 這些插件運(yùn)行時無法做權(quán)限管控怠褐,具有Kubernetes所有組件的所有權(quán)限畏梆,存在一定的安全風(fēng)險。
  • 插件的實(shí)現(xiàn)必須通過Golang語言編寫并與Kubernetes一起開源奈懒,可能對一些廠商不利奠涌。

因此從1.8開始,Kubernetes停止往Kubernetes代碼中增加新的存儲支持磷杏, 并推出了一種新的插件形式支持外部存儲系統(tǒng)溜畅,即FlexVolume,不過FlexVolume其實(shí)在1.2就提出了极祸。

FlexVolume類似于CNI插件慈格,通過外部腳本集成外部存儲接口,這些腳本默認(rèn)放在/usr/libexec/kubernetes/kubelet-plugins/volume/exec/遥金,需要安裝到所有Node節(jié)點(diǎn)上峦椰。

這樣每個存儲插件只需要通過外部腳本(out-of-tree)實(shí)現(xiàn)attachdetach汰规、mount汤功、umount等接口即可集成第三方存儲,不需要動Kubernetes源碼溜哮,可以參考官方的一個LVM FlexVolume Demo[1]滔金。

但是這種方法也有問題:

  • 腳本文件放在host主機(jī)上,因此驅(qū)動不得不通過訪問宿主機(jī)的根文件系統(tǒng)去運(yùn)行腳本茂嗓。
  • 這些插件如果還有第三方程序依賴或者OS兼容性要求餐茵,還需要在所有的Node節(jié)點(diǎn)安裝這些依賴并解決兼容問題。

因此這種方式雖然解決了in-tree的問題述吸,但顯然這種方式用起來不太優(yōu)雅忿族,不太原生锣笨。

因此Kubernetes從1.9開始又引入了Container Storage Interface (CSI)容器存儲接口,并于1.13版本正式GA道批。

CSI的實(shí)現(xiàn)方案和CRI類似通過gRPC與volume driver進(jìn)行通信错英,存儲廠商需要實(shí)現(xiàn)三個服務(wù)接口Identity Service、Controller Service隆豹、Node Service椭岩,

  • Identity Service用于返回一些插件信息;
  • Controller Service實(shí)現(xiàn)Volume的CURD操作璃赡,
  • Node Service運(yùn)行在所有的Node節(jié)點(diǎn)判哥,用于實(shí)現(xiàn)把volume掛載在當(dāng)前Node節(jié)點(diǎn)的指定目錄,該服務(wù)會監(jiān)聽一個Socket碉考,controller通過這個Socket進(jìn)行通信塌计,可以參考官方提供的樣例CSI Hostpath driver Sample[2]

更多有關(guān)CSI介紹可以參考官方的設(shè)計(jì)文檔CSI Volume Plugins in Kubernetes Design Doc[3]侯谁。

通過CSI基本解決了如上in-tree以及FlexVolume的大多數(shù)問題夺荒,未來Kubernetes會把in-tree的存儲插件都遷移到CSI。

當(dāng)然Flex Volume Plugin也會與新的CSI Volume Plugin并存以便兼容現(xiàn)有的第三方FlexVolume存儲插件良蒸。

1.3 為什么需要云原生分布式存儲

通過CSI接口或者Flex Volume Plugin解決了Kubernetes集成外部存儲的問題技扼,目前Kubernetes已經(jīng)能夠支持非常多的外部存儲系統(tǒng)了,如NFS嫩痰、GlusterFS剿吻、Ceph、OpenStack Cinder等串纺,這些存儲系統(tǒng)目前主流的部署方式還是運(yùn)行在Kubernetes集群之外單獨(dú)部署和維護(hù)丽旅,這不符合All In Kubernetes的原則。

如果已經(jīng)有分布式存儲系統(tǒng)還好纺棺,可以直接對接榄笙。但如果沒有現(xiàn)成分布式存儲,則不得不單獨(dú)部署一套分布式存儲祷蝌。

很多分布式存儲部署相對還是比較復(fù)雜的茅撞,比如Ceph。而Kubernetes天生就具有快速部署和編排應(yīng)用的能力巨朦,如果能把分布式存儲的部署也通過Kubernetes編排管理起來米丘,則顯然能夠大大降低分布式存儲的部署和維護(hù)成本,甚至可以使用一條apply命令就可以輕松部署一個Ceph集群糊啡。

這主要有兩種實(shí)現(xiàn)思路:

  • 第一種思路就是重新針對云原生平臺設(shè)計(jì)一個分布式存儲拄查,這個分布式存儲系統(tǒng)組件是微服務(wù)化的,能夠復(fù)用Kubernetes的調(diào)度棚蓄、故障恢復(fù)和編排等能力堕扶,如后面要介紹的Longhorn碍脏、OpenEBS。
  • 另一種思路就是設(shè)計(jì)微服務(wù)組件把已有的分布式存儲系統(tǒng)包裝管理起來稍算,使原來的分布式存儲可以適配運(yùn)行在Kubernetes平臺上典尾,實(shí)現(xiàn)通過Kubernetes管理原有的分布式存儲系統(tǒng),如后面要介紹的Rook邪蛔。

1.4 Container Attached Storage,容器存儲的未來扎狱?

我們知道組成云計(jì)算的三大基石為計(jì)算侧到、存儲和網(wǎng)絡(luò),Kubernetes計(jì)算(Runtime)淤击、存儲(PV/PVC)和網(wǎng)絡(luò)(Subnet/DNS/Service/Ingress)的設(shè)計(jì)都是開放的匠抗,可以集成不同的方案,比如網(wǎng)絡(luò)通過CNI接口支持集成Flannel污抬、Calico等網(wǎng)絡(luò)方案汞贸,運(yùn)行時(Runtime)通過CRI支持Docker、Rkt印机、Kata等運(yùn)行時方案矢腻,存儲通過volume plugin支持集成如AWS EBS、Ceph射赛、OpenStack Cinder等存儲系統(tǒng)多柑。

但是我們發(fā)現(xiàn)目前主流的方案中存儲與計(jì)算、網(wǎng)絡(luò)稍有不同楣责,計(jì)算和網(wǎng)絡(luò)都是以微服務(wù)的形式通過Kubernetes統(tǒng)一編排管理的竣灌,即Kubernetes既是計(jì)算和網(wǎng)絡(luò)的消費(fèi)者,同時也是計(jì)算和網(wǎng)絡(luò)的編排者和管理者秆麸。

而存儲則不一樣初嘹,雖然Kubernetes已經(jīng)設(shè)計(jì)了PV/PVC機(jī)制來管理外部存儲,但只是弄了一個標(biāo)準(zhǔn)接口集成沮趣,存儲本身還是通過獨(dú)立的存儲系統(tǒng)來管理屯烦,Kubernetes根本不知道底層存儲是如何編排和調(diào)度的。

社區(qū)認(rèn)為既然計(jì)算和網(wǎng)絡(luò)都由我Kubernetes統(tǒng)一編排了房铭,是不是存儲也考慮下漫贞?

于是社區(qū)提出了Container Attached Storage(CAS)理念,這個理念的目標(biāo)就是利用Kubernetes來編排存儲育叁,從而實(shí)現(xiàn)我Kubernetes編排一切迅脐,這里的一切包括計(jì)算、存儲豪嗽、網(wǎng)絡(luò)谴蔑,當(dāng)然更高一層的還包括應(yīng)用豌骏、服務(wù)、軟件等隐锭。

這個方案如何實(shí)現(xiàn)呢窃躲?CAS提出如下方案:

  • 每個volume都由一個輕量級的Controller來管理,這個Controller可以是一個單獨(dú)的Pod钦睡。
  • 這個Controller與使用該volume的應(yīng)用Pod在同一個Node(sidecar模式)蒂窒。
  • 不同的Volume的數(shù)據(jù)使用多個獨(dú)立的Controller Pod進(jìn)行管理。
image

由于Pod是通過Kubernetes編排與調(diào)度的荞怒,因此毫無疑問通過這種形式其實(shí)就實(shí)現(xiàn)了Kubernetes編排和調(diào)度存儲: )

Kubernetes畢竟是目前主流趨勢洒琢,通過Kubernetes編排和管理存儲也必然是一種發(fā)展趨勢,目前OpenEBS就是CAS的一種開源實(shí)現(xiàn)褐桌,商業(yè)存儲如PortWorx衰抑、StorageOS也是基于CAS模式的。

更多關(guān)于CAS的可以參考CNCF官宣文章Container Attached Storage: A Primer[4]荧嵌。

2 簡單好用的Longhorn

2.1 Longhorn簡介

Longhorn[5]在我之前的文章輕量級Kubernetes k3s初探已經(jīng)簡單介紹過呛踊,最初由Rancher公司開發(fā)并貢獻(xiàn)給社區(qū),專門針對Kubernetes設(shè)計(jì)開發(fā)的云原生分布式塊存儲系統(tǒng)啦撮,因此和Kubernetes契合度很高谭网,主要體現(xiàn)在兩個方面,一是它本身就直接運(yùn)行在Kubernetes平臺上赃春,通過容器和微服務(wù)的形式運(yùn)行蜻底;其二是能很好的與PV/PVC結(jié)合。

與其他分布式存儲系統(tǒng)最大的不同點(diǎn)是聘鳞,Longhorn并沒有設(shè)計(jì)一個非常復(fù)雜的控制器來管理海量的volume數(shù)據(jù)卷薄辅,而是將控制器拆分成一個個非常輕量級的微控制器,這些微控制器能夠通過Kubernetes抠璃、Mesos等平臺進(jìn)行編排與調(diào)度站楚。

每個微控制器只管理一個volume,換句話說搏嗡,一個volume一個控制器窿春,每個volume都有自己的控制器,這種基于微服務(wù)的設(shè)計(jì)使每個volume相對獨(dú)立采盒,控制器升級時可以先選擇一部分卷進(jìn)行操作旧乞,如果升級出現(xiàn)問題,可以快速選擇回滾到舊版本磅氨,升級過程中只可能會影響正在升級的volume尺栖,而不會導(dǎo)致其他volume IO中斷。

Longhorn的實(shí)現(xiàn)和CAS的設(shè)計(jì)理念基本是一致的烦租,相比Ceph來說會簡單很多延赌,而又具備分布式塊存儲系統(tǒng)的一些基本功能:

  • 支持多副本除盏,不存在單點(diǎn)故障;
  • 支持增量快照挫以;
  • 支持備份到其他外部存儲系統(tǒng)中者蠕,比如S3;
  • 精簡配置(thin provisioning);
  • ...

我覺得Longhorn還有一個特別好的功能是內(nèi)置了一個Web UI掐松,通過UI能夠很方便的管理Node踱侣、Volume以及Backup,不得不說Longhorn真是麻雀雖小五臟俱全大磺。

根據(jù)官方的說法抡句,Longhorn并不是為了取代其他分布式塊存儲系統(tǒng),而是為了設(shè)計(jì)一個更簡單的適合容器環(huán)境的塊存儲系統(tǒng)量没,其他分布式存儲有的一些高級功能Longhorn并沒有實(shí)現(xiàn)玉转,比如去重(deduplication)突想、壓縮殴蹄、分塊、多路徑等猾担。

Longhorn存儲管理機(jī)制比較簡單袭灯,當(dāng)在Longhorn中Node節(jié)點(diǎn)增加物理存儲時,其本質(zhì)就是把Node對應(yīng)的路徑通過HostPath掛載到Pod中绑嘹,我們可以查看該路徑的目錄結(jié)構(gòu)稽荧,在replicas目錄中一個volume一個子目錄,文件內(nèi)容如下:

# find replicas/int32bit-volume-3-ab6717d6/
replicas/int32bit-volume-3-ab6717d6/
replicas/int32bit-volume-3-ab6717d6/volume.meta
replicas/int32bit-volume-3-ab6717d6/volume-head-000.img
replicas/int32bit-volume-3-ab6717d6/revision.counter
replicas/int32bit-volume-3-ab6717d6/volume-head-000.img.meta

其中int32bibt-volume-3是volume名稱工腋,ab6717d6對應(yīng)副本名稱姨丈,子目錄中包含一些volume的metadata以及img文件,而img文件其實(shí)就是一個raw格式文件:

# qemu-img info volume-head-000.img
image: volume-head-000.img
file format: raw
virtual size: 20G (21474836480 bytes)
disk size: 383M

raw格式其實(shí)就是Linux Sparse稀疏文件擅腰,由于單個文件大小受文件系統(tǒng)和分區(qū)限制蟋恬,因此Longhorn volume會受單個磁盤的大小和性能的限制,不過我覺得Kubernetes Pod其實(shí)也很少需要用到特別大的volume趁冈。

更多關(guān)于Longhorn的技術(shù)實(shí)現(xiàn)原理可以參考官宣文章Announcing Longhorn: an open source project for microservices-based distributed block storage[6]歼争。

2.2 Longhorn部署

Longhorn部署也非常簡單,只需要一個kubectl apply命令:

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml

創(chuàng)建完后就可以通過Service或者Ingress訪問它的UI了:

image

在Node頁面可以管理節(jié)點(diǎn)以及物理存儲渗勘,Volume頁面可以管理所有的volumes沐绒,Backup可以查看備份等。

volume詳情頁面可以查看volume的掛載情況旺坠、副本位置乔遮、對應(yīng)的PV/PVC以及快照鏈等:

image

除此之外,Longhorn還支持創(chuàng)建備份計(jì)劃取刃,可以通過cron指定時間點(diǎn)或者定時對volume進(jìn)行快照或者備份到S3中申眼。

image

2.3 Kubernetes集成Longhorn存儲

Longhorn既支持FlexVolume也支持CSI接口瞒津,安裝時會自動根據(jù)Kubernetes版本選擇FlexVolume或者CSI。

Kubernetes集成Longhorn括尸,根據(jù)前面對StorageClass的介紹巷蚪,我們需要先安裝Longhorn StorageClass:

kubectl create -f \
https://raw.githubusercontent.com/longhorn/longhorn/master/examples/storageclass.yaml

聲明一個20Gi的PVC:

# kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: longhorn-volv-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 2Gi

創(chuàng)建Pod并使用新創(chuàng)建的PVC:

# kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: test-volume-longhorn
spec:
  containers:
  - name: test-volume-longhorn
    image: jocatalin/kubernetes-bootcamp:v1
    volumeMounts:
    - name: volv
      mountPath: /data
  volumes:
  - name: volv
    persistentVolumeClaim:
      claimName: longhorn-volv-pvc

通過Longhorn Dashboard查看volume狀態(tài)已經(jīng)掛載到Pod中:

image

Longhorn volume不僅可以通過PV形式掛載到Kubernetes容器,還可以直接通過ISCSI接口掛載到Node節(jié)點(diǎn)上:

image

此時可以通過lsblk在OS查看到塊存儲設(shè)備濒翻,通過iscsiadm命令可以查看Node節(jié)點(diǎn)連接的設(shè)備會話:

# lsblk -S
NAME HCTL       TYPE VENDOR   MODEL             REV TRAN
sda  0:0:0:1    disk IET      VIRTUAL-DISK     0001 iscsi
# iscsiadm  -m session
tcp: [3] 10.244.0.50:3260,1 iqn.2019-10.io.longhorn:int32bit-volume-1 (non-flash)

3 CAS開源實(shí)現(xiàn)OpenEBS

3.1 OpenEBS簡介

OpenEBS[7]MayaData[8](之前叫CloudByte)公司開源的云原生容器存儲項(xiàng)目屁柏,命名上可能參考了AWS EBS(Elastic Block Storage)。

OpenEBS也是目前Container Attached Storage的一種開源實(shí)現(xiàn)方案有送,因此它直接運(yùn)行在Kubernetes平臺上淌喻,通過Kubernetes平臺進(jìn)行編排與調(diào)度。

OpenEBS支持如下三種存儲類型雀摘,分別為cStor裸删、Jiva以及LocalPV。

Jiva

后端實(shí)現(xiàn)其實(shí)就是前面介紹的Longhorn阵赠,也就是使用了raw格式sparse稀疏文件作為虛擬磁盤實(shí)現(xiàn)容器volume涯塔,這個和虛擬機(jī)的本地虛擬磁盤實(shí)現(xiàn)類似,可以通過qemu-img info查看volume分配的虛擬大小以及實(shí)際使用的空間清蚀,稀疏文件默認(rèn)路徑為/var/openebs生蚁,所以volume的容量總大小取決于這個路徑掛載的文件系統(tǒng)大小你弦。

實(shí)現(xiàn)上使用了Longhorn早期版本設(shè)計(jì),即一個3副本的volume會有4個Pod控制器管理,一個是主控制器狞洋,三個副本控制器沸柔,其中主控制器運(yùn)行了iSCSI Target服務(wù)眼俊,通過Service暴露ISCSI 3260端口慎颗,主控制器會把IO復(fù)制到所有副本的控制器。

[圖片上傳失敗...(image-41e5bc-1595940598655)]

cStor

這是OpenEBS最推薦的存儲類型嘶卧,測試最完備尔觉,經(jīng)過了生產(chǎn)部署考驗(yàn),支持多副本脸候、快照穷娱、克隆、精簡配置(thin provisioning)运沦、數(shù)據(jù)強(qiáng)一致性等高級特性泵额。

和Jiva不一樣的是,cStor使用了類似ZFS或者LVM的Pool的概念携添,blockdevices就相當(dāng)于LVM的PV嫁盲,而Pool則類似LVM的VG概念,volume類似LVM的LV,其中blockdevice對應(yīng)物理上的一塊磁盤或者一個分區(qū)羞秤,多個blockdevices組成Pool缸托,這些blockdevices如何存儲落盤取決于Pool策略,cStor支持的Pool策略包括striped瘾蛋、mirrored俐镐、raidz、raidz2哺哼,這些概念都不陌生佩抹。

[圖片上傳失敗...(image-bdae90-1595940598655)]

Pool是一個單Node節(jié)點(diǎn)層面的概念而不是分布式的,創(chuàng)建一個cStor Pool實(shí)際上會在每個Node節(jié)點(diǎn)創(chuàng)建相同策略的Pool實(shí)例取董,因此即使使用striped策略棍苹,數(shù)據(jù)打散后也只是存儲在本地的多塊磁盤,不會跨節(jié)點(diǎn)存儲茵汰,當(dāng)然volume副本是跨節(jié)點(diǎn)的枢里。

OpenEBS通過ISCSI接口實(shí)現(xiàn)volume的掛載,每當(dāng)創(chuàng)建一個cStor Volume蹂午,OpenEBS就會創(chuàng)建一個新的cStor target Pod栏豺,cStor target會創(chuàng)建對應(yīng)的LUN設(shè)備。

cStor target除了負(fù)責(zé)LUN設(shè)備管理画侣,還負(fù)責(zé)副本之間的數(shù)據(jù)同步冰悠,每當(dāng)用戶有數(shù)據(jù)寫入時堡妒,cStor target會把數(shù)據(jù)拷貝到其他所有副本中去配乱。

比如假設(shè)創(chuàng)建了一個三副本的cStor PV,當(dāng)用戶寫入數(shù)據(jù)時皮迟,cStor target會同時往三個副本寫入數(shù)據(jù)搬泥,只有等三個副本都寫成功后,才會響應(yīng)用戶伏尼,因此顯然OpenEBS是一個強(qiáng)一致性分布式存儲系統(tǒng)忿檩。

image

不過這也是cStor性能比較差的原因之一,它不像Ceph一樣一個RBD image會分塊存儲在多個節(jié)點(diǎn)多個硬盤的多個OSD上爆阶,可以避免單節(jié)點(diǎn)的IO性能瓶頸問題燥透。

LocalPV

LocalPV就是直接把本地磁盤(local disk)掛載到容器,這個其實(shí)就是Kubernetes LocalPV的增強(qiáng)版辨图,因?yàn)橹苯幼x取本地磁盤班套,相對iSCSI需要走網(wǎng)絡(luò)IO來說性能肯定是最好的,不過缺點(diǎn)是沒有多副本故河、快照吱韭、克隆等高級特性。

3.2 OpenEBS部署

直接使用kubectl安裝:

kubectl apply -f \
    https://openebs.github.io/charts/openebs-operator-1.8.0.yaml

如上會把所有的/dev下的塊設(shè)備都當(dāng)作OpenEBS的block devices鱼的,建議修改下openebs-ndm-configConfigmap理盆,通過path-filter指定分給OpenEBS的物理設(shè)備痘煤。

正如LVM有了PV還需要創(chuàng)建VG一樣,cStor需要手動創(chuàng)建一個Pool:

apiVersion: openebs.io/v1alpha1
kind: StoragePoolClaim
metadata:
  name: cstor-disk-pool
  annotations:
    cas.openebs.io/config: |
      - name: PoolResourceRequests
        value: |-
            memory: 2Gi
      - name: PoolResourceLimits
        value: |-
            memory: 4Gi
spec:
  name: cstor-disk-pool
  type: disk
  poolSpec:
    poolType: striped
  blockDevices:
    blockDeviceList:
    - blockdevice-ad96d141bd7804554d431cb13e7e61bc
    - blockdevice-b14cd44f3bfcbd94d3e0bda065f6e2bd
    - blockdevice-e3a5cf960033d7a96fdee46a5baee9d2

其中poolType選擇pool策略猿规,如果使用mirror需要注意每個Node的磁盤數(shù)量必須是偶數(shù)衷快,這里我們選擇striped,即數(shù)據(jù)會打散分布存儲在Node Pool的所有磁盤姨俩。

blockDevices選擇要放入該P(yáng)ool的物理設(shè)備烦磁,這里作為測試每個Node節(jié)點(diǎn)只有一塊盤,實(shí)際生產(chǎn)時應(yīng)該至少使用3塊盤以上哼勇,使用mirror則至少兩塊盤以上.

可以通過如下命令查看可用的blockDevices:

# kubectl get blockdevices --all-namespaces  -o wide
NAMESPACE   NAME                                           NODENAME                                             PATH           SIZE           CLAIMSTATE   STATUS   AGE
openebs     blockdevice-ad96d141bd7804554d431cb13e7e61bc   ip-192-168-193-6.cn-northwest-1.compute.internal     /dev/nvme0n1   107374182400   Claimed      Active   4d17h
openebs     blockdevice-b14cd44f3bfcbd94d3e0bda065f6e2bd   ip-192-168-193-172.cn-northwest-1.compute.internal   /dev/nvme0n1   107374182400   Claimed      Active   4d17h
openebs     blockdevice-e3a5cf960033d7a96fdee46a5baee9d2   ip-192-168-193-194.cn-northwest-1.compute.internal   /dev/nvme0n1   107374182400   Claimed      Active   4d17h

其中Claimed表示已分配都伪,PATH對應(yīng)物理設(shè)備路徑。

使用CR cstorpools可以查看Pool:

# kubectl get cstorpools.openebs.io --all-namespaces
NAME                   ALLOCATED   FREE    CAPACITY   STATUS    READONLY   TYPE      AGE
cstor-disk-pool-9tey   272K        99.5G   99.5G      Healthy   false      striped   4m50s
cstor-disk-pool-lzg4   272K        99.5G   99.5G      Healthy   false      striped   4m50s
cstor-disk-pool-yme1   272K        99.5G   99.5G      Healthy   false      striped   4m50s

可見OpenEBS會在所有的Node節(jié)點(diǎn)創(chuàng)建Pool實(shí)例积担,與前面的解釋一致陨晶。

3.3 Kubernetes集成OpenEBS塊存儲

本小節(jié)主要以cStor為例演示下如何使用OpenEBS,前面已經(jīng)創(chuàng)建了cStor Pool帝璧,接下來只需要再創(chuàng)建對應(yīng)的StorageClass即可:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cstor-disk-pool
  annotations:
    openebs.io/cas-type: cstor
    cas.openebs.io/config: |
      - name: StoragePoolClaim
        value: "cstor-disk-pool"
      - name: ReplicaCount
        value: "3"
provisioner: openebs.io/provisioner-iscsi

其中StoragePoolClaim指定使用的Pool名稱先誉,ReplicaCount指定volume的副本數(shù)。

創(chuàng)建完StorageClass后就可以創(chuàng)建PVC了:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-openebs-cstor
  namespace: default
spec:
  storageClassName: cstor-disk-pool
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: test-openebs-cstor
  namespace: default
spec:
  containers:
  - name: test-openebs-cstor
    image: jocatalin/kubernetes-bootcamp:v1
    volumeMounts:
    - name: test-openebs-cstor
      mountPath: /data
  volumes:
  - name: test-openebs-cstor
    persistentVolumeClaim:
      claimName: test-openebs-cstor

每創(chuàng)建一個PV的烁,OpenEBS就會創(chuàng)建一個Target Pod褐耳,這個Pod通過一個單副本的Deployment管理,這個Pod會創(chuàng)建一個LUN并export渴庆,通過Service暴露iSCSI端口:

# kubectl get pvc --all-namespaces
NAMESPACE   NAME                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
default     test-openebs-cstor   Bound    pvc-6673e311-0db1-4f15-b480-aafb16a72d46   50Gi       RWO            cstor-disk-pool   31m
# kubectl get pod -n openebs | grep pvc-6673e311-0db1-4f15-b480-aafb16a72d46
pvc-6673e311-0db1-4f15-b480-aafb16a72d46-target-55bb467574mf7hf   3/3     Running   1          31m
# kubectl get deployments.apps -n openebs | grep pvc-6673e311-0db1-4f15-b480-aafb16a72d46
pvc-6673e311-0db1-4f15-b480-aafb16a72d46-target   1/1     1            1           32m
# kubectl get svc -n openebs | grep pvc-6673e311-0db1-4f15-b480-aafb16a72d46
pvc-6673e311-0db1-4f15-b480-aafb16a72d46   ClusterIP   10.96.24.35      <none>        3260/TCP,7777/TCP,6060/TCP,9500/TCP   32m

毫無疑問铃芦,OpenEBS的所有服務(wù)運(yùn)行、存儲調(diào)度襟雷、服務(wù)之間通信以及存儲的管理都是通過Kubernetes完成的刃滓,它就像集成到Kubernetes的一個內(nèi)嵌功能一樣,一旦配置完成耸弄,基本不需要額外的運(yùn)維和管理咧虎。

一個Volume對應(yīng)一個Target Pod,這完全遵循了CAS的設(shè)計(jì)理念计呈。

4 讓分布式存儲簡化管理的Rook

4.1 Rook簡介

Rook[9]也是目前開源中比較流行的云原生存儲編排系統(tǒng)砰诵,它和之前介紹的LongHorn和OpenEBS不一樣,它的目標(biāo)并不是重新造輪子實(shí)現(xiàn)一個全新的存儲系統(tǒng)捌显,最開始Rook項(xiàng)目僅僅專注于如何實(shí)現(xiàn)把Ceph運(yùn)行在Kubernetes平臺上茁彭。

隨著項(xiàng)目的發(fā)展,格局也慢慢變大苇瓣,僅僅把Ceph搞定是不夠的尉间,項(xiàng)目當(dāng)前的目標(biāo)是將外部已有的分布式存儲系統(tǒng)在云原生平臺托管運(yùn)行起來,借助云原生平臺具有的自動化調(diào)度、故障恢復(fù)哲嘲、彈性擴(kuò)展等能力實(shí)現(xiàn)外部存儲系統(tǒng)的自動管理贪薪、自動彈性擴(kuò)展以及自動故障修復(fù)。

按照官方的說法眠副,Rook要把原來需要對分布式存儲系統(tǒng)手動做的一些運(yùn)維工作借助云原生平臺能力(如Kubernetes)實(shí)現(xiàn)自動化画切,這些運(yùn)維工作包括部署、初始化囱怕、配置霍弹、擴(kuò)展、升級娃弓、遷移典格、災(zāi)難恢復(fù)、監(jiān)控以及資源管理等台丛,這種自動化甚至不需要人去手動觸發(fā)耍缴,而是云原生平臺自動觸發(fā)的,因此叫做self-managing挽霉,真正實(shí)現(xiàn)NoOpts防嗡。

比如集群增加一塊磁盤,Rook能自動初始化為一個OSD侠坎,并自動加入到合適的故障域中蚁趁,這個OSD在Kubernetes中是以Pod的形式運(yùn)行的。

目前除了能支持編排管理Ceph集群实胸,還支持:

  • EdgeFS
  • CockroachDB
  • Cassandra
  • NFS
  • Yugabyte DB

不同的存儲通過不同的Operator實(shí)現(xiàn)他嫡,但使用起來基本一致,Rook屏蔽了底層存儲系統(tǒng)的差異童芹。

4.2 Rook部署

安裝部署Rook非常簡單涮瞻,以Ceph為例鲤拿,只需要安裝對應(yīng)的Operator即可:

git clone --single-branch --branch release-1.3 \
    https://github.com/rook/rook.git
cd rook/cluster/examples/kubernetes/ceph
kubectl create -f common.yaml
kubectl create -f operator.yaml
kubectl create -f cluster.yaml

通過Rook管理Ceph假褪,理論上不需要直接通過Ceph Client命令行接口與Ceph集群直接交互。不過如果有需要近顷,可以通過如下方式進(jìn)行簡單配置:

kubectl create -f toolbox.yaml # 安裝Ceph client工具
export CEPH_TOOL_POD=$(kubectl -n rook-ceph \
  get pod -l "app=rook-ceph-tools" \
  -o jsonpath='{.items[0].metadata.name}')
alias ceph="kubectl -n rook-ceph exec -it $CEPH_TOOL_POD -- ceph"
alias rbd="kubectl -n rook-ceph exec -it $CEPH_TOOL_POD -- rbd"

使用ceph命令查看集群狀態(tài):

# ceph osd df
ID CLASS WEIGHT REWEIGHT SIZE RAW USE DATA OMAP META AVAIL %USE VAR PGS STATUS
                   TOTAL  0 B     0 B  0 B  0 B  0 B   0 B    0
MIN/MAX VAR: -/-  STDDEV: 0

我們發(fā)現(xiàn)Ceph集群是空的生音,沒有任何OSD,這是因?yàn)槲业臋C(jī)器沒有裸磁盤(即沒有安裝任何文件系統(tǒng)的分區(qū))窒升。

DeamonSet rook-discover會定時監(jiān)視Node節(jié)點(diǎn)是否有新的磁盤缀遍,一旦有新的磁盤,就會自動啟動一個Job進(jìn)行OSD初始化饱须。如下是Node節(jié)點(diǎn)增加磁盤的結(jié)果:

image

如果運(yùn)行在公有云上域醇,rook還會根據(jù)節(jié)點(diǎn)的Region以及AZ自動放到不同的故障域,不需要手動調(diào)整crushmap:

image

如圖,由于我的測試集群部署在AWS上譬挚,并且三個節(jié)點(diǎn)都放在了一個AZ上锅铅,因此三個OSD都在zone cn-northwest-1b中,實(shí)際生產(chǎn)環(huán)境不推薦這么做减宣。

我們發(fā)現(xiàn)整個磁盤以及OSD初始化過程盐须,無需人工干預(yù),這就是所謂的self-manage漆腌。

想想我們平時在做Ceph集群擴(kuò)容贼邓,從準(zhǔn)備磁盤到crushmap配置,沒有半個小時是搞不定的闷尿,而通過Rook我們幾乎不用操心OSD是如何加到集群的塑径。

Rook默認(rèn)還會安裝Ceph Dashboard,可以通過Kubernetes Service rook-ceph-mgr-dashboard進(jìn)行訪問填具,admin的密碼保存在secret rook-ceph-dashboard-password中晓勇,可通過如下命令獲取:

kubectl -n rook-ceph get secret rook-ceph-dashboard-password \
-o jsonpath="{['data']['password']}" \
| base64 --decode && echo

image

Rook會把Ceph集群的監(jiān)控導(dǎo)出,便于與Prometheus集成灌旧。

4.3 Kubernetes集成Rook Ceph存儲

Longhorn绑咱、OpenEBS都只提供了塊存儲接口,意味著一個Volume只能掛載到一個Pod枢泰,而Rook Ceph則同時提供了塊存儲描融、共享文件系統(tǒng)存儲以及對象存儲接口,其中共享文件系統(tǒng)存儲以及對象存儲都能實(shí)現(xiàn)跨節(jié)點(diǎn)的多個Pod共享衡蚂。

接下來我們通過例子演示下如何使用窿克。

4.3.1 塊存儲

Ceph通過RBD實(shí)現(xiàn)塊存儲,首先我們在Kubernetes上安裝StorageClass:

kubectl create -f \
cluster/examples/kubernetes/ceph/csi/rbd/storageclass.yaml

我們首先需要創(chuàng)建一個Ceph Pool毛甲,當(dāng)然我們可以通過ceph osd pool create命令手動創(chuàng)建年叮,但這樣體現(xiàn)不了self-managing,我們應(yīng)該屏蔽Ceph集群接口玻募,直接使用Kubernetes CRD進(jìn)行聲明:

# kubectl apply -f -
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3

通過如上方式只损,我們基本不需要使用ceph命令,即創(chuàng)建了一個3副本的rbd pool七咧。

可以通過kubectl get cephblockpools查看pool列表:

# kubectl get cephblockpools
NAME          AGE
replicapool   107s

創(chuàng)建一個Pod使用CephBlockPool新建Volume:

# kubectl apply -f -
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-ceph-blockstorage-pvc
spec:
  storageClassName: rook-ceph-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: test-ceph-blockstorage
spec:
  containers:
  - name: test-ceph-blockstorage
    image: jocatalin/kubernetes-bootcamp:v1
    volumeMounts:
    - name: volv
      mountPath: /data
  volumes:
  - name: volv
    persistentVolumeClaim:
      claimName: test-ceph-blockstorage-pvc

輸出結(jié)果如下:

# kubectl get pod test-ceph-blockstorage
NAME                     READY   STATUS    RESTARTS   AGE
test-ceph-blockstorage   1/1     Running   0          5m4s
# kubectl get pvc
NAME                         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
test-ceph-blockstorage-pvc   Bound    pvc-6ff56a06-86a1-437c-b04f-62bb18e76375   20Gi       RWO            rook-ceph-block   5m8s
# rbd -p replicapool ls
csi-vol-e65ec8ef-7cc1-11ea-b6f8-ce60d5fc8330

從輸出結(jié)果可見PV volume對應(yīng)Ceph的一個RBD image跃惫。

4.3.2 共享文件系統(tǒng)存儲

共享文件系統(tǒng)存儲即提供文件系統(tǒng)存儲接口,我們最常用的共享文件系統(tǒng)存儲如NFS艾栋、CIFS爆存、GlusterFS等,Ceph通過CephFS實(shí)現(xiàn)共享文件系統(tǒng)存儲蝗砾。

和創(chuàng)建Ceph Pool一樣先较,同樣使用Kubernetes即可聲明一個共享文件系統(tǒng)實(shí)例携冤,完全不需要調(diào)用ceph接口:

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: myfs
  namespace: rook-ceph
spec:
  metadataPool:
    replicated:
      size: 3
  dataPools:
    - replicated:
        size: 3
  preservePoolsOnDelete: true
  metadataServer:
    activeCount: 1
    activeStandby: true

可以通過如下命令查看mds服務(wù)是否就緒:

# kubectl -n rook-ceph get pod -l app=rook-ceph-mds
NAME                                   READY   STATUS    RESTARTS   AGE
rook-ceph-mds-myfs-a-f87d59467-xwj84   1/1     Running   0          32s
rook-ceph-mds-myfs-b-c96645f59-h7ffr   1/1     Running   0          32s
# kubectl get cephfilesystems.ceph.rook.io
NAME   ACTIVEMDS   AGE
myfs   1           111s

創(chuàng)建cephfs StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-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/controller-expand-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/controller-expand-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
allowVolumeExpansion: true
mountOptions:

我們知道CephFS是共享文件系統(tǒng)存儲,支持多個Pod共享闲勺,首先我們創(chuàng)建一個ReadWriteMany的PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: rook-cephfs

通過Deployment創(chuàng)建三個Pod共享這個PVC:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cephfs-demo
  labels:
    k8s-app: cephfs-demo
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 3
  selector:
    matchLabels:
      k8s-app: cephfs-demo
  template:
    metadata:
      labels:
        k8s-app: cephfs-demo
    spec:
      containers:
      - name: cephfs-demo
        image: jocatalin/kubernetes-bootcamp:v1
        volumeMounts:
        - name: volv
          mountPath: /data
      volumes:
      - name: volv
        persistentVolumeClaim:
          claimName: cephfs-pvc
          readOnly: false

等待Pod初始完成后噪叙,我們從其中一個Pod寫入數(shù)據(jù),看另一個Pod能否看到寫入的數(shù)據(jù):

image

如上圖霉翔,我們往Pod cephfs-demo-5799fcbf58-2sm4b寫入數(shù)據(jù)睁蕾,從cephfs-demo-5799fcbf58-bkw9f可以讀取數(shù)據(jù),符合我們預(yù)期债朵。

在Ceph Dashboard中我們也可以看到myfs實(shí)例一共有3個 clients:

image.png

4.3.3 對象存儲

Ceph通過RGW實(shí)現(xiàn)對象存儲接口子眶,RGW兼容AWS S3 API,因此Pod可以和使用S3一樣使用Ceph RGW序芦,比如Python可以使用boto3 SDK對桶和對象進(jìn)行操作臭杰。

首先我們需要創(chuàng)建RGW網(wǎng)關(guān):

apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
  name: my-store
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: host
    replicated:
      size: 3
  dataPool:
    failureDomain: host
    erasureCoded:
      dataChunks: 2
      codingChunks: 1
  preservePoolsOnDelete: true
  gateway:
    type: s3
    sslCertificateRef:
    port: 80
    securePort:
    instances: 1

網(wǎng)關(guān)就緒后,我們就可以創(chuàng)建bucket了谚中,雖然bucket不是Volume渴杆,但Rook也把bucket抽象封裝為StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-bucket
provisioner: ceph.rook.io/bucket
reclaimPolicy: Delete
parameters:
  objectStoreName: my-store
  objectStoreNamespace: rook-ceph
  region: us-east-1

接下來就像聲明PVC一樣創(chuàng)建bucket了,不過不叫PVC宪塔,而是叫OBC(Object Bucket Claim)磁奖,

apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
  name: ceph-bucket
spec:
  generateBucketName: ceph-bkt
  storageClassName: rook-ceph-bucket

其中AK(access key)以及SK(secret key)保存在Secret ceph-bucket中,我們可以通過如下命令獲取:

export AWS_ACCESS_KEY_ID=$(kubectl -n default \
    get secret ceph-bucket -o yaml \
    | grep AWS_ACCESS_KEY_ID \
    | awk '{print $2}' | base64 --decode)
export AWS_SECRET_ACCESS_KEY=$(kubectl -n default \
    get secret ceph-bucket -o yaml \
    | grep AWS_SECRET_ACCESS_KEY \
    | awk '{print $2}' | base64 --decode)
# S3 Endpoint為Service rook-ceph-rgw-my-store地址
export AWS_ENDPOINT=http://$(kubectl get svc \
    -n rook-ceph \
    -l app=rook-ceph-rgw \
    -o jsonpath='{.items[0].spec.clusterIP}')

此時就可以使用s3命令進(jìn)行操作了:

image

如上我們通過aws s3 cp命令上傳了一個文本文件某筐,通過aws s3 ls命令我們發(fā)現(xiàn)文件已經(jīng)上傳成功比搭。

4.4 總結(jié)

通過Rook,我們幾乎不需要直接對Ceph進(jìn)行任何操作南誊,Rook實(shí)現(xiàn)了Ceph對象對應(yīng)的CRD身诺,集群部署、配置抄囚、資源供給等操作都能通過Kubernetes CR進(jìn)行聲明霉赡,借助Kubernetes的能力實(shí)現(xiàn)了Ceph集群的self-managing、self-scaling以及self-healing幔托。

5 總結(jié)

本文首先介紹了PV/PVC/Storageclass穴亏、Kubernetes存儲發(fā)展過程以及CAS存儲方案,然后分別介紹了目前比較主流的開源云原生分布式存儲Longhorn柑司、OpenEBS以及Rook迫肖,其中Longhorn比較簡單,并且提供了原生的WebUI攒驰,麻雀雖小五臟俱全。OpenEBS是CAS的開源實(shí)現(xiàn)方案故爵,支持Jiva玻粪、cStor以及LocalPV存儲后端隅津,Rook Ceph則實(shí)現(xiàn)通過Kubernetes管理和運(yùn)行Ceph集群。

網(wǎng)上有一篇文章Storage on Kubernetes: OpenEBS vs Rook (Ceph) vs Rancher Longhorn[10] 針對如上開源云原生存儲方案以及部分商業(yè)產(chǎn)品的性能使用fio進(jìn)行了測試劲室,供參考伦仍。

如下表格中綠色表示性能表現(xiàn)最好,紅色表示性能最差:

image

轉(zhuǎn)自 https://zhuanlan.zhihu.com/p/136352369

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末很洋,一起剝皮案震驚了整個濱河市充蓝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喉磁,老刑警劉巖谓苟,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異协怒,居然都是意外死亡涝焙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門孕暇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仑撞,“玉大人,你說我怎么就攤上這事妖滔∷硐” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵座舍,是天一觀的道長近迁。 經(jīng)常有香客問我,道長簸州,這世上最難降的妖魔是什么鉴竭? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮岸浑,結(jié)果婚禮上搏存,老公的妹妹穿的比我還像新娘。我一直安慰自己矢洲,他們只是感情好璧眠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著读虏,像睡著了一般责静。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盖桥,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天灾螃,我揣著相機(jī)與錄音,去河邊找鬼揩徊。 笑死腰鬼,一個胖子當(dāng)著我的面吹牛嵌赠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熄赡,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼姜挺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了彼硫?” 一聲冷哼從身側(cè)響起炊豪,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拧篮,沒想到半個月后词渤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡他托,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年掖肋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赏参。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡志笼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出把篓,到底是詐尸還是另有隱情纫溃,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布韧掩,位于F島的核電站紊浩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疗锐。R本人自食惡果不足惜坊谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滑臊。 院中可真熱鬧口芍,春花似錦、人聲如沸雇卷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽关划。三九已至小染,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贮折,已是汗流浹背裤翩。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脱货,地道東北人岛都。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓律姨,卻偏偏與公主長得像振峻,于是被迫代替她去往敵國和親臼疫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345